Skip to content

Commit 71ec9a4

Browse files
committed
added dashboard functionality
1 parent b90c189 commit 71ec9a4

3 files changed

Lines changed: 398 additions & 171 deletions

File tree

dashboard/home.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,29 @@
22
import streamlit as st
33
import pandas as pd
44
from dashboard.utils import load_portfolio_data, load_ohlc_summary
5+
from src.settings_manager import SettingsManager
56

67
st.set_page_config(page_title="Petunia Dashboard", page_icon="🌸", layout="wide")
78

89
st.title("🌸 Petunia | System Overview")
10+
11+
# --- 1. ACTIVE STRATEGY BADGE ---
12+
try:
13+
settings = SettingsManager()
14+
active_strat = settings.get_active_strategy_name()
15+
16+
# Usiamo un container colorato per evidenziare la strategia
17+
with st.container():
18+
c_badge, c_link = st.columns([3, 1])
19+
with c_badge:
20+
st.info(f"🧠 **Active Intelligence:** The system is currently running on **{active_strat}** strategy.")
21+
with c_link:
22+
# Purtroppo Streamlit non ha link interni facili, usiamo un testo guida
23+
st.caption("Change Strategy in:")
24+
st.page_link("pages/control_panel.py", label="🕹️ Control Panel", icon="🔧")
25+
except Exception as e:
26+
st.error(f"⚠️ Error loading configuration: {e}")
27+
928
st.markdown("---")
1029

1130
# 1. KPI Portafoglio

dashboard/pages/backtest_lab.py

Lines changed: 214 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,242 @@
11
import streamlit as st
22
import pandas as pd
3+
import plotly.express as px
4+
import plotly.graph_objects as go
35
from pathlib import Path
46
import json
5-
from services.backtest import run_backtest # Importiamo la funzione backend
7+
import subprocess
8+
from src.strategies import STRATEGY_MAP
9+
from src.settings_manager import SettingsManager
610

711
st.set_page_config(page_title="Backtest Lab", page_icon="🧪", layout="wide")
8-
912
st.title("🧪 Backtest Laboratory")
1013

11-
# Sidebar: Configurazione
14+
# --- HELPER FUNCTIONS ---
15+
def load_benchmark_data(session_path: Path):
16+
"""
17+
Scansiona una cartella di sessione (TIMESTAMP) e carica i dati di tutte le strategie trovate.
18+
Ritorna:
19+
- strategies_data: dict { 'StrategyName': {'equity': df, 'config': dict} }
20+
- comparison_df: DataFrame riassuntivo per la tabella
21+
"""
22+
strategies_data = {}
23+
summary_list = []
24+
25+
# Cerca tutte le sottocartelle (ogni sottocartella è una strategia)
26+
subdirs = [x for x in session_path.iterdir() if x.is_dir()]
27+
28+
for strat_dir in subdirs:
29+
strat_name = strat_dir.name
30+
31+
# Carica Equity Curve
32+
eq_path = strat_dir / "equity_curve.csv"
33+
conf_path = strat_dir / "config.json"
34+
35+
if eq_path.exists() and conf_path.exists():
36+
df_eq = pd.read_csv(eq_path)
37+
df_eq['date'] = pd.to_datetime(df_eq['date'])
38+
39+
with open(conf_path) as f:
40+
conf = json.load(f)
41+
42+
strategies_data[strat_name] = {
43+
"equity": df_eq,
44+
"config": conf,
45+
"path": strat_dir
46+
}
47+
48+
# Dati per la tabella riassuntiva
49+
initial = conf.get('initial_capital', 0)
50+
final = conf.get('final_equity', 0)
51+
roi = ((final - initial) / initial) * 100 if initial > 0 else 0
52+
53+
summary_list.append({
54+
"Strategy": strat_name,
55+
"Final Equity": final,
56+
"ROI %": roi,
57+
"Trades": conf.get('total_trades', 0),
58+
"Params": str(conf.get('params', {}))
59+
})
60+
61+
return strategies_data, pd.DataFrame(summary_list)
62+
63+
def plot_comparison(strategies_data):
64+
"""Genera un grafico Plotly unificato."""
65+
fig = go.Figure()
66+
67+
for name, data in strategies_data.items():
68+
df = data['equity']
69+
if not df.empty:
70+
fig.add_trace(go.Scatter(
71+
x=df['date'],
72+
y=df['equity'],
73+
mode='lines',
74+
name=name
75+
))
76+
77+
fig.update_layout(
78+
title="Equity Curve Comparison",
79+
xaxis_title="Date",
80+
yaxis_title="Capital (€)",
81+
hovermode="x unified",
82+
legend=dict(orientation="h", y=1.02, yanchor="bottom", x=1, xanchor="right")
83+
)
84+
return fig
85+
86+
# --- SIDEBAR CONFIGURATION ---
1287
with st.sidebar:
1388
st.header("⚙️ Configuration")
1489

15-
strategy = st.selectbox("Strategy", ["RSI", "MACD (Coming Soon)"])
16-
17-
st.subheader("Parameters")
18-
initial_cap = st.number_input("Initial Capital (€)", value=10000, step=1000)
19-
years = st.slider("Years History", 1, 5, 2)
90+
mode = st.radio("Run Mode", ["Single Strategy", "Benchmark (Run All)"])
2091

21-
# Parametri Dinamici per RSI
92+
# Carica parametri salvati per pre-compilare
93+
try:
94+
manager = SettingsManager()
95+
saved_config = manager.load_config()
96+
except:
97+
saved_config = {}
98+
99+
selected_strat = None
22100
params = {}
23-
if strategy == "RSI":
24-
params['rsi_period'] = st.slider("RSI Period", 5, 30, 14)
25-
params['rsi_lower'] = st.slider("Oversold (<)", 10, 45, 30)
26-
params['rsi_upper'] = st.slider("Overbought (>)", 55, 90, 70)
27-
params['atr_period'] = st.slider("ATR Period", 5, 30, 14)
28101

29-
run_btn = st.button("🚀 RUN BACKTEST", type="primary")
102+
if mode == "Single Strategy":
103+
selected_strat = st.selectbox("Select Strategy", list(STRATEGY_MAP.keys()))
104+
105+
# Parametri override (UI semplificata per RSI/EMA)
106+
default_params = saved_config.get("strategies_params", {}).get(selected_strat, {})
107+
st.subheader("Parameters Override")
108+
109+
if selected_strat == "RSI":
110+
params['rsi_period'] = st.number_input("RSI Period", value=default_params.get('rsi_period', 14))
111+
params['rsi_lower'] = st.number_input("Lower", value=default_params.get('rsi_lower', 30))
112+
params['rsi_upper'] = st.number_input("Upper", value=default_params.get('rsi_upper', 70))
113+
params['atr_period'] = st.number_input("ATR", value=default_params.get('atr_period', 14))
114+
elif selected_strat == "EMA":
115+
params['short_window'] = st.number_input("Fast EMA", value=default_params.get('short_window', 50))
116+
params['long_window'] = st.number_input("Slow EMA", value=default_params.get('long_window', 200))
117+
params['atr_period'] = st.number_input("ATR", value=default_params.get('atr_period', 14))
118+
119+
st.markdown("---")
120+
initial_cap = st.number_input("Initial Capital (€)", value=10000, step=1000)
121+
years = st.slider("History (Years)", 1, 5, 2)
122+
123+
run_btn = st.button("🚀 RUN SIMULATION", type="primary")
30124

31-
# Main Area
32-
tab_run, tab_history = st.tabs(["Current Run", "History Archive"])
125+
# --- MAIN AREA ---
126+
tab_run, tab_results = st.tabs(["🚀 Run Simulation", "📊 Results Analysis"])
33127

34128
with tab_run:
35129
if run_btn:
36-
with st.spinner("Running simulation... (This may take a moment)"):
37-
try:
38-
# Eseguiamo il backtest
39-
result_path = run_backtest(
40-
strategy_name=strategy,
41-
initial_capital=initial_cap,
42-
days_history=years*365,
43-
strategy_params=params
44-
)
45-
st.success(f"Simulation completed! Saved in: {result_path}")
46-
47-
# Visualizzazione Rapida Risultati
48-
p = Path(result_path)
49-
50-
# Immagine
51-
if (p / "chart.png").exists():
52-
# CORRETTO: use_container_width invece di width
53-
st.image(str(p / "chart.png"), use_container_width=True)
130+
st.info("Simulation started... please wait.")
131+
progress_bar = st.progress(0)
132+
133+
try:
134+
# Costruzione comando per subprocess
135+
# Usiamo subprocess per garantire che il backtest giri in un processo pulito
136+
# e per supportare facilmente la modalità 'ALL' gestita dal main() del backend.
137+
cmd = ["python", "-m", "services.backtest"]
138+
139+
if mode == "Benchmark (Run All)":
140+
cmd.append("ALL")
141+
else:
142+
cmd.append(selected_strat)
143+
# Nota: Per passare i parametri override al subprocess servirebbe un meccanismo CLI più complesso.
144+
# Per ora, in modalità Single, questo userà i parametri del JSON salvato se non modifichiamo services/backtest.py
145+
# PER ORA: Accettiamo che il 'Single' da UI legga dal JSON o implementiamo un fix rapido.
146+
# FIX RAPIDO: Per semplicità, in questa versione 'Single' usa i parametri salvati nel JSON.
147+
# Se vuoi l'override live, dovremmo passare i parametri come stringa JSON al comando CLI.
148+
149+
# Esecuzione
150+
process = subprocess.run(cmd, capture_output=True, text=True)
151+
progress_bar.progress(100)
152+
153+
if process.returncode == 0:
154+
st.success("Simulation completed successfully!")
54155

55-
# Metriche da JSON
56-
if (p / "config.json").exists():
57-
with open(p / "config.json") as f:
58-
res = json.load(f)
156+
# Parsing dell'output per trovare dove ha salvato i dati
157+
# Cerchiamo la riga "Cartella Sessione: ..." nei log
158+
for line in process.stdout.splitlines() + process.stderr.splitlines():
159+
if "Cartella Sessione:" in line:
160+
session_path_str = line.split("Cartella Sessione:")[-1].strip()
161+
st.session_state['last_run_path'] = session_path_str
162+
st.experimental_rerun() # Ricarica per mostrare i risultati nel tab Results
163+
else:
164+
st.error("Error during execution.")
165+
with st.expander("Show Error Logs"):
166+
st.code(process.stderr)
167+
st.code(process.stdout)
59168

60-
m1, m2, m3 = st.columns(3)
61-
m1.metric("Final Equity", f"€ {res.get('final_equity', 0):,.2f}")
62-
m2.metric("Total Trades", res.get('total_trades', 0))
63-
# Calcolo ROI al volo
64-
roi = ((res.get('final_equity', 0) - initial_cap) / initial_cap) * 100
65-
m3.metric("Total ROI", f"{roi:+.2f}%")
66-
67-
except Exception as e:
68-
st.error(f"Errore durante il backtest: {e}")
169+
except Exception as e:
170+
st.error(f"Critical Error: {e}")
69171

70-
with tab_history:
71-
st.write("📂 **Previous Runs Browser**")
72-
base_dir = Path("data/backtests")
172+
with tab_results:
173+
st.write("### 📂 Results Browser")
73174

74-
if base_dir.exists():
75-
# Logica per esplorare le cartelle
76-
strategies = [x.name for x in base_dir.iterdir() if x.is_dir()]
175+
base_dir = Path("data/backtests")
176+
if not base_dir.exists():
177+
st.warning("No backtests found.")
178+
st.stop()
77179

78-
# --- LAYOUT A COLONNE PER I SELETTORI ---
79-
c_strat, c_run = st.columns(2)
180+
# Elenco sessioni (Timestamp) ordinate dalla più recente
181+
sessions = sorted([x.name for x in base_dir.iterdir() if x.is_dir()], reverse=True)
182+
183+
# Se abbiamo appena finito una run, selezionala di default
184+
default_idx = 0
185+
if 'last_run_path' in st.session_state:
186+
last_name = Path(st.session_state['last_run_path']).name
187+
if last_name in sessions:
188+
default_idx = sessions.index(last_name)
189+
190+
selected_session = st.selectbox("Select Session (Timestamp)", sessions, index=default_idx)
191+
192+
if selected_session:
193+
session_path = base_dir / selected_session
194+
st.caption(f"Path: {session_path}")
80195

81-
with c_strat:
82-
sel_strat = st.selectbox("Select Strategy Folder", strategies)
196+
# CARICAMENTO DATI
197+
strat_data, summary_df = load_benchmark_data(session_path)
83198

84-
sel_run = None
85-
if sel_strat:
86-
with c_run:
87-
# Controllo se la cartella esiste e non è vuota
88-
strat_path = base_dir / sel_strat
89-
if strat_path.exists():
90-
runs = sorted([x.name for x in strat_path.iterdir()], reverse=True)
91-
sel_run = st.selectbox("Select Timestamp", runs)
199+
if not strat_data:
200+
st.warning("Empty session folder.")
201+
else:
202+
# 1. GRAFICO COMPARATIVO
203+
st.subheader("📈 Equity Curve Comparison")
204+
fig = plot_comparison(strat_data)
205+
st.plotly_chart(fig, use_container_width=True)
92206

93-
if sel_run:
94-
run_path = base_dir / sel_strat / sel_run
95-
st.caption(f"Path: {run_path}")
96-
st.markdown("---")
97-
98-
# Visualizzazione Dati
99-
col_img, col_data = st.columns([1, 1])
207+
# 2. TABELLA METRICHE
208+
st.subheader("🏆 Performance Summary")
209+
# Formattazione colonne
210+
st.dataframe(
211+
summary_df.style.format({
212+
"Final Equity": "€ {:,.2f}",
213+
"ROI %": "{:+.2f}%"
214+
}).background_gradient(subset=["ROI %"], cmap="RdYlGn"),
215+
use_container_width=True,
216+
hide_index=True
217+
)
218+
219+
st.markdown("---")
220+
221+
# 3. DETTAGLIO SINGOLA STRATEGIA
222+
st.subheader("🔍 Deep Dive: Single Strategy Details")
223+
strat_keys = list(strat_data.keys())
224+
detail_strat = st.selectbox("Inspect Strategy:", strat_keys)
225+
226+
if detail_strat:
227+
d_data = strat_data[detail_strat]
228+
d_path = d_data['path']
100229

101-
with col_img:
102-
if (run_path / "chart.png").exists():
103-
# CORRETTO: use_container_width
104-
st.image(str(run_path / "chart.png"), use_container_width=True)
230+
c1, c2 = st.columns([1, 1])
231+
with c1:
232+
st.write("**Configuration Used:**")
233+
st.json(d_data['config'].get('params', {}))
105234

106-
with col_data:
107-
if (run_path / "config.json").exists():
108-
with open(run_path / "config.json") as f:
109-
conf = json.load(f)
110-
st.json(conf, expanded=False)
111-
112-
if (run_path / "trades.csv").exists():
113-
# CORRETTO: use_container_width e height numerico
114-
st.dataframe(pd.read_csv(run_path / "trades.csv"), use_container_width=True, height=300)
235+
with c2:
236+
st.write("**Trade History:**")
237+
trades_csv = d_path / "trades.csv"
238+
if trades_csv.exists():
239+
df_trades = pd.read_csv(trades_csv)
240+
st.dataframe(df_trades, height=200, use_container_width=True)
241+
else:
242+
st.info("No trades executed.")

0 commit comments

Comments
 (0)