44import plotly .graph_objects as go
55from pathlib import Path
66import json
7- import subprocess
7+
8+ # IMPORT DIRETTO DEL SERVIZIO BACKEND
9+ from services .backtest import run_backtest_session
810from src .strategies import STRATEGY_MAP
911from src .settings_manager import SettingsManager
1012
1113st .set_page_config (page_title = "Backtest Lab" , page_icon = "🧪" , layout = "wide" )
1214st .title ("🧪 Backtest Laboratory" )
1315
1416# --- HELPER FUNCTIONS ---
17+
1518def load_benchmark_data (session_path : Path ):
1619 """
1720 Scansiona una cartella di sessione (TIMESTAMP) e carica i dati di tutte le strategie trovate.
1821 Ritorna:
19- - strategies_data: dict { 'StrategyName': {'equity': df, 'config': dict} }
22+ - strategies_data: dict { 'StrategyName': {'equity': df, 'config': dict, 'path': Path } }
2023 - comparison_df: DataFrame riassuntivo per la tabella
2124 """
2225 strategies_data = {}
@@ -84,12 +87,13 @@ def plot_comparison(strategies_data):
8487 return fig
8588
8689# --- SIDEBAR CONFIGURATION ---
90+
8791with st .sidebar :
8892 st .header ("⚙️ Configuration" )
8993
9094 mode = st .radio ("Run Mode" , ["Single Strategy" , "Benchmark (Run All)" ])
9195
92- # Carica parametri salvati per pre-compilare
96+ # Carica parametri salvati per pre-compilare i campi
9397 try :
9498 manager = SettingsManager ()
9599 saved_config = manager .load_config ()
@@ -102,85 +106,89 @@ def plot_comparison(strategies_data):
102106 if mode == "Single Strategy" :
103107 selected_strat = st .selectbox ("Select Strategy" , list (STRATEGY_MAP .keys ()))
104108
105- # Parametri override (UI semplificata per RSI/EMA)
109+ # Recupera default dal JSON salvato
106110 default_params = saved_config .get ("strategies_params" , {}).get (selected_strat , {})
111+
107112 st .subheader ("Parameters Override" )
113+ st .caption ("Modifica i parametri per questo test (non salva nel sistema)." )
108114
115+ # UI Dinamica per Override Parametri
116+ # I valori inseriti qui finiscono nel dizionario 'params'
109117 if selected_strat == "RSI" :
110118 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 ))
119+ params ['rsi_lower' ] = st .number_input ("Lower (<)" , value = default_params .get ('rsi_lower' , 30 ))
120+ params ['rsi_upper' ] = st .number_input ("Upper (>)" , value = default_params .get ('rsi_upper' , 70 ))
121+ params ['atr_period' ] = st .number_input ("ATR Period" , value = default_params .get ('atr_period' , 14 ))
122+
114123 elif selected_strat == "EMA" :
115124 params ['short_window' ] = st .number_input ("Fast EMA" , value = default_params .get ('short_window' , 50 ))
116125 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 ))
126+ params ['atr_period' ] = st .number_input ("ATR Period " , value = default_params .get ('atr_period' , 14 ))
118127
119128 st .markdown ("---" )
129+ st .subheader ("Simulation Settings" )
120130 initial_cap = st .number_input ("Initial Capital (€)" , value = 10000 , step = 1000 )
121131 years = st .slider ("History (Years)" , 1 , 5 , 2 )
122132
123133 run_btn = st .button ("🚀 RUN SIMULATION" , type = "primary" )
124134
125135# --- MAIN AREA ---
136+
126137tab_run , tab_results = st .tabs (["🚀 Run Simulation" , "📊 Results Analysis" ])
127138
139+ # TAB 1: ESECUZIONE
128140with tab_run :
129141 if run_btn :
130142 st .info ("Simulation started... please wait." )
131143 progress_bar = st .progress (0 )
132144
133145 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" ]
146+ session_path = ""
138147
148+ # CHIAMATA DIRETTA AL BACKEND
139149 if mode == "Benchmark (Run All)" :
140- cmd .append ("ALL" )
150+ # Esegue tutte le strategie usando i parametri nel JSON
151+ session_path = run_backtest_session (
152+ mode = "ALL" ,
153+ initial_capital = initial_cap ,
154+ years = years
155+ )
141156 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 )
157+ # Esegue singola strategia usando i parametri della Sidebar (params )
158+ session_path = run_backtest_session (
159+ mode = "SINGLE_OVERRIDE" ,
160+ override_strat_name = selected_strat ,
161+ override_params = params , # <--- Qui passiamo l'override!
162+ initial_capital = initial_cap ,
163+ years = years
164+ )
165+
151166 progress_bar .progress (100 )
152167
153- if process .returncode == 0 :
154- st .success ("Simulation completed successfully!" )
155-
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
168+ if session_path :
169+ st .success (f"Simulation completed! Path: { session_path } " )
170+ # Salviamo il path nella sessione per aprirlo automaticamente nel tab Results
171+ st .session_state ['last_run_path' ] = session_path
172+ st .rerun ()
163173 else :
164- st .error ("Error during execution." )
165- with st .expander ("Show Error Logs" ):
166- st .code (process .stderr )
167- st .code (process .stdout )
174+ st .error ("Simulation returned empty path. Check logs." )
168175
169176 except Exception as e :
170- st .error (f"Critical Error: { e } " )
177+ st .error (f"Critical Error during execution : { e } " )
171178
179+ # TAB 2: ANALISI RISULTATI
172180with tab_results :
173181 st .write ("### 📂 Results Browser" )
174182
175183 base_dir = Path ("data/backtests" )
176184 if not base_dir .exists ():
177- st .warning ("No backtests found." )
185+ st .warning ("No backtests found yet ." )
178186 st .stop ()
179187
180188 # Elenco sessioni (Timestamp) ordinate dalla più recente
181189 sessions = sorted ([x .name for x in base_dir .iterdir () if x .is_dir ()], reverse = True )
182190
183- # Se abbiamo appena finito una run, selezionala di default
191+ # Selettore Sessione (Default: l'ultima appena eseguita)
184192 default_idx = 0
185193 if 'last_run_path' in st .session_state :
186194 last_name = Path (st .session_state ['last_run_path' ]).name
@@ -191,22 +199,21 @@ def plot_comparison(strategies_data):
191199
192200 if selected_session :
193201 session_path = base_dir / selected_session
194- st .caption (f"Path : { session_path } " )
202+ st .caption (f"Viewing Data from : { session_path } " )
195203
196204 # CARICAMENTO DATI
197205 strat_data , summary_df = load_benchmark_data (session_path )
198206
199207 if not strat_data :
200- st .warning ("Empty session folder." )
208+ st .warning ("This session folder appears to be empty ." )
201209 else :
202- # 1. GRAFICO COMPARATIVO
210+ # 1. GRAFICO COMPARATIVO PLOTLY
203211 st .subheader ("📈 Equity Curve Comparison" )
204212 fig = plot_comparison (strat_data )
205213 st .plotly_chart (fig , use_container_width = True )
206214
207- # 2. TABELLA METRICHE
215+ # 2. TABELLA METRICHE RIASSUNTIVE
208216 st .subheader ("🏆 Performance Summary" )
209- # Formattazione colonne
210217 st .dataframe (
211218 summary_df .style .format ({
212219 "Final Equity" : "€ {:,.2f}" ,
@@ -219,24 +226,34 @@ def plot_comparison(strategies_data):
219226 st .markdown ("---" )
220227
221228 # 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 )
229+ st .subheader ("🔍 Deep Dive: Strategy Details" )
230+
231+ col_sel_strat , col_space = st .columns ([1 , 2 ])
232+ with col_sel_strat :
233+ strat_keys = list (strat_data .keys ())
234+ detail_strat = st .selectbox ("Inspect Strategy:" , strat_keys )
225235
226236 if detail_strat :
227237 d_data = strat_data [detail_strat ]
228238 d_path = d_data ['path' ]
229239
230240 c1 , c2 = st .columns ([1 , 1 ])
241+
231242 with c1 :
232243 st .write ("**Configuration Used:**" )
233244 st .json (d_data ['config' ].get ('params' , {}))
245+ st .write ("**Stats:**" )
246+ st .write (f"- Initial Capital: € { d_data ['config' ].get ('initial_capital' , 0 ):,.2f} " )
247+ st .write (f"- Final Equity: € { d_data ['config' ].get ('final_equity' , 0 ):,.2f} " )
234248
235249 with c2 :
236- st .write ("**Trade History:**" )
250+ st .write ("**Trade History (Top 50) :**" )
237251 trades_csv = d_path / "trades.csv"
238252 if trades_csv .exists ():
239253 df_trades = pd .read_csv (trades_csv )
240- st .dataframe (df_trades , height = 200 , use_container_width = True )
254+ if not df_trades .empty :
255+ st .dataframe (df_trades .head (50 ), height = 300 , use_container_width = True )
256+ else :
257+ st .info ("No trades executed." )
241258 else :
242- st .info ("No trades executed ." )
259+ st .info ("Trades file not found ." )
0 commit comments