fix(live): SH01 esegue shape-ML reale, non il wrapper squeeze scartato
Bug di wiring: runner.py avvolgeva lo sleeve SH01 nel MLWorkerWrapper legacy di multi_runner, che usa SignalEngine (famiglia squeeze ML01 SCARTATA), apre con Signal nudo ed esce a hold_bars=3 con tick() propria. Risultato: lo sleeve "SH01" del portafoglio live NON eseguiva SH01_shape_ml (generate_signals mai chiamata) e il fix horizon-exit era in un ramo morto -> SH01 continuava a chiudere a hold_limit/3. Fix: SH01 (kind="ml") gira come StrategyWorker normale. SH01_shape_ml. generate_signals fa il walk-forward internamente ad ogni tick ed emette metadata.max_bars=H=12 -> exit via StrategyWorker.tick (orizzonte H, fix applicato). Rimosso l'import/uso di MLWorkerWrapper e il blocco train esterno. ml_wf_entries ha train_min=4000 (>=4000 barre 1h per produrre segnali): aggiunto _ML_LOOKBACK_DAYS=365 cosi gli asset di sleeve ml fetchano >=365g (~8760 barre), senza dipendere dal fetch 440g di TSM01/ROT02. generate_signals su 365g: 0,17-0,24s (logit) -> trascurabile sul poll 60s. Test: test_build_ml_sh01_is_plain_strategyworker (StrategyWorker + strategy SH01_shape_ml + niente engine squeeze). Suite: 51 passed. Stato live: SH01 BTC/ETH flat -> contatori resettati (capitale preservato), trade squeeze archiviati. Rebuild+recreate: 14 worker RESUME puliti, healthy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+20
-14
@@ -2,9 +2,9 @@
|
||||
Riusa i worker esistenti come esecutori e il data layer Cerbero v2.
|
||||
|
||||
Worker per tipo di sleeve:
|
||||
single (fade/dip) -> StrategyWorker | ml (shape) -> MLWorkerWrapper(StrategyWorker)
|
||||
pairs -> PairsWorker (2 gambe) | basket (TR01) -> BasketTrendWorker
|
||||
rotation (ROT02) -> RotationWorker | tsmom (TSM01) -> TsmomWorker
|
||||
single (fade/dip) -> StrategyWorker | ml (shape, SH01) -> StrategyWorker (WF interno)
|
||||
pairs -> PairsWorker (2 gambe) | basket (TR01) -> BasketTrendWorker
|
||||
rotation (ROT02) -> RotationWorker | tsmom (TSM01) -> TsmomWorker
|
||||
|
||||
Feed: il runner fetcha candele 1h da Cerbero v2 e le RESAMPLA a 4h/1d (come get_df nel
|
||||
backtest) per i worker a cadenza piu' lenta. Il lookback per asset e' dimensionato sul
|
||||
@@ -22,7 +22,6 @@ from src.live.pairs_worker import PairsWorker
|
||||
from src.live.basket_trend_worker import BasketTrendWorker
|
||||
from src.live.rotation_worker import RotationWorker
|
||||
from src.live.tsmom_worker import TsmomWorker
|
||||
from src.live.multi_runner import MLWorkerWrapper
|
||||
from src.live.strategy_loader import load_strategy
|
||||
|
||||
# Codice-breve sleeve -> nome modulo Strategy in scripts/strategies/ (worker single/ml)
|
||||
@@ -36,6 +35,9 @@ DATA_DIR = Path("data/portfolio_paper")
|
||||
|
||||
# giorni di storia da fetchare per timeframe (TSM01 1d usa 252 barre -> ~440 giorni col buffer)
|
||||
_LOOKBACK_DAYS = {"1h": 90, "4h": 220, "1d": 440}
|
||||
# SH01 (ml) richiede >=4000 barre 1h (train_min di ml_wf_entries); 365g (~8760 barre) danno
|
||||
# margine ampio per il walk-forward. Difensivo: non dipende dal fetch 440g di TSM01/ROT02.
|
||||
_ML_LOOKBACK_DAYS = 365
|
||||
|
||||
|
||||
def build_worker_for(spec: SleeveSpec, alloc_capital: float, leverage: float,
|
||||
@@ -70,13 +72,16 @@ def build_worker_for(spec: SleeveSpec, alloc_capital: float, leverage: float,
|
||||
if module is None:
|
||||
raise ValueError(f"sleeve live non supportato: {spec.name} (kind={spec.kind})")
|
||||
strategy = load_strategy(module)
|
||||
worker = StrategyWorker(
|
||||
# SH01 (kind="ml") gira come StrategyWorker NORMALE: SH01_shape_ml.generate_signals fa il
|
||||
# walk-forward (retraining) internamente ad ogni tick ed emette metadata.max_bars=H -> gli
|
||||
# exit passano per StrategyWorker.tick (orizzonte H). NON usare il vecchio MLWorkerWrapper di
|
||||
# multi_runner: quello usa SignalEngine (famiglia squeeze SCARTATA), apre senza metadata ed
|
||||
# esce a hold_bars=3, ignorando del tutto SH01_shape_ml. Serve >=4000 barre 1h (train_min):
|
||||
# garantite da _ML_LOOKBACK_DAYS.
|
||||
return StrategyWorker(
|
||||
strategy=strategy, asset=spec.asset, tf=spec.tf, capital=alloc_capital,
|
||||
position_size=position_size, leverage=leverage, params=spec.params, data_dir=data_dir,
|
||||
)
|
||||
if spec.kind == "ml": # SH01: retraining periodico
|
||||
return MLWorkerWrapper(worker, {"retrain_hours": 24})
|
||||
return worker
|
||||
|
||||
|
||||
def _worker_equity(w) -> float:
|
||||
@@ -161,8 +166,11 @@ def run(config_path: str = "portfolios.yml"):
|
||||
asset_days: dict[str, int] = {}
|
||||
for s in live_specs:
|
||||
assets, tf = _spec_assets_tf(s)
|
||||
days = _LOOKBACK_DAYS.get(tf, 90)
|
||||
if s.kind == "ml": # SH01 ha bisogno di molta storia 1h
|
||||
days = max(days, _ML_LOOKBACK_DAYS)
|
||||
for a in assets:
|
||||
asset_days[a] = max(asset_days.get(a, 0), _LOOKBACK_DAYS.get(tf, 90))
|
||||
asset_days[a] = max(asset_days.get(a, 0), days)
|
||||
|
||||
inst_map = dict(INSTRUMENT_MAP)
|
||||
last_day = ""
|
||||
@@ -193,11 +201,9 @@ def run(config_path: str = "portfolios.yml"):
|
||||
elif s.kind in _MULTI_KINDS:
|
||||
w.tick(res)
|
||||
else:
|
||||
df = res[s.asset]
|
||||
inner = getattr(w, "worker", w)
|
||||
if hasattr(w, "needs_training") and w.needs_training():
|
||||
w.train(df, hold=inner.hold_bars)
|
||||
w.tick(df)
|
||||
# single (fade/dip) e ml (SH01): StrategyWorker. SH01 retraina dentro
|
||||
# generate_signals (walk-forward) -> nessun training esterno.
|
||||
w.tick(res[s.asset])
|
||||
|
||||
ledger.update_equity({sid: _worker_equity(wk) for sid, wk in workers.items()})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user