Files
PythagorasGoal/docs/diary/2026-06-11-stability-sweep.md
T
Adriano Dal Pastro 9d15506b05 feat(stability): sweep stabilità — fix TR01 mean(rets), XS01 phase-tranching K=3, z-stop pairs bocciato
Audit anti-overfit su tutte le 19 sleeve (diario 2026-06-11-stability-sweep.md):

- FIX BasketTrendWorker: mean(rets) sui soli asset in posizione sovrappesava N/k
  a paniere parziale (1 long = 0.45 del capitale invece di 0.09) -> replay -44%
  vs ref +42%. Ora sum(rets)/N (convenzione canonica 1/N): replay +32% vs +42%
  (residuo = convenzione dichiarata). Solo statistica PAPER.
- XS01 PHASE-TRANCHING (gate xs01_tranche_gate: plateau K=2 E K=3 promossi,
  PORT06 OOS Sh 10.07->10.15 DD 1.48->1.38, FULL pari): la fase del roll e'
  timing-luck (Sharpe daily 1.52-2.33, DD 13.8-33% sulle 12 fasi). Worker con
  param tranches (default 1), 3 sub-book sfasati hold/3 su capitale comune,
  migrazione status legacy, last_bar_ts solo-avanti; runner forward del param;
  _defs tranches=3; hourly_report aggrega i sub-book; validatore esteso e
  PASSATO (K=1 == xsec_sim esatto, K=3 == unione fasi esatto).
- Disaster-cap z sui pairs: pre-registrato e BOCCIATO su tutti i criteri (coda
  OOS peggiora 4/6 coppie, Sharpe -10..-49%, plateau solo del danno; 5a conferma
  stop-su-MR). Record pairs_zstop_research.py; pairs restano senza stop.
- Audit drift: regression-lock trendmax OK (parita' 1.00000, plateau 2.5/3.0/3.5
  confermato), correlazioni cross-famiglia ~0 invariate; PORT06 rolling al
  19-28mo pct (normale) ma FADE 120g al 2o percentile storico -> monitor in TODO
  (nessun ritocco parametri).
- TODO: forming-bar ROT02/TSM01 era gia' fixato (v1.1.10), item chiuso.

Test: pytest 99 passed; validate_honest_workers OK; validate_xsec_worker OK.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:29:14 +00:00

5.9 KiB
Raw Blame History

2026-06-11 — Sweep di stabilità su tutte le strategie (anti-overfit)

Obiettivo: analisi di tutte le 19 sleeve cercando miglioramenti/correzioni/protezioni che aumentino la STABILITÀ, con disciplina anti-overfit (ipotesi pre-registrate, griglie fissate prima di guardare i numeri, verdetti su TRAIN E OOS con plateau, gate PORT06; nessun re-test di idee già bocciate: ADX/vol-target/time-stop/hurst/stop SH01/multi-TF/entry-guard ecc.).

Audit di drift (nessun fitting)

  • report_families sui dati correnti: correlazioni cross-famiglia ancora ≈0 (pairs 0.03-0.11, XS01 0.01, SH 0.03-0.04 vs MASTER-9) → la diversificazione NON è driftata.
  • Regression-lock trend_max (trendmax_port06_impact) rieseguito su dati freschi: parità 1.00000 su tutte e 6 le fade (engine live-path integro) e plateau trend_max 2.5/3.0/3.5 confermato (OOS Sh 11.14/11.24/10.98, DD 1.33 identico).
  • Percentile del rolling-return PORT06 (cap weights, storia 2021+): finestra corrente 60g al 19°, 120g al 21°, 160g al 28° percentile → periodo fiacco ma in variazione normale.
  • ⚠️ FADE in coda storica: il rolling 120g equal-weight delle 6 fade è a 1.0% = 2° percentile della propria storia (p5 = +0.4%). È il tratto peggiore mai attraversato dalla famiglia. Decisione esplicita: NESSUN ritocco ai parametri (sarebbe fit sul regime corrente, l'errore che il progetto evita da sempre); la protezione è la diversificazione (già in atto: il portafoglio regge al 19-28° pct) + monitoraggio. Follow-up in TODO: alert di drift per-famiglia nel hourly_report (distribuzione storica precomputata).

Correzione: bug contabile TR01 worker (FIXATO)

BasketTrendWorker.tick usava mean(rets) sui SOLI asset in posizione → con paniere parziale sovrappesa N/k (con 1 solo long: 0.45 del capitale invece di 0.09). Era l'origine della divergenza replay 44% vs reference +42% annotata nel TODO. La convenzione canonica (backtest PORT06 via _tr_basket_daily) è equal-weight 1/N sull'universo: fix 1 riga (sum(rets)/len(universe)). Replay post-fix: TR01 +32% vs reference +42% (stesso segno e ordine di grandezza = gate del validatore; il residuo è la differenza dichiarata capitale-unico vs media-equity). ROT02 +171%==ref, TSM01 +5%==ref invariati. Solo statistica PAPER: nessun effetto su pool/ordini. NB: il forming-bar su ROT02/TSM01 segnato nel TODO era GIÀ fixato (v1.1.10, _panel scarta la barra in formazione) — TODO aggiornato.

Protezione 1 (pre-registrata): disaster-cap z sui pairs → NO-GO

Ipotesi: exit immediata se |z| ≥ z_stop dopo l'ingresso taglia la coda da structural-break senza toccare i trade normali. Griglia fissata: z_stop {3.0,3.5,4.0,5.0} × 5 coppie 1h + {2.5,3.0,3.5,4.0} sul 15m, train <2023-11-01 / OOS, engine con regression-lock ESATTO su pairs_sim/pairs_sim_flat. Esito (potere statistico AMPIO, centinaia di trigger): bocciata su tutti e 3 i criteri — il DD peggiora quasi ovunque, il worst-trade OOS peggiora su 4 coppie su 6 (ETH/BTC 62→−168%!), Sharpe OOS cala oltre il 10% relativo in TUTTE le celle, e il "plateau" esiste solo per il danno (monotono: più stretto, peggio). Meccanismi: (i) lo stop realizza la perdita al massimo overshoot — il movimento che la strategia fada (stessa lezione EXIT-16/SH01, 5ª conferma); (ii) l'engine non-overlap rientra subito nello spread ancora divergente → churn di fee a 2 gambe e stop ripetuti. I pairs restano senza stop by design; la mitigazione resta la taglia (position_size_family PAIRS 0.20). Record: scripts/analysis/pairs_zstop_research.py.

Protezione 2 (pre-registrata): phase-tranching XS01 → PROMOSSA e LIVE

Diagnosi: il roll non-sovrapposto di XS01 ha una FASE arbitraria (dipende da quando il worker parte) e l'esito ne dipende parecchio — sulle 12 fasi possibili: Sharpe daily FULL 1.52-2.33, DD per-trade 13.8-33.1% (xs01_tranche_research.py). È timing-luck puro: il backtest canonico (fase 0) è una delle estrazioni FORTUNATE sul FULL (DD 15.4 vs mediana ~21). Rimedio senza parametri fittati: ensemble di fase — K sub-book sfasati di hold/K barre su capitale comune (PnL/K). Gate onesto su equity daily (xs01_tranche_gate.py):

  • standalone OOS: Sharpe 3.79→4.27 (K=2) →4.85 (K=3), DD 7.99→7.25→5.56;
  • FULL standalone: il DD sale verso la mediana di fase (~21%) — il "peggioramento" è la RIMOZIONE della fortuna della fase 0, non un costo;
  • PORT06 swap-sleeve: FULL invariato (7.33/3.46), OOS Sh 10.07→10.11 (K=2) →10.15 (K=3), OOS DD 1.48→1.43→1.38. Plateau: K=2 E K=3 entrambi promossi (non best-pick).

Implementazione: CrossSectionalWorker param tranches (default 1 = storico; live K=3 in _defs.py), books indipendenti con sfasamento iniziale wait, capitale comune, migrazione automatica dello status legacy (il vecchio book → tranche 0), last_bar_ts solo-avanti (robustezza ai panel accorciati da feed in ritardo). Solo path LIVE come disp_min (il backtest canonico resta single-phase → il confronto live/backtest va letto con la mediana di fase in mente). Validatore esteso e PASSATO: K=1 replay == xsec_sim ESATTO (1427 trade, cap 4993==4993); K=3 replay == unione fasi 0/4/8 ESATTO (4279 trade, 4512==4512). Osservabilità: hourly_report aggrega i sub-book nel book medio.

Non toccati (per evidenza, non per pigrizia)

  • Fade/DIP01: exit-lab già esaustivo (23+11 famiglie); plateau e parità riconfermati oggi.
  • SH01: 11 famiglie di stop già bocciate; cap famiglia + monitor trade-rate in essere.
  • ROT02/TSM01: replay == reference; forming-bar già fixato.
  • TR01 come sleeve (3 anni negativi su 6): rimosso? NO — sarebbe una decisione guidata dal regime recente (recency bias); il suo ruolo è catturare i trend che le fade non prendono. Resta, con la statistica ora contabilizzata correttamente.

Esito test

pytest: 99 passed. Validatori: honest workers OK, xsec K=1/K=3 OK, trendmax lock OK.