import streamlit as st import yfinance as yf import pandas as pd import plotly.graph_objects as go import plotly.express as px from datetime import datetime, timedelta import base64 # === Grundlegendes Design & Titel === st.set_page_config(page_title="JQuant - Marktanalyse", page_icon="📊", layout="wide") # Funktion zum Konvertieren des Bildes in Base64 def get_base64_image(image_path): with open(image_path, "rb") as img_file: return base64.b64encode(img_file.read()).decode() # Logo als Base64 einlesen image_base64 = get_base64_image("JQ.png") # Bild lokal im Space hochgeladen # CSS für fixiertes Logo OHNE Hintergrund st.markdown( f""" """, unsafe_allow_html=True ) # Standardwerte für das Datum: heute und ein Jahr davor default_end_date = datetime.today().date() default_start_date = default_end_date - timedelta(days=365) # Abstand nach unten hinzufügen, damit der Titel nicht direkt unter dem fixierten Logo ist st.markdown("


", unsafe_allow_html=True) # Titel der App st.title("📈 Interaktive Analyse von Aktienindizes") indices = { # 🌎 Globale Indizes #"MSCI World": "^MSCI", #"MSCI Emerging Markets": "EEM", # 🇺🇸 USA "S&P 500 (USA)": "^GSPC", "Nasdaq 100 (USA)": "^NDX", "Dow Jones Industrial Average (USA)": "^DJI", "Russell 2000 (USA)": "^RUT", # 🇪🇺 Europa "DAX (Deutschland)": "^GDAXI", "MDAX (Deutschland)": "^MDAXI", "CAC 40 (Frankreich)": "^FCHI", "FTSE 100 (UK)": "^FTSE", "EURO STOXX 50": "^STOXX50E", "AEX (Niederlande)": "^AEX", "IBEX 35 (Spanien)": "^IBEX", # 🇨🇭 Schweiz "SMI (Schweiz)": "^SSMI", # 🇨🇳 China "Shanghai Composite": "000001.SS", "Hang Seng (Hongkong)": "^HSI", # 🇯🇵 Japan "Nikkei 225": "^N225", "TOPIX": "^TOPX", # 🇮🇳 Indien "BSE Sensex": "^BSESN", "Nifty 50": "^NSEI", # 🇦🇺 Australien "ASX 200": "^AXJO", # 🇨🇦 Kanada "TSX Composite": "^GSPTSE", # 🇧🇷 Brasilien "Bovespa": "^BVSP" } # Dropdown für Ticker 1 & Ticker 2 ticker1_name = st.selectbox("📈 Wähle Index 1:", list(indices.keys()), index=0) ticker2_name = st.selectbox("📈 Wähle Index 2:", list(indices.keys()), index=1) # Die zugehörigen Ticker aus dem Dictionary holen ticker1 = indices[ticker1_name] ticker2 = indices[ticker2_name] # Benutzer kann das Datum weiterhin anpassen start_date = st.date_input("Startdatum", default_start_date, min_value=datetime(1900, 1, 1).date(), max_value=default_end_date) # Enddatum kann ebenfalls bis 1900 zurückgehen end_date = st.date_input("Enddatum", default_end_date, min_value=datetime(1900, 1, 1).date(), max_value=datetime.today().date()) # Farben für die Linien (fixe Farben für Konsistenz) color_ticker1 = "#1f77b4" # Blau color_ticker2 = "#ff7f0e" # Orange color_ticker3 = "#e377c2" # Pink # Button zum Laden der Daten if st.button("GO"): # Daten abrufen (Erst `Adj Close`, falls nicht verfügbar `Close`) data_dict = {} for name, symbol in [(ticker1_name, ticker1), (ticker2_name, ticker2)]: ticker = yf.Ticker(symbol) df = ticker.history(start=start_date, end=end_date) # 🕒 Datum als Index setzen & Uhrzeit entfernen df.index = pd.to_datetime(df.index).date # Konvertiert Timestamp zu Date ohne Uhrzeit # Prüfe, ob `Adj Close` existiert, falls nicht, nutze `Close` if "Adj Close" in df.columns and not df["Adj Close"].isna().all(): selected_data = df["Adj Close"] elif "Close" in df.columns and not df["Close"].isna().all(): selected_data = df["Close"] else: st.warning(f"⚠️ Keine gültigen Daten für {name} ({symbol}) gefunden!") continue # Falls keine Daten vorhanden sind, überspringen # 🔹 Doppelte Einträge pro Tag entfernen → den letzten Wert behalten selected_data = selected_data[~selected_data.index.duplicated(keep="last")] # Spaltennamen für spätere Zusammenführung setzen selected_data.name = name # Name des Index setzen # Speichere bereinigte Daten für den Index data_dict[name] = selected_data # Sicherstellen, dass beide DataFrames existieren if ticker1_name in data_dict and ticker2_name in data_dict: df1 = data_dict[ticker1_name].to_frame() df2 = data_dict[ticker2_name].to_frame() # 🔹 Beide DataFrames über gemeinsamen `Index (Datum)` zusammenführen df_indices = pd.merge(df1, df2, left_index=True, right_index=True, how="outer") # Fehlende Werte mit vorherigem Wert füllen df_indices.ffill(inplace=True) # Falls noch NaN-Werte übrig sind, entfernen df_indices.dropna(inplace=True) # Falls keine Daten geladen wurden, abbrechen if df_indices.empty: st.error("❌ Keine gemeinsamen Daten für die gewählten Indizes gefunden!") st.stop() # --------- 1. INTERAKTIVER CHART: Vergleich mit zwei Achsen --------- fig1 = go.Figure() # Linke Achse (Ticker 1) fig1.add_trace(go.Scatter( x=df_indices.index, y=df_indices[ticker1_name], mode='lines', name=ticker1_name, yaxis='y1', line=dict(color=color_ticker1))) # Rechte Achse (Ticker 2) fig1.add_trace(go.Scatter( x=df_indices.index, y=df_indices[ticker2_name], mode='lines', name=ticker2_name, yaxis='y2', line=dict(color=color_ticker2))) # Layout mit zwei Achsen definieren fig1.update_layout( title=f"Vergleich der Close-Preise: {ticker1_name} vs. {ticker2_name}", xaxis=dict(title="Datum"), yaxis=dict(title=f"{ticker1_name} Index", side='left', showgrid=False), yaxis2=dict(title=f"{ticker2_name} Index", side='right', overlaying='y', showgrid=False), legend=dict(x=0, y=1), hovermode="x" ) # Streamlit: Interaktives Chart anzeigen st.plotly_chart(fig1, use_container_width=True) # --------- 2. INTERAKTIVER CHART: Normierter Vergleich --------- df_normalized = df_indices / df_indices.iloc[0] * 100 # Rendite berechnen (% Veränderung von Start bis Ende) return_ticker1 = round(((df_indices[ticker1_name].iloc[-1] - df_indices[ticker1_name].iloc[0]) / df_indices[ticker1_name].iloc[0]) * 100, 2) return_ticker2 = round(((df_indices[ticker2_name].iloc[-1] - df_indices[ticker2_name].iloc[0]) / df_indices[ticker2_name].iloc[0]) * 100, 2) # Namen mit Rendite für die Legende anpassen legend_ticker1 = f"{ticker1_name} = {return_ticker1}%" legend_ticker2 = f"{ticker2_name} = {return_ticker2}%" # Diagramm 2 mit Renditen in der Legende anzeigen fig2 = px.line(df_normalized, x=df_normalized.index, y=df_normalized.columns, title="Normierter Vergleich der Close-Preise", labels={"value": "Index (Startwert = 100)", "variable": "Index"}, template="plotly_white", color_discrete_map={ticker1_name: color_ticker1, ticker2_name: color_ticker2}) # Legenden-Namen ändern fig2.for_each_trace(lambda t: t.update(name=legend_ticker1 if t.name == ticker1_name else legend_ticker2)) # Legendenposition anpassen (oben rechts, wie in Chart 1) fig2.update_layout(legend=dict(x=0, y=1)) # Streamlit: Interaktives Chart anzeigen st.plotly_chart(fig2, use_container_width=True) # --------- 3. INTERAKTIVER CHART: Relative Performance (Differenz der normierten Werte) --------- df_relative_performance = df_normalized[ticker1_name] - df_normalized[ticker2_name] # Letzter Wert der relativen Performance berechnen relative_performance_change = round(df_relative_performance.iloc[-1], 2) # Legenden-Text mit relativer Performance legend_relative = f"{ticker1_name} - {ticker2_name} = {relative_performance_change}%" # Chart für relative Performance erstellen fig3 = px.line(x=df_relative_performance.index, y=df_relative_performance, title=f"Relative Performance: {ticker1_name} vs. {ticker2_name}", labels={"x": "Datum", "y": "Relative Performance (%)"}, template="plotly_white") # Farbe der Linie auf die gleiche wie Index 1 setzen fig3.update_traces(line=dict(color=color_ticker3), name=legend_relative) # Legendenposition anpassen (oben rechts, wie in Chart 1 und 2) fig3.update_layout(legend=dict(x=0, y=1)) # Letzten Wert als Text direkt ins Chart einfügen fig3.add_annotation( x=df_relative_performance.index[-1], y=df_relative_performance.iloc[-1], text=f"{df_relative_performance.iloc[-1]:.2f}%", showarrow=True, arrowhead=2, font=dict(size=14, color=color_ticker3), ax=20, ay=-40 ) # Streamlit: Chart anzeigen st.plotly_chart(fig3, use_container_width=True) # Daten-Check: DataFrame anzeigen #st.subheader("📊 Rohdaten der ausgewählten Indizes") #st.dataframe(df_indices)