feat(live): Stage 2 — GridWorker (Price Ladder) come PAPER sleeve nel runner
Wire del Price Ladder come sleeve PAPER (sim-only, fuori dal pool €500, NESSUN ordine reale): - runner: kind="grid" -> GridWorker in build_worker_for; _spec_assets_tf grid; tick branch (w.tick(res[asset])); meccanismo PAPER_EXTRA (sleeve paper letti da overrides.paper_extra, NON da _defs.py -> NON entrano nel backtest canonico/regression-lock: PORT06 resta 19 sleeve). Parsing difensivo (un errore non crasha il runner mainnet). Loop dati estesi a paper_extra. - GridWorker: bootstrap storia FULL (start fisso, come SH01) + mappatura capitale forward dal deploy (capital = initial*eq[-1]/base_norm) -> niente salti da finestra mobile; base_norm persistito (resume). grid_mtm robusto al df live (timestamp senza datetime; param df). - portfolios.yml: GRID_BTC in paper_extra (regime range1.5, rd0.20/ru0.06, L6, sl0.10/tp0.03, position_size 0.15 PINNATO). Gira in data/portfolio_paper_stats/GRID_BTC/. Validazione (validate_grid_worker.py): [A] logica n_trades==backtest, [B] forward-tracking esatto, [C] resume esatto. Dry-test integrazione runner: import OK, build OK, tick OK, pos 0.15. SICUREZZA: kind=grid mai eseguito reale (runner avvia ordini solo per single/ml); €500 intatti. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+29
-2
@@ -23,6 +23,7 @@ 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.xsec_worker import CrossSectionalWorker
|
||||
from src.live.grid_worker import GridWorker
|
||||
from src.live.strategy_loader import load_strategy
|
||||
|
||||
# Codice-breve sleeve -> nome modulo Strategy in scripts/strategies/ (worker single/ml)
|
||||
@@ -102,6 +103,13 @@ def build_worker_for(spec: SleeveSpec, alloc_capital: float, leverage: float,
|
||||
capital=alloc_capital, position_size=position_size, leverage=leverage,
|
||||
data_dir=data_dir,
|
||||
)
|
||||
if spec.kind == "grid":
|
||||
# Price Ladder (griglia) — SIM/PAPER (shadow stage 1): nessun ordine reale.
|
||||
return GridWorker(
|
||||
sid=spec.sid, asset=spec.asset, params=spec.params, capital=alloc_capital,
|
||||
work_dir=Path(data_dir) / spec.sid, leverage=leverage,
|
||||
position_size=position_size, fee_side=0.0005,
|
||||
)
|
||||
module = _STRAT_MODULE.get(spec.name)
|
||||
if module is None:
|
||||
raise ValueError(f"sleeve live non supportato: {spec.name} (kind={spec.kind})")
|
||||
@@ -191,6 +199,8 @@ def _spec_assets_tf(spec: SleeveSpec):
|
||||
return [spec.a, spec.b], spec.tf
|
||||
if spec.kind in _MULTI_KINDS:
|
||||
return list(spec.params["universe"]), spec.params.get("tf", "1d" if spec.kind != "basket" else "4h")
|
||||
if spec.kind == "grid":
|
||||
return [spec.asset], spec.params.get("tf", spec.tf)
|
||||
return [spec.asset], spec.tf
|
||||
|
||||
|
||||
@@ -375,6 +385,20 @@ def run(config_path: str = "portfolios.yml"):
|
||||
paper_codes = {str(c).upper() for c in (_ov.get("paper_sleeves") or [])}
|
||||
live_specs = [s for s in supported if s.name.upper() not in paper_codes]
|
||||
paper_specs = [s for s in supported if s.name.upper() in paper_codes]
|
||||
# PAPER_EXTRA: sleeve paper definiti SOLO in config (NON in p.sleeves) -> NON entrano
|
||||
# nel backtest canonico/regression-lock (all_sleeve_equities non sa costruirli). Nato
|
||||
# per il Price Ladder (kind=grid, shadow stage 1 sim). Parsing DIFENSIVO: un errore qui
|
||||
# non deve crashare il runner mainnet.
|
||||
for _ex in (_ov.get("paper_extra") or []):
|
||||
try:
|
||||
paper_specs.append(SleeveSpec(
|
||||
kind=str(_ex["kind"]), name=str(_ex.get("name", _ex["sid"])),
|
||||
sid=str(_ex["sid"]), asset=_ex.get("asset"),
|
||||
tf=str(_ex.get("tf", "1h")), params=dict(_ex.get("params", {})),
|
||||
cluster=str(_ex.get("cluster", "")),
|
||||
))
|
||||
except Exception as e:
|
||||
print(f"[runner] WARN paper_extra ignorato ({_ex}): {e}")
|
||||
if paper_specs:
|
||||
print(f"[runner] sleeve PAPER (solo statistica, fuori dal pool): "
|
||||
f"{[s.sid for s in paper_specs]}")
|
||||
@@ -467,7 +491,7 @@ def run(config_path: str = "portfolios.yml"):
|
||||
|
||||
# lookback (giorni) richiesto per ogni asset = max sui worker che lo usano
|
||||
asset_days: dict[str, int] = {}
|
||||
for s in supported: # live + PAPER (anche XS01/TR01/ROT02/TSM01)
|
||||
for s in live_specs + paper_specs: # live + PAPER (incl. paper_extra grid)
|
||||
assets, tf = _spec_assets_tf(s)
|
||||
days = _LOOKBACK_DAYS.get(tf, 90)
|
||||
if s.kind == "ml": # SH01 ha bisogno di molta storia 1h
|
||||
@@ -478,7 +502,7 @@ def run(config_path: str = "portfolios.yml"):
|
||||
# timeframe SUB-orari (es. pairs 15m, flat-skip): non resamplabili dal 1h ->
|
||||
# fetch DIRETTO da Cerbero per (asset, tf). Inerte se nessuno sleeve e' sub-orario.
|
||||
subhourly_needs: dict[tuple[str, str], int] = {}
|
||||
for s in supported: # live + paper
|
||||
for s in live_specs + paper_specs: # live + paper (incl. paper_extra grid)
|
||||
assets, tf = _spec_assets_tf(s)
|
||||
if tf in _SUBHOURLY:
|
||||
for a in assets:
|
||||
@@ -603,6 +627,9 @@ def run(config_path: str = "portfolios.yml"):
|
||||
# interno fitta solo l'ultimo blocco (last_block_only nei params).
|
||||
w.tick(_with_history(ml_hist.get(s.asset), res[s.asset],
|
||||
ml_gap_warned, s.asset))
|
||||
elif s.kind == "grid":
|
||||
# Price Ladder SIM/PAPER: ricomputa la griglia sul feed live (BTC 1h).
|
||||
w.tick(res[s.asset])
|
||||
else:
|
||||
# single (fade/dip): StrategyWorker su feed live.
|
||||
w.tick(res[s.asset])
|
||||
|
||||
Reference in New Issue
Block a user