JQuant / app.py
JQ87's picture
Update app.py
feedbd9 verified
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"""
<style>
.fixed-logo {{
position: fixed;
top: 10px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
text-align: center;
}}
</style>
<div class="fixed-logo">
<img src="data:image/png;base64,{image_base64}" width="250">
</div>
""",
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("<br><br><br>", 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)