Skip to content

Commit 9392d4a

Browse files
committed
fixed wekly report
1 parent 813119c commit 9392d4a

2 files changed

Lines changed: 148 additions & 100 deletions

File tree

run_weekly_report.py

Lines changed: 147 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
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

1817
import logging
1918
import os
2019
import sys
20+
import inspect
2121
from datetime import datetime, timedelta
2222
import pandas as pd
2323

@@ -36,66 +36,69 @@
3636

3737

3838
def 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+
109131
def _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

scripts/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
# Google Sheets IDs
2929
UNIVERSE_SPREADSHEET_ID = "1Uh3S3YCyvupZ5yZh2uDi0XYGaZIkEkupxsYed6xRxgA"
3030
WEEKLY_REPORTS_FOLDER_ID = "1pGSxyPjc8rotTFZp-HA-Xrl7yZHjDWDh"
31-
TEST_SHEET_ID = "1fGTT6O197auwyHGnEBKwvHm3oZwwWz9r8Nm4hz76jKM"
31+
WEEKLY_REPORTS_SHEET_ID = "1fGTT6O197auwyHGnEBKwvHm3oZwwWz9r8Nm4hz76jKM"
3232

3333
# Secret Manager secret names
3434
DB_SECRET_NAME = "db_info"

0 commit comments

Comments
 (0)