1- # run_weekly_report.py - Versione Semplificata
1+ # run_weekly_report.py - Versione Finale
22"""
3- Entry Point: Report Settimanale Portfolio (VERSIONE SEMPLIFICATA)
4- ================================================================
5-
6- Workflow semplice e diretto:
7- 1. Genera segnali da tutte le strategie disponibili
8- 2. Applica risk management per validare/raffinare i segnali
9- 3. Apre Google Sheet esistente nel drive
10- 4. Pulisce e scrive N+1 fogli:
11- - 1 foglio per strategia con segnali validati
12- - 1 foglio con snapshot portfolio della settimana
13- 5. Fine!
14-
15- Niente classi complesse, solo funzioni dirette.
3+ Entry Point: Report Settimanale Portfolio
4+ =========================================
5+
6+ Workflow semplice e automatico:
7+ 1. Scopre automaticamente tutte le strategie dal modulo strategies
8+ 2. Genera segnali per ogni strategia (parametri da config)
9+ 3. Apre Google Sheet WEEKLY_REPORTS_SHEET_ID
10+ 4. Cancella tutti i fogli esistenti
11+ 5. Crea N fogli strategia + 1 foglio portfolio snapshots (lun-ven)
12+ 6. Fine!
13+
14+ Eseguito ogni venerdì.
1615"""
1716
1817import logging
1918import os
2019import sys
20+ import inspect
2121from datetime import datetime , timedelta
2222import pandas as pd
2323
3636
3737
3838def main ():
39- """Entry point per generazione report settimanale semplificato ."""
39+ """Entry point per generazione report settimanale."""
4040 start_time = datetime .now ()
4141 today = start_time .strftime ("%Y-%m-%d" )
4242
4343 logger .info ("=" * 60 )
44- logger .info ("🚀 AVVIO REPORT SETTIMANALE SEMPLIFICATO " )
44+ logger .info ("🚀 AVVIO REPORT SETTIMANALE" )
4545 logger .info ("=" * 60 )
46+ logger .info (f"Data: { today } " )
4647
4748 try :
48- # Import dopo logging setup
4949 from scripts import database , portfolio , strategies , risk_manager , google_services
5050 from scripts import config
5151
52- # 1. CARICA PORTFOLIO
52+ # 1. SCOPRI STRATEGIE AUTOMATICAMENTE
53+ strategy_functions = _discover_strategy_functions ()
54+ logger .info (f"🎯 Strategie scoperte: { list (strategy_functions .keys ())} " )
55+
56+ # 2. CARICA PORTFOLIO DEMO
5357 logger .info ("📊 Caricamento portfolio 'demo'..." )
5458 pf = portfolio .Portfolio ("demo" , today )
55- logger .info (f"Portfolio caricato: valore={ pf .get_total_value ():,.2f} €, cash={ pf .get_cash_balance ():,.2f} €" )
56-
57- # 2. CARICA DATI UNIVERSE (ultima settimana)
58- week_ago = (datetime .now () - timedelta (days = 7 )).strftime ("%Y-%m-%d" )
59- logger .info (f"📈 Caricamento dati universe dal { week_ago } al { today } ..." )
60- df = database .get_universe_data (start_date = week_ago , end_date = today )
61- logger .info (f"Dati caricati: { len (df )} righe, { df ['ticker' ].nunique ()} ticker" )
59+ logger .info (f"Portfolio: valore=€{ pf .get_total_value ():,.2f} , cash=€{ pf .get_cash_balance ():,.2f} " )
6260
63- # 3. GENERA SEGNALI PER OGNI STRATEGIA
64- strategies_list = [
65- ("Moving_Average" , strategies .moving_average_crossover ),
66- ("RSI_Strategy" , strategies .rsi_strategy ),
67- ("Breakout_Strategy" , strategies .breakout_strategy )
68- ]
61+ # 3. CARICA DATI UNIVERSE (ultimi 60 giorni per sicurezza)
62+ days_ago = (datetime .now () - timedelta (days = 60 )).strftime ("%Y-%m-%d" )
63+ logger .info (f"📈 Caricamento dati universe dal { days_ago } ..." )
64+ df = database .get_universe_data (start_date = days_ago , end_date = today )
65+ logger .info (f"Dati: { len (df )} righe, { df ['ticker' ].nunique ()} ticker" )
6966
70- signals_data = {}
71- for strategy_name , strategy_fn in strategies_list :
67+ # 4. GENERA SEGNALI PER OGNI STRATEGIA
68+ all_signals = {}
69+ for strategy_name , strategy_fn in strategy_functions .items ():
7270 logger .info (f"🎯 Generazione segnali: { strategy_name } " )
7371
74- # Genera e raffina segnali
75- signals = risk_manager .generate_signals (strategy_fn , df , today , pf )
76-
77- # Converti in DataFrame per Google Sheets
78- signals_df = _convert_signals_to_dataframe (signals , strategy_name )
79- signals_data [strategy_name ] = signals_df
80-
81- buy_count = len (signals .get ("BUY" , {}))
82- sell_count = len (signals .get ("SELL" , {}))
83- hold_count = len (signals .get ("HOLD" , {}))
84- logger .info (f"Segnali { strategy_name } : BUY={ buy_count } , SELL={ sell_count } , HOLD={ hold_count } " )
72+ try :
73+ signals = risk_manager .generate_signals (strategy_fn , df , today , pf )
74+ signals_df = _convert_signals_to_dataframe (signals , strategy_name )
75+ all_signals [strategy_name ] = signals_df
76+
77+ buy_count = len (signals .get ("BUY" , {}))
78+ sell_count = len (signals .get ("SELL" , {}))
79+ hold_count = len (signals .get ("HOLD" , {}))
80+ logger .info (f"Segnali { strategy_name } : BUY={ buy_count } , SELL={ sell_count } , HOLD={ hold_count } " )
81+
82+ except Exception as e :
83+ logger .error (f"Errore strategia { strategy_name } : { e } " )
84+ # Crea DataFrame vuoto per non rompere il report
85+ all_signals [strategy_name ] = pd .DataFrame ()
8586
86- # 4 . GENERA SNAPSHOT PORTFOLIO SETTIMANALE
87- logger .info ("📸 Generazione snapshot portfolio settimanale ..." )
88- portfolio_df = _generate_portfolio_snapshots ( pf , week_ago , today )
87+ # 5 . GENERA PORTFOLIO SNAPSHOTS (lun-ven)
88+ logger .info ("📸 Caricamento portfolio snapshots settimana ..." )
89+ portfolio_df = _get_portfolio_snapshots_week ( portfolio_name , today )
8990
90- # 5 . SCRIVI GOOGLE SHEETS
91- logger .info ("📝 Scrittura Google Sheets ..." )
92- sheet_url = _write_to_google_sheets ( signals_data , portfolio_df , today )
91+ # 6 . SCRIVI GOOGLE SHEET
92+ logger .info ("📝 Scrittura Google Sheet ..." )
93+ sheet_url = _write_to_google_sheet ( all_signals , portfolio_df )
9394
9495 # SUCCESS
9596 duration = datetime .now () - start_time
9697 logger .info ("=" * 60 )
9798 logger .info ("✅ REPORT GENERATO CON SUCCESSO!" )
9899 logger .info ("=" * 60 )
100+ logger .info (f"Strategie processate: { len (all_signals )} " )
101+ logger .info (f"Portfolio snapshots: { len (portfolio_df )} giorni" )
99102 logger .info (f"Durata: { duration .total_seconds ():.1f} secondi" )
100103 logger .info (f"Google Sheet: { sheet_url } " )
101104 logger .info ("📊 REPORT PRONTO PER REVIEW!" )
@@ -106,6 +109,25 @@ def main():
106109 raise
107110
108111
112+ def _discover_strategy_functions ():
113+ """Scopre automaticamente tutte le funzioni strategia dal modulo strategies."""
114+ from scripts import strategies
115+
116+ strategy_functions = {}
117+
118+ # Scandisce tutti gli attributi del modulo strategies
119+ for name in dir (strategies ):
120+ obj = getattr (strategies , name )
121+
122+ # Controlla se è una funzione (non classe, non built-in, non privata)
123+ if (inspect .isfunction (obj ) and
124+ not name .startswith ('_' ) and
125+ obj .__module__ == strategies .__name__ ):
126+ strategy_functions [name ] = obj
127+
128+ return strategy_functions
129+
130+
109131def _convert_signals_to_dataframe (signals : dict , strategy_name : str ) -> pd .DataFrame :
110132 """Converte dict segnali in DataFrame per Google Sheets."""
111133 rows = []
@@ -114,21 +136,21 @@ def _convert_signals_to_dataframe(signals: dict, strategy_name: str) -> pd.DataF
114136 for ticker , data in signals .get ("BUY" , {}).items ():
115137 rows .append ({
116138 "Ticker" : ticker ,
117- "Signal" : "BUY" ,
139+ "Signal" : "BUY" ,
118140 "Strategy" : strategy_name ,
119141 "Size" : data .get ("size" , 0 ),
120142 "Price" : data .get ("price" , 0 ),
121143 "Stop_Loss" : data .get ("stop" , 0 ),
122144 "Risk_Amount" : data .get ("risk" , 0 ),
123- "Notes" : f"Size: { data .get ('size' , 0 )} shares"
145+ "Notes" : f"Size: { data .get ('size' , 0 )} shares at € { data . get ( 'price' , 0 ):.2f } "
124146 })
125147
126- # SELL signals
148+ # SELL signals
127149 for ticker , data in signals .get ("SELL" , {}).items ():
128150 rows .append ({
129151 "Ticker" : ticker ,
130152 "Signal" : "SELL" ,
131- "Strategy" : strategy_name ,
153+ "Strategy" : strategy_name ,
132154 "Size" : data .get ("quantity" , 0 ),
133155 "Price" : data .get ("price" , 0 ),
134156 "Stop_Loss" : "" ,
@@ -149,77 +171,103 @@ def _convert_signals_to_dataframe(signals: dict, strategy_name: str) -> pd.DataF
149171 "Notes" : data .get ("reason" , "Keep position" )
150172 })
151173
174+ if not rows :
175+ # Se non ci sono segnali, crea DataFrame con intestazioni vuote
176+ return pd .DataFrame (columns = ["Ticker" , "Signal" , "Strategy" , "Size" , "Price" ,
177+ "Stop_Loss" , "Risk_Amount" , "Notes" ])
178+
152179 return pd .DataFrame (rows )
153180
154181
155- def _generate_portfolio_snapshots ( pf , start_date : str , end_date : str ) -> pd .DataFrame :
156- """Genera snapshots portfolio per la settimana ."""
182+ def _get_portfolio_snapshots_week ( portfolio_name : str , today_str : str ) -> pd .DataFrame :
183+ """Recupera snapshots portfolio da lunedì a venerdì (today-5 giorni lavorativi) ."""
157184 from scripts import database
158185
159- # Query snapshot della settimana
186+ # Calcola lunedì della settimana corrente (oggi è venerdì)
187+ today = datetime .strptime (today_str , "%Y-%m-%d" )
188+ monday = today - timedelta (days = 4 ) # Venerdì - 4 giorni = Lunedì
189+
190+ start_date = monday .strftime ("%Y-%m-%d" )
191+ end_date = today_str
192+
193+ logger .info (f"Caricamento snapshots da { start_date } a { end_date } " )
194+
160195 query = """
161196 SELECT date, total_value, cash_balance, positions_count,
162- total_return_pct, max_drawdown_pct, volatility_pct, sharpe_ratio
163- FROM portfolio_snapshots
197+ total_return_pct, max_drawdown_pct, volatility_pct, sharpe_ratio, win_rate_pct
198+ FROM portfolio_snapshots
164199 WHERE portfolio_name = %s AND date BETWEEN %s AND %s
165200 ORDER BY date
166201 """
167202
168- rows , columns = database .execute_query (query , (pf .name , start_date , end_date ))
169- df = pd .DataFrame (rows , columns = columns )
170-
171- # Converti date in stringhe per Google Sheets
172- if not df .empty and 'date' in df .columns :
173- df ['date' ] = df ['date' ].astype (str )
174-
175- return df
203+ try :
204+ rows , columns = database .execute_query (query , (portfolio_name , start_date , end_date ))
205+ df = pd .DataFrame (rows , columns = columns )
206+
207+ # Converti date in stringhe per Google Sheets
208+ if not df .empty and 'date' in df .columns :
209+ df ['date' ] = df ['date' ].astype (str )
210+
211+ return df
212+
213+ except Exception as e :
214+ logger .error (f"Errore caricamento snapshots: { e } " )
215+ # Ritorna DataFrame vuoto con colonne corrette
216+ return pd .DataFrame (columns = ["date" , "total_value" , "cash_balance" , "positions_count" ,
217+ "total_return_pct" , "max_drawdown_pct" , "volatility_pct" ,
218+ "sharpe_ratio" , "win_rate_pct" ])
176219
177220
178- def _write_to_google_sheets ( signals_data : dict , portfolio_df : pd .DataFrame , date : str ) -> str :
179- """Scrive tutti i dati su Google Sheets e ritorna URL ."""
221+ def _write_to_google_sheet ( all_signals : dict , portfolio_df : pd .DataFrame ) -> str :
222+ """Scrive tutti i dati nel Google Sheet configurato ."""
180223 from scripts import google_services , config
181224
225+ logger .info ("Apertura Google Sheet..." )
182226 client = google_services .get_gsheet_client ()
183227
184- # Apri/crea spreadsheet per la settimana
185- sheet_name = f"Weekly_Report_{ date } "
228+ # Apri sheet configurato
229+ spreadsheet = client .open_by_key (config .WEEKLY_REPORTS_SHEET_ID )
230+ logger .info (f"Aperto sheet: { spreadsheet .title } " )
186231
187- try :
188- # Prova ad aprire sheet esistente nella cartella
189- # (questo richiede di implementare una funzione per cercare nella cartella)
190- # Per ora creiamo sempre nuovo sheet
191- spreadsheet = client .create (sheet_name )
192-
193- # Sposta nella cartella corretta se definita
194- if hasattr (config , 'WEEKLY_REPORTS_FOLDER_ID' ):
195- # Codice per spostare il file nella cartella...
196- pass
197-
198- except Exception as e :
199- logger .warning (f"Creazione nuovo sheet fallita: { e } " )
200- # Fallback: usa sheet di test
201- spreadsheet = client .open_by_key (config .TEST_SHEET_ID )
202-
203- # Pulisci tutti i worksheet esistenti (tranne il primo)
232+ # CANCELLA TUTTI I FOGLI ESISTENTI
204233 worksheets = spreadsheet .worksheets ()
205- for ws in worksheets [1 :]: # Mantieni il primo
206- spreadsheet .del_worksheet (ws )
234+ for ws in worksheets :
235+ try :
236+ spreadsheet .del_worksheet (ws )
237+ logger .info (f"Cancellato foglio: { ws .title } " )
238+ except Exception as e :
239+ logger .warning (f"Errore cancellando foglio { ws .title } : { e } " )
207240
208- # Rinomina il primo worksheet
209- main_ws = worksheets [ 0 ]
210- main_ws . update_title ( "Portfolio_Snapshot" )
241+ # CREA FOGLIO PORTFOLIO SNAPSHOTS
242+ logger . info ( "Creazione foglio Portfolio_Snapshots..." )
243+ portfolio_ws = spreadsheet . add_worksheet ( title = "Portfolio_Snapshots" , rows = 50 , cols = 15 )
211244
212- # Scrivi snapshot portfolio
213245 if not portfolio_df .empty :
214- main_ws .update ([portfolio_df .columns .tolist ()] + portfolio_df .values .tolist ())
246+ # Scrivi intestazioni + dati
247+ data_to_write = [portfolio_df .columns .tolist ()] + portfolio_df .values .tolist ()
248+ portfolio_ws .update (data_to_write )
249+ logger .info (f"Scritti { len (portfolio_df )} snapshots portfolio" )
250+ else :
251+ portfolio_ws .update ([["Nessun dato disponibile" ]])
252+ logger .warning ("Nessun snapshot portfolio disponibile" )
215253
216- # Crea worksheet per ogni strategia
217- for strategy_name , signals_df in signals_data .items ():
254+ # CREA FOGLIO PER OGNI STRATEGIA
255+ for strategy_name , signals_df in all_signals .items ():
256+ logger .info (f"Creazione foglio: { strategy_name } " )
257+
258+ # Crea worksheet
218259 ws = spreadsheet .add_worksheet (title = strategy_name , rows = 100 , cols = 10 )
260+
219261 if not signals_df .empty :
220- ws .update ([signals_df .columns .tolist ()] + signals_df .values .tolist ())
262+ # Scrivi intestazioni + dati
263+ data_to_write = [signals_df .columns .tolist ()] + signals_df .values .tolist ()
264+ ws .update (data_to_write )
265+ logger .info (f"Scritti { len (signals_df )} segnali per { strategy_name } " )
266+ else :
267+ ws .update ([["Nessun segnale generato" ]])
268+ logger .warning (f"Nessun segnale per strategia { strategy_name } " )
221269
222- logger .info (f"Google Sheet scritto : { spreadsheet . title } " )
270+ logger .info (f"✅ Google Sheet aggiornato : { len ( all_signals ) } strategie + 1 portfolio " )
223271 return spreadsheet .url
224272
225273
0 commit comments