L'override caps in portfolios.yml SOSTITUISCE interamente il dict di _defs.py
(setattr in base.py:load_active_portfolio) -> il cap SHAPE introdotto in v1.1.2
era inerte e SH01 (senza SL by-design) girava a 2x peso. Ridichiarato il dict
completo {PAIRS: 0.33, SHAPE: 0.0588} + commento sul footgun.
Verificato live post-restart: SH_BTC+SH_ETH = 0.0588, non-cappati a 0.0647,
sum = 1.0 (== backtest canonico FULL 6.43 / OOS 8.58).
Include il diario dello sweep multi-agente 2026-06-06 (106 agenti, 37 finding
validi): fonte dei P0/P1 in roadmap.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
14 KiB
2026-06-06 — Improvement sweep multi-agente (review live PORT06)
Ricerca multi-agente del 2026-06-06 (106 agenti, 196 finding grezzi, 60 verificati avversarialmente, 37 validi / 23 uccisi). Workflow: port06-improvement-sweep (wf_56b7d4de-a06).
1. Quadro d'insieme
Il sistema PORT06 gira correttamente nella struttura (17 sleeve, runner pool/ledger == backtest), ma la review ha trovato due classi sistematiche di problemi, non bug isolati.
(a) Divergenze live-vs-backtest per omissione di protezione. Il pattern documentato del progetto è "il live AGGIUNGE protezione che il backtest canonico non ha" (Hurst, EXIT-16) → il live fa meglio del backtest sul DD. Qui si trova il contrario in più punti: il filtro trend_max=3.0 è ON nel backtest canonico ma OFF in produzione su tutte e 6 le fade (base.py:91 / _defs.py:35-38); il cap SHAPE=0.0588 v1.1.2 è inerte (sovrascritto da portfolios.yml) → SH01 gira a 2x peso senza stop-loss; EXIT-16 non è esteso a DIP01 (l'unico sleeve BTC che esegue davvero); la forming-bar lesson non è portata a TR01 né a PairsWorker. Diversi diari (2026-06-04-partial-tp-ladder.md:15, 2026-06-04-exit-lab.md) documentano config che il codice NON implementa.
(b) Calibrazione/sizing che diverge dal validato. SH01 fa fuoco sul 25% delle barre vs 2.9% validato (th0.58 inerte per train-window troppo corto); i pairs girano a pos=0.5 mentre tutta la validazione PR01 è pos=0.15 (DD ~3.3x); TR01 sotto-carica le fee 2x (manca il fattore leva).
Tema ricorrente onesto: la maggior parte delle "scoperte" sono ripristini di config già validate, non nuovo alpha. I bug di config (cap, trend_max) vanno corretti subito; ogni cambio di strategia deve passare il gate PORT06/OOS (precedente FR01: robusto-individualmente ≠ migliora PORT06).
2. P0 — Protezione capitale / gap live
| Titolo | Cosa fare | Evidenza | Effort |
|---|---|---|---|
| Cap SHAPE 0.0588 inerte → SH01 a 2x peso | load_active_portfolio deve fare deep-merge dei caps (partire da p.caps e aggiornare con ov["caps"]), oppure ridichiarare il dict completo in portfolios.yml:7 come {PAIRS:0.33, SHAPE:0.0588}. Ri-snapshot pesi, confermare sum=1.0. SH01 non ha SL by-design e ha già subìto un −15.6% live: il cap è l'UNICA protezione coda. |
src/portfolio/base.py:91-93 setattr sovrascrive caps; portfolios.yml:7 {PAIRS:0.33}; scripts/portfolios/_defs.py:75 {PAIRS:0.33,SHAPE:0.0588}; live data/portfolios/PORT06/status.json SH_BTC=SH_ETH=0.0588 (totale 0.1176 = 2.0x intended). Backtest canonico usa il cap giusto (FULL 6.43/OOS 8.58) → è una rottura di parità. |
low |
Nota: la stessa correzione risolve anche il sotto-peso ~9% di tutti gli altri non-pairs (0.0588 live vs 0.0647 intended); il rischio materiale è solo SHAPE.
3. P1 — Alto valore, testabile subito
| Titolo | Cosa fare | Evidenza | Effort |
|---|---|---|---|
trend_max=3.0 OFF in produzione su tutte le 6 fade |
Aggiungere "trend_max":3.0, "ema_long":200 alla SleeveSpec FADE _defs.py:36-37. Ripristina la config del backtest canonico (look-ahead-free, EMA200 causale). MR02/MR07 lo onorano (non lo ignorano). Ri-validare a livello PORT06 (template exit16_port06_impact.py) perché Hurst+trend si sovrappongono. |
_defs.py:35-38 (no trend_max); MR01_bollinger_fade.py:61 get('trend_max')→None; combine_portfolio.py:47 canonico usa trend_max=3.0; risk_management.py:242 lo applica a tutte e 3. Live: MR01_ETH/MR02_ETH hanno fadato il crash ETH 06-05 più volte (long@1658.9 poi long@1592.8 in caduta 1727→1517), tutti stoppati (trades.jsonl). Misurato (live-hurst-on, 3x): MR02_ETH DD 27.3%→16.2%, WR +8pp, Sharpe +1.34 (return ~dimezzato — trade-off DD/return). |
low |
| Disaster-bracket on-book durante outage feed | Il poll-loop intero è in un try/except: in un outage le posizioni REALI restano senza stop sul book e senza valutazione exit. Piazzare un bracket lontano (~−30%) su Deribit per i fade eseguiti [MR01,MR02,MR07,DIP01] via cerbero_client.py:137 set_stop_loss (esiste, non cablato). Non spara mai in operatività normale → 0 costo Sharpe; pura assicurazione. |
runner.py:246-298 (try/except unico, sleep su errore); ETH gap p99.9=996bps, max=3301bps, peggior 3h −3492bps (data/raw/eth_1h.parquet); REAL_OPEN 06-05 verificati; docs/diary/2026-06-04-exit-lab.md:119-121 lo segnala, mai implementato. Caveat: nei crash il fill è al gap, non al livello → cappa ma non al trigger. |
low-med |
| SH01 th0.58 inerte: fa fuoco sul 25% delle barre vs 2.9% validato | Ri-validare SH01 OOS con train-window pari al regime live (~8760 barre) invece dell'expanding full-history. La LogReg su train piccolo è over-confident (mean max-proba 0.582 vs 0.536). NON limitarsi ad alzare th (la calibrazione resta sbagliata). Se l'edge non sopravvive nel regime live, riconsiderare SH01. | misurato: small-train frac≥0.58 = 44.8% vs full 3.8%; live-window 1197 entry (49.9% long) vs 139 (71.9% long), trade-rate 25.1% vs 2.9%. SH01_shape_ml.py:60 th=0.58; tutta la validazione (shape_ml_validate.py) usa training expanding. È la causa meccanica del WR live 1/9. Blast radius limitato dal cap SHAPE (vedi P0). |
low (test) |
| Pairs a pos=0.5 vs validazione pos=0.15 (DD ~3.3x) | Introdurre un position_size per-famiglia (plumb SleeveSpec/build_worker_for, runner.py:43-89) e portare i pairs a 0.15-0.25. Ri-eseguire combine alla pos live prima del deploy. I pairs sono la famiglia a DD-grezzo più alto (ETH/BTC 47.6%) e senza stop (exit solo |
z | ≤z_exit o max_bars). |
| TR01: fee sotto-caricate 2x (manca leva) | Moltiplicare il termine fee per self.leverage (basket_trend_worker.py:69) per allinearlo al return leveraged (riga 67) e al reference fee=FEE_RT/2*LEV (honest_improve2.py:150). |
worker fee/flip 1.5e-05 vs corretto 3.0e-05 (POS0.15/LEV2/N5) = 2.0x. tsmom/rotation_worker sono consistenti → TR01 è l'unico difettoso. Drag assoluto piccolo (low turnover) ma bias ottimistico mono-direzionale che gonfia total_capital nel rebalance. | low |
| PairsWorker agisce sulla barra IN FORMAZIONE | Portare la EXIT-16 forming-bar logic ai pairs: calcolare bar_ms da median(diff(ts)) e valutare z sull'ultima barra COMPLETA (entry+exit), come strategy_worker.py:445-447. |
pairs_worker.py:185 i=len(m)-1, :195 exit, :204-207 entry su barra in formazione; backtest pairs_research.py:69-75 solo close settled; validate_worker_pairs.py non esercita il path → divergenza non testata. |
low |
| TR01 valuta crossover EMA sulla barra 4h in formazione | In tick scartare la barra in formazione (k=-1 if now_ms≥ts[-1]+bar_ms else -2) e valutare il segnale sull'ultima 4h COMPLETA; correggere ANCHE il booking del return (riga 66 legge la finestra in formazione). |
basket_trend_worker.py:60-72 legge [-1] (forming); reference honest_improve2.py:152 range(n-1) solo completi. Evidenza: SOL flip 0→1@85.66 poi 1→0@77.72 a 59min, stessa finestra 08-12 UTC (−9.3% intra-window glitch). Fix incompleto se si tocca solo il segnale. |
low |
| DIP01 senza EXIT-16 (ed è l'unico sleeve BTC che esegue reale) | Aggiungere "sl_confirm_atr":0.5 (e valutare hurst_max) ai params DIP01 _defs.py:41-42 (il worker lo legge già, zero codice). Ri-validare sul grid OOS proprio di DIP01 + a livello PORT06 con engine gap-aware prima del deploy (DD standalone sale BTC 23→37%). |
_defs.py:41-42 DIP01 ha solo {min_tp_frac}; worker gira il branch intrabar wick-sensitive (strategy_worker.py:466) invece del close-confirm (:429). Riproducibile: BTC FULL 299→726/OOS 60→168, ETH FULL 187→898/OOS 221→255. I 3 fade BTC sono flat (0 ordini reali); DIP01_BTC è l'UNICO BTC con round-trip reale → gap non teorico. |
low (fix) / med (validazione) |
| TSM01 252d lookback: shortfall su un asset tronca l'inner-join sotto need=253, tick() esce in silenzio | Aggiungere un WARN/Telegram quando panel<need (gated su "era già operativo" per evitare falsi positivi al cold-start). Coprire anche RotationWorker (stesso silent return). |
runner.py:37 1d=440; tsmom_worker.py:53-55 need=253, return muto; rotation_worker._panel how='inner' tronca al più corto; live margine 188 (OK ora) → latente, non attivo. |
low |
| Osservabilità: TR01/ROT02/TSM01 invisibili in hourly_report | hourly_report.collect() filtra su chiavi (event, in_position) che i worker multi-asset non emettono → 100% delle loro righe scartate. Aggiungere in_position ai _save() dei 3 worker e/o esporre status_summary (long correnti + giorni-dall'ultimo-flip). Distingue "flat in chop" da "wiring rotto". |
TR01 2 record (entrambi 06-03, lo SOL whipsaw), flat da allora; ROT02/TSM01 0 trade (rispettivamente cash / risk-off da 06-01, by-design). hourly_report.py:105,111; worker _log/_save senza event/in_position. |
low |
| MR07 reale: ripristino-fill non esercitato (short-side TP-resting mai testato) | Smoke testnet live_shadow_smoke.py con direction=-1 per esercitare il limit reduce-only BUY sotto mercato (può crossare subito → path resting_fills). MR07 (+ tutti i worker) ha 0 TP-resting filled finora → reconciliation completamente non testata in produzione. |
MR07_ETH 5-6 REAL_OPEN tutti 06-03 PRE-fix (TP-resting 2026-06-04); +257/267/283/333bps slippage legacy (sunk, non ricorrente). Tutti i REAL_TP_RESTING verificati sono side=buy (long); il path short non è mai stato esercitato né in ledger né negli smoke. strategy_worker.py:253 _place_real_tp. |
low |
4. P2 — Filoni di ricerca
- MR01 ETH k=2.0 vs k=2.5 deployato. k=2.0 è uniformemente meglio OOS su ETH (no-filtro +1238% vs +487%; trend-filtrato +569% vs +196%) e neutro su BTC sotto filtro live. Non è nuovo overfit: è il config per-asset documentato (MR01:15-16) che il k=2.5 universale ha silenziosamente abbandonato. Da confermare a livello PORT06 con i param live (hurst/sl_confirm).
risk_management.py:40,44. - Limit-order maker entry sui fade — NO-GO confermato. Strutturalmente infattibile: il segnale fade fa fuoco quando il close ha già bucato la banda (mediana 33bps sotto); un buy-limit al livello è marketable→taker; un limit più profondo manca i winner che rimbalzano in 1-4 min. Negativo-EV, non implementare.
MR01_bollinger_fade.py:84,93; live MR07_ETH 5 open→TP in ~1.4min.
5. P3 / Scartati in verifica
- Commento errato in
_defs.py:17-18("MR02/MR07 ignorano min_tp_frac") — falso (lo leggono e applicano, MR02:46,72), ma il filtro è di fatto inerte su MR02/MR07 nel campione → solo igiene doc, no PnL. - TSM01 inerte (0 trade da 06-03) — corretto by-design (BTC sotto SMA100 dal 06-01 + tutti gli asset consensus negativo). Solo diagnostica; NON redistribuire il capitale (riduce la diversificazione quando il regime gira).
- Time-limit fade NON sono un leak — myth-busting: il bucket max_bars è net-positivo OOS (+118÷+195bps, +154%/+335% totale); il vero loser è lo SL (già gestito). NON accorciare max_bars.
- Fill max_bars next-open vs close — wash misurato (5/6 sleeve identici); il worker già filla ~next-open. Nessuna azione.
- SH01 H=12 fuori scope
exit_lab.py— routing: usaresh01_exit_lab.py(gap-aware) per qualsiasi studio orizzonte SH01. Clarificazione, non bug. - PairsWorker bars_held conta poll-con-nuovo-ts non barre reali — latente (0 time_limit su 9 close), bite solo dopo outage >2h con z ancora oltre soglia; rischio di introdurre exit-anticipato se bar_ms via median-diff su feed testnet flaky → usare bar_ms fisso. Igiene, bassa priorità.
6. Piano consigliato (2 settimane)
Settimana 1 — protezione capitale e parità (config-only, niente cambio di strategia):
- Giorno 1 — P0 cap SHAPE. Deep-merge dei caps in
base.py:91; ri-snapshot pesi PORT06, confermare SH totale=0.0588, sum=1.0, nessuno sleeve affamato. Verificare contro il backtest canonico (FULL 6.43/OOS 8.58). Questo dimezza l'esposizione SH01-no-SL già colpito dalla coda. - Giorno 1-2 — TR01 fee 2x + forming-bar (TR01 e Pairs). Fix puramente di parità/contabilità, nessun gate OOS necessario (avvicinano il live al backtest validato). Coprire il silent-return TSM01/Rotation con WARN.
- Giorno 2-3 — disaster-bracket on-book + osservabilità outage. Cablare
set_stop_losslontano (~−30%) sui 4 fade eseguiti; aggiungere alert su N poll falliti consecutivi conreal_in_position=Truee log del gap% al risveglio. Smoke testnet (€0). Pura assicurazione coda, 0 costo Sharpe. - Giorno 3 — osservabilità report (TR01/ROT02/TSM01 in hourly_report) + smoke MR07 short-side TP-resting.
Settimana 2 — ripristini di config validata, ognuno dietro il gate PORT06/OOS:
- Giorno 4-6 —
trend_max=3.0sulle 6 fade. Implementare in_defs.py, poi ri-eseguire il gate PORT06 (templateexit16_port06_impact.py) misurando trend-ON+hurst+EXIT-16 vs attuale trend-OFF: deploy SOLO se Sharpe OOS non peggiora e DD scende (sovrapposizione hurst+trend va quantificata). - Giorno 6-8 — pos pairs per-famiglia. Plumbing
SleeveSpec.position_size; ri-eseguirecombinea pos live (target 0.15-0.25); scegliere il punto sul trade-off DD/ritorno; deploy. - Giorno 8-10 — DIP01 EXIT-16+hurst. Validare sul grid OOS proprio di DIP01 (famiglia honest, meccanismi tarati su fade) con engine gap-aware, poi gate PORT06. Deploy solo se passa entrambi.
- Giorno 10-12 — SH01 ri-validazione train-window. Ri-eseguire validazione con train cap ~8760 barre (regime live). Se l'edge sopravvive → tarare th/calibrazione su slice held-out; se non sopravvive → riconsiderare SH01 (NON ri-tunare il th a forza). Mai impostare sl/sl_confirm_atr su SH.
Filoni P2 (MR01 k ETH) e cleanup P3 in coda, opportunistici. Regola trasversale: i punti 1-4 sono fix di bug/parità e vanno per primi; 5-8 toccano il comportamento di trading e nessuno va in produzione senza superare il gate OOS + PORT06 (precedente FR01: robusto individualmente ≠ migliora il portafoglio).