"""VALIDA GridWorker — replay live == 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. 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 """ from __future__ import annotations import shutil import sys import tempfile from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(PROJECT_ROOT)) from src.data.downloader import load_data from scripts.analysis.grid_game_gate import grid_mtm from scripts.analysis.ladder_search import regime_mask from src.live.grid_worker import GridWorker CFG = dict(tf="1h", range_down=0.20, range_up=0.06, levels=6, sl_buf=0.10, tp_buf=0.03, max_bars=720, regime="range", trend_max=1.5) ASSET, INIT, POS, LEV, FEE = "BTC", 1000.0, 0.15, 3.0, 0.0005 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}%)") wd = Path(tempfile.mkdtemp(prefix="gridval_")) try: # [A] un tick con tutta la storia w = GridWorker("GRID_BTC", ASSET, CFG, INIT, wd, leverage=LEV, position_size=POS, fee_side=FEE) 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'}") # [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'}") # [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.)") 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'}") finally: shutil.rmtree(wd, ignore_errors=True) if __name__ == "__main__": main()