Create app
Browse files
app
ADDED
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import yfinance as yf
|
3 |
+
import pandas as pd
|
4 |
+
import numpy as np
|
5 |
+
import matplotlib.pyplot as plt
|
6 |
+
from datetime import datetime, timedelta
|
7 |
+
|
8 |
+
# Funktionen für Berechnungen
|
9 |
+
def calculate_diversification_ratio(returns, weights):
|
10 |
+
cov_matrix = returns.cov() * 252
|
11 |
+
portfolio_variance = np.dot(weights.T, np.dot(cov_matrix, weights))
|
12 |
+
portfolio_std = np.sqrt(portfolio_variance)
|
13 |
+
individual_stds = returns.std() * np.sqrt(252)
|
14 |
+
weighted_avg_std = np.dot(weights, individual_stds)
|
15 |
+
return portfolio_std / weighted_avg_std if weighted_avg_std > 0 else np.nan
|
16 |
+
|
17 |
+
def calculate_average_correlation(returns):
|
18 |
+
corr_matrix = returns.corr()
|
19 |
+
n = corr_matrix.shape[0]
|
20 |
+
upper_triangle = corr_matrix.values[np.triu_indices(n, k=1)]
|
21 |
+
return np.mean(upper_triangle) if len(upper_triangle) > 0 else np.nan
|
22 |
+
|
23 |
+
# ETF-Liste mit Namen und Tickers
|
24 |
+
etf_options = {
|
25 |
+
"Keine Auswahl": "",
|
26 |
+
"iShares Core S&P 500 ETF": "IVV",
|
27 |
+
"iShares Core U.S. Aggregate Bond ETF": "AGG",
|
28 |
+
"Invesco DB Commodity Index Tracking Fund": "DBC",
|
29 |
+
"SPDR Gold Shares": "GLD",
|
30 |
+
"iShares MSCI EAFE ETF": "EFA",
|
31 |
+
"iShares J.P. Morgan USD Emerging Markets Bond ETF": "EMB"
|
32 |
+
}
|
33 |
+
|
34 |
+
# Streamlit App
|
35 |
+
st.title("Diversifikationsanalyse-Tool")
|
36 |
+
|
37 |
+
# Sidebar für Eingaben
|
38 |
+
st.sidebar.header("Eingabeparameter")
|
39 |
+
|
40 |
+
# Fünf separate Dropdowns für ETF-Auswahl
|
41 |
+
st.sidebar.subheader("ETF-Auswahl (2-5 ETFs)")
|
42 |
+
etf_choices = [
|
43 |
+
st.sidebar.selectbox(f"ETF {i+1}", options=list(etf_options.keys()), index=i if i < 4 else 0)
|
44 |
+
for i in range(5)
|
45 |
+
]
|
46 |
+
tickers = [etf_options[choice] for choice in etf_choices if etf_options[choice]] # Nur nicht-leere Auswahlen
|
47 |
+
tickers = list(dict.fromkeys(tickers)) # Entfernt Duplikate, falls vorhanden
|
48 |
+
|
49 |
+
if len(tickers) < 2:
|
50 |
+
st.sidebar.error("Bitte wählen Sie mindestens 2 verschiedene ETFs aus.")
|
51 |
+
st.stop()
|
52 |
+
|
53 |
+
# Rollfenster-Auswahl
|
54 |
+
rolling_options = {
|
55 |
+
"1 Jahr (252 Tage)": 252,
|
56 |
+
"3 Jahre (756 Tage)": 756,
|
57 |
+
"5 Jahre (1260 Tage)": 1260,
|
58 |
+
"Benutzerdefiniert": "custom"
|
59 |
+
}
|
60 |
+
rolling_choice = st.sidebar.selectbox("Rollfenster", list(rolling_options.keys()), index=1)
|
61 |
+
if rolling_choice == "Benutzerdefiniert":
|
62 |
+
rolling_window = st.sidebar.number_input("Rollfenster (Tage)", min_value=30, max_value=2520, value=756, step=1)
|
63 |
+
else:
|
64 |
+
rolling_window = rolling_options[rolling_choice]
|
65 |
+
|
66 |
+
# Datumsauswahl
|
67 |
+
st.sidebar.subheader("Zeitraum")
|
68 |
+
default_start = datetime(2008, 1, 1)
|
69 |
+
default_end = datetime(2025, 3, 15)
|
70 |
+
start_date = st.sidebar.date_input("Startdatum", value=default_start, min_value=datetime(2000, 1, 1), max_value=default_end)
|
71 |
+
end_date = st.sidebar.date_input("Enddatum", value=default_end, min_value=start_date, max_value=default_end)
|
72 |
+
|
73 |
+
# Daten laden und verarbeiten
|
74 |
+
if st.sidebar.button("Analyse starten"):
|
75 |
+
with st.spinner("Daten werden geladen und analysiert..."):
|
76 |
+
try:
|
77 |
+
data = yf.download(tickers, start=start_date, end=end_date, progress=False)
|
78 |
+
prices = data['Close'][tickers]
|
79 |
+
returns = prices.pct_change().dropna()
|
80 |
+
|
81 |
+
if len(returns) < rolling_window:
|
82 |
+
st.error(f"Zu wenig Datenpunkte ({len(returns)}) für das gewählte Rollfenster ({rolling_window} Tage).")
|
83 |
+
st.stop()
|
84 |
+
|
85 |
+
# Rollende Berechnungen
|
86 |
+
avg_correlations = []
|
87 |
+
div_ratios = []
|
88 |
+
dates = returns.index[rolling_window-1:]
|
89 |
+
weights = np.array([1/len(tickers)] * len(tickers)) # Gleiche Gewichte
|
90 |
+
|
91 |
+
for i in range(len(returns) - rolling_window + 1):
|
92 |
+
window_returns = returns.iloc[i:i + rolling_window]
|
93 |
+
avg_corr = calculate_average_correlation(window_returns)
|
94 |
+
div_ratio = calculate_diversification_ratio(window_returns, weights)
|
95 |
+
avg_correlations.append(avg_corr)
|
96 |
+
div_ratios.append(div_ratio)
|
97 |
+
|
98 |
+
# Ergebnisse speichern
|
99 |
+
results = pd.DataFrame({
|
100 |
+
'Date': dates,
|
101 |
+
'Average_Correlation': avg_correlations,
|
102 |
+
'Diversification_Ratio': div_ratios
|
103 |
+
}).set_index('Date')
|
104 |
+
|
105 |
+
# Ergebnisse anzeigen
|
106 |
+
st.subheader("Ergebnisse")
|
107 |
+
selected_etf_names = [key for key, value in etf_options.items() if value in tickers]
|
108 |
+
st.write(f"Analyse für ETFs: {', '.join(selected_etf_names)}")
|
109 |
+
st.write(f"Rollfenster: {rolling_window} Tage")
|
110 |
+
st.write(f"Zeitraum: {start_date} bis {end_date}")
|
111 |
+
|
112 |
+
# Letzte Werte
|
113 |
+
latest_date = results.index[-1]
|
114 |
+
latest_corr = results['Average_Correlation'].iloc[-1]
|
115 |
+
latest_div = results['Diversification_Ratio'].iloc[-1]
|
116 |
+
|
117 |
+
st.write(f"**Letzte Werte (Stand: {latest_date.date()})**")
|
118 |
+
st.write(f"Durchschnittliche Korrelation: {latest_corr:.3f}")
|
119 |
+
corr_status = "Grün (gut)" if latest_corr < 0.2 else "Gelb (moderat)" if latest_corr < 0.5 else "Rot (schlecht)"
|
120 |
+
st.write(f"Diversifikation: {corr_status}")
|
121 |
+
st.write(f"Diversifikationsverhältnis: {latest_div:.3f}")
|
122 |
+
div_status = "Gut" if latest_div < 1 else "Schlecht"
|
123 |
+
st.write(f"Diversifikationsverhältnis zeigt: {div_status}")
|
124 |
+
|
125 |
+
# Spezifische Interpretation der Ergebnisse
|
126 |
+
st.subheader("Interpretation Ihrer Ergebnisse")
|
127 |
+
st.write(f"""
|
128 |
+
**Was bedeutet das für Ihr Portfolio?**
|
129 |
+
- **Durchschnittliche Korrelation ({latest_corr:.3f})**:
|
130 |
+
Ein Wert von {latest_corr:.3f} zeigt, dass Ihre Anlageklassen {'sehr unabhängig' if latest_corr < 0.2 else 'moderat korreliert' if latest_corr < 0.5 else 'stark korreliert'} sind. {'Das bedeutet eine starke Risikoreduktion durch Diversifikation.' if latest_corr < 0.2 else 'Die Diversifikation wirkt, aber nicht optimal – es gibt Überschneidungen in den Bewegungen.' if latest_corr < 0.5 else 'Die Diversifikation ist schwach, da die Anlageklassen ähnlich reagieren.'}
|
131 |
+
{'Überlegen Sie, Anlageklassen mit geringerer Korrelation (z. B. Gold) zu erhöhen, wenn Sie die Diversifikation verbessern möchten.' if latest_corr >= 0.2 else ''}
|
132 |
+
|
133 |
+
- **Diversifikationsverhältnis ({latest_div:.3f})**:
|
134 |
+
Mit {latest_div:.3f} ist Ihr Portfolio {'weniger volatil' if latest_div < 1 else 'ebenso oder mehr volatil'} als die Summe der Einzelrisiken. {'Das zeigt, dass Ihre Diversifikation effektiv das Risiko reduziert.' if latest_div < 1 else 'Es gibt keinen Diversifikationsvorteil – überprüfen Sie Ihre Auswahl oder Gewichte.'}
|
135 |
+
{'Die Kombination aus stabileren (z. B. Anleihen) und volatileren (z. B. Rohstoffen) Anlageklassen hilft hier.' if latest_div < 1 else ''}
|
136 |
+
|
137 |
+
**Tipp**: Schauen Sie sich die Diagramme an, um Trends zu erkennen – steigende Korrelationen oder ein Verhältnis nahe 1 könnten auf nachlassende Diversifikation hinweisen.
|
138 |
+
""")
|
139 |
+
|
140 |
+
# Visualisierung
|
141 |
+
st.subheader("Visualisierung")
|
142 |
+
fig1, ax1 = plt.subplots(figsize=(10, 5))
|
143 |
+
ax1.plot(results.index, results['Average_Correlation'], label='Durchschnittliche Korrelation', color='blue')
|
144 |
+
ax1.axhline(y=0.2, color='green', linestyle='--', label='Grenze Grün (<0.2)')
|
145 |
+
ax1.axhline(y=0.5, color='red', linestyle='--', label='Grenze Rot (≥0.5)')
|
146 |
+
ax1.set_title(f'Rollende Durchschnittliche Korrelation ({rolling_window} Tage)')
|
147 |
+
ax1.set_xlabel('Datum')
|
148 |
+
ax1.set_ylabel('Korrelation')
|
149 |
+
ax1.legend()
|
150 |
+
ax1.grid(True)
|
151 |
+
st.pyplot(fig1)
|
152 |
+
|
153 |
+
fig2, ax2 = plt.subplots(figsize=(10, 5))
|
154 |
+
ax2.plot(results.index, results['Diversification_Ratio'], label='Diversifikationsverhältnis', color='orange')
|
155 |
+
ax2.axhline(y=1, color='black', linestyle='--', label='Grenze (1)')
|
156 |
+
ax2.set_title(f'Rollendes Diversifikationsverhältnis ({rolling_window} Tage)')
|
157 |
+
ax2.set_xlabel('Datum')
|
158 |
+
ax2.set_ylabel('Diversifikationsverhältnis')
|
159 |
+
ax2.legend()
|
160 |
+
ax2.grid(True)
|
161 |
+
st.pyplot(fig2)
|
162 |
+
|
163 |
+
# Allgemeine Interpretation unter den Charts
|
164 |
+
st.subheader("Was bedeuten die Indikatoren?")
|
165 |
+
st.write("""
|
166 |
+
**Durchschnittliche Korrelation**:
|
167 |
+
- Misst, wie stark die ausgewählten Anlageklassen (ETFs) im Durchschnitt zusammen bewegen.
|
168 |
+
- **Grün (<0,2)**: Sehr gute Diversifikation, die Anlageklassen bewegen sich unabhängig.
|
169 |
+
- **Gelb (0,2–0,5)**: Moderate Diversifikation, es gibt einige Überschneidungen.
|
170 |
+
- **Rot (≥0,5)**: Schlechte Diversifikation, die Anlageklassen bewegen sich ähnlich.
|
171 |
+
|
172 |
+
**Diversifikationsverhältnis**:
|
173 |
+
- Vergleicht die Risikoreduktion des Portfolios mit der Summe der Einzelrisiken.
|
174 |
+
- **Gut (<1)**: Das Portfolio ist weniger volatil als die Summe seiner Teile – Diversifikation wirkt.
|
175 |
+
- **Schlecht (≥1)**: Kein Diversifikationsvorteil, das Risiko wird nicht reduziert.
|
176 |
+
|
177 |
+
Eine rollende Analyse zeigt, wie sich diese Werte über die Zeit entwickeln. Nutzen Sie diese Informationen, um Ihr Portfolio zu optimieren, z. B. durch Anpassung der Gewichte oder Hinzufügen weiterer Anlageklassen.
|
178 |
+
""")
|
179 |
+
|
180 |
+
except Exception as e:
|
181 |
+
st.error(f"Fehler bei der Analyse: {str(e)}")
|
182 |
+
|
183 |
+
# Hinweise
|
184 |
+
st.sidebar.subheader("Hinweise")
|
185 |
+
st.sidebar.write("""
|
186 |
+
- Wählen Sie 2 bis 5 ETFs aus den Dropdown-Menüs.
|
187 |
+
- Das Rollfenster definiert die Analyseperiode (mindestens 30 Tage).
|
188 |
+
- Datenquelle: Yahoo Finance.
|
189 |
+
""")
|