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:
@@ -1,9 +1,11 @@
|
||||
"""VALIDA GridWorker — replay live == backtest grid_mtm (gate obbligatorio del progetto).
|
||||
"""VALIDA GridWorker — forward-tracking == backtest grid_mtm (gate obbligatorio del progetto).
|
||||
|
||||
Confronta il GridWorker (sim/paper, src/live/grid_worker.py) col motore canonico grid_mtm:
|
||||
[A] un tick con tutta la storia -> capital == initial * grid_mtm(full).equity[-1] (parita');
|
||||
[B] replay INCREMENTALE (tick su finestra che cresce) -> converge allo stesso capitale finale;
|
||||
[C] resume: reistanzia il worker (rilegge status.json) -> capitale persistito.
|
||||
Il GridWorker (sim/paper) bootstrappa la storia FULL (start fisso) e mappa il capitale forward
|
||||
dal deploy: capital = initial * eq[-1]/base_norm. Proprieta' da validare:
|
||||
[A] LOGICA: la griglia sul feed full == backtest (stesso n_trades/win).
|
||||
[B] FORWARD: deployato a T0 (bootstrap up to T0), dopo aver ticcato fino a T1 il capitale
|
||||
e' initial * eq_full(T1)/eq_full(T0) — cioe' il ritorno della griglia DA T0 (causale).
|
||||
[C] RESUME: reistanziato (rilegge base_norm da status.json) -> stesso capitale.
|
||||
Config = la finale del re-gate pulito: BTC 1h range1.5 rd0.20 ru0.06 L6 sl0.10 tp0.03.
|
||||
|
||||
uv run python scripts/analysis/validate_grid_worker.py
|
||||
@@ -28,50 +30,58 @@ CFG = dict(tf="1h", range_down=0.20, range_up=0.06, levels=6,
|
||||
ASSET, INIT, POS, LEV, FEE = "BTC", 1000.0, 0.15, 3.0, 0.0005
|
||||
|
||||
|
||||
def _eq_last(df, mask):
|
||||
cfg_bt = {k: v for k, v in CFG.items() if k not in ("regime", "trend_max")}
|
||||
eqd, st = grid_mtm(ASSET, **cfg_bt, pos=POS, lev=LEV, fee_side=FEE, deploy_mask=mask, df=df)
|
||||
return float(eqd.iloc[-1]), st
|
||||
|
||||
|
||||
def main():
|
||||
df = load_data(ASSET, "1h")
|
||||
# backtest canonico
|
||||
mask = regime_mask(ASSET, "1h", trend_max=CFG["trend_max"])
|
||||
cfg_bt = {k: v for k, v in CFG.items() if k not in ("regime", "trend_max")}
|
||||
eqd_bt, st_bt = grid_mtm(ASSET, **cfg_bt, pos=POS, lev=LEV, fee_side=FEE, deploy_mask=mask)
|
||||
target_cap = INIT * float(eqd_bt.iloc[-1])
|
||||
print(f"[backtest] grid_mtm equity[-1]={eqd_bt.iloc[-1]:.6f} -> capital {target_cap:,.2f} "
|
||||
f"(trades {st_bt['trades']}, win {st_bt['win']:.1f}%)")
|
||||
n = len(df)
|
||||
nboot = n - 400 # deploy a T0 = nboot, poi 400 barre forward
|
||||
mask_full = regime_mask(ASSET, "1h", trend_max=CFG["trend_max"])
|
||||
|
||||
F, st_full = _eq_last(df, mask_full) # eq full @ T1 (== backtest)
|
||||
B, _ = _eq_last(df.iloc[:nboot].reset_index(drop=True), mask_full[:nboot]) # eq @ T0 (causale)
|
||||
expected = INIT * F / B
|
||||
print(f"[backtest] eq@T0={B:.4f} eq@T1={F:.4f} -> capitale forward atteso {expected:,.2f} "
|
||||
f"(trades full {st_full['trades']}, win {st_full['win']:.1f}%)")
|
||||
|
||||
wd = Path(tempfile.mkdtemp(prefix="gridval_"))
|
||||
try:
|
||||
# [A] un tick con tutta la storia
|
||||
boot = df.iloc[:nboot].reset_index(drop=True)
|
||||
w = GridWorker("GRID_BTC", ASSET, CFG, INIT, wd, leverage=LEV,
|
||||
position_size=POS, fee_side=FEE)
|
||||
position_size=POS, fee_side=FEE, hist=boot)
|
||||
# [A] logica: tick col feed full -> n_trades come backtest
|
||||
w.tick(df)
|
||||
dA = abs(w.capital - target_cap)
|
||||
print(f"[A] full-tick capital {w.capital:,.2f} delta {dA:.6f} "
|
||||
f"{'OK' if dA < 1e-6 else 'MISMATCH'}")
|
||||
dA = abs(w.n_trades - st_full["trades"])
|
||||
print(f"[A] logica n_trades worker {w.n_trades} vs backtest {st_full['trades']} "
|
||||
f"{'OK' if dA == 0 else 'MISMATCH'}")
|
||||
|
||||
# [B] replay incrementale (ultimi 3 tick su finestra crescente)
|
||||
n = len(df)
|
||||
caps = []
|
||||
for end in (n - 200, n - 50, n):
|
||||
wd2 = Path(tempfile.mkdtemp(prefix="gridval_inc_"))
|
||||
wi = GridWorker("GRID_BTC", ASSET, CFG, INIT, wd2, leverage=LEV,
|
||||
position_size=POS, fee_side=FEE)
|
||||
wi.tick(df.iloc[:end].reset_index(drop=True))
|
||||
caps.append(wi.capital)
|
||||
shutil.rmtree(wd2, ignore_errors=True)
|
||||
dB = abs(caps[-1] - target_cap)
|
||||
print(f"[B] incrementale capitali {[round(c,2) for c in caps]} "
|
||||
f"finale delta {dB:.6f} {'OK' if dB < 1e-6 else 'MISMATCH'}")
|
||||
# [B] forward: deploy a T0 (base), poi fino a T1
|
||||
wd2 = Path(tempfile.mkdtemp(prefix="gridval_fwd_"))
|
||||
wf = GridWorker("GRID_BTC", ASSET, CFG, INIT, wd2, leverage=LEV,
|
||||
position_size=POS, fee_side=FEE, hist=boot)
|
||||
wf.tick(boot) # deploy: base_norm=B, capital=initial
|
||||
cap0 = wf.capital
|
||||
wf.tick(df.iloc[:n - 200].reset_index(drop=True))
|
||||
wf.tick(df) # fino a T1
|
||||
dB = abs(wf.capital - expected)
|
||||
print(f"[B] forward: cap@deploy {cap0:,.2f} (atteso {INIT:,.0f}) cap@T1 {wf.capital:,.2f} "
|
||||
f"(atteso {expected:,.2f}) delta {dB:.4f} {'OK' if dB < 0.05 else 'MISMATCH'}")
|
||||
|
||||
# [C] resume
|
||||
w2 = GridWorker("GRID_BTC", ASSET, CFG, INIT, wd, leverage=LEV,
|
||||
position_size=POS, fee_side=FEE)
|
||||
dC = abs(w2.capital - w.capital)
|
||||
# status.json persiste capital a 4 decimali -> tolleranza = precisione di persistenza
|
||||
print(f"[C] resume capital {w2.capital:,.2f} delta {dC:.6f} "
|
||||
f"{'OK' if dC < 1e-3 else 'MISMATCH'} (persistenza 4 dec.)")
|
||||
wr = GridWorker("GRID_BTC", ASSET, CFG, INIT, wd2, leverage=LEV,
|
||||
position_size=POS, fee_side=FEE, hist=boot)
|
||||
wr.tick(df)
|
||||
dC = abs(wr.capital - wf.capital)
|
||||
print(f"[C] resume cap {wr.capital:,.2f} delta {dC:.4f} "
|
||||
f"{'OK' if dC < 0.05 else 'MISMATCH'} (base_norm persistito)")
|
||||
|
||||
ok = dA < 1e-6 and dB < 1e-6 and dC < 1e-3
|
||||
print(f"\n{'VALIDAZIONE OK: GridWorker replay == backtest' if ok else 'VALIDAZIONE FALLITA'}")
|
||||
ok = dA == 0 and dB < 0.05 and dC < 0.05
|
||||
print(f"\n{'VALIDAZIONE OK: GridWorker forward-tracking == backtest' if ok else 'VALIDAZIONE FALLITA'}")
|
||||
shutil.rmtree(wd2, ignore_errors=True)
|
||||
finally:
|
||||
shutil.rmtree(wd, ignore_errors=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user