Skip to content

Commit 4521521

Browse files
committed
feat(core): refactor weekly
1 parent 1dc3be4 commit 4521521

2 files changed

Lines changed: 86 additions & 25 deletions

File tree

services/weekly_run.py

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,77 +6,111 @@
66
from src.yfinance_manager import YFinanceManager
77
from src.drive_manager import DriveManager
88
from src.risk_manager import RiskManager
9+
from src.settings_manager import SettingsManager
910
from src.logger import get_logger
10-
from src.strategies import StrategyRSI, StrategyEMA
11+
12+
# Importiamo SOLO il Factory Method, non le classi specifiche
13+
from src.strategies import get_strategy
1114

1215
logger = get_logger("WeeklyRun")
1316

1417
def main():
1518
logger.info("📅 Inizio Weekly Run (Strategy & Risk Analysis)...")
1619

17-
# 1. INIT
18-
db = DatabaseManager()
19-
pm = PortfolioManager()
20-
yf = YFinanceManager()
21-
dm = DriveManager()
20+
# 1. INIT MANAGERS
21+
try:
22+
db = DatabaseManager()
23+
pm = PortfolioManager()
24+
yf = YFinanceManager()
25+
dm = DriveManager()
26+
settings = SettingsManager() # Carica config/strategies.json
27+
except Exception as e:
28+
logger.critical(f"Errore inizializzazione manager: {e}")
29+
return
2230

23-
# Config Strategia
24-
strategy = StrategyRSI(rsi_period=14, rsi_lower=30, rsi_upper=70)
31+
# 2. CONFIGURAZIONE DINAMICA STRATEGIA
32+
try:
33+
# Leggiamo dal JSON quale strategia usare
34+
active_strat_name = settings.get_active_strategy_name()
35+
strat_params = settings.get_strategy_params(active_strat_name)
36+
37+
logger.info(f"⚙️ Strategia Attiva: {active_strat_name}")
38+
logger.info(f"🔧 Parametri: {strat_params}")
39+
40+
# FACTORY PATTERN: Istanziamo la classe corretta al volo
41+
strategy = get_strategy(active_strat_name, **strat_params)
42+
43+
except Exception as e:
44+
logger.critical(f"Errore caricamento strategia da config: {e}")
45+
return
46+
47+
# Inizializziamo Risk Manager (NB: potremmo parametrizzare anche questo in futuro)
2548
risk_manager = RiskManager(risk_per_trade=0.02, stop_atr_multiplier=2.0)
2649

27-
# 2. LOAD STATE
28-
pm.load_from_db(db.load_portfolio())
29-
current_equity = pm.get_total_equity()
30-
logger.info(f"Equity Iniziale: {current_equity:.2f}")
50+
# 3. LOAD STATE
51+
try:
52+
pm.load_from_db(db.load_portfolio())
53+
current_equity = pm.get_total_equity()
54+
logger.info(f"💰 Equity Iniziale: {current_equity:.2f} €")
55+
except Exception as e:
56+
logger.error(f"Errore caricamento portfolio: {e}")
57+
return
3158

32-
# 3. DATA FETCH (Universe + Portfolio)
59+
# 4. DATA FETCH (Universe + Portfolio)
3360
universe_tkr = dm.get_universe_tickers()
3461
portfolio_tkr = pm.df_portfolio["ticker"].tolist()
3562
all_tickers = list(set(universe_tkr + portfolio_tkr))
3663

64+
if not all_tickers:
65+
logger.warning("Nessun ticker da analizzare.")
66+
return
67+
3768
logger.info(f"Aggiornamento dati per {len(all_tickers)} ticker...")
69+
# Scarichiamo dati recenti per aggiornare il DB
3870
new_data = yf.fetch_ohlc(all_tickers, days=5)
3971
db.upsert_ohlc(new_data)
4072

41-
# 4. STRATEGY ENGINE
42-
logger.info("Recupero storico e calcolo strategia...")
73+
# 5. STRATEGY ENGINE
74+
# Per la strategia servono dati storici profondi (es. EMA 200 richiede >200 giorni)
75+
logger.info("Recupero storico e calcolo segnali...")
4376
historical_data = db.get_ohlc_all_tickers(days=365)
4477

4578
if not historical_data:
46-
logger.error("Nessun dato storico trovato.")
79+
logger.error("Nessun dato storico trovato nel DB.")
4780
return
4881

82+
# Calcolo della strategia dinamica
4983
df_signals = strategy.compute(historical_data)
5084

51-
# Filtro ultimo segnale (Venerdì)
85+
# Filtro ultimo segnale (Venerdì/Oggi)
5286
if df_signals.empty:
87+
logger.info("Nessun segnale generato dalla strategia.")
5388
latest_signals = pd.DataFrame()
5489
else:
5590
last_date = df_signals['date'].max()
5691
latest_signals = df_signals[df_signals['date'] == last_date]
57-
logger.info(f"Analisi segnali per data: {last_date}")
92+
logger.info(f"Analisi segnali per data: {last_date.date()}")
5893

59-
# 5. RISK MANAGER
94+
# 6. RISK MANAGER & ORDER GENERATION
6095
pos_dict = dict(zip(pm.df_portfolio['ticker'], pm.df_portfolio['size']))
6196
available_cash = float(pm.df_cash.iloc[0]['cash']) if not pm.df_cash.empty else 0.0
6297

63-
logger.info("Valutazione Rischio e Generazione Ordini...")
98+
logger.info("Valutazione Rischio...")
6499
orders = risk_manager.evaluate(
65100
signals_df=latest_signals,
66101
total_equity=current_equity,
67102
available_cash=available_cash,
68103
current_positions=pos_dict
69104
)
70105

71-
# 6. OUTPUT -> GOOGLE SHEETS
106+
# 7. OUTPUT -> GOOGLE SHEETS
72107
if orders:
73108
logger.info(f"✅ Generati {len(orders)} ordini operativi.")
74109
for o in orders:
75-
logger.info(f" -> {o['action']} {o['ticker']} Qty:{o['quantity']} @ {o['price']}")
110+
logger.info(f" -> {o['action']} {o['ticker']} Qty:{o['quantity']} @ {o['price']:.2f}")
76111
else:
77-
logger.info("😴 Nessun ordine generato.")
112+
logger.info("😴 Nessun ordine generato (Risk Manager o Nessun Segnale).")
78113

79-
# SALVATAGGIO SUL CLOUD
80114
# Questo sovrascrive il tab "Orders" nel tuo Report Sheet
81115
dm.save_pending_orders(orders)
82116

src/strategies/__init__.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,31 @@
22
from .rsi import StrategyRSI
33
from .ema import StrategyEMA
44

5-
__all__ = ["StrategyBase", "StrategyRSI", "StrategyEMA"]
5+
# Mappa dei nomi strategia alle classi
6+
STRATEGY_MAP = {
7+
"RSI": StrategyRSI,
8+
"EMA": StrategyEMA
9+
# Qui aggiungerai le future strategie (es. "BOLLINGER": StrategyBollinger)
10+
}
11+
12+
def get_strategy(strategy_name: str, **kwargs) -> StrategyBase:
13+
"""
14+
Factory Method: Istanzia e restituisce la strategia richiesta.
15+
16+
Args:
17+
strategy_name (str): Nome della strategia (es. "RSI", "EMA").
18+
**kwargs: Parametri specifici (es. rsi_period=14, short_window=50).
19+
20+
Returns:
21+
StrategyBase: L'istanza della strategia configurata.
22+
23+
Raises:
24+
ValueError: Se il nome della strategia non è supportato.
25+
"""
26+
if strategy_name not in STRATEGY_MAP:
27+
raise ValueError(f"Strategia '{strategy_name}' non trovata. Disponibili: {list(STRATEGY_MAP.keys())}")
28+
29+
strategy_class = STRATEGY_MAP[strategy_name]
30+
31+
# Istanziamo la classe passando i parametri spacchettati
32+
return strategy_class(**kwargs)

0 commit comments

Comments
 (0)