Files
PythagorasGoal/docs/diary/2026-06-04-exit-lab.md
T
2026-06-04 21:29:55 +00:00

9.8 KiB
Raw Blame History

2026-06-04 — EXIT LAB: ricerca massiva exit dinamiche (34 agenti) → EXIT-16 close-confirm SL PROMOSSO

Obiettivo

Cercare exit migliori per le fade attive: TP dinamici, SL dinamici/protettivi, meccanismi per "cavalcare il prezzo". Mandato: ≥20 agenti con specifiche differenti, guardia anti-overfitting, test su tutto lo storico disponibile.

Esito in una riga: "cavalcare" non esiste (17ª conferma), ma la ricerca ha trovato una cosa più grossa — LO STOP-LOSS INTRABAR FISSO È IL COMPONENTE CHE DISTRUGGE PIÙ VALORE NELLE FADE, e la forma robusta del fix (SL confermato in chiusura, EXIT-16) è PROMOSSA a livello PORT06: OOS Sharpe 8.82→10.06, FULL DD 4.10→2.60.

Infrastruttura (riusabile)

  • scripts/analysis/exit_lab.py — harness CONDIVISO: cache segnali live-params delle 3 fade (data/cache/exit_lab_signals.pkl, 73.6k barre 1h 2018→2026-05, BTC+ETH), engine intrabar identico a fade_base (parità verificata al punto percentuale su tutti e 6 gli sleeve), contratto anti-look-ahead esplicito (levels(j) solo dati ≤ j-1; after_bar(j) sul close = eseguibile al poll), protocollo train/OOS (selezione parametri SOLO su pre-2023-11; OOS letto una volta), fill parziali, ExitPolicy pluggable.
  • scripts/analysis/exit_policies/ — 23 policy + 10 script di verifica.
  • scripts/analysis/exit16_port06_impact.py — replay canonico build_trades/ fade_daily_equity con parità ESATTA (corr 1.00000) + innesto EXIT-16 + ricalcolo PORT06 (cap pairs 0.33, leva 2x).

Fase 1 — Explore: 23 agenti, una famiglia ciascuno

Tally: 3 PROMISING, 5 NEUTRAL, 15 REJECT.

REJECT (la famiglia "cavalca il prezzo" è stata falcidiata): trail_atr_ride, trail_pct, breakeven, sar_trail, tp_decay, tp_extend_momentum, tp_moving_mean, partial_tp_trail, hurst_exit, giveback, loser_timestop, donchian_trail, ema_cross_exit, vol_rescale, sl_tp_ride. Autopsia ricorrente: il TP delle fade sta ALLA MEDIA; lì il movimento è esaurito per costruzione → oltre quel punto non c'è coda da cavalcare (il runner finiva oltre il TP ~3% delle volte). Caso da manuale trail_atr_ride: train 5/6 a favore, OOS 0/6 (MR01 ETH ret 134%→1%) — senza l'embargo train/OOS l'avremmo promosso.

NEUTRAL: ratchet, sl_tighten, z_overshoot, wide_sl_trail, swing_stop (miglioramenti misti, mai 5/6).

PROMISING — tre meccanismi diversi, UNA stessa tesi ("gli stop intrabar da wick sono falsi negativi: l'overshoot che buca lo stop è esattamente ciò che la fade vuole fadare, e rientra"):

  • EXIT-02 trail_atr_keep_tp (chandelier k=1.5 + TP fisso): 6/6 train, 5/6 OOS.
  • EXIT-16 close_confirm_sl (SL solo se la barra CHIUDE oltre sl0±0.5·ATR): 6/6 train E 6/6 OOS, plateau.
  • EXIT-22 no_sl (diagnostica: SL rimosso): 6/6+6/6, plateau monotono perfetto SL stretto < base < SL 2x < niente SL.

Fase 2 — Verify: 10 avversari (leakage / overfit / stress × 3 + tail-risk)

  • EXIT-02 CONFUTATO (2/3). (a) Overfit: è Pareto-dominato da no_sl a ZERO parametri su tutti i 6 sleeve OOS (Sharpe base→trail→noSL: MR01 BTC 2.77→3.08→5.67; MR02 ETH 12.35→11.41→17.24 — il trail PEGGIORA lo sleeve di punta) → overfit di selezione: la forma parametrizzata sbagliata di una tesi giusta. (b) Leakage: codice pulito MA scoperta di metodo: il 54% degli stop del trail è GAP-THROUGH (l'open del bar è già oltre il livello; gap mediano 62-174 bps): l'engine che filla gli stop "al livello" SOVRASTIMA ogni policy a stop stretti; col fill realistico worse(level, open) il confronto si ribalta. Da oggi ogni ricerca con stop tight deve modellare il gap-through. Lo SL largo della base ne è quasi immune.
  • EXIT-16 SUPERA 4/4. Leakage: contratto pulito, robusto a 1-3 bar di lag e al fill a open[j+1] (esce deliberatamente tardi → il timing fine è irrilevante per costruzione). Overfit: plateau monotono su buffer 0.4→1.0 FUORI griglia, il ponte SL-largo k=1.5→4 converge, 4 finestre temporali ok, e indipendente dal loss-guard Hurst (segnali rigenerati senza guard: la tesi regge anche nel regime trending). Stress: fee 2x 6/6, slippage 20 bps simmetrico 6/6, bear 2021-22 ok.
  • EXIT-22 invalidato SENZA mitigazione (tail-risk). p99 MAE ~raddoppia (MR01 ETH 38.8→−59.7%); finestra FTX 2022-11: MR02 ETH 39% (worst 48%, MAE 70%) dove la base faceva +2.7% uscendo in 2-3 barre. E un fatto di wiring: con sl=0 il worker cade nel ramo elif max_barsil fallback 2% è codice morto: no_sl live girerebbe SENZA ALCUNO stop. Il disaster-SL 4× tiene ~95% del ritorno ma il worst pooled resta 47%.
  • Raccomandazione convergente del tail-auditor: EXIT-16 — neutralizza quasi tutta la coda DA SOLO (p99/worst ≈ base; FTX: +2.4%) mantenendo l'immunità ai wick. Mai usare uno stop stretto 2% come fallback (cappa l'edge insieme alla coda).

Fase 3 — Test decisivo a livello PORT06 (path canonico)

Replay di build_trades/fade_daily_equity con parità ESATTA (corr 1.00000, ritorni identici, PORT06 base == Portfolio.backtest() al centesimo: FULL 6.47/4.10%, OOS 8.82/1.30% — i numeri 6.07/8.19 citati in CLAUDE.md sono pre-refresh-dati). Poi SOLO il ramo SL sostituito col close-confirm 0.5·ATR:

PORT06 FULL Sharpe FULL DD FULL CAGR OOS Sharpe OOS DD OOS CAGR
base 6.47 4.10% 61% 8.82 1.30% 86%
EXIT-16 7.84 2.60% 79% 10.06 1.15% 102%

Gate (lo stesso del loss-guard Hurst): PROMOSSO — migliora OGNI metrica, FULL e OOS. Per-sleeve: ritorni FULL ~4-10x (MR01 BTC 299→1963%) e DD giù su 5/6 (eccezione MR02 ETH +3.4pp, assorbita in aggregato).

Verdetto e piano di deploy (DA FARE, non ancora implementato)

EXIT-16 close_confirm_sl, buffer 0.5·ATR14, SOLO sulle 6 fade.

  1. Param sl_confirm_atr=0.5 nelle fade (default None = comportamento attuale, per non rompere i backtest canonici/regression-lock).
  2. FadeStrategy.backtest + StrategyWorker.tick: disattivare l'uscita intrabar a sl0; aggiungere il ramo sul CLOSE del bar (long: esci se close < sl0 0.5·ATR14; short speculare). TP intrabar e max_bars INVARIATI. ATR14 causale già disponibile.
  3. Shadow reale: nessun nuovo tipo d'ordine (il close-confirm esce al close del worker → market reduce-only come oggi; il limit resting al TP di v1.0.7 resta identico).
  4. DIP01/pairs/rotation/tsmom/shape NON toccati.

Caveat onesti

  • L'uscita al close può essere PEGGIO di sl0 sui veri sfondamenti (già nei numeri del backtest, ma la coda dipende dai gap: campione 1h crypto, stress estremi sottorappresentati).
  • Niente stop fisico sul book → rischio gap/disconnessione fra i poll (mitigazione possibile in futuro: disaster-SL intrabar a 4× come cintura, costa ~5% del guadagno).
  • avg_bars +~18% (esposizione su; già nei numeri via non-overlap).
  • I numeri per-sleeve FULL (ret 4-10x) vanno letti col solito sconto del compounding su 8 anni; la decisione è presa sul gate PORT06, non su quelli.

Scoperte collaterali da NON perdere

  1. Gap-through fill: l'engine intrabar filla gli stop al livello anche quando l'open è già oltre → bias sistematico PRO stop-stretti. Aggiungere il fill worse(level, open) all'harness prima di future ricerche su stop.
  2. Wiring worker: una strategia con sl=0+max_bars gira nel ramo elif max_bars → il fallback 2% non si applica. Oggi nessuna strategia attiva emette sl=0, ma il ramo va reso esplicito se mai si deployasse una policy senza SL.
  3. Il prior ladder di stamattina è confermato e generalizzato: 4 conferme indipendenti che oltre il TP non c'è nulla da cavalcare.

Costi della ricerca

34 agenti (23 explore + 10 verify + 1 PORT06), ~1.65M token subagente, ~70 minuti wall-clock su 2 core. Workflow: exit-policy-explore (wf_4b23e922-f48) + exit-policy-verify (wf_f2bb98df-c43).

IMPLEMENTATO E DEPLOYATO (sera stessa, v1.1.0)

Param sl_confirm_atr (default None = comportamento storico):

  • src/strategies/fade_base.py FadeStrategy.backtest + scripts/strategies/ MR01_bollinger_fade.py (ha un backtest proprio, duplicato storico — patch identica): ramo close-confirm al posto dell'SL intrabar; TP intrabar e max_bars invariati; in modalità confirm il TP ha priorità nel bar (come la policy validata in exit_lab).
  • src/live/strategy_worker.py tick: con sl_confirm_atr nei params dello sleeve, TP intrabar al livello come prima; stop SOLO se close sfonda sl ∓ buf·ATR14(df), uscita al prezzo corrente (reason stop_loss invariata → il monitor stop-rate di hourly_report continua a funzionare).
  • scripts/portfolios/_defs.py: SL_CONFIRM_ATR = 0.5 sulle 6 fade (path live, come hurst_max). Il backtest canonico resta NON filtrato.
  • Test: tests/portfolio/test_close_confirm_sl.py (wick non stoppa; breach sul close stoppa al close; TP intrabar resta; buffer regge; regressione senza param). 59/59 verdi.

Nota onesta dal sanity-check sul path GREZZO (nessun filtro): il ritorno esplode (MR02 ETH cap 366k→3.0M) ma il DD per-sleeve SALE (MR01 BTC 32→52%): senza trend-filter/hurst il close-confirm tiene posizione anche nei trend profondi. La riduzione di DD vive nella config live (trend_max 3.0 + hurst 0.55) e nella diversificazione — esattamente ciò che il test PORT06 canonico ha misurato (DD 4.10→2.60). Le tre protezioni sono COMPLEMENTARI: hurst toglie l'esposizione al regime tossico, il trend-filter gli ingressi sovra-estesi, il close-confirm i falsi stop da wick.

Monitoraggio live: lo stop-rate fade su hourly_report dovrebbe scendere sotto il ~38% atteso post-hurst; gli stop avranno exit leggermente oltre il livello (uscita al close), i TP restano al livello (e nello shadow reale al limit reduce-only di v1.0.7).

Deploy: commit a2579d2 (+ research ad65a0b), release v1.1.0.