244 Commits

Author SHA1 Message Date
Adriano Dal Pastro 8401a280b9 chore: bump 1.1.34 — GRID_BTC paper sleeve (Price Ladder shadow stage 1)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:19:48 +00:00
Adriano Dal Pastro b4d4961d77 merge: Price Ladder research + GRID_BTC paper sleeve (shadow stage 1)
Porta su main: pulizia feed (clean_feed, 254 spike-print Binance), ricerca Price Ladder
(ladder_search/regate/sltp), GridWorker paper + validazione, wiring runner kind=grid +
paper_extra (PORT06 canonico INTATTO: 19 sleeve), GRID_BTC paper in portfolios.yml.
Tutto additive + validato; nessun ordine reale (grid sim-only).
2026-06-18 16:19:48 +00:00
Adriano Dal Pastro 264b9200ea 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>
2026-06-18 16:18:53 +00:00
Adriano Dal Pastro b3d4ab7150 feat(live): GridWorker (Price Ladder) SIM/PAPER + validazione replay==backtest — shadow stage 1
Stage 1 dello shadow per il Price Ladder (config finale: BTC 1h range1.5 rd0.20 ru0.06 L6
sl0.10 tp0.03). GridWorker (src/live/grid_worker.py) gira sul feed LIVE e contabilizza
l'equity mark-to-market col motore CANONICO grid_mtm (parita' col backtest per costruzione),
SENZA piazzare ordini reali (sim/paper). Stato persistente + resume. grid_mtm esteso con
param df=None (retro-compatibile: il feed live passa il df; None = _load come prima, gate
invariato — BTC ladder 10.8/5.9, PORT06 base 8.18 identici). Validazione
validate_grid_worker.py: [A] full-tick == grid_mtm esatto, [B] replay incrementale converge
esatto, [C] resume entro la persistenza (4 dec) -> VALIDAZIONE OK.

NB SICUREZZA: nessuna modifica a runner/portfolios.yml/_defs -> il sistema mainnet (€500
reali) e' INTATTO; il worker e' inerte finche' non wirato. L'esecuzione REALE (griglia di
LIMIT resting su Deribit, fill parziali/episodi) e' lo stage 2-3, dietro testnet +
autorizzazione esplicita. Il runner avvia ordini reali solo per kind in (single,ml);
kind=grid resta sim per costruzione.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 14:58:11 +00:00
Adriano Dal Pastro 587fbc0f61 feat(analysis): ladder_sltp_study — 3 passi pre-deploy + SL/TP, config finale ladder
I 3 passi: [1] valutazione 2018-INCLUSIVE (il gate IDX2021+ e' cieco al 2018) -> il tail
vero del BTC ladder e' il 2018 (-27.7% regime-gated / -50% none); [2] fill maker 0% vs
taker 0.10% (Deribit = LIMIT = maker) -> maker leggermente MIGLIORE, harness conservativa,
nessun fee-cliff; [3] half-size (la coda 2018 si dimezza sul book). STUDIO SL/TP: sweet spot
sl_buf=0.10/tp_buf=0.03 cappa il 2018 a -23.5% (da -27.7) senza intaccare l'edge (oos 5.06).
Lezione (conferma prior progetto): SL troppo stretto PEGGIORA (redeploy nel coltello = falso
negativo MR), SL da solo senza regime-gate e' erratico -> il regime-gate e' il controllo
PRIMARIO della coda, il SL moderato fine-tuna. Config finale: BTC 1h range1.5 rd0.20 ru0.06
L6 sl0.10 tp0.03 half-size, PROMOSSO (OOS 10.86->11.0, corr 0.195). Unico passo residuo:
shadow ledger reale (operativo). Diario sez. 8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 14:48:39 +00:00
Adriano Dal Pastro 8abeb7a83f docs(diary)+feat(analysis): re-gate Price Ladder su dati puliti — tail vero = 2018
ladder_regate_clean.py ri-valuta i top candidati dopo clean_feed.py con gate PORT06 +
stress close_only + DD per anno. Esiti (sez. 7 diario): (1) l'obiezione "coda artefatto"
CADE -- il 54% BTC era spike-print 2024, ora DD gate (2021+) ~11-15%, tutti PROMOSSO half,
corr 0.22-0.29, reggono fee2x = candidati VERI; (2) emerge il tail REALE = 2018 (-44/-52%
standalone) che il gate NON vede (IDX parte dal 2021) -> lacuna metodologica generale; il
regime-gate stretto (BTC rd0.2 L6 range1.5) lo dimezza a -27.7%; (3) l'edge dipende dai fill
INTRABAR (close_only crolla 4.7->0.24): legittimo per LIMIT ma serve shadow ledger.
Verdetto: miglior candidato BTC 1h rd0.20 ru0.06 L6 range1.5 (PROMOSSO, corr 0.23, miglior
coda 2018); prima del deploy servono gate 2018-inclusive + shadow fill + half-size.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 14:42:18 +00:00
Adriano Dal Pastro b9e3388f76 feat(data): clean_feed — ripara spike-print del feed Deribit coi dati reali Binance
La ricerca Price Ladder ha rivelato spike-print nei parquet storici (es. BTC 2024-02-13
low 38.580 / high fasulli, BTC reale ~50k) = stesso problema TP_PHANTOM/feed testnet di
CLAUDE.md, che avvelena i backtest (stop/entry su prezzi mai avvenuti) e gonfia le code.
Tool conservativo: DETECT (wick >15% oltre il cluster close locale) -> ARBITRA con Binance
spot (ccxt, gia' cablato): sostituisce O/H/L/C solo se Binance dissente >2% su high/low (un
wick VERO confermato da Binance resta intatto), backup in data/_feed_backup/ + scrittura
atomica + validazione. RIPARATE 254 barre (BTC 132, ETH 122) su 8 file BTC/ETH x TF; 2 wick
reali confermati da Binance e TENUTI (ETH 30m/1h flash-crash veri). Impatto validato: il
BTC ladder che dava FULL DD 53.69% (artefatto) ora ne da 10.8% (la "coda di trend" era spike
fasulli); PORT06 baseline FULL Sharpe 8.13->8.18, DD/OOS invariati (canonici stabili).
data/raw e' gitignored: e' committato il TOOL, non i parquet (rigenerabili + backup locale).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 14:25:34 +00:00
Adriano Dal Pastro e631e1b89c docs(diary): price-ladder search 2026-06-18 — verdetto RETTIFICATO (nessun deploy)
Caccia multi-agente (34 agenti, 200 celle) a Price Ladder che passi il gate PORT06 su
Deribit. La sintesi iniziale trovava candidati "deployabili" (ETH 1h range-gated half-size,
OOS 10.86->11.0; BTC half-size OOS->11.34). Il critico + verifica dati ROVESCIANO il verdetto
(sez. 6): (1) la "coda BTC ~54%" e' DATI CORROTTI da spike-print (37 barre >20% intrabar, feb-2024
wick fasullo -23%, min 22.7k/max 104.7k vs BTC reale ~47-57k = stesso problema TP_PHANTOM/feed
testnet di CLAUDE.md) -> BTC non affidabile, lo stress-2022 non toccherebbe la coda vera;
(2) il +0.14 OOS Sharpe e' rumore al peso deployabile (~2.7% half, 592g regime calmo, no
significativita'); (3) il finalista ETH e' corr 0.305 (vicino allo 0.40 che fece bocciare la
griglia originale) e il regime_mask condivide il filtro trend con le fade ETH live; (4) FULL DD
standalone mascherata dal compounding (31x); (5) close_only disponibile ma non girato sui
finalisti. VERDETTO: nessun Price Ladder deployabile dimostrato; il multi-agente+critico ha
evitato un deploy ingiustificato. Config live INVARIATA. Harness validata resta come record.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 14:16:54 +00:00
Adriano Dal Pastro d733196564 feat(analysis): ladder_search — harness caccia Price Ladder che passi il gate PORT06
Goal "decine di agenti a cercare strategie Price Ladder" (Deribit). Riusa il motore
grid_mtm mark-to-market ONESTO (SL gap-aware, flat-skip, fee 0.10% RT taker = CONSERVATIVO
su Deribit, dove i fill ai livelli LIMIT sono maker ~0%) ed espone:
  - eval  <asset tf rd ru levels sl_buf tp_buf max_bars [regime] [trend_max]>
  - scan  <asset tf [regime] [trend_max]>  (sub-griglia struttura, gate PORT06, baseline
    cachata) -> top celle con verdetto gate + max_corr coi 19 sleeve + FULL DD.
Leva NUOVA: regime-gate `range` (deploy_mask in grid_mtm, retro-compatibile) = deploya la
griglia SOLO in regime di range (|close-EMA200|/ATR < trend_max), dove la griglia vive, e
non in trend dove muore. Contesto della ricerca: la griglia ETH del gioco e' BOCCIATA
(ridondante, corr 0.40); i ladder BTC sono meno correlati (~0.18) e passano il gate, MA il
nodo e' la FULL DD (coda di trend 2021/22, ~60% standalone) che il verdetto del gate NON
controlla -> la harness la espone (full_dd standalone + full_full_dd di portafoglio).
grid_mtm: aggiunto param deploy_mask (None = comportamento storico, parita' col gate).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 12:29:25 +00:00
Adriano Dal Pastro 57758ffcb1 feat(analysis): leverage_sweep — sweep leva 1-10, stress 2022, leva max raccomandata
Risponde a "se porto a leva 10 cosa succede?". Sweep leva 1->10 su PORT06
(modello daily lineare == live) FULL/OOS + anno per anno (2021-2026), con le
non-linearita' che il modello daily NASCONDE: volatility drag/Kelly, rovina
intraday vs daily. Stress con shock REALI 2022 (BTC/ETH storici: LUNA -29/-36%,
3AC-giugno -44/-52%, FTX -26/-32%) sull'esposizione netta del book -> leva di
rovina per net-long beta. Verdetto: leva 10 NON sopravvive a un 2022-repeat
(rovina gia' a ~5-9x se net-long). Leva MAX raccomandata ~3x (DD<=50% recuperabile,
beta=0.6) == config live attuale. Non modifica nulla del live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 09:48:33 +00:00
Adriano Dal Pastro c8b956ab56 fix(dashboard): PnL totale dal ledger, non init hardcoded 2000
La KPI "PnL totale" sottraeva un capitale iniziale fisso di 2000 (retaggio
testnet) -> sul micro-test mainnet da 500 mostrava -1499 (-74.95%) invece di
+0.95 (+0.19%). Nuovo helper _ledger_pnl(): legge pnl_total dall'ultima riga
di equity.jsonl (il ledger lo scrive come equity-initial_capital) e ne deriva
l'iniziale -> la dashboard combacia col ledger per costruzione, qualunque sia
il capitale (500 mainnet / 2000 testnet), niente piu' valori fissi da
aggiornare a mano. Fallback: primo punto equity, poi l'equity stessa.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 09:48:33 +00:00
Adriano Dal Pastro 3b60289cbd fix(gitignore): commento su riga propria per data/funds_watch.json
Il commento inline rendeva il pattern non-matchante (gitignore non
supporta i commenti a fine riga).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 21:16:13 +00:00
Adriano Dal Pastro e328836614 feat(live): switch a MAINNET — micro-test fade-only €500 + funds-watch
Conversione testnet -> mainnet Deribit (soldi veri). Vedi
docs/specs/mainnet-microtest-plan.md, Fase 1.

- portfolios.yml: total_capital 500, leva 3, weighting equal; eseguono
  REALE solo i 7 single-leg (6 fade MR01/02/07 x BTC/ETH 15m + DIP01 1h);
  pairs/SH01/multi-asset -> paper (rumore arrotondamento a €500); real_truth.
- docker-compose.yml: portfolio+dashboard caricano anche .env.mainnet
  (token mainnet, prevale su .env; .env.mainnet resta gitignored).
- reconcile_account.py: watermark FONDI (compute_funds_change) — rileva
  aumenti di capitale (deposito/top-up) e cali anomali (prelievo) sul
  balance USDC, alert Telegram FUNDS_INCREASE/FUNDS_DECREASE; soglia
  max(€25, 5%). Stato in data/funds_watch.json (host-writable).
- .gitignore: ignora data/funds_watch.json (stato runtime).

Ledger testnet archiviato in data/_reset_backup/pre_mainnet_*.tgz e azzerato.
Crons host ri-puntati al conto reale (sourcing .env + .env.mainnet).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 21:15:55 +00:00
Adriano Dal Pastro a0842a61cc feat(analysis): trades_status — PnL live per posizione aperta (entry reale vs mark USDC)
Riusa la convenzione della dashboard (get_ticker_batch USDC perp, real_entry vs mark;
pairs a 2 gambe). Mostra unrealized per trade + Σ REALE, e segnala che il ledger
unrealized e' sul feed sim-decisione testnet DISLOCATO (lo scarto e' dislocazione,
non soldi). Fix sys.path: import src.* anche eseguito come script file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 15:56:22 +00:00
Adriano Dal Pastro 3a66907f43 feat(analysis): trades_status.py — report stato trades con POOL reale vs PAPER-STATS separati
La dir e' la fonte di verita': portfolio_paper/ = pool (ledger/equity), portfolio_paper_stats/
= TR01/ROT02/TSM01/XS01 solo statistica. Niente piu' glob su portfolio_paper* (matchava
entrambi -> +8.15 XS01 sim attribuiti per sbaglio all'equity il 2026-06-17). Mostra realizzato
POOL + unrealized aperti e riconcilia con Δequity del ledger. Caveat + comando in CLAUDE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 15:17:50 +00:00
Adriano Dal Pastro aee53161c3 docs: Fase 0 smoke mainnet VERDE (ETH->USDC limit) + aggiorna piano micro-test
Catena d'esecuzione validata su mainnet con soldi veri a costo ~0: LIMIT SELL ETH_USDC
0.0138 @ floor 1776.3 -> filled@1778.1 (=bid), fee 0, USDC 25->49.54. Consolidato l'ETH
stranded in margine USDC. Funding parziale ~$49.54 (sotto il min EUR500) -> Fase 1 in
attesa di top-up a ~EUR1000. Diario + STATO/checklist del piano aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 15:48:38 +00:00
Adriano Dal Pastro 7b3e554424 release: v1.1.33 2026-06-16 14:16:22 +00:00
Adriano Dal Pastro b576ee66ac feat(worker): guard TP-invertito — sopprime l'entry quando il TP e' gia' sfondato (wick-print)
Un wick transitorio fa calcolare un tp dal lato sbagliato dell'entry (donchian: segnale
su barra wickata, entry al prezzo recuperato oltre il proprio tp) -> l'exit intrabar
scatta a bars_held=0 in perdita (16-06: 8 giri MR02_BTC 15m, sim -17.9 / reale -2.3).
TP_PHANTOM non lo prende (niente resting oracle, prezzo oltre il livello). Gate
zero-parametri in StrategyWorker._open_position, solo path live. Test + diario + CLAUDE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 14:16:13 +00:00
Adriano Dal Pastro ef797f1ff0 release: v1.1.32 2026-06-15 08:44:42 +00:00
Adriano Dal Pastro 4c184d5cbc feat(runner): gate feed CONGELATO — sospende gli sleeve ETH-leg finche' il feed non si sblocca
Audit "stato ordini": il feed ETH-PERPETUAL congelato a 1661.95 (36h+) generava
perdite REALI (SH01_ETH -2.83 reali vs -0.09 sim su un close + riapertura; 4 pairs
con gamba ETH entrati su z-score spuri -3/-5/+5.6 = artefatto del log-ratio con ETH
pinnato mentre gli alt si muovono).

_frozen_assets + _feed_gated_sids: quando il feed di decisione 1h di un asset e'
congelato, gli sleeve concentrati (single/ml/pairs) che ne dipendono saltano il tick
(entry+exit) finche' non si sblocca (come outage; disaster-SL on-book = coda).
Auto-guarente: rilascio alla prima barra COMPLETA non-flat (NON l'entry-guard
post-flat bocciata). Detector guasto-vs-illiquido: conta la run di close INVARIATE
(ETH/BNB/DOGE run 40-64, 1-4 val/48h = morti; SOL/LTC/ADA run <=12, 5-31 val = vivi).
Soglia feed_freeze_gate_bars=24 -> gatea le 9 gambe ETH esatte, PR_BTCLTC e i
multi-asset restano attivi. Alert Telegram FEED_FROZEN_GATE GATED/RIPRESO.

Test test_freeze_gate.py (6, detector+scope+rilascio). Suite portfolio 140/140.
Cerotto testnet: il fix vero e' mainnet. Diario 2026-06-15-frozen-feed-gate.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 08:44:39 +00:00
Adriano Dal Pastro c21e1dc635 docs: timing-sweep pairs/honest (NO deploy) + stato mainnet + vincolo feed v2
- CLAUDE: feed ETH ancora congelato 36h+; vincolo Cerbero v2 (serve SOLO
  5m/15m/1h, 30m/10m -> 400, legacy 404; _SUBHOURLY "30m" speculativo mai
  testato); esito timing-sweep (5m non conviene: regime recente peggiore +
  flat ETH 29%; gate full+OOS necessario ma non sufficiente); stato mainnet
  (token in .env.mainnet verificato is_mainnet=True, conto VUOTO = blocco)
- spec mainnet-microtest: blocco STATO 2026-06-14, .env.mainnet separato +
  servizio dedicato, checklist aggiornata (token done, funding = blocco)
- nuovo diario 2026-06-14-timing-sweep-pairs-honest.md + harness riusabile
  scripts/analysis/timing_sweep_pairs_honest.py
- .gitignore: .env.mainnet (token mainnet mai in git)

Nessuna modifica a codice/config live: PORT06 invariato (19 sleeve).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 20:19:56 +00:00
Adriano Dal Pastro 47e92a0425 docs(CLAUDE): aggiorna memoria — dashboard, report ledger-vs-backtest, feed ETH congelato, micro-test mainnet
Fatti di sessione: dashboard web (porta 8787), cron ledger-vs-backtest (gate scaling),
caveat feed testnet ETH-PERPETUAL congelato (spiega 0-trade fade ETH 15m; fix=mainnet),
piano micro-test mainnet + token Cerbero env-configurabile (CERBERO_TOKEN).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 22:46:29 +00:00
Adriano Dal Pastro faf08b3988 docs(spec): piano micro-test mainnet + token Cerbero env-configurabile
Piano operativo per validare l'edge su mainnet con poco denaro reale (il gate per
scalare). Fasi: smoke -> fade-only €1000 2-4 sett -> verdetto ledger-vs-backtest ->
espansione. Sizing motivato (fades €1000 = rumore arrotondamento 2.6% BTC; pairs
esclusi: 30% a quella taglia). Token ora da env CERBERO_TOKEN (default testnet
invariato) -> switch mainnet = solo .env, niente codice. is_mainnet() helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 22:36:26 +00:00
Adriano Dal Pastro 3ac8a46e6a feat(dashboard): trade attivi con data/ora ingresso + tempo in posizione
Nuove colonne 'Ingresso (UTC)' (entry_time) e 'In posizione' (durata dall'ingresso,
formattata m/h/g) con flag stantio. Sostituisce la colonna Eta (staleness) col
tempo di holding effettivo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 22:24:22 +00:00
Adriano Dal Pastro 7ee5b1767f feat(dashboard): solo dati REALI nei trade attivi + valore mercato reale (anche pairs) + attivi sotto il grafico
- entry/mark/unrealized SEMPRE reali (entry di esecuzione vs mark USDC live); il
  feed sim/decisione (testnet inverse dislocato) non e' piu' mostrato come prezzo
- pairs: valore di mercato reale di ENTRAMBE le gambe + PnL non realizzato reale a
  2 gambe (prima 'pairs (z)' senza valore)
- tabella attivi spostata subito sotto il grafico equity

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 22:21:22 +00:00
Adriano Dal Pastro d5a83075f8 fix(dashboard): unrealized usa entry REALE (non sim) per coerenza col mark reale
Le posizioni eseguite mostravano l'entry SIM (feed decisione testnet inverse,
dislocato ~1.3% dal lineare USDC) confrontato col mark REALE -> unrealized gonfiato
(SH01_ETH -1.36% invece di ~-0.25%) e due trade ETH con lo stesso entry sim 1661.95.
Ora entry = real_entry_price/real_entry_a se eseguito; unrealized vs mark reale su
base coerente. Indicatore '⚠sim' quando il feed di decisione diverge dal reale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 22:13:48 +00:00
Adriano Dal Pastro 183209dbd4 feat(dashboard): grafico equity per famiglia + tabella strategie raggruppata per famiglia
- nuovo grafico multi-linea 'Equity per famiglia' (PnL cumulato realizzato di ogni
  famiglia su asse-tempo comune, colori per famiglia, legenda col totale)
- tabella strategie reali raggruppata per famiglia con intestazioni colorate +
  PnL di famiglia; attive prima delle ritirate dentro ogni gruppo
- backend: fam_curves (step-wise cumulato per famiglia dai CLOSE reali), tag fam
  sui trade chiusi

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 21:48:56 +00:00
Adriano Dal Pastro e311d00fe4 fix(dashboard): tooltip equity allineato al mouse (responsive canvas)
Forzare l'altezza del canvas via CSS !important con maintainAspectRatio default
sfalsava la risoluzione interna vs quella mostrata -> hit-test tooltip offset dal
mouse. Fix canonico: canvas in contenitore .chartbox ad altezza fissa + chart
responsive:true/maintainAspectRatio:false + interaction index su tutti e 3 i grafici
(equity, modal, paper).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 21:35:15 +00:00
Adriano Dal Pastro 37ffe92498 docs(report): strategie_attive.html aggiornato a config live (fade 15m, leva 3x)
Era fermo al 2026-06-12 06:17 (pre-swap): diceva fade 'intraday 1h' e 'leva 2x'.
Rigenerato -> numeri post-swap (FULL Sh 8.13/DD 2.47%, OOS 10.86/2.09%) + fix
etichette hardcoded nel generatore (header FADE 1h->15m, leva 2x->3x, nota tabella).
E' la scheda linkata dal modal della dashboard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 21:30:28 +00:00
Adriano Dal Pastro f6287fcd36 feat(dashboard): area PAPER distinta con equity separata
I 4 multi-asset (TR01/ROT02/TSM01/XS01, solo statistica fuori dal pool reale) ora
in una zona dedicata con la PROPRIA equity (capitale nozionale + PnL cumulato del
book paper) e tabella separata. La tabella/fam_pnl/closed dell'area principale
diventano SOLO reali. Curva paper Chart.js (ocra), cliccabile -> stesso modal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 21:25:17 +00:00
Adriano Dal Pastro b6b465878a feat(dashboard): scheda per strategia (modal: curva PnL reale vs sim + trade + link grafici) + versione sistema/strategia + equity restyling
- click su una strategia -> modal con descrizione, versione di creazione/modifica,
  curva PnL CUMULATO reale vs sim (mostra il leakage), trade, e link alla scheda
  dettagliata strategie_attive.html (servita, docs/report montato ro)
- versione di sistema (APP_VERSION) nell'header; per ogni strategia la versione/
  origine documentata (mappa VERSIONS da CLAUDE.md)
- equity restyling: gradiente verde/rosso secondo segno, tooltip con EUR e delta,
  valore+delta in testata, assi in EUR, piu' alto. /api/strategy con guard
  path-traversal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 21:20:33 +00:00
Adriano Dal Pastro 22af19c7f9 feat(dashboard): badge strategie RITIRATE (staleness) + descrizioni per strategia
Le fade 1h sostituite dallo swap 15m sono marcate RITIRATA→15m (rilevate per
status.json stantio >30min, ordinate in fondo, riga barrata). Descrizioni concise
per ogni strategia (da strategie_attive.html) come tooltip + sezione legenda.
Fix rilevazione paper: per directory (portfolio_paper_stats), non per chiave
'weights' (TR01/TSM01/XS01 non ce l'hanno) -> ora i 4 multi-asset sono paper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 21:12:42 +00:00
Adriano Dal Pastro 31f08ddf32 feat(dashboard): frontend web PORT06 — stato live, PnL totale/per-strategia, grafici, trade
Server stdlib http.server (zero dipendenze nuove) che legge data/: KPI equity/PnL/DD,
grafico equity (Chart.js CDN + fallback), PnL per-strategia (barre, realizzato reale),
trade attivi in TEMPO REALE (mark Cerbero best-effort, PnL non realizzato, barre, eta
stantio) e chiusi (ultimi 50). Servizio docker-compose 'dashboard' porta 8787, stessa
immagine, monta data/, restart unless-stopped + healthcheck. Nessuna auth -> rete interna.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 19:46:47 +00:00
Adriano Dal Pastro dc22256e0e feat(report): ledger reale vs backtest — il gate per scalare il capitale
Per gli sleeve eseguiti sim==backtest per costruzione -> reale vs backtest =
fuga di esecuzione (slippage + fee + netting/phantom/sim_fallback). Misura
LEAKAGE sim-reale per-trade, slippage ingressi/uscite, fee reali, sim_fallback,
ledger per-sleeve; verdetto verde/giallo/rosso. Clean-start --since 2026-06-13
(la finestra mobile includerebbe l'incidente testnet pre-fix). Cron host
giornaliero 08:30 UTC --telegram. Read-only, niente rete.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 11:24:19 +00:00
Adriano Dal Pastro 6fe45867f0 docs(CLAUDE): stato live v1.1.31 — leva 3x, swap fade 15m, lineage, stale-guard, ribilancio conservativo
Aggiorna il doc canonico: default PORT06 ora leva 3x + fade 15m (FULL 8.13/OOS
10.86), e i fix di sessione: INIT_LINEAGE, STALE_REAL_POSITION, conservazione
equity al ribilancio. TF sweep (1m/2m/5m/10m) annotato.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 10:21:27 +00:00
Adriano Dal Pastro ab9899b0c4 release: v1.1.31 2026-06-13 10:09:57 +00:00
Adriano Dal Pastro 432c1ad790 fix(ledger): ribilancio conserva l'equity (no doppio conteggio in-position)
Bug: i flat si dividevano l'INTERO total includendo il capitale dei worker in
posizione, che lo trattenevano in piu' -> equity gonfiata di Sigma(capital-alloc)
sugli in-pos. Caso MR02_BTC 15m seedato (181.19) e in posizione al ribilancio
00:01: +4.77 fantasma. allocate(weights, reserved={sid:cap}): gli in-pos
trattengono il loro, i flat si dividono total-Sigma(reserved) per peso
rinormalizzato -> Sigma alloc == total sempre. Parita' runner intatta
(5.8e-08). Test conservazione (ledger + runner). Suite verde.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 10:09:36 +00:00
Adriano Dal Pastro 85043bf2d3 feat(reconcile): guard STALE_REAL_POSITION — rileva posizioni reali non gestite
Caso MR02_BTC 1h (2026-06-13): ritirato dallo swap a 15m mentre short reale ->
short nudo (TP perso nel netting), reconciler cieco perche' lo status fermo
contava ancora come libro. compute_stale_real_positions: worker con
real_in_position e status.json fermo >15min = non gestito -> alert
STALE_REAL_POSITION. Discriminante = staleness (venue-agnostico: cattura
ritirati-da-swap/crashati). Gira dal cron host :40 (no rebuild). 3 test nuovi,
suite 132 passed. Orfano chiuso a mano (testnet, +0.85 netto).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 09:43:47 +00:00
Adriano Dal Pastro 643ff7f943 feat(live): INIT_LINEAGE — eredita' capitale al cambio timeframe + sweep migliorie serale
Worker nuovo (no status.json) eredita capital/real_capital dal gemello piu'
recente stessa strategia+asset (mai la posizione): niente piu' seed manuale
al prossimo swap. 3 test nuovi, suite 129 passed.
Ricerca: gate 10m ADD bocciato (OOS Sh 10.86->10.76, watchlist chiusa);
XSEC breadth 8->14 Deribit BOCCIATO (gambe flat 91-99% corrompono il ranking)
ma promettente su dati HL reali (-6%->+9% stessa finestra) -> sblocco = routing
dati Hyperliquid quando la storia sara' sufficiente.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 21:18:30 +00:00
Adriano Dal Pastro 12e71d4c8b research: fade TF sweep 1m/2m/5m/10m/30m — 15m confermato, 10m in watchlist, 1m/2m chiusi
Frontiera Sharpe monotona al scendere del tf ma margine fee si assottiglia:
MR02_BTC muore a fee2x a 5m (-1.70); MR02 sotto i 15m e' fee-death nel regime
corrente (1m -64%). 1m: flat share ETH 25.6% + niente storia full -> chiuso.
Corr col 15m live: 5m 0.46 / 10m 0.53. Fix resample unit-safe (pandas 2.x ms).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:56:15 +00:00
Adriano Dal Pastro 378ec7fb43 release: v1.1.30 2026-06-12 20:47:45 +00:00
Adriano Dal Pastro f78380596a feat(live): SWAP fade 1h -> 15m su PORT06 (scelta utente, gate fade15m_port06_gate)
FADE specs tf=15m (sid invariati -> pesi/alloc intatti; DIP01 resta 1h);
builder canonico allineato (combine_portfolio.FADE_TF) -> parita' delle due
facce; regression-lock aggiornato al nuovo baseline (FULL Sh 7.34->8.13
DD 3.46->2.47, OOS Sh 10.07->10.86 DD 1.48->2.09). Runner invariato (fetch
15m gia' esistente dal blend pairs). Suite 126 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:47:44 +00:00
Adriano Dal Pastro 82a1c60e08 research(gate): fade 15m PROMOSSI al gate PORT06 (ADD: OOS Sh 10.07->10.48, DD giu' su entrambe le finestre)
Parita' builder esatta; corr 15m-1h media 0.26 (vera diversificazione);
flat-entry-skip OK su ETH (edge reale, non stale-print). Raccomandata ADD.
Deploy plumbing elencato nel diario (non eseguito).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:37:53 +00:00
Adriano Dal Pastro 515193a203 docs(research): XEX — discordanze Deribit testnet vs Hyperliquid
Spread D/H mean-reverting (half-life 3-7h); su BTC/ETH inverse il BOOK stesso
e' dislocato 1-2% (reale, eseguibile), su DOGE/SOL e' stale-print illusion
(book allineato a HL, 87%/35% barre flat). Edge testnet-only: non deployato,
record + telemetria. Vedi diario.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:29:11 +00:00
Adriano Dal Pastro 612f2bfced feat(live): reconcile resting + orphan single-leg + circuit-breaker venue-lock + FEED_BOOK_GAP
Codice della tornata v1.1.27/28 (gia' in produzione, mai committato):
- reconcile_account: estensione ordini RESTING (FILLED_UNBOOKED/MISSING/STALE,
  caso MR02_BTC: TP fillato di notte scoperto ore dopo) + expected_resting in books
- strategy_worker: orphan_legs su REAL_CLOSE_PARTIAL anche single-leg, persistito
- execution: circuit-breaker su venue-lock admin (stop ordini dopo errori ripetuti)
- runner/hourly_report: alert FEED_BOOK_GAP + timestamp closed trades
- cerbero_client: get_open_orders (merge all + trigger_all)
Test: 12 nuovi, suite completa 126 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:29:02 +00:00
Adriano Dal Pastro a2d581691a release: v1.1.29 2026-06-12 20:23:51 +00:00
Adriano Dal Pastro 5fe53841e9 feat(live): leva 2x -> 3x su PORT06 (scelta utente, frontiera ACCEL50)
PAIRS position_size_family 0.20 -> 0.13 per conservare l'esposizione validata
~0.40 (gate 2026-06-07): la leva accelera le famiglie con stop, non i no-stop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:23:43 +00:00
Adriano Dal Pastro e0257c6c88 docs(research): ACCEL50 — frontiera di leva PORT06 + probe fade 15m verso i 50 EUR/giorno
Leva 2->3/4 dimezza i tempi (OOS CAGR 111->206/343%, DD full 3.5->5.2/6.9%).
Fade 15m passa il probe (6/6 sleeve OOS+, fee 2x OK, BTC 15m > 1h a meta' DD).
Pairs nuove e PAXG bocciati: stale-print illusion (gambe alt 88-98% barre flat).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:21:51 +00:00
Adriano Dal Pastro 6f294e3889 docs(diary): chiusura incidente testnet — flatten + orfani risolti, reconciler OK
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:04:56 +00:00
Adriano Dal Pastro 1a0b601fab release: v1.1.28 2026-06-12 12:35:49 +00:00
Adriano Dal Pastro 2b9adaeea3 docs(diary): reconcile resting + FEED_BOOK_GAP + epoca v1.1.26 (tornata v1.1.27)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:24:37 +00:00
Adriano Dal Pastro e7fd286fe0 release: v1.1.27 2026-06-12 12:22:10 +00:00
Adriano Dal Pastro 003875f2c3 docs(report): strategie_attive.html rigenerato su dati freschi (tutti gli 8 asset al 2026-06-12)
- parquet rinfrescati post-fix downloader: BTC+ETH 1h/15m/5m completi via v2,
  alt 1h incrementali (erano fermi al 28-29/05, panel multi-asset incoerenti)
- make_strategy_doc.yearly_stats: clamp di j (l'engine live-path lascia j>=n
  per il trade aperto al bordo serie -> IndexError coi dati freschi)
- header v1.1.26, card XS01 con phase-tranching, metodologia con verita'
  d'esecuzione/netting. Backtest canonico STABILE su +2 settimane di dati:
  FULL Sharpe 7.34 / DD 3.46 — OOS 10.07 / 1.48 (identico al 2026-06-11)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 06:17:39 +00:00
Adriano Dal Pastro e18adba4a6 fix(data): downloader su endpoint v2 + guard anti-troncamento (post-mortem refresh notturno)
Il refresh notturno e' rimasto 10h appeso e ha TRONCATO i parquet BTC al 2018:
cerbero-mcp ha rimosso l'endpoint legacy /mcp-deribit/tools/get_historical (404),
il downloader skippava in silenzio OGNI chunk (3 retry + sleep esponenziale x
migliaia di chunk) e a fine giro scriveva comunque il file con la sola Fase 1
storica. Aggravante: TIMEFRAMES includeva '1m' (1 giorno/richiesta = ~3000
richieste/asset). ETH salvato in tempo (kill prima della sovrascrittura);
BTC ripristinato via v2 (1h/15m/5m completi 2018->oggi in ~30 min).

- _fetch_deribit -> endpoint v2 /mcp/tools/get_historical (lo stesso del runner)
- guard chunk: >50% skippati = endpoint rotto -> RuntimeError, niente parziali
- guard anti-regressione in download_asset: mai sovrascrivere un parquet con
  dati che finiscono PRIMA dell'esistente
- '1m' fuori da TIMEFRAMES (refresh torna 5m/15m/1h; il 1m ad-hoc se serve)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 06:08:42 +00:00
Adriano Dal Pastro 36ac2426a1 release: v1.1.26 2026-06-11 21:36:30 +00:00
Adriano Dal Pastro 82c05f6f81 fix(exec): code-review serale — guard anti-posizione-nuda sul netting + verità per-frazione
7 finder paralleli sul diff della giornata (8adf388..HEAD), fix dei confermati:

CRITICI (money-path):
- close_amount: GUARD stato-stantio sul fallback netting — il residuo
  non-reduce-only e' consentito solo fino al gap (conto reale - libri degli
  altri worker - orfani) nella direzione della chiusura (src/live/books.py =
  fonte unica, usata anche dal reconciler). Senza guard, un close su stato
  stantio (DSL scattato in outage, flatten manuale) APRIVA una posizione nuda
  a taglia piena bookata come 'chiusura verificata'. Fail-safe se il gap non
  e' calcolabile. Check polvere PRIMA di _quantize_step (che clampa al lotto
  minimo: un residuo 1e-17 diventava un ordine nudo da un lotto).
- _real_close: market_amt = filled_amount anche a merged verified=False (i
  contratti chiusi dal reduce-only non si buttano se il leg netting fallisce);
  REAL_CLOSE_PARTIAL non piu' gateato su verified (era soppresso proprio nel
  caso parziale reale).
- pairs: verita' per-FRAZIONE di gamba (gross proporzionale al fillato, orfano
  = solo il residuo — prima falsava reconciler e real_capital della parte gia'
  chiusa); REAL_OPEN_PAIR booka filled_amount; docstring applied corretta.
- open_pair unwind: chiude il FILLATO, non il richiesto (senza il cap silenzioso
  del reduce-only avrebbe mangiato quota altrui).
- place_tp_limit: quantize CONSERVATIVO (sell=floor/buy=ceil) — il rounding al
  tick piu' vicino poteva mettere il resting oltre il TP sim -> tocco genuino
  classificato phantom sistematicamente.

ROBUSTEZZA/OSSERVABILITA':
- runner: WORKER_ERROR_STREAK a 5 e poi ogni 50 poll + recovery RIPRESO +
  flag real_in_position nell'alert (prima: one-shot a n==5, poi silenzio).
- _tp_phantom: TTL 120s sul verdetto (era ~50 HTTP/h per worker a barra
  fantasma); merge notes con entrambi gli order_id (audit-trail).
- reset_flatten: _quantize_step Decimal (round float produceva amount che
  Deribit rifiuta); hourly_report: book XS01 con gambe SHORT visibili (abs).
- validate_xsec_worker: POS/LEV importati (non hardcoded); xs01_tranche:
  regression-lock ESEGUITO vs xsec_sim; reconcile su src/live/books;
  drift_monitor: rolling vettoriale + exit code 1 su warn.

Test: +4 guard/dust, fixture filled_amount -> 114 passed.
Deferiti (TODO): resting esposti al netting, lifecycle orphan_legs, finestra
trade-history TP_PHANTOM, validazione feed a monte, dedup minori.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:36:30 +00:00
Adriano Dal Pastro 3f1feb1a6f docs(report): generatore aggiornato — tranching XS01 + verità esecuzione/netting in metodologia
- card XS01: paragrafo phase-tranching v1.1.21 (3 sub-book sfasati, gate plateau
  K=2/K=3, PORT06 OOS 10.07->10.15 DD 1.48->1.38)
- metodologia: bullet v1.1.23-25 (TP_PHANTOM conferma dal book reale, ledger su
  amount fillato, chiusure netting-aware, reconciler ACCOUNT_DRIFT, drift-monitor)

L'HTML rigenerato segue in un commit separato (in coda dietro il refresh parquet).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:07:06 +00:00
Adriano Dal Pastro cfdd8f6a72 release: v1.1.25 2026-06-11 20:54:44 +00:00
Adriano Dal Pastro 844a9d0e22 feat(exec): netting delle chiusure market — il residuo reduce-only cappato/respinto va in market puro
close_amount ora: (1) tenta il market reduce-only (sicurezza storica: un bug di
stato filla 0 invece di aprire posizioni); (2) il residuo cappato/respinto dal
netting di conto (worker in direzioni opposte sullo stesso strumento) viene
rieseguito in MARKET PURO con label '|net' — muove il conto esattamente del
delta del libro = netta contro le quote opposte. Niente piu' gambe pairs orfane
ne' close cappati per costruzione (close_pair passa da close_amount).

- _merge_close_fills: il chiamante riceve UN Fill (prezzo medio pesato sui fill,
  fee sommate, filled_amount totale, verified se copre il richiesto, notes
  'netting' quando il fallback scatta)
- worker single-leg + pairs: evento NET_CLOSE (log jsonl + Telegram) a ogni
  fallback — osservabilita' della frequenza dei conflitti di netting
- sicurezza persa sul residuo coperta dal reconciler orario (ACCOUNT_DRIFT);
  orphan_legs/REAL_CLOSE_PARTIAL restano come ultima difesa
- 4 test nuovi (full r-o senza fallback, cappato, respinto, doppio fail) -> 110 passed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 20:54:43 +00:00
Adriano Dal Pastro 22b77668f1 feat(ops): reconciler read-only conto vs libri worker (primo passo verso il position-manager)
scripts/analysis/reconcile_account.py: per strumento USDC confronta l'atteso
(somma quote reali dai status.json: single-leg + pairs 2 gambe + orphan_legs
registrati = drift spiegato) col conto reale (get_positions, size/mark -> coin).
Tolleranza 1.5x step contratto, anti-race (ricontrollo a 10s), alert Telegram
ACCOUNT_DRIFT con --telegram. SOLO lettura, gira dall'host (cron orario :40,
nessun deploy). Al primo run: vero positivo su BTC (libro MR02 short 0.0028 vs
conto flat — TP fillato dallo spike reale, si riconcilia alla chiusura sim).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 20:43:17 +00:00
Adriano Dal Pastro 521447fce6 release: v1.1.24 2026-06-11 20:05:43 +00:00
Adriano Dal Pastro 429fa01c97 fix(exec): verità contabile sul netting — filled_amount, gambe orfane, tick isolato
Da audit live (3 indagini parallele, diario 2026-06-11-system-audit.md): il modello
quote-per-worker reduce-only si rompe con direzioni opposte sullo stesso strumento
(pairs long ETH vs fade short ETH) — close cappati bookati pieni, 3 gambe pairs mai
eseguite col PnL sim nel ledger reale, conto short 0.027 ETH oltre i libri
(riallineato a mano + DSL orfano cancellato).

- Fill.filled_amount (order.filled_amount): TUTTI i ledger usano il fillato reale
- REAL_CLOSE_PARTIAL (log+alert) su close cappato; residuo orfano dichiarato
- pairs: PnL solo per gambe verificate; gamba respinta -> orphan_legs persistito
  + alert PAIR_LEG_ORPHAN; applied solo con entrambe le gambe (else sim_fallback)
- REAL_DIVERGENCE anche su jsonl (prima solo Telegram)
- runner: tick isolato per-worker + WORKER_ERROR_STREAK a 5 fail consecutivi

Strutturale APERTO (decisione utente, opzioni nel diario): position-manager
centrale / sotto-conti per famiglia / status-quo monitorato.

Test: +2 (partial-close, orphan-leg), fixture filled_amount -> 106 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 20:05:42 +00:00
Adriano Dal Pastro 8a2b065dd7 chore(analysis): dedup engine gate PORT06 + drift monitor giornaliero + impact bfill
- _port06_gate_common.py: build_trades_variant/equity_from_trades/port_metrics/dd
  fattorizzati dai 3 gate exit16/trendmax/dip01 (-214 righe duplicate). Nessun
  copy-drift trovato; versione promossa = trendmax (superset con hurst_mask).
  Output dei 3 gate verificato BYTE-IDENTICO prima/dopo. dip_trades resta nel suo
  script (sibling deliberato long-only/orig_gap, non una copia).
- drift_monitor.py: rolling-return per famiglia vs distribuzione storica propria
  (warn sotto p5; oggi: FADE 120g al p2). In crontab host giornaliero 07:15 UTC
  con report Telegram. Osservabilita', non filtro di trading.
- daily_equity_bfill_impact.py: bug bfill _daily_equity QUANTIFICATO -> non
  materiale (OOS invariato per costruzione, FULL DD 3.46->3.67 col fix, nessun
  verdetto gate a rischio). Lasciato documentato in TODO, niente fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 20:04:23 +00:00
Adriano Dal Pastro 51baed22e4 release: v1.1.23 2026-06-11 19:32:24 +00:00
Adriano Dal Pastro 12625b3315 feat(exec): gate TP_PHANTOM — il tocco TP va confermato dal book reale, non dal feed
Il feed testnet stampa wick che 'toccano' il TP intrabar senza che il prezzo
abbia mai scambiato al livello: il sim bookava +4% fantasma a bars_held=0 e
_real_close chiudeva A MERCATO una posizione col resting TP a zero fill
(-fee/spread a giro, 14 giri l'11-06). Ora StrategyWorker._tp_phantom sopprime
l'exit take_profit quando: tocco sim + resting LIMIT zero-fill + prezzo corrente
che non ha raggiunto il livello (il limit sul book reale e' l'oracolo del tocco).
Zero parametri (verita' d'esecuzione, non filtro di strategia); SL close-confirm
e max_bars restano attivi nello stesso tick; fill parziale/prezzo oltre il
livello/worker non eseguito/errore rete -> comportamento storico. Log TP_PHANTOM
dedup per barra + alert Telegram una tantum. 5 test nuovi (104 passed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 19:32:24 +00:00
Adriano Dal Pastro 15520dda70 release: v1.1.22 2026-06-11 19:18:55 +00:00
Adriano Dal Pastro 2ae6d7afe6 fix(report): conta i CHIUSI col flag win del worker (PnL REALE), non col net_return sim
Col real-truth ledger net_return resta il numero SIM diagnostico: sui TP fantasma
da spike-print testnet (ETH, bars_held=0: lo spike genera il segnale E tocca il TP
intrabar; il reale chiude a mercato pagando fee/spread) il report Telegram diceva
26 positivi / 0 negativi mentre il reale era 11/15. Ora collect() usa ev.win
(real-truth-aware, fallback nr>0 per eventi storici); i PnL per-motivo erano gia'
reali. TODO: nota monitor sul churn da spike (niente filtri anti-spike nei segnali:
artefatto testnet, real-truth gia' contabilizza giusto).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 19:18:55 +00:00
Adriano Dal Pastro b208417f2d release: v1.1.21 2026-06-11 13:29:25 +00:00
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
Adriano Dal Pastro ba5ec7bd6b docs: aggiorna strategie_attive.html + CLAUDE/TODO a v1.1.20 (XS01, real-truth, 19 sleeve)
- make_strategy_doc: nuova famiglia XSEC — card XS01 (meccanismo, dispersion-gate
  disp_min=0.0313, gate PORT06 OOS 10.07->10.37) con grafico book+gate e tabella
  per anno (engine canonico ungated); pesi aggiornati a 19 sleeve (5.69/5.26/2.94%);
  header con real-truth ledger e 4 book paper; bullet metodologia real-truth;
  ripuliti i riferimenti 'oggi' (EXIT-16 su DIP01, fix punto-10 SH01)
- strategie_attive.html rigenerato: v1.1.20, backtest canonico FULL Sharpe 7.34 /
  DD 3.46% — OOS Sharpe 10.07 / DD 1.48% (19 sleeve, XS01 incluso)
- CLAUDE.md: sezione XS01 (famiglia XSEC, gate, dispersion-gate live-only, FC01
  scartata), copertura reale 15/19, varianti gioco options/session/grid in struttura
- TODO.md: stato esecuzione aggiornato (6 pairs, XS01 paper, real-truth)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 10:00:48 +00:00
Adriano Dal Pastro 8d69a0cef5 feat(games): sessioni 2-3 Blind Traders (opzioni/session/grid) + gate PORT06 e tooling reset
- Gioco GRID TRADERS (sessione 3, regola STRATEGIA_GRIGLIA.md): grid_engine
  (backtest causale fee-aware della griglia geometrica), grid_brief (digest
  anonimo per dimensionare la griglia), grid_arena (torneo 100 agenti);
  diario docs/diary/2026-06-10-grid-traders-game3.md
- Gioco OPZIONI: options_engine (BS + skew fittato + DVOL storica),
  options_arena, opt_calibrate (superficie premi REALE da cerbero-bite)
- Gioco SESSION: session_engine/session_arena (pattern orari intraday)
- arena: vincolo GAME_NO_LIVE=1 (vieta pairs e fade zscore/breakout/momentum
  gia' live, coercizione a trend/ma_cross) + normalize del candidato PRIMA
  della valutazione nel hill-climb
- Gate: grid_game_gate (griglia ETH vincitrice vs PORT06, mark-to-market),
  pairs30m_gate (ETH/BTC 30m ridondante col 15m gia' deployato?)
- reset_flatten: flatten one-shot del conto testnet per il reset portafoglio
- .gitignore: data/portfolio_paper_stats/ (stato runtime sleeve paper-only)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 09:49:17 +00:00
Adriano Dal Pastro 8adf388e86 release: v1.1.20 2026-06-10 21:42:12 +00:00
Adriano Dal Pastro e25d5db6ad feat(xsec): dispersion-gate XS01 live (disp_min=0.0313) — Sharpe 3.46, PORT06 OOS 10.07->10.37; FC01 funding-carry scartata
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 21:42:12 +00:00
Adriano Dal Pastro d1180ef25b release: v1.1.19 2026-06-10 21:24:00 +00:00
Adriano Dal Pastro 61fcc29c5b fix(xsec): cast numpy->Python in _save/_close_book — int64 rompeva json.dumps e bloccava il runner in error-streak
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 21:23:52 +00:00
Adriano Dal Pastro 5e557fe998 release: v1.1.18 2026-06-10 12:23:58 +00:00
Adriano Dal Pastro cc39c36c08 feat(live): REAL-TRUTH ledger — capital aggiornato dal PnL dei fill reali, sim ridotto a diagnostica
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 12:23:52 +00:00
Adriano Dal Pastro d2948d582b release: v1.1.17 2026-06-09 21:38:12 +00:00
Adriano Dal Pastro a85289d7c7 feat(xsec): XS01 reversione cross-sectional (8 asset) -> PORT06 PAPER
Famiglia NUOVA trovata in sessione (dopo aver scartato trend/breakout/seasonal/
opzioni/funding come rumore): ogni 12h long i perdenti relativi / short i vincenti
su 8 asset, market-neutral. Scorrelata (~0) da pairs e fade -> diversificatore.

- engine canonico scripts/strategies/XS01_cross_sectional.py (no look-ahead, plateau
  OOS Sharpe 2-3.9, 5/5 anni+, edge concentrato 2025, cost-sensitive ~0.35% RT).
- src/live/xsec_worker.py CrossSectionalWorker: validate_xsec_worker == backtest ESATTO
  (4993/1427 trade). Mirror della cadenza engine (entry-to-entry = hold+1).
- gate PORT06: +XS01 -> OOS Sharpe 9.66->10.07, FULL DD 3.68->3.46 (OOS DD +0.17pp,
  risk-contrib 2.2%). xsec_port06_gate.py.
- wiring: _defs XSEC in PORT06 (19 sleeve, family XSEC), build_everything, runner
  kind=xsec, asset_days da supported (fix fetch alt anche per paper sleeves), paper.
- 8 gambe -> niente exec reale -> gira PAPER. Regression-lock 18->19, FULL 7.20->7.34,
  OOS 9.66->10.07. 93 test verdi. Diario 2026-06-09-xs01-cross-sectional.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:38:05 +00:00
Adriano Dal Pastro d3dab57532 feat(games): sessione 2 del gioco Blind Traders su timing diversi (30m/2h/4h)
- engine: resampling (_RESAMPLE) per 30m/2h/4h/1d + TF_BPM esteso -> nuovi timing.
- arena/run_game: TIMEFRAMES estesi, out_name e GAME_SPECS_DIR/GAME_OUT parametrizzati
  (game 1 non sovrascritto).
- Risultato: 10 finalisti tutti 30m pairs ETH/BTC (vincitore #36: OOS Sh 12.3, 43 tr/mese).
  La regola >=10 trade/mese filtra i tf lunghi (4h: 4/33 qualificati). Conferma la
  frontiera frequenza-vs-edge. Diario 2026-06-09-blind-traders-game2.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 13:01:34 +00:00
Adriano Dal Pastro abc1c1cb80 docs(diary): analisi robustezza ETH/BTC 15m — robusto come segnale, fragile sui costi
Plateau 16/16, 8/9 anni+, OOS Sh 17.6, non-artefatto-flat (83% Sharpe), corr 0.37.
MA fee-sweep: il 15m va negativo a ~4x i costi mentre il 1h regge ~6x (frequenza 5x,
margine di costo sottile). Conferma mezza-size e NO allo swap; giudice = ledger reale.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 12:34:08 +00:00
Adriano Dal Pastro 1a9032feaa docs(diary): statistiche per-anno delle 15 sleeve attive in real
Snapshot post-BLEND 15m (v1.1.16): PnL%/n-trade per anno + maxDD per ogni sleeve
che esegue reale (6 fade + DIP01 + 6 pairs + 2 SH01), engine path-live, fee incluse.
+ aggregato PORT06 per anno. Esclusi i 3 book PAPER (TR01/ROT02/TSM01).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 12:12:53 +00:00
Adriano Dal Pastro 90c4726a31 docs: aggiorna CLAUDE.md + strategie_attive.html col BLEND ETH/BTC 15m
- CLAUDE.md: default PORT06 (FULL 7.20/OOS 9.66, 18 sleeve), paragrafo BLEND 15m
  flat-skip (origine gioco Blind Traders, gate, validazione, caveat slippage),
  copertura reale ~83% (6 pairs), scripts/games/ + pairs15m_* nella struttura.
- make_strategy_doc.py: header dinamico dal backtest, colonna ETH/BTC·15m nella
  tabella pairs, card PR01 col blend, conteggi sleeve aggiornati -> rigenerato HTML.
- pairs_sim_flat: ritorna yearly_n (parita' con pairs_sim, usato dal doc).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:59:36 +00:00
Adriano Dal Pastro be72b157cf release: v1.1.16 2026-06-09 11:48:26 +00:00
Adriano Dal Pastro d25d897fd1 feat(pairs): attiva ETH/BTC 15m flat-skip in PORT06 (BLEND, mezza size)
Origine: gioco "Blind Traders" (100 agenti ciechi su BTC/ETH anonimizzati) ->
vincitore = spread ETH/BTC reversion a 15m. Testato sul serio col gate PORT06:
non duplicato (corr 1h vs 15m = 0.37), robusto (16/16 celle Sharpe>1), edge NON
artefatto delle candele flat ETH 15m (filtrandole resta l'83% dello Sharpe).

Percorso live costruito e validato:
- pairs_research.pairs_sim_flat: engine generalizzato con exit LIVE-REALIZABLE
  (arma exit_ready, esce alla 1a barra pulita); regression-lock a pairs_sim.
- PairsWorker: flat_skip + exit_ready + rilevamento flat da OHLC (1h byte-exact).
- runner: fetch diretto dei timeframe sub-orari + override position_size per-sleeve.
- validate_worker_pairs: replay worker == backtest a 15m (8452 vs 8453 trade).
- _defs/build_everything: sleeve PR_ETHBTC_15M (mezza size, pos 0.10) -> PORT06
  FULL 6.43->7.20, OOS 8.58->9.66, DD giu'. Rischio bilanciato col 1h.
- smoke live: Cerbero serve candele 15m fresche; worker ticca.

Diari docs/diary/2026-06-09-*. Caveat slippage: mezza size = blend-tilt prudente.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:48:15 +00:00
Adriano Dal Pastro 5d45f4ef6e research(cerbero-bite): validazione edge credit-spread su prezzi reali
Backtest dell'edge credit-spread ETH di cerbero-bite con la chain reale
(data/options/). Esito: NON edge robusto su ciclo completo. Entry cw reale
0.106 (short 9.4% OTM, max-loss/credito 8.4x); hold-to-expiry EV -1.0 cr/trade
7/9 anni negativi; managed (skew) EV -0.02 cr win-rate 37%. Il "+0.48%/mese"
era artefatto di finestra calma; coda concentrata col fade ETH. TODO aperto:
calibrazione esatta credito (bid/ask + griglia) per EV managed definitivo.
Script riprendibile + diario.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 08:08:53 +00:00
Adriano Dal Pastro cfc48cdef4 release: v1.1.15 2026-06-09 07:43:09 +00:00
Adriano Dal Pastro aa4361ddde feat(live): MR02/ETH stop-largo sl_atr 3.0 (miglioria gated, regola sl=0 rispettata)
Esito ricerca sostituto/miglioria MR02/ETH: nessun sostituto batte il live
EXIT-16; la miglioria deployabile e' allargare lo stop close-confirm da
sl_atr 2.0->3.0 sul solo MR02_ETH. Gate PORT06 (swap MR02_ETH): FULL Sh
6.73->6.77 DD 3.67->3.30, OOS 8.80->8.82 DD 1.23 (domina il live). Cattura
il beneficio del no-SL tenendo lo stop di catastrofe: worst-trade -50% vs
-65% del no-SL -> regola "mai sl=0" rispettata. Override scoped a MR02/ETH
(_fade_params); altri 5 fade invariati. 93/93 test ok.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 07:31:55 +00:00
Adriano Dal Pastro c997bce00e feat(options): attach_market() panel helper (regime panel, causal, no look-ahead)
attach_market(df, asset): merge_asof causale del pannello market_snapshots
(spot/VRP/funding/net-GEX/gamma-flip/liquidation) sul prezzo, pronto per
regime_lab. Fix look-ahead: merge su datetime tz-aware (ns-aligned), MAI
astype(int64) su datetime (darebbe ns -> match all'ultimo snapshot = leak).
Copertura reale documentata: net-GEX denso solo da ~2026-05-01 (~5-6 sett,
singolo regime) -> infrastruttura pronta, edge validabile solo forward.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 07:24:44 +00:00
Adriano Dal Pastro df8bfbceaa research(mr02eth): ricerca sostituto + integrazione dati opzioni cerbero-bite
Ricerca sostituto/miglioria a MR02/ETH (127 strategie + 18 overlay opzioni,
verifica avversariale, gate PORT06). Esito: miglioria = no-SL (gia' ~catturata
da EXIT-16 live); tail-hedge opzioni NO-GO empirico su prezzi reali.

Infrastruttura opzioni REALE (muro ARGO/GEX caduto):
- options_fetcher.py: importa lo storico per-strike + market_snapshots da
  cerbero-bite -> data/options/ (chain bid/ask/IV/greche/OI + pannello regime
  con net-GEX dealer/gamma-flip/VRP/funding/liquidation).
- options_chain.py: loader + skew_curve/premium_levels (aggregati reali) +
  quote() causale + load_market() (pannello regime).
- option_overlay_lab.py: overlay opzioni BS su DVOL (pricing sintetico).
- mr02eth_port06_gate.py / eth_collar_gate.py: gate swap-sleeve e collar.
- mr02eth_search/options.workflow.js: i 2 workflow.

Numeri reali: skew put 10%OTM ~1.1 (liquido), premio ~1.0%/mese; niente strike
10%OTM a 24h (overlay per-trade infattibile); collar standing paga nei crash ma
net-negativo a PORT06 (alza OOS DD). Diario + CLAUDE.md aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 07:05:00 +00:00
Adriano Dal Pastro cff40283f4 docs(report): PR01 + SH01 in esecuzione reale, pool real-only 14 sleeve
Aggiorna strategie_attive.html (e il generatore make_strategy_doc.py) allo
stato post v1.1.12/1.1.13/1.1.14:
- PR01 (5 coppie) e SH01 (BTC/ETH): badge SIMULATO -> ESECUZIONE REALE
- header: pool live real-only 14 sleeve (3 multi-asset in statistica)
- metodologia: esecuzione reale ~81% (fade+DIP single-leg, pairs 2 gambe,
  SH01 a orizzonte); restano simulati solo TR01/ROT02/TSM01

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 15:08:30 +00:00
Adriano Dal Pastro ce25be0475 release: v1.1.14 2026-06-08 11:29:00 +00:00
Adriano Dal Pastro 0e8ad9c5f0 feat(live): portafoglio REALE-only — i 2000EUR ai soli 14 sleeve eseguiti, paper fuori dal pool
Decisione utente: il conto reale deve mostrare il risultato puro. I 2000 si dividono
SOLO tra i 14 sleeve reali (6 fade + DIP01 + 5 pairs + SH01). I 3 multi-asset
(TR01/ROT02/TSM01), non eseguibili in reale, escono da pool/pesi/ledger e girano solo
per statistica.

- portfolios.yml: paper_sleeves [TR01,ROT02,TSM01].
- runner: live_specs (pool/ledger) vs paper_specs (capitale nozionale fisso = total/N,
  data/portfolio_paper_stats/, ticcati ma MAI in update_equity).
- hourly_report: sezione PAPER da portfolio_paper_stats, etichettata 'fuori dal conto reale'.

Pesi reale-only (14, cap): non-cappati 0.087, PAIRS 0.33, SHAPE 0.0588. Costo misurato
e accettato: FULL DD 3.96->5.34%, OOS DD 1.36->1.70% (perde i diversificatori PAPER,
corr 0.07); Sharpe ~invariato. Test 93/93. Diario docs/diary/2026-06-08-real-only-portfolio.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:29:00 +00:00
Adriano Dal Pastro 3d3ebd85d2 release: v1.1.13 2026-06-08 11:18:06 +00:00
Adriano Dal Pastro 0b900a5420 feat(live): SH01 in esecuzione reale shadow (diversificatore decorrelato)
L'infrastruttura no-TP esisteva gia': _place_real_tp ritorna subito senza TP,
_real_close chiude tutta la quota a market reduce-only (exit a orizzonte H=12).
Bastava: (1) _exec_for accetta kind 'ml', (2) SH01 in execution.sleeves.
Disaster-bracket on-book = unica protezione di coda di SH01 (esce a H=12 ben prima
del -30%). Motivo: SH01 e' il diversificatore piu' decorrelato (corr 0.07) — senza
i 5 sleeve PAPER il DD del portafoglio sale 3.96->5.35%, OOS Sharpe 8.58->8.27.

Test: SH01 open/close reale senza TP + disaster bracket (93/93). Copertura reale
ora ~81% (resta paper solo TR01/ROT02/TSM01, bloccati dal capitale).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:18:06 +00:00
Adriano Dal Pastro c6970d96c9 release: v1.1.12 2026-06-08 10:26:23 +00:00
Adriano Dal Pastro 2da19c3a7e config(pairs): attiva pairs_enabled (esecuzione reale 2 gambe) a conto flat
Smoke testnet end-to-end verificato (v1.1.11). Conto Deribit flat su tutti gli
strumenti pair confermato in tempo reale prima dell'accensione. I 5 pairs ora
eseguono reale shadow a 2 gambe; shadow pulito dalla prossima apertura pair.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 10:26:23 +00:00
Adriano Dal Pastro 59901e236b release: v1.1.11 2026-06-08 10:19:56 +00:00
Adriano Dal Pastro c655604131 feat(live): executor a 2 gambe per i pairs (PairsExecutionClient, shadow) — pronto, spento
PairsExecutionClient (compone ExecutionClient): open_pair (2 market long A/short B,
verifica per-gamba, LEG-RISK unwind se una sola filla), close_pair (2 reduce-only via
close_amount, MAI close_position), register_contract (fetch spec USDC). Spec LTC/ADA/SOL
aggiunti. PairsWorker: ledger reale shadow a 2 gambe resume-safe (_real_open_pair/
_real_close_pair, PnL per gamba dir A=+d/B=-d, doppio arrotondamento riportato). Runner:
pairs_executor gated su execution.pairs_enabled (false di default).

Validazione: test 92/92 (open/close, leg-risk unwind, resume) + smoke testnet
end-to-end (open 2 gambe verificate, close reduce-only, PnL reale -0.039 vs sim -0.036,
conto flat). Smoke ora aborta se ci sono posizioni di produzione + usa solo close_amount.

NB incidente testnet documentato (diario): pulizia manuale con close_position ha flattato
le quote shadow dei fade sul conto condiviso -> auto-riconciliazione al prossimo close.
Lezione: mai close_position su strumenti condivisi.

pairs_enabled resta FALSE: accendere con finestra a conto flat + osservazione.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 10:19:56 +00:00
Adriano Dal Pastro a2f1b960ec research: gate PORT06 index_comp_disp — promosso marginale, documentato e rimandato
Decorrela bene (corr 0.06 col MASTER, smentisce il timore ridondanza) ma beneficio
OOS nullo (Sharpe 8.58->8.56, DD 1.36->1.40); migliora solo FULL DD 3.96->3.73. Non
deployato (wiring + simulato per guadagno OOS nel rumore). Gate riusabile committato.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 10:06:34 +00:00
Adriano Dal Pastro d277d02cf5 release: v1.1.10 2026-06-08 10:00:16 +00:00
Adriano Dal Pastro f509f51543 fix(live): ROT02/TSM01 scartano la barra 1d in formazione (forming-bar in _panel)
Code-review follow-up: i worker daily valutavano momentum/regime e bookavano il
return sulla candela 1d PARZIALE al primo poll dopo mezzanotte UTC, poi last_bar_ts
bloccava la rivalutazione a giorno chiuso (stessa classe del bug gia' fixato su
fade/TR01/Pairs). Fix in _panel (un punto solo -> ROT02 e TSM01 lo ereditano) via
src.live.bars.last_bar_is_forming. Replay honest invariato (dati storici: nessuna
barra in formazione). Test: 88/88 + parity drop forming daily bar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 10:00:16 +00:00
Adriano Dal Pastro 7f4378190e research: ricerca dispersion/correlation index (165 agenti) — 2 edge su 60, nessun nuovo motore
Workflow 60 celle (15 famiglie x 4 finestre), verifica avversariale 2-skeptic.
Esito: 2 edge confermati causali (index_comp_disp W168, rel_idio_fade W24), ma
entrambi fade-BTC del residuo idiosincratico -> sovrapposti alle MR esistenti
(P migliora-PORT06 ~20%, lezione FR01). 13/15 famiglie rumore (overfit/regime,
non look-ahead). Edge preservati in scripts/analysis/dispersion_edges/; harness
riusabile dispersion_lab.py (gia' committato). Diario + TODO follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 03:32:31 +00:00
Adriano Dal Pastro cb122683fe feat(research): harness condiviso dispersion/correlation index (feature causali + cache)
Feature realized causali (avg pairwise corr, cross-sectional dispersion, beta vs
indice EW, componente idiosincratica) su universo 8-asset, finestra comune dal
2022-07. NO implied (opzioni non backtestabili). Check no-look-ahead OK. Cache su
disco per il fan-out di ricerca. Base per la ricerca multi-agente dispersion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 21:16:10 +00:00
Adriano Dal Pastro 9275eab4af docs: TODO follow-up post-sweep (executor 2-gambe pairs, SH01 reale, capitale, code-review minori)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:57:28 +00:00
Adriano Dal Pastro 2a97294d67 docs: statistiche per anno (trd/PnL%/maxDD) per strategia e mercato nel doc HTML
Ogni card ora include la tabella anno x mercato:
- fade MR01/02/07 (BTC+ETH) e DIP01: calcolate col PATH LIVE (EXIT-16 + trend 3.0),
  trades/PnL per anno di entry, DD per anno su equity compounding pos 0.15 + DD totale
- PR01: matrice anno x 5 coppie, cella = PnL% (n trade, DD anno) + riga TOT
  (pairs_sim espone ora anche yearly_n, modifica non-breaking)
- TR01/ROT02/TSM01: ret%/DD% per anno dall'equity canonica daily 2021+
- SH01: per anno dal walk-forward EXPANDING (regime validato e ora live)
Nota di convenzione su ogni tabella (leva 3x test vs 2x live, fee incluse) +
caveat: finestra canonica dal 2021, anni 2018-2020 mostrati per onesta' storica.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:32:00 +00:00
Adriano Dal Pastro bc0ad3e86f docs: documento HTML strategie attive PORT06 (descrizioni + grafici da episodi reali)
scripts/analysis/make_strategy_doc.py genera docs/report/strategie_attive.html
(autocontenuto, PNG base64): le 6 strategie attive per famiglia con tesi,
config live, badge sim/reale e 11 grafici costruiti su EPISODI REALI dai
parquet locali — fade con bande/canale/z e trade annotato (entry/TP/SL),
explainer EXIT-16 (wick che buca lo SL senza conferma sul close), DIP01,
TR01 EMA 4h, ROT02 (gate regime + ranking momentum), PR01 (gambe normalizzate
+ z-score del ratio con soglie), TSM01 (risk-off + heatmap consenso),
SH01 (finestra forma + schema walk-forward), pesi cap del portafoglio.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:55:45 +00:00
Adriano Dal Pastro cc9a380a63 release: v1.1.9 2026-06-07 16:48:22 +00:00
Adriano Dal Pastro 8c5372c487 feat(SH01): bootstrap storia full-history + last_block_only (punto-10: regime live non robusto)
Ri-validazione sh01_trainwindow_validate.py (rolling train_window == regime live):
- tw=8760 (live 365g): BTC FULL +82% ma fee-2x -42% (6/9 anni), ETH Sharpe -0.02,
  trade-rate 21.7-26.4% vs 9.8% validato -> NON robusto. Diagnosi sweep confermata
  (LogReg over-confident su train corto, th 0.58 inerte).
- progressione MONOTONA con la memoria (2y: Sh 2.05; 3y: 3.09; expanding: ROBUSTO
  8/9) -> l'edge di SH01 e' la memoria lunga.
- sweep soglia nel regime corto: instabile/incoerente fra asset -> NON ri-tunare.

Fix (decisione utente): ripristinare in live il regime validato.
- ml_wf_entries(last_block_only=True): fitta/predice solo l'ultimo blocco del WF
  (confini deterministici start+k*retrain) -> entries IDENTICHE per costruzione
  al tail del WF completo (parity test esatto); 0.6s/tick su 73k barre vs ~140 fit.
- runner._with_history: tick degli sleeve ml su parquet locale + feed live
  (dedup timestamp, gap-guard con WARN una-tantum se parquet stantio).
- _defs.py: params {last_block_only: true} sugli sleeve SHAPE (solo path live;
  il backtest canonico resta WF completo).

Effetto atteso live: trade-rate SH01 ~25% -> ~10% delle barre, selettivita'
ripristinata. Manutenzione: parquet fresco via download_all (oggi 2026-05-28,
margine ~11 mesi col feed 365g).

Test: 87/87 (4 nuovi: parity last-block esatta, merge storia, gap fallback).
Diario docs/diary/2026-06-07-sh01-trainwindow.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:48:22 +00:00
Adriano Dal Pastro be85a454e0 release: v1.1.8 2026-06-07 16:19:25 +00:00
Adriano Dal Pastro ed2a9013aa feat(DIP01): EXIT-16 close-confirm SL (gate gap-aware 36/36 BTC, PORT06 promosso)
Punto 9 roadmap sweep. DIP01 era l'unico sleeve BTC con esecuzione reale
round-trip ancora sul branch SL intrabar wick-sensitive.

Gate dip01_exit16_impact.py (parita' canonica 1.00000; engine GAP-AWARE: orig
filla lo SL a worse(livello, open) per rimuovere il bias pro-stop-intrabar
dell'engine canonico sui gap-through):
- grid 3x3x2 BTC: EXIT-16 >= orig in 36/36 (train E oos), OOS Sharpe ~2-4x
  (canonica 1.47->3.48); ETH 35/36 (robustezza).
- plateau buffer piatto 0.4-1.0 (OOS DD 6.4% identico) -> 0.5 come le fade.
- PORT06: FULL Sh 6.43->6.61 DD 3.96->3.58 | OOS Sh 8.58->8.77 DD 1.36->1.34.
5a conferma del principio EXIT-16 (wick-stop = falsi negativi per mean-reversion).
hurst_max non valutato (ridondante-dannoso post-EXIT-16, gate trendmax).

Deploy: "sl_confirm_atr": 0.5 nei params DIP01_BTC (il worker legge gia').

Trappola di metodo documentata: la prima parita' falliva per l'ancora bfill di
_daily_equity (la serie a punti-trade reindexata su IDX aggancia il primo valore
al PRIMO trade in finestra, non al capitale portato avanti) — replicata per
parita'; finding aperto perche' tocca le metriche canoniche di tutti gli sleeve
a punti-trade. Diario docs/diary/2026-06-07-dip01-exit16.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:19:25 +00:00
Adriano Dal Pastro bf1cada33f release: v1.1.7 2026-06-07 14:59:58 +00:00
Adriano Dal Pastro f5173fba06 fix(live): 4 hardening da code-review — REAL_DIVERGENCE, outage su feed vuoto, bars.py condiviso, DSL cancel retry
Review multi-agente 7-angoli su 8c4e1cd..HEAD + check trades live:

1. Alert Telegram REAL_DIVERGENCE quando |slippage sim/reale| >= 100bps a
   open/close. Causa scatenante: spike print testnet 10:37 (candela 10:00
   H=65618, O/C ~62400) -> 3 fade BTC short su close fantasma 65266.5, reale
   fillato a 62395 (-440bps), sim +2.26 mai esistiti — passato in silenzio.
2. FEED_OUTAGE anche su feed degradato SENZA eccezione (HTTP 200 + candles
   vuote: i worker saltavano il tick in silenzio, streak a 0). Helper unico
   _outage_tick; chiavi payload uniformate (minuti su start e RIPRESO).
3. src/live/bars.py: detection forming-bar unificata (bar_ms_of /
   last_bar_is_forming / last_settled_idx) — era copiata in 4 punti
   (strategy_worker, basket, pairs, _check_stale_feed hardcoded 1h).
   E' l'invariante di sicurezza EXIT-16: ora una sola implementazione testata.
4. DSL cancel hardening in _real_close: retry su errore transitorio + alert
   REAL_DSL_CANCEL_FAIL se lo stop resta forse orfano sul book (prima l'id
   veniva dimenticato in silenzio); order_not_found = probabile trigger in
   outage -> solo log (il close a valle esce gia' verified=False).

Refutato il finding top dei finder ("stop_market senza trigger"): cerbero-mcp
traduce price->trigger_price+mark, e in produzione 2 DSL armati + 1 ciclo
completo pulito (MR07_BTC).

Test: 83/83 (9 nuovi: bars helper + DSL/divergence con executor finto).
Smoke testnet 4 scenari verdi, conto flat, zero falsi allarmi.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 14:59:58 +00:00
Adriano Dal Pastro 7bf8b197e0 release: v1.1.6 2026-06-07 10:44:15 +00:00
Adriano Dal Pastro 6097205694 feat(pairs): position_size per-famiglia, PAIRS 0.5->0.20 (gate PORT06)
Punto 8 roadmap sweep. La famiglia pairs (SENZA stop, validata a pos 0.15 lev 3 =
esposizione 0.45) girava col position_size globale 0.5 a leva 2 = esposizione 1.0,
~2.2x il validato (ETH/BTC DD grezzo 78% a quella taglia; live: ADA/ETH -4.26%
sleeve in un trade il 2026-06-05).

Gate pairspos_port06_impact.py (pairs_sim alla leva live 2x, parita' canonica
1.00000 5/5, PORT06 pesi cap): griglia pos {0.5, 0.25, 0.20, 0.15}. Il gate
meccanico boccia ogni riduzione (costa OOS Sharpe — il compounding in-sample a 0.5
e' fantasia: +2.6e9% ETH/BTC), ma 0.20 e' il ginocchio del trade-off: OOS DD
3.40->1.26% (meglio anche di 0.15), famiglia DD 14->5.8%, costo OOS Sharpe
9.05->8.43. Decisione assicurativa (utente), come il precedente cap SHAPE.

- runner.pos_for_spec(sid, global, family_overrides): override per-famiglia
  (chiave weighting.family_of), plumbato in build_worker_for.
- portfolios.yml: position_size_family {PAIRS: 0.20} (globale 0.5 invariato).
- Test: pos_for_spec mapping + PairsWorker.position_size (74/74 verdi).

Nota transizione: la posizione LTC/ETH aperta verra' chiusa col pos nuovo
(one-time). Diario docs/diary/2026-06-07-pairspos-gate.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 10:44:15 +00:00
Adriano Dal Pastro 0aaab08612 release: v1.1.5 2026-06-07 10:29:57 +00:00
Adriano Dal Pastro e3bb622b90 feat(fade): swap filtro live hurst->trend_max 3.0 (gate PORT06 sul path live)
Punto 7 roadmap sweep. Gate trendmax_port06_impact.py (engine exit16_port06_impact
riusato, parita' canonica 1.00000; maschera hurst IDENTICA al live via
fade_base.hurst_skip_mask; PORT06 pesi cap, path live EXIT-16):

- CANDIDATO (hurst+trend) BOCCIATO: over-filtering (FULL Sharpe 7.23->7.11,
  meta' dei trade) nonostante DD 2.68->2.06.
- SCOPERTA: il loss-guard Hurst e' ridondante-DANNOSO post-EXIT-16 (NESSUNO
  batte LIVE: FULL Sh 8.07 vs 7.23). EXIT-16 ha eliminato i wick-stop che hurst
  evitava -> gli ingressi saltati (66% delle barre) sono tornati vincenti.
  Il test che promosse hurst (2026-06-02) era sull'engine PRE-EXIT-16.
- TREND-ONLY domina LIVE su tutte le metriche (FULL Sh 7.89 DD 2.46, OOS Sh 9.91
  DD 1.20) ed e' la config che la ricerca EXIT-16 aveva davvero promosso (entries
  trend-filtrate, no hurst) mai eseguita dal live. Plateau 2.5/3.0/3.5 robusto.

Decisione (utente): SWAP. _defs.py: trend_max=3.0 + ema_long=200 nelle 6 fade,
hurst_max rimosso (hurst_skip_mask resta in fade_base). hourly_report: monitor
stop-rate per epoca PRE->HURST->TREND (verdetto a n>=30). CLAUDE.md aggiornato
(paragrafo hurst marcato storico). Diario docs/diary/2026-06-07-trendmax-gate.md.

Lezione: ri-gateare ogni filtro quando cambia l'exit engine.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 10:29:57 +00:00
Adriano Dal Pastro 11ace196c7 release: v1.1.4 2026-06-07 09:36:17 +00:00
Adriano Dal Pastro 62e272255b feat(live): disaster-bracket on-book sui fade reali + alert FEED_OUTAGE + osservabilita' multi-asset
Punti 5-6 dell'improvement-sweep 2026-06-06 (protezione capitale + osservabilita'):

Punto 5 — disaster bracket:
- ExecutionClient.place_disaster_sl: STOP_MARKET reduce-only a ~-30% dall'ingresso
  (trigger mark price), piazzato a ogni REAL_OPEN (MR01/MR02/MR07/DIP01) e
  cancellato in _real_close. Assicurazione outage: il poll-loop in except lascia
  le posizioni reali senza valutazione exit (ETH gap max storico 33%/1h). In
  operativita' normale non scatta mai -> 0 costo Sharpe. real_dsl_order_id
  persistito (resume-safe). Config overrides.execution.disaster_sl_pct (0.30).
- NB: set_stop_loss di cerbero-mcp e' un private/edit Deribit (solo ordini APERTI)
  -> non usabile su market fillati; il bracket e' un trigger order autonomo via
  place_order(type=stop_market). Cancel di un trigger order risponde 'untriggered'
  (= successo, verificato testnet: re-cancel -> order_not_found).
- Runner: alert Telegram FEED_OUTAGE dopo 5 poll falliti consecutivi (elenco
  posizioni reali aperte) + notifica RIPRESO con durata.

Punto 6 — osservabilita':
- in_position nei _save() di TR01/ROT02/TSM01; hourly_report: sezione MULTI-ASSET
  (book | ultimo flip | freschezza status) — prima i 3 worker erano invisibili
  (collect() filtra su event/in_position che non emettevano); esclusi dalla
  tabella IN CORSO (assume entry/bars single-leg).
- live_shadow_smoke esteso: scenari C/D SHORT (TP-resting BUY mai esercitato
  prima) + disaster bracket in tutti gli scenari.

Verifiche: 72/72 test; smoke testnet 4 scenari verdi (DSL piazzato/cancellato due
lati, zero ordini orfani sul book, conto flat); multi_asset_section renderizza sui
dati live. Diario docs/diary/2026-06-07-sweep-fixes.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 09:36:11 +00:00
Adriano Dal Pastro bd5f1a6ea0 release: v1.1.3 2026-06-07 08:23:25 +00:00
Adriano Dal Pastro 3909646028 fix(live): parita' worker multi-asset — TR01 fee x leva, forming-bar su TR01/Pairs, WARN panel corto
Punti 2-4 dell'improvement-sweep 2026-06-06 (fix di parita', nessun cambio di strategia):

- TR01 (basket_trend_worker): fee per flip = POS * LEV * fee_rt/2 come il reference
  honest_improve2._tr_basket_daily:150 (mancava il fattore leva -> fee sotto-caricate 2x,
  bias ottimistico che gonfiava total_capital al ribilancio).
- TR01: crossover EMA e booking del return valutati SOLO su barre 4h COMPLETE
  (la riga -1 e' la candela in corso finche' non e' trascorsa la sua durata) — lezione
  EXIT-16; evidenza live: flip SOL 0->1->0 in 59min nella stessa finestra 4h.
- PairsWorker: entry ED exit valutati sul close di barra COMPLETA, come il backtest
  pairs_research (close settled). Stesso pattern bar_ms di strategy_worker.tick.
- TSM01/ROT02: il silent return su panel inner-join troncato sotto il lookback ora
  emette WARN log + Telegram PANEL_SHORT (helper _warn_panel_short condiviso), gated
  su "era gia' operativo" (no falsi positivi al cold-start), una notifica per episodio.

Test: 72/72 verdi (7 nuovi: forming-bar TR01/pairs + controllo positivo, fee con leva,
PANEL_SHORT warn/dedup/cold-start). Replay parity: validate_worker_pairs ESATTO
(ETH/BTC e BTC/LTC == backtest); validate_honest_workers invariato (TR01 -44% vs ref
+42% IDENTICO pre/post fix: e' la divergenza di convenzione capitale-unico vs
media-equity gia' documentata, da rivisitare a parte).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 08:23:19 +00:00
Adriano Dal Pastro 9ce43031ee fix(PORT06): ripristina cap SHAPE 0.0588 in portfolios.yml (P0 improvement-sweep)
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>
2026-06-07 08:07:00 +00:00
Adriano Dal Pastro 8c4e1cdf13 config(docker): escludi pythagoras-portfolio da Watchtower (label enable=false)
Watchtower gira sull'host (containrrr/watchtower): senza esclusione potrebbe
riavviare/aggiornare automaticamente il trader stateful con posizioni aperte.
I deploy passano SOLO da scripts/deploy.sh. Label aggiunta da root sull'host
il 2026-06-05 08:06, verificata e adottata su conferma esplicita dell'utente.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 18:52:11 +00:00
Adriano Dal Pastro 35c0133e63 release: v1.1.2 2026-06-05 18:43:32 +00:00
Adriano Dal Pastro 2da3457ca7 fix(live): EXIT-16 confirm-SL valutato sul close di barra COMPLETATA + alert STALE_FEED
Il worker valutava il confirm sul prezzo della candela IN FORMAZIONE ad ogni poll,
reintroducendo la wick-sensitivity che EXIT-16 elimina (audit crash ETH 2026-06-05:
2 stop su 3 erano wick-stop che il backtest validato non avrebbe preso in quel
momento). Ora il confirm usa SOLO il close dell'ultima barra completata (riga -1 =
candela in corso finche' now < ts[-1]+bar_ms), buf dall'ATR della stessa barra,
fill al prezzo corrente (~ stress lag_close_exit OK in exit-lab), TP intrabar
invariato. Concausa feed-gap: non mitigabile lato exit (fill reali ~ sim);
entry-guard post-flat TESTATA e BOCCIATA (skippare i segnali dopo barre flat
peggiora tutti gli sleeve ETH: la candela-gap e' l'overshoot che la fade fada).
Aggiunto alert Telegram STALE_FEED (>=2 barre 1h flat -> notifica + gap % al
risveglio, dedup per episodio, solo osservabilita').

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 18:32:14 +00:00
Adriano Dal Pastro ff2ff6de7d release: v1.1.1 2026-06-05 17:56:31 +00:00
Adriano Dal Pastro bd6232dc00 config(PORT06): cap SHAPE 0.0588 — SH01 resta senza SL (ricerca multi-agente: 11 famiglie di stop, 0 sopravvissute)
Crash ETH 2026-06-05: SH01 ETH −15.6% su un trade (exit solo a orizzonte, nessuna
protezione). Ricerca con harness dedicato sh01_exit_lab (cache walk-forward, engine
fill gap-aware worse(livello,open), parity esatta con explore_lab, train<=2023-11-01):
ATR intrabar/close-confirm, %, chandelier, breakeven, giveback, loser-timestop,
disaster-cap close+intrabar, swing, vol-regime — NESSUNA passa il gate (ogni stop
stretto rompe BTC, ogni stop largo non tocca la coda ETH; nei crash il fill e' al gap).
Mitigazione: peso famiglia SHAPE 11.8%->5.9% in PORT06 (FULL 6.47->6.43 DD 4.10->3.96,
OOS 8.82->8.58 DD 1.30->1.36) — la prossima coda impatta il conto per meta'.
Regression-lock test aggiornato. Diario: docs/diary/2026-06-05-sh01-sl-research.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:56:16 +00:00
Adriano Dal Pastro 6f86c644bf docs: CLAUDE.md + README aggiornati — PORT06 canonici correnti (6.47/8.82, con EXIT-16 7.84/10.06), shadow reale 7 sleeve (DIP01 incluso), sezione esecuzione reale nel README, famiglia SHAPE in tabella, exit_lab in struttura/comandi
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:58:04 +00:00
Adriano Dal Pastro 2e9ec81567 config(live): DIP01 BTC aggiunto allo shadow reale (7° sleeve single-leg, wiring StrategyWorker invariato)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:45:27 +00:00
Adriano Dal Pastro 3bc42517b0 docs(diary): exit-lab — sezione implementazione+deploy v1.1.0 (complementarieta' hurst/trend/close-confirm)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:29:55 +00:00
Adriano Dal Pastro 59609a535a release: v1.1.0 2026-06-04 21:26:50 +00:00
Adriano Dal Pastro a2579d21bc feat(fade): EXIT-16 close-confirm SL — stop solo se il CLOSE sfonda sl∓0.5·ATR14 (immune ai wick)
Param sl_confirm_atr (None = comportamento storico) in FadeStrategy.backtest,
MR01.backtest e StrategyWorker.tick; attivo live a 0.5 sulle 6 fade in _defs.py.
TP intrabar al livello e max_bars invariati; in modalita' confirm il TP ha
priorita' nel bar. Ricerca exit-lab (34 agenti, 3 lenti avversariali + tail
audit): gli stop intrabar da wick sono falsi negativi per le fade. PORT06
canonico: FULL Sharpe 6.47->7.84 DD 4.10->2.60, OOS 8.82->10.06 DD 1.30->1.15.
NB path grezzo non filtrato: ret esplode ma DD per-sleeve sale — la riduzione
DD vive nel config live (trend_max+hurst) + diversificazione, come misurato.
5 test nuovi (59 verdi).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:25:17 +00:00
Adriano Dal Pastro ad65a0b344 research(exit-lab): 34 agenti su exit dinamiche → EXIT-16 close-confirm SL PROMOSSO a livello PORT06
23 famiglie esplorate (harness condiviso exit_lab, train/OOS embargo nov-2023,
tutto lo storico 1h 2018-2026) + 10 verifiche avversariali + test PORT06.
'Cavalcare il prezzo' non esiste (4a conferma: oltre il TP=media non c'e' coda).
Scoperta: lo SL intrabar fisso e' il distruttore di valore n.1 delle fade
(stop da wick = falsi negativi). Forma robusta: SL solo su CHIUSURA oltre
sl0±0.5·ATR14 — PORT06 FULL Sharpe 6.47→7.84 DD 4.10→2.60, OOS 8.82→10.06.
Collaterali: bias gap-through dell'engine sugli stop stretti; ramo -2% del
worker morto con sl=0. Diario: docs/diary/2026-06-04-exit-lab.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:16:58 +00:00
Adriano Dal Pastro 3accc91f84 docs(diary): exit ladder 80/20 con runner+stop dinamico SCARTATO (il runner non corre: TP=media, ~3% oltre TP, −38% ret su MR02 ETH)
Test onesto scripts/analysis/partial_tp_ladder.py: stessi segnali, engine
intrabar fade_base, fee identiche. Il ladder compra win-rate/DD pagando i
winner migliori — stesso profilo del vol-target gia' scartato.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 19:48:45 +00:00
Adriano Dal Pastro 7707cc17a0 release: v1.0.7 2026-06-04 19:24:20 +00:00
Adriano Dal Pastro abd396fa54 feat(live): TP reale = LIMIT reduce-only al livello (fix +235bps slippage exit)
All'apertura reale il worker piazza un limit reduce-only al TP della strategia
(quota del SOLO worker, prezzo quantizzato al tick); alla chiusura sim cancella
il resting, riconcilia i fill (anche parziali) via get_trade_history per
order_id e chiude a market solo il residuo. real_tp_order_id persistito
(resume-safe). SL resta market-on-poll (deliberato: trigger Deribit = nuovo
order_id al trigger, fill non verificabile; e il rimbalzo sul SL lavora a
favore). Smoke testnet 2 scenari OK (resting+cancel / fill immediato).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 19:24:07 +00:00
Adriano Dal Pastro e8075b6292 docs(diary): divergenza shadow sim/reale misurata (+235bps su TP) + TODO limit reduce-only al TP
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:20:07 +00:00
Adriano Dal Pastro a26397b1bf release: v1.0.6 2026-06-04 16:10:33 +00:00
Adriano Dal Pastro 74670dae05 fix(live): quantizzazione amount senza artefatti float (Decimal) — 72*0.001=0.07200000000000001 causava 'Invalid params' su Deribit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:10:28 +00:00
Adriano Dal Pastro 4f5bd32c38 release: v1.0.5 2026-06-03 18:01:30 +00:00
Adriano Dal Pastro 74c79aa6c1 feat(live): position_size configurabile da overrides (0.5 = max impiego dei 2K senza debito margine)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 18:01:30 +00:00
Adriano Dal Pastro 092e809946 docs(live): leva per-strumento NON impostabile su Deribit (set_leverage=400, verificato nei log Cerbero) — l'esposizione la decide la size
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 17:52:55 +00:00
Adriano Dal Pastro 92de7c9867 release: v1.0.4 2026-06-03 10:20:12 +00:00
Adriano Dal Pastro 719509fb2f feat(live): alert Telegram esecuzione reale + doc shadow-execution in CLAUDE.md
- telegram_notifier: eventi REAL_EXEC_LIVE (primo ordine reale verificato per worker)
  e REAL_OPEN_FAIL (apertura reale non verificata)
- StrategyWorker: notifica una-tantum al primo REAL_OPEN verificato + ogni FAIL;
  flag real_first_notified persistito
- CLAUDE.md: documentato lo shadow-execution (ExecutionClient, lineari USDC,
  verifica sul trade, fee reali, config, alert, smoke)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:20:08 +00:00
Adriano Dal Pastro fec15c07bb release: v1.0.3 2026-06-03 10:12:23 +00:00
Adriano Dal Pastro cb1b6ea46a feat(live): esecuzione REALE su Deribit testnet (shadow) per i 6 fade sui lineari USDC
- ExecutionClient: notional->amount (lineare USDC + inverse), open/close_amount
  reduce-only, verifica sul trade (order_id), fee reali lette dai trades[]
- CerberoClient: place_order market + reduce_only, get_trade_history
- StrategyWorker: shadow (REAL_OPEN/REAL_CLOSE accanto al sim), ledger reale
  parallelo persistito, confronto slippage/fee sim-vs-reale
- runner+portfolios.yml: config execution (6 fade MR01/MR02/MR07 x BTC/ETH su
  BTC_USDC/ETH_USDC-PERPETUAL), capitale 2000
- smoke: live_exec_smoke (layer) + live_shadow_smoke (catena worker), provati su testnet

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:11:26 +00:00
Adriano Dal Pastro 1f0c1ab02a release: v1.0.2 (revert fallback mainnet) 2026-06-02 19:50:47 +00:00
Adriano Dal Pastro b5de241ebc revert(live): rimuovi fallback mainnet — il paper deve usare il venue di esecuzione (testnet)
Il fallback a Deribit mainnet per i dati introduceva una DIVERGENZA paper<->esecuzione: il paper
simulava su prezzi mainnet ma gli ordini (place_order via Cerbero) andrebbero su TESTNET, i cui
prezzi/liquidità divergono ~9%. Un paper trader deve usare la STESSA fonte/venue dove gli ordini
verrebbero eseguiti, altrimenti non predice il comportamento reale. Durante un outage testnet il
runner si mette in pausa (corretto: senza il venue non si eseguirebbe comunque). Rimosso
get_historical_mainnet e il wiring nel runner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 19:50:47 +00:00
Adriano Dal Pastro 5157cd544d release: v1.0.1 (fallback mainnet) 2026-06-02 16:58:47 +00:00
Adriano Dal Pastro 722837e599 Merge feat/mainnet-fallback: OHLCV da Deribit mainnet quando testnet e' giu'
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 16:58:14 +00:00
Adriano Dal Pastro 8e72d7ad02 feat(live): fallback OHLCV a Deribit MAINNET quando Cerbero/testnet e' giu'
Deribit testnet (test.deribit.com) va giu' periodicamente (502) e Cerbero lo rilancia ->
il runner si bloccava senza dati. Aggiunto CerberoClient.get_historical_mainnet (Deribit MAINNET
public, NO-AUTH, paginato sotto il cap ~5000 candele/chiamata) e fallback nel runner: try Cerbero
-> on fail/empty usa mainnet. Prezzi REALI (meglio del testnet farlocco per il paper). Verificato
durante l'outage: tutti gli 8 strumenti (BTC/ETH + alt _USDC) coperti su mainnet. Log una-tantum
all'attivazione/disattivazione del fallback.

Caveat: testnet e mainnet hanno prezzi diversi (~9%) -> al primo switch le posizioni aperte su
prezzi testnet vanno resettate (transizione pulita).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 16:58:14 +00:00
Adriano Dal Pastro 87b420f459 docs: monitor loss-guard nel report orario + effetto misurato (stop-loss -67%)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 16:17:05 +00:00
Adriano Dal Pastro 6605008f49 feat(report): monitor loss-guard Hurst nel report orario Telegram
Traccia lo stop-rate delle fade (MR01/MR02/MR07) PRIMA/DOPO l'attivazione del loss-guard
(2026-06-02 14:34 UTC). Verdetto automatico quando il campione DOPO >= 30 trade. Conferma gia'
visibile: stop-rate live PRIMA 42% (n=36) == backtest 42.1%. Gira host-side (cron), no rebuild.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:54:37 +00:00
Adriano Dal Pastro b5f644e52d docs: aggiorna CLAUDE.md e README (loss-guard Hurst, versione/deploy, fix SH01, regime infra)
- Loss-guard Hurst sulle fade (hurst_max=0.55, dimezza DD PORT06) — attivo live
- Sistema versione + deploy.sh (versione nei msg Telegram, +1 ad ogni deploy)
- Fix wiring SH01 (StrategyWorker reale, non MLWorkerWrapper squeeze)
- Fix StrategyWorker: exit orizzonte, is_win netto, min_tp_frac
- Infrastruttura regime (regime_lab/regime_fetcher, data/regime/)
- Esiti ricerca: ARGO/GEX no-go, frattali x regime (FR01 robusto ma non deployato)
- Nota deploy = up -d --build (non restart)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:39:59 +00:00
Adriano Dal Pastro 7da9dc36e4 Merge feat/version-tag: versione nei msg Telegram (+1 ad ogni deploy)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:34:18 +00:00
Adriano Dal Pastro 4770a8368f feat(deploy): tag di versione nei msg Telegram, +1 ad ogni deploy
File VERSION (semver, cotto nell'immagine) letto da src/version.py. Compare nelle notifiche trade
(telegram_notifier) e nel report orario -> sai quale codice ha generato quale msg.
scripts/bump_version.py incrementa la patch; scripts/deploy.sh = bump+commit+rebuild (versione
aumenta ad OGNI deploy). v1.0.0 = primo release versionato (include hurst loss-guard).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:34:18 +00:00
Adriano Dal Pastro d624fe74c9 Merge feat/fade-lossguard: loss-guard Hurst sulle fade (attivo live)
Sistema anti-perdite: skip fade in regime persistente (hurst>=0.55). Dimezza il DD del PORT06
(FULL 4.10%->2.39%, Sharpe 6.62->6.76, OOS 8.89->9.15). Solo l'Hurst supera il gate (ADX/vol-expansion
falliscono). Attivo live sulle 6 fade. Diagnostici + workflow 11 agenti + diario.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:17:07 +00:00
Adriano Dal Pastro 29c2ea488f feat(live): attiva loss-guard Hurst sulle fade PORT06 (hurst_max=0.55)
Attivazione live: hurst_max=0.55 nei params delle 6 fade (MR01/MR02/MR07 x BTC/ETH) in _defs.py.
hurst_skip_mask step=6 (5.9s/fade su 10560 barre live, ~35s per le 6 -> OK su poll 60s, e coincide
con la cache di validazione). Calcolato dalle SOLE close -> nessun feed esterno, il worker lo computa
inline.

NB live/backtest: il filtro agisce solo sul path LIVE (spec.params); il backtest canonico
(build_everything/regression-lock, via risk_management) NON e' filtrato -> il live FARA' meglio del
backtest sul DD (FULL 4.10%->2.39% atteso). Divergenza intenzionale (miglioramento). Backtest-parity
aggiornabile in seguito. Suite: 54 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:17:07 +00:00
Adriano Dal Pastro ac6f3766b0 feat(fade): loss-guard Hurst (skip regime persistente) — dimezza il DD del PORT06
GOAL: limitare le perdite delle fade in regime sfavorevole. Diagnosi (3022 trade): le perdite/stop
si concentrano nel regime PERSISTENTE (hurst>0.55: stop-rate 43% vs 21% anti-persistente), NON in
bassa vol (low-vol e' net positivo). Ricerca web + workflow 11 agenti: l'UNICO meccanismo che riduce
DD senza uccidere l'edge e' il filtro Hurst (ADX, vol-expansion, time-stop, ER, vol-target falliscono
il gate FR01). Test esterni ADX/vol-expansion NON si replicano su queste fade crypto.

TEST DECISIVO PORT06 (gate FR01) SUPERATO: Hurst-skip h<0.55 sulle 6 fade ->
FULL Sharpe 6.62->6.76, FULL DD 4.10%->2.39% (quasi dimezzato), OOS Sharpe 8.89->9.15.
Migliora il portafoglio (a differenza di FR01 che diluiva).

Implementazione: hurst_skip_mask in fade_base.py (rolling-Hurst causale dalle SOLE close -> nessun
feed dati esterno, deployabile inline dal worker) + param hurst_max (default None=off) in
MR01/MR02/MR07. Test: test_hurst_lossguard.py. Default off -> zero impatto su backtest/parita'/live
finche' non attivato.

FIX collaterale: regime_fetcher/regime_lab scrivevano DVOL/funding/feature in data/raw/ ->
inquinavano la discovery asset del backtest (rompeva il regression-lock PORT06). Spostati in
data/regime/ (gitignored). Suite: 54 passed (lock incluso).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:08:15 +00:00
Adriano Dal Pastro 4d9f2af0c0 research(fade-lossguard): diagnostici perdite fade per regime + workflow anti-perdite
Diagnosi (3022 trade fade 2021+): perdite/stop concentrati in regime PERSISTENTE (hurst>0.55,
SL-rate 43% vs 21% anti-persistente), NON in bassa vol (low-vol e' net positivo). Ricerca web
conferma: filtro Hurst<0.45 / ADX<20 / vol-expansion ratio>1.5 (prevenne 72% perdite maggiori).
Workflow 11 agenti testa i meccanismi sulle fade reali.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 13:35:44 +00:00
Adriano Dal Pastro 26de94e57d Merge feat/fractal-argo-search: ricerca 100 agenti frattali x ARGO (record)
Esito: 15 strategie frattale x regime robuste e causali, ma NESSUNA migliora PORT06
(FR01 lo diluisce: OOS Sharpe 8.89->8.72). Prior ARGO 'VRP>0=range' smentito, edge su
VRP<0 + DVOL bassa. RECORD DI RICERCA: non deployato, infrastruttura regime riusabile.

Artefatti: regime_fetcher/regime_lab (dati DVOL+funding Deribit mainnet, feature regime+frattali
causali), fractal_argo_workflow.js (171 agenti), FR01_hurst_calm_fade.py (NON in MODULE_MAP/yml),
analisi per-anno + test MASTER, diario 2026-06-02.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 11:49:28 +00:00
Adriano Dal Pastro 8fd8f64368 test(research): FR01 NON migliora PORT06 (verdetto decisivo)
Contributo marginale al MASTER (equal-weight): OOS Sharpe 8.89->8.72 (diluisce),
OOS ret +175%->+156%. Corr FR01 vs MASTER +0.18/+0.23. Sharpe daily-return standalone
~1.85 (non il 3.73 per-trade) -> troppo basso per un PORT06 a 8.89. Ridondanza robusta.
ESITO: search a 100 agenti rigoroso, ma nessuna strategia migliora PORT06. Deploy abbandonato,
resta record di ricerca sul branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 11:48:02 +00:00
Adriano Dal Pastro 7005763517 feat(research): FR01 Hurst-Calm Fade + analisi per-anno/mercato (ricerca 100 agenti)
Esito ricerca frattali x regime ARGO (171 agenti):
- FR01_hurst_calm_fade.py: vincitore = fade gateato da hurst<0.55 (anti-persistente) +
  dvol_pct<0.4 (DVOL bassa). OOS Sharpe 3.73 BTC, 6/6 anni positivi su BTC+ETH, corr bassa
  coi fade esistenti (MR01 +0.17/MR02 +0.08/MR07 -0.03) -> diversificatore non ridondante.
- fractal_argo_peryear.py: analisi per-anno/regime-mercato dei top candidati.
- diario 2026-06-02: verdetto completo. Finding chiave: prior ARGO 'VRP>0=range=fade' SMENTITO,
  l'edge robusto e' su VRP<0 + DVOL bassa.

Diversificatori, NON spodestano PORT06 (OOS Sharpe 8.19). Branch di ricerca.
Deploy bloccato da: verifica corr sul MASTER intero + wiring DVOL live nel runner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 07:22:10 +00:00
Adriano Dal Pastro 5f229cd66e fix(regime_lab): vrp annualizzato per timeframe + report() safe su 0 entries
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 00:08:33 +00:00
Adriano Dal Pastro 7169614506 feat(research): substrato ricerca frattali x regime ARGO
- regime_fetcher.py: fetch DVOL (2021+) + funding (2019+) BTC/ETH da Deribit mainnet public
- regime_lab.py: API condivisa, allineamento regime<->prezzo CAUSALE no-look-ahead,
  feature regime (dvol_pct/vrp/funding_z/dvol_chg) + frattali (hurst/higuchi/vratio/williams),
  cache feature precalcolate, report()=netto-fee OOS via explore_lab
- fractal_argo_workflow.js: workflow ~100 agenti (84 griglia + 8 wildcard + verifica + sintesi)

Branch di ricerca: nessun impatto su main/live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 00:05:14 +00:00
Adriano Dal Pastro 69619df4c7 chore: gitignore stato locale tooling (.claude/, .omc/)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 14:35:19 +00:00
Adriano Dal Pastro 056d18a3fd Merge feat/hourly-telegram-report: report orario PORT06 su Telegram
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 14:02:37 +00:00
Adriano Dal Pastro 99efc7a042 feat(report): report orario PORT06 su Telegram
Script standalone che legge lo stato persistito del paper trader a portafoglio
(data/portfolio_paper/*/ + data/portfolios/PORT06/status.json) e invia su
Telegram: (1) trade CHIUSI positivi/negativi NETTO fee con breakdown per motivo
e PnL; (2) tabella trade IN CORSO (posizioni aperte, single e pairs a 2 gambe);
(3) PnL realizzato totale + equity mark-to-market (con non realizzato).

Carica .env da solo (cron non eredita l'env del container), legge file
world-readable scritti dal container, non tocca lo stato del trader. Riusa
send_telegram di telegram_notifier.

Schedulazione: crontab host orario (minuto 0):
  0 * * * * uv run --project /opt/docker/PythagorasGoal python \
            scripts/portfolios/hourly_report.py
(uv run --project + path assoluto: nessun cd necessario.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 14:02:37 +00:00
Adriano Dal Pastro e44a310a3b Merge fix/sh01-live-wiring: SH01 esegue shape-ML reale (non squeeze)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 12:24:14 +00:00
Adriano Dal Pastro 83c4e7a334 fix(live): SH01 esegue shape-ML reale, non il wrapper squeeze scartato
Bug di wiring: runner.py avvolgeva lo sleeve SH01 nel MLWorkerWrapper legacy
di multi_runner, che usa SignalEngine (famiglia squeeze ML01 SCARTATA), apre
con Signal nudo ed esce a hold_bars=3 con tick() propria. Risultato: lo sleeve
"SH01" del portafoglio live NON eseguiva SH01_shape_ml (generate_signals mai
chiamata) e il fix horizon-exit era in un ramo morto -> SH01 continuava a
chiudere a hold_limit/3.

Fix: SH01 (kind="ml") gira come StrategyWorker normale. SH01_shape_ml.
generate_signals fa il walk-forward internamente ad ogni tick ed emette
metadata.max_bars=H=12 -> exit via StrategyWorker.tick (orizzonte H, fix
applicato). Rimosso l'import/uso di MLWorkerWrapper e il blocco train esterno.

ml_wf_entries ha train_min=4000 (>=4000 barre 1h per produrre segnali):
aggiunto _ML_LOOKBACK_DAYS=365 cosi gli asset di sleeve ml fetchano >=365g
(~8760 barre), senza dipendere dal fetch 440g di TSM01/ROT02. generate_signals
su 365g: 0,17-0,24s (logit) -> trascurabile sul poll 60s.

Test: test_build_ml_sh01_is_plain_strategyworker (StrategyWorker + strategy
SH01_shape_ml + niente engine squeeze). Suite: 51 passed.

Stato live: SH01 BTC/ETH flat -> contatori resettati (capitale preservato),
trade squeeze archiviati. Rebuild+recreate: 14 worker RESUME puliti, healthy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 12:24:13 +00:00
Adriano Dal Pastro 56565f6f73 Merge fix/win-net-and-tp-min-edge: win netto-fee + filtro TP edge-minimo
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 11:07:30 +00:00
Adriano Dal Pastro ab4f706057 fix(live): win netto-fee + filtro TP edge-minimo sulle fade
Due correzioni emerse da close live con win=True ma pnl<0.

1) Metrica win lorda -> netta. _close_position contava is_win=trade_return>0
   (lordo), gonfiando l'accuracy: un take-profit colpito con mossa < fee RT
   risultava "win" pur perdendo. 51 close live: 39 win (76,5%) -> 13 falsi win
   -> accuracy netta reale 52,9%. Fix: is_win = net > 0. Capitale/PnL erano
   già corretti (netti). Contatori persistiti riconciliati a parte (MR01/DIP01
   BTC 7->1).

2) Filtro edge-minimo min_tp_frac. I 13 falsi win erano tutti MR01/DIP01 BTC in
   regime piatto: TP (la media) entro il costo round-trip -> perdenti garantiti.
   Aggiunto param min_tp_frac (default 0.0=off) a tutte e 4 le fade (MR01 banda,
   MR02 midpoint, MR07 ATR, DIP01): salta i segnali col TP entro la soglia.
   Non si "allarga" il TP (rischierebbe di perdere di piu'): si evita la trade.
   Cablato live a 0.0015 (1,5x fee) in _defs.py.

Validazione backtest BTC+ETH 1h: neutro su tutte le fade (0-1 trade rimossi,
pnl invariato o +leggero su DIP01). I micro-scalp sotto-fee non esistono nello
storico -> artefatto del regime attuale. Filtro puro-upside.

Test: test_win_net_of_fees.py, test_min_tp_frac.py (monotonia + gap > soglia +
default-off invariato). Suite: 50 passed.

NB deploy: il sorgente e' COPY nell'immagine, non montato -> serve
`docker compose up -d --build`, non un semplice restart (vale anche per il fix
SH01 horizon-exit, andato live solo con questo rebuild). Volume ./data persiste,
14 worker in RESUME puliti.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 11:07:30 +00:00
Adriano Dal Pastro 05ebd6754b Merge fix/sh01-horizon-exit: exit a orizzonte SH01 (H=12, non hold_bars=3)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 09:06:49 +00:00
Adriano Dal Pastro b5d277478c docs(diary): bugfix exit a orizzonte SH01 (3 barre -> H=12)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 09:06:49 +00:00
Adriano Dal Pastro 53e0965c4b fix(live): exit a orizzonte per strategie senza TP/SL (SH01)
Lo StrategyWorker onorava max_bars (orizzonte del Signal) solo nel ramo
`if self.tp and self.sl`. SH01 (shape-ML, H=12) non porta TP/SL, quindi
cadeva sul fallback legacy hold_bars=3 e chiudeva a 3 barre invece delle
12 validate: l'edge (asimmetria sull'orizzonte, non frequenza) non aveva
tempo di realizzarsi -> accuratezza live falsata (33%), tutti exit
"hold_limit" a bars_held=3.

Aggiunto un ramo `elif self.max_bars` che esce a "time_limit" quando
bars_held>=max_bars, prima del fallback hold_bars. Tocca solo le
strategie horizon-only (SH01); le fade con tp+sl+max_bars sono invariate.

Test: tests/portfolio/test_horizon_exit.py (resta in posizione a 3 barre
con max_bars=12; esce a 12 con reason time_limit). Suite: 43 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 09:05:53 +00:00
Adriano Dal Pastro aaf0221957 docs(diary): studio exit fade (scalping / TP dinamico / TP-ATR)
Tre alternative di uscita misurate su MR02 (fee-aware) vs baseline TP=centro canale:
- 15m "scalp": piu' PnL lordo ma fragile a fee/slippage (DD esplode a 0.20%).
- trailing/TP dinamico: win-rate 48%->36%, azzera l'edge (let-it-run = continuazione).
- TP-ATR: TP stretto m=0.5 -> win 77% ma edge ~0 (trappola scalping); nessun
  multiplo ATR batte il centro su avg/trade e Sharpe (FULL+OOS, BTC/ETH).
Verdetto: TP=centro canale e' ottimale (target adattivo alla struttura, gia'
scalato alla vol). Design blindato. Nota: win-rate alto != edge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 13:52:44 +00:00
Adriano Dal Pastro 1177da33ea docs(diary): stato trade live PORT06 (snapshot verificato 2026-05-31)
~43h di paper trading: 10 trade chiusi (9W/1L, +EUR0.40 realizzato), 3 aperti,
equity MtM EUR1000.36, max DD 0.40%. Check: 0 anomalie (net=gross-fee, win
coerente, fee incluse), uscite pairs conformi a z_exit=0.75, riconciliazione
ledger spiegata (timing rebal). Campione a livello rumore ma sistema sano e
fedele al backtest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 13:23:35 +00:00
Adriano Dal Pastro 03e8938a18 docs(diary): record anti-ripetizione strategie scartate (opzioni + TA classiche)
Conclusioni-only (codice testato e scartato, non conservato): perche' non
funzionano, cosi' da non ri-testarle.
- Opzioni: overlay PORT06 / OH01 direzionale / OH02 credit-spread / V5 debit
  spread (Casario) / V4 box. Nessun edge nuovo: trend e MR gia' catturati 50-100x
  meglio dai perp; VRP contro chi compra, code grasse contro chi vende.
- TA: SMA pullback / ORB / weakness rectangle -- tutte continuazione, negative
  anche a fee 0. Flip al fade: 2/3 segno positivo ma = MR01/MR02 inferiori, WR
  rumore. Riconferma: solo mean-reversion paga.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 07:17:10 +00:00
Adriano Dal Pastro c13773e762 test(portfolio): aggiorna regression-lock PORT06 dopo recupero dati BNB/DOGE/XRP
Il recupero dello storico BNB/DOGE/XRP (29 mag) ha ampliato la copertura del
backtest -> metriche migliorate, non una regressione:
  Sharpe FULL 6.07 -> 6.47, Sharpe OOS 8.19 -> 8.82, DD FULL 4.9% -> 4.1%.
Aggiornati i tre valori attesi (tolleranze invariate) + commento col motivo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 07:10:40 +00:00
Adriano Dal Pastro 5a219ca8e5 feat(analysis): proiezione 3 anni capitale PORT06 (live 2x, esclude 2024)
Script di proiezione: partendo da 1000 EUR con capitale che compone e
puntata che cresce, stima guadagno giornaliero e traiettoria a 3 anni.
Riscalata sul sizing LIVE (pos 0.15 x 2x) vs backtest 3x; ROT02/TSM01
usano gross fisso (non riscalano). Esclude il 2024 (anno eccezionale)
e include haircut ~50% per lo scenario sobrio prudente.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 19:29:07 +00:00
Adriano Dal Pastro 2a11728384 feat(docker): deploy PORT06 portfolio runner via docker compose
Wire the Docker image and compose service for the capital-pool portfolio
paper trader (src.portfolio.runner) instead of the single-leg multi_runner:

- Dockerfile: copy full scripts/ (runner imports scripts.analysis.* and
  scripts.portfolios._defs via sleeves.py) and portfolios.yml.
- docker-compose.yml: service "portfolio" / container pythagoras-portfolio,
  command override to src.portfolio.runner, mount portfolios.yml, healthcheck
  on data/portfolios/ status.json.
- .gitignore: ignore portfolio runtime state (data/portfolio_paper, data/portfolios).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 07:30:33 +00:00
Adriano 7b2e0049eb test(live): demo numerica exit intrabar fade -- worker replay ~= backtest
MR01 BTC 1h, 4000 barre, no filtro trend: backtest build_trades +3.5%/73 trade vs
worker replay intrabar +4.5%/78 trade -> gap +1.0pt (allineato). Conferma che il fix
exit intrabar chiude il gap live-vs-backtest delle fade (residuo = bar-timing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 18:43:38 +02:00
Adriano fb65df7861 merge: fix exit intrabar StrategyWorker (fade/DIP01 allineate al backtest)
Lo StrategyWorker esce su TP/SL toccati intrabar (high/low della barra, al livello,
SL prioritario) come il backtest, chiudendo il gap live-vs-backtest delle fade/DIP01.
+4 test. 39 test totali passano.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 18:02:14 +02:00
Adriano 49039ac286 fix(live): StrategyWorker esce intrabar su TP/SL (high/low, al livello) come il backtest
Chiude il gap live-vs-backtest delle fade/DIP01: prima il worker controllava solo il
close, ora controlla high/low della barra ed esce AL LIVELLO tp/sl (SL prioritario),
identico alla semantica intrabar del backtest. +4 test. Pairs/rotation/tsmom invariati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 18:00:12 +02:00
Adriano 9e91ad6335 merge: fase 2 portafogli - validazione runner + worker live honest/TSM01
A) PortfolioRunner certificato (pool/ribilancio/ledger == backtest).
B) worker live dedicati: DIP01 (Strategy), BasketTrendWorker (TR01), RotationWorker (ROT02),
   TsmomWorker (TSM01) + integrazione runner (resample 1h->4h/1d). PORT06 gira live completo.
Validati vs reference (TSM01 esatto, ROT02 canonico, TR01 stesso ordine). 35 test passano.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 17:52:47 +02:00
Adriano 924ed8eeff docs: fase 2 completata - tutti gli sleeve PORT06 girano live (worker dedicati + validazione) 2026-05-29 17:50:43 +02:00
Adriano fe8c272460 test(portfolio): valida worker honest/TSM01 vs backtest reference
TSM01 esatto (+98%==+98%); ROT02 riproduce il +1303% canonico (reference normalizzata
su finestra piu' corta = +984%); TR01 stesso ordine (+465 vs +591%, differenza di
convenzione capitale-unico-live vs media-equity-report, non un bug). Worker fedeli.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 17:50:08 +02:00
Adriano a7ada9f36c feat(portfolio): integra worker honest/TSM01 nel runner (PORT06 live completo)
build_worker_for gestisce basket/rotation/tsmom + DIP01 via StrategyWorker; run()
fetcha 1h e resampla a 4h/1d, lookback dimensionato sui daily (TSM01 252g); tick
multi-asset per kind. _defs marca TR01/ROT02/TSM01 col kind+universo. Niente piu'
sleeve saltati in PORT06.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 17:45:39 +02:00
Adriano 1e60835612 feat(live): TsmomWorker (TSM01) consenso TSMOM multi-orizzonte risk-gated 2026-05-29 17:42:44 +02:00
Adriano a40315563e feat(live): RotationWorker (ROT02) dual-momentum top-k risk-gated 2026-05-29 17:41:13 +02:00
Adriano e7e8041dae feat(live): BasketTrendWorker (TR01) EMA-cross long/flat multi-asset
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 17:39:11 +02:00
Adriano ce601c4507 feat(live): DIP01 dip-buy come Strategy single-asset (worker via StrategyWorker)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 17:36:32 +02:00
Adriano dc63399cc7 docs(portfolio): piano fase 2-B worker honest/TSM01 dedicati (6 task) 2026-05-29 17:35:10 +02:00
Adriano e374cca103 test(portfolio): valida runner pool+ribilancio+ledger == backtest (identico)
Certifica il livello aggiunto dal PortfolioRunner (capitale pool, ribilancio
giornaliero, ledger aggregato): replay deterministico == port_returns del backtest
(errore 4.4e-08, floating-point). Fedeltà per-worker: pairs esatta, fade approssimata
(exit close live vs intrabar backtest = gap noto dello StrategyWorker), shape a tempo ok.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 17:28:03 +02:00
Adriano 2749553577 merge origin/main (discovery strumenti downloader) in shape+portfolios 2026-05-29 17:22:15 +02:00
Adriano 0bb14d1c6e merge: shape patterns (SH01) + cartella portfolios (PORT01-06, runner pool)
Ricerca pattern-forma (4/5 famiglie rumore, SH01 Shape-ML edge/diversificatore) +
cartella portfolios/ completa (portafogli pool, backtest+live, Cerbero v2, default PORT06).
21 test passano. Live v1 = fade+pairs+shape; honest/TSM01 backtest-only (fase 2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 17:21:51 +02:00
Adriano 04f64c8f89 feat(portfolio): compare_all confronto PORT01-06 in un processo 2026-05-29 16:32:57 +02:00
Adriano 0f582db265 fix(portfolio): runner data_dir dedicata, no resize posizioni aperte, poll da config, +test cap/cluster_rp
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 16:22:33 +02:00
Adriano d02bc10ab5 docs(portfolio): documenta cartella portfolios, comandi, scope live e default PORT06
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 16:13:49 +02:00
Adriano a5547fb3d2 feat(portfolio): PortfolioRunner live (data v2, tick, ribilancio giornaliero, ledger)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 16:05:07 +02:00
Adriano 2b3d3e3ff8 feat(portfolio): build_worker_for (worker esecutori con capitale da alloc pool)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 16:02:35 +02:00
Adriano 169819fe31 feat(portfolio): portfolios.yml + load_active_portfolio (override operativi) 2026-05-29 16:00:54 +02:00
Adriano 7a4bdb74f0 feat(portfolio): PortfolioLedger (alloc, equity/DD, persistenza+resume)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 15:59:57 +02:00
Adriano eaf4800b6d feat(portfolio): definizioni PORT01-06 + report run() (default PORT06)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 15:58:33 +02:00
Adriano 3f6b0ccf91 feat(portfolio): SleeveSpec/Portfolio/backtest con parità verso report_families
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 15:55:29 +02:00
Adriano 9ff469cb8e feat(portfolio): builder unificato equity-per-sleeve (parità con report_families)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 15:51:38 +02:00
Adriano d99c9895bb feat(portfolio): schemi di peso (equal/manual/cap/inverse_vol/cluster_rp) 2026-05-29 15:49:27 +02:00
Adriano ea04dcd9d1 feat(portfolio): metodi Cerbero v2 (get_historical_v2, get_instruments, get_ticker_batch)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 15:47:10 +02:00
Adriano 753d786bb5 docs(portfolios): piano di implementazione TDD (10 task)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 15:29:41 +02:00
Adriano 602c46e5bf docs(portfolios): design spec cartella portfolios (brainstorming)
Portafogli come oggetti di prima classe (pool condiviso, backtest+live unificati,
ribilancio periodico, 4 schemi pesi, data layer Cerbero v2). Default PORT06
(master+shape, cap pairs 33%, leva 2x). Include analisi accorpamento sleeve
(cluster per asset/regime, pairs=47% rischio) e fuori-scope (ledger unico,
Hyperliquid alt, cointegration).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 15:24:17 +02:00
Adriano 9e1be75444 analysis(portfolio): clustering sleeve per correlazione + contributo rischio
I cluster naturali sono per ASSET/REGIME, non per famiglia (BTC-reversion,
ETH-reversion, trend TR01+TSM01, shape, rotation ROT02). Ridondanza lieve
(max corr 0.43). PAIRS = 47% del rischio a equal-weight -> conferma cap 30-35%.
Equal-weight batte inverse-vol/risk-parity in OOS calmo (pairs corrono liberi).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 13:09:27 +02:00
Adriano e002968914 report(shape): integra SH01 (sleeve SHAPE) nel report per-anno e nell'integrazione MASTER
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 12:43:19 +02:00
Adriano 2596687679 feat(shape): SH01 Shape-ML validato come diversificatore + doc
Validazione dura del solo edge sopravvissuto alla ricerca shape (ML walk-forward
LogisticRegression sulle feature di forma). SH01 config W24 H12 th0.58:
- BTC robusto ovunque (expanding +219%/OOS+42% Sharpe2.72 8-9anni; rolling2y
  +166%/+96%; stress leva2x+slippage OK), ETH/ADA solo expanding, LTC/SOL/XRP no.
- Griglia 5/27 robuste su cresta W24/H8-12 -> overfit moderato, config conservativa.
- Free-lunch: corr +0.08 col MASTER, aggiungerlo migliora OOS (Sharpe 4.33->5.10,
  DD 4.7->4.2%). Diversificatore, non motore standalone. Regge fee 0.20% RT.

SH01 come Strategy (in MODULE_MAP) + run() riproducibile. shape_ml_research esteso
con walk-forward rolling (train_window). Live richiede worker con retraining.
Diario 2026-05-29-shape.md, CLAUDE.md famiglia SHAPE-ML.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 12:31:26 +02:00
Adriano 4ac87ab385 research(shape): 5 famiglie di pattern-forma su harness onesto
Harness shape_lab (analog kNN causale, no look-ahead verificato) + 5 ricerche
parallele. 4/5 famiglie = RUMORE (confermano dominanza mean-reversion):
- analog kNN forma grezza: solo BTC-overfit, non robusto >=2 asset
- encoding candele UP/DOWN/DOJI + body/shadow: hit-rate ~50%, muore a fee
- DTW + template geometrici: DTW peggiora euclidea; template overfit
- PIP/pivot/zig-zag: 0/48 config robuste
1/5 = EDGE REALE: ML walk-forward (LogisticRegression) sulle feature di forma.
  BTC logit W24H12 th0.58: FULL +219% / OOS +42% / Sharpe 2.72 / 8-9 anni+ /
  regge fee 0.20% RT (+60/+26). Causalita' verificata. Da validare a fondo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 12:09:28 +02:00
Adriano Dal Pastro 2b01443efe Merge branch 'main' of ssh://git.tielogic.xyz:222/Adriano/PythagorasGoal
# Conflicts:
#	CLAUDE.md
#	README.md
2026-05-29 08:06:19 +00:00
Adriano Dal Pastro 5ac9ebed5b docs: documenta discovery/validazione strumenti e gate del downloader
CLAUDE.md, README.md, API_REFERENCE.md aggiornati per il nuovo layer
src/data/instruments.py: validazione strumenti per exchange (Deribit +
Hyperliquid; esclusi Alpaca e Bybit testnet), congruenza prezzo cross-exchange,
registry come allowlist, gate nel downloader. Aggiunti schemi param
get_instruments/get_markets/get_historical per exchange e convenzione simboli
Deribit (inverse vs USDC lineari). Universo dati esteso con SOL/LTC/ADA 1h.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 07:51:47 +00:00
Adriano Dal Pastro 1561005d41 feat(data): discovery + validazione strumenti con gate nel downloader
src/data/instruments.py: enumera i perpetui per exchange (Deribit, Hyperliquid;
esclusi Alpaca/stocks e Bybit per feed testnet farlocco) e valida ogni strumento
sui DATI STORICI realmente raccoglibili:
  - esistenza, congruenza OHLC, not-flat (scarta contratti morti)
  - liquidita' (volume daily) e congruenza prezzo cross-exchange via mediana
    del base-coin (scarta outlier come Deribit SOL-PERPETUAL=9.6 vs SOL ~82)
Produce data/instruments_registry.json con strumenti validi, timeframe e start-date.

Gate: _download_cerbero_range rifiuta strumenti non validati (override esplicito
allow_unvalidated). La raccolta dati e' possibile solo per strumenti validati.

Registry attuale (testnet): Deribit 18/106 validi (BTC dal 2018, alt dal 2022),
Hyperliquid 66/74. I major liquidi (BTC,ETH,SOL,LTC,ADA,XRP,DOGE,AVAX,BNB,...) passano.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 07:49:57 +00:00
Adriano b9e5176a5b docs: aggiorna README e CLAUDE.md allo stato attuale (4 famiglie + pairs worker)
README riscritto: 4 famiglie (FADE/HONEST/PAIRS/TSMOM) con profili netti OOS,
portafoglio MASTER e numeri sobri anti-overfit, tabella strategie completa, comandi
analisi/validazione, struttura aggiornata (pairs_worker), dati 8 asset + nota naming
Deribit (alt = _USDC-PERPETUAL), paper trading a 2 gambe con sezione pairs in YAML.
CLAUDE.md: struttura/comandi/sezione paper trader aggiornati (niente piu' "solo MR01").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 09:48:39 +02:00
Adriano a60ad30ac0 fix(live): naming Deribit corretto per alt -> tutte le 5 coppie pairs tradabili live
Gli alt su Deribit sono perp LINEARI USDC: "<COIN>_USDC-PERPETUAL" (storia dal 2022),
non "<COIN>-PERPETUAL" (vuoto per LTC/ADA, dati errati per SOL). INSTRUMENT_MAP corretto.
Smoke test live (live_smoke_pairs.py): tutte e 5 le coppie ricevono feed fresco (1448
barre, ultima ~0.4h) e ticcano. Riabilitate tutte le coppie in strategies.yml.
BTC/ETH restano inverse ("<COIN>-PERPETUAL"). CLAUDE.md / docstring PR01 aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 09:26:27 +02:00
Adriano bd31a15548 fix(live): smoke test REALE pairs -> live solo ETH/BTC (alt assenti su Deribit)
Test reale (scripts/analysis/live_smoke_pairs.py): fetch live Cerbero + tick vero per
coppia. Scoperta: l'endpoint Deribit serve solo BTC/ETH freschi; LTC/ADA-PERPETUAL sono
VUOTI e SOL ha pochi dati. Il backtest usava i parquet locali (8 asset completi), ma la
pipeline live no -> tradabile live SOLO ETH/BTC.

- strategies.yml: abilitata solo la coppia ETH/BTC; le altre 4 disabilitate con nota
  (valide a backtest, off finche' non si aggiunge un feed live per gli alt).
- live_smoke_pairs.py (nuovo): verifica end-to-end della pipeline live (no ordini reali).
- CLAUDE.md / docstring PR01: distinzione esplicita logica-validata vs live-disponibile.

Onesta': la validazione precedente era backtest-equivalence (ESATTA), NON test live;
il test live ha rivelato il limite del feed alt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 09:16:50 +02:00
Adriano 4dc0e77ee5 feat(live): worker a 2 gambe per i pairs (PR01 market-neutral)
src/live/pairs_worker.py: PairsWorker market-neutral (long A / short B sullo z-score
del log-ratio, exit |z|<=z_exit o max_bars, FEE SU 2 GAMBE = 2*fee_rt*lev, stato
persistente come StrategyWorker). multi_runner: sezione `pairs:` nello YAML, fetch di
entrambe le gambe, tick/status/shutdown; INSTRUMENT_MAP esteso agli alt. strategies.yml:
5 coppie PR01 (config universale n50 z2 zx0.75 mb72).

Validazione (scripts/analysis/validate_worker_pairs.py): replay live bar-per-bar ==
backtest pairs_sim ESATTAMENTE -> ETH/BTC capitale 2.870.429 = 2.870.429, 1754 trade,
win 74.1% identici. Caveat: shortabilita'/liquidita' del perp B sugli alt da verificare
in trading reale. CLAUDE.md / docstring PR01 aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 09:12:25 +02:00
Adriano 9a066eb76f feat(analysis): report_families.py - riepilogo strategie/famiglie per anno + integrazione
Report consolidato: (A) Ret%/anno per famiglia (FADE/HONEST/PAIRS/TSM01) e portafogli,
(B) Ret%/anno per ogni strategia singola (15 sleeve), (C) analisi di integrazione delle
nuove famiglie nel MASTER (+pairs/+TSM01/esteso) con correlazioni, (D) numeri sobri e
raccomandazione (leva 2x, cap pairs 30-35%).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 08:20:23 +02:00
Adriano 7226946911 refactor(explore): irrobustimento anti-overfit di pairs/TSM01/master
Giro di validazione scettica (walk-forward, plateau, stress, scomposizione):

- PAIRS: config PER-COPPIA -> config UNIVERSALE (n50 z2 zx0.75 mb72), niente
  cherry-picking. Plateau confermato (heatmap 20/20 Sharpe>1) + walk-forward
  (ETH/BTC 11/12 finestre+, BTC/LTC 9/10). Scartata BNB/ETH (overfit). 5 coppie robuste.
- TSM01: gross 0.45->0.30 (stesso Sharpe, DD 22->15%); corr reale con ROT02 = 0.62
  (non 0.53); diversificatore, non motore. Robusto (36/36 config OOS+).
- Confluenza multi-TF SCARTATA: overfit (taglia 97% trade, ~40 in 8 anni, Sharpe crolla).
- MASTER: numeri sobri onesti -> OOS Sharpe 7.7/DD 2.3% e' regime calmo 2024-25
  (ottimistico ~50%); worst-DD 90g ~6%, Sharpe atteso ~5, regge leva 2x+slippage.
  Config robusta: equal-weight, leva 2x, cap pairs ~30-35% (sono ~57% del rischio).

Quanto trovato regge l'esame anti-overfit; numeri comunicati sobri. Doc/CLAUDE/memoria aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 08:11:33 +02:00
Adriano 945c2a2db6 feat(explore): 3a ondata - PAIRS a 6 coppie + verdetti su 4 nuove famiglie
Espansione dei meccanismi provati + 2 nuovi sondaggi (agenti paralleli, honest):
- PAIRS espansa a 6 coppie robuste: + BTC/LTC (robusta 1h E 4h, Sharpe 2.21, DD 24-34%),
  ETH/SOL e BNB/ETH (Sharpe 2.4+, solo 1h). Pattern: alt-liquido vs major.
- Fade su 6 alt: 0 robuste (mean-reversion vive solo su BTC/ETH; DOGE = artefatto).
- Low-vol anomaly: invertita in cripto (vince alta vol), ridondante -> scartata.
- Confluenza multi-TF: dimezza il DD di MR01 (ETH same Sharpe a DD 38% vs 63%) ->
  variante low-DD utile, non edge nuovo.

MASTER + 6 pairs (15 sleeve): CAGR 47->71%, OOS DD 4.7->2.3%, Sharpe OOS 4.33->7.71,
tutti gli anni positivi. Bilancio robusti: famiglia PAIRS (6) + TSM01 = 7 strategie nuove.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 01:13:54 +02:00
Adriano 33e3e2a603 feat(explore): esplora 9 famiglie alternative -> PAIRS (nuovo edge forte) + TSM01
Esplorazione onesta con agenti paralleli su harness condiviso (explore_lab.py):
ingresso close[i], netto fee, OOS, DD basso, attenzione fee. 7 famiglie su 9 sono
rumore (stagionalita' oraria/mensile, cross-sectional reversal, opening-range,
lead-lag BTC->alt, continuation intraday) e l'harness le rifiuta senza falsi positivi.

Due edge reali verificati indipendentemente:
- PR01 Pairs: spread reversion market-neutral su log-ratio z-score (ETH/BTC, LTC/ETH,
  ADA/ETH). ETH/BTC CAGR 144% Sharpe 4.04 OOS DD 17% 8/9 anni, corr mercato ~0.02,
  no-look-ahead verificato, regge fee 0.40%/coppia. Fee su 2 gambe (worker da estendere).
- TSM01: TSMOM multi-orizzonte 3/6/12m + risk-off, distinto da ROT02 (corr 0.53),
  DD 22%/12% OOS, mai un anno negativo, regge fee 0.40%.

Payoff: aggiungere i pairs (quasi scorrelati ~0.05) al MASTER -> CAGR 47->66%,
DD 5.2->3.8% full / 4.7->3.3% OOS, Sharpe OOS 4.33->6.86 (combine_v2.py).

Fix: explore_lab.get_df ora produce timestamp ms reale per 1d/4h (era placeholder).
Diario 2026-05-29-exploration.md + nota CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 01:07:08 +02:00
Adriano 666c906907 feat(analysis): report.py aggiornato con numero trade per anno
Report consolidato: (A) Ret% netto per anno di ogni strategia singola + portafogli
(FADE/HONEST/MASTER eq/5050), (B) numero trade per anno per strategia (ingressi per
fade/DIP01; ribilanciamenti per TR01/ROT02 a posizione continua), (C) riepilogo
portafogli FULL/OOS. Config deployata (MR03/ROT01 in waste, ROT02 top_k=3).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 00:33:00 +02:00
Adriano 9ebdfc7a7a feat(ROT02): riduce il DD diversificando (top_k 2 -> 3)
ROT02 concentrava il book su 2 soli asset (DD 40%). top_k=3 dimezza quasi il DD
(40% -> 26%) e ALZA il ritorno full (+1095 -> +1303%, ret/DD da 27 a 50). Il
vol-target abbassa il DD ma sacrifica ritorno (de-leverage) -> tenuto top_k=3
senza VT. Caveat: OOS di ROT02 cala (+98 -> +68%, DD 12 -> 14%), ma il portafoglio
MASTER migliora lo Sharpe full (3.95 -> 4.23). Applicato a ROT02_dual_momentum.py
e a _rot_daily_equity (usata da PORT01/PORT03). Docstring/CLAUDE/diario aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 00:30:55 +02:00
Adriano bcccfde9a0 feat(strategie): portafogli master (PORT02/PORT03) + waste delle peggiori (MR03, ROT01)
Crea gli artefatti accorpati e migliorati:
- PORT02_fade_master: 3 fade (MR01/MR02/MR07) x BTC/ETH = 6 sleeve, filtro trend,
  equal-weight daily. DD 8.2% full / 5.9% OOS, Sharpe 3.95/4.09, CAGR ~46%.
- PORT03_all_master: portafoglio MASTER fade+honest (9 sleeve), varianti equal
  (max Sharpe: DD 5.2%/4.7% OOS, Sharpe 3.95/4.42) e 50/50 (min DD 5.1%/4.3%).

Sposta in scripts/waste/ le due peggiori:
- MR03 keltner_fade: fade piu' debole (BTC Sharpe 1.22), ridondante con MR01, il
  filtro trend la peggiorava; rimuoverla MIGLIORA il portafoglio fade.
- ROT01 xsect_rotation: strettamente dominata da ROT02 (stesso meccanismo, ROT02
  meglio su tutto), non usata da alcun portafoglio.

Sganciata MR03 da strategy_loader, strategies.yml e dal motore portafogli
(risk_management.STRATS). La funzione keltner_fade resta in strategy_research_v2
come record. CLAUDE.md aggiornato.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 00:21:37 +02:00
Adriano 1af4addbdd feat(analysis): studio combinazione strategie fade + honest (diversificazione)
combine_portfolio.py: costruisce l'equity giornaliera di tutte le sleeve (8 fade +
3 honest) su indice comune 2021-2026, misura la correlazione cross-famiglia e
confronta i portafogli FULL/OOS (ret, CAGR, DD, Sharpe).

Risultato: le due famiglie sono quasi scorrelate (corr ~0.05). Combinarle migliora
il rischio/rendimento: equal-weight 11 sleeve -> DD 6.1% full / 4.6% OOS, Sharpe OOS
4.46 (vs honest-only 12% DD / 2.23 e fade-only 8.6% DD / 4.14), CAGR ~43% mantenuta.
Il 50/50 fra famiglie da' il DD piu' basso (5.5% full / 4.0% OOS). Diario 2026-05-29
e nota CLAUDE.md aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 00:14:14 +02:00
Adriano 22c6080873 chore(analysis): pulizia e accorpamento script di analisi (25 -> 15 file)
- accorpa risk_improvements.py + risk_portfolio.py -> risk_management.py
  (sezione A screening leve, sezione B filtro trend + portafoglio)
- rimuove 4 script legacy della famiglia squeeze (ormai in waste, non
  referenziati): compare_strategies, best_yearly, final_report, yearly_market_report
- rimuove 5 script honest_* di diagnostica/iterazione superati da honest_matrix
  (consolidato) e non importati: honest_diag, honest_diag2, honest_candidates,
  honest_yearly, honest_yearly2
- mantiene il core honest (lab/improve/improve2/rotation/trend) + canonici
  (final/matrix), tutta la ricerca fade (strategy_research[_v2]), validazione
  (oos_validation, validate_worker_mr01), intrabar_test (lezione squeeze)
- aggiorna riferimento in CLAUDE.md. Import-check: 14/14 moduli OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 00:08:24 +02:00
Adriano fd547748dd merge: strategy_free -> main (fade MR02/MR03/MR07 + filtro trend Acc/DD)
Integra il lavoro su branch strategy_free, indipendente dalle strategie oneste
(DIP/TR/ROT/PORT) gia' su main: nessun file in comune, merge pulito.

- 3 nuove fade mean-reversion validate OOS fee-aware: MR02 Donchian fade,
  MR03 Keltner fade, MR07 Return reversal (+ base condivisa fade_base).
- Filtro trend (trend_max/ema_long) su tutte le fade: alza Acc e riduce DD
  (drastico su ETH), edge OOS confermato; modello portafoglio equipesato.
2026-05-29 00:00:51 +02:00
Adriano a51129acf6 feat(analysis): matrice PnL/anno consolidata (confronto strategie + portafoglio)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 23:53:37 +02:00
Adriano 1b099bb47b feat(analysis): tabella per-anno (PnL/DD) versioni migliorate + portafoglio
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 23:52:25 +02:00
Adriano 783fa5546f feat(analysis): miglioramenti - ROT02 dual-momentum + portafoglio (DD 12%)
Obiettivo: alzare Acc, ridurre DD, migliorare PnL. Leve oneste, no tuning per-anno.

- ROT02: overlay absolute-momentum (cash se BTC<SMA100) su ROT01. Domina su tutte
  le metriche: FULL +679->+1095%, OOS +44->+98%, DD 53->40%.
- DIP01 market-gate (variante low-DD): alza Acc (ETH 52->57, SOL 49->52) e dimezza
  il DD (ETH 53->23), al costo di PnL. De-risking opzionale; su BTC il gate va evitato.
- PORT01: portafoglio equal-weight giornaliero delle 3 sleeve anti-correlate
  (DIP01+TR01+ROT02). DD 12% (sotto ogni sleeve), CAGR 45%, 2022 bear -1% (era -30%).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 23:49:14 +02:00
Adriano cff0d08fca feat(risk): filtro trend per alzare Acc e ridurre DD + modello portafoglio
Filtro opzionale trend_max/ema_long su tutte le fade (MR01/MR02/MR03/MR07):
salta i segnali quando |close-EMA200|/ATR supera la soglia (non fadare un trend
o crollo estremo). Con trend_max=3.0 (default in strategies.yml): accuratezza su
e DD giu' su 7/8 sleeve, drastico su ETH (MR01 71->26%, MR02 42->25%,
MR03 66->34%, MR07 46->21%); edge OOS confermato. MR03 BTC: filtro disattivo
(unico sleeve dove peggiora entrambe).

Scartate come non robuste: vol-target sizing e skip-alta-volatilita' (peggiorano
sia Acc che DD). Aggiunto modello di portafoglio equipesato su sotto-conti
indipendenti: DD aggregato ~14% full / ~10% OOS sul paniere di 8 sleeve, contro
20-70% del singolo -> vera leva anti-drawdown.

Banco di prova: scripts/analysis/risk_improvements.py, risk_portfolio.py.
Helper trend_distance() in fade_base. CLAUDE.md e diario aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 23:47:52 +02:00
Adriano ad141f080c feat(analysis): report per-anno (Trade/Acc/DD/PnL) delle 3 strategie
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 23:42:04 +02:00
Adriano 212427ffa1 feat(analysis): 3 strategie oneste validate OOS multi-crypto (DIP/TR/ROT)
Ricerca onesta post-squeeze su 8 crypto (2018-2026), engine fee-aware con
ingresso eseguibile a close[i], uscita TP/SL intrabar, OOS held-out, sweep fee.

Lezione madre: shortare cripto perde OOS sistematicamente (campione net-bull)
-> tutte le strategie robuste sono long-biased.

Tre meccanismi distinti e complementari:
- DIP01  dip-buy z-score reversion (long-only, 1h)  robusto BTC/ETH/SOL
- TR01   EMA 20/100 trend-following (long-only, 4h) robusto su 5/8 asset
- ROT01  rotazione cross-sectional momentum sul paniere (1d) OOS +44%, param-insensitive

Engine e validazione: scripts/analysis/honest_lab.py + honest_final.py
(+ honest_candidates/diag/diag2/trend/rotation). Diario in docs/diary/.

Onesto sull'obiettivo: €50/giorno su €1000 in pochi mesi non e' raggiungibile a
rischio sano (~1825%/anno); edge reali 30-60% OOS pluriennale. Via realistica:
portafoglio delle 3, leva moderata, crescita composta.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 23:28:00 +02:00
Adriano 21d3ba609d feat(strategie): 3 nuove fade mean-reversion validate OOS fee-aware (MR02/MR03/MR07)
Trovate e promosse 3 strategie con edge netto distinto da MR01, stessa
metodologia (ingresso close[i], netto fee 0.10% RT + leva 3x, OOS ultimo 30%,
robustezza su griglia + sweep fee 0.00-0.20%):

- MR02 Donchian Fade: fade rottura canale H/L, TP al centro. BTC +172% OOS.
- MR03 Keltner Fade: canale ATR su EMA (indipendente da Bollinger). BTC +112%.
- MR07 Return Reversal: fade movimento di barra estremo (z dei rendimenti). BTC +105%.

Tutte positive netto OOS su entrambi gli asset e su tutto lo sweep fee, anche
0.20% RT pessimista (validate anche via oos_validation live-path). Scartate
MR04 (= MR01 riparametrizzato), MR05 (ADX non robusto), MR06 (RSI2 ETH neg).

Base condivisa fade_base.FadeStrategy (backtest intrabar TP/SL/max_bars).
Aggiunte a strategy_loader e strategies.yml (BTC+ETH 1h). Ricerca in
strategy_research_v2.py. Diario e CLAUDE.md aggiornati.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 23:26:21 +02:00
Adriano Dal Pastro 48435f6858 feat(live): worker con exit TP/SL/max_bars per MR01 + doc aggiornata
StrategyWorker ora supporta exit guidati dalla strategia via Signal.metadata
(take-profit alla media / stop-loss ad ATR / time-limit), con fallback al
vecchio hold_bars/stop -2% per strategie senza metadata. Usa fee_rt della
strategia (MR01 = 0.10% RT reale Deribit, non piu' 0.20% hardcoded).
Persistenza di tp/sl/max_bars in status.json per resume.

Re-validato col worker reale (replay finestre mobili 1h, fee 0.10%):
  BTC 1h MR01: +196% OOS, ETH 1h: +251% OOS (nov 2023->mag 2026) — coerente col backtest.

README + CLAUDE.md riscritti: squeeze = artefatto di look-ahead -> waste,
MR01 mean-reversion unica attiva, metodologia anti-look-ahead e fee reali 0.10% RT.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:46:35 +00:00
Adriano Dal Pastro 9879b46688 refactor(strategie): tieni solo MR01 mean-reversion, squeeze -> waste
L'analisi out-of-sample fee-aware ha dimostrato che l'intera famiglia
squeeze-breakout (SQ01-04, MT01, ML01, AD01, CM01, PD01) non ha edge:
le accuratezze storiche 76-82% erano un artefatto di look-ahead (ingresso
a close[i-1] con direzione decisa da close[i]). Sotto ingresso onesto a
close[i] e fee reali tutte perdono, anche a fee zero.

- nuova MR01_bollinger_fade (mean-reversion): edge netto validato OOS,
  robusto su griglia parametri e fino a 0.20% fee RT. BTC 1h n50 k2.5: +201% OOS, DD 15%
- 9 strategie squeeze spostate in scripts/waste/
- strategy_loader + strategies.yml: solo MR01 (BTC/ETH 1h)
- signal_engine.train: validazione OOS (accuratezza test + signal precision)
- scripts/analysis/strategy_research.py: harness di ricerca fee-aware

NOTA: lo StrategyWorker va aggiornato per usare gli exit TP/SL passati in
metadata prima di tradare MR01 dal vivo (ora esce solo a hold_bars/stop fisso).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:22:11 +00:00
Adriano Dal Pastro ca88e62a11 feat(analysis): validazione out-of-sample fee-aware delle strategie
oos_validation.py: backtest OOS fedele al worker live (non-overlap, hold,
stop, fee, leva) su finestra held-out. Mostra che l'edge storico 76-79%
e' un artefatto di look-ahead (ingresso a close[i-1]) e che nessuna regola
di direzione onesta supera il lancio di moneta; le fee sono secondarie
(4/6 config perdono anche a fee zero).

intrabar_test.py: ingresso intra-barra su 5m vs close 15m a parita' di exit.
Lo "scatto" del breakout e' avverso (rientro immediato alla media), quindi
la granularita' piu' fine non recupera edge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 19:57:15 +00:00
Adriano Dal Pastro 8fd2c16cac fix(live): MT01 usa trend 1h live da Cerbero, non dal parquet statico
Il paper trader restava a zero trade: il feed Cerbero era fermo a
mezzanotte (bug end_date lato cerbero-mcp, poi risolto) e MT01 leggeva
il trend 1h da un parquet statico, di fatto congelandolo (gap ~15h sul
bar corrente). Ora il runner fa fetch 1h live per le strategie MTF e lo
passa a generate_signals via il parametro df_1h (fallback al parquet se
assente). Aggiornati CLAUDE.md, README e diario 2026-05-28.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 15:30:26 +00:00
Adriano 31be1b43aa docs: aggiorna README e CLAUDE.md con strategie MT01/PD01/CM01/AD01
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 09:50:58 +02:00
Adriano bdcef09057 chore: untrack paper_trades runtime data + report per anno/mercato
- data/paper_trades/ rimosso dal tracking (dati runtime, gitignored)
- scripts/analysis/yearly_market_report.py: accuracy/trades/PnL per anno×mercato

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 09:46:24 +02:00
348 changed files with 45524 additions and 1411 deletions
+23
View File
@@ -16,3 +16,26 @@ data/processed/
*.pt
*.pth
notebooks/.ipynb_checkpoints/
data/paper_trades/
data/portfolio_paper/
data/portfolio_paper_stats/
data/portfolios/
# watermark fondi del reconciler (stato runtime, contiene il balance)
data/funds_watch.json
# stato locale di tooling (non condiviso)
.claude/
.omc/
# dati regime (DVOL/funding/feature cache, rigenerabili)
data/regime/
_disp_scratch/
data/regime/dispersion_features.parquet
# storico catena opzioni importato da cerbero-bite (rigenerabile: options_fetcher.py)
data/options/
data/_reset_backup/
# game artifacts (log/json di scripts/games e gate)
data/games/
.env.mainnet
+37
View File
@@ -292,3 +292,40 @@ curl -X POST http://localhost:9000/mcp-bybit/tools/get_ticker \
Per lo schema completo dei body di richiesta e risposta:
<http://localhost:9000/apidocs>.
---
## 15. Discovery strumenti — schemi `get_instruments` / `get_markets` / `get_historical`
Schemi dei body verificati sull'OpenAPI live (usati da `src/data/instruments.py`).
### Lista strumenti
| Exchange | Tool | Body | Risposta (campi utili) |
|---|---|---|---|
| Deribit | `get_instruments` | `{currency:"any", kind:"future", offset:int, limit:100}` (paginato, `has_more`) | `instruments[].name` (es. `BTC-PERPETUAL`, `SOL_USDC-PERPETUAL`), `expiry`, `tick_size` |
| Bybit | `get_instruments` | `{category:"linear", symbol?}` | `instruments[]`: `symbol`, `status`, `base_coin`, `quote_coin` |
| Hyperliquid | `get_markets` | `{}` | lista `{asset, mark_price, funding_rate, open_interest, volume_24h, max_leverage}` |
### Storico OHLCV (`get_historical`, chiave `candles` uniforme `{timestamp(ms),open,high,low,close,volume}`)
| Exchange | Body |
|---|---|
| Deribit | `{instrument, start_date:"YYYY-MM-DD", end_date, resolution}` — resolution `1/5/15/60/1D` |
| Bybit | `{symbol, category:"linear", interval:"1/5/15/60/D", start:int_ms, end:int_ms, limit}` |
| Hyperliquid | `{asset|instrument, start_date, end_date, resolution:"1m/5m/15m/1h/1d", limit}` |
### Simboli Deribit
- BTC/ETH → perpetui **inverse**: `BTC-PERPETUAL`, `ETH-PERPETUAL`
- Altcoin → perpetui **lineari USDC**: `<COIN>_USDC-PERPETUAL` (es. `SOL_USDC-PERPETUAL`)
- Trappola: `LTC-PERPETUAL`/`ADA-PERPETUAL` non esistono; `SOL-PERPETUAL` esiste ma è un contratto sbagliato (prezzo ~9.6 vs SOL reale ~82).
### Validazione (lato progetto)
`src/data/instruments.py` valida ogni strumento sui dati storici realmente
raccoglibili — esistenza, congruenza OHLC, not-flat, liquidità (volume daily) e
**congruenza prezzo cross-exchange** (scostamento dalla mediana del base-coin ≤5%).
Solo gli exchange con feed affidabile sono inclusi: **Deribit** e **Hyperliquid**
(esclusi Alpaca/stocks e **Bybit**, il cui feed testnet è farlocco). Output in
`data/instruments_registry.json`; il downloader scarica **solo** strumenti validati.
> **Testnet.** Il token osservatore punta a testnet (`"testnet": true` nei ticker):
> i prezzi possono divergere dal mainnet. La congruenza cross-exchange via mediana
> è il filtro che scarta i feed incongrui prima di usarli per backtest/trading.
+592 -32
View File
@@ -17,40 +17,111 @@ Progetto di ricerca: riconoscimento pattern frattali per trading algoritmico su
## Struttura
```
src/data/ → download e caricamento dati (downloader.py)
src/data/ → download e caricamento dati
downloader.py → download/caricamento parquet (gate: solo strumenti validati)
instruments.py → discovery + validazione strumenti per exchange, registry
src/fractal/ → indicatori frattali (patterns.py, indicators.py, similarity.py)
src/backtest/ → engine di backtesting (engine.py)
src/strategies/ → classe base Strategy ABC + indicatori condivisi
base.py → Strategy, Signal, BacktestResult, YearlyStats
indicators.py → keltner_ratio, detect_squeezes, ema, atr, rv, correlation
src/live/ → paper trading live multi-strategia
multi_runner.py → orchestratore: carica YAML, fetch candele, tick worker
strategy_worker.py → worker indipendente: capital, trade log, stato persistente
multi_runner.py → orchestratore: carica YAML (strategies + pairs), fetch candele, tick worker
strategy_worker.py → worker single-leg: capital, trade log, stato persistente.
Exit guidati da strategia (TP/SL/max_bars via Signal.metadata),
fallback hold_bars/stop -2%. Usa fee_rt della strategia.
pairs_worker.py → worker a 2 GAMBE per PR01 (market-neutral): long A / short B sullo
z-score del log-ratio, exit |z|<=z_exit o max_bars, fee su 2 gambe.
strategy_loader.py → import dinamico classi Strategy da scripts/strategies/
cerbero_client.py → client HTTP per Cerbero MCP (Deribit testnet)
signal_engine.py → squeeze + ML real-time (per ML01)
cerbero_client.py → client HTTP per Cerbero MCP (Deribit testnet): dati + ordini
(place_order market/reduce_only, get_trade_history, get_positions)
execution.py → ExecutionClient: esecuzione REALE su Deribit (shadow). notional→
amount (lineari USDC + inverse), open/close_amount reduce-only,
verifica sul trade (order_id), fee reali dai trades[]
signal_engine.py → squeeze + ML real-time (legacy ML01, ora in waste) + validazione OOS
telegram_notifier.py → notifiche Telegram per trade
scripts/strategies/ → strategie attive (SQ01-SQ04, ML01)
scripts/waste/ → strategie scartate (W01-W22 + REF originali)
scripts/analysis/ → script di confronto e report
src/portfolio/ → portafogli di prima classe (capitale-pool, backtest+live)
base.py → SleeveSpec, Portfolio (.backtest), load_active_portfolio
weighting.py → schemi pesi: equal/cap/inverse_vol/cluster_rp/manual
sleeves.py → builder unificato equity-per-sleeve (fonte unica, parità report)
ledger.py → PortfolioLedger: capitale/PnL/DD/persistenza+resume
runner.py → PortfolioRunner live (data Cerbero v2, sizing, ribilancio)
src/version.py → APP_VERSION (legge il file VERSION) — mostrato nei msg Telegram
src/strategies/fade_base.py → FadeStrategy + helper: atr, trend_distance, hurst_skip_mask (loss-guard),
exit close-confirm SL via param sl_confirm_atr (EXIT-16)
scripts/strategies/ → strategie con edge validato OOS: FADE (MR01/MR02/MR07),
HONEST (DIP01/TR01/ROT02), PAIRS (PR01), TSMOM + portafogli (PORT01/02/03);
FR01_hurst_calm_fade.py = record ricerca (robusto ma NON deployato)
scripts/portfolios/ → definizioni PORT01-06 (_defs.py) + report run() + hourly_report.py (Telegram)
scripts/waste/ → strategie scartate (W01-W28 + famiglia squeeze SQ/MT/ML/AD/CM/PD)
scripts/analysis/ → ricerca/validazione OOS fee-aware (strategy_research, oos_validation, ...);
regime_fetcher.py + regime_lab.py (DVOL/funding/feature regime per la ricerca);
exit_lab.py + exit_policies/ (harness ricerca exit: cache segnali, train/OOS);
options_fetcher.py + options_chain.py (storico opzioni REALE da cerbero-bite);
option_overlay_lab.py (overlay opzioni prezzato; mr02eth_port06_gate.py = gate swap-sleeve);
pairs15m_*.py (gate/flatcheck/smoke ETH/BTC 15m -> sleeve PR_ETHBTC_15M)
scripts/games/ → gioco "Blind Traders": 100 agenti ciechi su BTC/ETH anonimi (engine.py,
arena.py epoche+cull, agent_brief.py digest, run_game.py). Origine del BLEND 15m.
Varianti: options_* (strutture in opzioni, BS+skew+DVOL; opt_calibrate da
cerbero-bite), session_* (pattern orari), grid_* (griglie da STRATEGIA_GRIGLIA.md;
gate scripts/analysis/grid_game_gate.py). arena: GAME_NO_LIVE=1 vieta le
strategie gia' deployate (pairs, fade zscore/breakout/momentum)
scripts/bump_version.py, scripts/deploy.sh → versionamento e deploy (bump+commit+rebuild)
VERSION → versione semver (cotta nell'immagine, +1 ad ogni deploy)
strategies.yml → config multi-strategy paper trader
docs/diary/ → diario di ricerca giornaliero
docs/specs/ → specifiche di design
data/raw/ → file .parquet OHLCV (gitignored)
data/raw/ → file .parquet OHLCV (gitignored) | data/regime/ → DVOL+funding+feature (gitignored)
data/options/ → storico catena opzioni per-strike (bid/ask/IV/greche/OI) importato da cerbero-bite (gitignored)
data/instruments_registry.json → allowlist strumenti validati (gate del downloader)
```
## Comandi
```bash
uv sync # installa dipendenze
uv run python -m src.data.downloader # scarica dati storici
uv run python scripts/strategies/SQ02_squeeze_antifake_vol.py # miglior strategia robusta
uv run python scripts/strategies/ML01_squeeze_gbm.py # squeeze + ML (GBM)
uv run python -m src.live.multi_runner # paper trading live multi-strategia
docker compose up -d # deploy Docker
uv run python -m src.data.downloader # scarica dati storici (solo strumenti validati)
uv run python -m src.data.instruments # (ri)costruisci il registry strumenti validati
uv run python scripts/strategies/MR01_bollinger_fade.py # backtest una strategia (es. fade)
uv run python scripts/strategies/PR01_pairs_reversion.py # backtest pairs market-neutral
uv run python scripts/analysis/strategy_research.py # ricerca strategie fee-aware OOS
uv run python scripts/analysis/oos_validation.py # perche' la famiglia squeeze e' scartata
uv run python scripts/analysis/report_families.py # report per anno di tutte le famiglie
uv run python scripts/analysis/validate_worker_pairs.py # replay worker 2 gambe == backtest
uv run python -m src.live.multi_runner # paper trading live multi-strategia (strategie + pairs)
uv run python scripts/portfolios/PORT06_master_shape.py # report backtest portafoglio (default)
uv run python -m src.portfolio.runner # paper trading a PORTAFOGLIO (capitale pool)
uv run python scripts/analysis/smoke_portfolio.py # smoke live data layer Cerbero v2
uv run python scripts/analysis/live_exec_smoke.py # smoke ESECUZIONE reale (ordine→verifica→fee, testnet)
uv run python scripts/analysis/live_shadow_smoke.py # smoke catena shadow nel worker (open/close reali)
uv run python scripts/analysis/regime_fetcher.py # fetch DVOL+funding (Deribit mainnet) -> data/regime/
uv run python scripts/analysis/exit_lab.py # (ri)costruisci cache segnali exit-lab + parity check
uv run python -m src.live.dashboard --port 8787 # dashboard web (servizio compose 'dashboard', porta 8787)
uv run python scripts/analysis/ledger_vs_backtest.py --since 2026-06-13 # report fuga di esecuzione (reale vs sim)
uv run python scripts/analysis/trades_status.py [--since ISO] # report "stato trades": POOL reale vs PAPER-STATS, realizzato + unrealized
./scripts/deploy.sh [patch|minor|major] # DEPLOY: bump versione + commit + rebuild Docker
uv run pytest # test
```
> **Dashboard web (`src/live/dashboard.py`, servizio compose `dashboard`, porta 8787).**
> Stdlib http.server, legge `data/`: equity (restyling), PnL totale e per-strategia,
> **grafico equity per famiglia**, trade attivi in tempo reale (entry/mark/PnL **REALI** —
> il feed sim-decisione testnet è dislocato, vedi sotto) + chiusi, modal per-strategia
> (curva reale vs sim + scheda `strategie_attive.html`), **area PAPER distinta** (equity
> propria dei 4 multi-asset), strategie **ritirate** marcate (staleness >30min) + versione
> sistema/strategia. Nessuna auth → solo rete interna. `docker compose up -d --build dashboard`.
> **Cron host (monitoraggio, già schedulati):** `hourly_report.py` (orario), `drift_monitor.py`
> (07:15), `reconcile_account.py --telegram` (:40), **`ledger_vs_backtest.py --since 2026-06-13
> --telegram` (08:30)** = il GATE per scalare il capitale (per gli sleeve eseguiti sim==backtest
> per costruzione → reale-vs-sim = fuga di esecuzione: slippage+fee+netting; verdetto 🟢/🟡/🔴).
> **Deploy.** Il sorgente è **COPY nell'immagine Docker** (non montato) → `docker compose restart`
> NON ricarica il codice: serve **`docker compose up -d --build`** (o `./scripts/deploy.sh`, che bumpa
> la versione, committa e rebuilda). Il volume `data/` persiste → i worker fanno RESUME dello stato.
> La **versione** (file `VERSION`, semver, +1 ad ogni deploy via `deploy.sh`) compare nei messaggi
> Telegram (notifiche trade + report orario) → correli ogni msg al codice che l'ha generato.
## Dati storici
Scaricati e salvati localmente in Parquet. Per rigenerarli:
@@ -64,37 +135,456 @@ df = load_data("ETH", "15m") # carica un asset/timeframe
Fonte primaria: Cerbero MCP (endpoint `/mcp-deribit/tools/get_historical`).
Token observer: nel file `secrets/observer.token` del progetto CerberoSuite.
### Strumenti & validazione (gate raccolta dati)
`src/data/instruments.py` scopre e **valida** gli strumenti per ogni exchange
implementato — **Deribit** e **Hyperliquid** (esclusi Alpaca/stocks e **Bybit**,
il cui feed testnet è farlocco). Per ogni perpetuo enumera via `get_instruments`
/`get_markets` e verifica sui **dati storici realmente raccoglibili**:
esistenza, congruenza OHLC, not-flat (scarta contratti morti), liquidità (volume
daily) e **congruenza prezzo cross-exchange** (scostamento dalla mediana del
base-coin ≤ 5% → scarta outlier come `SOL-PERPETUAL`=9.6 vs SOL reale ~82).
Output: `data/instruments_registry.json` (strumenti validi, timeframe, start-date).
**Gate:** `_download_cerbero_range` rifiuta gli strumenti non validati (override
`allow_unvalidated=True` solo per casi eccezionali). Rigenera con
`python -m src.data.instruments`.
> **NB testnet.** Il token Cerbero punta a testnet; la congruenza cross-exchange
> è il filtro che distingue i feed realistici (Deribit, Hyperliquid) da quelli
> farlocchi (Bybit). Simboli Deribit: BTC/ETH = `<COIN>-PERPETUAL` (inverse);
> alt = `<COIN>_USDC-PERPETUAL` (lineari USDC). Registry attuale: Deribit 18/106,
> Hyperliquid 66/74 validi (major liquidi: BTC dal 2018, alt dal 2022).
## Strategie attive
Tutte le strategie estendono `src.strategies.base.Strategy` con interfaccia comune:
`generate_signals() → backtest() → report()`.
> **LEZIONE CRITICA (2026-05-28).** L'intera famiglia squeeze-breakout (SQ01-04,
> MT01, ML01, AD01, CM01, PD01) è stata **scartata in `scripts/waste/`**: le
> accuratezze storiche 76-82% erano un **artefatto di look-ahead**. Quei backtest
> decidono la direzione con `sign(close[i]-close[i-1])` (la candela di breakout `i`)
> ma entrano a `close[i-1]` — cioè comprano *prima* della candela che usano per
> scegliere la direzione. Dal vivo il worker scopre il breakout solo a `close[i]`
> ed entra lì: l'edge sparisce (win-rate ~47%, lancio di moneta). Sotto ingresso
> onesto e fee reali **tutte perdono, anche a fee zero**. Inoltre i breakout
> *rientrano* (mean-reversion > continuation). Vedi `scripts/analysis/oos_validation.py`
> e `intrabar_test.py`.
| Codice | Nome | Tipo | Accuracy | Note |
|--------|------|------|----------|------|
| SQ01 | Squeeze Base | Regole | 76.7% | Squeeze breakout puro, baseline |
| SQ02 | Antifake+Vol | Regole | **79.7%** | **Miglior robusto** — 9 anni, Sharpe 5.01 |
| SQ03 | Filtered | Regole | 79.2% | Filtri selezionabili (9 preset) |
| SQ04 | Ultimate | Regole | 81.6% | Max accuracy ma concentrato 2018 |
| ML01 | Squeeze+GBM | ML | 78.8% | Walk-forward, €8-12/day, DD basso |
Tutte le strategie estendono `src.strategies.base.Strategy`
(`generate_signals() → backtest()`). Le strategie mean-reversion condividono
`src.strategies.fade_base.FadeStrategy` (backtest intrabar TP/SL/max_bars).
**Strategie con edge netto validato OOS fee-aware (tutte fade/mean-reversion):**
Per aggiungere una strategia:
1. Crea script in `scripts/strategies/` che estende `Strategy`
2. Aggiungi mapping in `src/live/strategy_loader.py``MODULE_MAP`
3. Aggiungi entry in `strategies.yml` per paper trading
| Codice | Nome | Meccanismo | Edge OOS netto (1h, fee 0.10% RT) | DD | Note |
|--------|------|-----------|-----------------------------------|----|------|
| **MR01** | Bollinger Fade | banda std attorno a SMA | BTC +201% / ETH +1238% | 15-72% | Fada la banda, TP alla media, SL ad ATR |
| **MR02** | Donchian Fade | estremi canale H/L | BTC +172% / ETH enorme | 30-42% | Fada la rottura del canale, TP al centro |
| **MR07** | Return Reversal | z dei rendimenti di barra | BTC +105% / ETH +195% | 25-46% | Fada il movimento estremo, exit in ATR; esposizione ~8% |
> **MR03 Keltner Fade** spostata in `scripts/waste/`: era la fade più debole
> (BTC Sharpe 1.22, il filtro trend la peggiorava) e ridondante con MR01 (stessa
> idea di banda). Rimuoverla dal portafoglio ne ha *migliorato* le metriche.
> La funzione `keltner_fade` resta in `strategy_research_v2.py` come record.
**Lezione confermata:** l'edge è sempre *mean-reversion* (i breakout rientrano).
Il trend-following (Donchian trend, RSI cross) e gli oscillatori senza filtro
(RSI revert, ADX-filtered fade) perdono netti → restano scartati.
Ogni strategia è robusta su **tutta** la sua griglia parametri (entrambi gli asset
→ tutte positive OOS) e su **tutte** le fee 0.00-0.20% RT (margine ampio).
MR01 validato col worker reale: BTC +196% / ETH +251% OOS (nov 2023→mag 2026).
Ricerca completa: `scripts/analysis/strategy_research.py` (MR01) e
`scripts/analysis/strategy_research_v2.py` (MR02/MR03/MR07).
Validazione live-path: `scripts/analysis/oos_validation.py`.
**Filtro trend (riduzione DD + aumento Acc).** Tutte le fade accettano i parametri
opzionali `trend_max` / `ema_long`: saltano i segnali quando il prezzo è troppo
esteso rispetto al trend di fondo (`|close EMA(ema_long)| / ATR(14) > trend_max`),
cioè quando si starebbe fadando un trend/crollo estremo. Con `trend_max=3.0`,
`ema_long=200` (default in `strategies.yml`): accuratezza su tutti gli sleeve
e DD giù drasticamente su ETH (MR01 71%→26%, MR02 42%→25%, MR03 66%→34%,
MR07 46%→21%), edge OOS confermato (vedi `scripts/analysis/risk_management.py`).
Unica eccezione: MR03 BTC, dove il filtro peggiora entrambe → lasciato disattivo.
Leva non robusta scartate: vol-target sizing e skip-alta-volatilità (peggiorano).
**SWAP filtri fade: hurst→trend (2026-06-07).** Il gate PORT06 sul path live
(`scripts/analysis/trendmax_port06_impact.py`, parità 1.00000 col canonico) ha mostrato che
**post-EXIT-16 il loss-guard Hurst è ridondante-dannoso**: EXIT-16 ha eliminato i wick-stop che
hurst evitava → gli ingressi saltati (66% delle barre!) sono in maggioranza tornati vincenti.
Su PORT06: LIVE hurst-only FULL Sh 7.23 / OOS 9.35-DD 1.68 vs **TREND-ONLY 7.89 / 9.91-DD 1.20**
(domina su tutte le metriche; hurst+trend insieme over-filtra: 7.11, metà dei trade; plateau
trend_max 2.5/3.0/3.5 robusto). TREND-ONLY è la config che la ricerca EXIT-16 aveva davvero
promosso (entries trend-filtrate, no hurst) e che il live non aveva mai eseguito. **Live ora:
`trend_max=3.0, ema_long=200` nelle 6 fade di `_defs.py`, `hurst_max` rimosso** (la maschera resta
in `fade_base`). Monitor: `hourly_report` traccia lo stop-rate per epoca PRE→HURST→TREND.
Lezione: ri-gateare ogni filtro quando cambia l'exit engine. Diario
`docs/diary/2026-06-07-trendmax-gate.md`. Il paragrafo sotto resta come record storico:
**Loss-guard Hurst (storico: live dal 2026-06-02 al 2026-06-07, poi sostituito dal filtro trend).** Le fade accettano `hurst_max`: saltano i segnali in
regime PERSISTENTE/trending (rolling-Hurst ≥ soglia), dove si concentrano stop-loss e perdite
(diagnosi: stop-rate 43% per hurst>0.55 vs 21% anti-persistente; i peggiori 1% trade hanno hurst
medio 0.61). Helper `src.strategies.fade_base.hurst_skip_mask` (rolling-Hurst causale **dalle sole
close** → nessun feed esterno; step=6 per velocità live). **`hurst_max=0.55` attivo sulle 6 fade in
`_defs.py`**: il test decisivo a livello PORT06 lo conferma — **FULL DD 4.10%→2.39% (quasi dimezzato),
Sharpe 6.62→6.76, OOS Sharpe 8.89→9.15**. È l'UNICO meccanismo anti-perdite che supera il gate (ADX,
vol-expansion/vratio, efficiency-ratio, time-stop, vol-target FALLISCONO: tagliano i winner insieme ai
loser; i claim esterni ADX/ATR-ratio non si replicano su queste fade crypto). NB: il filtro agisce solo
sul path LIVE (`spec.params`); il backtest canonico (`build_everything`/regression-lock) NON è filtrato
→ il live farà MEGLIO del backtest sul DD. Ricerca: `scripts/analysis/fade_lossguard_workflow.js`,
diagnosi `fade_loss_by_regime.py`, diario `docs/diary/2026-06-02-fade-lossguard.md`.
**Effetto misurato (backtest):** stop-loss fade 67% in numero (1881→621), perdite totali 68%, coda
61%→−48% (lo stop-RATE per-trade scende poco, 42→38%: il guard lavora riducendo l'ESPOSIZIONE nel
regime tossico, non rendendo sicuro ogni trade). **Monitor live:** `hourly_report.py` traccia lo
stop-rate fade PRIMA/DOPO l'attivazione (14:34 UTC del 2026-06-02) e dà il verdetto su Telegram quando
il campione DOPO ≥30 (già confermato: stop-rate live PRIMA 42% == backtest 42.1%).
**FIX EXIT-16 live — confirm su barra COMPLETATA (2026-06-05).** Il worker valutava il confirm-SL
sul prezzo della candela IN FORMAZIONE ad ogni poll → reintroduceva la wick-sensitivity che EXIT-16
elimina (audit: 2 stop su 3 del crash ETH erano wick-stop che il backtest non avrebbe preso in quel
momento). Ora `tick` valuta il confirm SOLO sul close dell'ultima barra completata (detection: la
riga -1 del df è la candela in corso finché `now < ts[-1]+bar_ms`), buf dall'ATR della stessa barra;
fill al prezzo corrente (≈ stress lag_close_exit, OK in exit-lab); TP intrabar invariato. La concausa
feed-gap NON è mitigabile lato exit (fill reali ≈ sim) e l'entry-guard post-flat è BOCCIATA (skippare
i segnali dopo barre flat PEGGIORA tutti gli sleeve ETH: la candela-gap è l'overshoot che la fade
fada). Aggiunto alert Telegram `STALE_FEED` (≥2 barre 1h flat → notifica + gap % al risveglio, solo
osservabilità). Diario `docs/diary/2026-06-05-confirm-sl-forming-bar.md`.
**EXIT-16 close-confirm SL (ATTIVO LIVE, 2026-06-04; esteso a DIP01 il 2026-06-07).** Le fade E
DIP01 accettano `sl_confirm_atr` (live: 0.5 in `_defs.py`): lo SL **intrabar è disattivato** e lo stop scatta solo se il **CLOSE** della barra
sfonda `sl ∓ 0.5·ATR14`, con uscita al close (TP intrabar al livello e max_bars invariati; in modalità
confirm il TP ha priorità nel bar). Scoperta della ricerca exit-lab (34 agenti, 23 famiglie esplorate +
10 verifiche avversariali + test PORT06): **gli stop intrabar da wick sono falsi negativi** — l'overshoot
che buca lo stop e rientra è esattamente il movimento che la fade fada. Verificato: indipendente dal
loss-guard Hurst, plateau buffer 0.4-1.0, regge fee 2x/lag/slippage, coda ≈ base nei crash veri (FTX:
+2.4% vs 39% del no-SL puro). **PORT06: FULL Sharpe 6.47→7.84 DD 4.10→2.60, OOS Sharpe 8.82→10.06 DD
1.30→1.15.** La famiglia "cavalca il prezzo" (trailing/ride/partial-runner, 15 varianti) è invece tutta
SCARTATA: oltre il TP=media non c'è coda (4ª conferma). Collaterali: l'engine intrabar filla gli stop
"al livello" anche su gap-through (54% dei casi per stop tight) → bias PRO stop-stretti nelle ricerche
future; mai deployare strategie con `sl=0` (il fallback 2% del worker non si applicherebbe). Harness
riusabile `scripts/analysis/exit_lab.py` + policy in `exit_policies/`. Implementazione: `fade_base.backtest`
+ `StrategyWorker.tick` (param `sl_confirm_atr`, None = comportamento storico; il backtest canonico
`build_everything` resta NON filtrato → il live farà meglio del backtest, come per il loss-guard).
Diario `docs/diary/2026-06-04-exit-lab.md`.
**Portafoglio.** Diversificare su sotto-conti indipendenti equipesati (le 4 strategie
× BTC/ETH, pos 0.15 ciascuno) abbatte il DD aggregato: ~14% full / ~10% OOS sul
paniere di 8 sleeve, contro il 20-70% del singolo. È la vera leva anti-drawdown.
**Combinare le due famiglie (fade + honest).** Le fade (reversione intraday 1h) e le
honest (DIP/TR/ROT trend+rotazione multi-crypto) sono **quasi scorrelate**
(correlazione cross-famiglia ~0.05). Combinarle in un unico portafoglio migliora il
rischio/rendimento rispetto a ciascuna famiglia da sola: equal-weight dei 9 sleeve
→ DD 5.2% full / 4.7% OOS e Sharpe 4.23 full / 4.33 OOS (vs honest-only 12.6% DD /
2.20 Sharpe e fade-only 8.2% DD / 4.09 Sharpe), CAGR ~47% mantenuta. Studio in
`scripts/analysis/combine_portfolio.py`.
**ROT02 — riduzione DD (top_k 2→3).** La rotazione dual-momentum honest concentrava
il book su 2 soli asset (DD 40%). Diversificare su 3 (`top_k=3`) dimezza quasi il DD
(40%→26%) e *alza* pure il ritorno full (+1095%→+1303%, ret/DD da 27 a 50); il
vol-target abbassa il DD ma sacrifica ritorno, quindi si tiene top_k=3 senza VT.
Applicato a `ROT02_dual_momentum.py` e a `_rot_daily_equity` (usata dai portafogli).
**Portafogli pronti (artefatti accorpati e migliorati).** Oltre a `PORT01` (solo
honest), due script in `scripts/strategies/`:
- `PORT02_fade_master.py` — le 3 fade × BTC/ETH accorpate (6 sleeve, filtro trend),
equal-weight daily: DD ~8.2% full / 5.9% OOS, Sharpe 3.95/4.09, CAGR ~46%.
- `PORT03_all_master.py` — portafoglio MASTER (fade + honest, 9 sleeve). Due varianti:
`equal` (massimo Sharpe: DD 5.2%/4.7% OOS, Sharpe 4.23/4.33) e `5050` fra le due
famiglie (minimo DD: 5.0% full / 4.5% OOS). È la configurazione consigliata.
Come `PORT01`, sono meta-portafogli (script `run()` di report), non `Strategy` con
`generate_signals`, quindi non nel `strategy_loader`.
**Esplorazione famiglie alternative (branch `strategy_explore`, 2026-05-29).** Esplorate
9 famiglie nuove con agenti paralleli su harness onesto condiviso
(`scripts/analysis/explore_lab.py`). 7 sono rumore (rifiutate: stagionalità oraria/mensile,
cross-sectional reversal, opening-range breakout, lead-lag BTC→alt, continuation intraday —
quest'ultima riconferma la dominanza mean-reversion). Due edge reali:
- **PR01 Pairs** (`scripts/strategies/PR01_pairs_reversion.py`): spread reversion
market-neutral sul log-ratio z-score, **config UNIVERSALE** `n=50 z_in=2.0 z_exit=0.75
max_bars=72` (anti-overfit, niente tuning per-coppia). **5 coppie robuste**: ETH/BTC
(Sharpe 4.36), LTC/ETH (3.08), ADA/ETH (2.69), BTC/LTC (2.36, robusta anche 4h), ETH/SOL
(1.96, la più debole). Pattern: sempre alt-liquido vs major. Plateau confermato
(heatmap 20/20 Sharpe>1) + walk-forward (ETH/BTC 11/12 finestre+). **BNB/ETH scartata**
(overfit). Corr col mercato ~0.02-0.08. Fee su **2 gambe**: worker live implementato
(`src/live/pairs_worker.py`, sezione `pairs:` in `strategies.yml`). LOGICA validata
(`validate_worker_pairs.py`: replay == backtest ESATTO). LIVE (`live_smoke_pairs.py`,
smoke reale Cerbero): **tutte e 5 le coppie con feed live fresco**. Naming Deribit:
BTC/ETH = `<COIN>-PERPETUAL` (inverse); alt = `<COIN>_USDC-PERPETUAL` (lineari USDC,
storia dal 2022). Trappola: `LTC-PERPETUAL`/`SOL-PERPETUAL` danno vuoto/dati errati →
usare sempre `_USDC-PERPETUAL`. Resta da verificare solo liquidità/fill in esecuzione.
Verifica edge: `pairs_research.py`.
- **TSM01** (`scripts/analysis/tsmom_research.py`): TSMOM multi-orizzonte 3/6/12m + risk-off,
**gross 0.30**, distinto da ROT02 (corr 0.62), DD 15-22%, mai un anno negativo. Robusto
(36/36 config OOS+) ma diversificatore, non motore di ritorno (rende meno di ROT02).
Aggiungere i **5 pairs** al MASTER (quasi scorrelati, ~0.02-0.09) è il free-lunch più
grande (`scripts/analysis/combine_v2.py`). **Numeri sobri onesti** (l'OOS singolo 2024-25
è regime calmo → ottimistico ~50%): worst-DD su 90g rolling **~6%** (non 2.3%), Sharpe
atteso **~5** (mediana semestrale), ogni anno positivo dal 2021, regge **leva 2x +
slippage doppio** (CAGR 36%, Sharpe 5.1). Config robusta raccomandata: **MASTER-esteso
equal-weight, leva 2x, cap pairs ~30-35%** (i pairs sono ~57% del rischio; worker live a
2 gambe implementato, validato e con feed live su tutte e 5 le coppie — resta da
verificare liquidità/fill in esecuzione reale). La confluenza multi-TF è stata SCARTATA (overfit).
**Pattern del segnale per FORMA (branch `shape_patterns`, 2026-05-29).** Esplorate 5 famiglie
di *shape forecasting* con agenti paralleli su harness onesto (`scripts/analysis/shape_lab.py`:
analog kNN causale, no-look-ahead verificato). **4/5 sono RUMORE** (riconfermano la dominanza
mean-reversion): analog kNN sulla forma grezza (solo BTC-overfit), encoding candele
UP/DOWN/DOJI+body/shadow (hit-rate ~50%), DTW+template geometrici (DTW *peggiora* l'euclidea;
template overfit), PIP/pivot/zig-zag (0/48 robuste). Vedi `scripts/analysis/shape_*_research.py`.
- **SH01 Shape-ML** (`scripts/strategies/SH01_shape_ml.py`): UNICO edge. Una LogisticRegression
legge 17 feature di forma (body/shadow, rendimenti, pendenza/curvatura, pos max/min, RSI,
estensione) e predice il segno del rendimento a H barre in **walk-forward** (scaler+modello
solo sul passato, no leakage). Config **W24 H12 th0.58**. A differenza dello squeeze
**regge fee 0.20% RT**. Win-rate ~50% → l'edge è nell'**asimmetria**, non nella frequenza.
Validazione (`scripts/analysis/shape_ml_validate.py`): BTC robusto OVUNQUE (expanding +219%/
OOS +42% Sharpe 2.72 8-9 anni; rolling 2y +166%/+96%; stress 2x+slippage OK), ETH/ADA
robusti solo expanding (secondari), LTC/SOL/XRP scartati. Griglia: **5/27 celle robuste su
cresta stretta W24/H8-12** → overfit moderato, scelta la config conservativa. **Valore vero:
diversificatore** (corr +0.08 col MASTER); aggiungerlo migliora l'OOS del MASTER (Sharpe
4.33→5.10, DD 4.7%→4.2%). NON motore standalone. **LIVE (2026-06-01): gira come StrategyWorker
reale** (vedi fix wiring sotto in SCOPE LIVE). Diario: `docs/diary/2026-05-29-shape.md`.
**ARGO / GEX opzioni (analisi 2026-06-01, SCARTATO).** Valutato ARGO (lettura del gamma-exposure dei
dealer Deribit) come filtro di regime. Esito **NO-GO**: il net-GEX si calcola live (Deribit mainnet
public API, OI reale ~368k contratti, DVOL/funding storici gratis) ma **lo storico per-strike dell'OI
non è gratuito → non backtestabile OOS** (stesso muro delle opzioni W18/19/21). Niente evidenza crypto,
segno fragile, mercato dominato dai perp. Diario `docs/diary/2026-06-01-argo-gex-feasibility.md`.
**Storico opzioni REALE da cerbero-bite (2026-06-09, il muro ARGO è caduto per il periodo recente).**
Il container **cerbero-bite** (accanto, `/opt/docker/cerbero-bite`) accumula in continuo lo **storico
per-strike** della catena opzioni Deribit (BTC+ETH) nella tabella `option_chain_snapshots` del suo
SQLite (`bite-data:/app/data/state.sqlite`, root-only): bid/ask/mid/**IV per-strike**/greche/OI/volume,
**dal 2026-05-01**, cadenza ~12 min (~110k righe/asset). È esattamente il dato che ARGO/W18-21
credevano non-gratuito. `scripts/analysis/options_fetcher.py` lo importa via `docker exec`
`data/options/{eth,btc}_chain.parquet`; `scripts/analysis/options_chain.py` (`OptionChain`) espone
loader + `skew_curve()` / `premium_levels()` (aggregati ROBUSTI) + `quote()` causale best-effort.
**Caveat granularità:** cerbero-bite snapshotta una **fetta rotante** (~1 scadenza per ciclo) → ottimo
per skew/premi **aggregati**, limitato per il pricing **per-trade** preciso (usare la finestra di
staleness). **Pannello regime `market_snapshots`** (anch'esso importato → `data/options/market_snapshots.parquet`,
loader `options_chain.load_market(asset)`; merge causale su prezzo via `options_chain.attach_market(df, asset)`):
feature REALI pre-calcolate — **spot, dvol, realized_vol_30d, iv_minus_rv (VRP), funding perp/cross,
`dealer_net_gamma` (net-GEX dealer!), `gamma_flip_level`, oi_delta_pct_4h, liquidation_long/short_risk**.
È il segnale ARGO/GEX che il progetto dichiarò non-backtestabile: ora accumula, reale e allineato (lo
`spot` elimina il proxy ATM). **Copertura reale:** spot/dvol dal 2026-03-26 (sparse/daily fino a fine
aprile), **net-GEX denso orario solo da ~2026-05-01** → ~5-6 settimane, **un singolo regime calmo**:
analisi esplorativa OK ma NIENTE edge validabile ora (i prior GEX/VRP non si replicano su 9 sett.;
`liquidation_risk` costante 'low'). Valore FORWARD: rivalutare quando il pannello attraversa
gamma-flip/liquidazioni/crash con held-out. **NB look-ahead:** usare `attach_market` (merge_asof
causale, NaN prima della copertura), MAI `astype(int64)` su un timestamp datetime (darebbe ns →
match all'ultimo snapshot = leak). **Numeri reali misurati (ETH):** skew put 10% OTM = **×1.1** (liquido, spread ~7%, NON ×1.6
come sembrava da un singolo snapshot a 1g, illiquido); premio reale put 10% OTM ≈ **1.0%/mese** (0.65%/sett),
catastrofe 15%+ OTM ≈ 0.33%/mese. **Vincolo strutturale:** gli strike **10% OTM a 24h NON esistono**
(Deribit lista solo near-ATM sui tenor corti) → un overlay **per-trade a 24h è infattibile**; l'unica
struttura eseguibile è una **put settimanale/mensile standing** (catastrofe-cap di sleeve), da gateare
coi premi reali. Vedi `docs/diary/2026-06-08-mr02eth-replace-search.md`.
**Frattali del segnale × Regime ARGO (ricerca 100 agenti, 2026-06-02, RECORD).** Cercata una strategia
che combini un segnale frattale (Hurst/Higuchi/Williams/analog) con un gate di regime (DVOL/VRP/funding).
Infrastruttura riusabile: `scripts/analysis/regime_fetcher.py` (DVOL+funding da Deribit mainnet →
**`data/regime/`**, NON `data/raw/` che è solo OHLCV) e `regime_lab.py` (feature regime+frattali causali,
cache, harness netto-OOS). Esito: **15 strategie robuste e causali, ma NESSUNA batte/migliora il PORT06**
(diversificatori sovrapposti alle fade). **Finding: il prior ARGO "VRP>0=range=fade" è SMENTITO**
l'edge è su **VRP<0 + DVOL bassa**. Il vincitore `FR01_hurst_calm_fade.py` è robusto ma DILUISCE il
PORT06 (OOS Sharpe 8.89→8.72) → **non deployato** (in `scripts/strategies/` ma NON in MODULE_MAP/yml).
Il sottoprodotto utile è stato il **loss-guard Hurst** (vedi sopra), che invece MIGLIORA il PORT06.
Diario `docs/diary/2026-06-02-fractal-argo-search.md`.
**Metodologia obbligatoria per ogni nuova strategia** (per non ripetere l'errore squeeze):
1. Ingresso eseguibile: direzione e prezzo decisi con dati **fino a `close[i]`**, mai `close[i-1]` con direzione da `i`.
2. Backtest **NETTO** dopo fee realistiche Deribit (**0.10% RT** taker, non 0.20%) + leva.
3. Validazione **out-of-sample** (held-out) + robustezza su griglia parametri + sweep fee.
4. Crea script in `scripts/strategies/`, aggiungi a `MODULE_MAP` (`strategy_loader.py`) e a `strategies.yml`.
Strategie scartate storiche in `scripts/waste/` (W01-W28 + la famiglia squeeze).
**Verso €50/giorno.** Con 4 strategie indipendenti (MR01/MR02/MR03/MR07) × 2 asset
(BTC/ETH) su €1000 ciascuna, il PnL medio storico aggregato è ben oltre €50/giorno;
ma quei numeri sono backtest a leva 3x su 8 anni e includono anni eccezionali (es.
ETH 2024). Stima onesta: il target è *plausibile* su un portafoglio diversificato di
queste fade, ma va confermato col paper trader live prima di rischiare capitale reale.
## Portafogli
- Un `Portfolio` è un oggetto di prima classe (`src/portfolio/`) con definizione (sleeve + schema pesi) e due facce sulla **STESSA** definizione: `.backtest()` (riusa il builder unico di `sleeves.py` → parità esatta con `report_families`) e live (`PortfolioRunner`: capitale pool condiviso, sizing per peso, ribilancio giornaliero, ledger aggregato in `data/portfolios/{code}/`).
- **Schemi peso:** `equal` (default), `cap` (tetto per famiglia, es. pairs 33% — config raccomandata), `inverse_vol`, `cluster_rp` (equal fra cluster naturali poi inverse-vol intra-cluster), `manual`. Definiti in `weighting.py`; la chiave cap è la famiglia (PAIRS/FADE/HONEST/SHAPE/TSM/XSEC).
- **Default `portfolios.yml`:** PORT06 (master+shape), `weighting=cap pairs 0.33 + shape 0.0588`, **leva 3x** (era 2x fino al 2026-06-12, vedi sotto), ribilancio 1D. Backtest PORT06 canonico (dati al 2026-05-28, pre-cap-shape): FULL Sharpe 6.47 DD 4.10% / OOS Sharpe 8.82 DD 1.30%; **con EXIT-16 close-confirm: FULL 7.84 / 2.60%, OOS 10.06 / 1.15%** (i vecchi 6.07/8.19 erano pre-loss-guard/pre-refresh dati). Col cap SHAPE (2026-06-05): FULL 6.43 / 3.96%, OOS 8.58 / 1.36% — assicurazione sulla coda SH01, vedi sotto. Col BLEND ETH/BTC 15m (2026-06-09, v1.1.16): FULL 7.20 / 3.68%, OOS 9.66 / 1.31% — 18 sleeve. Con XS01 (2026-06-09): OOS Sharpe 10.07, FULL DD 3.46 — 19 sleeve (pool live real-only 15; i 4 book multi-asset TR01/ROT02/TSM01/XS01 girano in statistica). **Con SWAP fade 1h→15m (config live attuale, v1.1.30, 2026-06-12, vedi sotto): FULL Sharpe 8.13 DD 2.47% / OOS Sharpe 10.86 DD 2.09%** (regression-lock `test_backtest_parity_cap`).
- **LEVA 3x (v1.1.29, 2026-06-12, scelta utente).** Era 2x ("sobrio") dal primo deploy live. Frontiera ACCEL50 (`scripts/analysis/accel50_research.py`): a Sharpe OOS ~10 il collo di bottiglia è la TAGLIA, non il rischio — lev 2→3 porta gli anni-a-€50/g da 3.3 a 1.9 con FULL DD 3.5→5.2% (modello lineare). PAIRS `position_size_family` 0.20→0.13 per CONSERVARE l'esposizione validata ~0.40 (la leva accelera le famiglie con stop, non i pairs no-stop). Diario `docs/diary/2026-06-12-accel50.md`.
- **SWAP fade 1h→15m (v1.1.30, 2026-06-12, scelta utente "swap secco").** Le 6 fade (MR01/02/07 × BTC/ETH) girano a **15m** invece di 1h: stessi `sid` (pesi/alloc/ledger intatti), `tf="15m"` in `_defs.FADE` + `combine_portfolio.FADE_TF` (le due facce backtest/live sulla stessa def). Gate `scripts/analysis/fade15m_port06_gate.py`: parametri 1h NON ri-tunati (anti-overfit), corr daily 15m↔1h media **0.26** (diversificazione vera, non lo stesso edge più veloce), edge ETH regge il flat-entry-skip (non stale-print). SWAP promosso: FULL 7.34→8.13 / OOS 10.07→10.86 (OOS DD 1.48→2.09 accettato). DIP01 resta 1h (non era nel gate). `max_bars=24` ora = 6h; EXIT-16 confirm sulla barra 15m completata. Caveat: MR02_BTC 15m è il più fee-sensitive (fee2x OOS Sh 0.60) → monitorare le fee reali. **TF sweep** (`fade_tf_sweep.py`): 1m/2m chiusi (fee-death + flat 13-26%), 5m no-swap (MR02_BTC muore a fee2x), 10m in watchlist (ADD bocciato: OOS 10.86→10.76). Diari `docs/diary/2026-06-12-fade15m-gate.md` / `-fade-tf-sweep.md`.
- **XS01 — Cross-Sectional Reversion (ATTIVO LIVE 2026-06-09; dispersion-gate v1.1.20, 2026-06-10).**
Famiglia nuova XSEC, distinta da pairs (pairwise) e fade (single-asset): ogni `hold=12` ore
classifica 8 crypto (UNIVERSE8) per rendimento a `lb=48` ore e va long i perdenti relativi /
short i vincenti (peso ∝ (ret media cross-section)), market-neutral gross 1, fee 0.20%
RT/book (turnover 2). Scorrelato ~0 da pairs e fade. FULL Sharpe ~3.3, plateau lb 12-72 ×
hold 6-24; cost-sensitive (muore a ~0.35% RT/book). Gate PORT06: OOS Sharpe 9.66→10.07,
FULL DD 3.68→3.46. **Dispersion-gate** (`disp_min=0.0313` = p50 TRAIN): entra solo se la
std cross-section del momentum ≥ soglia — diagnostica monotona TRAIN+OOS, plateau p30-p70,
standalone Sharpe 2.50→3.46 (regge fee 2x), PORT06 OOS 10.07→10.37 a DD pari. Come
trend/hurst sulle fade, il gate agisce SOLO sul path live (backtest canonico non filtrato
→ il live farà meglio del backtest). 8 gambe → PAPER (niente esecuzione reale a €2k).
Worker `CrossSectionalWorker` validato (`validate_xsec_worker`: replay == backtest esatto).
**PHASE-TRANCHING (2026-06-11, `tranches=3`)**: la fase del roll non-sovrapposto è arbitraria
e da sola muove Sharpe daily FULL 1.52-2.33 / DD 13.8-33% (timing-luck) → live gira con 3
sub-book sfasati di hold/3 su capitale comune (PnL/K), ensemble di fase SENZA parametri
fittati. Gate `xs01_tranche_gate.py`: plateau K=2 E K=3 promossi (PORT06 OOS Sh 10.07→10.15,
DD 1.48→1.38, FULL pari); validatore esteso (K=1 == xsec_sim esatto; K=3 == unione fasi
0/4/8 esatto). Solo path live (backtest canonico single-phase). Strategia
`scripts/strategies/XS01_cross_sectional.py`, gate `scripts/analysis/xsec_port06_gate.py` /
`xs01_dispersion_gate.py`. NB scartata nella stessa tornata: FC01 funding-carry (nessun
edge su 6.5 anni). Diari `docs/diary/2026-06-09/10-*`.
- **BLEND ETH/BTC 15m flat-skip (ATTIVO LIVE v1.1.16, 2026-06-09).** Sleeve `PR_ETHBTC_15M` accanto al 1h: pairs ETH/BTC a **15m** (n=66, z_in=1.674, z_exit=1.0, max_bars=35) a **mezza size** (`params.position_size=0.10`). **Origine: gioco "Blind Traders"** (100 agenti ciechi su BTC/ETH anonimizzati → riscoprono la mean-reversion, vincitore = spread ETH/BTC 15m; `scripts/games/`). Gate PORT06 sul serio: **non duplicato (corr 1h↔15m = 0.37)**, robusto (16/16 celle Sharpe>1), e l'edge **NON è artefatto delle candele flat ETH 15m** (16% storico; filtrandole resta l'83% dello Sharpe). Engine `pairs_research.pairs_sim_flat` con uscita **LIVE-REALIZABLE** (`flat_skip`: niente entry/exit su barre O=H=L=C, esce alla 1a barra pulita), **regression-lock** a `pairs_sim` con flat_skip=False. **Worker validato** (`validate_worker_pairs` a 15m: replay == backtest, 8452 vs 8453 trade; 1h byte-exact). Runner: fetch sub-orario diretto da Cerbero + `pos_for_spec` override **per-sleeve** (params.position_size > famiglia > globale). Mezza size perché a peso pieno il 15m pesava il 25.8% del rischio PORT06 (→ 11.5%, bilanciato col 1h) — blend-tilt prudente sul **caveat slippage** (a slippage realistico il vantaggio di Sharpe regge, quello di DD si assottiglia: il vero banco di prova è il ledger reale shadow). Diari `docs/diary/2026-06-09-pairs15m-*.md`, gate `scripts/analysis/pairs15m_port06_gate.py` / `pairs15m_gate_final.py` / `pairs15m_flatcheck.py`, smoke `pairs15m_live_smoke.py`.
- **SH01 SENZA STOP-LOSS — by design, CONFERMATO da ricerca (2026-06-05).** Dopo il crash ETH (15.6% su un trade SH01 live), ricerca multi-agente con harness dedicato `scripts/analysis/sh01_exit_lab.py` (cache segnali walk-forward, engine con **fill gap-aware** `worse(livello, open)`, parity esatta con explore_lab, protocollo train≤2023-11-01/OOS): **11 famiglie di stop testate (ATR intrabar/close-confirm, %, chandelier, breakeven, giveback, loser-timestop, disaster-cap close+intrabar, swing, vol-regime), 0 sopravvissute** al gate (ETH migliorato senza degradare BTC, train E oos, plateau). Pattern: ogni stop stretto abbastanza da toccare la coda ETH rompe BTC; ogni stop largo non arriva alla coda; nei crash il fill è al gap, non al livello (lo stop "protettivo" PEGGIORA la coda OOS). Mitigazione adottata: **cap famiglia SHAPE a 0.0588 in PORT06** (≈ dimezzata; costo OOS Sharpe 0.24, FULL DD 0.14pp) — la prossima coda impatta il conto per metà. NON impostare mai `sl`/`sl_confirm_atr` su SH_BTC/SH_ETH. Direzione futura: liquidity-gate sull'entry (skip dopo feed flat). Diario `docs/diary/2026-06-05-sh01-sl-research.md`.
- **Data layer Cerbero v2:** `get_historical_v2` unificato + `get_instruments` (naming robusto) + `get_ticker_batch`. Trading su Deribit.
- **SCOPE LIVE (fase 2 completata):** il runner esegue TUTTI gli sleeve di PORT06. Worker: single `StrategyWorker` (fade MR01/02/07, DIP01, **e SH01**), `PairsWorker` (PR01 2 gambe), e i multi-asset dedicati `BasketTrendWorker` (TR01 4h), `RotationWorker` (ROT02 1d), `TsmomWorker` (TSM01 1d). Il runner fetcha 1h da Cerbero v2 e **resampla a 4h/1d** (lookback dimensionato sui daily: TSM01 usa 252g). Validazione: runner pool/ribilancio/ledger == backtest (`validate_portfolio_runner.py`, identico); worker multi-asset == reference (`validate_honest_workers.py`: TSM01 esatto, ROT02 +1303% canonico, TR01 +32% vs +42% — residuo di convenzione capitale-unico vs media-equity; il bug `mean(rets)` sui soli asset in posizione, che dava 44%, è FIXATO il 2026-06-11: sovrappesava N/k a paniere parziale).
- **SH01 bootstrap full-history (punto-10, 2026-06-07).** La ri-validazione col train-window del
regime live (`sh01_trainwindow_validate.py`) ha mostrato che SH01 a train 365g **NON è robusto**
(BTC fee-2x FULL 42%, ETH Sharpe 0.02, trade-rate 22-26% vs 10% validato: LogReg over-confident,
soglia inerte — la diagnosi sweep era esatta). L'edge è MONOTONO nella memoria: solo l'expanding
full-history passa il gate. Fix live: il runner passa agli sleeve `ml` la storia FULL
(`_with_history`: parquet locale + feed, gap-guard con WARN) e `ml_wf_entries(last_block_only=True)`
fitta SOLO l'ultimo blocco del walk-forward → segnali **identici per costruzione** al WF completo
(parity test esatto), 0.6 s/tick su 73k barre. Manutenzione: tenere fresco il parquet
(`download_all()`). MAI ri-tunare la soglia nel regime corto (instabile/incoerente fra asset).
Diario `docs/diary/2026-06-07-sh01-trainwindow.md`.
- **FIX SH01 wiring (2026-06-01).** SH01 gira come **`StrategyWorker` NORMALE** (NON il vecchio `MLWorkerWrapper` di `multi_runner`, che usava il `SignalEngine` **squeeze SCARTATO**: apriva senza metadata ed usciva a `hold_bars=3`, ignorando del tutto SH01_shape_ml). `SH01_shape_ml.generate_signals` fa il walk-forward (retraining) internamente ad ogni tick ed emette `metadata.max_bars=12` → exit a orizzonte via `StrategyWorker.tick`. Serve ≥4000 barre 1h (`_ML_LOOKBACK_DAYS=365`). Vedi `docs/diary/2026-06-01-sh01-wiring-squeeze-bug.md`.
- **Altri fix StrategyWorker (2026-06-01).** Exit a orizzonte puro per strategie senza TP/SL (`elif self.max_bars`, SH01 esce a H=12 non hold_bars=3); `is_win = net > 0` (win NETTO fee, non lordo); filtro `min_tp_frac` (salta micro-scalp col TP entro le fee); loss-guard `hurst_max=0.55` sulle fade (vedi sopra).
- **Exit intrabar (fase 3, risolto):** lo `StrategyWorker` ora esce sui TP/SL toccati INTRABAR (high/low della barra, al livello, SL prioritario) come il backtest — non più solo sul close. Allinea fade/DIP01 live al backtest intrabar (`tests/portfolio/test_intrabar_exit.py`). Caveat residuo onesto: nel paper trading l'high/low usato è quello della barra in corso al poll; su un fill reale conterebbe il momento del tocco.
- **ESECUZIONE REALE — pairs 2 gambe + SH01 (2026-06-08).** Estesa oltre i fade: i **5 pairs PR01**
eseguono reale a 2 gambe (`PairsExecutionClient`: open/close long A/short B, leg-risk unwind, MAI
close_position; `pairs_enabled: true` acceso a conto flat, v1.1.12); **SH01** (BTC/ETH) esegue
single-leg con exit a orizzonte H=12 — niente TP/SL, `_place_real_tp` no-op e `_real_close` chiude
tutto market reduce-only, disaster-bracket on-book come unica protezione di coda (v1.1.13). Motivo:
SH01 è il diversificatore più decorrelato (senza i 5 sleeve PAPER il DD del portafoglio sale
3.96→5.35%). Copertura reale ora 15 sleeve su 19 (fade+DIP+**6 pairs incl. ETH/BTC 15m**+SH01);
restano simulati TR01/ROT02/TSM01/XS01 (book multi-asset, bloccati dal capitale: rumore
arrotondamento 20-30% a €2k, serve ~€20k). Diari `docs/diary/2026-06-08-pairs-executor.md`.
- **ESECUZIONE REALE — shadow (v1.0.3, 2026-06-03).** I **6 fade** (MR01/MR02/MR07 × BTC/ETH) eseguono ordini **REALI su Deribit testnet** accanto al fill simulato (*shadow*: sim + reale in parallelo, il sim resta la verità che guida le decisioni). `src/live/execution.py` `ExecutionClient`: `open` (market) + `close_amount` (market **reduce-only della SOLA quota del worker** — i 3 fade BTC condividono lo strumento e le posizioni si nettano per conto, quindi NON si usa `close_position` che flatterebbe le quote altrui); **verifica l'esecuzione sul TRADE** (order_id in `get_trade_history`, non sulla size netta aggregata); **fee REALI lette dai `trades[]`**. Strumenti = **lineari USDC** (`BTC_USDC`/`ETH_USDC-PERPETUAL`, amount nel base-coin, step 0.0001/0.001): scelti perché il **payoff lineare == matematica del backtest** (l'inverse `*-PERPETUAL` introdurrebbe una base 1/prezzo) e fee/PnL sono in USDC. Lo `StrategyWorker` tiene un **ledger reale parallelo** (`real_capital`, persistito) e logga `REAL_OPEN`/`REAL_CLOSE` col confronto **slippage** (prezzo sim vs eseguito) e **fee** (assunta 0.10% vs reale). Config: `portfolios.yml``overrides.execution {enabled, sleeves:[MR01,MR02,MR07,DIP01], instruments:{BTC:BTC_USDC-PERPETUAL, ETH:ETH_USDC-PERPETUAL}}` (**DIP01 aggiunto il 2026-06-04**: stesso wiring single-leg, TP resting incluso); pairs/rotation/tsmom/shape restano **simulati** (pairs richiede un executor a 2 gambe con gestione leg-risk; shape non ha TP). **Fee reali misurate = 0.05%/lato = 0.10% RT** (== assunto del backtest, su ETH; BTC inverse era ~0.094%). **Alert Telegram:** `REAL_EXEC_LIVE` (primo ordine reale verificato per worker) + `REAL_OPEN_FAIL`. Smoke (testnet, €0): `scripts/analysis/live_exec_smoke.py` (layer: ordine→verifica→fee) e `live_shadow_smoke.py` (catena worker open/close). **Capitale live portato a 2000** (notional fade ~$35) per ridurre il rumore di arrotondamento su BTC (step lineare ~$6.7). NB: ledger reale ≠ ledger sim — i worker già in posizione sim a un restart non hanno quota reale corrispondente; lo shadow reale parte pulito dalla prossima apertura.
- **Disaster-bracket on-book + alert outage (v1.1.4, 2026-06-07).** A ogni `REAL_OPEN` dei fade
eseguiti il worker piazza uno **STOP_MARKET reduce-only a ~30%** dall'ingresso (trigger sul mark,
`ExecutionClient.place_disaster_sl`, cancellato in `_real_close`): assicurazione per gli outage
(poll-loop fermo = exit non valutati), in operatività normale non scatta mai → 0 costo Sharpe.
Config `overrides.execution.disaster_sl_pct` (0.30; 0=off). NB: il `set_stop_loss` di cerbero-mcp
è un `private/edit` (solo ordini APERTI) → inutilizzabile su market fillati; la cancel di un
trigger order risponde `untriggered` (= successo, verificato testnet). Alert Telegram `FEED_OUTAGE`
dopo 5 poll falliti consecutivi (con elenco posizioni reali aperte) + notifica di ripresa. I fix
di parità della stessa tornata (v1.1.3): TR01 fee×leva + forming-bar TR01/Pairs + WARN
`PANEL_SHORT` su TSM01/ROT02; `hourly_report` ora mostra i multi-asset (sezione MULTI-ASSET).
Diario `docs/diary/2026-06-07-sweep-fixes.md`.
- **VERITÀ CONTABILE su netting di conto (v1.1.24, 2026-06-11, da audit live).** Il modello
"quote per-worker con reduce-only" si rompe quando worker dello stesso strumento hanno
direzioni OPPOSTE (pairs long ETH vs fade short ETH): Deribit cappa/respinge i reduce-only
in silenzio. L'audit ha trovato: close fillato 0.078 ma bookato 0.105 (`Fill.amount` era il
RICHIESTO), 3 gambe pairs mai eseguite col PnL sim sommato al ledger reale (gamba orfana sul
conto, ETH/SOL di fatto short nudo), e il conto short 0.027 ETH più dei libri (riallineato a
mano). Fix: (1) `Fill.filled_amount` (fonte `order.filled_amount`) usato da TUTTI i ledger;
(2) `REAL_CLOSE_PARTIAL` (log+alert) quando il close filla meno del residuo; (3) pairs:
PnL bookato SOLO per le gambe verificate, gamba respinta → `orphan_legs` persistito +
alert `PAIR_LEG_ORPHAN`, `applied` solo con ENTRAMBE le gambe (altrimenti sim_fallback
dichiarato); (4) `REAL_DIVERGENCE` anche su jsonl (prima solo Telegram); (5) runner:
tick isolato per-worker (un'eccezione non salta gli altri; alert `WORKER_ERROR_STREAK` a 5).
**RISOLTO in v1.1.25 col NETTING delle chiusure**: `close_amount` tenta il reduce-only
(sicurezza storica: un bug di stato filla 0) e riesegue il RESIDUO cappato/respinto in
**market puro** — che muove il conto esattamente del delta del libro, cioè netta contro le
quote opposte → niente più gambe orfane/close cappati per costruzione (copre anche i pairs
via `close_pair`). Il chiamante riceve UN Fill combinato (prezzo medio pesato, fee sommate,
`notes` con 'netting'); evento `NET_CLOSE` (log+Telegram) a ogni fallback. La sicurezza
persa sul residuo è coperta dal **reconciler orario** (`reconcile_account.py`, cron host
:40, alert `ACCOUNT_DRIFT`): conto vs Σ libri+orfani, tolleranza 1.5×step, anti-race.
`orphan_legs`/`REAL_CLOSE_PARTIAL` restano come ultima difesa (se fallisce anche il market
puro). Test `tests/portfolio/test_netting_close.py`.
- **TP_PHANTOM — il tocco TP va confermato dal book reale (v1.1.23, 2026-06-11).** Il feed
testnet stampa wick anomali che (a) generano segnali fade su ETH e (b) "toccano" il TP
intrabar della stessa barra: il sim bookava +4% fantasma a bars_held=0 e `_real_close`
chiudeva A MERCATO una posizione il cui resting TP non aveva mai fillato (fee/spread a
giro, 14 giri l'11-06, report Telegram 26/0 vs reale 11/15 — fix conteggio in v1.1.22).
Gate in `StrategyWorker._tp_phantom` (zero parametri, verita' d'esecuzione, NON un filtro
di strategia): tocco sim + **resting LIMIT a zero fill** + prezzo corrente che non ha
raggiunto il livello → exit SOPPRESSA (il limit sul book reale e' l'oracolo: se il prezzo
avesse scambiato li', avrebbe fillato); SL close-confirm e max_bars restano attivi.
Fill (anche parziale) o prezzo oltre il livello o worker non eseguito → comportamento
storico. Fail-open su errori di rete. Log `TP_PHANTOM` dedup per barra + alert Telegram.
Test `tests/portfolio/test_tp_phantom.py`.
- **INVERTED_TP_SKIP — l'entry con TP dal lato sbagliato va soppressa (v1.1.33, 2026-06-16).**
Un wick transitorio sul feed puo' far calcolare alla strategia un `tp` dal lato SBAGLIATO
dell'entry (es. donchian: segnale su barra wickata in basso → tp=centro canale, entry al
prezzo RECUPERATO sopra il proprio tp). L'exit intrabar `bar_high>=tp` (long)/`bar_low<=tp`
(short) scatta a `bars_held=0` in PERDITA, con churn di fee e TP reduce-only respinti
(16-06: 8 giri MR02_BTC 15m, sim 17.9 / reale 2.3 grazie al real-truth; TP_PHANTOM NON
lo prende — niente resting oracle, il prezzo HA superato il livello). Gate in
`StrategyWorker._open_position` (zero parametri, verita' d'esecuzione): se `tp<=entry`
(long) / `tp>=entry` (short) → **entry soppressa** (niente ordine reale/disaster-SL), log
`INVERTED_TP_SKIP` dedup per-barra + alert Telegram. Solo path live (il backtest entra al
close del bar del segnale → mai invertito, resta non filtrato → live meglio del backtest).
Cerotto testnet (il fix vero e' mainnet). Test `tests/portfolio/test_inverted_tp.py`,
diario `docs/diary/2026-06-16-inverted-tp-guard.md`.
- **TP reale = LIMIT reduce-only AL LIVELLO (2026-06-04).** Misurati +235 bps di slippage medio sulle uscite take-profit market-on-poll (sim esce al livello intrabar, il reale chiudeva al poll post-rimbalzo: sim +11.85 vs reale +0.62 USD sui primi 7 close). Fix: a ogni `REAL_OPEN` il worker piazza un **limit reduce-only al TP** (`ExecutionClient.place_tp_limit`, prezzo quantizzato al tick, SOLA quota del worker) → `REAL_TP_RESTING`; a ogni chiusura sim `_real_close` **cancella il resting → riconcilia i fill (anche parziali) via `get_trade_history` per order_id → market reduce-only solo del residuo** → ledger su prezzo combinato. `real_tp_order_id` persistito in `status.json` (resume-safe). Lo **SL resta market-on-poll** (deliberato: i trigger Deribit generano un nuovo order_id al trigger → fill non verificabile per order_id; e sul SL il rimbalzo lavora a favore). Fill da resting = fee **maker ~0%**. Smoke: `live_shadow_smoke.py` (2 scenari, testnet). Diario `docs/diary/2026-06-04-shadow-divergence.md`.
- **position_size per-famiglia (2026-06-07).** `portfolios.yml` accetta `position_size_family`
(chiave = `weighting.family_of`); plumbing `runner.pos_for_spec`. **PAIRS a 0.20** (esposizione
0.40 ≈ il validato 0.45): la famiglia è senza stop e col globale 0.5×lev2 girava a ~2.2x il
validato (ETH/BTC DD grezzo 78% a quella taglia). PORT06 OOS DD 3.40→1.26% al costo di OOS
Sharpe 9.05→8.43 — assicurazione come il cap SHAPE. Gate `pairspos_port06_impact.py`,
diario `docs/diary/2026-06-07-pairspos-gate.md`. NB (2026-06-11): il **disaster-cap sullo
z** (exit se |z|≥z_stop, griglia pre-registrata 3.0-5.0 su tutte le coppie) è **BOCCIATO**
— coda e Sharpe OOS peggiorano ovunque (lo stop realizza la perdita al massimo overshoot e
l'engine rientra nello spread divergente: churn di fee), **5ª conferma** che gli stop su
mean-reversion sono falsi negativi. Record `scripts/analysis/pairs_zstop_research.py`,
diario `docs/diary/2026-06-11-stability-sweep.md`.
- **REAL-TRUTH ledger (2026-06-10, scelta utente).** Con `overrides.execution.real_truth: true`
(ATTIVO) il `capital` dei worker eseguiti si aggiorna col **PnL dei FILL REALI** (fee reali
incluse) invece del PnL sim: `_real_close`/`_real_close_pair` ritornano `(real_pnl, applied)` e
`_close_position`/`_close` applicano il reale al ledger; il sim resta SOLO diagnostica nel log
CLOSE (`pnl_source`/`sim_pnl`/`real_pnl`). Fallback al sim dichiarato (`pnl_source=sim_fallback`)
solo se il trade reale non è mai esistito/fillato (REAL_OPEN_FAIL/leg-fail). Equity → pesi →
allocazioni → notional derivano così dai soldi veri sul conto (il notional reale era GIÀ la
formula sim `capital·ps·lev`; il gap storico sim/reale era contabile: ledger separati + spike
print delle candele testnet che il sim bookava e il reale no). Le DECISIONI (entry/exit) restano
guidate dal feed; i multi-asset (TR01/ROT02/TSM01/XS01) restano sim per costruzione. Test:
`tests/portfolio/test_real_truth.py`. Diario `docs/diary/2026-06-10-real-truth-ledger.md`.
- **RIBILANCIO CONSERVA L'EQUITY (fix v1.1.31, 2026-06-13).** Il ribilancio giornaliero
gonfiava l'equity quando un worker era in posizione: i flat si dividevano l'INTERO `total`
(che includeva il capitale degli in-position) e gli in-position lo trattenevano in più →
doppio conteggio di Σ(capitalalloc) sugli in-pos. Caso reale: MR02_BTC 15m seedato a 181.19
(eredità INIT_LINEAGE) e in posizione al ribilancio delle 00:01 → **+4.77 di equity dal nulla**
(scoperto perché l'equity saliva senza un trade dietro). Fix: `ledger.allocate(weights, reserved={sid:cap})`
— gli in-position TRATTENGONO il loro capitale (deployato, non spostabile), i flat si dividono
`total Σreserved` per peso RINORMALIZZATO → `Σalloc == total` sempre, equity conservata per
costruzione. `runner.rebalance_allocations` passa i `reserved` dai worker `in_position`. Parità
runner intatta (5.8e-08). Test `test_ledger.py`/`test_runner_rebalance.py`. Diario
`docs/diary/2026-06-13-rebalance-conservation.md`.
- **INIT_LINEAGE — eredità capitale al cambio timeframe (2026-06-12).** Un worker al primo avvio
(niente status.json) eredita `capital`/`real_capital` dal worker più recente di STESSA
strategia+asset su altro tf (glob `{strategy}__{asset}__*`), MAI la posizione. Nato dallo swap
fade 1h→15m: i worker nuovi partivano dall'allocazione del pool scartando il PnL del gemello
(16.8 di equity, riallineata a mano col seed la prima volta). `StrategyWorker._inherit_lineage_capital`,
evento `INIT_LINEAGE`. Test `tests/portfolio/test_capital_lineage.py`.
- **STALE_REAL_POSITION — guard anti-orfano nel reconciler (v1.1.31, 2026-06-13).** Lo swap
1h→15m aveva ritirato MR02_BTC 1h dal config mentre teneva uno short REALE → posizione nuda non
gestita per 13h, e il reconciler NON la vedeva (lo status fermo del worker morto contava ancora
come "libro" → conto == libri). `reconcile_account.compute_stale_real_positions(max_age_min=15)`:
un worker con `real_in_position` e status.json fermo da >15 min = non gestito → alert
`STALE_REAL_POSITION`. Discriminante = STALENESS (un worker vivo riscrive ogni poll): cattura
ritirati-da-swap, crashati, rimossi dal config. Orfano chiuso a mano (testnet, +0.85). Resta da
fare la PREVENZIONE lato runner (flattare/consegnare la posizione del ritirato al boot). Diario
`docs/diary/2026-06-13-orphan-swap-guard.md`.
- **Limite noto:** al ribilancio le posizioni APERTE restano sul loro notional (non travasate); fedele al backtest daily-rebalanced entro il turnover infragiornaliero.
## Multi-Strategy Paper Trader
Orchestratore che esegue N strategie in parallelo su dati live Cerbero, ognuna con €1000 USDC virtuali indipendenti.
**Config:** `strategies.yml`lista strategie con asset, tf, sizing, parametri.
**Persistenza:** `data/paper_trades/{strategy}___{asset}__{tf}/` con `trades.jsonl` (append-only) + `status.json` (resume al restart).
**Config:** `strategies.yml`due sezioni: `strategies` (single-leg: fade/honest) e
`pairs` (a 2 gambe). Attive: 6 fade (MR01/MR02/MR07 × BTC/ETH) + 5 coppie PR01.
**Due worker:** `strategy_worker.py` (single-leg) e `pairs_worker.py` (2 gambe,
long A / short B sullo z-score del log-ratio, fee su 2 gambe).
**Persistenza:** `data/paper_trades/{worker_id}/` con `trades.jsonl` (append-only) + `status.json` (resume al restart).
**Hot-add:** aggiungi riga YAML → `docker compose restart` → storico intatto.
**Exit strategia:** se un `Signal` porta `tp`/`sl`/`max_bars` in `metadata` (come le fade), il worker esce su take-profit/stop-loss/time-limit; i pairs escono su |z|≤z_exit o max_bars.
**Naming Deribit (feed live):** major = `<COIN>-PERPETUAL` (inverse); alt = `<COIN>_USDC-PERPETUAL` (lineari USDC). Vedi INSTRUMENT_MAP in `multi_runner.py`.
**Notifiche:** Telegram per ogni trade (richiede `.env` con `TELEGRAM_BOT_TOKEN` e `TELEGRAM_CHAT_ID`).
## Convenzioni
- Strategie in `scripts/strategies/` con codice univoco (SQ01, ML01, ...).
- Script scartati in `scripts/waste/` con prefisso W01-W22.
- Strategie in `scripts/strategies/` con codice univoco (MR01, ...).
- Script scartati in `scripts/waste/` (W01-W28 + famiglia squeeze).
- Diario in `docs/diary/YYYY-MM-DD.md`. Aggiornare dopo ogni esperimento significativo.
- Nessun dato sensibile nei commit (token, chiavi API). Usare `.gitignore`.
- Verificare sempre assenza di data leakage prima di fidarsi dei risultati. In particolare: `returns[i-w : i]` include `close[i]` che è un candle nel futuro — usare `returns[i-w : i-1]`.
@@ -102,6 +592,76 @@ Orchestratore che esegue N strategie in parallelo su dati live Cerbero, ognuna c
## Attenzione
- **Data leakage:** è stata trovata e corretta nello script 05. Ogni volta che si usano rendimenti logaritmici (`np.diff(np.log(close))`), ricordare che `returns[k]` usa `close[k+1]`. I feature devono fermarsi a `returns[i-2]` se il prezzo corrente è `close[i-1]`.
- **Fee:** sempre 0.1% per lato (0.2% round-trip). Includere nel backtest.
- **Fee:** Deribit perp reale = taker ~0.05%/lato (**0.10% round-trip**), maker ~0%. Usare 0.10% RT come baseline (lo 0.20% storico era pessimista 2x). Includere SEMPRE nel backtest: sono vincolo di prim'ordine, molte operazioni = morte per fee. Il worker usa `strategy.fee_rt` (MR01 = 0.001).
- **Leva:** testato con 3x. Aumentare a 5x migliora i rendimenti ma raddoppia il drawdown.
- **GBM:** GradientBoostingClassifier di scikit-learn. Ensemble di alberi decisionali sequenziali. Walk-forward per evitare leakage temporale.
- **Report "stato trades" — separare SEMPRE pool reale e paper-stats.** La directory è la
fonte di verità: `data/portfolio_paper/` = sleeve nel POOL (capitale reale, muovono
equity/conto); `data/portfolio_paper_stats/` = TR01/ROT02/TSM01/**XS01** in SOLA statistica
(PnL a precisione frazionaria, MAI nel ledger). **NON** fare glob su `portfolio_paper*`
(matcha entrambi → gli utili sim dei paper-stats inquinano il realizzato: errore reale del
2026-06-17, +8.15 XS01 attribuiti per sbaglio all'equity). Verità aggregata = ledger PORT06
(`equity`, `total_capital`; unrealized = equitytotal_capital). Usare
`scripts/analysis/trades_status.py` (riconcilia: realizzato POOL + Δunrealized ≈ Δequity).
- **Cerbero `get_historical` (fix 2026-05-28):** `end_date` come data nuda è inclusivo dell'intera giornata fino all'ultima candela chiusa (es. `end=oggi` arriva fino ad ora, non più a mezzanotte); accettati anche timestamp con orario (`...T14:00:00`, naive=UTC); nessun cap a ~5000 righe (paginazione interna). Il client passa già `end=oggi`, ora corretto. Prima del fix il paper trader restava a zero trade perché il feed era fermo a mezzanotte.
- **Dati ETH Deribit 15m:** 14-30%/anno di candele *flat* (O=H=L=C, volume 0, run fino a ~54h) per bassa liquidità del perpetuo. Verificato (2026-05-28): escluderle NON cambia i backtest (Δacc ≤0.5pp) → edge robusto. Resta un caveat operativo (slippage/fill in trading reale, irrilevante per paper). BTC pulito eccetto picco ~8% nel 2024.
- **FEED TESTNET INAFFIDABILE — `ETH-PERPETUAL` inverse CONGELATO (2026-06-13).** Il feed di
DECISIONE che il runner usa per BTC/ETH (inverse perp, `INSTRUMENT_MAP`) può congelarsi sul
testnet: il 13-06 `ETH-PERPETUAL` era fermo a **1661.95 per 12h+** (1 solo valore) mentre il
reale (`ETH_USDC-PERPETUAL`, lineare, dove si ESEGUE) si muoveva (gap 1.3%); BTC era vivo.
Effetto: tutte le decisioni ETH (SH01_ETH, 3 fade ETH 15m, gambe ETH dei pairs) girano su un
prezzo morto → **spiega lo 0-trade delle fade ETH 15m** (feed piatto = niente rottura banda).
È un GUASTO testnet, non di strategia: su **mainnet** l'arbitraggio tiene inverse ≈ lineare ≈
realtà. Fix testnet possibile (reroute decisione ETH a USDC lineare in `INSTRUMENT_MAP`, con
caveat bootstrap SH01) NON applicato — il fix vero è andare a mainnet. Nota: la dashboard mostra
entry/mark/PnL **REALI** (non il sim-feed dislocato); il sim resta solo nel modal diagnostico.
**Update 2026-06-14: ANCORA congelato a 1661.95 (36h+).** Confermato anche un vincolo strutturale
del feed: **Cerbero v2 (`get_historical_v2`) serve SOLO 5m/15m/1h** — 30m/10m danno HTTP 400 in
ogni formato (l'endpoint legacy `get_historical` è 404, rimosso). La voce `"30m"` in
`runner._SUBHOURLY` era **speculativa, mai testata live** → qualsiasi TF sub-orario oltre 5m/15m
va DERIVATO resamplando nel runner (come 4h/1d dal 1h), non fetchato diretto.
- **GATE FEED CONGELATO — sleeve ETH-leg sospesi (v1.1.32, 2026-06-15, scelta utente "congela
gli sleeve ETH-leg finché il feed non si sblocca").** Audit "stato ordini": il feed ETH congelato
stava generando **perdite REALI** (SH01_ETH 2.83 reali vs 0.09 sim su un close, poi riapertura
della stessa trappola; 4 pairs con gamba ETH entrati su z-score spuri 3/5/+5.6 = artefatto del
log-ratio con ETH pinnato). Fix: `runner._frozen_assets` + `_feed_gated_sids` — quando il feed di
decisione 1h di un asset è CONGELATO, gli sleeve CONCENTRATI (single/ml/pairs) che ne dipendono
**saltano il tick** (entry E exit) finché non si sblocca (come un outage; disaster-SL on-book =
protezione di coda). **Auto-guarente**: si rilascia alla prima barra COMPLETA non-flat (NON è
l'entry-guard post-flat bocciata: quella salta la candela-gap di ripresa, questo no). **Detector
guasto-vs-illiquido**: conta la run di close **INVARIATE** (prezzo mai cambiato), NON le barre flat
— sul feed reale ETH/BNB/DOGE hanno run 40-64 con 1-4 valori distinti/48h (MORTI) mentre
SOL/LTC/ADA sono flat ma VIVI (run ≤12, 5-31 valori/48h: si muovono ogni ~10 barre). Soglia
`feed_freeze_gate_bars=24` (1 giorno di prezzo immobile, configurabile, 0=off): gatea le **9 gambe
ETH esatte** (3 fade ETH 15m + SH_ETH + 5 pairs con gamba ETH) lasciando attivi BTC-only,
**PR_BTCLTC** (BTC vivo + LTC solo illiquido) e i multi-asset PAPER. Le posizioni ETH già aperte
NON vengono flattate (freeze=pausa: un close forzato passerebbe per lo stesso feed morto). Alert
Telegram `FEED_FROZEN_GATE` GATED/RIPRESO. Test `tests/portfolio/test_freeze_gate.py`, diario
`docs/diary/2026-06-15-frozen-feed-gate.md`. **Resta un cerotto testnet: il fix vero è mainnet.**
- **TIMING SWEEP pairs & honest (2026-06-14, NESSUN deploy).** Domanda utente: pairs/honest
beneficiano di TF più veloci come le fade (swap 1h→15m)? Esito: **no, niente deployato.** Vincolo
dati (alt 1h-only) → sweepabili solo **pairs ETH/BTC** e **DIP01 (BTC)**; TR01/ROT02 fuori scope
(multi-asset alt + lente). Il gate full+OOS migliorava PORT06 (+10m/30m/5m su tutte le metriche),
MA due muri: (a) **30m/10m feed-blocked** (v2 serve solo 5/15m/1h, vedi sopra); (b) **5m** è
nativo ma nel regime ATTUALE è il PEGGIORE (10.5% 30g, DD 19.5%/180g vs 3% del 1h, Sharpe sotto
15m/1h) + flat-share ETH 29% (slippage reale) → guadagno backtest modesto (FULL Sh +0.39) non vale
il rischio su soldi reali. **Lezione: il gate PORT06 full+OOS è necessario ma NON sufficiente**
incrociare con (1) la fattibilità del FEED live e (2) il regime RECENTE (un edge full-history può
essere un relitto di volatilità passata: "più veloce = più Sharpe storico" si rovescia in "più
veloce = più DD nel regime calmo attuale"). Artefatti: `scripts/analysis/timing_sweep_pairs_honest.py`,
diario `docs/diary/2026-06-14-timing-sweep-pairs-honest.md`. (Config live invariata: 19 sleeve.)
- **MICRO-TEST MAINNET = il gate per scalare il capitale (piano pronto, 2026-06-13).** Testnet
valida solo la MECCANICA (feed/fill farlocchi); l'edge sopravvive ai fill veri? si risponde solo
con poco denaro reale su mainnet. **Switch già eseguibile via `.env`**: `CerberoClient` legge il
token da env (`CERBERO_TOKEN`, default testnet invariato; `is_mainnet()` helper) → puntare a
mainnet = solo `.env`, niente codice. Piano completo (fasi smoke→fade-only €1000 2-4 sett→verdetto
ledger-vs-backtest→espansione; sizing: fade €1000 = arrotondamento 2.6% BTC, pairs esclusi al 30%):
`docs/specs/mainnet-microtest-plan.md`. **Stato 2026-06-14:** token mainnet (`MAINNET_TOKEN` di
cerbero-mcp, che ha già le chiavi Deribit LIVE) **wired e VERIFICATO**`is_mainnet()=True`, auth
OK su `get_account_summary` read-only. Tenuto in **`.env.mainnet`** dedicato (chmod 600, gitignored),
FUORI dal `.env` condiviso → il runner testnet NON flippa a mainnet a un riavvio accidentale (il
micro-test girerà come **servizio separato** con dir dati pulita + portfolios fade-only, per non
mescolare i ledger). NB architettura token: il `MAINNET_TOKEN` è la chiave d'accesso *all'MCP* +
selettore d'ambiente (non una chiave Deribit) → il nostro client DEVE presentarlo per essere
instradato al live. **UNICO BLOCCO: conto Deribit mainnet VUOTO (€0 USDC/BTC/ETH) → serve deposito
~€1000 USDC** prima di Fase 0 smoke. Minimo assoluto €500 (rumore BTC ~5%), raccomandato €1000 (2.6%).
+4 -2
View File
@@ -8,9 +8,11 @@ COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev
COPY src/ src/
COPY scripts/strategies/ scripts/strategies/
COPY strategies.yml strategies.yml
COPY scripts/ scripts/
COPY strategies.yml portfolios.yml VERSION ./
VOLUME /app/data
# Default: paper trader multi-strategia. Il servizio "portfolio" in docker-compose
# sovrascrive il command per il runner a portafoglio (src.portfolio.runner).
CMD ["uv", "run", "python", "-m", "src.live.multi_runner"]
+301 -58
View File
@@ -4,41 +4,99 @@ Sistema di riconoscimento pattern frattali e predizione per il trading di cripto
## Obiettivo
Partendo da un capitale iniziale di €1.000, raggiungere un profitto medio di €50 al giorno entro 68 mesi, tramite strategie algoritmiche che combinano analisi frattale, squeeze di volatilità e machine learning.
Partendo da un capitale iniziale di €1.000, raggiungere un profitto medio di €50 al giorno entro 68 mesi, tramite un portafoglio di strategie algoritmiche poco correlate fra loro — mean-reversion, trend/rotazione e spread market-neutral — validate out-of-sample e fee-aware.
## Risultati
Oltre 30 strategie testate su dati storici 20182026 (BTC e ETH, timeframe 15m / 1h). Le migliori:
> ⚠️ **Revisione 2026-05-28.** La famiglia squeeze-breakout (SQ/MT/ML/AD/CM/PD, con
> accuracy storiche dichiarate 76-82%) è stata **scartata**: quei numeri erano un
> **artefatto di look-ahead**. I backtest decidevano la direzione dalla candela di
> breakout `close[i]` ma entravano a `close[i-1]` — impossibile dal vivo. Sotto
> ingresso onesto (`close[i]`) e fee reali, l'edge sparisce e tutte perdono, anche
> a fee zero. Dettagli e prove: `scripts/analysis/oos_validation.py`.
| Codice | Strategia | Accuracy | Trades | Max DD | €/giorno | Robustezza |
|--------|-----------|----------|--------|--------|----------|------------|
| SQ02 | Antifake+Vol BTC 15m | **79.7%** | 1250 | 6.5% | €5.23 | ✅ 9/9 anni |
| ML01 | Squeeze+GBM BTC 15m | 79.1% | 1929 | 5.5% | €8.45 | ✅ 5/5 anni |
| SQ02 | Antifake+Vol ETH 15m | 78.6% | 942 | 3.4% | €4.33 | 8/9 anni |
| SQ02 | Antifake+Vol BTC 1h | 78.0% | 473 | 3.5% | €3.85 | ✅ 9/9 anni |
| SQ01 | Squeeze Base ETH 15m | 76.4% | 2948 | 6.2% | €10.31 | 9/9 anni |
| ML01 | Squeeze+GBM ETH 15m | 76.7% | 1210 | 4.2% | €11.12 | 5/5 anni |
Dopo una validazione **out-of-sample, fee-aware** di molte famiglie di strategie,
emergono cinque famiglie con edge netto reale, tutte radicate nella stessa lezione
(in cripto la **mean-reversion** funziona, la continuazione no) o nella diversificazione:
La strategia più robusta (SQ02 BTC 15m) mantiene accuracy ≥73% ogni anno dal 2018 con Sharpe 5.01.
| Famiglia | Meccanismo | Strategie | Profilo (netto OOS) |
|----------|-----------|-----------|---------------------|
| **FADE** | mean-reversion intraday 1h (long/short, BTC/ETH) | MR01 Bollinger, MR02 Donchian, MR07 Return-reversal | Acc 52-55%, DD 18-34% |
| **HONEST** | long-only multi-regime multi-crypto | DIP01 dip-buy, TR01 EMA-trend, ROT02 dual-momentum | CAGR 31-56%, DD 15-27% |
| **PAIRS** | spread reversion *market-neutral* (2 gambe) | PR01 ETH/BTC, LTC/ETH, ADA/ETH, BTC/LTC, ETH/SOL | Sharpe 2.0-4.4, corr col mercato ~0.05 |
| **TSMOM** | time-series momentum multi-orizzonte | TSM01 (3/6/12m + risk-off) | diversificatore, DD 15-22% |
| **SHAPE** | ML walk-forward su feature di *forma* del prezzo | SH01 (LogisticRegression, orizzonte 12 barre) | diversificatore, corr +0.08 col resto |
Tutti i numeri sono **netti** dopo fee realistiche (Deribit 0.10% RT single-leg, 0.20%
RT/coppia sui pairs), leva 3x, su finestra held-out. Le strategie sono robuste su griglia
parametri, sweep fee 0.00-0.20% RT e — per i pairs — validate con **walk-forward** e
config universale (niente cherry-picking).
### Portafoglio combinato (la vera leva anti-drawdown)
Le famiglie sono **quasi scorrelate fra loro** (~0.05). Combinandole in un unico
portafoglio equipesato il drawdown crolla sotto quello di ogni singola sleeve:
| Portafoglio | CAGR | Max DD | Sharpe |
|-------------|------|--------|--------|
| FADE (6 sleeve) | ~46% | 8% | 3.9 |
| HONEST (3 sleeve) | ~46% | 13% | 2.2 |
| **MASTER** (FADE + HONEST, 9) | ~47% | **5%** | 4.2 |
| **MASTER + PAIRS + TSM01** (15) | ~67% | ~5% | ~6 |
| **PORT06 live** (17 sleeve, cap pairs 33%, leva 2×, config EXIT-16) | ~79% | **2.6%** | 7.8 (FULL) / 10.1 (OOS) |
> 🔎 **Numeri sobri (anti-overfit).** L'OOS singolo cade nel regime favorevole 2024-25:
> i valori di Sharpe/DD sopra sono ottimistici di circa il 50%. Da pianificare per le
> decisioni: **Sharpe atteso ~5**, **worst-drawdown su 90 giorni ~6%**, profilo che regge
> a leva 2x con slippage raddoppiato. Configurazione raccomandata: equal-weight, leva 2x,
> con un cap sull'allocazione ai pairs (~30-35%, poiché concentrano ~57% del rischio).
> Tutto resta da confermare nel paper trading live.
## Come funziona
### Volatility Squeeze Breakout
### MR01 — Bollinger Fade (mean-reversion)
Il meccanismo centrale sfrutta i cicli naturali di compressione ed espansione della volatilità:
La strategia attiva sfrutta il fatto, emerso dai dati, che su BTC/ETH a 1h gli estremi
di prezzo **rientrano verso la media** più di quanto proseguano:
1. **Compressione** — le Bollinger Bands entrano dentro i Keltner Channel (il prezzo si muove sempre meno, accumulando "energia").
2. **Breakout** — le bande escono dal canale. Un impulso direzionale parte.
3. **Filtri** — anti-fakeout (scarta breakout con retrace >60%) + volume confirmation (breakout >1.3× media).
4. **ML opzionale** — un modello GradientBoosting (GBM), addestrato walk-forward su feature strutturali, conferma la direzione e filtra i segnali deboli.
1. **Bollinger Bands** (window `n`, `k` deviazioni standard) sul close.
2. **Entry** — quando il close esce *sotto* la banda inferiore → **long** (o *sopra* la superiore → **short**). Ingresso a `close[i]`, eseguibile dal vivo.
3. **Take-profit** alla media mobile (il rientro atteso).
4. **Stop-loss** a `sl_atr × ATR` oltre l'estremo; **time-limit** a `max_bars`.
### Feature ML (44 dimensioni)
Nessun look-ahead: direzione e livelli sono calcolati con dati fino a `close[i]`.
- Rapporti body/shadow normalizzati su finestre multiple (12, 24, 48 candele)
- Momentum, volatilità, skewness, kurtosis dei rendimenti logaritmici
- Autocorrelazione lag-1 e profilo volumetrico
- Durata della fase di squeeze e rapporto di espansione Keltner
- Posizione del prezzo rispetto al range recente e ATR normalizzato
### Le altre famiglie
- **FADE** (oltre MR01): MR02 fada la rottura del canale Donchian verso il centro;
MR07 fada il movimento di barra estremo misurato in deviazioni standard dei
rendimenti. Stessa logica di reversione, indicatori indipendenti.
- **HONEST** (long-only, multi-crypto): DIP01 compra i dip estremi e rivende al
recupero; TR01 segue il trend con incrocio di EMA su un paniere; ROT02 ruota ogni
giorno sui tre asset col momentum più forte, andando in cash quando BTC è sotto la
sua media (risk-off). Coprono i regimi di trend e rotazione, complementari alle fade.
- **PAIRS** (market-neutral): scommette sul rientro verso la media del log-ratio fra
due cripto (z-score). Long su una, short sull'altra: l'esposizione netta al mercato è
quasi nulla (correlazione ~0.02), il che la rende un diversificatore eccellente.
- **TSMOM**: tiene gli asset con momentum positivo persistente su più orizzonti
(3/6/12 mesi), con overlay risk-off. Rende meno ma è poco correlato, utile in ensemble.
### Perché lo squeeze breakout è stato abbandonato
L'ipotesi originale era opposta — *continuazione* dopo la compressione di volatilità
(Bollinger dentro Keltner → breakout direzionale). Su dati storici sembrava dare
76-82% di accuracy, ma era un **artefatto di look-ahead**: il backtest entrava a
`close[i-1]` con direzione decisa da `close[i]`. Replicando l'esecuzione reale
(ingresso a `close[i]`) l'edge collassa al ~47% (lancio di moneta) e i costi fanno
il resto. Il test sui breakout intra-barra a 5m conferma che il movimento *rientra*
subito (mean-reversion), giustificando MR01. Tutta la famiglia squeeze è in `scripts/waste/`.
### Lezione metodologica
Ogni nuova strategia deve passare: (1) **ingresso eseguibile** senza look-ahead,
(2) backtest **netto** dopo fee realistiche (0.10% RT Deribit), (3) validazione
**out-of-sample** + robustezza su griglia parametri + sweep fee. Strumenti in
`scripts/analysis/` (`strategy_research.py`, `oos_validation.py`, `intrabar_test.py`).
## Struttura progetto
@@ -51,20 +109,30 @@ PythagorasGoal/
│ ├── strategies/ # Classe base Strategy ABC + indicatori condivisi
│ │ ├── base.py # Strategy, Signal, BacktestResult, YearlyStats
│ │ └── indicators.py # keltner_ratio, detect_squeezes, ema, atr, rv, corr
── live/ # Paper trading live su Deribit testnet
├── multi_runner.py # Orchestratore multi-strategia
├── strategy_worker.py # Worker indipendente con stato persistente
├── strategy_loader.py # Import dinamico classi Strategy
├── cerbero_client.py # Client HTTP per Cerbero MCP
├── signal_engine.py # Squeeze + ML real-time (per ML01)
── telegram_notifier.py
── live/ # Paper trading live su Deribit testnet
├── multi_runner.py # Orchestratore multi-strategia (strategie + pairs)
├── strategy_worker.py # Worker single-leg con stato persistente
├── pairs_worker.py # Worker a 2 gambe per i pairs (market-neutral)
├── strategy_loader.py # Import dinamico classi Strategy
├── cerbero_client.py # Client HTTP per Cerbero MCP
── signal_engine.py # Squeeze + ML real-time (legacy) + validazione OOS
│ │ └── telegram_notifier.py
│ └── portfolio/ # Portafogli di prima classe (capitale condiviso, backtest + live)
│ ├── base.py # SleeveSpec, Portfolio (.backtest), load_active_portfolio
│ ├── weighting.py # Schemi di ponderazione: equal, cap, inverse_vol, cluster_rp, manual
│ ├── sleeves.py # Builder unificato equity-per-sleeve (fonte unica, parità report)
│ ├── ledger.py # PortfolioLedger: PnL/DD aggregati, persistenza e resume
│ └── runner.py # PortfolioRunner live (Cerbero v2, sizing, ribilancio giornaliero)
├── scripts/
│ ├── strategies/ # Strategie attive (SQ01-SQ04, ML01)
│ ├── waste/ # Strategie scartate (W01-W22)
── analysis/ # Script di confronto e report
│ ├── strategies/ # Strategie con edge validato OOS (FADE, HONEST, PAIRS, TSMOM + portafogli)
│ ├── portfolios/ # Definizioni PORT01-06 e report run() dei portafogli di prima classe
── waste/ # Strategie scartate (squeeze SQ/MT/ML/AD/CM/PD, MR03, ROT01, W01-W28)
│ └── analysis/ # Ricerca/validazione OOS fee-aware, gestione rischio, report
├── strategies.yml # Config multi-strategy paper trader
├── data/
── raw/ # Parquet OHLCV (gitignored, ~70 MB)
── raw/ # Parquet OHLCV (gitignored, ~70 MB)
│ └── regime/ # DVOL + funding (Deribit mainnet) + cache feature regime (gitignored)
├── VERSION # versione semver (cotta nell'immagine, mostrata nei msg Telegram)
├── docs/
│ ├── diary/ # Diario di ricerca giornaliero
│ └── specs/ # Specifiche di design
@@ -75,25 +143,73 @@ PythagorasGoal/
## Strategie attive
Tutte le strategie estendono `src.strategies.base.Strategy` con interfaccia comune: `generate_signals() → backtest() → report()`.
Le strategie single-asset estendono `src.strategies.base.Strategy`
(`generate_signals() → backtest()`); i pairs hanno un worker dedicato a 2 gambe.
| Codice | Script | Tipo | Descrizione |
|--------|--------|------|-------------|
| SQ01 | `SQ01_squeeze_base.py` | Regole | Squeeze breakout puro |
| SQ02 | `SQ02_squeeze_antifake_vol.py` | Regole | Squeeze + antifakeout + volume confirm |
| SQ03 | `SQ03_squeeze_all_filters.py` | Regole | Squeeze + filtri selezionabili (9 preset) |
| SQ04 | `SQ04_squeeze_ultimate.py` | Regole | Combo incrementali con correlazione/trend |
| ML01 | `ML01_squeeze_gbm.py` | ML | Squeeze + GBM walk-forward |
| Codice | Script | Famiglia | Descrizione |
|--------|--------|----------|-------------|
| **MR01** | `MR01_bollinger_fade.py` | FADE | Fada la banda di Bollinger, TP alla media, SL ad ATR |
| **MR02** | `MR02_donchian_fade.py` | FADE | Fada la rottura del canale Donchian, TP al centro |
| **MR07** | `MR07_return_reversal.py` | FADE | Fada il movimento di barra estremo (z dei rendimenti) |
| **DIP01** | `DIP01_dip_reversion.py` | HONEST | Dip-buy long-only su z-score estremo |
| **TR01** | `TR01_ema_trend.py` | HONEST | EMA 20/100 trend-following su paniere cripto (4h) |
| **ROT02** | `ROT02_dual_momentum.py` | HONEST | Rotazione cross-sectional top-3 + risk-off (1d) |
| **PR01** | `PR01_pairs_reversion.py` | PAIRS | Spread reversion market-neutral su 5 coppie |
| **TSM01** | `tsmom_research.py` | TSMOM | Time-series momentum multi-orizzonte + risk-off |
| **SH01** | `SH01_shape_ml.py` | SHAPE | LogisticRegression walk-forward su 17 feature di forma, orizzonte 12 barre (diversificatore) |
Per eseguire il backtest di una strategia:
Le fade applicano tre protezioni live: un **filtro trend** (`trend_max`/`ema_long`,
salta i segnali col prezzo troppo esteso rispetto alla EMA200), un **loss-guard Hurst**
(`hurst_max=0.55`, salta i segnali in regime persistente/trending dove si concentrano gli stop-loss
— dimezza il drawdown del portafoglio, calcolato dalle sole close) e l'**EXIT-16 close-confirm SL**
(`sl_confirm_atr=0.5`, 2026-06-04: lo stop scatta solo se la barra *chiude* oltre `sl ∓ 0.5·ATR14`
gli stop intrabar da wick erano falsi negativi, l'overshoot che buca lo stop è proprio il movimento
che la fade fada; a livello PORT06 porta l'OOS Sharpe da 8.82 a 10.06). Più un filtro `min_tp_frac`
che scarta i micro-scalp col take-profit entro il costo delle fee. Le tre protezioni sono
complementari: Hurst toglie il regime tossico, il trend-filter gli ingressi sovra-estesi, il
close-confirm i falsi stop. Portafogli pronti: `PORT01`
(honest), `PORT02` (fade), `PORT03` (master fade+honest), **`PORT06`** (master esteso, default live).
**Scartate** (in `scripts/waste/`): la famiglia squeeze (SQ01-04, ML01, MT01, PD01,
CM01, AD01 — artefatto di look-ahead), MR03 Keltner (debole/ridondante con MR01) e
ROT01 (dominata da ROT02).
### Comandi utili
```bash
uv run python scripts/strategies/SQ02_squeeze_antifake_vol.py
# Backtest di una strategia
uv run python scripts/strategies/MR01_bollinger_fade.py
uv run python scripts/strategies/PR01_pairs_reversion.py
# Ricerca e validazione fee-aware out-of-sample
uv run python scripts/analysis/strategy_research.py # screening famiglie + deep-dive fade
uv run python scripts/analysis/strategy_research_v2.py # MR02 / MR03 / MR07
uv run python scripts/analysis/oos_validation.py # perche' la famiglia squeeze e' scartata
uv run python scripts/analysis/pairs_research.py # ricerca + verifica no-look-ahead dei pairs
# Gestione rischio, combinazione, report
uv run python scripts/analysis/risk_management.py # filtro trend + portafoglio fade
uv run python scripts/analysis/combine_portfolio.py # combinare fade + honest
uv run python scripts/analysis/combine_v2.py # master esteso con pairs + TSM01
uv run python scripts/analysis/report_families.py # report per anno di tutte le famiglie
# Validazione dei worker live (replay == backtest)
uv run python scripts/analysis/validate_worker_mr01.py # worker single-leg su MR01
uv run python scripts/analysis/validate_worker_pairs.py # worker a 2 gambe sui pairs
uv run python scripts/analysis/live_smoke_pairs.py # smoke test feed live reale dei pairs
```
## Paper Trading Live
Il multi-strategy runner esegue N strategie in parallelo su dati live da Cerbero MCP, ognuna con €1000 USDC virtuali indipendenti.
Il multi-strategy runner esegue N strategie in parallelo su dati live da Cerbero MCP,
ognuna con €1000 USDC virtuali indipendenti. Gestisce due tipi di worker:
- **Single-leg** (`strategy_worker.py`): per le strategie direzionali. Se un `Signal`
porta `tp`/`sl`/`max_bars` in `metadata` (come le fade), chiude su take-profit /
stop-loss / time-limit; altrimenti usa il fallback `hold_bars`/stop -2%.
- **Due gambe** (`pairs_worker.py`): per i pairs market-neutral. Apre long su una gamba
e short sull'altra, esce sul rientro dello z-score o per time-limit, conta le fee su
entrambe le gambe. Validato: il replay storico coincide *esattamente* col backtest.
### Avvio
@@ -115,14 +231,24 @@ defaults:
position_size: 0.15
leverage: 3
strategies:
- name: SQ02_antifake_vol
strategies: # strategie single-leg
- name: MR01_bollinger_fade
asset: BTC
tf: 15m
tf: 1h
enabled: true
params: { bb_window: 50, k: 2.5, sl_atr: 2.0, max_bars: 24, trend_max: 3.0, ema_long: 200 }
pairs: # strategie a 2 gambe (market-neutral)
- name: PR01_pairs_reversion
a: ETH
b: BTC
tf: 1h
enabled: true
params: { n: 50, z_in: 2.0, z_exit: 0.75, max_bars: 72, jump_max: 0.08 }
```
Per aggiungere una strategia: nuova riga in `strategies.yml`, poi `docker compose restart`. Lo storico delle strategie esistenti rimane intatto.
Per aggiungere una strategia: nuova riga in `strategies.yml` (sezione `strategies` o
`pairs`), poi `docker compose restart`. Lo storico delle strategie esistenti rimane intatto.
### Persistenza
@@ -130,13 +256,101 @@ Ogni strategia ha la sua directory in `data/paper_trades/`:
```
data/paper_trades/
SQ02_antifake_vol__BTC__15m/
MR01_bollinger_fade__BTC__1h/
trades.jsonl # Storico trade append-only
status.json # Stato corrente (resume al restart)
status.json # Stato corrente (resume al restart, include tp/sl/max_bars)
```
Notifiche Telegram per ogni trade (richiede `TELEGRAM_BOT_TOKEN` e `TELEGRAM_CHAT_ID` in `.env`).
## Paper Trading a Portafoglio
Accanto al multi-strategy runner originale — in cui ogni strategia gestisce autonomamente il proprio conto virtuale da €1.000 — il progetto dispone ora di un **paper trader a portafoglio** (`src/portfolio/`) che tratta l'insieme delle strategie come un unico organismo con un capitale condiviso.
### Come funziona
La definizione di un portafoglio (`SleeveSpec` + schema di peso) ha due facce sulla stessa sorgente dati:
- **Backtest** (`.backtest()`): ricostruisce le equity-curve di ogni sleeve tramite il builder unificato in `sleeves.py`, le pondera secondo lo schema scelto e calcola le metriche aggregate (CAGR, Sharpe, max DD). La parità con i report prodotti da `report_families.py` è garantita dalla fonte unica.
- **Live** (`PortfolioRunner`): ogni ora il runner scarica le candele aggiornate via Cerbero v2, calcola i pesi correnti, avvia i worker appropriati per ogni sleeve attiva e registra il PnL aggregato nel ledger (`data/portfolios/{code}/`). Il ledger persiste tra i riavvii.
### Schemi di ponderazione
Il modulo `weighting.py` mette a disposizione cinque schemi: `equal` (default), `cap` (tetto per famiglia — p.es. `pairs: 0.33` per limitare la concentrazione), `inverse_vol` (pesi inversamente proporzionali alla volatilità storica), `cluster_rp` (equal tra cluster naturali poi inverse-vol all'interno del cluster) e `manual` (pesi liberi). Lo schema si specifica in `portfolios.yml` insieme al codice portafoglio e alla leva.
### Portafoglio di default: PORT06
La configurazione raccomandata è **PORT06** (`scripts/portfolios/PORT06_master_shape.py`): portafoglio master esteso che include tutte e sei le famiglie (FADE, HONEST, PAIRS, TSMOM, SHAPE), con schema `cap` che limita i pairs al 33% del capitale per moderare la loro concentrazione di rischio. Backtest canonico (dati al 2026-05-28): Sharpe 6.47 (FULL) / 8.82 (OOS), drawdown massimo 4.10% (FULL) / 1.30% (OOS), leva 2×; **con la config live attuale (EXIT-16 close-confirm): Sharpe 7.84 / 10.06, DD 2.60% / 1.15%**.
### Scope live
Il runner esegue **tutti e 17 gli sleeve** di PORT06: **fade** (MR01, MR02, MR07 × BTC/ETH),
**honest** (DIP01, TR01-basket 4h, ROT02-rotation 1d), **pairs** (PR01, cinque coppie),
**TSMOM** (TSM01 1d) e **shape** (SH01 × BTC/ETH). Worker dedicati: `StrategyWorker` (single-leg, fade/
dip/**shape**), `PairsWorker` (2 gambe), `BasketTrendWorker`, `RotationWorker`, `TsmomWorker`. Il runner
fetcha 1h da Cerbero v2 e resampla a 4h/1d; il pool di capitale, il ribilancio giornaliero e il ledger
sono validati == backtest.
> **SH01 (2026-06-01):** gira come `StrategyWorker` normale (il walk-forward è interno a
> `generate_signals`). Il vecchio `MLWorkerWrapper` usava il `SignalEngine` **squeeze scartato** —
> rimosso. **Loss-guard Hurst (2026-06-02):** le fade saltano i segnali in regime persistente
> (rolling-Hurst ≥ 0.55), dove si concentrano gli stop-loss — dimezza il drawdown del portafoglio
> (FULL 4.1%→2.4%; stop-loss fade 67% in numero, perdite totali 68%). Calcolato dalle sole close,
> attivo live (`hurst_max` nei params). Il report orario su Telegram **monitora lo stop-rate fade
> prima/dopo l'attivazione** e dà il verdetto automatico quando il campione è sufficiente.
### Esecuzione reale (shadow, Deribit testnet)
Sette sleeve single-leg — le **6 fade** (MR01/MR02/MR07 × BTC/ETH) e **DIP01** (dal 2026-06-04) —
eseguono ordini **reali su Deribit testnet** accanto al fill simulato (*shadow*: il sim resta la
verità che guida le decisioni; il reale misura la fattibilità). Punti chiave:
- **Strumenti lineari USDC** (`BTC_USDC`/`ETH_USDC-PERPETUAL`): payoff lineare = matematica del
backtest; fee e PnL in USDC. Quantizzazione `Decimal` di amount (step) e prezzi (tick).
- **Take-profit reale = limit reduce-only AL livello** (v1.0.7): piazzato all'apertura, copre la
sola quota del worker (gli strumenti sono condivisi fra worker e nettati per conto); alla
chiusura il worker cancella il resting, riconcilia i fill dal trade history per `order_id` e
chiude a market solo il residuo. Fix della divergenza misurata: il market-on-poll usciva
+235 bps oltre il livello TP. Fill da resting = fee maker (~0%).
- **Stop-loss close-confirm** (v1.1.0): uscita al close che sfonda il livello → market
reduce-only al poll (nessun ordine stop sul book, per scelta: i trigger Deribit generano un
nuovo order_id allo scatto, non verificabile, e i wick non devono stoppare).
- **Verifica sul trade** (order_id in `get_trade_history`), fee reali dai `trades[]`, ledger
reale parallelo persistito (`real_capital`), eventi `REAL_OPEN`/`REAL_TP_RESTING`/`REAL_CLOSE`
nel log + alert Telegram (`REAL_EXEC_LIVE`, `REAL_OPEN_FAIL`).
- Config in `portfolios.yml``overrides.execution {enabled, sleeves, instruments}`.
**Pairs/rotation/TSMOM/shape restano simulati**: i pairs richiedono un executor a 2 gambe
(leg-risk), i multi-asset un rebalance-to-target; roadmap nel diario.
### Versione & deploy
Ogni deploy ha una **versione** (file `VERSION`, semver) che compare nei messaggi Telegram (notifiche
trade + report orario), così correli ogni messaggio al codice che l'ha generato. Il sorgente è **cotto
nell'immagine** → per aggiornare il live serve un **rebuild**, non un semplice restart:
```bash
./scripts/deploy.sh # bump patch (1.0.0 → 1.0.1) + commit + rebuild + ricrea container
./scripts/deploy.sh minor # 1.0.x → 1.1.0
```
Il volume `data/` persiste tra i deploy → i worker fanno RESUME dello stato (capitale, posizioni aperte).
### Avvio del paper trader a portafoglio
```bash
# Backtest del portafoglio di default (PORT06)
uv run python scripts/portfolios/PORT06_master_shape.py
# Paper trading live a portafoglio
uv run python -m src.portfolio.runner
# Report orario su Telegram (stato + stop-rate fade prima/dopo loss-guard) — via cron
uv run python scripts/portfolios/hourly_report.py
# Smoke test del data layer Cerbero v2
uv run python scripts/analysis/smoke_portfolio.py
```
## Setup
```bash
@@ -147,8 +361,8 @@ uv sync
# Scarica dati storici (~70 MB)
uv run python -m src.data.downloader
# Backtest strategia migliore
uv run python scripts/strategies/SQ02_squeeze_antifake_vol.py
# Backtest strategia attiva
uv run python scripts/strategies/MR01_bollinger_fade.py
# Paper trading live
uv run python -m src.live.multi_runner
@@ -163,12 +377,41 @@ uv run python -m src.live.multi_runner
## Dati
| Asset | Timeframe | Candele | Copertura |
|-------|-----------|---------|-----------|
| BTC | 5m / 15m / 1h | 883K / 294K / 74K | 2018-01 → oggi |
| ETH | 5m / 15m / 1h | 882K / 294K / 74K | 2018-01 → oggi |
| Asset | Timeframe | Copertura |
|-------|-----------|-----------|
| BTC, ETH | 5m / 15m / 1h | 2018-01 → oggi |
| SOL, LTC, ADA, XRP, BNB, DOGE | 15m / 1h | 2019-2022 → oggi (variabile per asset) |
Fonte primaria: Deribit perpetual via Cerbero MCP. Fallback: Binance spot via ccxt. Formato: Apache Parquet.
Fonte primaria: perpetual Deribit via Cerbero MCP. Fallback: Binance spot via ccxt.
Formato: Apache Parquet (in `data/raw/`, gitignored).
> **Nota sul naming Deribit (per il feed live).** I major sono perpetui *inverse*
> (`BTC-PERPETUAL`, `ETH-PERPETUAL`); gli altcoin sono perpetui *lineari USDC*
> (`SOL_USDC-PERPETUAL`, `LTC_USDC-PERPETUAL`, …) con storia dal 2022. Attenzione:
> `LTC-PERPETUAL`/`ADA-PERPETUAL` non esistono e `SOL-PERPETUAL` restituisce dati
> errati — per gli altcoin usare sempre la forma `_USDC-PERPETUAL`.
### Discovery & validazione strumenti
`src/data/instruments.py` scopre e **valida** gli strumenti disponibili sugli
exchange implementati — **Deribit** e **Hyperliquid** (esclusi Alpaca/stocks e
**Bybit**, feed testnet inaffidabile). Ogni perpetuo viene testato sui dati
storici realmente raccoglibili: esistenza, congruenza OHLC, contratto non-morto,
liquidità e **congruenza prezzo cross-exchange** (mediana per base-coin, tolleranza
5%) — così feed farlocchi e contratti sbagliati (es. `SOL-PERPETUAL`=9.6) vengono
scartati. Il risultato è `data/instruments_registry.json` (strumenti validi +
timeframe + data d'inizio).
**Solo gli strumenti validati possono essere scaricati**: il downloader ha un gate
(`_download_cerbero_range`) che rifiuta quelli non nel registry. Rigenera con:
```bash
uv run python -m src.data.instruments
```
Simboli Deribit: BTC/ETH = `<COIN>-PERPETUAL` (inverse); altcoin =
`<COIN>_USDC-PERPETUAL` (lineari USDC). Registry attuale (testnet): Deribit 18/106
validi (major liquidi, BTC dal 2018), Hyperliquid 66/74.
## Riferimenti
+212
View File
@@ -0,0 +1,212 @@
# Strategia di Grid Trading — Versione Corretta
> Documento di specifica della strategia. Descrive *cosa* deve fare il bot e *perché*,
> non l'implementazione. È il riferimento da cui partire per riscrivere il codice in
> modo sicuro e testabile.
---
## 1. Obiettivo
Estrarre profitto dalla **volatilità di un asset all'interno di un intervallo di prezzo
(range)**, comprando automaticamente quando il prezzo scende e vendendo quando risale,
secondo livelli predefiniti (la "griglia").
La griglia **non prevede** la direzione del mercato: monetizza le oscillazioni. Funziona
quando il prezzo oscilla; perde quando il prezzo prende un trend deciso. Tutta la
progettazione che segue serve a massimizzare il primo caso e a limitare i danni nel
secondo.
---
## 2. Principi corretti (cosa cambia rispetto al bot originale)
| Aspetto | Bot originale (sbagliato) | Versione corretta |
|---|---|---|
| Asset | Shitcoin illiquida (LAND) | Coppia liquida (es. ETH/USDT, BNB/USDT) |
| Passo griglia | Assoluto in USDT (`GRID_STEP=3`) | **Percentuale** sul prezzo |
| Livelli | Mobili, inseguono il prezzo | **Fissi** dentro un range definito |
| Protezione perdite | Nessuna | **Stop-loss** sotto il range + **take-profit** sopra |
| Slippage | `amountOutMin = 0` (nessuna) | Calcolato da `getAmountsOut` tolleranza |
| Break-even fee | Ignorato | Passo griglia **> costo round-trip** |
| Capitale | Tutto, senza limiti | Allocazione fissa, suddivisa per livello |
| Chiave privata | In chiaro nel `.env` | Keystore cifrato o input a runtime |
| Validazione | Nessuna | **Backtest** + **testnet** prima del capitale reale |
---
## 3. Definizione della griglia
### 3.1 Parametri di ingresso
| Parametro | Significato | Esempio |
|---|---|---|
| `PAIR` | Coppia da tradare (base/quote) | `ETH/USDT` |
| `RANGE_LOW` | Estremo inferiore del range | `2800` |
| `RANGE_HIGH` | Estremo superiore del range | `3400` |
| `GRID_LEVELS` | Numero di livelli nella griglia | `12` |
| `CAPITAL_QUOTE` | Capitale totale in quote (USDT) | `1200` |
| `STOP_LOSS` | Prezzo sotto cui il bot chiude tutto e si ferma | `2650` |
| `TAKE_PROFIT` | Prezzo sopra cui il bot chiude tutto e si ferma | `3550` |
| `SLIPPAGE_BPS` | Tolleranza slippage in basis point (50 = 0,5%) | `50` |
| `FEE_BPS` | Fee del DEX in basis point (PancakeSwap = 25) | `25` |
### 3.2 Costruzione dei livelli (griglia geometrica)
I livelli vanno spaziati in **percentuale**, non in valore assoluto. Una griglia
geometrica mantiene lo stesso rendimento percentuale per ogni gradino, indipendentemente
dal prezzo.
```
ratio = (RANGE_HIGH / RANGE_LOW) ^ (1 / GRID_LEVELS)
livello[i] = RANGE_LOW * ratio ^ i per i = 0 .. GRID_LEVELS
```
Esempio (`RANGE_LOW=2800`, `RANGE_HIGH=3400`, `GRID_LEVELS=12`):
`ratio ≈ 1,0163` → passo di circa **1,63% per gradino**.
### 3.3 Capitale per livello
```
quote_per_livello = CAPITAL_QUOTE / GRID_LEVELS
```
Ogni livello di acquisto impegna `quote_per_livello`. Il capitale è suddiviso in anticipo:
il bot **non** può comprare più di quanto allocato, e non scende mai sotto zero.
---
## 4. Vincolo di break-even (regola anti-fee)
Una griglia con passi troppo fitti perde: le fee di ogni round-trip (compra + vendi) si
mangiano il profitto. **Il passo percentuale della griglia deve superare il costo totale
di un round-trip.**
```
costo_round_trip ≈ 2 * (FEE_BPS + SLIPPAGE_BPS) / 10000 (in frazione)
passo_griglia = ratio - 1
VINCOLO: passo_griglia > costo_round_trip * margine_sicurezza (margine ≥ 1,5)
```
Esempio con `FEE_BPS=25`, `SLIPPAGE_BPS=50`:
`costo_round_trip ≈ 2 * (25+50)/10000 = 1,5%`. Con margine 1,5 → il passo deve essere
**≥ 2,25%**. Se la griglia geometrica dà 1,63%, **è troppo fitta**: vanno ridotti i
`GRID_LEVELS` o allargato il range finché il vincolo è rispettato. Il bot deve rifiutarsi
di partire se il vincolo non è soddisfatto.
---
## 5. Logica operativa
### 5.1 Inizializzazione
1. Validare i parametri: `RANGE_LOW < prezzo_attuale < RANGE_HIGH`, vincolo di break-even
rispettato, capitale disponibile sul wallet.
2. Verificare la coppia: liquidità sufficiente, contratto non honeypot (controllo
obbligatorio, non opzionale), token con decimali noti.
3. Costruire i livelli (§3.2) e marcare ognuno come `attivo`/`riempito`.
4. Allocare il capitale (§3.3).
### 5.2 Ciclo principale
A ogni tick (es. ogni nuovo blocco, o ogni N secondi):
```
prezzo = prezzo_corrente(PAIR)
# --- guardie di uscita: hanno priorità su tutto ---
SE prezzo <= STOP_LOSS:
vendi_tutta_la_posizione() # con slippage protetto
ferma il bot, log "STOP-LOSS"
SE prezzo >= TAKE_PROFIT:
vendi_tutta_la_posizione()
ferma il bot, log "TAKE-PROFIT"
# --- logica griglia ---
PER ogni livello L:
SE prezzo attraversa L verso il basso E L non è ancora riempito:
compra quote_per_livello # con amountOutMin protetto
marca L come riempito
SE prezzo attraversa L verso l'alto E il livello sotto è riempito:
vendi la quantità di quel livello # con amountOutMin protetto
marca quel livello come libero
```
I livelli sono **fissi** (calcolati una volta), non inseguono il prezzo. Questo rende il
comportamento prevedibile e backtestabile.
### 5.3 Calcolo `amountOutMin` (protezione slippage — obbligatoria)
Mai passare `0`. Prima di ogni swap:
```
atteso = router.getAmountsOut(amountIn, path)[ultimo]
amountOutMin = atteso * (10000 - SLIPPAGE_BPS) / 10000
```
Se la transazione non rientra nella tolleranza, deve **fallire** (revert), non eseguire a
qualsiasi prezzo.
---
## 6. Gestione del rischio
1. **Stop-loss obbligatorio** sotto `RANGE_LOW`. È la differenza tra "strategia" e
"gambling": senza stop-loss un trend ribassista svuota il wallet.
2. **Take-profit** sopra `RANGE_HIGH` per chiudere quando il prezzo esce dal range al
rialzo (la griglia avrebbe già venduto tutto; il take-profit evita di restare esposti).
3. **Capitale segregato**: usare un wallet dedicato, con solo il capitale destinato alla
strategia. Mai il wallet principale.
4. **Limite di gas e prezzo gas** ragionevoli, ricalcolati dinamicamente (no valori fissi
obsoleti).
5. **Kill-switch manuale**: comando per fermare il bot e liquidare in qualsiasi momento.
6. **Idempotenza/recupero**: se il bot si riavvia, deve ricostruire lo stato dei livelli
(riempiti/liberi) dal saldo on-chain, non ripartire da zero.
---
## 7. Validazione prima del capitale reale
Nessun fondo reale prima di aver superato, in ordine:
1. **Backtest** su dati storici della coppia (almeno alcuni mesi, includendo sia fasi
laterali sia un trend marcato), misurando: PnL netto **dopo** fee e slippage,
max drawdown, numero di trade, comportamento allo stop-loss.
2. **Paper trading / simulazione** in tempo reale, senza eseguire ordini veri.
3. **Testnet** (BSC testnet) con la stessa logica e router di test, per verificare
l'esecuzione on-chain end-to-end.
4. **Mainnet con capitale minimo** (es. l'equivalente di pochi euro) per la prima
settimana, poi scalare solo se i risultati combaciano col backtest.
---
## 8. Quando NON usare questa strategia
- Asset illiquido o a rischio rug-pull/honeypot.
- Mercato in trend forte e prolungato (la griglia perde: lo stop-loss limita il danno ma
non genera profitto).
- Passo griglia che non rispetta il vincolo di break-even (§4).
- Capitale che non puoi permetterti di perdere.
---
## 9. Parametri di esempio (configurazione di partenza prudente)
```
PAIR = BNB/USDT # coppia liquida su PancakeSwap
RANGE_LOW = 580
RANGE_HIGH = 720
GRID_LEVELS = 8 # passo ≈ 2,7% > break-even
CAPITAL_QUOTE = 400 # USDT, su wallet dedicato
STOP_LOSS = 545 # ~6% sotto RANGE_LOW
TAKE_PROFIT = 760 # ~5,5% sopra RANGE_HIGH
SLIPPAGE_BPS = 50 # 0,5%
FEE_BPS = 25 # PancakeSwap v2
```
Verifica break-even: passo ≈ 2,7% > `1,5 × (2×0,75%) = 2,25%`
---
*Questo documento descrive la strategia. L'implementazione (ethers v6, gestione sicura
della chiave, calcolo slippage, stato persistente, backtester) va sviluppata a parte,
con test, e validata secondo §7 prima di qualsiasi capitale reale.*
+1
View File
@@ -0,0 +1 @@
1.1.34
File diff suppressed because it is too large Load Diff
@@ -1,13 +0,0 @@
{
"capital": 1000,
"in_position": false,
"direction": 0,
"entry_price": 0,
"entry_time": "",
"bars_held": 0,
"total_trades": 0,
"total_wins": 0,
"started_at": "2026-05-27T21:16:02.087963+00:00",
"last_bar_ts": 0,
"last_update": "2026-05-27T21:16:04.705726+00:00"
}
@@ -1 +0,0 @@
{"ts": "2026-05-27T21:16:02.087975+00:00", "worker": "ML01_squeeze_gbm__ETH__15m", "event": "INIT", "capital": 1000, "strategy": "ML01_squeeze_gbm", "asset": "ETH", "tf": "15m"}
@@ -1,13 +0,0 @@
{
"capital": 1000,
"in_position": false,
"direction": 0,
"entry_price": 0,
"entry_time": "",
"bars_held": 0,
"total_trades": 0,
"total_wins": 0,
"started_at": "2026-05-27T21:16:02.087646+00:00",
"last_bar_ts": 0,
"last_update": "2026-05-27T21:16:04.584685+00:00"
}
@@ -1 +0,0 @@
{"ts": "2026-05-27T21:16:02.087660+00:00", "worker": "SQ01_squeeze_base__BTC__15m", "event": "INIT", "capital": 1000, "strategy": "SQ01_squeeze_base", "asset": "BTC", "tf": "15m"}
@@ -1,13 +0,0 @@
{
"capital": 1000,
"in_position": false,
"direction": 0,
"entry_price": 0,
"entry_time": "",
"bars_held": 0,
"total_trades": 0,
"total_wins": 0,
"started_at": "2026-05-27T21:16:02.087214+00:00",
"last_bar_ts": 0,
"last_update": "2026-05-27T21:16:04.339917+00:00"
}
@@ -1 +0,0 @@
{"ts": "2026-05-27T21:16:02.087241+00:00", "worker": "SQ02_antifake_vol__BTC__15m", "event": "INIT", "capital": 1000, "strategy": "SQ02_antifake_vol", "asset": "BTC", "tf": "15m"}
@@ -1,13 +0,0 @@
{
"capital": 1000,
"in_position": false,
"direction": 0,
"entry_price": 0,
"entry_time": "",
"bars_held": 0,
"total_trades": 0,
"total_wins": 0,
"started_at": "2026-05-27T21:16:02.087438+00:00",
"last_bar_ts": 0,
"last_update": "2026-05-27T21:16:04.463602+00:00"
}
@@ -1 +0,0 @@
{"ts": "2026-05-27T21:16:02.087448+00:00", "worker": "SQ02_antifake_vol__ETH__15m", "event": "INIT", "capital": 1000, "strategy": "SQ02_antifake_vol", "asset": "ETH", "tf": "15m"}
-8
View File
@@ -1,8 +0,0 @@
{
"in_position": false,
"direction": null,
"entry_price": 0,
"entry_time": null,
"bars_held": 0,
"last_update": "2026-05-27T07:40:09.196718+00:00"
}
@@ -1,2 +0,0 @@
{"timestamp": "2026-05-27T07:35:10.715321+00:00", "event": "TRAINING", "lookback_days": 365}
{"timestamp": "2026-05-27T07:35:11.967644+00:00", "event": "TRAINING_DONE", "samples": 90, "up_ratio": 48.888888888888886, "train_accuracy": 100.0}
@@ -1,3 +0,0 @@
{"timestamp": "2026-05-27T07:36:03.120802+00:00", "event": "STARTUP", "equity": 101459.276155, "testnet": true}
{"timestamp": "2026-05-27T07:36:03.121518+00:00", "event": "TRAINING", "lookback_days": 365}
{"timestamp": "2026-05-27T07:36:04.249123+00:00", "event": "TRAINING_DONE", "samples": 90, "up_ratio": 48.888888888888886, "train_accuracy": 100.0}
@@ -1,6 +0,0 @@
{"timestamp": "2026-05-27T08:04:41.544464+00:00", "event": "TRAINING", "lookback_days": 365, "instrument": "ETH-PERPETUAL"}
{"timestamp": "2026-05-27T08:04:42.704464+00:00", "event": "TRAINING_DONE", "samples": 90, "up_ratio": 48.888888888888886, "train_accuracy": 100.0}
{"timestamp": "2026-05-27T08:04:42.918237+00:00", "event": "OPENING", "side": "buy", "amount": 0.216, "price": 2083.75, "virtual_capital": 1000.0, "notional": 450.0, "signal": {"direction": "buy", "probability": 0.75, "squeeze_duration": 10}}
{"timestamp": "2026-05-27T08:04:43.143718+00:00", "event": "OPENED", "order_result": {"order": {"label": "pythagoras-squeeze", "price": 2292.25, "order_id": "USDC-209283595178", "user_id": 81070, "amount": 0.216, "instrument_name": "ETH_USDC-PERPETUAL", "direction": "buy", "time_in_force": "good_til_cancelled", "web": false, "api": true, "creation_timestamp": 1779869083116, "mmp": false, "replaced": false, "post_only": false, "reduce_only": false, "filled_amount": 0.216, "last_update_timestamp": 1779869083116, "average_price": 2083.9, "contracts": 216.0, "order_state": "filled", "order_type": "market", "is_liquidation": false, "risk_reducing": false}, "trades": [{"label": "pythagoras-squeeze", "timestamp": 1779869083116, "state": "filled", "price": 2083.9, "order_id": "USDC-209283595178", "user_id": 81070, "amount": 0.216, "instrument_name": "ETH_USDC-PERPETUAL", "direction": "buy", "index_price": 2083.37, "trade_seq": 6674514, "api": true, "mark_price": 2083.86, "matching_id": null, "tick_direction": 0, "profit_loss": 0.0, "mmp": false, "post_only": false, "reduce_only": false, "self_trade": false, "contracts": 216.0, "trade_id": "USDC-32731729", "fee_currency": "USDC", "order_type": "market", "fee": 0.2250612, "liquidity": "T", "risk_reducing": false}], "data_timestamp": "2026-05-27T08:04:43.126155+00:00"}}
{"timestamp": "2026-05-27T08:04:46.361078+00:00", "event": "CLOSING", "reason": "test", "entry_price": 2083.75, "exit_price": 2083.95, "size": 0.216, "trade_pnl": 0.04, "fee": 0.9, "net_pnl": -0.86, "pnl_pct": -0.086, "bars_held": 0, "capital_before": 1000.0}
{"timestamp": "2026-05-27T08:04:46.574322+00:00", "event": "CLOSED", "result": {"order_id": "USDC-209283608601", "state": "filled", "data_timestamp": "2026-05-27T08:04:46.555823+00:00"}, "net_pnl": -0.86, "pnl_pct": -0.086, "virtual_capital": 999.14}
+36 -4
View File
@@ -1,17 +1,49 @@
services:
paper-trader:
portfolio:
build: .
container_name: pythagoras-multi
container_name: pythagoras-portfolio
restart: unless-stopped
command: ["uv", "run", "python", "-m", "src.portfolio.runner"]
volumes:
- ./data:/app/data
- ./strategies.yml:/app/strategies.yml:ro
- ./portfolios.yml:/app/portfolios.yml:ro
env_file:
- .env
- .env.mainnet # token MAINNET (soldi veri); prevale su .env (vedi micro-test plan)
environment:
- PYTHONUNBUFFERED=1
healthcheck:
test: ["CMD", "python", "-c", "import os; assert any(f.endswith('status.json') for r,d,fs in os.walk('/app/data/paper_trades') for f in fs)"]
test: ["CMD", "python", "-c", "import os; assert any(f.endswith('status.json') for r,d,fs in os.walk('/app/data/portfolios') for f in fs)"]
interval: 120s
timeout: 10s
retries: 3
labels:
- com.centurylinklabs.watchtower.enable=false
# Dashboard web read-only (stato live, PnL totale/per-strategia, grafico equity,
# trade attivi+chiusi). Stessa immagine del runner, monta gli stessi data/ in sola
# lettura logica (legge equity.jsonl + status/trades dei worker). Porta 8787.
# NB: nessuna auth -> non esporre su internet pubblico, solo rete interna/VPN.
dashboard:
build: .
container_name: pythagoras-dashboard
restart: unless-stopped
command: ["uv", "run", "python", "-m", "src.live.dashboard", "--port", "8787"]
ports:
- "8787:8787"
volumes:
- ./data:/app/data
- ./portfolios.yml:/app/portfolios.yml:ro
- ./docs/report:/app/docs/report:ro # scheda strategie_attive.html (modal "scheda dettagliata")
env_file:
- .env
- .env.mainnet # token MAINNET (soldi veri); prevale su .env (vedi micro-test plan)
environment:
- PYTHONUNBUFFERED=1
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8787/api/state', timeout=5)"]
interval: 120s
timeout: 10s
retries: 3
labels:
- com.centurylinklabs.watchtower.enable=false
+128
View File
@@ -0,0 +1,128 @@
# TODO — prossimi passi
> Raccolta dei follow-up aperti. Aggiornato 2026-06-11 (XS01 dispersion-gate live v1.1.20;
> real-truth ledger attivo; reset portafoglio 2026-06-10).
## Stato esecuzione per sleeve (REALE vs SIMULATO)
**REALE (shadow su Deribit testnet, REAL-TRUTH: il ledger segue i fill reali) — 15 sleeve:**
- 6 fade (MR01/MR02/MR07 × BTC/ETH) + DIP01_BTC → single-leg, v1.0.3+
- 6 pairs PR01 (ETH/BTC, LTC/ETH, ADA/ETH, BTC/LTC, ETH/SOL + ETH/BTC 15m) → 2 gambe,
v1.1.12 (2026-06-08); il 15m dal v1.1.16 (2026-06-09)
- SH01 × BTC/ETH → single-leg, exit a orizzonte (no TP/SL), v1.1.13 (2026-06-08)
**SIMULATO (paper, NON esegue reale) — 4 book multi-asset, fuori dal capitale-pool:**
- TR01 / ROT02 / TSM01 / XS01 → bloccati dal CAPITALE (rumore arrotondamento)
## Esecuzione reale — pezzi mancanti
- [x] ~~Executor a 2 gambe per i PAIRS~~ — FATTO + ATTIVO (v1.1.12, 2026-06-08).
- [x] ~~SH01 in reale~~ — FATTO (v1.1.13, 2026-06-08): l'infrastruttura no-TP esisteva già
(_place_real_tp no-op senza TP, _real_close chiude tutto market reduce-only a orizzonte).
Bastava accettare kind 'ml' in _exec_for + SH01 in execution.sleeves. Disaster-bracket
on-book = unica protezione di coda. Test SH01 open/close reale OK.
- [ ] **Multi-asset in reale** (SIMULATO: TR01/ROT02/TSM01/XS01) — **SBLOCCATO DA CAPITALE ≥ ~€20k**.
Strumenti USDC tutti esistenti (BNB/DOGE/XRP inclusi), ma a €2k il rumore di arrotondamento
per-asset è 20-30% (DOGE/XRP/SOL step grandi in $, esposizione frammentata su 5-8 asset) →
l'esecuzione reale misurerebbe il rumore dei lotti, non la strategia. Serve un
`BasketExecutionClient` (ribilancio delta multi-strumento, long-only, netting con fade+pairs
sui simboli condivisi). Beneficio limitato (~19% del portafoglio, diversificatori spesso
flat) → bassa priorità anche a capitale alto. Analisi: chat 2026-06-08.
## Capitale (decisione utente, non codice)
- [ ] **Valutare aumento capitale a €5.000-10.000** per togliere il rumore di arrotondamento.
A €2.000 il rumore BTC per trade è ~2.4% (morde); soglie: ~€4.800 (<1%), ~€9.500 (<0.5%,
"punto dolce"), ~€19.000 (trascurabile). Le commissioni invece sono % e neutre al capitale.
Col real-truth ledger il rumore di arrotondamento entra DIRETTAMENTE nell'equity → più
capitale = ledger più fedele alla strategia. Riguarda i 15 sleeve che eseguono reale.
## Follow-up dal code-review (bassa priorità, non urgenti)
- [x] ~~forming-bar su ROT02/TSM01~~ — GIÀ FIXATO (v1.1.10, 2026-06-08): `_panel` condiviso
scarta la barra in formazione via `last_bar_is_forming`. Item rimasto stantio nel TODO.
- [ ] **ancora bfill di `_daily_equity`** — QUANTIFICATO 2026-06-11 (`daily_equity_bfill_impact.py`):
**NON materiale, lasciare documentato**. PORT06 OOS invariato per costruzione (il bias vive
in testa alla serie, pre-SPLIT; ΔSharpe +0.001, ΔDD 0.000); FULL DD leggermente OTTIMISTICO
(3.46→3.67 col fix: il primo trade DIP01 2021, 3.75%, è nascosto dal bfill). Nessun verdetto
di gate a rischio (bias identico nei due bracci A-vs-B, si cancella). Unica eccezione OOS:
TSM01 (primo punto equity post-SPLIT, 0.014 Sh). Correggere SOLO in un eventuale refactor
del builder daily, OVUNQUE in un colpo (~12 file di gate replicano la convenzione) e
ri-baselinando i canonici nello stesso commit. CAVEAT per gate futuri: finestre IDX che
partono a metà storia amplificano il bug → usare lì la convenzione carry-forward corretta.
- [x] ~~convenzione TR01 worker vs reference~~ — ERA UN BUG, FIXATO (2026-06-11): `mean(rets)`
sui soli asset in posizione sovrappesava N/k a paniere parziale (con 1 long: 0.45 del
capitale invece di 0.09). Fix: `sum(rets)/len(universe)` (equal-weight 1/N canonico).
Replay post-fix: +32% vs reference +42% (residuo = convenzione capitale-unico vs
media-equity, dichiarata). Diario `2026-06-11-stability-sweep.md`.
- [x] ~~engine duplicato nei 3 gate~~ — FATTO (2026-06-11): fattorizzato in
`scripts/analysis/_port06_gate_common.py` (`build_trades_variant` versione trendmax =
superset con hurst_mask/trend_max parametrici, `equity_from_trades`, `port_metrics`,
`dd`); i 3 gate importano da lì. Nessuna drift di matematica trovata fra le copie
(solo firme/docstring). Output dei 3 gate verificato BYTE-IDENTICO prima/dopo.
`dip_trades` (DIP01) NON è una copia ma un sibling deliberato (long-only, orig_gap,
j clampato) → resta nel suo script, documentato nel modulo comune.
- [ ] **epoche hardcoded in `hourly_report.lossguard_section`** (LOSSGUARD_SINCE, TRENDSWAP_SINCE):
ogni nuova epoca-filtro richiede di editare la funzione. Derivarle da deploy history/config.
## Code-review 2026-06-11 sera (8a2b065..) — finding DEFERITI (i confermati critici sono fixati in v1.1.26)
- [ ] **RESTING reduce-only esposti al netting** (TP limit + disaster-SL): se un worker opposto
apre DOPO il piazzamento, il resting può fillare parziale o essere respinto — il fallback
netting copre solo i close sincroni. È il pezzo "position manager / sotto-conti" già noto;
lo scenario peggiore è il disaster-SL cappato proprio nel crash per cui esiste.
- [ ] **Lifecycle `orphan_legs`**: append-only — un orfano risolto a mano (o da reset_flatten)
resta nello status e MASCHERA per compensazione un drift vero futuro (falso negativo del
reconciler). Serve un comando di risoluzione (`--resolve-orphan`) e/o reset_flatten che
azzeri anche lo stato reale nei status.json. Idem: il PnL della chiusura manuale di un
orfano non viene mai bookato in real_capital (diagnostica shadow divergente).
- [ ] **TP_PHANTOM residuo**: `resting_fills` guarda solo le ultime 100 righe di trade-history
per strumento — su conto molto attivo un fill TP reale può scivolare fuori finestra →
falso phantom persistente (sim resta in posizione, reale flat). Mitigato dal quantize
conservativo (v1.1.26); fix vero = endpoint order-state in cerbero-mcp.
- [ ] **Validazione feed a monte** (altitudine): TP_PHANTOM copre solo i tocchi TP dei single-leg;
le ENTRY spike-driven, lo SL close-confirm su close spike e lo z dei pairs restano esposti
ai wick fantasma del feed testnet. Un validatore barre nel data layer coprirebbe tutti i
consumer con un meccanismo solo (su mainnet il fenomeno non esiste: priorità bassa finché
si resta su testnet, gli eventi TP_PHANTOM/NET_CLOSE ne misurano la frequenza).
- [ ] **Contratto dello schema status.json**: reconcile (`src/live/books.py`), hourly_report e i
worker condividono lo schema per convenzione implicita — books.py è ora la fonte unica per
i campi `real_*`, ma un helper `worker.real_book()` usato da _save e dai reader chiuderebbe
la classe di bug. Pulizia: `_tp_hit` helper per i 4 siti di tocco TP duplicati; port_metrics
ri-biforcato in xs01_tranche_gate/pairs30m_gate (importare da _port06_gate_common);
splits3/metriche duplicate nei games engine (estrarre in scripts/games/engine.py).
## Ricerca dispersion/correlation (2026-06-08, 165 agenti) — follow-up opzionale
- [x] ~~Gate PORT06 di `index_comp_disp` W=168~~ — FATTO (2026-06-08): PROMOSSO MARGINALE.
Decorrela bene (corr 0.06 col MASTER) ma OOS PIATTO (Sharpe 0.01). **Documentato e
rimandato** (non deployato): gate in `dispersion_edges/gate_index_comp_disp.py`,
riprendere solo se si costruisce una famiglia DISP più ampia. Diario aggiornato.
## Monitoraggio (osservare, non agire subito)
- [ ] **Churn da spike-print testnet su ETH (2026-06-11)**: il feed testnet stampa wick
anomali sulla barra 1h ETH che (a) generano segnali short MR01/MR07 (lo spike È lo
z-score estremo) e (b) "toccano" il TP intrabar della stessa barra → il sim booka
+4% fantasma a bars_held=0, il reale apre+chiude pagando solo fee/spread (~0.17€
a giro, 14 giri oggi ≈ 2.3€). Il real-truth ledger contabilizza GIUSTO (per questo
esiste) e il report orario ora conta i win dal flag reale. MITIGATO in v1.1.23:
gate `TP_PHANTOM` (il tocco TP deve essere confermato dal fill del resting sul book
reale, o dal prezzo oltre il livello) → niente più chiusure a mercato su wick
fantasma. Resta l'ENTRY spike-driven (il segnale stesso nasce dal wick): NON
filtrarlo nei segnali (= fit su artefatto testnet); osservare la frequenza dei log
TP_PHANTOM — se cresce, valutare un gate di QUALITÀ FEED nel data layer.
- [ ] **FADE in coda storica (2026-06-11)**: il rolling 120g equal-weight delle 6 fade è al
**2° percentile** della propria storia (1.0% vs p5 +0.4%); il PORT06 complessivo resta
in variazione normale (19-28° pct). NESSUN ritocco parametri (= fit sul regime corrente);
osservare se rientra. Follow-up tecnico: alert di drift per-FAMIGLIA nel `hourly_report`
(precomputare la distribuzione storica dei rolling-return di famiglia dal backtest
canonico → confrontare il rolling live; notifica sotto p5).
- [ ] **PnL live post-swap trend** (epoca TREND in `hourly_report`): verdetto stop-rate fade a n>=30.
- [ ] **SH01 post-bootstrap**: il trade-rate live deve scendere da ~25% a ~10% delle barre
(selettività della soglia ripristinata). Controllare nel report orario.
- [ ] **MR02_ETH**: ultima posizione aperta SENZA disaster-SL (aperta pre-v1.1.4). Si chiuderà
normalmente; le prossime aperture avranno il bracket.
- [ ] **parquet fresco per SH01 bootstrap**: oggi al 2026-05-28, il feed copre 365g → margine ~11
mesi. Rilanciare `download_all()` periodicamente (WARN automatico se il gap supera il lookback).
+136
View File
@@ -0,0 +1,136 @@
# 2026-05-28 — Ricerca onesta di nuove strategie (post-squeeze)
## Contesto e mandato
Dopo aver scoperto che l'intera famiglia squeeze-breakout era un artefatto di
look-ahead (accuratezze 76-82% svanite sotto ingresso eseguibile), il mandato è
stato: trovare in modo **onesto** almeno 3 strategie attendibili, testate su ~8
anni e su più criptovalute, con le fee incluse nella valutazione, partendo da
€1.000 con l'obiettivo (aspirazionale) di €50/giorno. Esplorare anche idee fuori
dal comune e l'uso combinato di più crypto e timeframe.
## Metodologia (engine onesto)
Tutto il lavoro usa un unico engine condiviso (`scripts/analysis/honest_lab.py`)
con questi vincoli anti-illusione:
1. **Ingresso eseguibile.** Ogni segnale alla barra `i` usa solo dati fino a
`close[i]` e l'ingresso avviene a `close[i]` (ciò che il worker live vede e
può eseguire). Disponibile anche l'ingresso più conservativo a `open[i+1]`.
2. **Uscita realistica.** Take-profit / stop-loss valutati intrabar su `high`/`low`,
in modo conservativo (SL prima del TP nello stesso bar), più time-limit.
Una posizione per volta (non-overlap), capitale composto.
3. **Fee di prim'ordine.** Tutto è NETTO dopo fee round-trip realistiche Deribit
(0.10% RT) moltiplicate per la leva (3x), con sweep fino a 0.20% RT.
4. **Validazione severa.** FULL + out-of-sample (ultimo 30%) + conteggio anni
positivi + sweep fee + griglia parametri + test su **8 crypto**
(BTC, ETH, SOL, BNB, XRP, LTC, DOGE, ADA, 2018→2026).
## Lezione madre
**Shortare le crypto perde OOS in modo sistematico in questo campione.** Sia la
mean-reversion sul lato short, sia il momentum short, crollano fuori campione: il
periodo 2018-2026 è net-bull e ogni rialzo "estremo" tende a continuare invece di
rientrare. Tutte le configurazioni che sopravvivono oneste sono **long-biased**.
È un fatto da dichiarare: parte della performance OOS è correlata al beta rialzista
delle crypto. Le strategie aggiungono *timing* sopra quel beta, non lo eliminano.
## Le 3 strategie selezionate (meccanismi distinti)
| Codice | Meccanismo | TF | Asset robusti | OOS netto (fee 0.10% RT) | DD | Anni+ |
|--------|-----------|----|---------------|--------------------------|----|-------|
| **DIP01** | Dip-buy z-score reversion (long-only) | 1h | BTC, ETH, SOL | BTC +59% · ETH +224% · SOL +13% | 23-55% | 6-7/9 |
| **TR01** | EMA 20/100 trend-following (long-only) | 4h | BNB, BTC, DOGE, SOL, XRP | BTC +27% · DOGE +53% · XRP +29% | 29-53% | 4-6/8 |
| **ROT01** | Rotazione cross-sectional momentum sul paniere | 1d | intero paniere (8) | **+44%** | 53% | 5/7 |
Dettagli e riproducibilità: `scripts/analysis/honest_final.py` (tabella di
validazione unica), `honest_rotation.py`, `honest_trend.py`, `honest_candidates.py`,
`honest_diag.py`/`honest_diag2.py` (diagnostica long/short e filtro trend).
### DIP01 — compra le capitolazioni
Long-only: entra quando lo z-score del prezzo rispetto alla media a 50 barre scende
sotto 2.5 (capitolazione), prende profitto al rientro verso la media, SL a 2.5·ATR.
È la versione robusta e onesta della famiglia mean-reversion: regge lo sweep fee
fino a 0.20% RT (BTC +45% OOS anche a 0.20%). Funziona sui major (BTC/ETH/SOL); sugli
alt molto parabolici (DOGE/BNB) un dip fisso continua a scendere e non ha edge.
### TR01 — cavalca i trend
Long-only: in posizione quando EMA(20) > EMA(100) sul 4h, altrimenti cash. Poche
operazioni (≈200 flip in 8 anni) ⇒ le fee non sono letali. È **complementare** a
DIP01: guadagna nei regimi di trend, dove la reversione soffre.
### ROT01 — la più affidabile e "fuori dal comune"
Una sola strategia che usa **tutto il paniere** in un unico book: ogni giorno ordina
le 8 crypto per momentum (rendimento a 60 giorni) e alloca a parti uguali alle 2
migliori con momentum positivo, il resto in cash. Cattura la *dispersione* tra
crypto (gli alt forti corrono molto più di BTC nei bull) senza shortare nulla.
È **param-insensitive** (tutte le combinazioni lookback/top-k sono positive OOS) e
regge le fee fino a 0.20% RT (+41% OOS). Risponde direttamente alla richiesta di
combinare più crypto e un timeframe diverso in un'unica strategia. Per-anno:
2020 +33% · 2021 +181% · 2022 29% (bear) · 2023 +43% · 2024 +59% · 2025 +6% · 2026 10% (YTD).
## Diversificazione
I tre meccanismi coprono regimi diversi e in larga misura anti-correlati:
reversione (DIP01), momentum di singolo asset (TR01), forza relativa cross-asset
(ROT01). Eseguirli insieme produce una curva di equity più liscia del singolo.
## Onestà sull'obiettivo €50/giorno
Va detto chiaramente: **€50/giorno su €1.000 in pochi mesi non è raggiungibile a
rischio sano.** Significa ~€18.250/anno, cioè ~1.825%/anno; gli edge onesti qui
trovati rendono il 30-60% OOS su orizzonti pluriennali. Le strade per avvicinare
quel numero sono: (a) far crescere il capitale per anni con interesse composto —
€50/giorno diventa plausibile solo quando il capitale è molto più grande; (b) alzare
la leva, che però aumenta proporzionalmente il drawdown (già 23-55%) ed espone a
rovina; (c) aggiungere capitale. Nessuna di queste è una scorciatoia. La proposta
onesta è un portafoglio delle 3 strategie a leva moderata, puntando alla
**sopravvivenza e alla crescita composta**, non al target giornaliero immediato.
## Miglioramenti (alzare Acc, ridurre DD, migliorare PnL)
Leve oneste e documentate, senza tuning sui singoli anni
(`scripts/analysis/honest_improve.py`, `honest_improve2.py`):
### ROT02 — dual-momentum overlay (migliora TUTTO)
Alla rotazione cross-sectional di ROT01 si aggiunge un overlay di *absolute
momentum*: cash quando BTC è sotto la sua media a 100 giorni (mercato risk-off).
Taglia i bear di sistema (gli unici anni rossi di ROT01).
| | FULL% | OOS% | DD% |
|---|---|---|---|
| ROT01 base | +679 | +44 | 53 |
| **ROT02 (SMA100)** | **+1095** | **+98** | **40** |
PnL su, DD giù: dominanza su tutte e tre le metriche. Param-insensitive (SMA100-150).
### DIP01 — market-gate (variante low-DD)
Comprare i dip solo quando BTC è risk-on alza l'**Acc** (ETH 52→57%, SOL 49→52%) e
**dimezza il DD** (ETH 53→23%, SOL 25→13%), al costo di parte della PnL (meno trade).
È de-risking, non un pasto gratis: utile per chi vuole una curva più liscia. Su BTC
il gate va evitato (i dip migliori di BTC arrivano proprio quando BTC è sotto la
propria SMA), quindi DIP01 base resta la versione di riferimento per BTC.
### PORT01 — portafoglio combinato (il vero motore di risk-reduction)
Equal-weight giornaliero ribilanciato delle 3 sleeve anti-correlate
(DIP01 BTC + TR01 basket + ROT02). La diversificazione porta il DD del portafoglio
**sotto** quello della sleeve meno rischiosa, mantenendo una CAGR alta.
| Sleeve | ret% | DD% | CAGR% |
|--------|------|-----|-------|
| DIP01 BTC | +322 | 15 | 31 |
| TR01 basket | +591 | 27 | 43 |
| ROT02 dual-mom | +771 | 40 | 49 |
| **PORTAFOGLIO** | **+642** | **12** | **45** |
Per-anno portafoglio: 2021 +203% · 2022 **1%** (bear neutralizzato, era 30% su ROT) ·
2023 +47% · 2024 +50% · 2025 +14% · 2026 2% (YTD). Nessun anno realmente negativo,
DD massimo 12%, CAGR 45%. È la configurazione di deployment raccomandata.
## Prossimi passi
- Integrare DIP01 nel worker (già compatibile: Signal con tp/sl/max_bars).
- Trailing-stop ad ATR per TR01 (per alzarne l'Acc e ridurne ulteriormente il DD).
- Estendere il worker per strategie position-based (TR01) e di portafoglio (ROT01).
- Backtest del portafoglio combinato con ribilanciamento del capitale.
- Walk-forward rolling (oltre al singolo split 70/30) per confermare la stabilità.
+193
View File
@@ -0,0 +1,193 @@
# 2026-05-28 — Giorno 3: Bug dati Cerbero, paper trader fermo, fix MT01 multi-timeframe
### 12:20 — Sintomo: paper trader live a zero trade
**Cosa:** check del container `pythagoras-multi` (multi-strategy paper trader, 6 strategie).
**Reale:** container healthy da ore, ma **0 trade** su tutte le strategie, tutte FLAT a €1000.
Primo falso indizio: `last_bar_ts: 0` in tutti gli `status.json`. Indagando il worker,
quel campo si aggiorna **solo a posizione aperta** (contatore `hold_bars`), non ad ogni
candela → non è la causa. Il loop era vivo (status.json riscritti ogni 60s).
**Lezione:** non fidarsi del nome di un campo; verificare nel codice quando viene scritto.
L'healthcheck del container controlla solo l'esistenza di `status.json`, non la freschezza
→ un loop bloccato risulterebbe comunque "healthy".
### 12:45 — Causa radice: bug lato Cerbero MCP `get_historical`
**Cosa:** probe dirette all'endpoint `/mcp-deribit/tools/get_historical`.
**Reale:** due bug lato server:
1. **`end_date` data-nuda tronca a mezzanotte:** `end=oggi` restituiva candele solo fino a
`oggi 00:00`. Il `df` live finiva sempre alla barra di mezzanotte e **non avanzava** durante
la giornata → nessun breakout fresco sull'ultima barra → nessun ingresso (condizione worker
`last_signal.idx >= last_idx - 1`).
2. **Cap a ~5000 righe** che ignora `start_date`: una richiesta di 365g a 15m restituiva ~52
giorni. Ecco perché ML01 si addestrava su soli 88 samples (overfit, train_acc 100%).
**Lezione:** lo zero-trade non era nelle strategie ma nel feed dati. Sempre validare la
freschezza/copertura dei dati prima di sospettare la logica.
### 13:30 — Fix lato Cerbero + verifica
**Cosa:** report passato al dev di `cerbero-mcp`; fix deployato (riavvio container) + doc
aggiornata in `cerbero-mcp/docs/API_REFERENCE.md`.
**Reale dopo deploy (verificato con probe):**
- `end=oggi` (data nuda) → ultima candela = ora corrente (age ~3 min). ✅
- 365g a 15m → **35.099 candele**, span 365.6g, nessun cap. ✅
- Supportati anche timestamp con orario (`...T14:00:00`, naive = UTC). ✅
Nostro client (`src/live/cerbero_client.py`) invariato: passa già `end=oggi`, ora corretto.
**Lezione:** "trust but verify" — la doc dichiarava i fix prima che fossero deployati; solo
la probe diretta ha confermato cosa era davvero attivo sul server.
### 14:00 — Problema residuo: MT01 usava un trend 1h STANTIO
**Cosa:** check di tutte le strategie sul percorso di codice reale con dati freschi.
**Reale:**
- Tutte le 6 strategie girano senza crash; SQ01/SQ02 generano molti segnali.
- **MT01 leggeva il trend 1h dal parquet statico** (`load_data(asset,"1h")`), non da Cerbero.
Il parquet finiva a mezzanotte → per ogni barra 15m di oggi `searchsorted` cadeva oltre la
fine e si agganciava sempre alla candela di mezzanotte (gap 14.8h). La conferma
multi-timeframe — il cuore di MT01 — era di fatto congelata e il gap cresce ogni giorno.
- In `data/raw/` mancavano del tutto i parquet **15m** (`btc_15m`, `eth_15m`) → backtest 15m rotti.
**Lezione:** una strategia live che dipende da un file statico ha un punto cieco temporale;
il dato live e quello di backtest devono provenire da fonti coerenti.
### 14:30 — Fix MT01: trend 1h live da Cerbero
**Cosa:** modifica al runner perché MT01 prenda l'1h live, non dal parquet.
- `MT01.generate_signals` accetta un `df_1h` opzionale (fallback al parquet se assente).
- `StrategyWorker.tick(df, df_1h=None)` lo inoltra ai signal.
- `multi_runner` fa fetch 1h live (resolution 60) per gli asset MT01 ad ogni poll (`htf_cache`).
**Reale (verificato a codice montato, pre-rebuild):** gap del trend 1h sull'ultima barra
**0.75h** (fresco) contro **14.8h** col parquet statico. Segnali invariati sullo storico.
**Lezione:** isolare la dipendenza dal file statico rende MT01 immune al drift tra un
`download_all()` e l'altro.
### 14:55 — Rigenerazione dati + rebuild
**Cosa:** `download_asset` per 15m+1h (saltati 1m/5m, lenti e inutilizzati), poi
`docker compose up -d --build` (il codice `src/` è baked nell'immagine).
**Reale:** parquet rigenerati con storia completa 2018→2026 e freschi (15m fino alle 14:45,
1h fino alle 14:00). Container ripartito: 6 strategie attive, ML01 riaddestrato su **534
samples** (anno pieno), MT01 senza errori, fetch 1h live OK.
### 15:00 — Regressione backtest sui dati rigenerati
**Cosa:** rilanciati i backtest per confermare che i numeri documentati si riproducano sui
dati ricreati da zero (BTC/ETH 15m, hold=3, fee 0.2% RT, leva 3x, pos 15%).
**Reale:** accuratezze e drawdown **identici**, solo +1/+3 trade dalle barre recenti in più.
| Strategia | Ottenuto | Documentato | Esito |
|---|---|---|---|
| SQ01 BTC 15m | 76.7% / DD 6.7% / 4063t | 76.7% / 6.7% / 4062 | ✓ |
| SQ01 ETH 15m | 76.4% / 6.2% / 2951t | 76.4% / 6.2% / 2948 | ✓ |
| SQ02 BTC 15m | 79.7% / 6.5% / 1251t | 79.7% / 6.5% / 1250 | ✓ |
| SQ02 ETH 15m | 78.6% / 3.4% / 944t | 78.6% / 3.4% / 942 | ✓ |
| **MT01 BTC 15m (ema20+vol)** | **82.7% / 5.9% / 503t** | 82.7% / 5.9% / 503 | ✓ esatto |
| MT01 ETH 15m (ema20+vol) | 81.2% / 2.9% / 404t | — | ok |
**Lezione:** l'integrità dei dati rigenerati è confermata — la pipeline di download produce
risultati riproducibili. La config live di MT01 (ema20+vol) coincide col best documentato.
### Punti aperti
1. **Backtest e drift dati:** MT01 live ora è immune (1h da Cerbero), ma i backtest girano
sempre sui dati fino all'ultimo `download_all()`. Per dati di backtest sempre freschi
serve uno scheduling del download (cron/job).
2. **Healthcheck:** valutare un check su mtime di `status.json` (< 180s) per rilevare uno
stallo del loop, non solo l'esistenza del file.
---
### 23:00 — 3 nuove strategie con edge OOS fee-aware (branch `strategy_free`)
**Obiettivo:** trovare almeno 3 nuove strategie (oltre MR01), edge netto validato
out-of-sample e fee-aware, per il target €1.000 → ~€50/giorno.
**Metodologia (invariata dalla lezione squeeze):** ingresso eseguibile a `close[i]`
(nessun look-ahead), backtest netto dopo fee Deribit 0.10% RT + leva 3x, OOS = ultimo
30% held-out, robustezza su griglia parametri + sweep fee 0.000.20% RT, exit
TP/SL intrabar o time-limit, una posizione per volta, capitale composto.
**Candidati** (`scripts/analysis/strategy_research_v2.py`), tutti mean-reversion
(l'edge è sempre il rientro, mai la continuazione):
| Candidato | Esito | Motivo |
|---|---|---|
| **MR02 Donchian Fade** | ✅ | Robusto su tutta la griglia `n × sl_atr` e tutte le fee |
| **MR03 Keltner Fade** | ✅ | Robusto su tutta la griglia `n × k`; banda ATR, indipendente da Bollinger |
| **MR07 Return Reversal** | ✅ | Intero blocco `tp_atr=2.0` positivo full+OOS; esposizione ~8% |
| MR04 Z-score Reversion | ⛔ | Robusto ma è MR01 riparametrizzato (stessa banda std): edge non *nuovo* |
| MR05 Bollinger + filtro ADX | ⛔ | Non robusto: negativo su gran parte della griglia BTC |
| MR06 RSI(2) Connors | ⛔ | ETH 1h negativo; non robusto su entrambi gli asset |
**Risultati** (netto 0.10% RT, leva 3x, OOS, 1h):
| Codice | Meccanismo | BTC OOS | ETH OOS | DD (full) |
|---|---|---|---|---|
| MR02 | estremi canale Donchian H/L | +172% | enorme | 30% / 42% |
| MR03 | canale ATR su EMA | +112% | +886% | 37% / 66% |
| MR07 | z dei rendimenti di barra | +105% | +195% | 25% / 46% |
**Validazione live-path** (`oos_validation.py`, legge `strategies.yml`, exit hold
del worker): tutte e tre positive netto OOS su tutto lo sweep fee, anche al
pessimistico 0.20% RT → edge robusto pure al meccanismo di exit.
**Verifiche:** equivalenza esatta backtest produzione vs research engine (MR02 BTC:
2039 trade, DD 29% identici); le 3 classi si caricano dal `strategy_loader`;
aggiunte a `strategies.yml` (BTC+ETH 1h). Nessuna suite di test nel progetto.
**Onestà sul target:** con 4 fade indipendenti × 2 asset il PnL storico aggregato
supera €50/giorno, ma sono backtest a leva 3x su 8 anni con annate eccezionali
(ETH 2024). Plausibile ma da confermare col paper trader live prima del capitale reale.
DD alto su ETH (MR03 ~66%, come MR01) → leva più bassa consigliata per quell'asset.
**File:** `strategy_research_v2.py`, `src/strategies/fade_base.py`,
`scripts/strategies/MR0{2,3,7}_*.py` (nuovi); `strategy_loader.py`, `strategies.yml`,
`CLAUDE.md` (aggiornati).
**Lezione confermata:** ogni edge robusto trovato finora è mean-reversion; ogni
variante trend/continuation o oscillatore senza filtro perde netto.
---
### 23:45 — Aumentare Acc e ridurre DD (filtro trend + portafoglio)
**Obiettivo:** alzare accuratezza e abbassare drawdown sulle 4 fade, senza
distruggere l'edge né overfittare (ogni leva misurata FULL **e** OOS).
**Diagnosi:** perdite/DD concentrati 20182021 (bear/covid/caos vol), su ETH DD
pieno 6671%. Banco di prova: `scripts/analysis/risk_improvements.py` e
`risk_portfolio.py`.
**Leve testate:**
| Leva | Esito | Motivo |
|---|---|---|
| Sizing vol-target (size ∝ 1/dist-SL) | ⛔ | Over-size sui trade a stop stretto → DD su, ritorno giù |
| Skip alta volatilità (ATR% in coda alta) | ⛔ | L'alta vol è *positiva* per le fade (più reversione): Acc e ritorno giù |
| **Filtro trend** (`\|closeEMA200\|/ATR > soglia` → salta) | ✅ | Non fada trend/crolli estremi: Acc↑ ovunque, DD↓ molto su ETH, OOS regge |
| **Portafoglio** equipesato (sotto-conti indipendenti) | ✅ | Curve poco correlate → DD aggregato 14% (full)/10% (OOS) vs 20-70% singolo |
**Filtro trend — sweep soglia** (assoluta in ATR, regola unica per tutte = niente
overfit): 3.0 ATR è l'equilibrio (2.0 taglia troppo ritorno). Effetto su config
deployata (base → filtro):
| Sleeve | Acc | DD |
|---|---|---|
| MR01 ETH | 46→55 | **71→26** |
| MR02 ETH | 49→55 | 42→25 |
| MR03 ETH | 49→52 | 66→34 |
| MR07 ETH | 48→54 | 46→21 |
| MR01 BTC | 51→54 | 32→34* |
| MR02 BTC | 48→52 | 29→23 |
| MR07 BTC | 49→53 | 25→18 |
| MR03 BTC | 47→47 | 37→37 (filtro OFF) |
\*MR01 BTC: DD full +2pt ma Acc +3.7 e DD OOS piatto (14.8→15.0). **MR03 BTC**:
il filtro peggiora entrambe (unico sleeve) → lasciato disattivo nello yaml.
**Implementazione:** helper `trend_distance()` in `fade_base.py`; param opzionali
`trend_max`/`ema_long` (default None = retro-compatibile) in tutte le strategie
(MR01/02/03/07); `strategies.yml` con `trend_max: 3.0, ema_long: 200` (eccetto
MR03 BTC). Verificato: equivalenza produzione vs ricerca.
**Lezione:** il modo onesto di ridurre il DD non è strozzare il sizing (peggiora),
ma (a) non opporsi a trend estremi e (b) diversificare su strategie scorrelate.
+155
View File
@@ -0,0 +1,155 @@
# Diario — 2026-05-29 — Esplorazione di nuove famiglie di strategie
## Obiettivo
Trovare 5-10 nuove famiglie di strategie, diverse da quelle esistenti, migliori o
complementari, con DD basso e attenzione alle fee. Esplorazione onesta (no
look-ahead, netto fee, OOS) condotta con **agenti paralleli**, ognuno su una famiglia
indipendente, tutti sullo stesso harness condiviso (`scripts/analysis/explore_lab.py`).
Lavoro sul branch `strategy_explore`.
## Famiglie esplorate (9) ed esito onesto
| Famiglia | Esito | Note |
|---|---|---|
| **Pairs / spread reversion** | ✅ **VINCITORE** | Market-neutral, genuinamente nuova, decorrelata |
| **TSMOM multi-orizzonte** | ✅ diversificatore | Marginale ma distinto (corr 0.53 con ROT02), DD basso |
| Stagionalità settimanale | ⚠️ marginale/fragile | "Mercoledì-long-24h" 7/8 asset OOS+ ma effetto concentrato a 00:00 UTC |
| Vol-target BTC | ⚠️ marginale | Sharpe 0.94 vs 0.76 buy&hold, DD ancora 44% |
| Stagionalità intraday (ora) | ❌ rumore | L'edge orario muore sotto le fee |
| Stagionalità mensile/turn-of-month | ❌ rumore | Reale in-sample, morto OOS dal 2024 |
| Cross-sectional reversal | ❌ nessun edge | Perde vs equal-weight, corr 0.98 col momentum |
| Opening-range breakout | ❌ non generalizza | Solo BTC/ETH, alcuni regimi, fee-fragile |
| Lead-lag BTC→alt | ❌ nessun edge | Reazione contemporanea (corr lag+1 ≈ 0), non batte buy&hold |
| Momentum/continuation intraday | ❌ negativo | Conferma: il *fade* (mean-reversion) domina |
7 famiglie su 9 sono rumore — e l'harness le ha rifiutate senza produrre falsi
positivi (segnale che la metodologia onesta funziona). Due edge reali emergono.
## Vincitore 1 — PAIRS (market-neutral) — `PR01_pairs_reversion.py`
Scommette sul rientro del log-ratio di due cripto verso la media (z-score). Quando
`z ≤ 2` → long A / short B; `z ≥ +2` → l'opposto; esce al rientro (`|z| ≤ 0.5`) o a
tempo. Engine onesto verificato in `pairs_research.py` (test esplicito no-look-ahead:
`z[i]` invariato perturbando il futuro). Fee contate su **2 gambe** (0.20% RT/coppia).
Validazione (netto, leva 3x, OOS = ultimo 30%, 1h):
| Coppia | CAGR | Sharpe | OOS DD | anni+ |
|---|--:|--:|--:|--:|
| ETH/BTC | 144% | 4.04 | 17% | 8/9 |
| LTC/ETH | 71% | 2.52 | 10% | 7/8 |
| ADA/ETH | 77% | 2.16 | 11% | 7/8 |
Tutte le 10 coppie testate positive FULL+OOS, regge fee 0.40% RT/coppia, correlazione
col mercato ~0.02 (market-neutral confermato). DD pieno 42-49% (alto), ma OOS DD
10-17% (buono) e soprattutto **quasi-zero correlazione** col resto → diversificatore
eccezionale. Limite: 2 gambe (long+short), il worker live va esteso prima del live.
## Vincitore 2 — TSM01 (TSMOM multi-orizzonte) — `tsmom_research.py`
Long-only multi-crypto: tiene equal-weight gli asset con consenso pieno del segno di
momentum su 3/6/12 mesi, cash se BTC<SMA100. Distinto da ROT02 (persistenza assoluta
vs ranking relativo), corr 0.53. FULL +169% / OOS +80% / DD 22% / Sharpe 1.07,
**mai un anno negativo**, regge fee 0.40%. Verificato no-look-ahead (cheat-test
esplode a +575%). Marginale come stand-alone (rende meno di ROT02) ma utile in ensemble.
## Il payoff — combinare le nuove fonti col MASTER (`combine_v2.py`)
Le nuove sleeve sono quasi scorrelate col MASTER-9 (pairs ~0.02-0.08, TSM01 0.05).
Aggiungerle migliora nettamente il portafoglio:
| Portafoglio | CAGR | DD% | Sharpe | OOS DD% | OOS Sharpe |
|---|--:|--:|--:|--:|--:|
| MASTER-9 (base) | 47 | 5.2 | 4.23 | 4.7 | 4.33 |
| **MASTER + pairs (12)** | **66** | **3.8** | **5.67** | **3.3** | **6.86** |
| MASTER + TSM01 (10) | 44 | 4.7 | 4.21 | 4.2 | 4.33 |
| MASTER esteso (13) | 62 | 3.6 | 5.66 | 3.0 | 6.79 |
I **pairs** sono l'aggiunta decisiva: alzano la CAGR (47→66), **abbassano il DD**
(5.2→3.8 full, 4.7→**3.3** OOS) e portano lo Sharpe OOS a **6.86** — il free-lunch
della diversificazione da una fonte market-neutral scorrelata. TSM01 contribuisce
poco (diluisce il ritorno) ma abbassa lievemente il DD.
## Caveat onesti
- I pairs hanno DD pieno alto (42-49%) sull'1h; il vantaggio sta nella decorrelazione,
non nel DD stand-alone. Richiedono esecuzione a 2 gambe (short del perp B) — da
verificare shortabilità/liquidità sugli alt e raddoppio fee nel worker.
- Sharpe combinati 5-7 e CAGR 60%+ sono backtest a leva 3x su finestra 2021-2026 con
OOS ~1.6 anni e il 2024 cripto eccezionale: numeri ottimistici, da confermare in
paper trading live.
- TSMOM e le strategie honest condividono l'overlay risk-off SMA100: parte della loro
difensività è comune (non perfettamente indipendente).
## Terza ondata — espansione dei meccanismi provati + 2 nuovi sondaggi
Esplorate altre 4 direzioni con agenti paralleli:
- **Fade su 6 nuovi alt (ADA/BNB/DOGE/LTC/SOL/XRP)**: 0 robuste. La mean-reversion
fade vive solo su BTC/ETH (liquidi); sugli alt sparisce o è artefatto di pochi pump
(DOGE). Coerente con la lezione del progetto.
- **Espansione PAIRS** (tutte le 28 coppie): trovate **3 nuove coppie robuste**
BTC/LTC (robusta 1h *e* 4h, Sharpe 2.21, DD 24-34%, concentrazione PnL 9%), ETH/SOL
e BNB/ETH (Sharpe 2.4+, solo 1h). Pattern: sempre alt-liquido vs major, mai alt/alt.
PR01 ora ha **6 coppie**.
- **Low-volatility anomaly**: ❌ in cripto è INVERTITA (vince l'alta vol = alta beta),
ridondante con EW+risk-off/ROT02. L'anti-test high-vol stravince.
- **Confluenza multi-timeframe (fade 1h confermato da 4h)**: non crea edge nuovo e non
migliora lo Sharpe, ma **dimezza il DD** di MR01 (ETH: stesso Sharpe 3.17 a DD 38% vs
63%) e stabilizza l'OOS → utile variante low-DD, non strategia indipendente.
## Bilancio finale e MASTER esteso (6 pairs)
Robusti deployabili: **famiglia PAIRS (6 coppie) + TSM01** (+ confluenza MTF come variante
low-DD di MR01, + tilt stagionale mercoledì marginale). I 6 pairs sono quasi scorrelati col
MASTER (corr 0.02-0.08). MASTER + 6 pairs:
| Portafoglio | CAGR | DD% | Sharpe | OOS DD% | OOS Sharpe |
|---|--:|--:|--:|--:|--:|
| MASTER-9 (base) | 47 | 5.2 | 4.23 | 4.7 | 4.33 |
| **MASTER + 6 pairs (15)** | **71** | 5.7 | **5.93** | **2.3** | **7.71** |
| MASTER esteso +TSM01 (16) | 67 | 5.4 | 5.95 | **2.2** | 7.67 |
Aggiungere i 6 pairs porta l'**OOS DD a 2.2-2.3%** (da 4.7%) con Sharpe OOS ~7.7 e tutti
gli anni positivi: il guadagno di diversificazione da fonti market-neutral scorrelate.
## Quarto giro — validazione anti-overfitting e irrobustimento
Tre audit scettici paralleli (walk-forward, plateau, stress, scomposizione):
**Pairs — de-overfittati.** Sostituita la config per-coppia (cherry-picking di z_exit/n)
con **una config universale `n=50 z_in=2.0 z_exit=0.75 max_bars=72`**. Verifiche:
- plateau (non picco): heatmap n×z_in → 20/20 celle Sharpe>1 su ETH/BTC e BTC/LTC;
- walk-forward (train 2y / test 6m rolling): ETH/BTC 11/12 finestre positive, BTC/LTC
9/10 → edge distribuito su tutta la storia, non un regime singolo;
- **BNB/ETH scartata** (era robusta solo coi suoi parametri → overfit; crolla con la
universale e muore per prima allo stress costi). Famiglia ridotta a **5 pairs**.
- stress: 5/6 reggono fee+slippage realistici; solo ETH/BTC regge fee 6x (coda fee-fragile).
**Master — numeri sobri.** L'OOS Sharpe 7.7 / DD 2.3% è **ottimistico ~50%** perché l'OOS
cade nel bull calmo 2024-25. Numeri onesti da usare:
- worst-DD su finestra mobile 90g (2021-2026) = **5.7%** (bear FTX) → budget DD ~6%, non 2.3%;
- Sharpe per-semestre: mediana **~5** (min 1.2, max 12) → atteso ~5, non 7.7;
- ogni anno e ogni semestre dal 2021 positivo (anche il 2022 bear, grazie alle gambe short);
- equal-weight ≈ inverse-vol (non dipende da pesi fortunati);
- regge **leva 2x + slippage doppio** (CAGR 36%, Sharpe 5.1);
- **rischio concentrato: i pairs portano ~57% del rischio** → cap consigliato ~30-35%.
- Config robusta raccomandata: **MASTER-esteso, equal-weight, leva 2x, cap pairs ~30-35%**.
**TSM01 — confermato robusto** (36/36 config OOS+, walk-forward stabile) ma corr reale con
ROT02 = **0.62** (non 0.53), e gran parte del DD basso viene dall'overlay risk-off condiviso.
Tenuto come diversificatore con **gross 0.30** (stesso Sharpe, DD 22%→15%).
**Confluenza multi-TF — SCARTATA: era overfit.** Taglia il 97% dei trade (restano ~40 in
8 anni = non significativo), distrugge lo Sharpe (1.58→0.27 su BTC) e il caso "bello" non
sopravvive alle perturbazioni. Per abbassare il DD di MR01 meglio ridurne la leva, non il filtro 4h.
**Risultato del giro:** quanto trovato regge l'esame anti-overfit (NON è l'errore squeeze),
ma i numeri vanno comunicati sobri (Sharpe ~5, DD ~6%) e con leva 2x + cap pairs. Famiglia
pairs consolidata a 5 coppie con config universale; confluenza MTF rimossa dai vincitori.
## File creati (branch strategy_explore)
`scripts/analysis/explore_lab.py` (harness onesto condiviso), `pairs_research.py`
(verifica + ricerca pairs), `tsmom_research.py` (TSM01), `combine_v2.py` (master
esteso); `scripts/strategies/PR01_pairs_reversion.py` (artefatto pairs).
+84
View File
@@ -0,0 +1,84 @@
# Diario — 2026-05-29 — Pattern del segnale per FORMA (analog/shape forecasting)
## Obiettivo
Verificare se la **forma** del segnale (la morfologia recente del prezzo) permette di
prevedere l'andamento successivo, e ricavarne edge verso il target €1000 → €50/giorno.
Esplorazione onesta (no look-ahead, netto fee, OOS) con **agenti paralleli**, ognuno su
una famiglia di forma indipendente, tutti sullo stesso harness shape (`scripts/analysis/
shape_lab.py`, che riusa l'engine netto-fee+OOS di `explore_lab.py`). Branch `shape_patterns`.
## Harness
`shape_lab.py` — analog forecasting causale: a ogni barra `i` si guarda la forma recente
`W` (closes z-normalizzati fino a `close[i]`), si cercano nel passato le `K` finestre più
simili **il cui esito a `H` barre era già noto prima di `i`** (KDTree ricostruito ogni
`rebuild` barre → niente O(N²)), si prevede la direzione = segno del rendimento medio degli
analoghi. **No-look-ahead verificato** (perturbare il futuro non cambia la forma a `i`,
max diff 0.0). Baseline forma grezza: marginale e **muore sulle fee** (W24H12K50: FULL
+112% / OOS +48% ma a 0.20% RT → 72%; troppi trade, exp 74%, win 49.5%).
## Famiglie esplorate (5) ed esito onesto
| Famiglia | Esito | Note |
|---|---|---|
| Analog kNN (forma grezza, selettività) | ❌ RUMORE | Solo BTC-overfit, non robusto ≥2 asset |
| Encoding candele (UP/DOWN/DOJI + body/shadow) | ❌ RUMORE | Hit-rate condizionale ~50%, segno incoerente fra asset |
| DTW + template geometrici (M/W, testa-spalle, V, U) | ❌ RUMORE | DTW *peggiora* l'euclidea; template overfit (FULL ok, OOS crolla) |
| PIP / pivot / zig-zag (geometria svolte) | ❌ RUMORE | 0/48 config robuste; le rotture S/R rientrano (riconferma MR) |
| **Feature-vector + ML walk-forward** | ✅ **EDGE REALE** | LogisticRegression sulla forma, fee-robusto |
4 famiglie su 5 sono rumore: riconfermano che la forma grezza non contiene edge
direzionale eseguibile e che l'unico edge "classico" resta la mean-reversion (fade/pairs).
## L'edge: SH01 — Shape-ML
Una **LogisticRegression** legge 17 feature di forma (body/shadow ratio, rendimenti,
pendenza/curvatura del path, posizione di max/min, RSI, estensione) e predice il segno del
rendimento a `H` barre. **Walk-forward rigoroso**: scaler+modello fittati solo sul passato
con esito noto, poi predicono il blocco corrente; si entra a `close[i]` se la probabilità
≥ soglia. Causalità verificata con check espliciti (feature e predizioni invarianti al
futuro). Il GradientBoosting dà edge equivalente ma è ~60× più lento → si usa il logit.
A differenza della famiglia squeeze (che moriva anche a fee zero), **questo edge
sopravvive a fee 0.20% RT**. Win-rate ~50% → l'edge è nell'**asimmetria** (quando indovina
la direzione i moti sono più grandi), non nella frequenza.
### Validazione dura (config W24 H12 th0.58, netto fee, leva 3x, pos 0.15, OOS 30%)
- **Multi-asset expanding**: robusti **BTC** (FULL +219% / OOS +42% / Sharpe 2.72 / DD 23%
/ 8-9 anni+ / accOOS 56%), **ETH** (+80% / +144% / Sharpe 1.21, più volatile), **ADA**
(+707% / +57% / Sharpe 3.22). Scartati LTC/SOL/XRP (perdono netti).
- **Walk-forward rolling (train fisso 2 anni)**: regge **solo BTC** (+166% / +96% / Sharpe
2.05). L'edge si appoggia in parte alla memoria lunga → BTC è il più solido.
- **Stress leva 2x + slippage doppio (0.20% RT)**: BTC OK (+40% / +17% / Sharpe 1.24),
ETH marginale (+7% / +73% / Sharpe 0.37).
- **Griglia (W,H,thresh) su BTC**: **5/27 celle robuste**, su una **cresta** stretta (W24,
H8-12), non altopiano largo → rischio overfit moderato. Per prudenza si sceglie la config
robusta sul maggior numero di test (W24 H12 th0.58), non il PnL massimo (W24 H8 rende di
più ma accOOS ~49% = più drift che segnale).
### Il valore vero: diversificatore di portafoglio
Correlazione daily col MASTER **+0.08** (quasi scorrelato). Aggiungere lo sleeve shape
(BTC+ETH) al MASTER migliora l'OOS: **Sharpe 4.33 → 5.10, DD 4.7% → 4.2%** (FULL: Sharpe
4.23 → 4.37, DD 5.2% → 4.3%). Non è un motore standalone (per-asset troppo stretto fuori
da BTC), ma un **free-lunch** da aggiungere al paniere.
## Artefatti
- `scripts/analysis/shape_lab.py` — harness analog/forma causale.
- `scripts/analysis/shape_{analog,candle,template,pivot,ml}_research.py` — le 5 ricerche.
- `scripts/analysis/shape_ml_validate.py` — validazione dura del candidato ML.
- `scripts/strategies/SH01_shape_ml.py` — la strategia (Strategy + run() riproducibile).
- Aggiunta a `MODULE_MAP` (caricabile per backtest).
## Conclusione e prossimi passi
La forma del segnale **non** predice in modo grezzo (4/5 famiglie rumore), ma un modello
lineare sulle feature di forma in walk-forward onesto **sì**, soprattutto su BTC, e vale
come diversificatore quasi-scorrelato del MASTER. Da fare prima del live:
1. **Worker con retraining periodico** (lo StrategyWorker attuale è a regola fissa; SH01
riallena il modello → serve un loop tipo legacy signal_engine).
2. Validazione live-path (replay worker == backtest) come fatto per i pairs.
3. Decidere il peso nel MASTER-esteso (cap, leva) col paper trader.
+64
View File
@@ -0,0 +1,64 @@
# Diario di ricerca — 2026-05-29
## Combinare le strategie migliora i risultati?
**Domanda:** usare insieme le due famiglie di strategie presenti sul repo migliora
il profilo rischio/rendimento rispetto a usarle separatamente?
- **FADE** (mie): reversione intraday 1h, long/short, BTC/ETH — MR01 Bollinger,
MR02 Donchian, MR03 Keltner, MR07 Return-reversal (tutte col filtro trend 3.0 ATR).
- **HONEST** (altra sessione): long-only multi-regime multi-crypto — DIP01 (dip-buy
1h BTC), TR01 (EMA-trend 4h basket), ROT02 (dual-momentum rotation 1d).
**Metodo** (`scripts/analysis/combine_portfolio.py`): per ogni sleeve si costruisce
l'equity **giornaliera** normalizzata su un indice comune (2021-01-01 → 2026-05-26),
si passa ai rendimenti giornalieri, si misura la correlazione cross-famiglia e si
confrontano i portafogli equal-weight (ribilanciati ogni giorno), 50/50 fra famiglie
e inverse-vol. Metriche FULL e OOS (ultimo 30% della finestra comune, da 2024-10-12):
ritorno, CAGR, max DD, Sharpe annualizzato. Le curve honest sono riusate da
`honest_improve2.py`; quelle fade da `risk_management.build_trades`.
**Correlazione:** cross-famiglia **+0.05** (quasi indipendenti). Intra-fade +0.18,
intra-honest +0.05. L'unica coppia un po' correlata è MR01_BTC↔DIP01_BTC (+0.43),
entrambe mean-reversion su BTC. Famiglie scorrelate ⇒ diversificazione quasi ideale.
**Risultati (FULL | OOS):**
| Portafoglio | Ret% | CAGR | DD% | Sharpe | oDD% | oSharpe |
|---|---|---|---|---|---|---|
| FADE only (8) | +549 | 41 | 8.6 | 3.75 | 5.4 | 4.14 |
| HONEST only (3) | +642 | 45 | 12.0 | 1.90 | 6.5 | 2.23 |
| **ALL equal-weight (11)** | +589 | 43 | 6.1 | **3.95** | 4.6 | **4.46** |
| **ALL 50/50 famiglie** | +615 | 44 | **5.5** | 3.18 | **4.0** | 3.87 |
| ALL inverse-vol | +483 | 39 | 5.8 | 3.97 | 4.6 | 4.02 |
**Conclusione: sì, combinare conviene.**
- DD crolla: combinato 5.56.1% full / 4.04.6% OOS, contro 8.6% (fade) e **12%**
(honest) da sole → drawdown ridotto del 3550%.
- Sharpe sale: combinato OOS **4.46** vs honest 2.23 (raddoppia) e batte pure fade (4.14).
- CAGR resta ~4344% (≈ media delle due famiglie) ma con metà del rischio: è il
"free lunch" della diversificazione fra sorgenti di edge scorrelate.
- Best Sharpe = equal-weight degli 11 sleeve; best DD = 50/50 fra le due famiglie.
**Caveat onesti:** la finestra comune è 20212026 (5.4 anni), OOS ~2024-10→oggi
(1.6 anni) — pochi regimi. CAGR e Sharpe sono backtest a leva 3x; il 2024 cripto
favorevole pesa. Il target €50/giorno resta vincolato dal capitale: 43% CAGR su
€1000 non fa €50/giorno a breve, serve compounding pluriennale o più capitale.
Prossimo passo: confermare il portafoglio combinato nel paper trader live.
**File:** `scripts/analysis/combine_portfolio.py` (nuovo).
## Pulizia roster + miglioria ROT02
- **Waste delle peggiori:** MR03 Keltner (fade più debole, Sharpe 1.22, ridondante
con MR01 — rimuoverla *migliora* il portafoglio fade: DD 8.6→8.2, ret +549→+666)
e ROT01 (dominata da ROT02). Spostate in `scripts/waste/`.
- **Portafogli pronti:** `PORT02_fade_master` (6 sleeve fade) e `PORT03_all_master`
(9 sleeve fade+honest, varianti equal/5050).
- **ROT02 DD alto → migliorato:** la rotazione concentrava il book su 2 asset
(DD 40%). Sweep su `rot_improved`: `top_k=3` dimezza quasi il DD (40%→26%) e
*alza* il ritorno full (+1095→+1303%, ret/DD 27→50). Il vol-target abbassa il DD
ma sacrifica ritorno (de-leverage) → tenuto top_k=3 senza VT. Caveat onesto:
l'OOS di ROT02 cala un po' (+98→+68%, DD 12→14%), ma il MASTER (config deployata)
migliora lo Sharpe full 3.95→4.23. Applicato a `ROT02_dual_momentum.py` e
`_rot_daily_equity`. Sweep in `honest_improve.rot_improved`.
+68
View File
@@ -0,0 +1,68 @@
# 2026-05-31 — Studio sugli EXIT delle fade: scalping, TP dinamico, TP-ATR
> Innescato da una domanda operativa ("un TP è stato raggiunto, non si poteva
> scalpare / fare un TP dinamico?"). Studio fee-aware su MR02 (Donchian fade,
> segnali invariati `n=20 sl_atr=2.0 max_bars=24`, fee 0.10% RT, leva 3x). Tre
> alternative di uscita misurate contro il baseline attuale (**TP = centro del
> canale**). Verdetto: **il design attuale è già ottimale; nessuna alternativa lo batte.**
## 1. "Scalping" = timeframe più veloce (15m vs 1h)
A fee 0.10% il 15m rende di più in lordo (~4× più trade), MA è molto più **fragile**:
| | trade | PnL @0% | @0.10% | @0.20% | DD @0.10% |
|---|--:|--:|--:|--:|--:|
| BTC 1h | 2041 | +22.768 | +16.645 | +10.522 | 29% |
| BTC 15m | 8251 | +65.286 | +40.533 | +15.780 | 29% |
| ETH 15m | 9388 | +120.103 | +91.939 | +63.775 | **62%** |
Da 0% a 0.20% il 15m perde **~76%** del profitto (vs 54% del 1h) e il DD esplode
(ETH 15m → 93% a 0.20%). 4× più trade = 4× più fee + slippage (non modellato, ma
peggiore su book sottili). **L'1h è scelto per il margine di sicurezza, non per il PnL
lordo.** Lo scalping vero (<0.3% target) è in pieno territorio "morte da fee".
## 2. TP dinamico / trailing ("lascia correre il vincitore")
Stessi segnali, exit per trailing a k·ATR dal massimo favorevole invece del TP fisso:
| policy | BTC win% | ETH win% | equity |
|--------|---------:|---------:|--------|
| FIXED (centro, attuale) | **48%** | **49%** | 🟢 di gran lunga il migliore |
| TRAIL (lascia correre) | 36% | 36% | 🔴 azzerato |
| MID+TRAIL | 47% | 47% | 🔴 peggio |
Il win-rate crolla 48%→36%: i trade che avrebbero incassato il TP fanno andata-e-ritorno
e stoppano fuori. **Concettuale:** l'edge della fade è la reversione *fino* alla media;
una volta toccata, l'edge è esaurito. Lasciar correre *oltre* = scommettere sulla
continuazione, che sui perp crypto NON ha edge (rientra). È la stessa logica per cui
SMA/ORB/WR (continuazione) hanno fallito: **let-it-run = trend = il lato perdente.**
## 3. TP scalato all'ATR (TP = entry + dir·m·ATR, SL fisso 2 ATR → R:R = m/2)
| Config | win% | avg %/trade | Sharpe | sumRet% |
|--------|-----:|-----------:|-------:|--------:|
| **BTC MID (attuale)** | 48% | **0.816** | **3.8** | **1664** |
| BTC ATR m=0.5 (RR0.25) | **77%** | 0.081 | 1.0 | 217 |
| BTC ATR m=1.0 | 67% | 0.192 | 1.6 | 465 |
| BTC ATR m=2.0 | 53% | 0.563 | 3.0 | 1199 |
| BTC ATR m=3.0 | 46% | 0.679 | 3.0 | 1331 |
| **ETH MID (attuale)** | 49% | **1.738** | **7.5** | **4169** |
| ETH ATR m=0.5 | 77% | 0.041 | 0.5 | 134 |
| ETH ATR m=3.0 | 46% | 1.082 | 4.7 | 2515 |
OOS (ultimo 30%) identico: **MID** batte ogni `m` (BTC MID avg 1.14/Sh 3.2; ETH MID avg
4.43/Sh 10.9). Due lezioni:
- **TP stretto (m=0.5) = trappola dello scalping quantificata:** win-rate **77%** ma edge
**zero/negativo** (BTC 0.08%/trade). I rari stop a 2 ATR spazzano via le micro-vincite,
la fee mangia il resto. **Win-rate alto ≠ edge.**
- **Nessun multiplo ATR fisso batte il centro del canale**, su avg/trade E Sharpe, FULL e OOS,
entrambi gli asset.
## Verdetto unificato
Il **TP al centro del canale è ottimale** perché è un target *adattivo alla struttura*: un
multiplo fisso di ATR misura solo *quanta* vol c'è, ma ignora *dove* sta la media; il centro
adatta al punto reale di reversione **ed è già scalato alla volatilità** (il canale si allarga
in regime volatile). Per una mean-reversion il punto giusto dove chiudere è **la media — niente
prima, niente dopo.** Tre alternative escluse coi numeri (15m, trailing, TP-ATR) → la scelta
di design corrente è blindata.
> Nota metodologica ricorrente: diffidare del **win-rate alto**. Il segnale vero è
> rendimento-medio-per-trade × Sharpe; un TP stretto regala win-rate e nasconde l'assenza
> di edge. (Stesso tranello dei guru: backtest cherry-picked ad alta % di vincite.)
+70
View File
@@ -0,0 +1,70 @@
# 2026-05-31 — Stato trade LIVE PORT06 (paper trading)
> Snapshot verificato del paper trader a portafoglio (`src.portfolio.runner`, Docker
> `pythagoras-portfolio`). Dati da `data/portfolios/PORT06/` + log del container.
> Avvio container: 2026-05-29 18:37 UTC. Snapshot: 2026-05-31 13:20 UTC (~43h).
## Riepilogo capitale
| Metrica | Valore |
|---------|--------|
| Capitale iniziale | €1000.00 (17 sleeve equal-weight, ~€58.82 ciascuno) |
| `total_capital` (realizzato, ultimo rebal 00:00) | **€1000.09** (+0.09) |
| Equity mark-to-market (live) | **€1000.36** (+0.036%) |
| Peggior punto toccato | −€0.01 |
| **Max DD** | **0.40%** |
| Container | running, healthy, 0 restart |
## Trade chiusi (storia completa dallo startup: 10 trade, 9W/1L)
| # | Sleeve | Uscita | Net % | PnL € | Esito |
|---|--------|--------|------:|------:|:---:|
| 1 | PR01 ETH/SOL | mean_revert | +0.503 | +0.040 | W |
| 2 | PR01 ETH/SOL | mean_revert | +0.683 | +0.060 | W |
| 3 | SH01 BTC (ML) | hold_limit | 0.462 | 0.040 | L |
| 4 | SH01 BTC (ML) | hold_limit | +0.017 | +0.000 | W |
| 5 | PR01 ETH/SOL | mean_revert | +0.488 | +0.040 | W |
| 6 | PR01 ETH/SOL | mean_revert | +0.284 | +0.030 | W |
| 7 | PR01 LTC/ETH | mean_revert | +0.745 | +0.070 | W |
| 8 | PR01 BTC/LTC | mean_revert | +0.434 | +0.040 | W |
| 9 | MR02 ETH fade | take_profit | +0.995 | +0.090 | W |
| 10 | SH01 ETH (ML) | hold_limit | +0.742 | +0.070 | W |
| | **TOTALE** | | | **+0.400** | **90% win** |
### Aggregato per sleeve (trade chiusi)
| Sleeve | n | win | acc% | PnL € |
|--------|--:|----:|----:|------:|
| PR01 ETH/SOL | 4 | 4 | 100 | +0.170 |
| MR02 ETH fade | 1 | 1 | 100 | +0.090 |
| PR01 LTC/ETH | 1 | 1 | 100 | +0.070 |
| SH01 ETH (ML) | 1 | 1 | 100 | +0.070 |
| PR01 BTC/LTC | 1 | 1 | 100 | +0.040 |
| SH01 BTC (ML) | 2 | 1 | 50 | 0.040 |
Motore del PnL finora: **pairs PR01** (market-neutral, mean_revert rapidi 1-6 barre) +
una fade **MR02** su take_profit. Unica perdita: SH01 BTC (ML) su hold_limit (fisiologico,
edge nell'asimmetria, win-rate ~50%). Sleeve daily (ROT02/TSM01/TR01) e diverse fade non
hanno ancora chiuso trade (orizzonte più lungo / pochi segnali in ~2 giorni).
## Posizioni aperte (3)
| Sleeve | Dir | Entry | Capitale |
|--------|-----|------:|---------:|
| MR02 BTC fade | short | 73969.0 | €58.83 |
| MR02 ETH fade | long | 2016.15 | €58.92 |
| SH01 BTC (ML) | long | 73811.5 | €58.83 |
## Verifica (check 2026-05-31)
- **0 anomalie** sui 10 CLOSE: `net = gross fee` rispettato, flag `win` coerente col
PnL, fee sempre presente (pairs 0.4% su 2 gambe, fade 0.10% RT).
- **Uscite = backtest**: tutti i CLOSE pairs sono `mean_revert` con **|z| ≤ 0.75** al close
(0.363/0.605/0.684/0.619/0.656) = esattamente `z_exit=0.75` di PR01; MR02 esce a TP al
livello. Il worker live replica la regola del backtest.
- **Riconciliazione**: +0.40 realizzato vs +0.09 `total_capital` NON è un errore — è il
timing del ribilancio giornaliero (`total_capital` snapshotta a 00:00, le posizioni
aperte restano sul notional fino al rebal; CLAUDE.md). L'equity MtM live (+0.36) è il
numero corrente, confermato da `equity.jsonl`.
## Lettura onesta
Campione minuscolo (**~2 giorni, 10 trade**) → il PnL (+€0.40 realizzato, +€0.36 MtM) è a
livello di **rumore**: non se ne deduce performance. Quello che il check conferma a questo
stadio è che il sistema è **sano e fedele**: esecuzione corretta, costi reali inclusi,
uscite conformi al backtest, DD trascurabile (0.40%), 0 errori/restart. L'edge si
manifesterà solo su orizzonte settimane/mesi. Monitor Docker attivo per down/unhealthy/restart.
+51
View File
@@ -0,0 +1,51 @@
# 2026-05-31 — Copertura opzioni: idee testate e SCARTATE (record anti-ripetizione)
> Record delle conclusioni. Il **codice** di queste prove è stato testato e poi
> scartato (non conservato nel repo): qui restano i numeri e il *perché*, così da
> non ri-testare le stesse idee in futuro. Motore di pricing usato: Black-Scholes
> r=0 + IV stimata onestamente = RV × moltiplicatore VRP ≥ 1 (il compratore
> SOVRAPPAGA, come in W18-W21), fee Deribit reali (0.03%/gamba + ~0.10% slippage).
## TL;DR
**La copertura opzioni non genera edge nuovo per questo progetto.** I due edge
disponibili (trend e mean-reversion) sono già catturati **50-100× più a buon
mercato dai perp** (fee 0.10% RT) di quanto facciano le opzioni (premio + VRP +
asimmetria coda). Comprare premio perde contro il VRP crypto; venderlo paga le
code grasse. Cappare la perdita su una strategia senza expectancy positiva limita
solo *quanto* perdi: **non esiste il pasto gratis "leva alta + perdite coperte".**
## 1. Overlay opzioni su PORT06 — non fattibile
Mismatch di orizzonte: l'edge di PORT06 è intraday (hold fade ~9h). Carry ATM 9h
**0.96%** del notional vs edge fade per-trade **0.10-0.30%** → costo 3-10× l'edge.
La coda di PORT06 è già piccola (DD ~5%) e market-neutral (pairs ~57% del rischio):
poco da assicurare. La copertura giusta era già lì (diversificazione + stop), gratis.
## 2. Strategie nuove a copertura opzioni
- **OH01 — direzionale (TSMOM) + opzione protettiva / sola opzione.** Frontiera
iso-rischio: il **perp NON coperto domina a ogni livello di rischio** (Sharpe 0.90
vs 0.33-0.57; CAGR +33% vs negativo). Comprare protezione su un trend perde per il
carry/VRP (il trend-following è "long vol" nel *payoff*, non comprando opzioni).
- **OH02 — spread di credito su mean-reversion (vendi premio = VRP a favore).**
La copertura funziona (perdita cappata, DD basso, win-rate 73-80%: la reversione è
reale). Ma **expectancy ~0/leggermente negativa**: il 27% di trade dove il movimento
*continua* (code grasse) costa ~5× ogni vincita. Un trend filter porta solo *singole
celle* a +1-2% (overfit: config diversa per asset). Non robusto.
## 3. V5 — Bull Call Spread / debit spread (stile Casario)
È **la migliore struttura long-premium**: rischio definito funziona (worstRoll 13%
vs 64% della call secca, DD 54% vs 94%). **Ma net-negativo in crypto** (BTC 2.2%
full / 13.5% OOS) e il perp non coperto lo batte. Sweep larghezza: spread più larghi
rendono di più → **cappare l'upside toglie le code grasse che pagano il premio**.
**Verdetto:** valido **sulle AZIONI** (vol/VRP bassi, uptrend puliti da screener), NON
in crypto. Casario ha ragione nel suo dominio (equity), non trasferibile ai perp crypto.
## 4. V4 — Box strategy (max/min giorno prima, supply/demand) → SKIP
Core tradabile = **fadare gli estremi del canale = MR02** (già live). La candela di
conferma (doji/hammer/rejection) = pattern di rigetto = rumore (vedi diario TA). Nessun
edge nuovo: costruirlo ri-deriverebbe solo MR02.
## Cosa servirebbe per un vero edge a opzioni (fuori scope attuale)
Non direzione né reversione (già coperte dai perp), ma un edge *specifico delle opzioni*:
dislocazioni della superficie IV/skew, o gestione attiva (chiusura al 50% del credito,
roll). Richiede storico prezzi opzioni reale (qui assente, prezzi sintetici da BS) e un
feed greche/IV che il `CerberoClient` oggi non espone.
@@ -0,0 +1,43 @@
# 2026-05-31 — 3 strategie TA "classiche": testate e SCARTATE (record anti-ripetizione)
> Record delle conclusioni. Codice testato e poi scartato (non conservato nel repo).
> Strategie da contenuti trading-guru: (1) SMA20/200 trend+pullback, (2) Opening Range
> Breakout "ironclad", (3) "Weakness rectangle" reversal (ICT). Testate con la
> metodologia onesta del progetto: ingresso eseguibile a `close[i]`, SL/TP intrabar,
> fee Deribit 0.10% RT, leva 3x, OOS(ultimo 30%), griglia robustezza, sweep fee.
## TL;DR — tutte e 3 NO EDGE (negative anche a fee ZERO)
Tutte e tre **direzionali/continuazione**, tutte negative su BTC/ETH, su tutta la
griglia, **anche a fee 0%** → il problema è il *segnale* (avg_R per-trade ≤ 0), non i
costi. Riconfermano la lezione centrale: *sui perp crypto i breakout/continuazione
rientrano; l'unico edge robusto è la mean-reversion.*
| Strategia | Tipo | avg_R @ fee0 | Motivo |
|-----------|------|--------------|--------|
| **SMA01** MA-pullback | continuazione | 0.15 BTC / 0.07 ETH | win ~30% (serve ~40% a R:R 2) |
| **ORB01** opening-range breakout | breakout | 0.10…−0.19 | crypto 24/7: manca l'asta d'apertura, ragione d'essere dell'ORB |
| **WR01** weakness rectangle | reversal→continuazione | ≈ 0.05/0.00 | R:R "5:1" illusorio (win cala in proporzione); le weakness vengono travolte |
> Verificato indipendentemente (reimplementazione minima SMA01): a fee 0 avg_R
> 0.15/0.07. Il 100% di CAGR è solo l'edge negativo composto a leva 3x su migliaia
> di trade, non un bug.
## Tentativo di MIGLIORAMENTO — ribaltarle sul lato fade
Miglioramento *di principio* (non tuning): visto che perdono perché sono continuazione,
ribaltate sul **fade** (l'unico lato con edge in crypto).
| versione fade | edge? (avg_R@fee0) | verdetto |
|---------------|--------------------|----------|
| **SMA02** fade dell'estensione→SMA20 | **+** (0.04…0.36) | = **MR01 inferiore** (FULL 1h negativo, Sharpe 0.4-0.9 vs 2.7+) |
| **ORB02** fade del breakout del range | **+** (win 35%→50-66%) | = **MR02/MR07** senza controlli di rischio (DD 90-100%) |
| **WR02** weakness come reversione | **≈0** | **rumore**, non una fade-family nascosta |
- Il flip restituisce segno positivo a 2/3 (riconferma *fade > continuazione*) **ma nulla
di additivo**: SMA02/ORB02 sono ri-scoperte inferiori di strategie già live; WR02 è rumore.
- **Ipotesi "SMA200 piatta = meglio fadare" SMENTITA**: il regime *range* non batte il fade
semplice; semmai il regime *trend* dà avg_R migliore ma con time-in-market 0.5-9%.
## Lezione metodologica
La prova del nove è l'**avg_R a fee 0**: se una strategia perde anche senza costi, il
problema è il segnale e nessun tuning la salva. Le strategie che funzionano restano
MR01/MR02/MR07 (fade) + PR01 (pairs) + PORT06 — l'edge è mean-reversion + diversificazione.
@@ -0,0 +1,81 @@
# 2026-06-01 — Bugfix: SH01 usciva a 3 barre invece di H=12 (exit a orizzonte)
> Diagnosi partita da un check sulla debolezza apparente di **SH01_BTC** nel paper
> trading live PORT06 (accuratezza 33,3% su 3 trade). Non era sfortuna statistica:
> era un bug di exit nello `StrategyWorker`.
## Sintomo
Nel live PORT06 (Docker `pythagoras-portfolio`), SH01_BTC mostrava 3 trade tutti
`long`, **tutti chiusi con `reason: "hold_limit"` a `bars_held: 3`**, con `tp: null
sl: null`:
| # | entry | exit | bars | net % | esito |
|---|-------|------|------|------:|:---:|
| 1 | 73529.5 | 73433.0 | 3 | 0,46% | ❌ |
| 2 | 73759.5 | 73839.5 | 3 | +0,02% | ✅ |
| 3 | 73811.5 | 73766.0 | 3 | 0,32% | ❌ |
`oos_signal_precision` nei log di TRAIN scendeva 55,6% → 50,0% → 43,3%.
## Causa
SH01 (`scripts/strategies/SH01_shape_ml.py`, config **W24 H12 th0.58**) è una
strategia **horizon-only**: predice il segno del rendimento a **H=12 barre** ed esce a
H barre. I suoi Signal portano `metadata={"max_bars": H}` (=12) e **nessun TP/SL**.
Nello `StrategyWorker.tick()` la logica di uscita era:
```python
if self.tp and self.sl: # SH01: False (tp=sl=0)
... usa self.max_bars ... # -> max_bars=12 consultato SOLO qui
elif self.bars_held >= self.hold_bars: # fallback legacy hold_bars=3
self._close_position(..., "hold_limit") # SH01 finiva QUI
```
`self.max_bars` (=12, settato correttamente in `_open_position`) era onorato **solo
dentro il ramo `tp and sl`**. Senza TP/SL, SH01 cadeva sul fallback `hold_bars=3` e
chiudeva a 3 barre. L'edge di SH01 — per CLAUDE.md è nell'**asimmetria sull'orizzonte
H, non nella frequenza** (win-rate ~50%) — non aveva tempo di realizzarsi: tagliato a
3/12, degenera in rumore.
Solo SH01 (BTC+ETH) era colpito: tutte le fade (MR01/MR02/MR07, DIP01) portano
tp+sl+max_bars e usano il ramo intrabar corretto.
## Fix
`src/live/strategy_worker.py`: aggiunto un ramo per l'exit a orizzonte puro, prima del
fallback `hold_bars`:
```python
elif self.max_bars:
# Exit puro a orizzonte (strategie senza TP/SL, es. SH01 shape-ML H=12):
# onora max_bars dalla metadata del Signal, non il fallback hold_bars=3.
if self.bars_held >= self.max_bars:
self._close_position(current_price, "time_limit")
```
Le fade restano invariate (entrano nel ramo `tp and sl`).
## Verifica
- Nuovo test `tests/portfolio/test_horizon_exit.py` (2 casi): con `max_bars=12` resta
in posizione a 3 barre; esce a 12 con `reason: "time_limit"` e `bars_held: 12`.
- Suite completa: **43 passed**.
- Container riavviato: **tutti i 17 sleeve RESUME puliti**, inclusa una posizione
SH01_ETH short aperta che ora seguirà l'exit a 12 barre.
## Atteso d'ora in poi
I trade SH01 nei log mostreranno `reason: "time_limit"` con `bars_held: 12` invece di
`hold_limit / 3`. Il 33% di accuratezza era un artefatto dell'exit prematuro; ora la
strategia gira sull'orizzonte su cui è validata (BTC OOS Sharpe 2,72, expanding).
Resta comunque un **diversificatore** del MASTER, non un motore di ritorno standalone.
## Lezione
Il backtest di SH01 (`fade_base`/engine onesto) esce a H barre via `max_bars`; il
worker live deve replicarlo. Quando una strategia non porta TP/SL ma solo un
orizzonte, il fallback `hold_bars` del worker la **falsa silenziosamente**. Verificare
sempre che la convenzione di exit del worker live coincida con quella del backtest
validato — non solo l'ingresso.
@@ -0,0 +1,71 @@
# 2026-06-01 — SH01 live eseguiva la strategia SBAGLIATA (squeeze scartato), non shape-ML
> Scoperto verificando perché SH01 continuava a chiudere a `hold_limit/3` **anche dopo**
> il rebuild col fix horizon-exit. Il fix era corretto ma in un **ramo morto**: SH01 live
> non passava da `StrategyWorker.tick()`.
## Sintomo
Dopo il deploy del fix SH01 (exit a H=12), un close SH01_BTC delle 12:00 era ancora
`reason=hold_limit bars=3` (perdita 1,27%). Il fix non aveva effetto sul path reale.
## Causa (bug di wiring, più grave del previsto)
`src/portfolio/runner.py` importava `MLWorkerWrapper` da **`src/live/multi_runner.py`** e
ci avvolgeva lo sleeve SH01:
```python
if spec.kind == "ml":
return MLWorkerWrapper(worker, {"retrain_hours": 24})
```
Ma quel wrapper è **legacy, per la famiglia squeeze ML01** (scartata, vedi CLAUDE.md):
- usa `SignalEngine` = squeeze-detection + GradientBoosting (NON SH01_shape_ml);
- ha una `tick()` propria che apre con un `Signal` **nudo** (niente tp/sl/max_bars) ed
esce con `if bars_held >= hold_bars: close("hold_limit")` → ignora del tutto la
strategia caricata e il fix horizon.
Quindi lo sleeve "SH01" del portafoglio live **non eseguiva shape-ML**: eseguiva il
motore squeeze scartato. I log `TRAIN OK / oos_signal_precision` venivano da lì. Il
`worker` con strategy=SH01_shape_ml era costruito ma la sua `generate_signals` non
veniva **mai** chiamata.
## Fix
SH01 (`kind="ml"`) ora gira come **StrategyWorker normale**: `SH01_shape_ml.generate_signals`
fa il walk-forward (retraining) **internamente** ad ogni tick (`ml_wf_entries`) ed emette
`metadata.max_bars=H=12` → gli exit passano per `StrategyWorker.tick()` e il fix horizon
si applica davvero.
```python
# runner.py: niente più MLWorkerWrapper per kind="ml"
return StrategyWorker(strategy=strategy, asset=spec.asset, tf=spec.tf, ...)
```
**Lookback dati.** `ml_wf_entries` ha `train_min=4000` → servono ≥4000 barre 1h prima di
produrre segnali (con 90g/2160 barre → 0 segnali, runtime 0.01s — il falso "muto"). Le
candele 1h di BTC/ETH già arrivano a 440g (le richiede TSM01/ROT02 a 1d), ma per non
dipendere da quella coincidenza ho aggiunto `_ML_LOOKBACK_DAYS=365`: gli asset usati da
sleeve ml fetchano ≥365g (~8760 barre). Costo `generate_signals` su 365g: **0,170,24s**
(modello logit) → trascurabile sul poll 60s.
**Verifica.** Build SH01 → `StrategyWorker` con `strategy.name=="SH01_shape_ml"`, niente
attributo `engine` (regression test `test_build_ml_sh01_is_plain_strategyworker`). Smoke
su 365g: 7661786 segnali, tutti `max_bars=12`; tick live 0,17s. `ml_wf_entries` non
predice mai l'ultima barra (`n-1`) ma fino a `n-2` = esattamente la condizione di apertura
del worker (`idx >= last_idx-1`) → apre quando il segnale è fresco. Suite: 51 passed.
**Stato live.** SH01 BTC/ETH erano flat: contatori resettati a 0 (capitale preservato
58,76/58,78), vecchi trade squeeze archiviati in `trades_squeeze_archive.jsonl`. Rebuild
+ recreate: 14 worker RESUME puliti, container healthy, nessun log TRAIN/squeeze, zero
errori.
## Lezione
1. **Verificare il path REALE, non solo il codice del fix.** Il fix horizon era giusto ma
SH01 non lo attraversava. Un fix non testato end-to-end sul percorso vivo è un fix
presunto. (Mi ero fidato del rebuild senza confermare il reason dei close SH01.)
2. Riusare un wrapper legacy "perché c'è" è un rischio: `MLWorkerWrapper` di multi_runner
era per la famiglia squeeze scartata, non per shape-ML.
3. Un modello ML "muto" può essere solo **fame di dati** (train_min), non un bug logico:
controllare sempre la dimensione della finestra prima di concludere.
+89
View File
@@ -0,0 +1,89 @@
# 2026-06-01 — "Win" che perdono: metrica netto-fee + filtro TP edge-minimo
> Partito da un'osservazione dell'utente sui trade live PORT06: **ci sono close con
> `win=True` ma `pnl` negativo**. Due problemi distinti, entrambi risolti.
## Problema 1 — la metrica `win` mentiva (lordo invece di netto)
In `strategy_worker.py::_close_position`:
```python
trade_return = price_change * direction # LORDO, prima delle fee
net = trade_return * leverage - fee_rt * leverage
pnl = capital * position_size * net # corretto (netto)
is_win = trade_return > 0 # BUG: usa il LORDO
```
`is_win` scattava appena il prezzo si muoveva di un soffio a favore, **prima delle
fee**. Capitale e PnL erano giusti (netti); solo la metrica `win`/`accuracy` era
gonfiata.
**Quantificazione (51 close live):** 39 win dichiarate (76,5%) → **13 falsi win**
(`win=True` ma `pnl≤0`) → accuratezza **netta reale 52,9%**. PnL realizzato +€0,77
(resta positivo: lo trascinano i pairs).
**Fix:** `is_win = net > 0`. + `tests/portfolio/test_win_net_of_fees.py` (mossa
sotto-fee = non win; oltre-fee = win; perdita = non win).
**Riconciliazione contatori persistiti:** i `total_wins` su disco erano gonfiati dal
vecchio conteggio lordo. Ricalcolati come `net_return>0` dai `trades.jsonl`:
**MR01_BTC 7→1, DIP01_BTC 7→1** (gli unici toccati; tutti gli altri già coerenti).
Capitale invariato.
## Problema 2 — i 13 falsi win erano tutti MR01_BTC / DIP01_BTC in take_profit
Causa: in `MR01_bollinger_fade` e `DIP01_dip_buy` il **TP è la media** (`tp = ma[i]`)
e l'entry è a `close[i]` appena fuori banda. Nel regime BTC **piatto** (inchiodato
~73.700 per ore, vol bassissima) la media è a pochi dollari dall'entry → il TP cade
**dentro** il costo round-trip (0,10%): colpire il TP = perdita netta garantita.
**Meccanismo del fix (importante).** "Spostare il TP più in là" NON garantisce di non
perdere: il prezzo rientra solo fino alla media, non oltre → si finirebbe su SL/time-
limit, perdendo di più. La mossa provabilmente non-perdente è un **filtro di edge
minimo**: se `|tp entry|/entry ≤ min_tp_frac` non si apre la trade. Break-even
esatto = `fee_rt` (= 0,10%, indipendente dalla leva, perché
`ret = mossa·lev fee_rt·lev > 0 ⇔ mossa > fee_rt`).
**Implementazione:** parametro `min_tp_frac` (default 0.0 = off) in **tutte le 4 fade**
(MR01 banda, MR02 midpoint canale, MR07 ATR-scaled) e DIP01; salta i segnali sotto
soglia. Cablato negli sleeve live a **0.0015 (1,5× fee)** in `_defs.py` (`MIN_TP_FRAC`).
**Validazione backtest (BTC+ETH 1h, config sleeve, min_tp_frac ∈ {0,.001,.0015,.002}):**
neutro su tutte e 4 le fade.
- MR01: 0 trade rimossi (BTC +8028€, ETH +10395€) — metriche identiche.
- DIP01 BTC: 1 trade a 0.002, **migliora** (+7492→+7522€, DD 26,3→25,9%).
- MR02 BTC: 1 trade a 0.0015 (pnl invariato +12198€), ETH 0 rimossi.
- MR07 BTC/ETH: 0 rimossi (TP ATR-scaled sempre ben oltre le fee nello storico).
Conclusione: i micro-scalp sotto-fee **non esistono nel campione storico** — sono un
artefatto del regime attuale. Il filtro è **puro upside**: neutro sul backtest validato,
protettivo dal vivo. (Le 12 trade live incriminate, tutte MR01/DIP01 BTC, avevano gap
~0,026%, ben sotto 0,15% → tutte bloccate.)
+ `tests/portfolio/test_min_tp_frac.py` (monotonia + ogni superstite ha gap > soglia
+ default-off invariato).
## Nota deploy — il codice è COTTO nell'immagine, non montato
Scoperta durante il deploy: `docker-compose.yml` monta solo `./data` e
`./portfolios.yml`; il sorgente (`src/`, `scripts/`) è `COPY` nel Dockerfile. Quindi
**`docker compose restart` NON ricarica le modifiche al codice** — serve
`docker compose up -d --build`. Conseguenza retroattiva: anche il fix SH01
horizon-exit di stamattina è andato live solo con questo rebuild. Da ricordare per ogni
futura modifica ai worker. Il volume `./data` persiste → i 14 worker fanno RESUME
puliti dopo il rebuild (capitale e posizioni intatti).
## Stato finale
- `is_win = net > 0` live; contatori riconciliati (MR01/DIP01 BTC 1/9).
- Filtro `min_tp_frac=0.0015` live su tutti i fade + DIP01 (attivo solo MR01/DIP01).
- Fix SH01 horizon-exit ora **effettivamente** live (rebuild).
- Suite: 49 passed. Container ricostruito, healthy, 14 sleeve in RESUME.
## Lezione
1. Una metrica di "win" deve essere **netto fee**, altrimenti l'accuracy è teatro.
2. Quando il TP è dentro il costo di transazione, la trade è persa in partenza: meglio
**non prenderla** che ritoccare il TP.
3. Per i worker live in Docker: **rebuild**, non restart. Il restart ricarica solo lo
stato dal volume, non il codice.
+67
View File
@@ -0,0 +1,67 @@
# 2026-06-02 — Loss-guard per le fade: filtro Hurst (regime persistente)
> Goal: limitare le perdite delle fade in "bassa vol". Diagnosi empirica + ricerca web + workflow
> 11 agenti + test decisivo a livello PORT06. Branch `feat/fade-lossguard`.
## Riformulazione del problema (la premessa era imprecisa)
Diagnosi su 3022 trade fade (MR01/MR02/MR07 × BTC/ETH, 2021+): **le perdite NON si concentrano in
bassa vol** — anzi il terzile low-DVOL è net positivo (+2,30%/trade). Il vero driver è il **regime
PERSISTENTE/trending**, misurato dall'Hurst:
- somma perdite peggiore: **hurst>0,55** (2695% in low-vol, dominante in ogni terzile vol)
- **stop-rate 43% per hurst>0,55 vs 21% per hurst<0,45** (anti-persistente) — 2x
- peggiori 1% trade: Hurst medio 0,61 (77% con hurst>0,55, solo 13% in bassa-DVOL)
## Ricerca web (confermata e smentita dai dati reali)
- **Hurst regime filter** (MR solo H<0,45, evitare H>0,55): **CONFERMATO** sui dati reali. ✅
- **ADX** (PF 1,62 sotto 20 vs 0,74 sopra 30): **NON si replica** — ADX-skip uccide l'edge
(Sharpe 4,82→0,99) e lo stop-rate non scende. ❌
- **vol-expansion ATR-ratio>1,5 (72% perdite)**: **NON si replica** — alza DD e stop-rate. ❌
- **time-stop ~15 barre**: riduce stop-rate ma alza il DD full → non passa standalone. ❌
## Workflow 11 agenti — meccanismi testati
| Meccanismo | OOS Sharpe (base→filt) | DD full | Buon loss-guard? |
|---|---|---|---|
| **Hurst-SKIP h<0,55** | 4,82→4,96 ↑ | 24,3→13,8% ↓ | **SÌ** |
| **Hurst-SIZE 1/0,5/0,25** | 4,65→5,32 ↑ (full) | 33,6→11,3% maxDD ↓ | **SÌ** |
| ADX-skip | 4,82→0,99 ✗ | — | NO (uccide edge) |
| vol-expansion vratio | 4,82→4,04 | 24,3→27,5% ✗ | NO |
| Kaufman ER, time-stop, vol-target, DVOL-rising, combo | tutti ↓ o DD↑ | — | NO |
**Solo l'Hurst** isola chirurgicamente il regime tossico; gli altri sono "dimmer uniformi" che
tagliano winner insieme ai loser (gate FR01 fallito).
## TEST DECISIVO a livello PORT06 — SUPERATO ✅
Applicato l'Hurst-skip alle 6 fade dentro il PORT06 intero (equal-weight, le altre 11 sleeve
invariate):
| Portafoglio | FULL Sharpe | FULL DD | OOS Sharpe | OOS DD | OOS ret |
|-------------|:--:|:--:|:--:|:--:|:--:|
| PORT06 baseline | 6,62 | 4,10% | 8,89 | 1,22% | +175% |
| **+ Hurst-skip h<0,55** | **6,76** | **2,39%** | **9,15** | 1,54% | +158% |
| + Hurst-skip h<0,50 | 6,61 | 2,08% | 9,02 | 1,54% | +150% |
**A differenza di FR01 (che diluiva), il filtro Hurst MIGLIORA il PORT06**: FULL Sharpe ↑, **FULL
DD quasi dimezzato (4,10→2,39%)**, OOS Sharpe ↑ (8,89→9,15). Costo: OOS DD +0,3pp (resta minuscolo),
OOS ret 17pp. **h<0,55 è il pick** (0,50 taglia più ritorno). Non aumenta il profitto: è puro
**rischio** — dimezza il DD mantenendo/alzando lo Sharpe.
## Implementazione
Aggiunto `hurst_skip_mask` in `src/strategies/fade_base.py` (rolling-Hurst causale dalle SOLE close)
+ parametro `hurst_max` (default None=off) in MR01/MR02/MR07. Test: `test_hurst_lossguard.py`.
**Vantaggio operativo decisivo vs FR01:** l'Hurst si calcola **dalle sole close** → nessun feed
DVOL/regime live necessario. Lo `StrategyWorker` lo computa inline dai dati che già ha → **deployabile
senza nuova infrastruttura**, basta settare `hurst_max: 0.55` nei params degli sleeve fade.
## Da fare per attivarlo live (deploy)
1. Settare `hurst_max: 0.55` nei params delle fade in `_defs.py` (sleeve live) + aggiornare i params
fade del backtest (`combine_portfolio`/`report_families`) per PARITÀ + rigenerare il
regression-lock PORT06 (i numeri canonici cambiano: DD 4,9→~2,4%).
2. Verificare che il rolling-Hurst live nel worker coincida col backtest (stessa finestra 100,
stesso stepping causale).
3. Rebuild immagine Docker (`up -d --build`, non restart) + verifica RESUME.
Default attuale: `hurst_max` OFF → zero impatto su backtest/parità/live finché non lo si attiva
esplicitamente. Il SISTEMA è trovato e validato; l'attivazione è una decisione di deploy.
@@ -0,0 +1,89 @@
# 2026-06-02 — Ricerca a 100 agenti: Frattali del segnale × Regime ARGO
> Workflow multi-agente (171 agenti, 8,4M token, ~7h) per cercare una strategia che combini
> un SEGNALE FRATTALE con un GATE/INTERAZIONE DI REGIME ARGO (DVOL/funding/VRP), validata OOS.
> Branch: `feat/fractal-argo-search`. Substrato e codice sul branch, niente impatto su main/live.
## Substrato costruito
- `regime_fetcher.py`: DVOL (2021-03→oggi) + funding (2019→oggi) BTC/ETH da **Deribit mainnet
public** (no-auth, OI/IV reali — non il testnet farlocco di Cerbero).
- `regime_lab.py`: allineamento regime↔prezzo **causale no-look-ahead** (merge_asof backward),
feature regime (dvol_pct, **vrp=dvolrv**, funding_z, dvol_chg) + frattali (rolling Hurst,
Higuchi FD, vol-ratio, Williams pivot), cache feature precalcolate, validazione netto-fee OOS
via `explore_lab`. Bug corretto in corsa: `vrp` annualizzava la realized-vol sempre come 1h →
rotta su 4h/1d (sempre negativa); fix per timeframe.
- `fractal_argo_workflow.js`: 84 agenti griglia (7 famiglie frattali × 6 angoli regime × BTC/ETH)
+ 8 wildcard + verifica avversariale dei survivor + sintesi.
## Verdetto
**Esistono edge frattale×regime reali, causali, robusti** (15 confermati dalla batteria
avversariale: audit look-ahead bit-esatto, cross-asset, split alternativo, fee 0.2% RT, plateau).
**MA nessuno batte PORT06 standalone** (OOS Sharpe 8,19 / DD 2,3%): sono **diversificatori a
bassa esposizione** (1,5-8%, ~100-460 trade), profilo SH01/pairs.
### Top candidati confermati
| Strategia | Asset/TF | OOS Sharpe | OOS DD | trade |
|-----------|----------|:--:|:--:|:--:|
| FRAC-VRP multiscala (chop × VRP<0) | ETH 1h | 5,55 | 11% | 184 |
| HigVRP-Fade (Higuchi alto × VRP<0) | BTC 1h | 4,55 | 7,2% | 286 |
| **HurstCalmFade (hurst<0,55 × dvol<0,4)** | BTC 1h | **3,73** | 5,1% | 198 |
| WILD8 Pivot-Hurst | BTC 4h | 3,87 | 10% | 482 |
| AnalogFundingFade (kNN forma × funding) | ETH 4h | 2,15 | 9,3% | 229 |
## Il finding chiave (controintuitivo): il prior ARGO è SMENTITO
La tesi naïve **"VRP>0 = GEX+ = range = fade" FALLISCE** sistematicamente. L'edge robusto e
ripetuto è su **VRP<0** (vol implicita *sottoprezzata* vs realizzata → mean-reversion whippy) e su
**DVOL bassa**, l'opposto del brief. Gate invertito VRP>0 → Sharpe 2,08/1,30 su entrambi gli
asset. È il risultato più solido di tutta la ricerca, look-ahead-clean (lag-1/3/6 robusto).
## Cosa aggiunge valore vs cosa è decorativo
- **Load-bearing (confermato per ablazione):** VRP<0; hurst-low × dvol-low (HurstCalmFade taglia
DD OOS 17%→5%); funding_z estremo |fz|≥1,8 (analog ETH 4h).
- **Decorativo (DD-reducer, non interazione):** dvol_chg, dvol_high/low e funding come gate spesso
riducono solo esposizione senza migliorare il segno.
## Cosa è RUMORE (conferma i priori del progetto)
- Frattali da soli (angolo=none): hurst/Higuchi/vratio/chop/candle/analog intraday → non robusti
(DD 30-90%, muoiono di fee). Conferma `shape_lab`.
- Momentum/breakout gateato (hurst>0,6, dvol-rising): catastrofico (Sharpe 2…−7) → riconferma
dominanza mean-reversion, i breakout rientrano.
- ARGO-GEX nella direzione attesa (VRP>0): perde. Coerente con W18/19/21 scartate.
- Pivot-fade non-laggato (frac_up[i]): artefatto squeeze-like, va sempre laggato a i2.
## Vincitore selezionato + test decisivo
**FR01 HurstCalmFade BTC 1h** (`scripts/strategies/FR01_hurst_calm_fade.py`): il più verificato,
DD più basso (5,1% OOS), generalizza a ETH. **Test di correlazione decisivo** (la domanda che
conta: aggiungerlo migliora il PORT06 o è ridondante?): correlazione daily-returns coi fade
esistenti **MR01 +0,17 / MR02 +0,08 / MR07 0,03****BASSA, quasi-ortogonale**, NON ridondante.
Passa il gate → vale l'inserimento come diversificatore.
## Onestà finale
L'edge frattale×regime è **reale, causale, robusto** ma è sempre **mean-reversion già nota
condizionata dal regime (VRP<0 / hurst-low / dvol-low)**, non un motore ortogonale nuovo. Valore =
**riduzione DD aggregato come sleeve a bassa esposizione**. La correlazione bassa lo qualifica come
diversificatore reale.
## TEST DECISIVO SUL MASTER — VERDETTO FINALE: NON deployare
Misurato il contributo marginale di FR01 al PORT06 intero (equal-weight, `master_corr`):
| Portafoglio | FULL Sharpe | OOS Sharpe | OOS DD | OOS ret |
|-------------|:--:|:--:|:--:|:--:|
| PORT06 (17 sleeve) | 6,62 | **8,89** | 1,2% | +175% |
| PORT06 + FR01 (19) | 6,55 | **8,72** | 1,1% | +156% |
**FR01 NON migliora il PORT06: lo DILUISCE** (OOS Sharpe 8,89→8,72, OOS ret +175%→+156%; DD
marginalmente meglio 1,2→1,1% ma a costo di Sharpe). Corr FR01 vs MASTER +0,18 (BTC)/+0,23 (ETH).
**Causa + nota di onestà metrica:** lo Sharpe "3,73" dei report del workflow è **per-trade/annuale**
(`explore_lab`); quello rilevante per il portafoglio è lo **Sharpe daily-return** (`combine`), che per
FR01 è solo **~1,85/1,53** — troppo basso per muovere un PORT06 a 8,89. È "ridondanza robusta":
mean-reversion regime-gated che si sovrappone a ciò che il MASTER già fa.
**ESITO: il search a 100 agenti ha trovato strategie robuste e causali, ma NESSUNA migliora il
PORT06.** Non deployare FR01 né i candidati gemelli. Valore del progetto resta nell'estendere
fade/pairs validati. Tutto resta come RECORD DI RICERCA sul branch (non si merge in produzione).
Wiring DVOL live e walk-forward: non necessari, deploy abbandonato.
+176
View File
@@ -0,0 +1,176 @@
# 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_bars` → **il 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.
@@ -0,0 +1,63 @@
# 2026-06-04 — Exit ladder (80% del TP → esci 80%, runner con stop dinamico): SCARTATO
## La proposta
"E se all'80% del TP usciamo con l'80% della posizione, mettiamo uno SL dinamico
su quella soglia (profitto lockato) e lasciamo correre il 20% restante?"
Idea: uscita scalare — bancare prima la maggior parte del profitto (la soglia
all'80% viene toccata più spesso del TP pieno) e tenere un runner free-ride con
worst-case alla soglia, best-case oltre il TP.
## Il test (onesto, stessi segnali)
`scripts/analysis/partial_tp_ladder.py`: replay intrabar degli STESSI segnali
delle 3 fade (params live: `trend_max=3.0, ema_long=200, hurst_max=0.55,
min_tp_frac=0.0015`), engine identico a `fade_base` (ingresso a `close[i]`,
SL pieno prioritario nello stesso bar, fee 0.10% RT × leva 3 — il ladder NON
paga fee extra: due uscite ma stesso notional). Convenzioni conservative: se il
bar che tocca la soglia chiude oltre (rientro), il runner è stoppato subito.
Griglia: base vs ladder (f=0.8 q=0.8 — la proposta —, f=0.8 q=0.5, f=0.5 q=0.5),
FULL + OOS (nov 2023→), BTC+ETH.
## Risultati (proposta f0.8 q0.8)
| Sleeve | ret% FULL base→ladder | DD% | ret% OOS | win% OOS |
|---|---|---|---|---|
| MR01 BTC | 92 → 92 | 13.8 → 10.0 | 41 → 39 | 60 → 65 |
| MR01 ETH | 194 → 195 | 16.5 → 15.3 | 134 → 147 | 70 → 75 |
| MR02 BTC | 129 → 112 | 19.0 → 17.2 | 66 → 54 | 55 → 59 |
| **MR02 ETH** | **2135 → 1319** | 16.2 → 12.7 | **893 → 651** | 75 → 80 |
| MR07 BTC | 78 → 78 | 6.8 → 5.2 | 43 → 40 | 64 → 66 |
| MR07 ETH | 115 → 96 | 10.6 → 10.6 | 68 → 64 | 66 → 70 |
## Verdetto: NO-GO (gate fallito)
1. **Il runner non corre.** Su centinaia di trade finisce oltre il TP quasi mai
(~3% dei partial; 0-15 casi per sleeve) e viene **stoppato alla soglia nel
~60% dei tocchi**. È la fisica mean-reversion: il TP delle fade sta ALLA
MEDIA — lì il movimento è esaurito per costruzione. Il runner è un
trend-follow della coda, e i breakout rientrano (lezione squeeze).
2. **Il costo è concentrato sui winner migliori**: ogni TP pieno ora esce
all'80% della strada sulla quota grossa → MR02 ETH (lo sleeve più forte)
38% ret FULL / 27% OOS. Sharpe per-trade ~piatto: non migliora il
rischio/rendimento, scambia ritorno con DD.
3. **Quello che compra** (win-rate +4-5pp, DD 1-4pp) è lo stesso profilo del
**vol-target sizing già scartato** ("abbassa il DD ma sacrifica ritorno");
il lavoro anti-DD lo fa già il loss-guard Hurst, che il gate l'ha passato
perché taglia SOLO i loser.
Nota dal grid: `f0.5 q0.5` su MR07 ETH fa Sharpe OOS 6.58 / win 82%, ma
distrugge MR02 (2135→531) → nessuna variante robusta su tutti gli sleeve,
niente cherry-picking per-sleeve.
Argomento esecutivo in più: il ladder raddoppierebbe la complessità dello
shadow appena deployato (v1.0.7: limit reduce-only al TP) — due resting order
per posizione, due riconciliazioni, e lo stop dinamico avrebbe il problema dei
trigger Deribit (nuovo order_id al trigger → fill non verificabile per
order_id).
**Conferma della lezione di famiglia:** i tweak d'exit che toccano i winner
falliscono il gate (time-stop, vol-target, ADX, vratio… e ora il ladder);
l'unico anti-perdite che passa resta il loss-guard Hurst, che riduce
l'esposizione nel regime tossico senza toccare le uscite.
@@ -0,0 +1,92 @@
# 2026-06-04 — Shadow execution: divergenza sim/reale misurata + TODO limit reduce-only
## Deploy v1.0.6
Fix float deployata (`74670da` + release): `_quantize_step` con `Decimal` in
`src/live/execution.py``72*0.001 = 0.07200000000000001` causava `Invalid params`
su Deribit (1 `REAL_OPEN_FAIL` su MR07 il 2026-06-03). Resume pulito al restart.
## Misura della divergenza sim vs reale (primi 7 close shadow)
**Bilancio: PnL sim +11.85 USD vs reale +0.62 USD → delta 11.22 USD.**
Le fee NON sono il problema ($0.63 totali = 0.05%/lato, esattamente l'assunto del
backtest). Il problema è tutto nel **timing delle uscite**.
- **Aperture allineate**: slippage da 8 a +27 bps, mediana ~±7 bps. OK.
- **Chiusure take-profit: +235 bps di slippage medio** (MR07 ETH short: +257/+267/+283/+333 bps).
Causa strutturale, non liquidità: il sim esce *al livello TP* intrabar (come il
backtest), il reale chiude **a mercato solo al poll del tick**, quando il prezzo è
già rientrato (TP toccato a ~1790, fill reale a ~1840). Sim +3 USD a trade, reale ~0.
- **Stop-loss: stessa meccanica ma a favore** (MR02 SL: 0.68 reale vs 2.87 sim).
Le fade però hanno più TP che SL → netto negativo.
- **2 `REAL_CLOSE` non verificate** (order_id null, fallback su stima sim). Venue flat
(`get_positions()=[]`), nessuna posizione orfana — ma sorvegliare i prossimi close.
**Implicazione onesta: l'edge di MR07 non sopravvive all'esecuzione market-on-poll.**
Il backtest assume il fill al livello TP; il reale incassa il prezzo post-rimbalzo.
## TODO (richiesto, da implementare)
Nello shadow-execution (`src/live/execution.py` + `StrategyWorker`): all'apertura
della posizione reale piazzare su Deribit un **LIMIT reduce-only appoggiato al
livello TP** (e idealmente stop-market per lo SL), invece di chiudere a mercato al
poll — così il fill reale replica l'assunzione intrabar del backtest.
Dettagli da considerare:
- cancellare/aggiornare l'ordine resting se il sim esce per `max_bars` o al restart (resume);
- i 3 fade BTC condividono lo strumento (posizioni nettate per conto): il limit
reduce-only deve coprire la SOLA quota del worker, come già fa `close_amount`;
- verificare il fill del resting order via `get_trade_history` (order_id), come per i market.
## IMPLEMENTATO (sera): LIMIT reduce-only al TP
**Flusso nuovo.** A ogni `REAL_OPEN` verificato il worker piazza subito un
**limit reduce-only al livello TP** (lato opposto, stesso `amount` della SOLA
quota del worker — strumento condiviso fra i 3 fade per coin) → evento
`REAL_TP_RESTING` (o `REAL_TP_FAIL` → si resta sul vecchio market-on-poll).
Alla chiusura sim, qualunque sia la ragione, `_real_close` ora:
1. **cancella** il resting (innocuo se già fillato — e dopo il cancel nessun
fill può più arrivare, niente race);
2. **riconcilia i fill** (anche parziali) dal `get_trade_history` per order_id
(stessa fonte autorevole dei market), con retry se il cancel risulta
"già fillato" ma la history è in ritardo;
3. chiude a **market reduce-only solo la quota residua** (exit non-TP:
stop_loss/time_limit, o TP non raggiunto sullo strumento d'esecuzione);
4. ledger reale su **prezzo d'uscita combinato** (media pesata fill-TP +
market) e fee sommate. `REAL_CLOSE` logga `tp_order_id`,
`tp_filled_amount`, `market_amount`.
**Dettagli del TODO coperti:**
- `max_bars`/`stop_loss` → il cancel è il passo 1 di ogni chiusura;
- restart → `real_tp_order_id` è **persistito** in `status.json`: al resume il
resting resta in book e la prima chiusura lo riconcilia normalmente;
- quota del worker → il limit usa lo stesso `amount` dell'apertura (mai
`close_position`); il residuo è ri-quantizzato allo step (Decimal) dentro
`close_amount` (niente artefatti float tipo `0.072-0.024`);
- prezzi limit quantizzati al **tick** dello strumento (`quantize_price`,
BTC_USDC 0.5 / ETH_USDC 0.05 — Deribit rifiuta prezzi off-tick).
**SL: niente stop-market, decisione deliberata.** (a) Su Deribit un trigger
order, quando scatta, genera l'ordine eseguito con un **nuovo order_id** → il
fill non sarebbe verificabile via `get_trade_history` per order_id (il
requisito di verifica); (b) la misura di stamattina mostra che sul SL il
market-on-poll lavora **a favore** (il rimbalzo riduce la perdita). Lo SL resta
quindi market-on-poll.
**Bonus fee.** Quando il TP filla da resting il fill è **maker** (~0% su
Deribit) invece di taker 0.05% — il reale farà meglio dell'assunto 0.10% RT
sulle uscite TP.
**Verifica.** `live_shadow_smoke.py` esteso ai due percorsi e passato su
testnet: (A) resting in book → exit `time_limit` → cancel + market fallback,
conto flat; (B) TP già crossato → fill immediato del limit, chiusura
riconciliata dalla history **senza ordine market** (`market_amount=0`),
prezzo persino migliore del TP (+48 bps, taker sul cross). 54 test pytest OK.
**Caveat residuo onesto.** Se il processo muore DOPO il fill del resting e
PRIMA della chiusura sim, il worker al resume riconcilia al prossimo exit sim
(corretto); ma nell'intervallo la quota reale è già chiusa mentre il sim la
crede aperta — finestra breve e solo contabile. Inoltre il TP del sim è sul
feed (`BTC-PERPETUAL` inverse) mentre il limit vive sul lineare USDC: la base
fra i due può far fillare/non fillare il limit a cavallo del livello — il
fallback market copre il caso.
@@ -0,0 +1,67 @@
# Diario — 2026-06-05 — Fix EXIT-16 live: confirm sul close di barra COMPLETATA + alert STALE_FEED
## Innesco
Crash ETH 2026-06-05: gli stop MR01/MR02 ETH sono scattati molto sotto il livello
nominale (MR02: sl 1636.28 → exit 1600.45; MR01: sl 1610.75 → exit 1594.35).
Analisi delle due concause dichiarate:
1. **EXIT-16 close-confirm** (by design): uscita al close oltre `sl ∓ 0.5·ATR14`,
non al livello — costo accettato e MODELLATO nel backtest validato (7.84/10.06).
2. **Feed gappato**: ETH testnet flat ~2h (1656.80/1655.55, 13:0014:50 UTC), poi
una candela 14:55 da 1634 a 1600 — nessun prezzo esisteva tra SL ed exit. I fill
REALI confermano (1600.2 / 1589.7, slippage 1.6/29 bps): non è un bug, è il mercato.
## Scoperta: divergenza implementativa REALE nel worker
Audit dei 3 stop ETH del 2026-06-04/05 contro le candele:
| Stop live | Close barra COMPLETATA al momento | Confirm | Verdetto |
|---|---|---|---|
| MR02 06:03 @1672.9 | 1682.25 (05:00) | ~1675.4 | **wick-stop**: il backtest NON avrebbe stoppato lì |
| MR02 15:00 @1600.45 | 1600.05 (14:00) | ~1621 | legittimo, fill ≈ backtest |
| MR01 15:01 @1594.35 | 1600.05 (14:00) | ~1594.4 | **wick-stop**: il backtest NON avrebbe stoppato lì |
**2 su 3 erano valutazioni sulla barra IN FORMAZIONE**: `tick` confrontava
`current_price` (close istantaneo della candela in corso al poll) con `sl buf`.
Il backtest (`fade_base`) valuta il confirm SOLO sul close di barra completata
`c[j]`. La differenza reintroduce live esattamente la wick-sensitivity che EXIT-16
elimina: un dip infrabarra sotto il confirm stoppava anche se la barra chiudeva
sopra (e nei dip-che-rientrano la fade vince — è il suo edge). Live sarebbe stato
strutturalmente PIÙ stop-prone del backtest validato.
## Fix (`strategy_worker.py`, ramo sl_confirm_atr)
- Il confirm è valutato sul **close dell'ultima barra COMPLETATA**: l'ultima riga
del df è la candela in corso finché non è trascorsa la sua durata
(`now_ms < ts[-1] + bar_ms`, `bar_ms` = mediana dei delta timestamp → regge 1h/4h/1d).
- Il `buf` usa l'ATR14 alla stessa barra completata (prima: barra in formazione).
- Il **fill resta al prezzo corrente** al poll (lag ≈ stress `lag_close_exit`,
superato in exit-lab). TP intrabar sulla barra in corso INVARIATO (= backtest).
- Test nuovi in `test_close_confirm_sl.py` (sezione FIX 2026-06-05): il dip della
candela in corso non stoppa; il breach della completata stoppa anche se rimbalzato;
TP intrabar invariato. NB fixture: timestamp in ms ESPLICITI
(`Timestamp.now()` è risoluzione µs → `astype int64 // 10**6` darebbe secondi).
## Concausa feed: entry-guard TESTATA e BOCCIATA, solo osservabilità
Quantificato (1h, storico completo): segnali fade subito dopo barre flat = BTC
0.20.9%, **ETH 3.411.8%**. Backtest con skip di quei segnali (config live,
sl_confirm 0.5): **PEGGIORA tutti gli sleeve ETH** (MR01 +1344→+1256, MR02
+2372→+2250, MR07 +740→+621; BTC ~invariato). La candela-gap post-flat è
l'overshoot che la fade fada con profitto — e i fill reali post-crash lo
confermano (riaperture a 1.9 bps di slippage). **Niente skip degli ingressi.**
Aggiunto invece **alert Telegram `STALE_FEED`** (`runner._check_stale_feed`):
notifica quando un asset ha ≥2 barre 1h complete flat (worker ciechi) e al
risveglio col **gap %** del primo prezzo reale. Una notifica per episodio
(dedup per asset). Solo osservabilità, zero effetto sulle decisioni.
## Cosa NON è stato toccato (e perché)
- Il **costo del confirm** (uscita al close, non al livello nominale) resta: è il
trade-off validato di EXIT-16 (FULL 6.47→7.84, OOS 8.82→10.06) — i wick-stop
evitati pagano molto più dei close-stop più profondi.
- Il **gap-through** non è mitigabile lato exit: nessun prezzo esisteva tra SL ed
exit (fill reali ≈ sim). L'unica protezione strutturale resta la diversificazione
(17 sleeve) + i cap di famiglia.
+99
View File
@@ -0,0 +1,99 @@
# Diario — 2026-06-05 — SL su SH01: ricerca multi-agente (verdetto: NO-SL, cap del peso)
## Innesco
Crash ETH del 2026-06-05 (1742→1546, 11%, con feed testnet FLAT ~2h e gap 1655→1600):
SH01 ETH si è preso la coda intera, **15.6% in un trade** (long 1727.8 → time_limit 1594.35,
leva 2x) — il singolo trade peggiore del portafoglio live. SH01 non ha TP/SL per design
(exit solo a orizzonte H=12). Domanda: esiste uno SL che taglia le code senza distruggere
l'edge (win ~50%, edge tutto nell'asimmetria dei winner)?
## Numeri storici per anno (config live W24 H12 th0.58, netto fee 0.10% RT, leva 3x)
Somma per-trade in pp (leva 3x); equity-level ≈ ×0.15.
| Anno | BTC | ETH |
|---|---:|---:|
| 2018 | 65.8 | +73.7 |
| 2019 | +87.7 | 19.3 |
| 2020 | +194.0 | **293.1** |
| 2021 | +301.3 | +67.4 |
| 2022 | +64.3 | +79.1 |
| 2023 | +17.4 | +20.6 |
| 2024 | +110.0 | +108.1 |
| 2025 | +76.8 | +539.7 |
| 2026 | +59.0 | 29.5 |
BTC = motore robusto (8/9 anni+). ETH = fragile (DD 61%, win 48.8%): la coda non
protetta è un problema STRUTTURALE di ETH, non solo dell'episodio live.
## Infrastruttura (riusabile)
`scripts/analysis/sh01_exit_lab.py` — harness onesto stile exit-lab:
- cache segnali walk-forward (`data/cache/sh01_exit_lab.pkl`, 7248 BTC + 7386 ETH entries);
- engine intrabar con **fill GAP-AWARE**: stop fillato a `worse(livello, open[j])`, non
"al livello" — chiude il bias PRO-stop-stretti documentato il 2026-06-04 (54% di fill
ottimisti) e modella il caso reale del crash (gap 1655→1600 senza scambi intermedi);
- modalità stop `intrabar` e `close` (close-confirm stile EXIT-16), `after_bar` per
policy discrezionali, stress `lag_close_exit` (poll in ritardo);
- parity ESATTA con `explore_lab.simulate` (BTC +218.50%/1531 trade, ETH +80.13%/1257);
- protocollo: TRAIN fino 2023-11-01 (selezione SOLO lì, plateau ≥3 celle), OOS una volta.
Baseline (orizzonte puro): BTC TRAIN +127%/dd23/shrp2.09/worst5.5 | OOS +41%/8/2.18/3.1.
ETH TRAIN **26%/dd61/shrp0.16/worst14.9** | OOS +143%/7/**3.60**/4.6.
## Esito: 11 famiglie testate, 0 sopravvissute
Policy in `scripts/analysis/sh01_exit_policies/` (01-10 dagli agenti + 11 dal
completeness-probe della sintesi):
| # | Famiglia | Verdetto |
|---|---|---|
| 01 | ATR fisso intrabar (k 15) | NO: 0/7 celle migliorative sul train |
| 02 | ATR fisso close-confirm (k 15) | NO: k=4 migliora ETH train (shrp 0.16→+0.62, DD 61→41, worst 8.4) MA ETH-OOS peggiora (shrp 3.60→2.32, worst 6.6) |
| 03 | % fisso (15%, 2 modi) | NO: BTC↓, ETH-OOS ret 52% del baseline |
| 04 | Chandelier trailing | NO: cella isolata; ETH-OOS tutto peggio (4ª bocciatura della famiglia trailing) |
| 05 | Breakeven ratchet | NO: 0 celle migliorative |
| 06 | Giveback (profit-protection) | NO: dormiente sulle code (worst invariato), taglia winner BTC |
| 07 | Loser time-stop (m,x) | NO: ETH-OOS shrp↓ DD↑, BTC↓ |
| 08 | Disaster-cap LARGO close-confirm (k 36) | NO: k=4 plateau sul train ma ETH-OOS DD 7→11-15%, ret 59-74%; FABBRICA short da stop a 44/54% |
| 09 | Swing strutturale | NO: 0/18 celle |
| 10 | Stop condizionale vol-regime | NO: il migliore dei falliti (ETH-OOS shrp 3.28 vs 3.60) ma BTC train <95% |
| 11 | Disaster-cap LARGO **intrabar** (k 512) | NO: k=10 cella isolata; BTC-OOS shrp 2.18→**0.92**, worst 3.1→−11.4 (il fill gap-aware rende lo stop più tossico del no-stop) |
**Pattern (5ª conferma della lezione exit-lab):** ogni stop abbastanza stretto da toccare
la coda ETH intacca il motore BTC; ogni stop abbastanza largo da risparmiare BTC non
arriva mai alla coda ETH. Per un segnale win~50% con edge nell'asimmetria dei winner,
OGNI SL taglia i winner-in-drawdown insieme ai loser. In più il fill gap-aware mostra
che proprio nei crash (quando lo stop servirebbe) il fill è al gap, non al livello:
lo stop intrabar largo PEGGIORA la coda OOS di BTC invece di proteggerla.
## Decisione
- **NO SL su SH01** (né `sl``sl_confirm_atr` per SH_BTC/SH_ETH: corretto E sicuro —
il fallback 2% del worker si applica solo se `sl` è presente).
- **La leva giusta è il PESO**: cap della famiglia SHAPE nel weighting di PORT06
(`weighting.py` supporta già `caps` per famiglia, come PAIRS 0.33). Dimezzare la quota
SHAPE ≈ dimezza l'impatto della prossima coda 15% sul conto, a costo ~nullo di Sharpe
(SH01 è un diversificatore, corr +0.08 col MASTER).
- **APPLICATO E DEPLOYATO (stesso giorno):** `caps={"PAIRS": 0.33, "SHAPE": 0.0588}` in
`_defs.py` (SHAPE 11.8%→5.9%, ≈ mezzo sleeve equal; SH_BTC=SH_ETH=0.0294, somma pesi 1.0,
verificato). Quantificato sul backtest PORT06:
| Config | FULL Sharpe | FULL DD | CAGR | OOS Sharpe | OOS DD |
|---|---:|---:|---:|---:|---:|
| precedente (SHAPE 11.8%) | 6.47 | 4.10% | 61.2% | 8.82 | 1.30% |
| **cap SHAPE 5.9% (scelto)** | 6.43 | 3.96% | 62.1% | 8.58 | 1.36% |
| SHAPE rimosso | 6.30 | 3.88% | 63.0% | 8.26 | 1.41% |
Il costo (0.24 OOS Sharpe) è il premio dell'assicurazione su una coda che il backtest
daily NON modella (gap di feed, flat testnet). Rimuovere SHAPE costa troppo (8.26).
## Direzioni future (fuori scope SL, annotate dalla sintesi)
1. **Liquidity/staleness-gate sull'ENTRY** (skip ingressi dopo finestre di feed flat —
il flat 2h ha preceduto il gap del 2026-06-05): la direzione più promettente per il
gap-through specifico, merita uno studio dedicato.
2. Vol-target sizing per-trade su SH01 (sulle fade fallì, su SH01 mai testato).
3. SL solo-ETH: scartato anche concettualmente (il beneficio coda ETH è marginale/nullo
OOS in TUTTE le famiglie — non è un problema di uniformità del meccanismo).
@@ -0,0 +1,71 @@
# 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). | `portfolios.yml:14` pos=0.5; `pairs_worker.py:157` pnl=cap*pos*net; `pairs_research.py:29` POS=0.15; live ADA/ETH 06-05 net 8.52% = 4.26% sleeve (sarebbe 1.28% a 0.15). Choice deliberata (commento yaml), ma diverge dal validato. | low-med |
| **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: usare `sh01_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):**
1. **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.
2. **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.
3. **Giorno 2-3 — disaster-bracket on-book + osservabilità outage.** Cablare `set_stop_loss` lontano (~30%) sui 4 fade eseguiti; aggiungere alert su N poll falliti consecutivi con `real_in_position=True` e log del gap% al risveglio. Smoke testnet (€0). Pura assicurazione coda, 0 costo Sharpe.
4. **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:**
5. **Giorno 4-6 — `trend_max=3.0` sulle 6 fade.** Implementare in `_defs.py`, poi **ri-eseguire il gate PORT06** (template `exit16_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).
6. **Giorno 6-8 — pos pairs per-famiglia.** Plumbing `SleeveSpec.position_size`; ri-eseguire `combine` a pos live (target 0.15-0.25); scegliere il punto sul trade-off DD/ritorno; deploy.
7. **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.
8. **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).
+49
View File
@@ -0,0 +1,49 @@
# 2026-06-07 — DIP01 EXIT-16: PROMOSSO (gate gap-aware, il più netto della serie)
Punto 9 della roadmap sweep: DIP01 era l'unico sleeve BTC con esecuzione REALE
round-trip e girava ancora sul branch SL intrabar wick-sensitive. EXIT-16 era
validato SOLO sulle fade: estensione a una strategia honest dietro doppio gate
(grid proprio di DIP01 + PORT06), con engine GAP-AWARE come da lezione exit-lab.
## Metodo (`scripts/analysis/dip01_exit16_impact.py`)
- Parità con la equity canonica: corr **1.00000**, ret +322% identico.
- **Engine gap-aware**: nel ramo orig lo SL filla a `worse(livello, open[j])`
l'engine canonico filla "al livello" anche su gap-through (54% dei casi per stop
tight) e regalerebbe al ramo intrabar un vantaggio fittizio.
- Grid 3×3×2 (z_in × sl_atr × max_bars) su BTC (deployato) ed ETH (robustezza);
train (pre 2024-10-12) e OOS separati. Buffer plateau {0.4, 0.5, 0.75, 1.0}.
- NB `hurst_max` NON valutato su DIP01: il gate trendmax dello stesso giorno ha
mostrato che è ridondante-dannoso post-EXIT-16.
### Trappola di metodo trovata: l'ancora bfill di `_daily_equity`
La prima parità falliva (corr 0.9956, 16pp) con **engine identici** (662 trade,
stessi return, stesso capitale finale). Causa: la convenzione canonica
`_daily_equity` (serie a punti trade-exit → `reindex(IDX).ffill().bfill()`)
**ancora il primo valore della finestra al PRIMO trade dentro IDX** (es. 942.54
dopo le perdite di gennaio 2021), non al capitale portato avanti dall'ultimo trade
del 2020 (979.23). Il mio replay hourly era più "corretto" ma per parità va
replicata la convenzione. **Finding aperto**: questo bias tocca le metriche
canoniche di TUTTI gli sleeve a punti-trade (honest/pairs/tsm) — l'ancoraggio
distorce leggermente il punto di partenza della finestra. Da valutare a parte se
correggerlo OVUNQUE in un colpo solo (cambierebbe i numeri canonici di riferimento).
## Risultati
- **Grid BTC: 36/36** (EXIT-16 ≥ orig gap-aware in train E OOS in tutte le 18
celle). OOS Sharpe ~2-4x (cella canonica 1.47→3.48), DD giù in 14/18 celle.
- **Grid ETH: 35/36** (unica eccezione la cella estrema z3.0/sl3.0/mb48, marginale).
- **Plateau buffer piatto**: OOS DD 6.4% identico da 0.4 a 1.0; scelto 0.5 (== fade).
- **PORT06**: FULL Sharpe 6.43→6.61, DD 3.96→3.58 | OOS Sharpe 8.58→8.77, DD
1.36→1.34. Migliora tutto. **PROMOSSO**.
È la 5ª conferma indipendente del principio EXIT-16: gli stop intrabar da wick
sono falsi negativi per le strategie mean-reversion — l'overshoot che buca lo
stop è il movimento che si sta comprando.
## Deploy
`_defs.py`: `"sl_confirm_atr": 0.5` nei params DIP01_BTC (il worker legge già il
param — zero codice). Live path: TP intrabar invariato, SL solo su close confermato,
disaster-bracket on-book già attivo per gli outage.
+46
View File
@@ -0,0 +1,46 @@
# 2026-06-07 — Gate position_size PAIRS: 0.5 → 0.20 (assicurazione, come il cap SHAPE)
Punto 8 della roadmap improvement-sweep: i pairs giravano col `position_size`
GLOBALE 0.5 a leva 2 (esposizione 1.0 della fetta) mentre TUTTA la validazione PR01
è a pos 0.15 lev 3 (esposizione 0.45) — ~2.2x il validato su una famiglia **senza
stop** (exit solo |z|≤z_exit o max_bars). Scelta deliberata (commento yml) ma mai
gateata. Evidenza live: ADA/ETH 2026-06-05 net 8.52% = 4.26% di sleeve in un trade.
## Metodo
`scripts/analysis/pairspos_port06_impact.py`: le 5 equity pairs ricostruite con
`pairs_sim` alla **leva live (2x)** su griglia pos {0.50, 0.25, 0.20, 0.15},
innestate nel PORT06 canonico (pesi cap PAIRS 0.33 + SHAPE 0.0588). Parità
verificata: `pairs_sim(0.15, lev3)` == equity canoniche (corr 1.00000, 5/5).
## Risultati
| pos | esp. | FULL Sh / DD | CAGR | OOS Sh / DD | fam DD | worst pair DD grezzo |
|---|---|---|---|---|---|---|
| 0.50 (live) | 1.00 | 6.69 / 4.77% | 130% | 9.05 / 3.40% | 14.0% | ETH/BTC 78% |
| 0.25 | 0.50 | 6.50 / 4.04% | 67% | 8.69 / 1.54% | 7.2% | 51% |
| **0.20** | **0.40** | 6.33 / 3.90% | 57% | 8.43 / **1.26%** | 5.8% | 44% |
| 0.15 | 0.30 | 6.03 / 3.78% | 47% | 7.99 / 1.33% | 4.3% | 35% |
Letture:
- Il gate meccanico (OOS Sharpe non peggiora) **boccia ogni riduzione**: ovvio —
ridurre l'esposizione di una famiglia che in-sample non è mai esplosa costa sempre
Sharpe. Ma il +130% CAGR / +2.6e9% compounded di ETH/BTC a pos 0.5 è fantasia da
compounding: dice solo che il tail event non è ancora successo in-sample.
- **0.20 è il ginocchio**: OOS DD 1.26% batte anche 0.15 (1.33% — oltre il ginocchio
si taglia ritorno senza guadagnare DD); esposizione 0.40 ≈ il validato 0.45.
- Decisione di tipo assicurativo, identica al precedente cap SHAPE (adottato pagando
0.24 OOS Sharpe).
## Decisione (utente): pos PAIRS = 0.20
Implementazione:
- `runner.pos_for_spec(sid, global_ps, family_overrides)` — override per-famiglia
(chiave = `weighting.family_of`), plumbato in `build_worker_for`.
- `portfolios.yml`: `position_size_family: {PAIRS: 0.20}` (globale 0.5 invariato
per fade/dip/shape).
- Test: `test_pos_for_spec_family_override`, `test_build_pairs_worker_position_size`.
Nota di transizione: la posizione LTC/ETH aperta al deploy (entrata a sizing 0.5)
verrà chiusa col pos nuovo 0.20 → il pnl % sleeve di quel singolo trade è bookato
alla taglia nuova (one-time, conservativo).
+51
View File
@@ -0,0 +1,51 @@
# 2026-06-07 — SH01 punto-10: il regime live (train 365g) NON è robusto → bootstrap full-history
Punto 10 della roadmap sweep: ri-validare SH01 col train-window del regime live
(~8760 barre, il `_ML_LOOKBACK_DAYS=365` del runner) invece dell'expanding
full-history usato da tutta la validazione storica.
## Ri-validazione (`scripts/analysis/sh01_trainwindow_validate.py`)
`ml_wf_entries(train_window=...)` rolling su storia piena ≈ il regime live.
Protocollo explore_lab (FULL / OOS 30% / sweep fee / anni positivi), W24 H12 th0.58.
| BTC train | trade-rate | FULL / OOS | Sharpe | fee 0.2%: FULL/OOS | anni+ | robusto |
|---|---|---|---|---|---|---|
| 8760 (live) | **21.7%** | +82 / +74 | 1.31 | **42** / +20 | 6/9 | ❌ |
| 17520 (2y) | 15.4% | +166 / +96 | 2.05 | +9 / +47 | 6/9 | ❌ |
| 26280 (3y) | 11.8% | +348 / +95 | 3.09 | +109 / +61 | 7/9 | ❌ |
| EXPANDING | 9.8% | +219 / +42 | 2.72 | +60 / +26 | 8/9 | ✅ |
ETH a tw=8760: **FULL 33%, Sharpe 0.02** (lancio di moneta che paga fee).
Conferme: (a) la diagnosi del sweep era esatta (trade-rate 22-26% live vs 10%
validato — soglia inerte per over-confidence su train piccolo); (b) la
progressione è MONOTONA: l'edge di SH01 *è* la memoria lunga; (c) lo sweep della
soglia nel regime corto dà risultati instabili e incoerenti fra asset → ri-tunare
th sarebbe curve-fitting sul rumore di calibrazione (come da warning del piano).
## Decisione (utente): bootstrap full-history
Ripristinare in live ESATTAMENTE il regime validato (expanding full-history):
1. **`ml_wf_entries(last_block_only=True)`** — fitta/predice SOLO l'ultimo blocco
del walk-forward. I confini dei blocchi sono deterministici (start + k·retrain)
e al worker servono solo i segnali recenti → le entries dell'ultimo blocco sono
**identiche per costruzione** al tail del WF completo (parity test esatto in
`tests/portfolio/test_sh01_last_block.py`). Costo live: **0.6 s/tick** su 73k
barre (1 fit LogReg) vs ~140 fit del WF completo.
2. **Bootstrap storia nel runner**: per gli sleeve `ml` il tick riceve
`concat(parquet_locale < feed_start, feed_live)` (`runner._with_history`).
Gap-guard: se il parquet è stantio oltre il lookback del feed → solo feed +
WARN una-tantum (meglio il regime corto dichiarato che una serie col buco).
Manutenzione: tenere fresco il parquet con `download_all()` (oggi al 2026-05-28,
il feed copre 365g → margine ~11 mesi).
3. `_defs.py`: `params={"last_block_only": True}` sugli sleeve SHAPE (solo path
live; il backtest canonico resta WF completo).
Effetto atteso live: trade-rate SH01 da ~25% a ~10% delle barre, selettività
della soglia ripristinata, WR atteso ≈ validazione (acc OOS ~56% BTC).
Il monitor naturale è il report orario (SH01 farà MENO trade).
Nota: `_ML_LOOKBACK_DAYS` resta 365 (il feed serve comunque per le barre recenti
e come fallback se il parquet è stantio).
+101
View File
@@ -0,0 +1,101 @@
# 2026-06-07 — Implementazione fix improvement-sweep (P0 + punti 2-6, settimana 1)
Implementati e deployati i fix "settimana 1" del piano dello sweep 2026-06-06
(tutti fix di parità/protezione/osservabilità, nessun cambio di strategia → nessun
gate OOS richiesto). Versioni: v1.1.3 (punti 1-4), v1.1.4 (punti 5-6).
## P0 — cap SHAPE 0.0588 ripristinato (v1.1.3)
`portfolios.yml` ridichiara il dict caps COMPLETO `{PAIRS: 0.33, SHAPE: 0.0588}`
(l'override sostituisce interamente i caps di `_defs.py` via setattr in
`base.py:load_active_portfolio` — footgun ora commentato nel yml). Verificato live
post-restart: SH_BTC+SH_ETH = 0.0588 totale (prima 0.1176 = 2x intended), non-cappati
risaliti a 0.0647, sum = 1.0 — parità col backtest canonico (FULL 6.43 / OOS 8.58).
## Punti 2-4 — parità worker multi-asset (v1.1.3)
- **TR01 fee × leva**: `POS · LEV · fee_rt/2` per flip come il reference
`honest_improve2._tr_basket_daily:150` (prima sotto-caricata 2x).
- **TR01 forming-bar**: crossover EMA E booking del return solo su barre 4h COMPLETE
(riga -1 = candela in corso finché non è trascorsa la sua durata, pattern EXIT-16).
Evidenza live che non può più ripetersi: flip SOL 0→1→0 in 59min stessa finestra 4h.
- **PairsWorker forming-bar**: entry ED exit sul close di barra COMPLETA, come il
backtest `pairs_research` (close settled).
- **TSM01/ROT02 silent-return**: WARN log + Telegram `PANEL_SHORT` quando il panel
inner-join è sotto il lookback (helper condiviso `_warn_panel_short` in
rotation_worker), gated su "era già operativo", una notifica per episodio.
Verifiche: 72 test (7 nuovi), `validate_worker_pairs` ESATTO, `validate_honest_workers`
invariato. **Nota onesta**: TR01 replay worker 44% vs reference +42% è IDENTICO
pre/post fix → divergenza di convenzione pre-esistente (capitale-unico con
`mean(rets)` solo sugli asset in posizione vs media-equity 1/N del reference).
Da rivisitare a parte: "stesso ordine di grandezza" oggi non regge più.
## Punto 5 — disaster-bracket on-book + alert outage (v1.1.4)
Durante un outage (poll-loop in except) le posizioni REALI restavano senza stop sul
book e senza valutazione exit. Ora:
- **`ExecutionClient.place_disaster_sl`**: STOP_MARKET reduce-only a ~30%
dall'ingresso (trigger sul mark), piazzato a ogni REAL_OPEN sui fade eseguiti
(MR01/MR02/MR07/DIP01) e cancellato in `_real_close` (prima del TP-reconcile).
In operatività normale non scatta mai → 0 costo Sharpe; nei crash il fill è al
gap (cappa la coda, non la elimina). Config: `overrides.execution.disaster_sl_pct`
(default 0.30, 0 = off). `real_dsl_order_id` persistito (resume-safe).
- **SCOPERTA**: il `set_stop_loss` di cerbero-mcp è un `private/edit` Deribit (solo
ordini APERTI) → inutilizzabile su market già fillati; il bracket va piazzato come
trigger order autonomo via `place_order(type="stop_market")` (già supportato).
- **Semantica cancel trigger order** (verificata su testnet): la cancel risponde con
lo stato AL MOMENTO della cancel (`untriggered` = successo; il re-cancel dà
`order_not_found`); `error` = non più in book (probabile trigger scattato → il
market a valle filla 0 e REAL_CLOSE esce verified=False).
- **Alert `FEED_OUTAGE`**: dopo 5 poll falliti consecutivi (~5 min) Telegram con
l'elenco delle posizioni reali aperte; notifica di ripresa con durata.
Smoke testnet (`live_shadow_smoke.py`, esteso): 4 scenari long+short — il path
**short-side TP-resting** (BUY limit reduce-only) non era MAI stato esercitato.
Tutti verdi: resting/cross-immediato due lati, DSL piazzato/cancellato (verificato
zero ordini orfani sul book), conto flat a fine smoke.
## Punto 6 — osservabilità multi-asset (v1.1.4)
- `in_position` aggiunto ai `_save()` di TR01/ROT02/TSM01.
- `hourly_report`: nuova sezione **MULTI-ASSET** (book corrente | ultimo flip |
freschezza status) — prima i 3 worker erano invisibili nel report (collect()
filtra su event/in_position che non emettevano): impossibile distinguere
"flat/risk-off by-design" da "wiring rotto". I multi-asset sono esclusi dalla
tabella IN CORSO (assume entry/bars single-leg).
## Code review multi-agente del giorno stesso (v1.1.7)
Review a 7 angoli su `8c4e1cd..HEAD` + check trades live. Il candidato top dei finder
("stop_market senza trigger") è REFUTATO: cerbero-mcp traduce `price→trigger_price +
trigger=mark_price` e in produzione 2 DSL erano armati + 1 ciclo completo pulito.
Quattro fix applicati:
1. **Alert `REAL_DIVERGENCE`** (|slippage sim/reale| ≥ 100bps a open/close). Scoperta
dal check trades: alle 10:37 uno **spike print testnet** (candela 10:00 H=65618 con
O/C~62400) ha fatto shortare alle 3 fade BTC un close fantasma a 65266.5 — il reale
ha fillato correttamente a ~62395 (440bps) ma il sim ha bookato +2.26 mai esistiti
(MR07 reale 0.13). Prima passava in silenzio.
2. **`FEED_OUTAGE` anche su feed degradato senza eccezione** (HTTP 200 con candles
vuote → i worker saltavano il tick in silenzio e lo streak restava 0). Helper unico
`_outage_tick` (fix anche dell'incoerenza chiavi minuti/durata_min).
3. **`src/live/bars.py`**: detection forming-bar UNIFICATA (era copiata in 4 punti,
con `_check_stale_feed` che hardcodava 1h). È l'invariante di sicurezza di EXIT-16:
ora vive in un posto solo, testato (`test_bars.py`).
4. **DSL cancel hardening**: retry su errore transitorio + alert Telegram
`REAL_DSL_CANCEL_FAIL` se lo stop resta forse ORFANO sul book (prima l'id veniva
dimenticato in silenzio → lo stop stantio poteva colpire la posizione successiva);
`order_not_found` = probabile trigger durante outage → solo log (il close a valle
esce già verified=False). Test con executor finto (`test_real_close_dsl.py`).
Finding noti NON ancora fixati (in coda): ROT02/TSM01 valutano la candela 1d in
formazione al primo poll dopo mezzanotte (stessa classe del fix TR01/Pairs, pre-esistente);
engine dei gate copiato fra i 3 script `*_port06_impact.py`; epoche hourly_report hardcoded.
## Resta in roadmap (settimana 2, OGNUNO dietro gate PORT06/OOS)
trend_max=3.0 sulle 6 fade → pos pairs 0.15-0.25 per-famiglia → DIP01 EXIT-16
(validazione gap-aware) → SH01 ri-validazione train-window ~8760 barre.
Follow-up tecnico: divergenza convenzione TR01 worker vs reference (sopra).
+64
View File
@@ -0,0 +1,64 @@
# 2026-06-07 — Gate trend_max sulle fade live: BOCCIATA l'aggiunta, PROMOSSO lo swap hurst→trend
Punto 7 della roadmap improvement-sweep: ripristinare `trend_max=3.0`/`ema_long=200`
sulle 6 fade live (ON nel backtest canonico, OFF in produzione). Il piano imponeva il
gate PORT06 perché hurst e trend si sovrappongono. Esito sorprendente ma coerente.
## Metodo
`scripts/analysis/trendmax_port06_impact.py` — engine riusato da `exit16_port06_impact`
(replay esatto del path canonico, parità verificata: corr 1.00000 e diff 0.00% su tutte
e 6 le fade) + maschera Hurst IDENTICA al live (`fade_base.hurst_skip_mask`, close-only,
w=100 step=6 — non la cache regime_lab). Tutte le varianti girano sul **path live**
(EXIT-16 close-confirm 0.5 attivo); PORT06 con pesi cap canonici (PAIRS 0.33,
SHAPE 0.0588), OOS da 2024-10-12.
Nota di metodo: il test PORT06 del loss-guard Hurst (`fade_lossguard_port_test`,
2026-06-02) era su entries GIÀ trend-filtrate E sull'engine PRE-EXIT-16 (SL intrabar).
La config live reale (hurst senza trend, con EXIT-16) non era MAI stata gateata.
## Risultati (PORT06, fade in path live EXIT-16)
| Variante | FULL Sh | FULL DD | FULL CAGR | OOS Sh | OOS DD | OOS CAGR |
|---|---|---|---|---|---|---|
| LIVE (hurst, no trend) | 7.23 | 2.68% | 68% | 9.35 | 1.68% | 92% |
| CANDIDATO (hurst+trend) | 7.11 | 2.06% | 59% | 9.36 | 1.62% | 83% |
| **TREND-ONLY 3.0** | 7.89 | 2.46% | 82% | **9.91** | **1.20%** | 103% |
| NESSUNO (solo EXIT-16) | **8.07** | 3.35% | **105%** | 9.72 | 1.38% | 119% |
| TREND-2.5 | 7.75 | 2.46% | 76% | 9.76 | 1.20% | 96% |
| TREND-3.5 | 7.87 | 2.46% | 84% | 9.64 | 1.20% | 103% |
## Letture
1. **CANDIDATO BOCCIATO** (gate formale): il DD scende (2.68→2.06) ma FULL Sharpe
0.12 e CAGR 9pp — over-filtering, i due filtri tagliano lo stesso regime
(n trade 50%, es. MR02_ETH 911→436).
2. **Il loss-guard Hurst è ridondante-dannoso post-EXIT-16**: NESSUNO batte LIVE su
Sharpe e ritorno ovunque. Spiegazione meccanica: hurst evitava i regimi stop-heavy
saltando gli ingressi; EXIT-16 ha eliminato i wick-stop alla radice → gli ingressi
che hurst salta sono in maggioranza tornati vincenti. Il 66% delle barre è oltre
soglia hurst → il filtro toglieva metà dell'esposizione per un beneficio che ormai
non c'è.
3. **TREND-ONLY domina LIVE su tutte e 4 le metriche** ed è ESATTAMENTE la config che
la ricerca EXIT-16 del 2026-06-04 aveva promosso (entries trend-filtrate, no hurst:
FULL 7.84/2.60, OOS 10.06/1.15 — i 7.89/2.46 e 9.91/1.20 di oggi combaciano al
netto di refresh dati e cap SHAPE). Il live non aveva mai eseguito quella config.
4. **Plateau robusto**: trend_max 2.5/3.0/3.5 quasi indistinguibili (OOS DD 1.20
identico su tutte) → non è una soglia tunata.
5. Il trend filter vs NESSUNO è un trade-off deliberato: 23pp CAGR FULL per
FULL DD 3.35→2.46 e OOS Sharpe 9.72→9.91. Coerente con la filosofia del progetto
(DD control > ritorno marginale).
## Decisione (utente, 2026-06-07)
**SWAP hurst→trend nelle 6 fade live** (`_defs.py`): `trend_max=3.0, ema_long=200`,
`hurst_max` rimosso. `hurst_skip_mask` resta in `fade_base` (param opzionale).
Monitor live aggiornato: `hourly_report` ora traccia lo stop-rate per epoca
PRE (nessun filtro) → HURST → TREND, verdetto a n≥30 nell'epoca TREND.
## Lezione
Ogni meccanismo anti-perdite va RI-gateato quando cambia l'exit engine: hurst era
genuinamente il migliore sull'engine a SL intrabar (unico su ~10 candidati), ma
EXIT-16 ne ha assorbito il lavoro. I filtri si valutano sul path live corrente,
non sui numeri storici di promozione.
@@ -0,0 +1,76 @@
# 2026-06-08 — Ricerca multi-agente: dispersion / correlation index (165 agenti)
Workflow `dispersion-correlation-research` (run wf_72edef49-6d9): 60 celle esplorate
(15 famiglie × 4 finestre di correlazione W=24/72/168/336), 165 agenti totali, ~10M
token, verifica avversariale a 2 skeptic su ogni candidato robusto + sintesi.
Infrastruttura riusabile: `scripts/analysis/dispersion_lab.py` — feature REALIZED
causali (avg pairwise correlation, dispersione cross-sectional, beta vs indice EW,
componente idiosincratica `rel_A = ret_A ret_idx`), universo 8-asset, finestra
comune dal 2022-07-22, cache su disco, no-look-ahead verificato. NB: **niente
implied** (opzioni non backtestabili — stesso muro ARGO/GEX).
## Esito: 2 edge confermati su 60, 13 famiglie su 15 sono RUMORE
Nessun look-ahead "hard" stile-squeeze (le feature hanno retto la perturbazione del
futuro ovunque). Il fallimento dominante è **overfit/concentrazione di regime**, non
leakage. Tre pattern ricorrenti di falso positivo:
1. **OOS circolare**: le feature esistono solo dal 2022-07 → lo split OOS (ultimo 30%)
cade nel bull/calm 2024-25 (il regime che il progetto documenta come ottimistico
~50%); FULL≈OOS non era robustezza ma "quasi tutti i trade in OOS".
2. **Il gate corr/disp non è quasi mai load-bearing**: in ablazione, togliere il gate
spesso MIGLIORA i numeri → è un throttle di esposizione, non alpha (e Hurst-guard +
EXIT-16 fanno già quel lavoro).
3. **Riconferma del prior**: l'edge è sempre mean-reversion del residuo, mai
momentum/continuation/breakout cross-sectional.
## I 2 edge reali (entrambi fade del residuo idiosincratico)
- **`index_comp_disp` W=168 (BTC)** — il vero "dispersion-trading realizzato": fade
della componente idiosincratica estrema verso l'indice, **gated da alta dispersione**
(`disp_168 ≥ quantile 0.7`). rel_len=12, z_thr=1.5, TP=1.0·ATR, SL=1.5·ATR, mb=24.
FULL +63% / OOS +36% (fee0.2% +23%), win 66%, Sharpe 2.7-3.0, DD 7-9%, 5/5 anni.
È l'UNICA famiglia dove il gate dispersione fa davvero qualcosa.
- **`rel_idio_fade` W=24** — fade dello z-score di `rel_A` su 24h, exit a tempo (no
TP/SL). Cella più pulita BTC z3.0 mb12: win 58.5%, Sharpe 4.40, DD 7%, OOS +98%.
Più forte ma è quasi MR07 ri-etichettata in chiave cross-sectional.
Script preservati: `scripts/analysis/dispersion_edges/{index_comp_disp,rel_idio_fade}.py`.
## Raccomandazione: bassa priorità
**Entrambi sono fade-BTC mean-reversion → sovrapposti alle fade MR già deployate.**
P(migliora PORT06): ~20-25% per index_comp_disp, ~15-20% per rel_idio_fade. Vale la
**lezione FR01** (robusto-individualmente ≠ migliora-il-portafoglio: FR01 era robusto
e causale ma diluiva PORT06 OOS Sharpe 8.89→8.72). index_comp_disp ha il profilo trade
più distinto (win 66%, hold corto, TP vicino) → è l'unico che merita eventualmente un
gate `combine_v2`/PORT06 formale, misurando corr col MASTER e ΔSharpe/ΔDD. Se non
decorrela, scartare.
**13 famiglie rumore** (record in `_disp_scratch/`, gitignored): corr_gate_fade/pairs,
disp_xs_reversal/momentum, corr_spike_btc, corr_meanrev, beta_disp, disp_regime_rot,
corr_vol_interact, leadlag_corr, corr_trend, disp_compression_breakout, corr_disp_combo
— tutte MR/fade ri-etichettate, concentrazione 2024-25, o artefatti di feed morto
(ADA/LTC 97% candele flat). disp_compression_breakout riconferma la lezione squeeze
(i breakout rientrano).
**Conclusione onesta: nessun nuovo motore di ritorno.** Il dispersion-trading realizzato
funziona solo come l'ennesima faccia della mean-reversion già sfruttata.
## Gate PORT06 del candidato n.1 (2026-06-08) — PROMOSSO MARGINALE, NON deployato
`scripts/analysis/dispersion_edges/gate_index_comp_disp.py` (config W=168: rel_len=12,
z_win=336, z_thr=1.5, disp_q=0.7, TP=1.0ATR, SL=1.5ATR, mb=24; equity daily innestata
come 18° sleeve, pesi cap):
- **Sorpresa positiva**: decorrela DAVVERO. corr daily col candidato: MR01_BTC +0.01,
MR02_BTC +0.05, MR07_BTC +0.06, DIP01_BTC +0.02, MASTER(EW) +0.06. Il timore di
ridondanza con le fade BTC era infondato (gate dispersione + TP vicino = profilo trade
distinto). Standalone 311 trade, FULL +67% / OOS +30%.
- **Ma beneficio nel rumore**: PORT06 BASE→+DISP: FULL Sharpe 6.43→6.47, FULL DD
3.96→3.73 (migliora), **OOS Sharpe 8.58→8.56 (0.01), OOS DD 1.36→1.40 (+0.04)**
l'OOS è PIATTO. Passa il gate tecnico ma il guadagno è solo nel FULL (regime storico).
- **Decisione (utente): documenta e rimanda.** NON deployato — wiring (nuova Strategy+worker,
famiglia DISP, peso cap) + resterebbe simulato (no executor), per un beneficio OOS nullo.
Gate script committato e pronto: riprendere SOLO se si costruisce una famiglia DISP più
ampia (più asset/sleeve) che insieme sposti l'OOS. Esito ~20% previsto, confermato.
@@ -0,0 +1,130 @@
# 2026-06-08 — Ricerca sostituto/miglioria a MR02/ETH (127 strategie + opzioni)
## Contesto
MR02/ETH è lo sleeve a DD più alto della famiglia fade ed è il maggior drag
dell'esecuzione reale (testnet: 19€ su 7 trade, 0 win, faded un downtrend 14%
in long poi shortato il rimbalzo — whipsaw classico). Obiettivo: trovare un
sostituto o una miglioria con ≥100 agenti su strategie differenti, metodologia
onesta (ingresso eseguibile, fee 0.10% RT, OOS, gate PORT06).
## Cosa è stato fatto
- **Workflow 1 — 109 strategie ETH distinte** (`mr02eth_search.workflow.js`):
gated-fade, mean-reversion alternative, exit, trend-following, volatilità,
cross-asset, ML/shape, microstruttura, statistico-robusto. Harness onesto
`explore_lab`. Poi **verifica avversariale** dei top-24 (audit look-ahead +
re-implementazione indipendente + robustezza). → 26 survivor, **6 confermati**.
- **Workflow 2 — 18 overlay di OPZIONI** (`mr02eth_options.workflow.js`) +
sintesi con audit crash reali. Harness nuovo `option_overlay_lab.py`: opzioni
prezzate Black-Scholes sulla **DVOL** reale di Deribit (storica/gratuita,
`data/regime/eth_dvol.parquet`), premio dedotto, ipotesi **conservative**
(skew_mult sui comprati, uscita anticipata solo a intrinseco).
- **Gate PORT06** (`mr02eth_port06_gate.py`): swap di MR02_ETH col candidato,
equity giornaliera su IDX 2021-2026, ri-misura FULL+OOS Sharpe/DD (cap
weighting). Sanity check: MR02 ricostruito col mio engine == canonico (Δ 0.00).
## Risultato (gate PORT06; baseline canonico FULL 6.43/3.96, OOS 8.58/1.36)
| variante (swap MR02/ETH) | FULL Sh | FULL DD | OOS Sh | OOS DD |
|---|---|---|---|---|
| EXIT-16 (config LIVE) | 6.73 | 3.67 | 8.80 | 1.23 |
| no-SL nudo | 6.76 | 3.68 | 8.87 | 1.23 |
| no-SL + put/call 10% OTM | 6.78 | 3.64 | 8.86 | 1.23 |
| varratio_gate (PROMOSSO vs canonico) | 6.41 | 3.73 | 8.53 | 1.36 |
| choppiness_donch | 6.37 | 3.35 | 8.47 | 1.32 |
| vrp_neg_dvol_low | 6.36 | 3.22 | 8.37 | 1.43 |
## MIGLIORIA TROVATA (gate vs LIVE EXIT-16, non vs canonico)
Riferimento onesto = config LIVE EXIT-16 (FULL 6.73/3.67 — OOS 8.80/1.23), non
l'intrabar-SL canonico che nessuno gira.
| sleeve ETH | ΔFULL Sh | ΔFULL DD | ΔOOS Sh | ΔOOS DD | esito |
|---|---|---|---|---|---|
| **no-SL + put/call 10% OTM** | **+0.05** | **0.03** | **+0.06** | 0.00 | ✅ MIGLIORIA deployabile |
| no-SL nudo | +0.04 | +0.02 | +0.07 | 0.00 | ✅ migliora ma sl=0 NON deployabile |
| EXIT16 + put10 | 0.01 | +0.03 | 0.00 | 0.00 | = pari (assicurazione gratis) |
| varratio / vrp / chop / blend | negativi | | | | peggiorano vs EXIT-16 |
**La miglioria = MR02/ETH no-SL + floor a opzioni 10% OTM** (put sui long, call
sugli short). Batte il live EXIT-16 in modo PARETO su 3/4 metriche PORT06 (4ª
piatta). Meccanismo: **rimuovere lo stop** (intrabar/close-confirm su una fade
mean-reverting sono falsi negativi — overshoot che rientra) e dare la funzione
dello stop (cap della catastrofe) a un'**opzione comprata** = protezione a
rischio-definito SENZA il costo whipsaw/falso-stop. Standalone il salto è grande
(Sharpe 8.45→12.14, OOS DD 13%→7%); a portafoglio è modesto (+0.05/+0.06 Sharpe)
perché è 1 sleeve su 17, ma è un miglioramento MISURATO e verificato (soglia
gate +0.03, ottenuto +0.05/+0.06), non rumore.
## Conclusioni (oneste)
1. **Sostituti/blend: tutti peggiorano vs il live EXIT-16.** I candidati gated
(varratio/vrp/choppiness) e i blend within-sleeve battono solo lo strawman
canonico, non il live; le loro basse esposizioni diluiscono il motore di
ritorno (Sharpe 0.08/0.44). Lo Sharpe ~20 nell'harness era artefatto di
`sqrt(N_trade)` (onesto ~4.3). → **non sostituire né blendare.**
2. **L'unica miglioria è la coppia no-SL + opzione.** Il no-SL recupera il drag
dello stop; l'opzione cappa la coda da **gap-through** che EXIT-16 NON protegge
(esce al close DOPO il buco → prende la perdita piena). Audit crash: FTX nov-22
48%→−30%, LUNA-era 2022-09 63%→−31%, 2018/19 65%→−30%. Premio ~break-even
(paga 156% / incassa 149%). È ciò che rende deployabile il no-SL.
## Azione raccomandata
- **Deployare MR02/ETH come no-SL + floor a opzioni 10% OTM** (put long / call
short, per-trade o settimanale ammortizzato). Batte il live su 3/4 metriche +
cappa esplicitamente la coda. **Prima del capitale**: validare prezzi/liquidità/
skew per-strike REALI su Deribit testnet (qui pricing BS sintetico sulla DVOL =
ATM-IV; stress skew ×1.5 gonfia il premio FULL ma l'OOS regge). Stesso muro
non-backtestabile di ARGO/GEX sul per-strike, aggirato col proxy DVOL.
- Step intermedio a costo zero: **aggiungere subito il floor opzione all'EXIT-16
live** (= "pari" sui numeri, ma assicurazione gratis sul gap-through) mentre si
valida la transizione a no-SL.
## Addendum 2026-06-09 — validazione su PREZZI REALI + integrazione cerbero-bite
- Probe live Deribit mainnet (singolo snapshot): sembrava skew ×1.6 e liquidità
pessima sul 10% OTM a 1g → l'overlay per-trade pareva bocciato.
- **cerbero-bite** (container accanto) ha però **lo storico per-strike reale** in
`option_chain_snapshots` (SQLite, dal 2026-05-01, ~110k righe/asset, bid/ask/IV/
greche/OI, cadenza ~12min). Importato in `data/options/` via `options_fetcher.py`;
loader `options_chain.py` (skew_curve/premium_levels/quote causale).
- **Correzione dai dati reali (5.5 settimane):** skew put 10% OTM = **×1.1** (liquido,
spread ~7%) — il ×1.6 del probe era un artefatto del deep-OTM a 1g illiquido. Premio
reale 10% OTM ≈ **1.0%/mese**. → la miglioria no-SL+opzione **regge** sui prezzi veri.
- **MA vincolo strutturale reale:** gli strike **10% OTM a 24h NON esistono** (n=0 in 5.5
settimane; Deribit lista solo near-ATM sui tenor corti). L'overlay **per-trade a 24h è
infattibile**; l'unica struttura eseguibile è una **put settimanale/mensile standing**
(catastrofe-cap di sleeve), da gateare a parte coi premi reali.
- **Infrastruttura sbloccata:** il muro ARGO/GEX ("opzioni non-backtestabili") è caduto
per il periodo recente — d'ora in poi le tesi opzioni si validano su prezzi VERI.
Importato anche il pannello `market_snapshots` (spot/VRP/funding/**net-GEX dealer**/
gamma-flip/liquidation, dal 2026-03-26) → `data/options/`, loader `options_chain.load_market`.
## Addendum 2 — gate del COLLAR standing (catastrophe-cap auto-finanziato) — NO-GO
Premi reali → collar = put 13% (1.0%/m) finanziata da call +10% (1.05%/m) ≈ premio netto 0.
Gate PORT06 (`eth_collar_gate.py`, BS calibrato skew_put 1.12 / skew_call 1.0, sweep OTM/hedge_frac):
- Il collar **PAGA nei crash** (ETH 40% dic-21 → +6.1%, 30% feb-25 → +6.6%): protezione reale.
- MA **net-negativo a portafoglio**: taglia il FULL DD (0.2/0.3pp) ma **costa Sharpe** (FULL e OOS)
e **ALZA l'OOS DD** (+0.18/+0.28pp) — nell'OOS (no crash profondo, qualche rally) la call corta
perde e il premio erode. Peggiora il DD che doveva ridurre.
- **Verdetto: il tail-hedge a opzioni NON conviene a PORT06** (né per-trade né collar). Strutturale:
lo sleeve ETH pesa 6.5% → la **diversificazione è già la protezione di coda** (trade 65% =
4% aggregato). La cap auto-finanziata era giusta sul *costo* (~break-even) ma la protezione non
vale il costo Sharpe/OOS-DD. Le opzioni si chiudono come **NO-GO empirico su prezzi reali**.
## Deploy della miglioria (2026-06-09): MR02/ETH stop-largo sl_atr 3.0
Nessun SOSTITUTO batte il live; la MIGLIORIA deployabile e' allargare lo stop EXIT-16.
Gate stop-width (EXIT-16 close-confirm, swap solo MR02_ETH):
| sl_atr | FULL Sh | FULL DD | OOS Sh | OOS DD | worst-trade |
|---|---|---|---|---|---|
| 2.0 (LIVE) | 6.73 | 3.67 | 8.80 | 1.23 | -48% |
| **3.0** | **6.77** | **3.30** | **8.82** | 1.23 | **-50%** |
| 4.0 | 6.78 | 3.49 | 8.82 | 1.23 | -62% |
| no-SL (sl=0) | 6.76 | 3.68 | 8.87 | 1.23 | -65% (vietato) |
`sl_atr=3.0` domina il live (FULL DD -0.37pp, Sh +0.04/+0.02) e cattura il beneficio del no-SL
(gli stop di meta'-discesa sono falsi negativi) MANTENENDO lo stop di catastrofe (worst -50%, non
-65%) -> regola "mai sl=0" rispettata; 4.0 inizia a perdere la coda. Plateau 2.5-4.0 (tutti > live
su Sharpe). Override SCOPED a MR02/ETH in `_defs.py` (`_fade_params`); gli altri 5 fade non
ri-gateati -> invariati. 93/93 test passano. Deploy: `./scripts/deploy.sh` (cambio live).
## Artefatti riusabili
- `scripts/analysis/option_overlay_lab.py` — overlay opzioni (pricing BS sintetico su DVOL).
- `scripts/analysis/options_fetcher.py` — import storico opzioni reale da cerbero-bite → data/options/.
- `scripts/analysis/options_chain.py` — loader + skew/premi reali + lookup causale (`OptionChain`).
- `scripts/analysis/mr02eth_port06_gate.py` — gate swap-sleeve PORT06.
- `scripts/analysis/mr02eth_search.workflow.js`, `mr02eth_options.workflow.js`.
+53
View File
@@ -0,0 +1,53 @@
# 2026-06-08 — Executor a 2 gambe per i pairs (PairsExecutionClient)
Implementato il pezzo di esecuzione reale più impegnativo rimasto: l'esecuzione
shadow a 2 gambe per la famiglia PAIRS (29% del portafoglio, finora solo simulata).
## Cosa
- **`execution.PairsExecutionClient`**: compone l'`ExecutionClient` single-leg.
- `open_pair(inst_a, inst_b, direction, notional)`: piazza le 2 gambe (long A/short B
o viceversa) a market, verifica per-gamba sul trade; **LEG-RISK**: se UNA sola gamba
filla → UNWIND (richiude la fillata reduce-only) per non restare direzionali →
verified=False.
- `close_pair(...)`: chiude entrambe reduce-only (solo `close_amount` della quota,
MAI `close_position`), riconcilia fee/prezzi.
- `register_contract`: fetch dinamico spec USDC da Deribit per strumenti non hardcodati.
- Strumenti = lineari USDC (payoff lineare = matematica del backtest a 2 gambe).
Aggiunti spec LTC/ADA/SOL_USDC (step 0.1/0.2/0.1).
- **`PairsWorker` shadow**: ledger reale parallelo (`real_capital`, `real_dir`,
`real_amount_a/b`, `real_entry_a/b`, `real_notional_a/b`, fee), persistito e resume-safe;
`_real_open_pair`/`_real_close_pair` agganciati a `_open`/`_close`; PnL reale per gamba
(dir A=+d, dir B=d). Doppio arrotondamento → piccolo sbilanciamento notional, riportato.
- **runner**: `pairs_executor` (PairsExecutionClient su stesso ExecutionClient dei fade),
`_pairs_exec_for`, gate su `execution.pairs_enabled`. Config `portfolios.yml`:
instruments estesi (LTC/ADA/SOL) + `pairs_enabled: false` (capability pronta, SPENTA).
## Validazione
- Test (executor finto): 92/92 — open/close a 2 gambe, **leg-risk unwind**, ledger reale
persiste e resume.
- **Smoke testnet end-to-end** (`live_pairs_smoke.py`, €0): open 2 gambe verificate
(long ETH 0.011 @1666 / short BTC 0.0003 @63263, fee $0.019), close 2 gambe reduce-only,
riconciliazione PnL reale 0.039 vs sim 0.036 (coerente), **conto flat dopo**.
## Incidente operativo (testnet, €0) — da ricordare
Durante la prima esecuzione, lo smoke è crashato (bug del test: prezzi sim=0 → divisione
per zero nel ramo sim) lasciando una posizione aperta. Per pulirla ho usato a mano
`ExecutionClient.close()` (= `close_position`) che **flatta l'intero strumento** → ho
chiuso anche le 3 posizioni reali SHADOW dei fade del runner (BTC/ETH_USDC condivisi).
Impatto: testnet, shadow (non guida le decisioni), €0; 3 worker fade con
`real_in_position=True` su conto flat → si auto-riconciliano al prossimo close sim
(close_amount su flat → verified=False, reset; "shadow pulito dalla prossima apertura").
I TP resting erano già stati cancellati da close_position (cancel → order_not_found).
**Lezione (CLAUDE.md la documentava già)**: MAI `close_position` su strumenti condivisi.
Fix permanente: lo smoke ora (a) usa solo `close_amount` della quota, (b) ABORTA se ci
sono posizioni di produzione aperte sugli strumenti, (c) usa prezzi sim reali.
## Stato: PRONTO ma SPENTO (`pairs_enabled: false`)
L'executor è validato (test + smoke). NON attivato in produzione: accenderlo richiede
una finestra a conto flat e un periodo di osservazione, dato il conto condiviso coi fade.
Quando si accende: i 5 pairs eseguiranno reale a 2 gambe accanto al sim.
@@ -0,0 +1,42 @@
# 2026-06-08 — Portafoglio live REALE-only: i €2000 ai soli sleeve eseguiti
Decisione utente: il portafoglio live deve mostrare il **risultato reale puro**. I €2000
si dividono SOLO tra i 14 sleeve che eseguono davvero (6 fade + DIP01 + 5 pairs + SH01);
i 3 multi-asset (TR01/ROT02/TSM01), non eseguibili in reale (bloccati dal capitale),
escono dal pool e girano **solo per statistica**.
## Implementazione (runner-level, non rompe le definizioni)
- `portfolios.yml`: `paper_sleeves: [TR01, ROT02, TSM01]`.
- `runner`: separa `live_specs` (pool/pesi/ledger) da `paper_specs`. I paper:
- capitale NOZIONALE fisso = `total_capital / N_sleeve_totali` (la fetta equal che
avrebbero avuto), NON dal pool;
- girano in `data/portfolio_paper_stats/` (binario separato);
- ticcati per statistica ma MAI in `ledger.update_equity` → non toccano l'equity reale.
- `hourly_report`: la sezione multi-asset ora legge `portfolio_paper_stats/` ed è
etichettata "PAPER — solo statistica, FUORI dal conto reale".
## Pesi del portafoglio reale-only (14 sleeve, cap)
sum=1.0; non-cappati 0.0873 (erano 0.0647 su 17), PAIRS 0.33 (cap), SHAPE 0.0588 (cap,
SH ciascuno 0.0294). Il cap SHAPE resta valido e protettivo anche su 14.
## Il costo, misurato e accettato
| | FULL Sh | FULL DD | OOS Sh | OOS DD |
|---|---|---|---|---|
| PORT06 completo (17, validato) | 6.43 | 3.96% | 8.58 | 1.36% |
| **REALE-only (14, live ora)** | 6.49 | **5.34%** | 8.54 | **1.70%** |
Perdendo i 3 diversificatori PAPER (corr 0.07), il DD del portafoglio reale sale
~3.96→5.34% FULL e 1.36→1.70% OOS, Sharpe sostanzialmente invariato. È il **prezzo di
vedere il risultato reale puro** invece di una curva mista reale+paper: scelta
consapevole dell'utente. I PAPER restano misurati per ri-integrarli quando saranno
eseguibili (SH01 è già stato integrato oggi; i multi-asset attendono capitale ~€20k).
## Note di transizione
- Il ledger `PORT06` (code invariato) ora rappresenta i 14 reali: la curva equity ha
una discontinuità di composizione da questo deploy (atteso).
- I 3 paper ripartono puliti in `portfolio_paper_stats/` con capitale nozionale uniforme
(statistica comparabile); i vecchi status in `portfolio_paper/` restano come storico.
@@ -0,0 +1,56 @@
# 2026-06-09 — Gioco "Blind Traders": 100 agenti ciechi
## Setup
100 agenti LLM (haiku) ricevono due serie anonime **X** e **Y** — in realta'
**BTC** e **ETH** 1h/15m/5m, mai etichettate — e devono proporre UNA regola che
"anticipi" i movimenti per un PnL netto positivo (fee 0.10% RT) con **>=10
trade/mese**. Non sanno cosa siano i dati. L'orchestratore (engine deterministico)
valuta ogni strategia, assegna un punteggio su **PNL + %win**, da' **90 epoche di
elaborazione** (hill-climb dei parametri) e **ogni 10 epoche blocca il 10% meno
profittevole** -> restano i **10 piu' profittevoli**.
Infrastruttura in `scripts/games/`:
- `engine.py` — dati anonimizzati, 6 famiglie segnale (zscore/breakout/ma_cross/
rsi/momentum/pairs), backtester causale fee-aware, scoring (>=10 tpm o squalifica).
- `agent_brief.py` — digest ANONIMO (stat aggregate + finestra normalizzata) + menu.
- `arena.py` — torneo a **3 finestre**: TRAIN (hill-climb), VALID (cull+rank
dell'orchestratore), TEST (OOS puro, mai ottimizzato). Anti-overfit.
- `run_game.py` — carica le 100 spec degli agenti e lancia il torneo.
## Risultato emergente
I 100 agenti ciechi, leggendo SOLO le statistiche anonime (autocorrelazione
negativa, "after_big_move_continues_pct" ~30-40% => le mosse estreme rientrano),
hanno **riscoperto da soli che il mercato e' mean-reverting**: 100/100 reversion,
67 hanno scelto il detector pairs, 30 zscore. Esattamente la lezione storica del
progetto (edge = reversione; pairs ETH/BTC il piu' robusto) — senza sapere che
fosse crypto.
## Classifica finale (top 10) — tutti PAIRS su 15m
Vincitore **agente #91** (15m, pairs market-neutral sul log-ratio X/Y):
- TEST/OOS puro: **PnL +3126%**, **win 77%**, **108.9 trade/mese**, **Sharpe 20.3**
- Full-period: PnL +8052%, win 70%, 94 tpm, Sharpe 12.2 (9604 trade)
- params: lookback 66, entry 1.67σ, exit 1.0σ, max_bars 35
- ipotesi (cieca): "Y altamente reversivo, X/Y log-ratio strong mean-reversion
(-0.43 autocorr), bassa correlazione cross-asset -> pairs market-neutral".
Tutti i 10 finalisti: pairs 15m, TEST Sharpe medio 19.9, tpm 66-109 (>>10).
## Caveat onesti
- Numeri OOS ottimistici: PnL additivo a notional fisso, **niente slippage sulle 2
gambe**, finestra OOS calma, 15m molti trade. Coerente col caveat PR01 del
progetto (Sharpe reale atteso ~4-5, non 20). Il valore del gioco e' il **metodo**
(scoperta cieca + selezione anti-overfit), non il livello assoluto di Sharpe.
- La convergenza su pairs conferma robustezza ma riduce la diversita': i 10 finalisti
sono varianti della stessa idea (ETH/BTC spread). Per un portafoglio servirebbe
diversificare (gia' fatto altrove: fade + honest + shape).
## Re-run "sobrio" con slippage (0.05%/lato)
`GAME_SLIP=0.0005` -> i pairs pagano +0.20% RT extra (4 lati). Lo slippage spinge
l'ottimizzatore verso **meno churn**: tpm dei finalisti 66-109 -> **40-47**, Sharpe
top-10 ~20 -> ~13.5. Vincitore **#43** (15m pairs): TEST PnL **+2091%**, win 77%,
**46.9 tpm**, Sharpe **15.6**. La gerarchia (pairs 15m domina) e la robustezza
reggono lo stress; lo Sharpe reale atteso resta ~4-5 (OOS calmo + PnL additivo).
Log: `data/games/game_slip.log`.
Artefatti: `data/games/tournament_result.json`, `data/games/specs/agent_*.json`,
`engine.set_slippage()` (env `GAME_SLIP`).
@@ -0,0 +1,41 @@
# 2026-06-09 — Gioco "Blind Traders" sessione 2: timing diversi (30m/2h/4h)
Seconda sessione del gioco (vedi `2026-06-09-blind-traders-game.md`), stesso protocollo
(100 agenti ciechi su BTC/ETH anonimi, scoring PNL+%win, ≥10 trade/mese, 90 epoche, cull
10% ogni 10 epoche → 10 finalisti, split a 3 anti-overfit) ma su **timeframe diversi**:
game 1 = 5m/15m/1h; **game 2 = 30m/2h/4h** (medio-lunghi). Engine con resampling aggiunto
(`engine._RESAMPLE`: 30m←15m, 2h/4h←1h). Specs in `data/games/specs2/`, risultato
`data/games/tournament_result2.json`.
## Diversita' proposte (di nuovo: riscoperta cieca della mean-reversion)
100 agenti: **74 pairs, 25 zscore, 1 breakout; 100% reversion**; tf 34/33/33. Come nel
game 1, leggendo solo le statistiche anonime (autocorrelazione negativa del log-ratio,
continuazione post-mossa ~40%) gli agenti convergono sulla reversione senza sapere che
sia crypto.
## Classifica finale — tutti 30m pairs
Vincitore **agente #36** (30m, pairs ETH/BTC):
- TEST/OOS: **PnL +1451%, win 77%, 43.4 trade/mese, Sharpe 12.3**.
- I 10 finalisti sono TUTTI 30m pairs (TEST Sharpe ~12, win 76-77%, tpm 43-49).
## Finding chiave: la regola ≥10 trade/mese e' un FILTRO sul timeframe
Quanti agenti per tf superano la soglia di attivita' + qualita':
| tf | agenti | ≥10 trade/mese | positivi OOS | miglior OOS Sharpe (pnl/win/tpm) |
|---|---|---|---|---|
| 30m | 34 | **34 (100%)** | 26 | **11.6** (1405% 76% 56) |
| 2h | 33 | 29 (88%) | 17 | 6.1 (512% 79% 17) |
| 4h | 33 | **4 (12%)** | 6 | 1.4 (103% 68% 14) |
A 4h solo 4/33 agenti riescono a fare ≥10 trade/mese (le barre sono troppo rade per
la reversione pairs); e l'edge cala col timeframe (Sharpe 11.6→6.1→1.4). Per questo i
finalisti sono tutti 30m.
## Lezione cross-game (game 1 + game 2)
Esiste una **frontiera frequenza-vs-edge**: la regola ≥10 trade/mese mette un *pavimento*
sul timeframe (i lunghi non fanno abbastanza trade), il costo/edge mette un *soffitto* (i
cortissimi sono cost-fragili). Il punto ottimo e' **il timeframe piu' corto con edge ancora
robusto**: game 1 (con 15m disponibile) → vince 15m; game 2 (senza 15m) → vince 30m. Sempre
**ETH/BTC spread reversion**. Coerente con l'analisi di robustezza del 15m
(`2026-06-09-pairs15m-robustezza.md`): piu' corto = piu' trade = piu' edge di backtest, ma
piu' fragile ai costi. Il gioco trova l'edge; la prudenza di deploy (mezza size) gestisce
la fragilita'. Artefatti: `scripts/games/`, `data/games/tournament_result2.json`.
@@ -0,0 +1,47 @@
# 2026-06-09 — Validazione edge credit-spread cerbero-bite (prezzi reali)
## Contesto
cerbero-bite (container accanto, `/opt/docker/cerbero-bite`) vende credit-spread su
ETH (bull-put primario, short delta ~0.18, DTE 18, Quarter-Kelly 13%, PT 50% / stop
2.5x credito / delta-breach 0.30 / vol-stop +10 DVOL / time-stop 7 DTE; testnet,
propose-only). Tune "Profilo B" del 2026-06-09: short delta alzato a 0.18 (da 0.10-0.15)
e `credit_to_width_ratio_min` 0.30->0.08 perche' a delta basso 0 spread erano eleggibili.
Domanda: l'edge regge su un ciclo ETH completo, o e' profittevole solo nei campioni calmi?
Validato con l'infrastruttura opzioni REALE (data/options/, importata da cerbero-bite).
Script riprendibile: `scripts/analysis/cerbero_bite_credit_spread.py`.
## Risultati
1. **Economia d'ingresso reale** (chain, 3145 spread): cw a delta 0.18 = **0.106** (p25 0.085),
eleggibilita' **65%**, short strike **~9.4% OTM** (NON 18% — quello era il vecchio delta basso),
**max-loss/credito = 8.4x**.
2. **Tail model-free** (8 anni ETH reali, cw 0.106, hold-to-expiry, niente modello opzioni):
win-rate 74%, **EV -1.0 crediti/trade, 7/9 anni NEGATIVI**, ETH <-13.4% a 17g (max-loss) il
**17.8%** delle volte. Un max-loss (-8.4cr) cancella 17 PT-winner. Gap 1g p5 = -25% (salta lo stop).
3. **Managed (skew calibrato sulle IV reali)**: win-rate **37%** (delta-breach esce sul 62% dei
trade a piccola perdita), **EV -0.02 cr/trade** (gia' a economia favorevole), worst -4.6 (2023).
2021+ EV -0.022.
## Caveat di calibrazione (TODO aperto)
Il mark mid+skew da **cw 0.228 vs 0.106 reale** -> sovrastima il credito ~2x (manca bid/ask
incrociato sulle 2 gambe + griglia strike reale). Quindi l'EV managed mostrato (-0.02) e' a
economia **2x troppo favorevole**: l'EV vero e' **<=**. Per il numero esatto: modellare bid/ask
reale + griglia (entrambi nella chain) cosi' entry cw -> 0.106. RIPRENDERE da qui.
## Verdetto
- **NON edge robusto su ciclo completo.** Il "+0.48%/mese netto" citato era **artefatto di
finestra calma** (mag-giu 2026, nessun crash): li' il body vince (EV +0.6), ma su ciclo
completo (mesi -13% al 18% di frequenza) e' breakeven-to-negativo in 2 stime indipendenti.
- **L'82% PoP e' ingannevole**: o esci presto sul delta-breach (win 37%, grind di piccole
perdite), o tieni e prendi la coda. Non c'e' la "macchina da 82% di vittorie".
- **Il tune Profilo B PEGGIORA la coda** (vendere a 9.4% OTM mette il max-loss in zona di
pullback ordinario). Strutturale: il mercato non paga per vendere lontano, vendere vicino
espone alla coda.
- **Coda CONCENTRATA con PythagorasGoal**: il gap che salta lo stop = il crash ETH, lo stesso
evento che colpisce lo sleeve fade. I due sistemi non diversificano, concentrano.
- **Azione**: cerbero-bite resta testnet/paper finche' non c'e' un campione che include un crash
vero; valutare un long deep-OTM put (0.33%/mese reale) come cap della coda del bull-put-spread.
## Stato
DA RIPRENDERE: calibrazione esatta credito (bid/ask + griglia) -> EV managed definitivo.
Tutto il resto e' chiuso e documentato.
@@ -0,0 +1,82 @@
# 2026-06-09 — Percorso live 15m per ETH/BTC pairs: COSTRUITO e VALIDATO
Seguito di `2026-06-09-pairs15m-port06-gate.md` (il gate passa, edge reale e non
artefatto flat). Qui si costruisce e VALIDA l'infrastruttura per eseguire il pairs
ETH/BTC a 15m con flat-skip, alla pari del backtest (disciplina validate_worker_pairs).
## 1. Engine canonico (regression-locked)
`scripts/analysis/pairs_research.py`: aggiunti `aligned_ohlc`, `is_flat_ohlc`,
`pairs_sim_flat(..., flat_skip, scan_buffer)`. Regola di uscita **LIVE-REALIZABLE**:
la condizione (|z|<=z_exit O bars>=max_bars) ARMA `exit_ready`; si esce al CLOSE della
PRIMA barra PULITA successiva (mai a un prezzo passato come faceva il prototipo push-back).
- **Regression-lock**: `pairs_sim_flat(flat_skip=False)` == `pairs_sim` ESATTO
(ETH/BTC 1h 1756 trade, 15m 9388 trade, ret/dd/sharpe identici al bit).
## 2. PairsWorker esteso (retrocompatibile)
`src/live/pairs_worker.py`: param `flat_skip`, stato `exit_ready` (persistito), tick
ora fa merge OHLC e rileva le candele flat (O=H=L=C in UNA gamba). Entry saltato su barra
stale; uscita con la stessa regola exit_ready dell'engine. **Default off = comportamento
1h storico invariato** (se mancano le colonne OHLC, flat=False).
## 3. Runner: fetch sub-orario (inerte finche' non c'e' uno sleeve 15m)
`src/portfolio/runner.py`: `_SUBHOURLY={5m,15m,30m}`, `_LOOKBACK_DAYS` esteso; il loop
fetcha DIRETTO da Cerbero i timeframe sub-orari per (asset,tf) (non resamplabili dal 1h) e
un router `_series_for` instrada la serie giusta a ogni worker. Zero impatto sul live
attuale: nessuno sleeve e' 15m → `subhourly_needs` vuoto → ramo morto.
## 4. VALIDAZIONE (validate_worker_pairs.py) — TUTTO OK
Replay bar-per-bar del worker == backtest:
| caso | worker | backtest | match |
|---|---|---|---|
| ETH/BTC 1h | 1756 trd, cap 2.886.616 | 1756, 2.886.616 | **OK esatto** |
| BTC/LTC 1h | 599 trd, cap 16.861 | 599, 16.861 | **OK esatto** |
| **ETH/BTC 15m-flat** | **8452 trd** | **8453 trd** (cap entro 0.15%) | **OK** |
(1 trade di differenza = posizione finale aperta non chiusa nel replay, atteso.)
## 5. Gate finale (engine == worker) — PROMOSSO
`pairs15m_gate_final.py` (corr 1h vs 15m = 0.372, 3201 ingressi flat saltati):
| variante ETH/BTC | FULL Sh | FULL DD | OOS Sh | OOS DD |
|---|---|---|---|---|
| baseline 1h | 6.43 | 3.96 | 8.58 | 1.36 |
| **SWAP 15m-flat** | 7.31 | 3.55 | **9.95** | **1.26** |
| **BLEND 1h+15m** | 7.03 | 3.66 | 9.57 | 1.24 |
Entrambi PROMOSSI (a fee backtest). Caveat slippage del gate precedente invariato → il
BLEND e' la forma raccomandata (meta' allocazione sul 1h pulito, slippage-robusto).
## Stato e attivazione (NON fatta — decisione di deploy)
Tutto il PERCORSO e' pronto e validato, ma il 15m **non e' attivo nel portafoglio live**:
attivarlo cambia il trading reale e va deciso esplicitamente. Per accenderlo:
1. `_defs.py`: aggiungere SleeveSpec pairs ETH/BTC a 15m (tf="15m",
params={n:66,z_in:1.674,z_exit:1.0,max_bars:35,flat_skip:True}) — come SWAP della 1h o
come 2a sleeve (BLEND) sotto il cap PAIRS.
2. `report_families.build_everything` / `sleeves`: l'equity del nuovo sleeve dal
`pairs_sim_flat(tf=15m, flat_skip=True)` (per parita' backtest==report).
3. Shadow smoke su testnet (come `live_smoke_pairs.py`) prima del paper reale.
4. `deploy.sh` (bump+rebuild) — il runner gia' fetcha 15m e passa flat_skip via spec.params.
Test suite: nessuna regressione (1h byte-exact). Artefatti: pairs_research.py,
pairs_worker.py, runner.py, validate_worker_pairs.py, pairs15m_gate_final.py.
## ATTIVAZIONE IN REALE (2026-06-09) — BLEND, mezza size
Deciso: BLEND (sleeve 15m ACCANTO al 1h, non swap). Implementato:
- `_defs.py`: SleeveSpec `PR_ETHBTC_15M` (tf=15m, flat_skip, params.position_size=0.10
= meta' del family PAIRS 0.20) in PAIRS -> entra in PORT04/05/06.
- `report_families.build_everything`: equity da `pairs_sim_flat(tf=15m, flat_skip=True, pos=0.075)`
(mezza size, == intento live) con sid PR_ETHBTC_15M.
- `runner.pos_for_spec`: override PER-SLEEVE (params.position_size) > famiglia > globale.
- **Mezza size perche'** a peso pieno il 15m pesava il 25.8% del rischio PORT06 (vs 9.5% del
1h): dimezzato -> 11.5% vs 10.6%, bilanciato. Disciplina come la cap SHAPE; rispetta il
caveat slippage (il 15m non domina il book).
**PORT06 col BLEND (mezza size)**: FULL Sharpe **6.43->7.20** DD **3.96->3.68**,
OOS Sharpe **8.58->9.66** DD **1.36->1.31**. Migliora tutto.
**Smoke live 15m** (`pairs15m_live_smoke.py`): Cerbero serve candele 15m FRESCHE per
ETH e BTC (ultima barra 0 min fa, flat live 2-3%), worker flat-skip ticca OK. Esecuzione
reale a 2 gambe gia' coperta da `live_pairs_smoke.py` (livello strumento, tf-indipendente).
**Regression-lock aggiornati** (miglioria attesa, non regressione): test_definitions
(17->18 sleeve), test_backtest_parity_cap (FULL 6.47->7.20, OOS 8.82->9.66). Suite verde.
Live: il runner fetcha 15m diretto, costruisce il PairsWorker(flat_skip) col pos 0.10,
e lo esegue reale a 2 gambe (pairs_enabled). Attivazione via deploy (bump+rebuild).
@@ -0,0 +1,89 @@
# 2026-06-09 — ETH/BTC pairs a 15m: gate PORT06 (dal gioco Blind Traders)
## Origine
Il gioco "Blind Traders" (100 agenti ciechi) ha eletto come vincitore una variante
ETH/BTC pairs su **15m** (config #43: n=66 z_in=1.67 z_exit=1.0 max_bars=35). Domanda:
e' un vero miglioramento o un duplicato piu' veloce della sleeve PR01 ETH/BTC gia'
deployata a 1h? Testato sul serio con l'engine di PRODUZIONE `pairs_sim` + gate PORT06.
Script: `scripts/analysis/pairs15m_port06_gate.py`.
## Risultati
- **Parita' OK** (corr 1.00000): l'harness riproduce esattamente il sleeve canonico
PR_ETHBTC → gate affidabile.
- **CORRELAZIONE 1h vs 15m = 0.349** (rendimenti giornalieri). **SMENTISCE la mia
ipotesi iniziale "duplicato ridondante"**: a 15m cattura eventi di reversione DIVERSI
→ e' un diversificatore reale, non una doppia scommessa sullo stesso spread.
- **Robustezza 15m**: griglia n×z_in → **16/16 celle Sharpe>1** (9-12), plateau non picco.
Non e' un punto overfit del gioco.
- **Standalone**: 15m fa 9388 trade (vs 1756 a 1h), Sharpe 11.7 (vs 4.36), DD 54% (vs 48%),
8/9 anni+ . (Le % FULL sono esplose dal compounding pos0.15·lev3 su 9k trade → guardare
Sharpe/DD/anni, non il livello %.)
## Gate PORT06 (pos0.15 lev3 canonico, OOS da 2024-10-12)
| variante ETH/BTC | FULL Sh | FULL DD | OOS Sh | OOS DD |
|---|---|---|---|---|
| **baseline 1h** | 6.43 | 3.96 | 8.58 | 1.36 |
| **SWAP 15m** | 7.64 | 3.49 | **10.39** | **1.26** |
| **BLEND 1h+15m** | 7.30 | 3.63 | 9.95 | 1.24 |
A fee di backtest (0.20% RT/coppia) **entrambe PROMOSSE**: Sharpe su e DD giu' ovunque.
## Stress slippage a livello PORT06 (il vero rischio: 15m = 5× i trade)
| fee_rt | RT/coppia | PORT06 FULL Sh | FULL DD | OOS Sh | OOS DD | std Sh | std oDD |
|---|---|---|---|---|---|---|---|
| baseline 1h | 0.20% | 6.43 | 3.96 | 8.58 | 1.36 | 4.36 | 16% |
| 15m | 0.20% | 7.64 | 3.49 | 10.39 | 1.26 | 11.7 | 13% |
| 15m | 0.40% | 7.04 | 4.08 | 9.78 | 1.45 | 8.5 | 27% |
| 15m | 0.60% | 6.43 | 4.67 | 9.15 | 1.66 | 5.3 | 47% |
**Degradazione graziosa ma reale**: il vantaggio di **Sharpe** sopravvive fino a slippage
pessimista (OOS 9.15 > 8.58 anche a 0.60%), ma il vantaggio di **DD si perde gia' a 0.40%**
(FULL DD 4.08 > 3.96 baseline; standalone oDD esplode 13→27→47%). La regola del progetto
("ri-gateare ogni filtro quando cambiano i costi") qui taglia: la frequenza 5× rende la
sleeve slippage-sensitive.
## Verdetto
- **NON un duplicato** (corr 0.35) e **NON overfit** (16/16 robusto) → la mia liquidazione
iniziale era SBAGLIATA, lo dico chiaro.
- **Passa il gate a fee di backtest, marginale sotto slippage**: migliora Sharpe sempre, ma
sotto slippage realistico (≥0.40% RT) peggiora leggermente il DD di portafoglio.
- **Due rischi di produzione NON ancora quantificati**: (a) qualita' dati ETH 15m (14-30%/anno
candele flat O=H=L=C → fill non eseguibili che gonfierebbero il backtest), (b) fill/liquidita'
reale a 2 gambe a 15m (5× ordini). Il worker pairs e' validato a 1h, non a 15m.
**Raccomandazione**: NON swap diretto in live. Candidato promettente → percorso forward:
preferire il **BLEND 1h+15m** (tiene il DD pulito del 1h e raccoglie il rendimento
decorrelato del 15m) **dopo** un check sull'impatto delle candele flat 15m sui pairs.
Allineato a come il progetto tratta FR01 (robusto ma non deployato finche' non domina pulito).
Resta come record di ricerca; deploy solo se il check flat-candle e' pulito.
## CHECK FLAT-CANDLE (pairs15m_flatcheck.py) — PULITO
Rischio: ETH 15m ha molte candele flat (O=H=L=C) → close stale che gonfia z-score →
reversione FINTA non eseguibile. Test:
- **Prevalenza**: ETH 15m **16.4% medio** (fino 30% nel 2022); BTC 15m solo 3.5%. Reale.
- **Fill toccati**: 12.9% degli entry e 15.2% degli exit cadono su una barra flat.
- **Test decisivo** (entry/exit SOLO su barre pulite, non-flat in entrambe le gambe):
rimuove 11.2% dei trade, **Sharpe trattenuto all'83%** (11.74→9.70; OOS Sharpe 18.4).
Se l'edge fosse un artefatto flat, filtrando crollerebbe → **NON crolla. NON e' artefatto.**
- **Gate PORT06 col 15m FLAT-FILTRATO** (corr 1h vs 15m-flat = 0.366, ancora decorrelato):
- SWAP 15m-flat: FULL 7.32/3.55, OOS **9.99/1.26** → PROMOSSO
- BLEND 1h+15m-flat: FULL 7.05/3.66, OOS **9.60/1.24** → PROMOSSO
## Conclusione (3 box su 4 puliti)
✅ NON duplicato (corr 0.35-0.37) ✅ robusto (16/16) ✅ NON artefatto flat (83% Sharpe)
⚠️ slippage-sensitive: a fee backtest passa pulito; a slippage ≥0.40% RT il vantaggio di
Sharpe regge ma il DD-edge si assottiglia. Il **BLEND** mitiga (meta' allocazione resta sul
1h pulito e slippage-robusto) → e' la forma deployabile.
## Realta' del deploy (perche' NON tocco ancora il live)
Il gate passa a livello BACKTEST. Ma il live NON puo' eseguire un sleeve 15m oggi:
- la live pairs gira SOLO a 1h (`PairsWorker`, validato da `validate_worker_pairs` a 1h);
il runner risampla a 1h/4h/1d, non gestisce un leg pairs a 15m.
- un BLEND richiede DUE sotto-sleeve ETH/BTC (1h + 15m) dentro il cap PAIRS, e il
**flat-skip va replicato nel worker live** (altrimenti il live tradera' le barre stale che
il backtest esclude → divergenza backtest-vs-live, la classe di bug che il progetto teme).
Editare `_defs.py` cambierebbe solo il backtest/report, NON il live → sarebbe ingannevole.
**Percorso deploy corretto** (da confermare): (1) estendere `PairsWorker`/runner al 15m +
flat-skip; (2) `validate_worker_pairs` a 15m (replay == backtest esatto); (3) aggiungere lo
sleeve 15m sotto il cap PAIRS; (4) shadow su testnet prima del paper. Finche' (1)-(2) non
sono fatti e validati, resta **record di ricerca PROMOSSO ma non live**.
@@ -0,0 +1,45 @@
# 2026-06-09 — ETH/BTC 15m: analisi di robustezza (il limite è il COSTO)
Domanda: lo sleeve `PR_ETHBTC_15M` (attivato in real, v1.1.16) è robusto? Risposta dai
dati: **sì su parametri, tempo e qualità-dati; fragile sui COSTI** — e quel limite è
strutturale (alta frequenza). Engine `pairs_research.pairs_sim_flat` (live-realizable).
## ✅ Robusto su parametri, tempo, dati
- **Plateau parametrico**: griglia n×z_in (40-80 × 1.5-2.5) → **16/16 celle Sharpe>1**
(range 9-12). Non un picco overfit. (`pairs15m_port06_gate.py`)
- **Consistenza temporale**: 2018-2021 +1874% (3/4 anni+, solo 2018 negativo);
2022-2026 +12958% (**5/5 anni+**); **OOS ultimo 30% Sharpe 17.6, DD 13%, +**. L'edge
è distribuito, non un singolo regime.
- **Non artefatto dei dati**: filtrando le candele flat ETH 15m (16% storico) resta
l'**83% dello Sharpe** (`pairs15m_flatcheck.py`).
- **Decorrelato dal 1h** (corr rendimenti giornalieri **0.37**): segnale diverso, non un relabel.
## ❌ Fragile sui COSTI — il vero tallone (frequenza 5× il 1h)
Sharpe in funzione del costo all-in RT/coppia (fee + slippage):
| costo RT/coppia | **15m Sharpe** | 1h Sharpe |
|---|---|---|
| 0.20% (reale, 1×) | **9.34** | 4.36 |
| 0.40% (2×) | 6.15 | 3.52 |
| 0.60% (3×) | 2.95 | 2.68 |
| 0.80% (4×) | **0.24** | 1.84 |
| 1.20% (6×) | 6.63 | 0.16 |
**Il 1h regge ~6× i costi; il 15m va negativo già a ~4×.** Tanti trade piccoli (8453 vs
1756) → margine di costo sottile. Lo **Sharpe 9.34 è un numero a costo basso**: appena lo
slippage reale a 2 gambe porta l'all-in verso 0.40-0.60%, scende a 3-6 (ancora positivo,
ma l'edge enorme del backtest è in gran parte illusione da bassi costi).
## Implicazioni (coerenti con la config deployata)
1. **La mezza size era giusta**: non si dà al 15m il peso che il backtest a costo basso
suggerirebbe. È un blend-tilt, non una scommessa.
2. **NO allo swap** (togliere il 1h, tenere solo 15m): sostituirebbe l'àncora cost-robust
(1h, regge 6×) con la sleeve cost-fragile. Confronto PORT06: swap OOS 9.95 vs blend 9.66
— +0.3 di Sharpe di backtest pagati in robustezza reale. Non vale.
3. **Giudice finale = ledger reale shadow**: misurerà lo slippage vero a 15m. Soglia di
lettura: all-in <0.40% (Sharpe ≥6) → ottimo diversificatore, valutare size piena;
verso 0.60%+ → vale appena la mezza-size che ha (già protetto).
**In una riga**: robusto come *segnale*, fragile come *esecuzione* → sta nel portafoglio a
metà size accanto al 1h, non al suo posto. Riproducibile: fee-sweep + sub-periodo +
OOS via `pairs_sim_flat`; plateau/flat-check negli script `pairs15m_*.py`.
@@ -0,0 +1,73 @@
# 2026-06-09 — Statistiche per-anno di tutte le sleeve attive in REAL
Snapshot post-attivazione BLEND ETH/BTC 15m (v1.1.16). Breakdown per-anno delle **15
sleeve che eseguono ordini reali** su Deribit testnet (escluse le 3 PAPER multi-asset
TR01/ROT02/TSM01). Engine **path-live**: EXIT-16 + filtro trend 3.0 per le fade,
walk-forward expanding per SH01, flat-skip per il pairs 15m. **PnL% = somma dei
rendimenti netti per-trade levered ×3, fee 0.10-0.20% RT incluse** — NON il contributo
al portafoglio (che applica sizing pos, cap-weighting, leva 2x, ribilancio 1D); serve a
confrontare edge grezzo e DD per sleeve.
Riproducibile: famiglie pairs via `pairs_research.pairs_sim`/`pairs_sim_flat`; fade/DIP/SH01
via le funzioni `stats_fades`/`stats_dip`/`stats_sh01` di `scripts/analysis/make_strategy_doc.py`.
## FADE (6) + DIP01 — PnL% per anno (n trade)
| anno | MR01 BTC | MR01 ETH | MR02 BTC | MR02 ETH | MR07 BTC | MR07 ETH | DIP01 BTC |
|---|---|---|---|---|---|---|---|
| 2018 | 78 (48) | 85 (42) | +69 (127) | +10 (127) | 12 (38) | +15 (25) | 78 (79) |
| 2019 | 84 (57) | +21 (46) | +18 (118) | +120 (114) | +55 (52) | 40 (35) | 104 (62) |
| 2020 | 29 (68) | 35 (55) | +100 (112) | 94 (135) | 11 (53) | +39 (43) | +67 (56) |
| 2021 | +326 (82) | +238 (60) | +332 (136) | +160 (127) | +245 (45) | +72 (24) | +315 (69) |
| 2022 | +567 (108) | +749 (76) | +728 (106) | +650 (91) | +446 (82) | +472 (58) | +407 (83) |
| 2023 | +318 (122) | +248 (73) | +235 (96) | +524 (146) | +168 (67) | +337 (58) | +228 (83) |
| 2024 | +413 (113) | +888 (126) | +868 (149) | +1952 (180) | +377 (75) | +645 (87) | +332 (81) |
| 2025 | +368 (90) | +361 (86) | +386 (136) | +931 (151) | +190 (60) | +226 (55) | +303 (85) |
| 2026* | +69 (29) | 9 (24) | +88 (37) | +25 (41) | +58 (17) | +40 (17) | +7 (29) |
| **TOT** | +1870 (717) | +2376 (588) | +2823 (1017) | +4278 (1112) | +1517 (489) | +1806 (402) | +1476 (627) |
| **maxDD%** | 32 | 23 | 19 | 31 | 12 | 23 | 37 |
## SHAPE SH01 (2) — PnL% per anno (n trade)
| anno | SH01 BTC | SH01 ETH |
|---|---|---|
| 2018 | 66 (237) | +74 (239) |
| 2019 | +88 (318) | 19 (365) |
| 2020 | +194 (240) | 293 (219) |
| 2021 | +301 (224) | +67 (146) |
| 2022 | +64 (142) | +79 (91) |
| 2023 | +17 (118) | +21 (17) |
| 2024 | +110 (144) | +108 (47) |
| 2025 | +77 (85) | +540 (108) |
| 2026* | +59 (23) | 30 (25) |
| **TOT** | +845 (1531) | +547 (1257) |
| **maxDD%** | 23 | 61 |
## PAIRS (6) — PnL% per anno (n trade) | 15m a mezza size (pos 0.075)
| anno | ETH/BTC 1h | LTC/ETH | ADA/ETH | BTC/LTC | ETH/SOL | ETH/BTC 15m |
|---|---|---|---|---|---|---|
| 2018 | 343 (177) | — | — | — | — | 492 (913) |
| 2019 | +233 (198) | — | — | — | — | +792 (963) |
| 2020 | +323 (211) | — | — | — | — | +452 (867) |
| 2021 | +549 (202) | — | — | — | — | +1122 (949) |
| 2022 | +1344 (206) | +292 (45) | +899 (157) | +328 (58) | +1243 (147) | +2136 (898) |
| 2023 | +464 (213) | +56 (94) | +341 (173) | +61 (106) | +125 (168) | +689 (976) |
| 2024 | +1661 (253) | +1024 (195) | +1078 (225) | +638 (186) | +1302 (199) | +6313 (1353) |
| 2025 | +1173 (225) | +962 (196) | +1046 (187) | +686 (178) | +1264 (202) | +3664 (1123) |
| 2026* | +61 (71) | +279 (76) | +140 (71) | +265 (71) | +270 (88) | +155 (411) |
| **TOT** | +5464 (1756) | +2614 (606) | +3504 (813) | +1978 (599) | +4204 (804) | +14832 (8453) |
| **maxDD%** | 48 | 14 | 19 | 21 | 24 | 34 |
| **Sharpe** | 4.36 | 4.22 | 4.90 | 2.72 | 4.61 | 9.34 |
\* 2026 parziale (fino al 28 mag). Coppie alt (LTC/ADA/SOL) hanno storia solo dal 2022.
## Aggregato PORT06 (BLEND attivo, 18 sleeve def.)
**FULL Sharpe 7.20 / DD 3.68% / CAGR 76% — OOS Sharpe 9.66 / DD 1.31%**. Per anno:
2021 +19.0% · 2022 +73.3% · 2023 +39.8% · 2024 +220.1% · 2025 +110.5% · 2026* +7.7%.
Nessun anno negativo dal 2021.
## Letture
- Il PnL% grezzo del 15m e' grande perche' fa ~5x i trade del 1h (8453 vs 1756), non
per-trade: il confronto equo e' Sharpe (9.34 vs 4.36) e DD (34% vs 48%). Nel portafoglio
entra a mezza size -> ~11.5% del rischio (== il 1h).
- MR02 ETH = singolo motore piu' forte (+4278%, 2024 +1952%); MR07 il piu' selettivo
(DD 12-23%); SH01 ETH il piu' rischioso (DD 61%, coda 2020 293%) -> mitigato dal cap SHAPE.
- I 3 book multi-asset (TR01/ROT02/TSM01) restano PAPER, fuori da questo elenco.
@@ -0,0 +1,49 @@
# 2026-06-09 — XS01: reversione cross-sectional (famiglia nuova, trovata + deployata PAPER)
## Origine
Dopo aver scartato (alla cieca, coi giochi) trend/breakout/seasonal/opzioni/funding come
rumore o EV, ho cercato io un meccanismo *diverso* dalla mean-reversion pairwise. Trovato:
**XS01 — reversione CROSS-SECTIONAL** su 8 asset (BTC/ETH/LTC/ADA/SOL/BNB/XRP/DOGE).
## Meccanismo
Ogni HOLD=12 ore: classifica gli 8 asset per rendimento su LB=48 ore, pesi
w = (ret media_cross-section), normalizzati a gross 1 → **long i perdenti relativi /
short i vincenti**, market-neutral. Roll non sovrapposto (entry-to-entry = hold+1 barre).
Fee 0.10% RT/book. Cattura il FATTORE reversione trasversale, distinto dai pairs (pairwise).
## Verifica (engine canonico `scripts/strategies/XS01_cross_sectional.py`)
- **No look-ahead** verificato (segnale invariato perturbando il futuro).
- **Robusto**: plateau OOS Sharpe **23.9** su lb 1272 × hold 624.
- **Scorrelato**: corr **0.006 / 0.035** da PR01 ETH/BTC, 0.028 dai fade → diversificatore.
- Per-anno (entry): 2022 +34, 2023 +6, 2024 +21, **2025 +225**, 2026 +85 (5/5 anni+).
- **Caveat**: edge concentrato sul 2025; cost-sensitive (muore ~0.35% RT/book); 8 gambe;
storia dal 2022 (no 2018-2020).
## Worker validato (== backtest esatto)
`src/live/xsec_worker.py` `CrossSectionalWorker`: book market-neutral che rolla ogni HOLD
barre, stessa formula pesi e cadenza dell'engine. `validate_xsec_worker.py`: replay
bar-per-bar == backtest **ESATTO** (worker 4993/1427 trade/49.8% == backtest 4993/1427/49.8%).
Bug risolto: il primo prototipo rollava 1 barra troppo tardi (cooldown extra) → rimosso,
guard a lb+1, entry-to-entry = hold+1.
## Gate PORT06 — PROMOSSO (con asterisco)
| | corr | FULL Sh | FULL DD | OOS Sh | OOS DD |
|---|---|---|---|---|---|
| ATTUALE (19→ senza XS01) | — | 7.20 | 3.68 | 9.66 | 1.31 |
| **+XS01** | 0.006 | **7.34** | **3.46** | **10.07** | 1.48 |
Migliora 3 metriche su 4 (OOS Sharpe **+0.41**, il salto più grande dal 15m; FULL DD giù).
Unico neo: OOS DD +0.17pp. Risk-contrib XS01 solo **2.2%** (diversificatore a bassa vol).
## Deploy (v?, 2026-06-09) — PAPER
8 gambe → niente esecuzione reale (come TR01/ROT02/TSM01) → XS01 gira **PAPER**
(`paper_sleeves`), fuori dal pool, raccoglie statistica forward. Wiring: `_defs.XSEC` in
PORT06 (19 sleeve, family XSEC via prefix "XS"), `build_everything` (equity da xsec_sim),
`runner` kind="xsec" → CrossSectionalWorker, `asset_days` ora include i paper (fix: gli alt
BNB/DOGE/XRP ora vengono fetchati anche per TR01/ROT02/TSM01). Regression-lock aggiornati
(18→19 sleeve, FULL 7.20→7.34, OOS 9.66→10.07, DD 3.68→3.46). 93 test verdi.
**Direzione futura:** se la statistica forward conferma, costruire l'esecuzione reale a
N gambe (oggi inesistente) per portarlo nel pool. Per ora: candidato validato che gira
PAPER e si osserva. Artefatti: `scripts/strategies/XS01_cross_sectional.py`,
`src/live/xsec_worker.py`, `scripts/analysis/{validate_xsec_worker,xsec_port06_gate}.py`.
+46
View File
@@ -0,0 +1,46 @@
# 2026-06-10 — FC01 funding-carry market-neutral: SCARTATA (il carry non paga le fee)
## Idea
Carry NEUTRALE sul funding Deribit (meccanismo mai esplorato: W12 era lo short
direzionale su funding alto, bocciato): short della gamba col funding alto /
long della gamba bassa (ETH vs BTC, dollar-neutral), incassando il differenziale
di funding con esposizione residua = solo lo spread ETH/BTC.
Dati REALI: `data/regime/{btc,eth}_funding.parquet` — funding orario effettivo
dic 2019 → giu 2026 (56.938 ore), `interest_1h` + index_price.
Harness: `scripts/analysis/funding_carry_research.py` (causale: decisione al
close t, accrual da t+1; fee 0.10% RT per gamba; TRAIN ≤2023-11-01 / OOS dopo).
## Numeri che uccidono l'idea (prima ancora del backtest)
- Funding annualizzato: BTC mean +7.2% / med +0.3%; ETH +5.2% / +0.05% —
la MEDIANA è ~zero: il funding alto è episodico, non strutturale.
- Spread ETHBTC: p10 19% / p90 +12.6% annualizzato, MA autocorr del
24h-smooth a 72h = 0.20 → poca persistenza.
- **Episodi |spread|>10% ann: durata mediana 19h** (p90 60h) → carry
incassabile per episodio ≈ 10%/8760×19h ≈ **0.02-0.05%**, contro **0.20%**
di fee (2 gambe). Strutturalmente morto: il carry è un ordine di grandezza
sotto i costi, e il price-leg dello spread ETH/BTC (vol ben maggiore del
carry) domina il PnL.
## Backtest (conferma)
- FC-A spread-carry, griglia smooth {24,72,168}h × thr {5,10,20}% ann:
TRAIN quasi tutto negativo (Sharpe 0.6…+0.03), OOS negativo ovunque
tranne una cella (s24 thr5: OOS +38% con TRAIN 45% = rumore/sign-flip).
Sweep fee: negativa anche a 0.05% RT/gamba. Annuale: 2021 93%, nessuna
stabilità.
- FC-B direzionale single-asset (confronto onesto): negativa ovunque,
riconferma W12.
## Verdetto
SCARTATA. Su Deribit BTC/ETH il funding non è un carry harvestabile: troppo
piccolo, troppo breve, e l'hedge cross-asset introduce più rischio del carry
che raccoglie. Eventuale rivisita SOLO se: (a) si aggiungono alt ad alto
funding cronico (Hyperliquid, registry già validato) dove i livelli sono
5-10x, o (b) si usa il funding come FEATURE/gate di strategie esistenti
(es. filtro sulle entry PR01) invece che come fonte di PnL.
Script: `scripts/analysis/funding_carry_research.py` (resta come record negativo).
+113
View File
@@ -0,0 +1,113 @@
# 2026-06-10 — Gioco "Blind Traders" sessione 3: GRID TRADERS (regola: STRATEGIA_GRIGLIA.md)
## Setup
Terza sessione del gioco dei trader ciechi, con una regola nuova: ogni agente deve
implementare la strategia descritta in `STRATEGIA_GRIGLIA.md` (grid trading a griglia
geometrica). 100 agenti (haiku, via Workflow) ricevono SOLO un digest anonimo di due
serie X/Y (in realtà BTC/ETH, mai rivelato) sul loro timing assegnato
(25×15m, 20×30m, 20×1h, 15×2h, 12×4h, 8×1d) + 5 archetipi di stile a rotazione
(prudente / aggressivo / asimmetrico-rialzista / asimmetrico-ribassista / data-driven),
e propongono la CONFIGURAZIONE della griglia: `range_down/up %`, `grid_levels`,
`sl_buf`, `tp_buf`, `max_bars`, serie.
Infrastruttura nuova in `scripts/games/`:
- **`grid_engine.py`** — backtest deterministico, causale, fee-aware della spec:
griglia geometrica `ratio=((1+ru)/(1-rd))^(1/L)` costruita sul close di deploy,
capitale 1/L per livello (§3.3), buy su attraversamento ↓ di un livello non
riempito, sell del livello su attraversamento ↑ del successivo (§5.2), SL sotto
il range e TP sopra che liquidano tutto (§6), redeploy a fine episodio
(SL/TP/max_bars). **Vincolo break-even §4 implementato alla lettera**: passo
≤ 1.5×costo RT → il motore si rifiuta di partire (`refused`, fitness 2e6);
`_normalize` dell'arena riduce i livelli al massimo legale. Fill intrabar lungo
il percorso O→L→H→C / O→H→L→C; fee 0.10% RT per round-trip + slippage opzionale.
- **`grid_arena.py`** — torneo identico alle sessioni 1-2: split 60/20/20
TRAIN/VALID/TEST, 90 epoche di hill-climb sul TRAIN, cull del 10% in VALID ogni
10 epoche → 10 superstiti; TEST = OOS puro mai ottimizzato.
- **`grid_brief.py`** — digest anonimo con statistiche per dimensionare una griglia:
escursione max/min rolling (w100/500/2000, mediana e p90) e probabilità di fuga
da un range ±5/10/20% entro 500 barre.
## Esito
`data/games/grid_result.json` (+ log `grid_tournament.log`). 100/100 spec da agenti
reali (nessun sostituto random).
- **Alla proposta, 80/100 agenti scelgono X (=BTC)**: dai soli numeri anonimi capiscono
che la griglia sopravvive meglio sulla serie meno volatile (escape ±20% in 500 barre:
BTC 34.5% vs ETH 53.2% a 1h).
- **L'evoluzione ribalta la scelta: tutti i 10 superstiti finiscono su Y (=ETH)** —
nel periodo VALID/TEST la vol più alta di ETH paga di più i round-trip, e il rischio
trend è gestito non dal range stretto ma dalla FORMA della griglia (sotto).
- **Convergenza fortissima della forma** (9/10 superstiti): griglia **asimmetrica
ribassista** — range profondo sotto (13/20%), corto sopra (+4/+8%), **livelli al
minimo (4)** → passo largo ~4.5-5.5%, SL buffer profondo (5-15%), max_bars lunghi.
Tradotto: **compra i dip di ETH in 4 tranche distanziate ~5%, rivendi ogni tranche
al rimbalzo di un passo, stop catastrofale a ~25/30%**. Il gioco ha ri-scoperto
per la terza volta la mean-reversion (qui in forma di dip-buying a tranche), e ha
imparato da solo la lezione anti-fee: meglio pochi passi larghi che griglie fitte.
**Vincitore: agente #11, ETH 15m**, griglia 17.1%/+4.6% × 4 livelli, SL buf 12.4%,
TP buf 4.8%, max 2143 barre. TEST(OOS): **PnL +891% (additivo), win 97%, 38.5
trade/mese, Sharpe 10.1**. FULL 2018-2026 (include bear 2018 e 2022): +4284%, Sharpe 9.6.
Stress slippage TEST: 0.05%/lato → 871%, 0.10%/lato → 850% (il passo ~5.4% ⋙ costi).
## Caveat onesti (perché NON è un candidato deploy così com'è)
1. **Il PnL è additivo per-trade e non misura il drawdown UNREALIZED**: l'engine
somma i round-trip realizzati; mentre la griglia tiene 4 tranche dentro un
drawdown 17%, l'equity vera è sott'acqua (fino a ~15% di episodio + SL 25/30%
quando scatta). Il win-rate 97% è il profilo classico della griglia: tante
micro-vincite, perdite rare ma grandi (stessa famiglia di rischio del
short-vol/martingala). Per un gate serio servirebbe l'equity curve mark-to-market.
2. **Long-only su ETH con VALID/TEST 2023-2026** (regime rialzista/oscillante):
l'asimmetria 17/+4.6 è anche un fit al regime. Il FULL positivo (bear inclusi)
è incoraggiante ma il grosso del PnL resta nei periodi di reversione rialzista.
3. Le candele flat ETH 15m (14-30%/anno) e i fill intrabar "al livello" condividono
i caveat noti del progetto (bias pro-stop-stretti dell'engine intrabar; qui gli
stop sono larghi, quindi l'effetto è minore).
## Lezioni
- La spec STRATEGIA_GRIGLIA.md è implementabile in modo causale e onesto; il suo
vincolo break-even (§4) è esattamente la "lezione fee" del progetto, e il torneo
l'ha confermato spingendo i livelli al minimo (passo massimo).
- La griglia evoluta è un parente povero delle fade MR già in live: stesso edge
(reversione ETH), ma incassato con inventory risk crescente invece che con
TP/SL per-trade. Non sostituisce le MR; eventuale interesse solo come variante
"a tranche" da gateare con equity mark-to-market (e confronto con MR01/MR02 a
parità di rischio) PRIMA di pensarci per il PORT06.
## GATE "si puo' inserire?" (stessa sera) — NO-GO: edge = artefatto delle wick testnet
`scripts/analysis/grid_game_gate.py`: engine **mark-to-market** dedicato (equity
per barra = capitale + inventario al close, SL gap-aware, flat-skip, fee 0.10% RT),
metriche standard del progetto + gate PORT06. Primo passaggio ingannevole: standalone
WINNER 15m FULL Sharpe 5.61 DD 15.8%, corr max coi sleeve 0.34, plateau 16/16,
e il criterio formale promuoveva il half-size (OOS Sh 10.07->10.12, DD 1.48->1.36).
MA il breakdown annuale mostrava **+4946% nel 2022** (ETH 70%): impossibile.
**Verifica avversariale decisiva**: il feed Deribit (testnet) e' pieno di **spike
print** — ETH 15m ha 1064 barre con wick ≥6% sotto i close adiacenti che rientrano
subito (fino a 19% in 15 minuti; BTC ha spike a 54% nel feb 2024). La griglia
intrabar compra su quelle wick FINTE e rivende al rimbalzo: free money in sim,
infillabile dal vivo. Stress **close-only** (fill solo su attraversamento del
close): WINNER 15m CAGR 1544%->21%, Sharpe 5.61->0.92, OOS +2738%->+32%,
trades 3803->1156; top3 1h CAGR -1.7%, OOS 18%. **~99% dell'edge vive nelle
wick.** La versione onesta (Sharpe 0.92, DD 27%) e' molto sotto ogni sleeve
deployato → **NON inseribile**, in nessuna size.
Lezioni aggiuntive:
- Il torneo ha massimizzato esattamente l'artefatto: la convergenza su ETH 15m
range profondo era la firma del **wick harvesting**, non di un edge.
- I motori intrabar dei giochi (`grid_engine`, e in misura minore i TP intrabar
di `engine.py`) vanno SEMPRE stressati con una variante close-only prima di
promuovere qualunque vincitore: aggiungere il check ai prossimi giochi.
- Gli sleeve in produzione NON sono toccati da questo artefatto nello stesso modo:
pairs entra/esce sui close, le fade hanno EXIT-16 close-confirm sullo SL, e
soprattutto il ledger REAL-TRUTH usa i fill reali (che gli spike non fillano).
Artefatti: `scripts/games/grid_{engine,arena,brief}.py`, spec agenti in
`data/games/specs_grid/`, digest `data/games/grid_digests.json`, risultato
`data/games/grid_result.json`, log `data/games/grid_tournament.log`, gate
`scripts/analysis/grid_game_gate.py` (param `close_only` per lo stress anti-wick).
@@ -0,0 +1,57 @@
# 2026-06-10 — REAL-TRUTH: il ledger segue i fill reali (sim → diagnostica)
## Richiesta
L'utente vuole che sim e reale coincidano — o meglio, che il sim non esista come
verità: «voglio vedere dati che hanno avuto movimento reale su exchange». La
ricognizione dei 77 eventi reali (dal 2026-06-03) ha mostrato che il gap
sim/reale NON era slippage (piccolo, es. 0.15 bps su un TP) ma **contabile**:
1. **Ledger separati.** Il `capital` che guida portafoglio/ribilanci/sizing era
aggiornato dal PnL SIM; il PnL reale finiva in un `real_capital` parallelo che
non guidava nulla. MR01 BTC: sim +28.93 vs reale +1.98 sugli stessi 4 trade.
2. **Prezzi sim da candele testnet** (spike print, es. 2026-06-07 sim short BTC a
65266.5 con mark reale 62395): il sim bookava PnL che il reale non vede. Con
il ledger sim come verità, l'equity del portafoglio accumulava questa fantasia.
Nota: il *notional* reale era GIÀ derivato dalla formula sim
(`capital·ps·lev` passato a `_real_open`/`_real_open_pair`) — il punto 1 della
richiesta (sizing allineato) si chiude da solo una volta che `capital` è reale.
## Implementazione (REAL-TRUTH)
- `StrategyWorker` e `PairsWorker` accettano `real_truth: bool` (default False =
shadow storico). Con flag attivo e esecuzione abilitata:
- `_real_close`/`_real_close_pair` ritornano `(real_pnl, applied)`;
`applied=True` se ci sono fill reali (o chiusura verificata).
- `_close_position`/`_close` chiamano la chiusura reale PRIMA dell'update
ledger: `capital += real_pnl` (fee reali incluse); `is_win = pnl_reale > 0`.
- Il sim resta nel log CLOSE come diagnostica: `pnl_source` ("real" |
"sim_fallback"), `sim_pnl`, `real_pnl`.
- **Fallback al sim** SOLO se il trade reale non è mai esistito/fillato
(REAL_OPEN_FAIL, fill zero) — dichiarato nel log, mai silenzioso.
- Runner: `overrides.execution.real_truth` (yml) → `build_worker_for(...,
real_truth=)`. `portfolios.yml`: **`real_truth: true`**.
- Conseguenza a catena: equity ledger → pesi → allocazioni → notional dei
prossimi ordini derivano ora dai soldi veri sul conto. Il `real_capital`
parallelo resta come ledger puro-reale di confronto.
## Test
`tests/portfolio/test_real_truth.py` (6 test): capital segue i fill reali
(single-leg e pairs), una divergenza sim-win/reale-loss viene contata come LOSS,
fallback sim dichiarato su REAL_OPEN_FAIL/leg-fail, e modalità shadow invariata
senza flag. Suite completa: 99 passed.
## Limiti onesti
- I multi-asset (TR01/ROT02/TSM01/XS01) restano sim per costruzione (paper
sleeves fuori dal pool, capitale insufficiente per i book multi-leg).
- La STORIA del ledger non è riscritta: l'equity attuale (~2154) ingloba il PnL
sim accumulato fino a oggi (reale realizzato era ≈ 15.6 dal 3/6). Da ora la
divergenza smette di accumularsi; se si vuole un azzeramento (reset equity al
conto reale) è un'operazione separata e deliberata.
- Le DECISIONI di trading (entry/exit) restano guidate dai prezzi del feed
(candele testnet): real-truth corregge la contabilità, non i segnali. Gli
spike print del feed possono ancora generare entry/exit subottimali — ma ora
il loro effetto si misura in PnL reale, non in PnL immaginario.
@@ -0,0 +1,51 @@
# 2026-06-10 — XS01 dispersion-gate: PROMOSSO e LIVE (entry solo con dispersione da fare rientrare)
## Domanda
L'edge di XS01 (reversione cross-sectional 8 asset) era concentrato (2025 domina,
2023 quasi piatto) e cost-sensitive. La reversione cross-sezionale va accesa solo
quando c'e' dispersione da far rientrare?
## Metodo (anti multiple-testing): `scripts/analysis/xs01_dispersion_gate.py`
3 feature di regime CAUSALI calcolate dallo stesso panel closes (nessun feed
esterno): `g_disp` = std cross-section del momentum lb (la grandezza che si fada),
`g_corr` = correlazione media pairwise 72h (identita' della varianza dell'indice),
`g_vol` = vol BTC 168h. Diagnostica per quintili (quintili dal TRAIN, 70/30) sul
net per-trade dell'engine canonico NON gateato, TRAIN e OOS separati: si procede
solo con relazione monotona e concorde nelle due finestre.
## Esito diagnostica
- **g_disp: monotona e concorde** — Q1 NEGATIVO (10 bps TRAIN / 8 OOS) →
Q5 +26/+280. Senza dispersione i trade sono solo fee. PROMOSSA.
- g_corr e g_vol: non monotone / segno incoerente → BOCCIATE (niente fishing).
## Gate (sweep soglie = percentili TRAIN, side dal TRAIN)
Plateau pieno p30-p70, niente picco: TRAIN Sh 1.51 → 2.0-2.3, OOS Sh 5.73 →
6.2-7.5. Scelta **p50 (disp_min = 0.0313)**, ~47% delle ore aperte:
- Standalone: trade 1427→859 (40% turnover → meta' fee), win 50→53%,
**Sharpe 2.50→3.46**, DD 16.2→15.8%. **Ogni anno migliora**: 2022 +34→+40,
2023 +6→+28, 2024 +21→+44, 2025 +225→+237, 2026 +85→+108 — risolve la
concentrazione, il punto debole della validazione originale.
- Fee stress 2x (0.20% RT/book): OOS Sh 6.76 — la cost-sensitivity e' mitigata
(il gate taglia proprio i trade che pagavano fee senza edge).
- **Gate PORT06** (swap equity sleeve): FULL Sh 7.34→7.41 DD pari,
**OOS Sh 10.07→10.37 DD 1.48→1.47** → PROMOSSO (criterio standard).
## Implementazione (solo path LIVE, come trend/hurst sulle fade)
- `src/live/xsec_worker.py`: param opzionale `disp_min` (None = off), check in
`_open_book` su `nanstd(logC[i] logC[ilb])`. Default off → la validazione
`validate_xsec_worker` (replay == backtest) resta esatta.
- `src/portfolio/runner.py`: pass-through di `disp_min` (il runner costruiva il
dict params esplicitamente e l'avrebbe perso).
- `scripts/portfolios/_defs.py`: `disp_min: 0.0313` nella spec XS01.
- Il backtest canonico (`build_everything`) resta NON filtrato → il live fara'
meglio del backtest, coerente con le altre guardie.
Unit check: gate blocca panel piatto / apre panel disperso / default off invariato.
99/99 test. Nota macro della giornata: FC01 funding-carry SCARTATA
(diario separato) — il protocollo promuove ~1 idea su molte, come deve.
+86
View File
@@ -0,0 +1,86 @@
# 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.
+92
View File
@@ -0,0 +1,92 @@
# 2026-06-11 — Audit di sistema + verità contabile sul netting (v1.1.24)
Seconda tornata della giornata (dopo lo sweep strategie): analisi del SISTEMA live
(esecuzione, stato, feed, processo) con 3 indagini parallele + fix.
## Audit live (dal reset 2026-06-10T21:24Z)
- **Churn phantom-TP quantificato**: 20 round-trip fantasma sui fade ETH (2 finestre,
13:06Z e 17:32-17:58Z, feed che stampava wick ~1640 con mercato a 1675-90 per 25 min).
Somma real_pnl 2.35 USD vs sim_pnl +80 USD: il real-truth ledger ha contenuto, il gate
TP_PHANTOM (v1.1.23) copre il pattern da stasera.
- **Il conto NON quadrava coi libri**: short 0.027 ETH in più (l'hedge long ETH del pair
ETH/SOL mangiato dai close reduce-only dei fade) e BTC flat con MR02_BTC convinto di
essere short (TP resting fillato da uno spike REALE del book a 3.8%, +6.6$ non bookati,
che il worker riconcilierà alla chiusura sim) col **disaster-SL residuo sul book a
posizione flat**. Bonifica eseguita: DSL `USDC-SLMB-26521` cancellato, riallineo
+0.027 ETH reduce-only (fill 1682.5) → conto == libri (verificato).
## Root-cause strutturale
Le **quote per-worker con ordini reduce-only su un conto a NETTING si rompono quando due
worker hanno direzioni opposte sullo stesso strumento** (pairs long ETH vs fade short ETH):
- un close reduce-only può essere **cappato** (Deribit riduce l'amount in silenzio) → il
ledger bookava la chiusura PIENA perché `Fill.amount` era il richiesto, non il fillato;
- un close reduce-only nel verso "sbagliato" rispetto al netto viene **respinto** → la
gamba pairs resta orfana sul conto, ma il worker bookava il PnL al prezzo sim e azzerava
lo stato (3 volte oggi: PnL fantasma nel ledger real-truth, ETH/SOL di fatto short nudo).
## Fix (v1.1.24) — verità, non workaround
1. **`Fill.filled_amount`** (da `order.filled_amount`, fallback trades/history): tutti i
ledger usano il fillato; nota "FILL PARZIALE" nel Fill.
2. **`REAL_CLOSE_PARTIAL`** (log + Telegram): close che filla meno del residuo → residuo
orfano dichiarato, `REAL_CLOSE verified=false`.
3. **Pairs per-gamba**: PnL bookato SOLO per gambe con fill verificato; gamba respinta →
record in `orphan_legs` (persistito nello status) + alert `PAIR_LEG_ORPHAN`;
`applied=True` (real-truth) solo con ENTRAMBE le gambe → altrimenti fallback sim
DICHIARATO (meglio del numero mezzo-reale di prima).
4. **`REAL_DIVERGENCE` anche su jsonl** (prima solo Telegram: l'audit ha dovuto
ricostruire gli episodi dai REAL_CLOSE).
5. **Runner: tick isolato per-worker** — un'eccezione in un worker non salta più gli
altri né l'update equity; streak per-worker con alert `WORKER_ERROR_STREAK` a 5.
Test: 2 nuovi (partial-close, orphan-leg) + fixture aggiornate → 106 passed.
## Decisione di design APERTA (per l'utente)
Finché pairs e fade condividono strumenti in direzioni opposte, le chiusure possono
orfanizzarsi: ora è VISIBILE e contabilizzato giusto, ma non eliminato. Opzioni:
A) **Position-manager centrale per strumento** (il runner netta i delta di tutti i
worker e manda UN ordine; ricostruisce le quote contabilmente) — corretto ma
invasivo, da progettare con calma;
B) **Sotto-conti Deribit separati** per famiglia (pairs vs fade) — pulito, richiede
setup conto e client multi-token;
C) **Status quo monitorato**: alert PAIR_LEG_ORPHAN + riallineo manuale (oggi: 1 giro
in 2 minuti) — accettabile su testnet, NON per capitale vero.
Raccomandazione: C ora, A prima di passare a capitale reale.
**Secondo passo — FATTO (v1.1.25): NETTING delle chiusure market.** `close_amount`
tenta il reduce-only e riesegue il residuo cappato/respinto in market puro (= il
netting contro le quote opposte: il conto si muove del delta esatto del libro).
Un solo punto di fix (anche `close_pair` ci passa). Fill combinato per il chiamante
(prezzo pesato, fee sommate), evento `NET_CLOSE` su log+Telegram a ogni fallback,
4 test dedicati. Niente più orfani per costruzione; `orphan_legs` resta come ultima
difesa se fallisce anche il market puro. Effetto collaterale benefico: la chiusura
futura della gamba ETH di ETH_SOL#2 (che sarebbe stata respinta di nuovo) ora
eseguirà correttamente. La scelta A-vs-B-vs-C resta aperta solo per la parte
RESTING (TP/DSL su book condiviso) e per i multi-asset.
**Primo passo verso A — FATTO (sera stessa): reconciler read-only.**
`scripts/analysis/reconcile_account.py`: per ogni strumento USDC confronta
atteso (Σ quote reali dai status.json: single-leg + pairs 2 gambe + orphan_legs
registrati = drift SPIEGATO) vs conto reale (`get_positions`, size/mark → coin),
tolleranza 1.5×step, anti-race (ricontrollo a 10s prima di segnalare). In crontab
host ORARIO (:40) con alert Telegram `ACCOUNT_DRIFT`. Al primo run ha beccato un
vero positivo: BTC libro short 0.0028 vs conto flat (il TP di MR02_BTC fillato
dallo spike reale delle 12:22Z, che il worker riconcilierà alla chiusura sim) —
esattamente la classe di divergenza che prima restava invisibile per ore.
## Altri esiti della tornata
- **Dedup engine gate** (TODO chiuso): `_port06_gate_common.py`, output 3 gate
byte-identici, nessun copy-drift trovato (la paura era fondata ma non ancora avverata).
- **Bug bfill `_daily_equity` quantificato** (TODO aggiornato): NON materiale — OOS
invariato per costruzione, FULL DD 3.46→3.67 col fix (l'attuale è lievemente ottimista),
nessun verdetto di gate a rischio. Lasciato documentato.
- **Drift monitor in produzione**: `drift_monitor.py` in crontab host (07:15 UTC,
Telegram): rolling 60/120g per famiglia vs distribuzione storica propria, warn < p5.
Oggi: FADE 120g al p2 (coda storica, nessun intervento), resto normale, XSEC p84.
- **Dati cerbero-bite refreshati**: catena opzioni a ~153k righe/asset (fino a oggi
19:30), pannello regime denso (net-GEX 644/673 ultimi 7g). Sempre un solo regime:
niente validazione edge, valore forward.
+74
View File
@@ -0,0 +1,74 @@
# 2026-06-12 — ACCEL50: cosa accelera davvero verso €50/giorno
**Domanda.** Quali strategie/leve accorciano il tempo per arrivare a €50/g da ~€2k?
Script: `scripts/analysis/accel50_research.py`.
## Il quadro onesto
A €2k, anche col CAGR OOS del PORT06 (~111% a lev 2), il PnL atteso è ~€4/g: il
collo di bottiglia NON è l'edge (Sharpe OOS 10), è la **taglia**. Le vie testate,
in ordine di impatto:
## 1. LEVA — l'acceleratore dominante (nessuna ricerca nuova)
Frontiera su daily return canonici PORT06 (scala lineare, fee pro-quota):
| lev | CAGR full | DD full | CAGR OOS | DD OOS | anni a €50/g da 2k |
|-----|-----------|---------|----------|--------|--------------------|
| 2 (attuale) | 74% | 3.5% | 111% | 1.5% | 3.3 |
| 3 | 128% | 5.2% | 206% | 2.2% | 1.9 |
| 4 | 200% | 6.9% | 343% | 2.9% | 1.2 |
| 5 | 293% | 8.6% | 539% | 3.7% | 0.9 |
Anche scontando l'OOS del 50% (regime calmo, caveat noto), lev 3-4 dimezza i
tempi tenendo il DD sotto il 10%. Caveat: il modello è lineare — non cattura
margine, code grasse, slippage che cresce col notional, e gli sleeve senza stop
(PAIRS/SH01) a leva alta hanno code peggiori del modello. Proposta sobria:
**lev 2→3 subito** (DD full 5.2% = ancora metà del PORT02 storico), rivalutare
4 dopo un mese di ledger reale pulito.
## 2. FADE 15m — la candidata nuova che PASSA il probe
MR01/02/07 a 15m, parametri live 1h non ri-tunati (anti-overfit), fee 0.10% RT:
- **Tutti e 6 gli sleeve positivi**, OOS 2025-26 positivo ovunque, fee 2x OK
(Sh 1.6-2.9 — margine ampio).
- **BTC 15m domina il suo 1h**: MR01 Sh 3.37 vs 2.76 con META' del DD
(15.1% vs 31.7%); MR02 3.49 vs 3.29 (DD 10.8 vs 18.7).
- ETH 15m leggermente sotto il 1h in Sharpe ma OOS molto più grande in valore
assoluto (4x trade = compounding più veloce; MR02 +22052 vs +9560).
- Infrastruttura live 15m GIA' esistente (fetch sub-orario del BLEND pairs).
**Prossimi passi obbligati prima del deploy** (metodologia standard):
gate PORT06 con correlazione 15m↔1h (se ~1 sostituire, se bassa aggiungere),
griglia parametri al 15m, validazione worker, caveat flat ETH 15m (14-30%
storico — per le fade single-leg il fill flat è meno tossico che per i pairs,
ma va guardato con flat-aware engine).
## 3. PAIRS nuove — BOCCIATE (stale-print illusion, di nuovo)
Sweep onesto delle 19 coppie mai testate (config universale pre-registrata
n=50 z2.0/0.5 max72): 8 candidate con Sh 1.5-4.3... MA le gambe alt hanno
88-98% barre flat (ADA 98%, LTC 97%, DOGE 91%, XRP 88%, BNB 88%) e con
`flat_skip=True` muoiono quasi tutte (BTC/ADA 4.33→0.17, ETH/DOGE 3.79→0.46;
migliore superstite ETH/XRP 1.34 < le 5 deployate). Identica classe di
illusione del XEX su DOGE/SOL (stessa giornata, `xex_divergence_research.py`).
**PAXG idem**: 92% flat su Deribit → chiuso anche il ramo "oro".
NB: questo getta anche una luce nuova sulle gambe alt dei pairs GIA' deployati
(ADA/LTC/SOL hanno flat share altissime nel parquet) — il loro ledger reale
shadow è il banco di prova giusto e finora regge, ma teniamolo d'occhio.
## 4. CAPITALE — domina tutto
A config attuale: €50/g ≈ €24k di capitale. Ogni € aggiunto accorcia
linearmente; nessuna ricerca batte un deposito. In più, a ~€20k si sbloccano
in esecuzione reale i 4 book multi-asset oggi solo paper (TR01/ROT02/TSM01/XS01).
## Sintesi operativa
Ordine d'impatto: **capitale > leva (2→3) > fade 15m (da gateare) >>** tutto il
resto. Le vie "nuova strategia esotica" (pairs nuove, PAXG, XEX) sono tutte
morte oggi sotto il test di esecuzione realistica — la lezione del giorno è che
su questo testnet ogni edge va validato col **book/flat-aware engine** prima di
crederci.
+57
View File
@@ -0,0 +1,57 @@
# 2026-06-12 — FADE TF SWEEP: 1m / 2m / 5m / 10m / 30m (post-swap 15m)
Richiesta utente: estendere l'analisi timeframe dei fade oltre il 15m appena
deployato (v1.1.30). Script: `scripts/analysis/fade_tf_sweep.py`.
Dati: parquet locale (5m/15m/30m full-history; 10m = resample dal 5m, unit-safe);
1m/2m da Cerbero (120 giorni recenti — la storia 1m locale non esiste: esclusa
dal refresh notturno per costo, 2m/10m non sono intervalli nativi del v2).
## A. Storia completa (engine canonico, OOS da 2024-10, fee 0.10% RT)
OOS Sharpe per timeframe (e OOS Sharpe a fee 2x del peggiore):
| tf | MR01_BTC | MR02_BTC | MR07_BTC | MR01_ETH | MR02_ETH | MR07_ETH | worst f2x |
|----|---------|---------|---------|---------|---------|---------|-----------|
| 5m | 3.66 | 1.90 | 4.12 | 5.31 | 6.54 | 5.52 | **MR02_BTC 1.70** |
| 10m | 2.62 | 2.69 | 3.31 | 5.32 | 6.49 | 5.59 | MR02_BTC 0.32 |
| 15m (live) | 1.94 | 2.30 | 2.37 | 4.94 | 6.40 | 4.44 | MR02_BTC 0.60 |
| 30m | 1.35 | 2.32 | 1.56 | 3.25 | 5.23 | 2.81 | MR02_BTC 1.40 |
**La frontiera è monotona**: più il tf scende, più Sharpe sale (MR01/MR07)… e più
il margine fee si assottiglia. A fee 2x MR02_BTC muore a 5m e resta fragile a 10m.
MR02 (donchian) fa 3-6x i trade degli altri: è la strategia più esposta al churn.
## B. Finestra comune recente (2026-02-12 → 06-12, il regime CORRENTE)
- **MR02 sotto i 15m è un disastro**: 1m 64%, 2m 44%, 5m 22% (fee-death).
- **MR01 a 1m brilla** (ETH +60.6%, Sh 5.7; BTC +33.5%) ma **muore a fee 2x**
(unico sopravvissuto MR01_ETH +16.5%): margine troppo sottile per fidarsi.
- Flat share a 1m: ETH 25.6%, BTC 13.3% → rischio stale-print alto (la lezione
del giorno: pairs-alt/XEX/PAXG).
- Il regime recente è CALMO: anche il 5m vi è fiacco (+4.8/22.9/+3.7 BTC).
I tf veloci pagano nella volatilità, non nella calma — il loro vantaggio
full-history viene dai regimi mossi (2021-22, 2024).
## C. Correlazione col 15m live (daily, storia completa)
5m↔15m media **0.46**, 10m↔15m media **0.53** (range 0.28-0.81). Diversificazione
parziale: un eventuale ADD del 10m avrebbe senso ma è meno pulito del salto
1h→15m (che era a 0.26).
## Verdetto
- **1m / 2m: CHIUSI.** Fee-margin nullo a stress, microstruttura flat pesante,
validazione full-history impraticabile. Non deployare mai MR02 sotto i 15m.
- **5m: no-swap.** L'edge c'è ma MR02_BTC muore a fee 2x — viola il criterio di
robustezza fee che tutte le strategie deployate rispettano.
- **10m: in WATCHLIST.** Quasi l'edge del 5m con più margine (f2x 0.32 resta
sotto la soglia di comfort per MR02_BTC; MR01/MR07 reggono bene). Possibile
ADD selettivo (solo MR01/MR07?) da gateare su PORT06 più avanti — NON ora:
il 15m è live da poche ore, un cambio alla volta e si lascia parlare il
ledger reale.
- **15m: confermato** come ginocchio della frontiera margine-fee/rendimento.
Collaterale tecnico: bug di resample scoperto e fixato nello sweep — pandas 2.x
conserva `datetime64[ms]` da `to_datetime(unit="ms")`, quindi `.view(int64)//10**6`
divide due volte e manda i timestamp nel 1970 (equity piatta silenziosa). Usare
`(index - EPOCH) // pd.Timedelta(milliseconds=1)`.
+78
View File
@@ -0,0 +1,78 @@
# 2026-06-12 — GATE PORT06: fade 15m PROMOSSI (tutte e tre le varianti)
Seguito del probe ACCEL50: i 6 sleeve fade (MR01/02/07 × BTC/ETH) a 15m,
parametri live 1h NON ri-tunati, engine canonico `build_trades`/`fade_daily_equity`
parametrizzato sul timeframe. Script: `scripts/analysis/fade15m_port06_gate.py`.
## Risultati
**[1] Parità.** Builder locale a 1h == sleeve canonico, diff 0.00 esatto su tutti e 6.
**[2] Standalone (daily, pos 0.15 lev 3, fee 0.10% RT).** Il 15m batte il twin 1h
quasi ovunque; OOS Sharpe: MR01_ETH 4.94 (vs 1.10), MR02_ETH 6.40 (vs 4.72),
MR07_ETH 4.44 (vs 1.97), MR07_BTC 2.37 (vs 1.59). Fee 2x: regge ovunque tranne
MR02_BTC (OOS 0.60 — il piu' fee-sensitive, ma a fee reali e' il migliore BTC).
**[3] Correlazione 15m↔1h: media 0.26** (range 0.16-0.41) — più decorrelato del
pairs 15m promosso a 0.37. NON è la stessa scommessa più veloce: è un edge a
orizzonte diverso.
**[4] Gate PORT06** (cap PAIRS 0.33 / SHAPE 0.0588):
| variante | FULL CAGR | FULL DD | FULL Sh | OOS CAGR | OOS DD | OOS Sh |
|----------|-----------|---------|---------|----------|--------|--------|
| BASELINE (1h) | 74% | 3.46% | 7.34 | 111% | 1.48% | 10.07 |
| **ADD (+6 sleeve 15m)** | 85% | **2.73%** | 8.02 | 125% | **1.38%** | **10.48** |
| SWAP (15m al posto 1h) | **101%** | 2.47% | 8.13 | **163%** | 2.09% | 10.86 |
| BLEND 50/50 | 87% | 2.31% | 8.06 | 136% | 1.77% | 10.82 |
Tutte e tre PROMOSSE dal criterio standard (OOS Sharpe non peggiora E DD scende).
**[5] Anti-illusione flat (il check che ha ucciso pairs-alt e XEX/DOGE).**
ETH 15m ha 14-30%/anno di candele flat. Con flat-entry-skip l'edge SOPRAVVIVE:
MR01_ETH Sh 3.11→2.85 (OOS 4.94→4.60), MR02_ETH 4.72→4.63, MR07_ETH 3.76→3.55;
BTC invariato (≤0.01). Coerente col finding registrato 2026-05-28. La quota di
ritorno persa (~30-40% del FULL ETH) è il caveat slippage onesto: il ledger
reale shadow sarà il giudice, come per il pairs 15m.
## Raccomandazione
**ADD** è la promozione più pulita: migliora TUTTE le metriche senza buttare il
track record live dei 6 sleeve 1h (SWAP rinuncia alla config validata dal vivo;
il suo OOS DD peggiora 1.48→2.09). In subordine BLEND (miglior FULL DD 2.31%).
## Per il deploy (non fatto qui)
1. `_defs.py`: 6 SleeveSpec nuovi `MR0x_{asset}_15M` con `tf: 15m` nei params.
2. Runner: estendere il fetch sub-orario (già esistente per PR_ETHBTC_15M) ai
fade 15m; verificare lookback (EMA200 a 15m = 50h → bastano ~2200 barre).
3. Validazione worker a 15m (replay == backtest, come validate_worker_pairs).
4. Sizing: a peso pieno i 6 sleeve 15m raddoppiano la famiglia FADE; valutare
position_size ridotto come il blend pairs (il gate ADD qui è già a peso pieno
e migliora comunque, ma il caveat slippage 15m suggerisce prudenza).
5. MR02_BTC 15m è il più fee-sensitive (fee2x OOS 0.60): monitorare le fee reali.
## Addendum — DEPLOY: SWAP SECCO (stessa giornata, scelta utente)
L'utente ha scelto **SWAP** (la variante a CAGR più alto), consapevole dei due
costi discussi: OOS DD 1.48→2.09% e perdita della maturità operativa 1h (la
meccanica live — EXIT-16 confirm, TP_PHANTOM, TP resting — gira 4x più veloce
su un feed testnet con wick anomali; il gate misura l'edge, non la robustezza
operativa). Proposta alternativa staged (ADD temporaneo 1-2 settimane, poi
spegnere il 1h) declinata: "swap secco".
Implementazione (deliberatamente minima, sid INVARIATI → pesi/alloc/epoche
intatti):
- `_defs.py`: FADE specs → `tf="15m"` (DIP01 resta 1h: non era nel gate).
- `combine_portfolio.py`: `FADE_TF="15m"` nel builder canonico → le due facce
(backtest/live) restano sulla STESSA definizione; il lock di parità
(`test_backtest_parity`) confronta le facce fra loro e resta verde.
- Runner: NESSUNA modifica (il fetch sub-orario BTC/ETH 15m esisteva già per il
blend pairs; `_spec_assets_tf`/`_series_for` sono generici).
- Worker: nuove dir `*__15m` (stato 1h preservato in `*__1h`, worker flat al
momento dello switch). Esecuzione reale invariata (match per nome MR01/02/07).
- max_bars=24 barre ora = 6h; EXIT-16 confirm sulla barra 15m completata.
Monitor post-deploy: stop-rate e divergenza sim/reale dei fade 15m
nell'hourly report; fee reali su MR02_BTC; STALE_FEED (le barre flat 15m
sono più frequenti che a 1h).
@@ -0,0 +1,51 @@
# 2026-06-12 — Sweep migliorie/strategie nuove (sera, post-swap 15m)
Richiesta: cercare altre migliorie da implementare o strategie nuove. Tre
esperimenti + una miglioria di codice.
## 1. INIT_LINEAGE — eredità capitale al cambio timeframe (IMPLEMENTATO)
`StrategyWorker._inherit_lineage_capital`: al primo avvio (niente status.json)
il worker eredita `capital`/`real_capital` dal worker più recente di stessa
strategia+asset su altro tf (glob `{strategy}__{asset}__*`). MAI la posizione.
Nato dallo swap 1h→15m di oggi: i worker nuovi partivano dall'allocazione del
pool scartando il PnL del gemello (16.8 di equity fantasma, riallineata a mano
col seed). Il prossimo swap non avrà bisogno di seed manuale.
Test: `tests/portfolio/test_capital_lineage.py` (eredita / no-sibling / resume).
## 2. Gate 10m ADD selettivo (MR01/MR07) — BOCCIATO, watchlist chiusa
Baseline aggiornata al PORT06 post-swap (fade 15m): ADD di 4 sleeve 10m
(MR01/MR07 × BTC/ETH, MR02 escluso perché fee-fragile) dà FULL Sh 8.13→8.33 e
DD 2.47→2.28, ma **OOS Sharpe 10.86→10.76** → fallisce il criterio standard.
Il 15m cattura già quasi tutto l'alpha veloce (corr 10m↔15m 0.53). Chiuso.
## 3. XSEC breadth (universo 8 → 14/15) — direzione GIUSTA, venue SBAGLIATO
La breadth è la leva classica delle strategie cross-sectional. Due banchi:
- **Hyperliquid 15 coin** (dati REALI, ma profondità v2 limitata a ~207 giorni,
regime calmo recente): CORE-8 **6.2%** (Sh 1.38, coerente col
dispersion-gate live che tiene XS01 fuori in questo regime) vs FULL-15
**+9.4%** (Sh 1.58, WR 41→51%). La breadth trasforma un book perdente in
vincente — **sui prezzi veri**.
- **Deribit 14 coin** (storia piena 2022-10→2026-06, +AVAX/DOT/TRX/LINK/BCH/UNI,
parity del sim verificata ESATTA vs `xsec_sim`): FULL Sh 1.48→**1.22**, OOS
4.66→**3.41**, fee 2x da 0.33 a **1.45**. PEGGIO dell'8: le 6 gambe nuove
hanno chiusure flat al **91-99%** e il loro "momentum" è rumore stale che
corrompe il ranking cross-section (3ª conferma della lezione del giorno:
pairs-alt, XEX/DOGE, ora XSEC).
**Conclusione:** l'espansione dell'universo XS01 è promettente ma bloccata dalla
qualità dati del venue. Sblocco strategico: **routing dati Hyperliquid** nel
runner (il client v2 già supporta `exchange="hyperliquid"`) + accumulo storia
HL in avanti. Rivalutare quando HL avrà ≥12-18 mesi di storia utile.
## Direzioni aperte residue (non attaccate oggi)
- **Put settimanale standing** (catastrofe-cap): unica struttura opzioni
eseguibile, da gateare coi premi reali cerbero-bite (~1%/mese il 10% OTM).
Harness `option_overlay_lab.py` pronto.
- **Hyperliquid come venue di esecuzione** (oltre che dati): aprirebbe fades su
alt liquidi con fill realistici; lavoro infrastrutturale grosso.
- 10m fade: chiuso oggi. 1m/2m/5m: chiusi oggi. Pairs nuove/PAXG/XEX: chiusi oggi.
@@ -0,0 +1,108 @@
# 2026-06-12 — Reconcile resting + guard feed↔book + epoca report (v1.1.27)
## Contesto: la notte ha validato (di nuovo) il tema osservabilità
Due eventi nelle 24h hanno motivato la tornata:
1. **MR02_BTC (06:00):** il TP resting LIMIT ha fillato sul **book reale** a 60481
nella notte (+6.55 USD reali) mentre il **feed sim** non è mai sceso sotto 63285
(443 bps di divergenza, sim chiuso a time_limit con 1.25). Il disaster-SL è
risultato `order_not_found` alla cancel (probabile auto-cancel a posizione flat).
Caso SPECULARE del TP_PHANTOM: lì il feed stampa wick che il book non ha; qui il
book si muove e il feed non lo vede. Real-truth ha bookato il reale (corretto),
ma il sistema se n'è accorto solo ORE dopo, al close sim.
2. **Incidente venue (dalle ~09:47):** il conto Deribit testnet ha iniziato a
rifiutare ordini (`locked_by_admin`, `invalid_reduce_only_order`) e le posizioni
sul conto sono cambiate da sole (ETH LONG 0.141 con avg 1849.5 — prezzo vecchio
di settimane): **rollback/reset admin del testnet**, non un nostro bug. Difese OK:
`REAL_CLOSE_PARTIAL`, leg-fail con unwind, orfani registrati (ETH 0.026,
SOL +0.7), `sim_fallback` dichiarato. MA: il reconciler delle 11:40 è **morto in
silenzio** su un 502 — il guardiano che non suona è indistinguibile dal tutto-ok.
## Fatto
### 1. Reconcile degli ordini RESTING (cron :40, read-only)
- **cerbero-mcp 9a74052:** nuovo endpoint Deribit `get_open_orders`
(`private/get_open_orders_by_currency`; per i trigger untriggered interrogare
anche `type='trigger_all'` e merge per order_id). Pattern già presente per
Hyperliquid/IBKR. Rebuild + smoke su testnet (book vuoto == stato atteso).
- **`books.expected_resting()`:** TP/DSL attesi dai libri dei worker single-leg
in posizione reale (i pairs non hanno resting).
- **`reconcile_account.compute_resting_drift()`** — tre classi:
`FILLED_UNBOOKED` (atteso non in book + fill nel trade history col worker ancora
in posizione = il caso MR02), `MISSING` (atteso, non in book, zero fill — per il
DSL triggered il fill ha order_id NUOVO → appare qui e il drift posizioni
completa), `STALE` (in book con label di un nostro worker ma nessun libro:
fillerebbe a sorpresa). Anti-race come per le posizioni; alert `RESTING_DRIFT`.
- **`RECONCILE_FAIL`:** il main è ora guardato — su eccezione alert Telegram +
exit 2 (fix del crash silenzioso delle 11:40).
### 2. Guard FEED_BOOK_GAP nel runner
`_check_feed_book_gap`: ad ogni poll confronta il close della candela in corso col
**mark dello strumento d'ESECUZIONE** (USDC, `get_ticker_batch`, 1 chiamata);
oltre soglia (`overrides.feed_book_gap_bps`, default 150) → alert per episodio,
recovery con isteresi a soglia/2, fail-open su errori di rete. Le decisioni
restano sul feed (il sim è la verità che guida): questo dice solo QUANDO i fill
reali possono divergere dal sim. Avrebbe segnalato il caso MR02 in tempo reale.
### 3. Epoca v1.1.26 nell'hourly_report
I conteggi CHIUSI cumulativi erano inquinati dal churn TP-fantasma dell'11-06
17:3217:58 (~24 giri pre-fix). Nuova riga `epoca v1.1.26+ (TP_PHANTOM attivo)`
da `EPOCH_V1126 = 2026-06-11T21:40` (deploy del gate): oggi legge **7/1** contro
il 18/17 storico — è il numero da confrontare col backtest da qui in poi.
## Test e deploy
`pytest` 121 passed (114 + 7 nuovi: `test_reconcile_resting.py`,
`test_feed_book_gap.py`). Deploy **v1.1.27** (runner nel container; reconciler e
hourly_report girano da host → già attivi). cerbero-mcp rebuildato (endpoint).
## Aperto / lezioni
- **Drift ETH +0.167 non spiegato** dal rollback testnet: quando il conto si
sblocca serve `reset_flatten` + riallineamento ledger (decisione utente).
- I **residui dei close single-leg falliti** (`REAL_CLOSE_PARTIAL`, es. MR07
0.102) NON finiscono in `orphan_legs` → il reconciler li vede come drift NON
spiegato. Estendere la registrazione orfani anche al single-leg è il naturale
passo successivo (punto in analisi sviluppi).
- Il testnet può **riscrivere il conto sotto i piedi** (lock + rollback): nessuna
invariante "il conto cambia solo per nostri ordini" è affidabile. Il reconciler
orario è l'unico rilevatore; valutare cadenza più fitta in presenza di
posizioni reali aperte.
## Addendum (pomeriggio, v1.1.28): orfani single-leg + circuit-breaker venue-lock
I due punti promossi dall'analisi sviluppi, entrambi figli dell'incidente di oggi:
1. **Orfani anche per i close single-leg.** `REAL_CLOSE_PARTIAL` ora registra la
quota residua in `StrategyWorker.orphan_legs` (stessa semantica dei pairs,
persistita in status.json, letta da `books.real_books`) → il reconciler vede
il drift come SPIEGATO invece che anomalo (il caso MR07 0.102 ETH delle 09:47
restava invisibile ai libri). Inoltre `_close_position` ora persiste lo stato
a fine booking (prima solo al save del tick: un crash li' in mezzo perdeva
capital e orfano). NB: nessun save a meta' chiusura — con `real_in_position`
ancora true l'orfano conterebbe doppio nei libri.
2. **Circuit-breaker venue-lock in `ExecutionClient`.** Dopo `lock_trip=3` reject
'locked' consecutivi (es. `locked_by_admin` di oggi) le APERTURE sono sospese
senza toccare l'API (Fill failed → path REAL_OPEN_FAIL/sim_fallback esistente;
per i pairs entrambe le gambe rifiutate localmente → niente leg parziali da
unwindare, niente fee sprecate). Le CHIUSURE si tentano SEMPRE (path gia'
sicuro: partial/orphan/netting) e un loro reject 'locked' rinfresca il
cooldown. Riarmo: dopo `lock_cooldown_s=900` la prossima apertura fa da probe.
Alert `VENUE_LOCK` al trip + RIENTRATO al primo ordine accettato.
Test: `test_single_leg_orphan.py`, `test_venue_lock_breaker.py` (126 passed).
## Chiusura incidente (13:05): testnet sbloccato, conto riallineato
Sequenza eseguita a venue sbloccato: stop runner → `reset_flatten` (chiusi i due
artefatti del rollback: ETH long 0.141 @1664.25, SOL long 0.7 @66.651, conto FLAT)
→ svuotati gli `orphan_legs` di ETH_SOL (le quote orfane non esistono piu' sul
conto post-flatten; edit a container fermo, altrimenti il worker li riscrive
dalla memoria) → restart. **Reconciler: OK — conto allineato ai libri** (posizioni
e resting), runner healthy, log puliti. Il ledger NON e' stato toccato: i booking
dell'incidente erano gia' dichiarati (`sim_fallback`/real) e l'equity (€2020.82)
resta la verita' contabile del paper.
+64
View File
@@ -0,0 +1,64 @@
# 2026-06-12 — XEX: discordanze Deribit testnet vs Hyperliquid
**Domanda.** I prezzi Deribit (testnet, dove eseguiamo) e Hyperliquid (feed
realistico, proxy della realtà) divergono? La divergenza è usabile per fare trade?
**Strumenti comuni validati:** 9 base-coin (ADA, AVAX, BNB, BTC, DOGE, ETH, PAXG,
SOL, TRUMP). Analisi su BTC/ETH (inverse, liquidi) + SOL/DOGE (lineari USDC).
Script: `scripts/analysis/xex_divergence_research.py`.
## Misure (1h, 2026-03-01 → 2026-06-12, ~2470 barre)
| Coin | spread medio | std | half-life | flat Deribit | ΔbookHL live |
|------|--------------|-----|-----------|--------------|----------------|
| BTC | 0.29% | 1.25% | 7.3h | 6% | **0.97%** |
| ETH | 0.55% | 1.33% | 5.1h | 9% | **1.54…−2.16%** |
| SOL | 0.38% | 3.60% | 5.2h | 35% | 0.05% (allineato) |
| DOGE | +0.34% | 1.88% | 4.2h | 87% | +0.16% (allineato) |
Per standard mainnet questi spread sono enormi (reale <0.05%): la divergenza è
l'artefatto del feed testnet che periodicamente si stacca dalla realtà e rientra.
## Findings
1. **Lo spread è mean-reverting e il gap viene chiuso ANCHE da Deribit** (il lato
che possiamo tradare): beta del ritorno futuro Deribit sullo spread negativo e
crescente con l'orizzonte (ETH 0.36, BTC 0.23 a 24h). Non è solo HL che si
muove.
2. **Trappola smascherata — stale print arb.** Il backtest su DOGE dava Sharpe
6.7 (OOS 9.1!) e SOL 2.7, MA il book live di entrambi sta attaccato a HL
(±0.16%) mentre i print restano vecchi (DOGE 87% barre flat): l'edge era
**finzione** — i fill reali avverrebbero al prezzo vero, non al print stantio.
Stessa classe di illusione del look-ahead squeeze: il backtest compra un
prezzo a cui nessuno fa fill.
3. **Su BTC/ETH inverse la dislocazione è REALE: è il book stesso a essere
spostato** (misurato live: bid/ask 0.97% / 1.54…−2.16% sotto HL con depth
>$1M sui primi 5 livelli, spread bid/ask 1-3 bps). Lì si può davvero comprare
sotto/vendere sopra la realtà.
4. **Edge netto moderato e timing-sensitive** (fee 0.10% RT, entry |s|≥1%, exit
|s|≤0.25% o 24 barre): BTC FULL Sh 1.0 / OOS 2.05; ETH FULL 2.28 / OOS 1.37.
Con entry ritardato di 1 barra (stress staleness) BTC FULL→0, ETH OOS→0.1:
con half-life di 3-7 ore, un'ora di ritardo erode quasi tutto. Un'eventuale
implementazione live deve leggere il **book in tempo reale** (non il close 1h)
— a quel punto il lag reale è minuti, non un'ora, e l'edge eseguibile sta fra
lag0 e lag1.
## Segnale live al momento dell'analisi
ETH book Deribit **2.16%** sotto HL (z=1.7, 6.7° percentile storico), BTC
0.97%: entrambi in zona "long Deribit" per il fade dello spread. (Solo
osservazione: nessun deploy senza gate.)
## Verdetto e prossimi passi
- Discordanza tradabile trovata: **fade dello spread D/H su BTC/ETH-PERPETUAL,
segnale dal book, soglia ~1%, exit a convergenza o 24h**. Da NON estendere a
DOGE/SOL (illusione stale-print).
- È un **edge di testnet** (non trasferibile a mainnet): legittimo per il
paper/shadow corrente, ma va dichiarato per quello che è.
- Prima di qualsiasi deploy, metodologia standard: gate PORT06 (correlazione con
le fade esistenti, che già fadano in parte questi stessi spike testnet —
rischio doppio conteggio), robustezza griglia, validazione worker.
Script: `scripts/analysis/xex_divergence_research.py` (fetch v2 due exchange,
tabella convergenza, backtest lag0/lag1, book reality-check live).
+40
View File
@@ -0,0 +1,40 @@
# 2026-06-13 — Dashboard web PORT06 (stato live + PnL + grafici + trade)
Richiesta utente: frontend per visualizzare lo stato con PnL totale e per-strategia,
grafici, e liste trade (attivi in tempo reale + chiusi).
## Cosa
`src/live/dashboard.py` — server **stdlib `http.server`** (zero nuove dipendenze),
legge i file `data/` e serve:
- `GET /api/state` → JSON con tutto lo stato calcolato
- `GET /` → single-page HTML (vanilla JS, polling ogni 5s)
Contenuto della pagina:
- **KPI**: equity, PnL totale (€ e %), max DD, peak
- **Grafico equity** (Chart.js da CDN, fallback testuale se offline) dalla
`equity.jsonl` del ledger (downsample a 400 punti)
- **PnL per strategia** (barre verdi/rosse): realizzato netto fee = Σ `pnl` reali
dai CLOSE (REAL-TRUTH), n trade, win-rate, capitale; tag `paper` per i
multi-asset non eseguiti, `•aperta` se in posizione
- **Trade attivi in tempo reale**: lato, entry, **mark corrente** (Cerbero
best-effort, cache 20s), **PnL non realizzato** (€ e %, da `real_entry_notional`),
barre/max_bars, distanza al TP, età dello status (⚠ se >15min = stantio)
- **Trade chiusi** (ultimi 50): ora, strategia, motivo, PnL reale, sim, esito
## Deploy
Servizio docker-compose `dashboard` (stessa immagine del runner, monta gli stessi
`data/`, porta **8787**), `restart: unless-stopped` + healthcheck sull'API.
Accesso: `http://<host>:8787`. **Nessuna auth** → solo rete interna/VPN, non
esporre pubblicamente. Avvio: `docker compose up -d --build dashboard` (il runner
non viene toccato).
uv run python -m src.live.dashboard --port 8787 # anche standalone su host
## Note
- Il PnL per-strategia usa il PnL REALE (real_truth), coerente col report orario.
- I 6 fade 1h ritirati dallo swap restano in lista (hanno storico CLOSE): flat,
mostrano il loro PnL realizzato storico accanto ai gemelli 15m attivi.
- Unrealized € sui pairs non mostrato (posizione a 2 gambe, z-based) → "pairs (z)".
@@ -0,0 +1,49 @@
# 2026-06-13 — Report ricorrente LEDGER REALE vs BACKTEST (il gate per scalare)
## Perché
Domanda dell'utente: "come cresco il capitale a 12k". Risposta: il prerequisito
prima di mettere soldi veri è che il **ledger reale combaci col backtest**
soprattutto lo slippage del 15m appena deployato. Questo report rende quel gate
un dato osservabile, non un'opinione.
Insight chiave: per gli sleeve eseguiti (6 fade 15m, DIP01, 6 pairs, SH01) il
**sim del worker == backtest canonico PER COSTRUZIONE** (validato). Quindi
"reale vs backtest" = "reale vs sim" = la **fuga di esecuzione**: slippage +
fee reali vs assunte + effetti netting/phantom/sim_fallback.
## Cosa misura
`scripts/analysis/ledger_vs_backtest.py` (read-only: solo trades.jsonl +
status.json, nessuna rete → affidabile in cron):
- PnL realizzato sim vs reale (Σ e per-trade) → **LEAKAGE** € e per-trade (bottom line)
- slippage ingressi (REAL_OPEN) e uscite-a-mercato (REAL_CLOSE; escluse le uscite
da TP resting, fill maker al livello = no slippage)
- fee reali vs assunte (0.10% RT)
- trade `sim_fallback` (reale mai eseguito/fillato) = quota NON coperta dal reale
- ledger per-sleeve: real_capital vs capital (sim)
- **verdetto** 🟢/🟡/🔴: <10 trade = campione piccolo; leakage basso+slippage ≤15bps
= verde (si può pensare a scalare); slippage >40bps = rosso (edge erode, NON scalare)
## Clean-start (importante)
Una finestra mobile pura includerebbe l'**incidente testnet pre-fix**: a 7g il
report dà sim +82 vs reale +5 (🔴) — ma è gonfiato dai +4% FANTASMA che il sim
bookava e il reale no, prima di TP_PHANTOM (v1.1.23), netting (v1.1.25) e
ribilancio-conservativo (v1.1.31). Lo scheduler usa **`--since 2026-06-13`** →
accumula SOLO dati post-fix, e diventa statisticamente significativo coi giorni.
Finestra pulita oggi: 1 trade, leakage +0.07, slippage ingresso 12-29 bps.
## Scheduling
Cron host (come reconcile/hourly_report), **giornaliero 08:30 UTC**:
`ledger_vs_backtest.py --since 2026-06-13 --telegram` → Telegram + log in
`~/port06_ledger_vs_backtest.log`. Invio verificato.
## Come usarlo
Quando il campione supera ~10-20 trade reali e il verdetto è 🟢 stabile per
qualche giorno (leakage per-trade piccolo, slippage medio ≤15 bps), allora il
15m regge l'esecuzione e si può passare da testnet a piccolo reale → poi scalare.
Se resta 🔴/🟡, l'edge si erode sui fill e NON va scalato: prima si capisce dove
perde (slippage ingressi? uscite a mercato? sim_fallback frequenti?).
@@ -0,0 +1,41 @@
# 2026-06-13 — Orfano da swap: incidente + guard nel reconciler
## Incidente
Lo `stato trades` del mattino ha scoperto una **posizione reale orfana**: il
worker fade **MR02_BTC 1h** aveva aperto uno short reale (0.0028 BTC @ 64135.5)
ieri alle 15:01; lo **swap a 15m (v1.1.30, ~20:48) lo ha rimosso dal config
mentre era ancora in posizione** → da allora nessun runner lo gestiva. Stato:
- conto Deribit: short 0.0028 BTC (il long di apertura del gemello 15m aveva
aperto e chiuso nettando via il resto)
- il **TP limit (63387.75) era sparito** dal book (cancellato durante il netting
della chiusura 15m) → short NUDO, protetto solo dal disaster-SL a +30%
- il **reconciler NON allarmava**: lo `status.json` del worker morto dichiarava
ancora `real_in_position: true` → conto == libri. Punto cieco: il reconciler
leggeva i libri dagli status ma non sapeva quali worker fossero VIVI.
Chiusura manuale (testnet): buy 0.0028 reduce-only @63766.5 (~+$0.85 netto sullo
short), cancel disaster-SL, worker marcato flat, PnL bookato (real_capital
181.18→182.03). Conto verificato flat su BTC; SH01_ETH short intatto.
## Guard implementato
`reconcile_account.compute_stale_real_positions(max_age_min=15)`: segnala i
worker che dichiarano `real_in_position` ma il cui `status.json` è fermo da
oltre 15 min. Un worker vivo riscrive lo status a ogni poll (~60s) → la
**staleness** è il discriminante robusto e venue-agnostico (cattura
ritirati-da-swap, crashati, worker rimossi dal config). Alert Telegram
`STALE_REAL_POSITION` (con `--telegram`), incluso nell'exit code e nel verdetto.
Gira già al prossimo cron host (:40) — nessun rebuild (lo script gira dal
working tree). Test: `tests/portfolio/test_reconcile_resting.py` (stantio
flaggato / fresco no / flat-vecchio no).
## Causa radice e direzione
La feature `INIT_LINEAGE` di ieri trasferisce il *capitale* al gemello del nuovo
timeframe, ma non la *posizione*. Il guard di oggi è la **rete di sicurezza**
(rileva e allarma entro un'ora). La **prevenzione** vera — flattare/consegnare la
posizione reale del worker ritirato al boot del runner — resta da implementare
(va fatta lato runner, con cautela: piazza ordini reali all'avvio). Per ora:
swap a conto fade-flat quando possibile, e il reconciler copre il resto.
@@ -0,0 +1,56 @@
# 2026-06-13 — Equity gonfiata dal ribilancio: +4.77 fantasma (causa + fix)
## Sintomo
Report "dall'ultimo reset": equity 2020.53 → 2025.80 (+5.27), ma un solo trade
chiuso (MR02_BTC 15m, reale +0.50). Il +4.77 residuo è comparso di colpo a
00:01:52 (primo poll dopo mezzanotte UTC = ribilancio giornaliero), senza alcuna
chiusura dietro.
## Causa — doppio conteggio del capitale degli in-position al ribilancio
`rebalance_allocations` faceva:
1. `total_capital = Σ capital di tutti i worker`
2. `alloc[sid] = peso × total_capital` per OGNI sid
3. ai worker in posizione NON applicava l'alloc (la posizione tiene il suo notional)
4. ai flat: `capital = alloc[sid]`
`update_equity` (ogni poll) = Σ capital. Dopo il ribilancio la somma diventa:
Σcapital = Σ(peso_i × total) [flat] + Σ(capital_j) [in-pos]
= total Σalloc_j + Σcapital_j
= total + Σ(capital_j alloc_j) ← inflazione
Cioè i flat si dividevano l'INTERO total (che includeva anche il capitale degli
in-position), e gli in-position lo tenevano in più → doppio conteggio.
Al ribilancio delle 00:01, **MR02_BTC 15m era in posizione** col capitale seedato
**181.19** (eredità del gemello 1h, INIT_LINEAGE di ieri) mentre la sua allocazione
era ~176.42 → **+4.77** di equity dal nulla. Lo scatenante è stato il seed (capitale
ben sopra l'allocazione media) combinato con la posizione aperta al ribilancio; ma
il bug era latente da sempre (ogni worker in-pos che devia dall'alloc gonfiava/sgonfiava
l'equity al ribilancio successivo).
## Fix — conservazione (ledger.allocate `reserved`)
`allocate(weights, reserved={sid: capitale})`: i worker in posizione TRATTENGONO il
loro capitale (deployato, non spostabile); i flat si dividono `total Σreserved`
per peso **rinormalizzato sui soli flat**. Così `Σalloc == total_capital` sempre →
**l'equity è conservata dal ribilancio** (un ribilancio sposta capitale fra sleeve,
non crea valore). `runner.rebalance_allocations` calcola `reserved` dai worker
`in_position` e lo passa. Senza `reserved` (default) = comportamento storico
(corretto solo a worker tutti flat, es. allocazione iniziale).
Test: `tests/portfolio/test_ledger.py::test_alloc_conserves_total_with_reserved`.
## Correzione dello stato (rimozione del +4.77 già accumulato)
Equity riportata a **2021.03** = baseline post-seed verificata (2020.53) + unico
trade reale (MR02_BTC 15m +0.50). Capitali dei worker del pool scalati ×
(2021.03/2025.79) e peak del ledger resettato a 2021.03 (il 2025.80 non è mai
stato un massimo legittimo → niente DD fantasma). L'orfano MR02_BTC 1h chiuso a
parte (+0.85) resta sul worker ritirato, fuori dal pool.
## Esito
Equity = soldi veri: dal reset il trading ha prodotto **+0.50** (1 trade), non +5.27.
Deploy con codice fixato (rebuild) così il prossimo ribilancio conserva.
@@ -0,0 +1,149 @@
# 2026-06-14 — Timing sweep: PAIRS & HONEST su 5/10/15/30m (vs live)
## Domanda (goal utente)
Dopo lo swap fade 1h→15m (v1.1.30), i **pairs** e le **honest** beneficiano dello stesso
trattamento — girare su timeframe più veloci (5/10/15/30 min)?
Script: `scripts/analysis/timing_sweep_pairs_honest.py` (riusa i motori canonici
`pairs_sim_flat`, replica DIP intrabar verificata == `dip_market_gated(market_n=0)`, gate
PORT06 == `pairs30m_gate`/`dip01`). Niente re-tuning dei parametri al cambio TF (anti-overfit,
come lo swap fade). Tutto netto, leva 3x, OOS held-out (da 2024-10).
## Vincolo dati (hard — definisce lo scope)
Solo **BTC/ETH** hanno 5m/15m/30m in locale (10m = resample causale da 5m). **TUTTI gli alt**
(ADA/BNB/DOGE/LTC/SOL/XRP) sono **SOLO 1h**. Quindi:
- **PAIRS:** solo **ETH/BTC** è sweepabile sub-orario. Gli altri 4 pair (gambe alt: LTC/ETH,
ADA/ETH, BTC/LTC, ETH/SOL) restano 1h per sempre senza dati alt sub-orari.
- **HONEST:** solo **DIP01** (BTC, mean-reversion) ha senso + dati. **TR01** (trend EMA20/100 su
4h, basket alt) e **ROT02** (rotazione dual-momentum 1d, momentum 60g, universo alt) sono lente
(orizzonte multi-giorno/mese) E multi-asset-su-alt → sub-orario **infattibile** (dati) e
**insensato** (momentum a 60 giorni su barre da 5 min). Nessuno sweep.
## Flat-share (print stale O=H=L=C, = rischio fill) — sale al scendere del TF
| asset | 5m | 10m | 15m | 30m | 1h |
|-------|----|-----|-----|-----|-----|
| BTC | 10.0% | 4.8% | 3.5% | 1.5% | 0.9% |
| ETH | **29.0%** | 17.3% | 16.4% | 9.0% | 6.7% |
ETH 5m al 29% è un allarme operativo serio. Nota: **30m ETH (9%) < 15m (16.4%)** → il 30m ha
*meno* rischio stale-print del 15m già live.
## PAIRS ETH/BTC — il vero free-lunch
Config UNIVERSALE 1h (n=50, z_in=2.0, z_exit=0.75, max_bars=72), flat_skip live-realizable:
| tf | Sharpe FULL | OOS Sh | OOS Sh fee2x | CAGR | win% |
|----|-------------|--------|--------------|------|------|
| 5m | 9.54 | 17.19 | 11.98 | 2071% | 56.0 |
| 10m | 9.26 | 16.54 | **13.03** | 1214% | 64.1 |
| 15m (live) | 8.10 | 14.40 | 11.74 | 673% | 66.7 |
| 30m | 6.20 | 11.17 | 9.59 | 312% | 71.7 |
| 1h (live) | 3.74 | 7.11 | 6.19 | 119% | 72.4 |
(I CAGR a quattro cifre sono compounding leva su singolo sleeve, irrealistici — il portafoglio
normalizza/cappa; il segnale robusto è lo **Sharpe** e il **gate PORT06**.)
- Sharpe **monotono** al scendere del TF, e **regge le fee 2x** (a differenza delle fade, dove
MR02 muore a 5m). Pairs market-neutral: lo spread rientra più spesso ad alta frequenza, e
ETH/BTC è la coppia più liquida.
- Corr daily fra TF: 5m↔1h 0.28, 10m↔1h 0.32, 30m↔1h 0.51; 10m↔15m 0.67 → i TF veloci sono
**fasi diverse dello stesso edge**, diversificanti, non duplicati.
**Gate PORT06 (add half-size al BLEND 1h+15m, OOS da 2024-10):**
| config | FULL Sh | FULL DD% | OOS Sh | OOS DD% | verdetto |
|--------|---------|----------|--------|---------|----------|
| ATTUALE (1h+15m) | 8.13 | 2.47 | 10.86 | 2.09 | — |
| +30m | 8.24 | 2.28 | 10.87 | 1.97 | **MIGLIORA** (già `pairs30m_gate`, mai deployato) |
| +10m | 8.44 | 2.28 | 11.04 | **1.92** | **MIGLIORA** (domina tutto) |
| +5m | 8.52 | 2.30 | 11.00 | 1.95 | **MIGLIORA** (FULL Sh max) |
Tutte e tre dominano su FULL Sh, FULL DD, OOS Sh, OOS DD. **10m** è il candidato più pulito
(miglior OOS DD, flat-share moderato 17%); **30m** è il più sicuro (flat-share 9% < 15m live).
## DIP01 (BTC) — più veloce, ma lo swap NON domina
Engine canonico (n=50, z_in=2.5, sl_atr=2.5, max_bars=24), parità 1h verificata:
| tf | OOS Sh BTC | OOS Sh fee2x BTC | corr↔1h | OOS Sh ETH |
|----|------------|------------------|---------|------------|
| 5m | 4.91 | 2.41 | 0.13 | 8.43 |
| 10m | 3.47 | 1.99 | 0.17 | 6.51 |
| 15m | 2.40 | 1.40 | 0.22 | 4.49 |
| 30m | 1.34 | **0.77** | 0.41 | 3.54 |
| 1h (live) | 1.69 | 1.36 | — | 2.68 |
Standalone Sharpe sale al scendere del TF e i TF veloci sono **fortemente diversificanti**
(corr 0.130.22 con l'1h). MA: **BTC fee-fragile** (30m f2x 0.77), e il **gate-swap non domina**:
| config | FULL Sh | FULL DD% | OOS Sh | OOS DD% |
|--------|---------|----------|--------|---------|
| DIP01 1h (live) | 8.13 | 2.47 | 10.86 | 2.09 |
| DIP01 30m | 8.16 | 2.27 | 10.78 | 2.03 |
| DIP01 15m | 8.16 | 2.30 | 10.82 | 2.14 |
| DIP01 10m | 8.31 | 2.49 | 10.85 | 2.15 |
| DIP01 5m | 8.46 | 2.27 | 11.02 | 2.16 |
Tutti migliorano i FULL ma **alzano l'OOS DD** (e 30m abbassa l'OOS Sharpe) → nessuno domina
sui 4 criteri. DIP01 5m dà il miglior OOS Sharpe (11.02) ma OOS DD 2.16 vs 2.09. La bassa corr
suggerirebbe un **ADD** (DIP01 5m come secondo sleeve diversificante) più che uno swap, ma
aumenta la concentrazione BTC-rev e non è il free-lunch netto che sono i pairs.
## Caveat (perché backtest ≠ via libera a scalare)
1. **Slippage su feed flat-alto** = il vero banco di prova, già flaggato per il 15m blend
("il vero banco di prova è il ledger reale shadow"). A 5m/10m è peggio (ETH 5m 29% flat).
Il backtest usa flat_skip (timing live-realizable) ma NON modella lo slippage attorno ai
print stali. → gateare via shadow ledger PRIMA di sizing, specie il 5m.
2. **OOS = un solo regime** (2024-10→, calmo). Lo Sharpe monotono-col-TF può essere in parte
un artefatto di regime calmo (la MR veloce prospera nel range a bassa vol; lo sweep fade
trovò "i tf veloci pagano nella volatilità, non nella calma"). I pairs sono market-neutral
→ meno esposti dei fade ai crash direzionali, ma il caveat resta.
## Verdetto del backtest (gate full + OOS 2024→)
- **PAIRS ETH/BTC: il gate diceva SÌ.** Estendere il BLEND con TF più veloci migliorava PORT06
su tutte le metriche (10m domina, 30m il più sicuro, 5m FULL Sh max), fee-2x robusto.
- **DIP01: marginale.** Più veloce è diversificante ma lo swap non domina e BTC è fee-fragile.
- **TR01/ROT02: NO.** Dati alt 1h-only + orizzonte lento → fuori scope per costruzione.
## Tentativo di deploy → due muri, NESSUN deploy (2026-06-14)
L'utente ha scelto di deployare un blend pairs ETH/BTC più veloce. Il wiring è stato fatto e
validato (sleeve def, backtest face, worker replay == backtest esatto, regression-lock
aggiornato) PRIMA di scoprire due blocchi che hanno ribaltato la decisione:
1. **MURO FEED (30m e 10m).** Il feed live **Cerbero v2 (`/mcp/tools/get_historical`) serve
solo 5m/15m/1h** — il 30m e il 10m danno 400 in OGNI formato (30m/30/30min/1800), e
l'endpoint legacy è 404 (rimosso). La voce `"30m"` in `runner._SUBHOURLY` era **speculativa,
mai testata live**. → un blend 30m/10m NON è drop-in: richiede di derivarlo resamplando il
feed 15m/5m nel runner (modifica al data-path live condiviso). **Lezione: il _SUBHOURLY del
runner ≠ ciò che il feed serve davvero — smoke-testare il fetch prima di promettere un TF.**
2. **MURO REGIME (5m).** Il 5m è l'unico TF veloce nativo (drop-in), ma il gate full-history
(Sharpe 9.54) è un **relitto dei regimi volatili vecchi**. Nel regime ATTUALE è il PEGGIORE
(ETH/BTC pairs half-size, ret%/Sharpe/DD):
| tf | ultimi 30g | ultimi 90g | ultimi 180g |
|----|-----------|-----------|------------|
| 5m | 10.5% / 11.5 / 11.6% | 2.0% / 0.2 / 14.1% | +78% / 2.93 / **19.5%** |
| 15m | 4.4% / 6.2 / 5.8% | 3.1% / 0.9 / 5.8% | +56% / 3.80 / 7.9% |
| 1h | 1.0% / 1.1 / 3.0% | +3.0% / +1.3 / 3.0% | +25% / **3.95** / 3.0% |
Il 5m perde su ogni finestra recente e ha **DD 3-6× il 1h**; persino sui 180g il suo Sharpe
(2.93) è SOTTO 15m/1h. Sommato al **flat-share ETH 5m 29%** (slippage reale, non modellato
dal backtest) e al fatto che girerebbe **reale**, il guadagno modesto di backtest (FULL
Sharpe +0.39) non vale il rischio. **5m: non conviene.**
**ESITO: nessun deploy. Config live invariata (19 sleeve, pairs 1h+15m).** I file tracciati
ripristinati a HEAD; restano solo questo diario + `timing_sweep_pairs_honest.py` come record.
**Lezione di metodo:** il gate PORT06 full+OOS è necessario ma non sufficiente — va incrociato
con (a) la fattibilità del FEED live e (b) il regime RECENTE. Un edge full-history può essere
un relitto di volatilità passata; "più veloce = più Sharpe storico" si rovescia in "più veloce
= più DD nel regime calmo attuale". Se in futuro si vorrà un blend più veloce: il **30m** resta
il migliore on-paper (flat 9%), ma serve prima derivarlo dal 15m nel runner + ri-verificarlo
nel regime del momento.
+85
View File
@@ -0,0 +1,85 @@
# 2026-06-15 — Gate del feed CONGELATO (freeze gate)
## Problema (da "stato ordini")
Audit degli ordini live: 5 sleeve in posizione reale, **4-5 contaminati dal feed ETH
congelato**. `ETH-PERPETUAL` (inverse, il feed di DECISIONE del runner) è fermo a
**1661.95 da 36h+** (100% barre flat, 57/57 nel windows 2g, **1 solo valore distinto**),
mentre il book reale dove si ESEGUE (`ETH_USDC-PERPETUAL`, lineare) si muove ~1717-1722.
Effetto misurato sui soldi reali (log container):
- **SH01_ETH** ha aperto short a sim 1661.95 / real 1717.10 (slippage 332 bps), chiuso a
time_limit con `sim_exit 1661.95` ma `real_fill 1719.21`**2.83 USD reali vs 0.09 sim**
→ e ha **riaperto subito** lo stesso short sulla trappola.
- I 4 pairs con gamba ETH (ETH/BTC 1h+15m, ETH/SOL, LTC/ETH, ADA/ETH) sono entrati con
z-score **estremi** (3.10, 2.96, +5.58, 2.56): artefatto MECCANICO del log-ratio con
ETH pinnato a 1661.95 mentre gli alt si muovono → falsa divergenza → carico di spread long-ETH.
È un GUASTO testnet, non di strategia: in mainnet l'arbitraggio tiene inverse ≈ lineare ≈ realtà.
## Decisione utente
> "congela gli sleeve ETH-leg finché il feed non si sblocca"
## Soluzione: gate del feed congelato nel runner (auto-guarente)
`src/portfolio/runner.py`: quando il feed di decisione 1h di un asset è **congelato**, gli
sleeve CONCENTRATI (single/ml/pairs) che ne dipendono **saltano il tick** (entry E exit)
finché il feed non si sblocca. Come un outage (i worker non valutano gli exit, protezione =
disaster-SL on-book). Si rilascia DA SOLO alla prima barra completa non-flat.
### Distinguere il GUASTO dall'ILLIQUIDITÀ (il punto critico)
Un detector flat-bar ingenuo sarebbe SBAGLIATO: sul feed reale (2026-06-15) gli alt sono
flat ma VIVI. Firma misurata (run finale di close invariate / valori distinti in 48h):
| Asset | run | distinti/48h | natura |
|-------|-----|--------------|--------|
| ETH | 64 | **1** | MORTO (guasto) |
| BNB | 64 | 2 | MORTO (testnet, solo paper) |
| DOGE | 42 | 4 | MORTO (testnet, solo paper) |
| XRP | 12 | 3 | illiquido |
| ADA | 11 | 5 | illiquido ma VIVO |
| LTC | 10 | 5 | illiquido ma VIVO |
| SOL | 1 | 31 | VIVO |
→ il detector conta la **run di close INVARIATE** (prezzo che non cambia MAI), non le barre
flat. Soglia **24** (`feed_freeze_gate_bars`, configurabile, 0=off): un giorno intero di
prezzo immobile separa nettamente le due popolazioni. ETH/BNB/DOGE (run 40-64) gateati,
ADA/LTC/XRP/SOL (run ≤12) no. **PR_BTCLTC resta attivo** (BTC vivo + LTC solo illiquido NON
deve sospendere il pair: si perderebbero segnali reali sulla gamba BTC).
### NON è l'entry-guard post-flat (BOCCIATA)
Si gatea **DURANTE** il freeze (ultima barra completa ferma). La barra di RIPRESA è non-flat
→ la run si azzera → il tick riprende SU di essa. NON si salta la candela-gap di ripresa
(quella è l'overshoot che la fade fada con profitto — l'entry-guard post-flat è bocciata,
CLAUDE.md / 2026-06-05). Rilascio solo su barra **completata** non-flat (la forming non basta,
coerente con EXIT-16).
### Scope
Solo sleeve `single`/`ml`/`pairs`. I multi-asset (basket/rotation/tsmom/xsec, tutti PAPER)
NON sono gateati: diversificati su 8 asset, un feed fermo non li compromette (e ROT02 ha ETH
nell'universo ma resta attivo). Osservabilità: log `FEED_FROZEN_GATE` + alert Telegram
GATED/RIPRESO, dedup per episodio.
## Esito (smoke feed reale, soglia 24)
- CONGELATI: ETH, BNB, DOGE.
- **GATEATI: le 9 gambe ETH esatte** — MR01/MR02/MR07_ETH, SH_ETH, PR_ETHBTC, PR_ETHBTC_15M,
PR_LTCETH, PR_ADAETH, PR_ETHSOL. BTC-only, PR_BTCLTC e i multi-asset restano attivi.
Le 5 posizioni ETH già aperte NON vengono flattate (freeze = pausa, non liquidazione): un
close forzato passerebbe per lo stesso feed congelato (= il close 2.83 di SH01). Restano
gestite alla ripresa del feed; protezione di coda = disaster-SL on-book a 30%.
Test: `tests/portfolio/test_freeze_gate.py` (detector guasto-vs-illiquido, rilascio su barra
completata, scope sui veri sleeve PORT06). Suite portfolio: 140/140 verdi.
## Lezione
Tre livelli di osservabilità del feed ora distinti: STALE_FEED (≥2 barre flat, alert umano),
FEED_BOOK_GAP (feed≠book d'esecuzione, alert), **FEED_FROZEN_GATE (≥24 barre invariate, AGISCE)**.
La firma che separa guasto da illiquidità è la **run di prezzo invariato**, non la barra flat:
gli alt illiquidi stampano barre flat ma il prezzo si muove ogni ~10 barre.
@@ -0,0 +1,66 @@
# 2026-06-16 — Guard TP-invertito (churn MR02_BTC 15m)
## Sintomo
Burst di **8 trade in 15 minuti** su `MR02_donchian_fade__BTC__15m` (09:0009:15 UTC),
tutti `long`, tutti chiusi `take_profit` a `bars_held=0-1` **in perdita**, accuracy
crollata 60%→27%. TP **fisso** a 66189.25, **sotto** ogni entry (6670066834):
```
09:00 OPEN long 66780.5 tp=66189.25 sl=64218.21 ← tp E sl ENTRAMBI sotto l'entry
09:02 CLOSE take_profit exit=66189.25 held=0 sim=-2.63 real=-0.12
... ×8 ... +7× REAL_TP_FAIL: invalid_reduce_only_order
```
I trade MR02 sani prima del cluster hanno il TP dal lato giusto (long → tp>entry:
65033>63414, 64148>63775, 66079>65717). Il cluster delle 09:00 è l'unico **invertito**.
## Causa radice — segnale da wick transitorio (NON feed congelato, NON spike d'ingresso)
BTC alle 09:00 era **davvero** ~66780 (MR01 short@66919, MR07 short@66780.5 nello stesso
istante, slippage reale 0.72.5 bps → feed sano). Il difetto è a monte:
1. Una barra con **wick verso il basso** (~64218 = il livello SL) fa scattare il donchian
"rottura canale basso" → segnale **LONG**, con `tp`=centro canale (66189) e `sl`=64218
calcolati **dal contesto wickato**.
2. Il wick rientra; il worker entra `long` al prezzo **recuperato** 66780 — cioè **sopra
il proprio TP=66189**.
3. L'exit intrabar (`bar_high >= tp`) è banalmente vera all'istante → chiude "take_profit"
a 66189 = **perdita garantita a held=0**. Si ripete 8× perché il livello donchian
wickato resta nel lookback.
Differenza chiave da TP_PHANTOM: lì il wick è sull'**exit** (tocco fantasma del TP);
qui il wick produce un **tp dal lato sbagliato dell'entry** → la posizione non doveva
proprio aprirsi.
## Perché i guard esistenti non l'hanno preso
- **TP_PHANTOM** richiede un limit reduce-only resting come oracolo. Qui il limit è
**respinto** (`invalid_reduce_only_order`: una sell reduce-only a 66189 sotto il mercato
è invalida) → niente `real_tp_order_id` → fail-open → l'exit passa. E il prezzo corrente
(66780) ha **realmente** superato il livello → non è un tocco fantasma.
- **Freeze-gate (v1.1.32)**: BTC non è congelato (prezzi che si muovono) → non scatta.
## Danno reale: contenuto (il real-truth ledger ha funzionato)
- Σ **reale** del cluster ≈ **−€2.29** (solo slippage+fee: il fill reale è al prezzo vero
~66700-66730, non al TP fantasma).
- Σ **sim** sarebbe stato ≈ **−€17.9** → il real-truth ledger ha evitato che ~**€15.6** di
perdita fantasma colpisse l'equity.
- Costi collaterali: 8 round-trip di fee, 7 TP reduce-only respinti, 8 disaster-SL da
piazzare/cancellare, sim accuracy/report inquinati.
- Auto-esaurito alle 09:15 (sleeve poi flat).
## Fix — guard "TP invertito" (v1.1.33, zero parametri)
`StrategyWorker._open_position`: **prima** di aprire, se il TP è già sfondato all'ingresso
(`long & tp<=entry` oppure `short & tp>=entry`) il segnale è malformato (artefatto wick)
**NON si apre** (niente ordine reale, niente disaster-SL). Verità d'esecuzione, non un
filtro di strategia (stessa filosofia di TP_PHANTOM). Log `INVERTED_TP_SKIP` (dedup
per-barra) + alert Telegram una-tantum per processo. Agisce **solo sul path live**: il
backtest canonico entra al close del bar del segnale (entry == base di tp/sl → mai
invertito) e resta non filtrato → il live farà meglio del backtest, come loss-guard /
trend / TP_PHANTOM.
Test: `tests/portfolio/test_inverted_tp.py` (long/short invertiti skippati, controlli
positivi long/short validi aprono, no-tp apre, dedup per-barra). Suite portfolio 146/146.
## Lezione
Cerotto testnet (il feed wicka): il fix vero è **mainnet**, dove l'arbitraggio elimina i
wick-print. Ma il guard è corretto in assoluto — un segnale con TP dal lato sbagliato
dell'entry non va mai eseguito, su qualsiasi venue.
@@ -0,0 +1,57 @@
# 2026-06-16 — Fase 0 smoke mainnet VERDE (ETH→USDC, limit non market)
## Contesto
Il conto Deribit mainnet, fino al 2026-06-14 **vuoto** (€0, unico blocco al micro-test), ha
ricevuto un deposito iniziale di **~$50**: 25.00 USDC + 0.013878 ETH. Occasione per eseguire la
**Fase 0** del piano (`docs/specs/mainnet-microtest-plan.md`): validare la catena d'esecuzione su
soldi VERI con rischio ~0, abbinandola a un'operazione utile — **consolidare l'ETH stranded in
margine USDC**.
Perché consolidare: noi eseguiamo sui perp **lineari USDC** (`*_USDC-PERPETUAL`), marginati in
**USDC**. I 0.0138 ETH stavano nel wallet ETH (collateral dell'inverse, che NON usiamo per la
matematica lineare del backtest) → margine effettivo solo i $25 USDC. Convertendo, l'intero ~$50
diventa USDC utilizzabile.
## Ordine (LIMIT, non market — istruzione utente)
Check capability read-only prima: spot `ETH_USDC` esiste (bid 1780.3/ask 1780.9, `testnet:false`,
price tick 0.1). Il listing strumenti NON espone lo spot (kind=spot vuoto, endpoint v1 404) ma il
ticker basta. `place_order` supporta `order_type="limit"` + `price`.
- **LIMIT SELL `ETH_USDC` 0.0138 ETH @ floor 1776.3** (~0.1% sotto il bid) — *marketable limit con
cap di slippage*: crossa e filla al bid corrente, ma con un floor sotto cui NON può fillare.
Soddisfa "non a market" (prezzo protetto, niente slippage incontrollato) garantendo il fill.
- Step 0.0001 ok al primo colpo (niente fallback 0.013); fallback previsti 0.013/0.012.
### Esito
```
order_state = filled filled_amount = 0.0138
average_price = 1778.1 (= bid al momento dell'invio; il bid era sceso 1780.3->1778.1 tra
quote e invio, ma > floor 1776.3 -> zero slippage negativo)
fee = 0.0 (maker, costo nullo)
order_id = ETH_USDC-8488088107
```
Riconciliazione conto (before→after):
| Wallet | Prima | Dopo |
|---|---|---|
| USDC | 25.00 | **49.538** (+24.538) |
| ETH | 0.013878 | 0.000078 (dust ~$0.14, sotto il min → non convertibile) |
| BTC | 0 | 0 |
| posizioni / ordini resting | — | nessuna / nessuno |
## Cosa è dimostrato
Catena end-to-end su **mainnet** (`testnet:false`), a costo ~0: routing live + auth + `place_order`
limit + fill + verifica via `get_trade_history` + riconciliazione `get_account_summary`. La
meccanica d'esecuzione reale FUNZIONA su soldi veri. **Fase 0 ✅.**
## Limite residuo (resta il blocco vero)
~$49.54 USDC è **sotto il minimo €500** del piano: a questo margine il rumore di arrotondamento
BTC (step lineare ~$6.6) soffoca il segnale → NON si può ancora testare l'**edge**. Per la **Fase 1**
(fade-only, €1000, 2-4 settimane, verdetto ledger-vs-backtest) serve un **top-up a ~€500-1000 USDC**.
Stato piano aggiornato: token+smoke ✅, funding 🟡 parziale, Fase 1 in attesa di capitale.
## Note operative
- Operazione MANUALE one-off (non il runner): `.env.mainnet` sourcato a mano, client puntato a
mainnet solo per questa sessione. Il runner di produzione resta su **testnet** (`.env` condiviso
invariato) → nessun rischio di flip accidentale.
- La marketable-limit con floor è il pattern giusto per conversioni one-shot a prezzo controllato
(vs il TP resting/limit-al-livello degli sleeve): fill immediato + cap di slippage, fee maker.
@@ -0,0 +1,265 @@
# 2026-06-18 — Price Ladder (griglia) vs gate PORT06
**Branch:** `price_ladder_research` · **Harness:** `scripts/analysis/ladder_search.py`
(engine `grid_mtm`: mark-to-market onesto, SL gap-aware, flat-skip, fee 0.10% RT taker).
**Baseline PORT06:** FULL Sharpe 8.13 / DD 2.47% — OOS Sharpe 10.86 / DD 2.09%.
**Metodo:** caccia multi-agente su asset × TF (15m/30m/1h) × regime-gate (none / range
trend_max 1.5/2.0/2.5), poi `verify` avversariale sui finalisti (plateau, fee 2x, corr,
FULL DD di portafoglio). **Goal di progetto:** trovare un Price Ladder che *diversifichi*
(PASSI il gate) senza accendere la coda di trend.
---
## 1. Cosa era già noto
Il gioco-griglia precedente aveva trovato una **griglia ETH fortissima standalone**
(Sharpe ~5.61) ma **BOCCIATA al gate PORT06**: corr ~0.40 con le fade ETH già deployate
**ridondante**, non un diversificatore. La lezione: lo standalone Sharpe non basta, conta
quanto il ladder *aggiunge ortogonalmente* al portafoglio. Questa ricerca cerca proprio un
ladder che PASSI il gate = che abbia corr bassa con gli sleeve esistenti.
---
## 2. Risultato NUOVO — i ladder PASSANO il gate
Sì. Su **tutti gli asset liquidi** (BTC e ETH) e su quasi tutte le combinazioni TF×regime,
i ladder migliorano l'OOS Sharpe di portafoglio (10.86 → ~11.011.35) **con corr bassa**:
- **BTC** (15m/30m/1h, range o none): corr **0.150.27**, molto più ortogonale della
griglia ETH bocciata (0.40). OOS di portafoglio 11.111.35 (il guadagno OOS più grosso).
- **ETH** (15m/30m/1h): corr **0.210.57** (15m la peggiore, fino a 0.57; a 1h scende a
0.220.39). OOS di portafoglio ~10.911.1.
- **SOL/LTC**: SOL passa solo 2 celle, half-only, edge standalone quasi nullo
(full_sh ~0.2); **LTC è negativo ovunque** (full_sh 0.4…−0.8, bocciato) → solo BTC/ETH
liquidi danno un ladder vivo, come atteso.
Il gate **misura corr + OOS**, e su questi due assi i ladder BTC vincono nettamente.
### Migliori candidati (dai `verify` avversariali)
| Asset/TF/regime/struttura | OOS base→new | FULL DD standalone | FULL DD portafoglio (full / half) | corr | fee2x OOS | robust? |
|---|---|---|---|---|---|---|
| **ETH 1h range2 rd0.16 ru0.04 L4** | 10.86→**11.00** | 11.79% | **2.70** / 2.52 | 0.305 | 4.89 | ✅ sì |
| **ETH 1h range2 rd0.16 ru0.06 L6** | 10.86→11.01 | 12.09% | 2.74 / 2.54 | 0.328 (al filo) | 5.05 | ✅ sì |
| **ETH 1h range2 rd0.16 ru0.06 L4** | 10.86→10.98 | 12.64% | **2.68** / 2.51 | 0.249 | 4.44 | ✅ sì |
| **ETH 30m range2 rd0.16 ru0.04 L3** | 10.86→10.93 | 12.56% | **2.48** / 2.41 | 0.223 | 4.21 | ✅ sì |
| **ETH 1h none rd0.20 ru0.06 L6** | 10.86→11.03 | 17.89% | 2.59 / 2.46 | 0.271 | 5.43 | ✅ sì |
| **ETH 30m none rd0.16 ru0.06 L4** | 10.86→10.92 | 18.68% | **2.37** / 2.35 | 0.319 | 5.42 | ✅ sì |
| **ETH 1h none rd0.16 ru0.06 L4** | 10.86→10.96 | 23.13% | **2.40** / 2.37 | 0.224 | 4.59 | ✅ sì |
| **BTC 1h none rd0.08 ru0.06 L3** | 10.86→**11.34** | **53.69%** | 3.11 / **2.48** | 0.216 | 5.78 | ⚠ half-only |
| **BTC 30m range2.5 rd0.08 ru0.06 L3** | 10.86→11.22 | 55.33% | 3.27 / **2.50** | 0.265 | 5.19 | ⚠ half-only |
| **BTC 30m none rd0.08 ru0.06 L3** | 10.86→11.32 | 55.33% | 3.27 / **2.44** | 0.220 | 5.37 | ⚠ half-only (tail) |
| **BTC 1h range1.5 rd0.20 ru0.06 L6** | 10.86→11.13 | 54.43% | 3.19 / **2.33** | **0.161** | 5.46 | ⚠ half-only (tail) |
`rd` = spacing down, `ru` = spacing up, `L` = livelli. Tutti i finalisti hanno plateau
robusto (47 vicini di struttura restano PROMOSSO) e fee2x ~= OOS standalone.
---
## 3. IL NODO — la FULL DD (che il gate IGNORA)
**Il gate verdict NON guarda la FULL DD.** Misura solo corr + OOS Sharpe/DD. Una griglia
long-only ha una coda di trend 2021/22 (viene comprata fino in fondo mentre il prezzo crolla
in un trend persistente) → la **FULL DD standalone** è il vero rischio nascosto. Il filtro
onesto è la **FULL DD di PORTAFOGLIO** (`full_full_dd` / `half_full_dd`), con soglia di
tail-risk **~+0.8pp sopra il baseline 2.47%** (cioè restare sotto ~3.3%).
Due famiglie nette emergono:
- **BTC** — FULL DD standalone **~5467%** (la coda long-only è devastante, come temuto).
Il regime-gate range *taglia gli ingressi nei trend* ma **non immunizza** dalle posizioni
già aperte → a FULL size la FULL DD di portafoglio sale a **3.16.6%** (+0.6/+4.0pp,
**oltre la soglia tail** in quasi tutte le celle). **A HALF size invece rientra a 2.332.54%**
(~baseline) preservando l'OOS ~11.011.3. → **BTC deployabile SOLO a half-size**, e il
guadagno OOS più grosso (11.3+) viene proprio da queste celle a coda alta.
- **ETH** — FULL DD standalone **molto più bassa** del previsto: **1233%** (1h range scende
a **~12%**), NON il ~60% temuto. Lo SL-buffer + flat-skip + il regime-gate range tengono la
griglia fuori dai trend estesi. Conseguenza: la FULL DD di portafoglio resta **2.372.74%**
(+0.0/+0.27pp, **sotto soglia anche a FULL size**) — in diversi casi *scende* sotto il
baseline (es. ETH 30m none L4 → 2.37%). → **ETH è la famiglia tail-safe.** Il prezzo è una
corr più alta (0.220.33 sui finalisti 1h/30m, fino a 0.57 sul 15m da scartare).
**Lettura chiave:** il regime-gate *abbassa* la FULL DD ma su BTC **non a sufficienza** a
full size (la coda 54% è troppo grande); è l'**half-size** a fare il lavoro. Su ETH la coda
è già piccola e il portafoglio l'assorbe. Quindi il discriminante reale è **asset (ETH vs
BTC) + size**, non solo il regime-gate.
---
## 4. Vantaggio Deribit — fill MAKER
Un Price Ladder è una griglia di **ordini LIMIT ai livelli** → su Deribit i fill sono
**MAKER (~0% fee)**, non taker. L'harness assume **0.10% RT taker** ovunque → è
**CONSERVATIVA**: la stessa griglia live pagherebbe meno fee e potrebbe rendere PIÙ del
backtest. Lo confermano i `verify`: il **fee2x** (raddoppio fee) lascia l'OOS Sharpe quasi
intatto (~98% trattenuto su quasi tutti i finalisti) → nessun fee-cliff, e con i fill maker
reali c'è upside, non downside, sul lato costi. (Il rischio resta la coda di trend e lo
slippage/fill in esecuzione reale, non le fee.)
---
## 5. Verdetto operativo
**C'è un Price Ladder deployabile.** Il candidato più pulito su tutti gli assi
(PROMOSSO + FULL DD di portafoglio controllata a full size + plateau robusto + corr genuina):
> **ETH 1h, regime=range trend_max 2.0, rd=0.16 / ru=0.04 / 4 livelli, sl_buf 0.12 / tp_buf 0.05, max_bars 720.**
> OOS 10.86→11.00, FULL DD portafoglio **2.70% a full / 2.52% a half** (standalone 11.79%),
> corr 0.305, fee2x OOS 4.89, plateau 4 vicini PROMOSSO. **Deploy raccomandato a HALF size**
> (FULL DD +0.05pp, marginale; il caveat slippage live giustifica la prudenza).
**Alternative ETH equivalenti** (stessa banda di sicurezza, scegliere per corr/coda):
`ETH 1h range2 rd0.16 ru0.06 L4` (corr 0.249, la più bassa) e `ETH 30m range2 rd0.16 ru0.04 L3`
(FULL DD portafoglio 2.48, ~baseline). Tutte ETH 1h/30m **range-gated rd≥0.16** → NON scendere
sotto rd 0.16 (rd 0.12 fa risalire la corr verso 0.38 = ricomincia a somigliare alle fade ETH;
il 15m ha corr 0.430.57 = troppo ridondante, scartare).
**I BTC NON a full size.** Danno il guadagno OOS più grande (fino a 11.34) e la corr più bassa
(0.160.22 = la diversificazione migliore), MA la FULL DD standalone ~54% li rende
**tail-risky a full size** (FULL DD portafoglio 3.16.6%, oltre soglia). **Deployabili SOLO a
half-size** (FULL DD portafoglio 2.332.50), dove restano un diversificatore eccellente. Il
migliore BTC half-size è `BTC 1h range1.5 rd0.20 ru0.06 L6` (corr **0.161**, FULL DD portafoglio
half **2.33**).
**BLOCCO prima di qualsiasi deploy:** il gate PORT06 (corr + OOS) è **necessario ma non
sufficiente** — non controlla la FULL DD, e l'OOS held-out è un **regime calmo** (202425). La
coda di trend 2021/22 è reale e standalone è enorme su BTC. **Nessun deploy senza un gate
stressato sul 2022** (verificare la FULL DD di portafoglio attraverso il trend duro, non solo
l'OOS calmo) e una conferma su **ledger reale shadow** per slippage/fill maker. Il verdetto del
gate va sempre incrociato con la FULL DD di portafoglio (filtro vero) + fattibilità feed live +
regime recente — la stessa lezione del timing-sweep pairs/honest (2026-06-14).
**Sintesi:** ETH 1h/30m range-gated rd≥0.16 a half-size è il **deploy-candidate sano e
tail-safe**; BTC è il diversificatore migliore ma **solo a half-size** per la coda di trend.
Prossimo passo obbligatorio: stress-test 2022 + shadow ledger reale (i fill maker possono solo
migliorare il risultato sul lato fee).
---
## 6. ⚠️ RETTIFICA CRITICA — il verdetto §5 è ROVESCIATO (critico + verifica dati)
Il critico di completezza + una verifica diretta dei dati **ribaltano la raccomandazione di
deploy della §5**. Messo a verbale: la §5 è SUPERATA da quanto segue.
**(1) I risultati BTC poggiano su DATI CORROTTI (spike-print), verificato.** La "coda di trend
BTC ~54%" su cui ruota la §3 NON è un crollo di mercato: è **feed sporco**. Misurato su `btc_1h`:
**37 barre con range intrabar >20%**, concentrate a **feb-2024** (es. 2024-02-13: low=38.580 con
close~49.968 = wick fasullo 23% che rimbalza nella stessa barra), con min 22.706 / max 104.688 in
un trimestre dove BTC stava ~4757k. È lo **stesso spike-print testnet documentato ovunque in
CLAUDE.md** (TP_PHANTOM / feed congelato). L'engine SL-gap-aware filla su questi wick fantasma e la
griglia "compra" i dip fasulli → la coda BTC ~54% e parte dell'edge OOS sono **artefatto, non
mercato**. Conseguenza: i candidati BTC NON sono affidabili, e lo "stress 2022" proposto NON
toccherebbe la coda vera (che è a feb-2024, da dati sporchi). Serve `close_only` + pulizia feed.
**(2) Il guadagno OOS è RUMORE al peso deployabile.** +0.14 OOS Sharpe su base 10.86, con il ladder
che pesa **~5.3% full / ~2.7% half** in un portafoglio già a Sharpe ~11, su **592 giorni di un
solo regime calmo** (2024-10→2026-05, niente 2022). Nessun test di significatività
(block-bootstrap). A half-size il contributo è quasi inerte (a volte l'OOS DD *migliora*): è dentro
l'errore di campionamento, non un edge dimostrato.
**(3) La diversificazione ETH è più sottile di quanto venduto.** Il finalista ETH "raccomandato" è
**corr 0.305** (non 0.150.20: quella è BTC half-only non deployabile), già vicino allo **0.40 che
fece bocciare la griglia ETH originale**. Peggio: il `regime_mask` usa **lo stesso filtro
trend-distance** (|closeEMA200|/ATR) delle fade ETH già live → la "diversificazione" è in parte
illusoria (gate e sleeve condividono il filtro). La corr misurata è **daily in regime calmo**, NON
co-tail: una griglia long-only e una fade reversion possono co-crollare in un trend (entrambe
"comprano il calo") — la metrica che conterebbe (co-movimento nella coda) non è calcolata.
**(4) La FULL DD standalone è mascherata dal compounding.** Il finalista ETH compone a **~31x
(+3.1M%) in 5.4 anni**: una "FULL DD 11.79%" accanto a un ritorno di milioni-% è artefatto di
notional reinvestito, e il daily-resample **nasconde l'inventory-risk intrabar** che il docstring
di `grid_game_gate.py` (righe 57) avverte esplicitamente di non vedere.
**(5) Stress disponibile NON usato.** `ladder_search`/`grid_mtm` hanno `close_only=True` (filla solo
sul close, neutralizza gli spike-print): NON è stato girato sui finalisti — proprio il test che
smonterebbe la coda BTC fasulla.
### Verdetto rettificato
**NON c'è un Price Ladder deployabile dimostrato.** La ricerca ha prodotto (a) una **harness onesta
e validata** (`ladder_search.py` + regime-gate, riproduce esatto il gate noto), (b) un **risultato
negativo/guardingo** robusto: ogni candidato o è ridondante (ETH, corr→0.40), o poggia su dati
sporchi (BTC spike-print), o è rumore al peso deployabile. **Il multi-agente + critico ha evitato
un deploy ingiustificato** — esattamente la lezione anti-squeeze del progetto.
**Prima di riconsiderare QUALSIASI ladder servono, in ordine:** (1) **pulire il feed BTC** dagli
spike-print + girare `close_only` sui finalisti; (2) **test di significatività** del delta OOS
(block-bootstrap) al peso reale; (3) **co-tail / corr nei crash** (2022), non corr daily calma;
(4) isolare e spiegare l'**evento feb-2024**. Config live **invariata**.
---
## 7. RE-GATE su DATI PULITI (post clean_feed.py) — quadro onesto finale
Dopo `clean_feed.py` (254 spike-print riparati con Binance) ho ri-gateato i top candidati con
le verifiche che il critico chiedeva (`ladder_regate_clean.py`): gate PORT06 + stress
`close_only` + **DD per anno** sulla storia completa.
| Candidato | DD (gate, 2021+) | OOS Sh | corr | fee2x | close_only OOS | gate½ | **DD 2018 (tail vero)** |
|---|---:|---:|---:|---:|---:|---|---:|
| ETH 1h rd0.16 ru0.04 L4 range2 | 12.4% | 4.70 | 0.29 | 4.62 | 0.24 | PROMOSSO | 52.4% |
| ETH 1h rd0.16 ru0.06 L4 range2 | 12.6% | 4.25 | 0.23 | 4.17 | 0.20 | PROMOSSO | 49.9% |
| BTC 1h rd0.08 ru0.06 L3 none | 10.8% | 5.90 | 0.24 | 5.78 | 0.60 | PROMOSSO | 50.3% |
| **BTC 1h rd0.20 ru0.06 L6 range1.5** | 14.8% | 5.58 | 0.23 | 5.46 | 0.89 | PROMOSSO | **27.7%** |
| BTC 30m rd0.08 ru0.06 L3 none | 14.8% | 5.47 | 0.25 | 5.37 | 1.07 | PROMOSSO | 36.6% |
**Cosa cambia coi dati puliti:**
1.**L'obiezione "coda artefatto" CADE.** Il 54% del BTC era spike-print 2024: ora la DD del
gate (2021+) è **~11-15%** e tutti i candidati restano PROMOSSO a half-size, corr 0.22-0.29,
reggono fee2x. Sono candidati *veri*, non artefatti.
2. 🔴 **Emerge il tail VERO: il 2018** (44/52% standalone), che **il gate NON VEDE** perché
l'IDX del progetto parte dal 2021-01-01. Una griglia long-only sarebbe stata sventrata nel
bear 2018 (BTC 84% sull'anno). È il nuovo rischio portante — e una **lacuna metodologica
generale**: tutte le metriche "FULL" del progetto escludono il 2018. Mitigante reale: il
**regime-gate stretto** (BTC rd0.20 L6 **range1.5**) dimezza il tail 2018 a **27.7%** (vs
50% senza gate) → il gate-di-regime fa il suo lavoro contro i bear sostenuti.
3. 🟡 **L'edge dipende dai fill INTRABAR.** `close_only` (fill solo sul close) fa crollare l'OOS
(4.7→0.24 ETH, 5.9→0.6 BTC). Per ordini LIMIT i fill intrabar sono legittimi (close_only è
troppo severo come stress), ma il gap segnala forte **sensibilità all'ipotesi di fill**
il ledger shadow reale è indispensabile prima di fidarsi.
4. 🟡 Restano: corr 0.22-0.29 (moderata), guadagno OOS ai limiti del rumore al peso deployabile
(~2.7% half), OOS singolo regime calmo.
**Verdetto finale (clean data):** la pulizia ha rimosso l'obiezione FALSA (coda artefatto) e
promosso i candidati a *genuini*, ma ha anche scoperto il tail REALE (2018, gate-cieco) e
confermato due cautele (fill intrabar, OOS rumoroso). **Il candidato più sensato è ora
`BTC 1h rd0.20 ru0.06 L6 regime=range trend_max1.5`**: PROMOSSO, corr 0.23, e la **miglior coda
2018 (27.7%)** grazie al regime-gate. **Prima di QUALSIASI deploy**: (1) backtest che INCLUDA il
2018 nel gate (non solo IDX 2021+), (2) ledger shadow reale per i fill intrabar/maker, (3) half-size.
La pulizia del feed resta il guadagno trasversale più grande (irrobustisce ogni backtest del progetto).
---
## 8. I 3 PASSI PRE-DEPLOY + STUDIO SL/TP (ladder_sltp_study.py)
**[1] Valutazione 2018-INCLUSIVE** (standalone, tutta la storia; il gate IDX2021+ è cieco al 2018):
| Candidato (sl0.12/tp0.05) | full_sh | full_dd | oos_sh | DD 2018 | DD 2021 |
|---|---:|---:|---:|---:|---:|
| BTC 1h L6 **range1.5** | 3.18 | 27.7% | 4.95 | **27.7%** | 14.8% |
| BTC 1h L3 none | 2.93 | 60.2% | 5.51 | **50.3%** | 10.8% |
Il regime-gate già dimezza il tail vero (2018: 50%→−27.7%). Senza gate la griglia long-only è insostenibile (60%).
**Studio SL/TP** (sweep sl_buf × tp_buf sul candidato regime-gated, full 2018+):
- **Sweet spot: `sl_buf=0.10, tp_buf=0.03` → DD 2018 23.5%** (da 27.7%), oos_sh 5.06, full_sh 2.92. Migliora il tail SENZA intaccare l'edge.
- **Lezione (conferma il prior del progetto):** un SL **troppo stretto PEGGIORA** (sl 0.06 → 33/35%): stoppa e rideploya più in basso nel coltello che cade = la trappola del falso-negativo mean-reversion. Un SL **troppo largo** (0.20) idem (29/31%, oos più basso). Il bene sta in mezzo (0.10).
- **Il SL da solo (senza regime-gate) è ERRATICO** (tabella L3 none non-monotona: 20% → 52% → 43% al variare di pochi punti): è il **regime-gate** il controllo primario della coda; il SL moderato fine-tuna. Catastrophe-control = "non deployare nel bear" (regime) + "cappa l'episodio" (SL 0.10), NON uno stop stretto.
**[2] Fill maker vs taker** (Deribit: i livelli sono LIMIT → maker):
| fee | oos_sh | DD 2018 | gate½ |
|---|---:|---:|---|
| taker 0.10% RT (harness) | 5.06 | 23.5% | PROMOSSO (OOS 10.86→11.0, corr 0.195) |
| **maker 0%** (Deribit reale) | 5.18 | 22.7% | PROMOSSO (OOS→11.0, corr 0.194) |
Il maker è leggermente MIGLIORE → la harness taker è conservativa, nessun fee-cliff. (La parte live = shadow ledger reale resta il passo operativo finale.)
**[3] Half-size:** il gate "half" è già a metà size e PROMOSSO; la coda 2018 standalone 23.5% si dimezza sul book (~12% in un 2018-repeat a half-size) = reale ma gestibile.
### CONFIG FINALE raccomandata (con SL/TP aggiunti)
> **BTC 1h · regime=range trend_max 1.5 · rd 0.20 / ru 0.06 / 6 livelli · sl_buf 0.10 / tp_buf 0.03 · HALF-size.**
> - Tail 2018-inclusive: 23.5% standalone (~12% sul book a half) — reale ma controllato dal regime-gate + SL moderato.
> - Gate PORT06: PROMOSSO (OOS 10.86→11.0, corr 0.195); maker Deribit lo migliora; nessun fee-cliff.
> - **Unico passo residuo: shadow ledger reale** (fill intrabar/maker su Deribit) — operativo, non backtestabile.
Caveat onesti residui: l'OOS gain al peso deployabile è modesto (10.86→11.0); il tail 2018 non è zero (12% a half); l'edge dipende dai fill intrabar (per i LIMIT è legittimo, ma lo shadow lo deve confermare).
File diff suppressed because one or more lines are too long
+181
View File
@@ -0,0 +1,181 @@
# Piano operativo — Micro-test MAINNET (Deribit)
**Obiettivo.** Rispondere all'unica domanda che testnet e backtest non possono:
*quando si passa ai fill VERI, quanto edge sopravvive?* Si misura con poco denaro
reale (€5001500) per 24 settimane, leggendo il report **ledger-vs-backtest**.
Costo del verdetto: poche centinaia di € invece di scoprirlo a €5k.
**Perché serve (e perché testnet non basta).** Testnet ha feed farlocchi — il
2026-06-13 `ETH-PERPETUAL` inverse era **congelato a 1661.95 da 12h+** mentre il
prezzo reale si muoveva (gap 1.3%); BTC era vivo. Su mainnet l'arbitraggio tiene
inverse ≈ lineare ≈ realtà (entro bps): **il micro-test risolve il problema feed
per costruzione**, oltre a dare fill/slippage/fee reali. (Il 2026-06-14 `ETH-PERPETUAL`
testnet era ancora congelato a 1661.95 da 36h+ → conferma definitiva: testnet è inaffidabile.)
---
## STATO (2026-06-16)
-**Token mainnet wired e VERIFICATO.** `MAINNET_TOKEN` (da cerbero-mcp, che ha già le chiavi
Deribit LIVE) copiato in **`.env.mainnet`** (chmod 600, gitignored), SEPARATO dal `.env`
condiviso. Verifica read-only: `is_mainnet()=True`, auth OK su `get_account_summary` (nessun
ordine). Il `.env` condiviso resta testnet → il runner attuale NON flippa a mainnet a un riavvio.
-**FASE 0 SMOKE VERDE (2026-06-16).** Catena d'esecuzione validata su mainnet con soldi VERI a
costo ~0: LIMIT SELL `ETH_USDC` spot 0.0138 ETH @ floor 1776.3 (marketable, NON market) →
`order_state=filled`, average_price **1778.1** (= bid, zero slippage negativo), **fee 0.0**.
Riconciliazione conto: USDC 25.00→**49.54** (+24.54), ETH→0.000078 dust, nessun ordine resting.
Provato end-to-end: routing live + auth + place_order limit + fill + verifica trade + balance.
(Doppio scopo: consolidare l'ETH stranded in margine USDC + smoke.) Diario
`docs/diary/2026-06-16-mainnet-fase0-smoke.md`.
- 🟡 **FINANZIAMENTO PARZIALE: ~$49.54 USDC** (deposito iniziale ~$50: 25 USDC + 0.0138 ETH, ora
consolidati). **Sotto il minimo €500** del piano → basta per la meccanica (Fase 0 ✅) ma NON per
testare l'edge (a $50 di margine il rumore di arrotondamento BTC soffoca il segnale). **Serve
top-up a ~€500-1000 USDC** prima della Fase 1.
- ⏭️ Appena finanziato a ~€1000: avviare il micro-test come **servizio separato** (env_file
`.env.mainnet`, dir dati pulita, `portfolios.yml` fade-only) → Fase 1.
---
## Prerequisiti (umani, fuori dal codice)
1. **Conto Deribit MAINNET** (KYC) finanziato con **~€1000** in USDC (vedi sizing).
2. **MAINNET_TOKEN Cerbero** abilitato al trading mainnet sul conto (lato CerberoSuite).
3. Decidere il capitale: **€1000 raccomandato** (vedi sezione Sizing).
> NB sicurezza: il MAINNET_TOKEN dà accesso a soldi veri. Va in `.env` (gitignored),
> mai committato. Un bot-tag dedicato (`pythagoras-mainnet`) per isolarlo nell'audit.
---
## Come si punta a mainnet (già pronto, 1 sola modifica .env)
Il client legge il token da env (default = testnet). Il token mainnet vive in un file
**`.env.mainnet`** DEDICATO (NON nel `.env` condiviso, che resta testnet → il runner attuale
non flippa a mainnet a un riavvio):
```
# .env.mainnet (gitignored, chmod 600) — caricato SOLO dal servizio micro-test mainnet
CERBERO_TOKEN=<MAINNET_TOKEN>
CERBERO_BOT_TAG=pythagoras-mainnet
```
Il servizio micro-test usa `env_file: [.env, .env.mainnet]` (le chiavi di `.env.mainnet`
prevalgono → Telegram da `.env`, token mainnet da `.env.mainnet`).
**Nessuna modifica di codice.** `CerberoClient.is_mainnet()` lo rileva; il runner,
l'ExecutionClient e gli smoke usano lo stesso client. Per tornare a testnet: togliere
le due righe. (Verificato: default invariato testnet, override flippa il rilevamento.)
> Consiglio: **non riusare il container/data testnet**. Avviare il micro-test su una
> dir dati pulita (`data/` separata o nuovo volume) così i ledger reali mainnet non
> si mescolano allo storico testnet, e il report parte clean-start.
---
## Sizing & taglia — perché €1000 e SOLO le fade in fase 1
Notional = `alloc_sleeve × position_size × leverage`; amount = notional/prezzo,
quantizzato allo step Deribit. Rumore di arrotondamento = step/amount.
**Fades-only (6 sleeve equal, pos 0.5 × leva 3) — rumore arrotondamento:**
| Capitale | notional/fade | round% BTC | round% ETH |
|----------|---------------|-----------|-----------|
| €500 | $125 | 5.2% | 1.3% |
| **€1000** | **$250** | **2.6%** | **0.7%** |
| €1500 | $375 | 1.7% | 0.4% |
**€1000 è il punto giusto**: BTC ~2.6% di rumore (leggibile), abbastanza piccolo
da non far male. €500 funziona ma BTC sale al 5%; €1500 è il più pulito.
**Perché NIENTE pairs/SH01 in fase 1:**
- **Pairs** (pos 0.13, 2 gambe): a €1000 il notional/gamba è ~$21 → arrotondamento
**~30%** (la gamba alt anche peggio): a questa taglia misureresti l'arrotondamento,
non l'edge. Servono ~€58k per pairs puliti. Inoltre l'executor a 2 gambe ha
leg-risk: più superfici d'errore. → fuori dalla fase 1.
- **SH01**: bootstrap full-history dal parquet inverse; su mainnet va ri-derivato il
parquet da prezzi mainnet. Diversificatore, non core. → fuori dalla fase 1.
Le **6 fade single-leg (MR01/02/07 × BTC/ETH)** sono il **core edge**, l'esecuzione
più semplice (open market + TP limit resting + disaster-SL), e quelle col track
record validato col worker reale. Sono il test giusto.
---
## Fasi
### Fase 0 — Smoke mainnet (€0 di rischio reale, 1 ordine minimo)
Con il token mainnet attivo, eseguire `live_exec_smoke.py` (ordine→verifica→fee) e
`live_shadow_smoke.py` su **size minima** per confermare: auth mainnet OK, strumenti
USDC risolti, fill verificato per order_id, fee reali lette. Se uno fallisce, STOP.
### Fase 1 — Solo fade, €1000, 24 settimane
`portfolios.yml` per il micro-test:
```yaml
overrides:
total_capital: 1000
leverage: 3 # config che vorremmo deployare (misura il vero)
position_size: 0.5
paper_sleeves: [TR01, ROT02, TSM01, XS01, PR01..., SH01...] # tutto tranne le 6 fade -> paper
execution:
enabled: true
sleeves: [MR01, MR02, MR07] # solo fade (DIP01 opz.: 1h BTC, core honest)
pairs_enabled: false # niente 2-gambe in fase 1
disaster_sl_pct: 0.30 # rete di coda invariata
real_truth: true
```
> Alternativa prudente settimana-1: `leverage: 2` per dimezzare il DD mentre si
> verifica l'esecuzione, poi 3.
Avviare il runner mainnet (container separato o stessa immagine con `.env` mainnet),
+ reconciler orario + report ledger-vs-backtest giornaliero `--since <data-start>`.
### Fase 2 — Verdetto (dal report ledger-vs-backtest)
Aspettare **≥1020 trade reali** (su mainnet le fade ETH/BTC tradano regolarmente, no
feed congelato). Criterio (lo stesso del report, soglie già nel verdetto 🟢/🟡/🔴):
- 🟢 **slippage medio ≤15 bps E leakage per-trade < €0.30** in relativo → l'edge regge,
si scala.
- 🟡 leakage moderato → estendere il campione, capire dove perde.
- 🔴 slippage >40 bps o leakage che mangia l'edge → **NON scalare**; l'edge non
sopravvive all'esecuzione (hai perso ~€2050, non €5k).
### Fase 3 — Espansione (solo se 🟢)
1. Alzare il capitale (€1000→€2k→€5k) — i pairs diventano puliti sopra ~€5k.
2. Riaccendere pairs (a conto flat) e SH01 (con parquet mainnet).
3. Continuare a leggere il report ad ogni step.
---
## Rischi & safeguard (già implementati)
- **Disaster-SL on-book 30%** ad ogni apertura → protegge da outage del runner.
- **Reconciler orario** (`reconcile_account --telegram`) → drift conto-vs-libri +
guard `STALE_REAL_POSITION` (worker non gestito).
- **Netting close** (v1.1.25) → niente gambe orfane/close cappati.
- **Real-truth ledger** → equity = soldi veri sul conto.
- **Kill-switch**: `execution.enabled: false` (smette di aprire reali, gestisce le
uscite) oppure stop del container + flatten manuale (procedura già rodata).
- Perdita massima plausibile in fase 1: qualche % di €1000 = **€2080**. È il prezzo
del verdetto.
---
## Cosa NON fare
- Non partire con pairs/SH01 (rumore/complessità).
- Non scalare prima del verdetto 🟢 stabile per più giorni.
- Non mescolare i dati testnet e mainnet nello stesso ledger.
- Non committare mai il MAINNET_TOKEN.
---
## Checklist di avvio
- [x] MAINNET_TOKEN Cerbero abilitato + verificato (auth OK, is_mainnet=True) — 2026-06-14
- [x] Token in `.env.mainnet` (separato, gitignored, chmod 600) + `CERBERO_BOT_TAG=pythagoras-mainnet`
- [x] Fase 0 smoke mainnet VERDE (LIMIT SELL ETH_USDC filled, fill@bid, fee 0, balance riconciliato) — 2026-06-16
- [~] Conto Deribit mainnet finanziato: **~$49.54 USDC** (parziale, sotto il min €500) ← **TOP-UP a ~€1000 = BLOCCO Fase 1**
- [ ] Servizio micro-test separato (env_file `.env.mainnet`, dir dati pulita)
- [ ] `portfolios.yml` micro-test (fade-only, €1000, exec sleeves MR01/02/07)
- [ ] runner mainnet + reconciler + report ledger-vs-backtest schedulati
- [ ] dopo ≥1020 trade: leggere il verdetto → scala / aspetta / stop
@@ -0,0 +1,377 @@
# Fase 2-B — Worker live honest/TSM01 (dedicati) — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development o executing-plans. Steps con checkbox `- [ ]`.
**Goal:** Costruire i worker live mancanti perché PORT06 giri live al completo (oltre a fade+pairs+shape già pronti): DIP01, TR01 (basket), ROT02 (rotation), TSM01 (tsmom rotation), e integrarli nel `PortfolioRunner`.
**Architecture:** Worker DEDICATI per ogni strategia (scelta utente). DIP01 è single-asset → Strategy subclass + `StrategyWorker` esistente. TR01/ROT02/TSM01 sono multi-asset/rotation → tre classi worker nuove in `src/live/` con stato per-asset persistente, ciascuna fedele alla rispettiva funzione di backtest in `scripts/analysis/{honest_improve2,tsmom_research}.py`. Integrazione in `src/portfolio/runner.py::build_worker_for` + tick.
**Tech Stack:** Python 3.11, pandas/numpy, pytest. Riusa CerberoClient v2 (multi-asset fetch), PortfolioLedger, e le funzioni di riferimento honest/tsm.
**Branch:** `portfolio_phase2`. **Spec madre:** `docs/superpowers/specs/2026-05-29-portfolios-design.md` (§ scope live, fase 2).
**Riferimenti di logica (NON modificare, sono la verità del backtest):**
- DIP01 → `honest_improve2.dip_market_gated` (z-score dip, gate BTC>SMA, TP=SMA/SL=ATR/max_bars, intrabar).
- TR01 → `honest_improve2._tr_basket_daily` (per asset 4h: EMA20>EMA100 long/flat; basket equal-weight).
- ROT02 → `honest_improve2._rot_daily_equity` (panel 1d, mom 60g, top-3 se mom>0 e BTC>SMA100, gross 0.45 split, ribilancio giornaliero).
- TSM01 → `tsmom_research.tsmom_sim` (panel 1d, Σ sign(P/P[-h]) h∈{63,126,252} ≥ thr=1.0, gate BTC>SMA100, gross 0.30 split).
---
## File structure
| File | Responsabilità |
|------|----------------|
| `scripts/strategies/DIP01_dip_buy.py` | Strategy `Dip01DipBuy` (single-asset; metadata tp/sl/max_bars + gate) |
| `src/live/basket_trend_worker.py` | `BasketTrendWorker` (TR01): N asset 4h, EMA cross, long/flat per asset |
| `src/live/rotation_worker.py` | `RotationWorker` (ROT02): panel 1d, dual-momentum top-k, gross split |
| `src/live/tsmom_worker.py` | `TsmomWorker` (TSM01): panel 1d, consenso segni multi-orizzonte |
| `src/live/strategy_loader.py` | **mod**: aggiungi `DIP01_dip_buy` a MODULE_MAP |
| `src/portfolio/runner.py` | **mod**: `build_worker_for` gestisce kind "basket"/"rotation"/"tsmom"; tick multi-asset |
| `src/portfolio/base.py` (`_defs.py`) | **mod**: SleeveSpec degli honest/tsm con `kind` e `universe` corretti |
| `tests/portfolio/test_honest_workers.py` | unit per ciascun worker + replay==backtest su finestra |
**Universi:** TR01 = [BNB,BTC,DOGE,SOL,XRP] (4h); ROT02/TSM01 = `available_assets()` (1d). I worker multi-asset ricevono il dict {asset: df} dal runner.
---
## Task 1: DIP01 come Strategy single-asset
**Files:** Create `scripts/strategies/DIP01_dip_buy.py`; Modify `src/live/strategy_loader.py`; Test `tests/portfolio/test_dip01.py`.
- [ ] **Step 1: Test (fallisce)**`tests/portfolio/test_dip01.py`:
```python
import pandas as pd
from src.data.downloader import load_data
from scripts.strategies.DIP01_dip_buy import Dip01DipBuy
def test_dip01_generates_long_signals_with_exits():
df = load_data("BTC", "1h").iloc[-5000:].reset_index(drop=True)
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
sigs = Dip01DipBuy().generate_signals(df, ts, asset="BTC", tf="1h")
assert len(sigs) > 0
s = sigs[0]
assert s.direction == 1 # dip-buy è solo long
assert {"tp", "sl", "max_bars"} <= set(s.metadata)
```
- [ ] **Step 2:** `uv run pytest tests/portfolio/test_dip01.py -v` → FAIL (ModuleNotFoundError).
- [ ] **Step 3: Implementa `scripts/strategies/DIP01_dip_buy.py`.** Replica ESATTA della logica di `dip_market_gated` (default `market_n=0` = senza gate, come lo sleeve DIP01_BTC del portafoglio: vedi combine_portfolio che usa `market_n=0`). Genera Signal long quando `z[i] <= -z_in and z[i-1] > -z_in`, con metadata `tp=SMA[i]`, `sl=c[i]-sl_atr*atr[i]`, `max_bars`. fee_rt=0.001, leverage 3, position 0.15.
```python
"""DIP01 — Dip-buy mean-reversion single-asset (z-score sotto-banda). Honest family.
Replica live della logica validata in scripts/analysis/honest_improve2.dip_market_gated
(con market_n=0, come lo sleeve DIP01_BTC del portafoglio): compra quando lo z-score del
prezzo rispetto a SMA(n) incrocia sotto -z_in; esce a TP=SMA, SL=close-sl_atr*ATR, o max_bars.
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from src.strategies.base import Strategy, Signal # noqa: E402
def _atr(df, n=14):
h, l, c = df["high"].values, df["low"].values, df["close"].values
pc = np.roll(c, 1); pc[0] = c[0]
tr = np.maximum(h - l, np.maximum(np.abs(h - pc), np.abs(l - pc)))
return pd.Series(tr).rolling(n).mean().values
class Dip01DipBuy(Strategy):
name = "DIP01_dip_buy"
description = "Dip-buy mean-reversion single-asset (z-score), exit TP=SMA/SL=ATR/max_bars"
default_assets = ["BTC"]
default_timeframes = ["1h"]
fee_rt = 0.001
leverage = 3.0
position_size = 0.15
def generate_signals(self, df: pd.DataFrame, ts: pd.DatetimeIndex,
n: int = 50, z_in: float = 2.5, sl_atr: float = 2.5,
max_bars: int = 24, **params) -> list[Signal]:
c = df["close"].values
ma = pd.Series(c).rolling(n).mean().values
sd = pd.Series(c).rolling(n).std().values
a = _atr(df, 14)
z = (c - ma) / np.where(sd == 0, np.nan, sd)
out: list[Signal] = []
for i in range(n + 14, len(c)):
if np.isnan(z[i]) or np.isnan(a[i]) or np.isnan(ma[i]):
continue
if z[i] <= -z_in and z[i - 1] > -z_in:
out.append(Signal(idx=i, direction=1, entry_price=float(c[i]),
metadata={"tp": float(ma[i]),
"sl": float(c[i] - sl_atr * a[i]),
"max_bars": int(max_bars)}))
return out
```
- [ ] **Step 4: Registra nel loader.** In `src/live/strategy_loader.py` MODULE_MAP aggiungi:
```python
"DIP01_dip_buy": ("DIP01_dip_buy", "Dip01DipBuy"),
```
- [ ] **Step 5:** `uv run pytest tests/portfolio/test_dip01.py -v` → 1 passed.
- [ ] **Step 6: Commit**
```bash
git add scripts/strategies/DIP01_dip_buy.py src/live/strategy_loader.py tests/portfolio/test_dip01.py
git commit -m "feat(live): DIP01 dip-buy come Strategy single-asset (worker via StrategyWorker)"
```
**Nota:** DIP01 nel runner usa lo StrategyWorker esistente (kind="single", name="DIP01"). Aggiorna `_STRAT_MODULE` in `runner.py` con `"DIP01": "DIP01_dip_buy"` e in `_defs.py` lo SleeveSpec DIP01_BTC resta kind="single". Il backtest dello sleeve DIP01_BTC continua a venire da `build_everything` (parità invariata).
---
## Task 2: `BasketTrendWorker` (TR01)
**Files:** Create `src/live/basket_trend_worker.py`; Test `tests/portfolio/test_basket_worker.py`.
- [ ] **Step 1: Test (fallisce)** — verifica che, dato un dict {asset: df 4h}, il worker calcoli posizione long/flat per asset secondo EMA20>EMA100 e aggiorni il capitale equal-weight:
```python
import numpy as np
import pandas as pd
from src.live.basket_trend_worker import BasketTrendWorker
def _ramp_df(n=300, slope=1.0):
c = np.linspace(100, 100 + slope * n, n)
ts = (pd.date_range("2024-01-01", periods=n, freq="4h", tz="UTC").astype("int64") // 10**6)
return pd.DataFrame({"timestamp": ts, "open": c, "high": c, "low": c, "close": c, "volume": 1.0})
def test_basket_goes_long_in_uptrend(tmp_path):
w = BasketTrendWorker(universe=["AAA", "BBB"], tf="4h", capital=1000.0, data_dir=tmp_path)
data = {"AAA": _ramp_df(slope=1.0), "BBB": _ramp_df(slope=1.0)}
w.tick(data)
assert w.positions["AAA"] == 1.0 and w.positions["BBB"] == 1.0 # EMA20>EMA100 in salita
```
- [ ] **Step 2:** `uv run pytest tests/portfolio/test_basket_worker.py -v` → FAIL.
- [ ] **Step 3: Implementa `src/live/basket_trend_worker.py`.** Stato: capitale totale + dict `positions` (asset→0/1) + persistenza. `tick(data: dict[str,df])`: per ogni asset calcola EMA20/EMA100 sull'ultima barra; target = 1.0 se ef>es else 0.0; applica fee `FEE_RT/2*LEV` sul turnover |Δpos|; aggiorna capitale equal-weight col rendimento di barra di ogni asset attivo (`POS*LEV*ret*pos/len(universe)`... mantieni la convenzione di `_tr_basket_daily`: ogni asset è uno sleeve normalizzato, equal-weight → applica `mean` dei rendimenti per-asset). Persisti `status.json` (capitale, positions, last_bar_ts per asset) e logga `trades.jsonl`. fee_rt=0.001, leverage 3, position 0.15.
```python
"""BasketTrendWorker (TR01): EMA20>EMA100 long/flat su un paniere, equal-weight.
Replica live di honest_improve2._tr_basket_daily."""
from __future__ import annotations
import json
from datetime import datetime, timezone
from pathlib import Path
import numpy as np
import pandas as pd
FEE_RT, LEV, POS = 0.001, 3.0, 0.15
def _ema(x, n):
return pd.Series(x).ewm(span=n, adjust=False).mean().values
class BasketTrendWorker:
def __init__(self, universe, tf="4h", capital=1000.0, position_size=POS,
leverage=LEV, fee_rt=FEE_RT, name="TR01_basket",
data_dir=Path("data/portfolio_paper")):
self.universe = list(universe)
self.tf = tf
self.initial_capital = capital
self.capital = capital
self.position_size = position_size
self.leverage = leverage
self.fee_rt = fee_rt
self.worker_id = f"{name}__{'-'.join(self.universe)}__{tf}"
self.work_dir = Path(data_dir) / self.worker_id
self.work_dir.mkdir(parents=True, exist_ok=True)
self.status_path = self.work_dir / "status.json"
self.trades_path = self.work_dir / "trades.jsonl"
self.positions = {a: 0.0 for a in self.universe}
self.last_bar_ts = {a: 0 for a in self.universe}
self.in_position = False # per il ribilancio del runner (skip se True)
self._load()
def _load(self):
if self.status_path.exists():
s = json.loads(self.status_path.read_text())
self.capital = s.get("capital", self.capital)
self.positions = {**self.positions, **s.get("positions", {})}
self.last_bar_ts = {**self.last_bar_ts, **s.get("last_bar_ts", {})}
self.in_position = any(v > 0 for v in self.positions.values())
def _save(self):
self.status_path.write_text(json.dumps({
"capital": round(self.capital, 2), "positions": self.positions,
"last_bar_ts": self.last_bar_ts,
"ts": datetime.now(timezone.utc).isoformat()}, indent=2))
def tick(self, data: dict):
rets = []
for a in self.universe:
df = data.get(a)
if df is None or len(df) < 110:
continue
c = df["close"].values
ef, es = _ema(c, 20)[-1], _ema(c, 100)[-1]
target = 1.0 if ef > es else 0.0
bar_ts = int(df["timestamp"].iloc[-1])
prev = self.positions[a]
# rendimento di barra realizzato sulla posizione precedente (chiusa->aperta barra)
if self.last_bar_ts[a] and bar_ts > self.last_bar_ts[a] and prev > 0:
r = (c[-1] - c[-2]) / c[-2]
rets.append(self.position_size * self.leverage * r * prev)
if target != prev:
self.capital -= self.capital * self.position_size * (self.fee_rt / 2) * abs(target - prev) / len(self.universe)
self._log(a, prev, target, float(c[-1]))
self.positions[a] = target
self.last_bar_ts[a] = bar_ts
if rets:
self.capital = max(self.capital * (1 + float(np.mean(rets))), 10.0)
self.in_position = any(v > 0 for v in self.positions.values())
self._save()
def _log(self, asset, frm, to, price):
with open(self.trades_path, "a") as f:
f.write(json.dumps({"ts": datetime.now(timezone.utc).isoformat(),
"asset": asset, "from": frm, "to": to,
"price": round(price, 6), "capital": round(self.capital, 2)}) + "\n")
@property
def status_summary(self):
longs = [a for a, v in self.positions.items() if v > 0]
return f"{self.worker_id}: cap={self.capital:.0f} long={longs}"
```
- [ ] **Step 4:** `uv run pytest tests/portfolio/test_basket_worker.py -v` → 1 passed.
- [ ] **Step 5: Commit**
```bash
git add src/live/basket_trend_worker.py tests/portfolio/test_basket_worker.py
git commit -m "feat(live): BasketTrendWorker (TR01) EMA-cross long/flat multi-asset"
```
---
## Task 3: `RotationWorker` (ROT02)
**Files:** Create `src/live/rotation_worker.py`; Test `tests/portfolio/test_rotation_worker.py`.
- [ ] **Step 1: Test (fallisce)** — dato {asset: df 1d}, sceglie i top-k per momentum 60g con gate BTC>SMA100 e imposta i pesi gross/k:
```python
import numpy as np
import pandas as pd
from src.live.rotation_worker import RotationWorker
def _df(n=200, slope=1.0):
c = np.linspace(100, 100 + slope * n, n)
ts = (pd.date_range("2023-01-01", periods=n, freq="1D", tz="UTC").astype("int64") // 10**6)
return pd.DataFrame({"timestamp": ts, "open": c, "high": c, "low": c, "close": c, "volume": 1.0})
def test_rotation_picks_top_momentum_when_risk_on(tmp_path):
w = RotationWorker(universe=["BTC", "AAA", "BBB"], top_k=2, gross=0.45, data_dir=tmp_path)
data = {"BTC": _df(slope=1.0), "AAA": _df(slope=3.0), "BBB": _df(slope=0.1)}
w.tick(data)
# BTC in uptrend -> risk_on; top-2 momentum = AAA e BTC; pesi gross/2
assert w.weights["AAA"] > 0 and abs(sum(w.weights.values()) - 0.45) < 1e-9
```
- [ ] **Step 2:** `uv run pytest tests/portfolio/test_rotation_worker.py -v` → FAIL.
- [ ] **Step 3: Implementa `src/live/rotation_worker.py`.** Replica di `_rot_daily_equity`: panel di close 1d allineato; `risk_on = BTC[-1] > SMA100(BTC)[-1]`; `mom = P[-1]/P[-61]-1`; `chosen = [top_k per mom con mom>0] se risk_on else []`; pesi `gross/len(chosen)`; turnover fee `FEE_RT/2 * Σ|Δw|`; capitale aggiornato col rendimento di portafoglio del giorno successivo (live: al tick si realizza il rendimento dell'ultima barra sui pesi correnti, poi si ricalcolano i pesi). Persisti capitale+weights+last_ts. `in_position = bool(weights)`.
(Implementazione analoga a BasketTrendWorker: stato persistente, `tick(data)` allinea i panel per timestamp comune, calcola momentum/gate, applica fee sul turnover e rendimento di barra. Mantieni `top_k=3, gross=0.45` come default — i valori dello sleeve ROT02_rot del portafoglio.)
- [ ] **Step 4:** test → 1 passed.
- [ ] **Step 5: Commit**
```bash
git add src/live/rotation_worker.py tests/portfolio/test_rotation_worker.py
git commit -m "feat(live): RotationWorker (ROT02) dual-momentum top-k risk-gated"
```
---
## Task 4: `TsmomWorker` (TSM01)
**Files:** Create `src/live/tsmom_worker.py`; Test `tests/portfolio/test_tsmom_worker.py`.
- [ ] **Step 1: Test (fallisce)** — consenso segni multi-orizzonte: sceglie gli asset con `Σ sign(P/P[-h]) ≥ thr` (h∈{63,126,252}) sotto gate, pesi gross/k.
- [ ] **Step 2-3: Implementa `src/live/tsmom_worker.py`** replicando `tsmom_sim`: `score[j] = mean_h sign(P[-1,j]/P[-1-h,j]-1)`; `chosen = [j: score>=thr] se risk_on`; pesi `gross/len(chosen)` con `gross=0.30`. Stessa struttura di RotationWorker (panel 1d, fee turnover, rendimento di barra, persistenza). Default `horizons=(63,126,252), thr=1.0, regime_n=100, gross=0.30`.
- [ ] **Step 4:** test → passed.
- [ ] **Step 5: Commit**
```bash
git add src/live/tsmom_worker.py tests/portfolio/test_tsmom_worker.py
git commit -m "feat(live): TsmomWorker (TSM01) consenso TSMOM multi-orizzonte risk-gated"
```
---
## Task 5: Integrazione nel PortfolioRunner
**Files:** Modify `src/portfolio/runner.py`, `scripts/portfolios/_defs.py`, `src/portfolio/base.py`; Test `tests/portfolio/test_runner_honest.py`.
- [ ] **Step 1:** In `_defs.py`, marca gli SleeveSpec multi-asset col `kind` giusto e l'universo:
- DIP01 → `kind="single", name="DIP01"` (resta StrategyWorker via _STRAT_MODULE["DIP01"]="DIP01_dip_buy").
- TR01 → `kind="basket"`, aggiungi campo universo (riusa `params={"universe": ["BNB","BTC","DOGE","SOL","XRP"], "tf": "4h"}`).
- ROT02 → `kind="rotation"`, `params={"top_k":3, "gross":0.45, "tf":"1d"}`.
- TSM01 → `kind="tsmom"`, `params={"horizons":[63,126,252], "thr":1.0, "gross":0.30, "tf":"1d"}`.
(Aggiungi `universe`/campi a SleeveSpec se serve, default None.)
- [ ] **Step 2:** In `runner.py::build_worker_for` aggiungi i rami `kind in ("basket","rotation","tsmom")` che costruiscono i rispettivi worker con `capital=alloc_capital` e `data_dir=DATA_DIR`. Aggiorna `_STRAT_MODULE` con `"DIP01": "DIP01_dip_buy"`. Rimuovi DIP01/TR01/ROT02/TSM01 dalla lista "saltati": ora sono supportati.
- [ ] **Step 3:** In `runner.run()` il tick deve passare ai worker multi-asset un dict {asset: df} (fetch di tutti gli asset dell'universo). Estendi la raccolta `keys` e il dispatch del tick: per kind basket/rotation/tsmom costruisci `data = {a: cache[(a, tf)] for a in universe}` e chiama `w.tick(data)`. Per `_worker_equity` i nuovi worker espongono `.capital` (già ok). Per il ribilancio, espongono `.in_position` (skip se True).
- [ ] **Step 4: Test** `tests/portfolio/test_runner_honest.py`: `build_worker_for` ritorna il tipo giusto per ogni kind con capitale = alloc; e `run()` con PORT06 non lascia più sleeve "saltati" (mocka il fetch o testa solo build).
- [ ] **Step 5:** `uv run pytest tests/portfolio/ -m "not network" -v` → tutti verdi.
- [ ] **Step 6: Commit**
```bash
git add src/portfolio/runner.py scripts/portfolios/_defs.py src/portfolio/base.py tests/portfolio/test_runner_honest.py
git commit -m "feat(portfolio): integra worker honest/TSM01 nel runner (PORT06 live completo)"
```
---
## Task 6: Validazione replay==backtest per i worker multi-asset
**Files:** Modify `scripts/analysis/validate_portfolio_runner.py` (o nuovo `validate_honest_workers.py`).
- [ ] **Step 1:** Per ogni worker multi-asset, replay bar-by-bar su dati storici (load_data) e confronto dell'equity finale con la funzione di riferimento (`_tr_basket_daily`, `_rot_daily_equity`, `tsmom_sim`) entro tolleranza. ROT02/TSM01 sono daily → replay veloce (poche migliaia di barre). TR01 4h → medio. Atteso: match stretto (differenze solo da bar-timing/cadenza). DIP01 ha il gap intrabar noto come le fade (documenta, non assert esatto).
- [ ] **Step 2: Commit**
```bash
git add scripts/analysis/validate_honest_workers.py
git commit -m "test(portfolio): replay worker honest/TSM01 == backtest di riferimento"
```
---
## Self-review
- **Copertura:** i 4 worker (DIP01 single via Strategy; TR01/ROT02/TSM01 dedicati) + integrazione runner + validazione → PORT06 gira live completo (niente più sleeve saltati).
- **Parità backtest:** invariata (gli sleeve del backtest vengono ancora da `build_everything`; i worker sono il path LIVE). La validazione replay==backtest (Task 6) certifica i worker live.
- **Gap noto:** DIP01, come le fade, ha exit intrabar nel backtest ma close-based nel live → gap strutturale documentato (non un bug). TR01/ROT02/TSM01 non hanno TP/SL intrabar (entry/exit a chiusura barra/giorno) → replay atteso stretto.
- **Tipi:** i nuovi worker espongono `.capital` e `.in_position` (richiesti da `_worker_equity`/`rebalance_allocations`); `tick(data: dict)` per i multi-asset vs `tick(df)`/`tick(dfa,dfb)` esistenti → il runner dispatcha per `kind`.
- **Rischio:** la convenzione di capitale/rendimento dei worker multi-asset deve combaciare con le funzioni di riferimento; la validazione Task 6 è il gate che lo verifica — se diverge, allineare la formula (non la reference).
> **Punto aperto:** verificare la disponibilità su Cerbero v2 dei timeframe 4h/1d per tutti gli asset dell'universo (TR01 usa 4h; ROT02/TSM01 usano 1d, oggi resample da 1h in get_df). Il runner live dovrà resamplare 1h→4h/1d dal feed v2 o fetchare nativamente — da decidere in Task 5/Step 3.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,233 @@
# Design — Cartella `portfolios/`: portafogli come oggetti di prima classe
**Data:** 2026-05-29
**Stato:** approvato in brainstorming, pronto per il piano di implementazione
**Branch:** `shape_patterns` (o branch dedicato `portfolios`)
## 1. Obiettivo e contesto
Oggi le strategie del progetto vivono come *sleeve* indipendenti: ogni worker del paper
trader (`StrategyWorker`, `PairsWorker`) gestisce un conto autonomo da €1000, con capitale
e stato propri in `data/paper_trades/{worker_id}/`. I "portafogli" `PORT01-03` esistenti
sono soltanto script di **report offline**: normalizzano le equity storiche dei singoli
sleeve e ne calcolano metriche equipesate. Non esiste un livello che gestisca davvero un
capitale condiviso, i pesi, il ribilanciamento e il PnL aggregato in tempo reale.
Questo design introduce una cartella `portfolios/` in cui il **portafoglio è un oggetto di
prima classe** che gestirà il trading e lo stato PnL. Un portafoglio possiede un capitale
totale, lo alloca ai propri sleeve secondo uno schema di pesi, dimensiona le posizioni,
ribilancia periodicamente e mantiene il ledger aggregato. La stessa definizione serve sia
al backtest sia al live, garantendo coerenza fra ciò che si misura e ciò che si tradia.
L'obiettivo strategico resta invariato: partire da €1000 e arrivare verso €50/giorno con un
paniere diversificato delle famiglie validate (fade, honest, pairs, TSMOM, shape-ML).
## 2. Decisioni di brainstorming
1. **Modello di capitale: pool condiviso.** Il portafoglio possiede il capitale totale, lo
alloca ai sleeve secondo i pesi, ridimensiona le posizioni e tiene lo stato/PnL
aggregato. I worker diventano esecutori.
2. **Scope: backtest + live unificati.** Un'unica classe `Portfolio` come fonte di verità,
capace sia di backtest/report storico sia di gestione live.
3. **Ribilanciamento periodico.** Il capitale viene riallocato ai pesi target a cadenza
fissa (giornaliera di default, configurabile), coerente con tutte le metriche misurate
finora.
4. **Schemi di peso supportati (tutti):** `equal` (default), `cap` (tetto per
famiglia/cluster, es. pairs 33% — configurazione sobria raccomandata), `inverse_vol`,
`cluster_rp` (equal fra cluster naturali poi inverse-vol dentro), `manual`.
5. **Scope live v1: tutti gli sleeve** — fade, honest, pairs (2 gambe) e shape-ML (SH01 via
worker con retraining periodico, sfruttando il `MLWorkerWrapper` esistente).
6. **Data layer Cerbero v2.** Il runner live adotta gli endpoint unificati v2: `get_historical`
unificato, `get_instruments` (naming robusto, niente `INSTRUMENT_MAP` hardcoded),
`get_ticker_batch` (fetch multi-gamba efficiente). Venue di trading = Deribit come ora.
### Analisi di accorpamento (a supporto delle decisioni)
`scripts/analysis/sleeve_clustering.py` ha mostrato che:
- i **cluster naturali** delle 17 sleeve non coincidono con le famiglie ma con
asset/regime: BTC-reversion, ETH-reversion, trend (TR01+TSM01), shape (SH_BTC+SH_ETH),
rotation (ROT02);
- la **ridondanza è lieve** (correlazione massima 0.43 MR01_BTC↔DIP01_BTC, 0.37 TR01↔TSM01):
nessuno sleeve è davvero fondibile, ognuno aggiunge diversificazione;
- a equal-weight i **pairs pesano il 47% del rischio** → giustifica lo schema `cap`;
- in OOS calmo equal-weight batte inverse-vol e risk-parity (i pairs ad alto rischio/ritorno
corrono liberi), ma è un risultato di regime → il cap resta la scelta prudente.
Il campo `cluster` di `SleeveSpec` codifica questi gruppi naturali per gli schemi `cap` e
`cluster_rp`.
## 3. Architettura e layout
Si rispecchia la struttura delle strategie (`src/strategies/` base + `scripts/strategies/`
concrete):
```
src/portfolio/
__init__.py
base.py # Portfolio (definizione + .backtest()), SleeveSpec, PortfolioResult
sleeves.py # costruzione UNIFICATA delle equity-per-sleeve (backtest);
# centralizza la logica oggi in combine_portfolio + report_families
weighting.py # schemi pesi: equal, cap, inverse_vol, cluster_rp, manual
ledger.py # PortfolioLedger: capitale, allocazioni, equity, PnL, peak/DD, persistenza
runner.py # PortfolioRunner (live): pool capital, sizing, ribilancio, aggregazione
scripts/portfolios/
PORT01_honest.py PORT02_fade.py PORT03_master.py
PORT04_master_pairs.py PORT05_master_esteso.py PORT06_master_shape.py
# definizioni concrete (lista SleeveSpec + schema pesi); run() = report backtest
portfolios.yml # config LIVE: portafoglio attivo, capitale, schema pesi, cap, cadenza, leva
```
**Integrazione col codice esistente:**
- Il backtest riusa i builder di equity-per-sleeve (`build_all_sleeves`, `pairs_sim`,
`shape_daily_equity`), centralizzati in `src/portfolio/sleeves.py`; `combine_portfolio.py`
e `report_families.py` diventano consumer sottili (niente duplicazione).
- Il live riusa da `multi_runner`: il fetch candele, `build_workers`,
`build_pairs_workers`, `MLWorkerWrapper`. `multi_runner` resta entrypoint legacy
single-sleeve finché `PortfolioRunner` non lo sostituisce.
- I vecchi `PORT01-03` di `scripts/strategies/` vengono migrati in `scripts/portfolios/`
come definizioni della nuova classe.
## 4. Definizione del portafoglio (schema)
```python
@dataclass
class SleeveSpec:
kind: str # "single" | "pairs" | "ml"
name: str # "MR01_bollinger_fade" | "PR01_pairs_reversion" | "SH01_shape_ml"
asset: str | None = None # single/ml
a: str | None = None # pairs: gamba long
b: str | None = None # pairs: gamba short
tf: str = "1h"
params: dict = field(default_factory=dict)
cluster: str = "" # BTC-rev | ETH-rev | trend | shape | rotation
@dataclass
class Portfolio:
code: str # "PORT06"
label: str # "Master + shape"
sleeves: list[SleeveSpec]
weighting: str = "equal" # equal | cap | inverse_vol | cluster_rp | manual
weights: dict | None = None # solo manual (sleeve-id -> peso)
caps: dict | None = None # solo cap: chiave = FAMIGLIA (derivata da kind/name:
# PAIRS/FADE/HONEST/SHAPE/TSM), es. {"PAIRS": 0.33}.
# cluster_rp usa invece il campo `cluster` degli sleeve.
total_capital: float = 1000.0
leverage: float = 3.0 # nota: 2x raccomandata per il live reale
rebalance: str = "1D"
vol_lookback: int = 90 # giorni per inverse_vol / cluster_rp
def backtest(self, ...) -> PortfolioResult: ...
def weight_vector(self, sleeve_returns) -> dict[str, float]: ...
```
Gli schemi di peso (in `weighting.py`) restituiscono un dict `sleeve-id -> peso` che somma a
1. `equal/cap/manual` sono statici; `inverse_vol/cluster_rp` si ricalcolano a ogni ribilancio
sulla finestra trailing `vol_lookback`, identicamente in backtest e live.
## 5. Faccia backtest
`Portfolio.backtest()` riusa la macchina che ha prodotto tutte le metriche viste finora,
centralizzata in `src/portfolio/sleeves.py`:
```
build_sleeve_equity(spec) -> pd.Series # equity daily normalizzata su IDX comune
kind="single" -> fade/honest daily equity builders
kind="pairs" -> pairs_sim -> daily
kind="ml" -> shape_daily_equity
```
Poi: `weight_vector()` → pesi → `port_returns()` con ribilancio giornaliero → `metrics()`
FULL/OOS + `yearly_returns()`. Restituisce un `PortfolioResult` con ret/CAGR/DD/Sharpe
(FULL e OOS), tabella per-anno e contributo al rischio per sleeve e per cluster. Lo `run()`
di ogni `scripts/portfolios/PORTxx.py` stampa questo report.
## 6. Faccia live (`PortfolioRunner`)
Loop a poll:
1. **Data layer v2.** All'avvio `get_instruments` risolve i nomi reali di ogni asset/coppia
(fallback a una mappa statica se l'endpoint non risponde). Per tick: `get_historical`
unificato per le candele + `get_ticker_batch` per i prezzi correnti di tutte le gambe in
un'unica chiamata.
2. **Costruzione sleeve→worker.** Riusa `build_workers` / `build_pairs_workers` /
`MLWorkerWrapper` (SH01). I worker sono esecutori, non possiedono più €1000 fissi.
3. **Capitale pool + sizing.** Il `PortfolioLedger` tiene `total_capital`. A ogni worker
viene assegnato `alloc_i = peso_i × total_capital`; il worker dimensiona il notional come
`alloc_i × position_size × leverage` (si riusa il campo `capital` del worker come base di
allocazione).
4. **Ribilancio (cadenza `rebalance`, default giornaliera).** `total_capital = Σ equity_sleeve`
(capitale + PnL realizzato); ricalcolo dei pesi (vol-based sulla finestra trailing o
statici); riallineo `alloc_i`.
5. **Aggregazione.** Dopo ogni tick il ledger aggiorna equity totale, peak, max_dd, PnL
aggregato e per-sleeve/cluster.
### Approssimazione dichiarata (limite noto)
Il ribilancio cambia la base di sizing delle posizioni **future**; le posizioni già aperte
restano sul notional con cui sono nate (nessun travaso forzato a metà trade). Per il paper
trading questo è fedele al backtest daily-rebalanced entro lo scarto dovuto al turnover
infragiornaliero. È un compromesso accettato per non introdurre la contabilità a ledger
unico (approccio C scartato in brainstorming), rimandata a quando si passerà a capitale
reale su un singolo conto-margine.
## 7. Persistenza e stato PnL
Stato del portafoglio separato dai singoli worker, in `data/portfolios/{code}/`:
```
data/portfolios/PORT06/
status.json # resume: total_capital, equity, peak, max_dd, pesi correnti,
# alloc+capitale+PnL per sleeve, ultimo ribilancio, ts
equity.jsonl # append-only: una riga per tick/giorno (ts, equity, dd, pnl_day) -> curva live
events.jsonl # append-only: ribilanci (pesi prima/dopo), milestone, errori
```
- I worker continuano a scrivere il proprio `trades.jsonl`/`status.json` in
`data/paper_trades/{worker_id}/` (storico per-sleeve intatto). Il portafoglio aggrega
sopra, non duplica i trade.
- **Resume:** al restart il runner ricarica lo `status.json` del portafoglio e gli stati
dei worker → riprende capitale, pesi e posizioni senza perdere storico.
- **Indicatori target:** il ledger espone `pnl_total`, `pnl_today`, `€/day` medio e DD
corrente.
- **Notifiche Telegram:** riepilogo a livello portafoglio (equity, PnL giorno, DD, ribilanci)
oltre alle notifiche per-trade dei worker.
## 8. Portafogli forniti e default
| Codice | Label | Sleeve | Pesi |
|--------|-------|--------|------|
| PORT01 | Honest | DIP01·TR01·ROT02 | equal |
| PORT02 | Fade master | MR01/02/07 × BTC/ETH (6) | equal |
| PORT03 | Master | fade+honest (9) | equal / manual 50-50 |
| PORT04 | Master + pairs | 9 + 5 pairs | equal · cap pairs 0.33 |
| PORT05 | Master esteso | 9 + pairs + TSM01 | equal · cap pairs |
| **PORT06** | **Master + shape** *(default)* | 9 + pairs + TSM01 + SH01 (BTC/ETH) | **cap pairs 0.33** |
**Default raccomandato:** PORT06 con `weighting="cap"` (pairs ~33%), `leverage=2` (sobrio),
`rebalance="1D"`. È la combinazione col miglior profilo OOS dell'analisi (Sharpe più alto,
DD più basso) e contiene tutte le famiglie validate. `portfolios.yml` seleziona il
portafoglio attivo e i suoi override.
## 9. Test
- **Unit** — `weighting.py` (somma pesi = 1, cap rispettato e ridistribuito,
inverse-vol/cluster corretti); `ledger.py` (capitale/PnL/DD, resume da status.json).
- **Parità backtest↔report** — `Portfolio.backtest()` di PORT03/04/05/06 riproduce
*esattamente* i numeri di `report_families.py` (regressione, stessa fonte).
- **Parità live↔backtest** — replay del `PortfolioRunner` su dati storici con ribilancio
giornaliero ≈ `Portfolio.backtest()` entro tolleranza (lo scarto è il turnover
infragiornaliero dichiarato), sullo stesso schema della validazione dei pairs.
- **Smoke live** — un tick reale end-to-end via Cerbero v2 (get_instruments +
get_historical + ticker_batch), nessun ordine reale, verifica ledger/persistenza/resume.
## 10. Fuori scope (note per il futuro)
- **Ledger unico / conto-margine reale** (approccio C): rinviato al passaggio a capitale
reale.
- **Hyperliquid come venue per gli alt** dei pairs (perp lineari nativi, evita i trap di
naming Deribit) — opzione abilitata dal data layer v2, non in v1.
- **Validazione pairs live via `get_cointegration_pairs`** e feature da macro/sentiment
(funding, liquidation, OI) per strategie future.
- **`run_backtest` server-side** di Cerbero come check incrociato.
+72
View File
@@ -0,0 +1,72 @@
# Config LIVE del trader a portafoglio. Seleziona UN portafoglio attivo
# (definito in scripts/portfolios/_defs.py) e ne fa l'override dei parametri operativi.
#
# ============ MICRO-TEST MAINNET — soldi VERI (Fase 1, 2026-06-17) ============
# Conversione da testnet a mainnet Deribit (vedi docs/specs/mainnet-microtest-plan.md).
# Capitale REALE €500 (periodo di prova; poi si scala col verdetto ledger-vs-backtest).
# Eseguono reale SOLO le 6 fade (MR01/MR02/MR07 x BTC/ETH, 15m) + DIP01 (BTC 1h);
# pairs/SH01/multi-asset -> PAPER (sola statistica, fuori dal pool/conto). Il token
# mainnet arriva da .env.mainnet (env_file del servizio). Ledger ripartito da ZERO:
# lo storico testnet e' archiviato in data/_reset_backup/pre_mainnet_20260617-205943.tgz.
active: PORT06
overrides:
# CAPITALE REALE del micro-test (era 2000 su testnet). €500 = minimo del piano
# (rumore arrotondamento BTC ~5-6%); si sale dopo il verdetto ledger-vs-backtest.
total_capital: 500
# equal-weight: nel pool REALE restano solo FADE (6) + DIP01 (1) -> 1/7 ciascuno.
# I cap PAIRS/SHAPE non servono piu' (quelle famiglie sono PAPER, fuori dal pool).
weighting: equal
# Leva 3x (scelta utente 2026-06-17 per il micro-test = config che vogliamo deployare,
# frontiera ACCEL50). NB su soldi veri al capitale minimo: DD pieno (alt prudente 2x).
leverage: 3
rebalance: 1D
poll_seconds: 60
# Gate feed CONGELATO: su mainnet l'arbitraggio tiene il feed vivo (niente freeze come
# sul testnet), lo lascio attivo come rete di sicurezza (non scattera' su BTC/ETH
# liquidi). 0 = disattivo.
feed_freeze_gate_bars: 24
# SLEEVE PAPER (fuori dal pool/pesi/ledger): i €500 si dividono SOLO tra i 7 sleeve
# REALI (6 fade + DIP01). Pairs (PR01, 2 gambe) e SH01 fuori dalla Fase 1: a €500 il
# rumore di arrotondamento li soffoca (pairs ~30%/gamba; servono ~€5-8k) e aggiungono
# superfici d'errore (leg-risk). Multi-asset (TR01/ROT02/TSM01/XS01) paper come sempre.
paper_sleeves: [TR01, ROT02, TSM01, XS01, PR01, SH01]
# Frazione di capitale-sleeve per posizione (0.5 con leva 3 = 1.5x la fetta impegnata).
position_size: 0.5
# Override per-famiglia: irrilevante per il conto reale (i pairs sono PAPER), tenuto
# solo perche' i worker pairs in sola-statistica dimensionino come da gate storico.
position_size_family: {PAIRS: 0.13}
# PAPER_EXTRA (2026-06-18): sleeve paper definiti SOLO qui (NON in _defs.py/PORT06) ->
# NON entrano nel backtest canonico/regression-lock. Shadow STAGE 1 del Price Ladder:
# GridWorker SIM-only su feed Deribit BTC 1h (NESSUN ordine reale; kind=grid non e' mai
# eseguito reale per costruzione). Config = re-gate su dati puliti (branch
# price_ladder_research): regime-gate range trend_max 1.5, rd0.20/ru0.06, 6 livelli,
# sl0.10/tp0.03. position_size 0.15 PINNATO (canonico validato; senza, erediterebbe il
# 0.5 globale del micro-test). Gira in data/portfolio_paper_stats/GRID_BTC/.
paper_extra:
- sid: GRID_BTC
kind: grid
name: GRID
asset: BTC
tf: "1h"
cluster: BTC-rev
params: {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, position_size: 0.15}
# Esecuzione REALE su Deribit MAINNET. Solo i 7 single-leg con TP/SL in metadata:
# 6 fade (MR01/MR02/MR07 x BTC/ETH 15m) + DIP01 (BTC 1h). Ordini sui LINEARI USDC
# (payoff lineare = matematica del backtest; fee/PnL in USDC).
execution:
enabled: true
# SOLO fade + DIP01 in Fase 1 (SH01 e pairs -> paper, vedi paper_sleeves).
sleeves: [MR01, MR02, MR07, DIP01]
instruments:
BTC: BTC_USDC-PERPETUAL
ETH: ETH_USDC-PERPETUAL
# niente esecuzione a 2 gambe in Fase 1 (pairs sono paper).
pairs_enabled: false
# Disaster-bracket on-book (~-30%) a ogni apertura: assicurazione per gli outage
# del runner. In operativita' normale non scatta mai -> 0 costo. 0 = disattivo.
disaster_sl_pct: 0.30
# REAL-TRUTH: equity/ribilanci/sizing derivano dai FILL REALI (fee reali incluse),
# non dal sim. Il sim resta solo diagnostica nel log CLOSE.
real_truth: true
+1
View File
@@ -27,3 +27,4 @@ dev = [
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
markers = ["network: test che richiede Cerbero MCP (rete+token)"]
View File
+142
View File
@@ -0,0 +1,142 @@
"""REGRESSION-LOCK COMUNE dei gate PORT06 live (exit16 / trendmax / dip01).
Queste funzioni erano copiate quasi-verbatim in exit16_port06_impact.py,
trendmax_port06_impact.py e dip01_exit16_impact.py. Sono il regression-lock
delle DECISIONI LIVE (EXIT-16, swap hurst->trend, DIP01 EXIT-16): la copy-drift
fra le copie avrebbe corrotto i verdetti, quindi vivono qui in un'unica copia.
NON cambiare la matematica: i gate devono restare riproducibili byte-a-byte.
Se un nuovo gate richiede un comportamento diverso, PARAMETRIZZARE (come fu
fatto per hurst_mask/trend_max), mai biforcare una copia.
Contenuto:
build_trades_variant : replay ESATTO di risk_management.build_trades sulle
fade (mode="orig" == canonico), con i rami varianti
EXIT-16 (mode="exit16"), filtro trend (trend_max) e
loss-guard Hurst (hurst_mask) parametrici.
equity_from_trades : trade -> equity giornaliera normalizzata su IDX
(stesso flusso di combine_portfolio.fade_daily_equity).
port_metrics : metriche FULL/OOS del portafoglio con la STESSA
matematica pesi di Portfolio.backtest (weight_vector
su tutti gli sleeve, ribilancio come port_returns).
dd : max drawdown % di una equity.
NB: l'engine DIP01 (dip_trades in dip01_exit16_impact.py) NON e' una copia di
build_trades_variant ma un sibling deliberatamente diverso (long-only, mode
"orig_gap" gap-aware, j clampato a n-1 a fine serie, niente filtri trend/hurst)
-> resta nel suo script.
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.strategy_research import atr
from scripts.analysis.risk_management import FEE_RT, LEV, POS, INIT
from scripts.analysis.combine_portfolio import (
_norm, IDX, port_returns, metrics, SPLIT,
)
from src.portfolio import weighting as W
BUFFER = 0.5 # EXIT-16 close-confirm (come in produzione)
EMA_LONG = 200
def build_trades_variant(ents, df, mode, trend_max, hurst_mask=None,
buffer=BUFFER, lev=LEV, fee_rt=FEE_RT, ema_long=EMA_LONG):
"""Replica ESATTA di risk_management.build_trades, con i rami varianti.
mode="orig" : SL intrabar al livello (SL prima del TP) == canonico.
mode="exit16" : SL intrabar OFF; TP intrabar al livello (priorita' nel bar);
SL solo se il CLOSE sfonda sl0 -/+ buffer*ATR14[j], fill a close[j].
trend_max : None = filtro OFF; 3.0 = config live.
hurst_mask : bool[i]=True -> salta l'ingresso (loss-guard storico).
"""
h, l, c = df["high"].values, df["low"].values, df["close"].values
n = len(c)
a = atr(df, 14)
el = pd.Series(c).ewm(span=ema_long, adjust=False).mean().values
fee = fee_rt * lev
out = []
last = -1
for e in ents:
i, d = e["i"], e["d"]
if i <= last or i + 1 >= n:
continue
if hurst_mask is not None and hurst_mask[i]:
continue
if trend_max is not None and a[i] and abs(c[i] - el[i]) / a[i] > trend_max:
continue
entry = c[i]
tp, sl0, mb = e["tp"], e["sl"], e["max_bars"]
exit_p = c[min(i + mb, n - 1)]
j = min(i + mb, n - 1)
for k in range(1, mb + 1):
j = i + k
if j >= n:
exit_p = c[n - 1]
break
if mode == "orig":
hs = (d == 1 and l[j] <= sl0) or (d == -1 and h[j] >= sl0)
ht = (d == 1 and h[j] >= tp) or (d == -1 and l[j] <= tp)
if hs:
exit_p = sl0
break
if ht:
exit_p = tp
break
if k == mb:
exit_p = c[j]
else: # exit16
ht = (d == 1 and h[j] >= tp) or (d == -1 and l[j] <= tp)
if ht:
exit_p = tp
break
aj = a[j] if np.isfinite(a[j]) else 0.0
confirm = (d == 1 and c[j] < sl0 - buffer * aj) or \
(d == -1 and c[j] > sl0 + buffer * aj)
if confirm:
exit_p = c[j]
break
if k == mb:
exit_p = c[j]
ret = (exit_p - entry) / entry * d * lev - fee
out.append((i, j, ret))
last = j
return out
def equity_from_trades(df, trades):
"""Trade -> equity giornaliera su IDX (flusso di combine_portfolio.fade_daily_equity)."""
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
n = len(df)
eq = np.full(n, INIT, dtype=float)
cap = INIT
for i, j, ret in sorted(trades, key=lambda t: t[1]):
cap = max(cap + cap * POS * ret, 10.0)
eq[j:] = cap
s = pd.Series(eq, index=ts).resample("1D").last().reindex(IDX).ffill().bfill()
return _norm(s)
def port_metrics(members: dict[str, pd.Series], p):
"""Metriche (FULL, OOS) del portafoglio p con la STESSA matematica pesi cap
di Portfolio.backtest."""
ids = p.sleeve_ids
dr = pd.DataFrame({i: members[i].pct_change().fillna(0.0) for i in ids})
w = W.weight_vector(p.weighting, ids, dr, weights=p.weights,
caps=p.caps, clusters=p.clusters, lookback=p.vol_lookback)
drp = port_returns({i: members[i] for i in ids}, w)
return metrics(drp), metrics(drp, lo=SPLIT)
def dd(s):
"""Max drawdown % di una serie equity."""
pk = s.cummax()
return float(((pk - s) / pk).max() * 100)
+117
View File
@@ -0,0 +1,117 @@
"""ACCEL50 — Ricerca acceleratori verso l'obiettivo €50/giorno (2026-06-12).
Domanda: quali strategie/leve portano PIU' VELOCEMENTE a €50/g partendo da ~€2k?
Diario: docs/diary/2026-06-12-accel50.md. Esiti:
1. LEVA su PORT06 (acceleratore dominante, zero ricerca nuova).
La frontiera (scala lineare dei daily return canonici, fee pro-quota) mostra
che a Sharpe ~7-10 il vincolo non e' il rischio ma la taglia: lev 2->4 porta
gli anni-a-target da 3.3 a 1.2 con FULL DD 3.5->6.9%. Vedi lev_frontier().
2. FADE 15m (candidata NUOVA, validazione preliminare PASSATA).
MR01/MR02/MR07 a 15m con i parametri live 1h (trend_max=3, ema_long=200,
sl_confirm_atr=0.5, fee 0.10% RT): tutti e 6 gli sleeve positivi, OOS
2025-26 positivo ovunque (spesso > del 1h: 4x trade = compounding piu'
rapido), reggono fee 2x (Sh 1.6-2.9). BTC 15m MIGLIORA il 1h (MR01 Sh
3.37 vs 2.76 con meta' DD). Prossimo passo obbligato: gate PORT06
(correlazione col gemello 1h, parita' worker — infra 15m gia' esistente
dal BLEND pairs). Vedi fade15m_probe().
3. PAIRS NUOVE: BOCCIATE (stale-print illusion).
Lo sweep delle 19 coppie mai testate (config universale pre-registrata)
dava 8 candidate con Sharpe 1.5-4.3, MA le gambe alt hanno 88-98% di barre
flat (LTC 97%, ADA 98%, DOGE 91%, XRP 88%, BNB 88%) e con flat_skip=True
(fill solo su barre pulite) muoiono quasi tutte (BTC/ADA 4.33->0.17,
ETH/DOGE 3.79->0.46). Migliore superstite ETH/XRP a 1.34: inferiore alle
5 deployate -> niente. Stessa classe di illusione del XEX su DOGE/SOL
(vedi xex_divergence_research.py). PAXG idem: 92% flat su Deribit.
4. CAPITALE: a config attuale servono ~€24k per €50/g; ogni € aggiunto
accorcia linearmente (non e' una strategia ma domina ogni altra leva).
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
def lev_frontier() -> None:
"""Frontiera di leva su PORT06: CAGR/DD/Sharpe e anni-a-€50/g per lev 1-6.
Modello: scala lineare dei daily return del backtest canonico (strumenti
lineari, fee proporzionali al notional). NON modella margine/code grasse."""
from scripts.portfolios._defs import PORTFOLIOS
from scripts.analysis.combine_portfolio import port_returns, SPLIT
from src.portfolio.sleeves import all_sleeve_equities, sleeve_returns_df
p = PORTFOLIOS["PORT06"]
eq = all_sleeve_equities()
members = {sid: eq[sid] for sid in p.sleeve_ids}
w = p.weight_vector(sleeve_returns_df(p.sleeve_ids))
base = port_returns(members, w) # == live a lev 2 (parita' validata)
def dd(x):
c = (1 + x).cumprod()
return ((c - c.cummax()) / c.cummax()).min() * 100
def cagr(x):
c = (1 + x).cumprod()
return ((c.iloc[-1]) ** (365 / len(x)) - 1) * 100
print("lev CAGR_full% DD_full% CAGR_oos% DD_oos% K_per_50/g anni_da_2k")
for f, lev in [(0.5, 1), (1.0, 2), (1.5, 3), (2.0, 4), (2.5, 5), (3.0, 6)]:
r = base * f
roos = r.iloc[SPLIT:]
co = cagr(roos)
daily = (1 + co / 100) ** (1 / 365) - 1
k = 50 / daily if daily > 0 else float("inf")
anni = np.log(k / 2020) / np.log(1 + co / 100) if co > 0 else float("inf")
print(f"{lev:>3} {cagr(r):>11.0f} {dd(r):>9.2f} {co:>10.0f} {dd(roos):>8.2f} "
f"{k:>11,.0f} {max(anni, 0):>11.1f}")
def fade15m_probe() -> None:
"""MR01/02/07 a 15m vs 1h, parametri live, fee 0.10% e stress 2x."""
import importlib.util
import inspect
from src.strategies.base import Strategy
LIVEP = dict(trend_max=3.0, ema_long=200, sl_confirm_atr=0.5)
paths = {
"MR01": "scripts/strategies/MR01_bollinger_fade.py",
"MR02": "scripts/strategies/MR02_donchian_fade.py",
"MR07": "scripts/strategies/MR07_return_reversal.py",
}
for code, rel in paths.items():
spec = importlib.util.spec_from_file_location(code.lower(), PROJECT_ROOT / rel)
m = importlib.util.module_from_spec(spec)
spec.loader.exec_module(m)
cls = next(o for _, o in vars(m).items()
if inspect.isclass(o) and issubclass(o, Strategy) and o.__module__ == m.__name__)
s = cls()
for asset in ("BTC", "ETH"):
line = f"{code} {asset}: "
for tf in ("1h", "15m"):
r = s.backtest(asset, tf, **LIVEP)
if r is None:
line += f"{tf}: no-sig | "
continue
oos = sum(y.pnl for y in r.yearly if y.year >= 2025)
old = s.fee_rt
s.fee_rt = 0.002
r2 = s.backtest(asset, tf, **LIVEP)
s.fee_rt = old
line += (f"{tf}: Sh{r.sharpe:5.2f} DD{r.max_dd:5.1f}% n={r.trades:4d} "
f"oos25-26={oos:+8.0f} fee2x_Sh{r2.sharpe:5.2f} | ")
print(line)
if __name__ == "__main__":
print("=== 1. Frontiera di leva PORT06 ===")
lev_frontier()
print("\n=== 2. Fade 15m probe ===")
fade15m_probe()
-309
View File
@@ -1,309 +0,0 @@
"""Confronto migliori strategie S1 e S2 — andamento per anno."""
from __future__ import annotations
import sys
sys.path.insert(0, ".")
import numpy as np
import pandas as pd
from src.data.downloader import load_data
from src.fractal.patterns import encode_candles
FEE_PERP = 0.002 # 0.1% taker roundtrip perpetual
FEE_OPT = 0.0052 # options roundtrip
INITIAL = 1000
LEVERAGE = 3
def keltner_ratio(close, high, low, window=14):
n = len(close)
r = np.full(n, np.nan)
for i in range(window, n):
wc, wh, wl = close[i-window:i], high[i-window:i], low[i-window:i]
ma = np.mean(wc)
bb_std = np.std(wc)
tr = np.maximum(wh-wl, np.maximum(np.abs(wh-np.roll(wc,1)), np.abs(wl-np.roll(wc,1))))
atr = np.mean(tr[1:])
kc = (ma+1.5*atr)-(ma-1.5*atr)
bb = (ma+2*bb_std)-(ma-2*bb_std)
if kc > 0:
r[i] = bb/kc
return r
def rv_ann(close, window):
lr = np.diff(np.log(np.where(close==0, 1e-10, close)))
r = np.full(len(close), np.nan)
for i in range(window, len(lr)):
r[i+1] = np.std(lr[i-window:i]) * np.sqrt(24*365)
return r
def rsi(close, period=14):
delta = np.diff(close)
gain = np.where(delta>0, delta, 0)
loss = np.where(delta<0, -delta, 0)
result = np.full(len(close), 50.0)
if len(gain) < period:
return result
ag = np.mean(gain[:period])
al = np.mean(loss[:period])
for i in range(period, len(delta)):
ag = (ag*(period-1)+gain[i])/period
al = (al*(period-1)+loss[i])/period
result[i+1] = 100 if al == 0 else 100-100/(1+ag/al)
return result
def ema(arr, period):
r = np.full(len(arr), np.nan)
k = 2/(period+1)
r[period-1] = np.mean(arr[:period])
for i in range(period, len(arr)):
r[i] = arr[i]*k + r[i-1]*(1-k)
return r
# =====================================================================
# S1 BEST: Squeeze Breakout ETH 1h (BBw=14, sq=0.8, brk=3)
# =====================================================================
def run_s1_squeeze(asset, tf):
df = load_data(asset, tf)
c, h, l, v = df["close"].values, df["high"].values, df["low"].values, df["volume"].values
n = len(c)
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
kcr = keltner_ratio(c, h, l, 14)
yearly = {}
in_sq = False
sq_start = 0
for i in range(15, n):
if np.isnan(kcr[i]):
continue
is_sq = kcr[i] < 0.8
if is_sq and not in_sq:
in_sq = True
sq_start = i
elif not is_sq and in_sq:
in_sq = False
if i - sq_start < 5 or i + 3 >= n:
continue
first_ret = (c[i] - c[i-1]) / c[i-1]
if abs(first_ret) < 0.001:
continue
direction = 1 if first_ret > 0 else -1
actual = (c[i+2] - c[i-1]) / c[i-1]
trade_ret = actual * direction
net = trade_ret * LEVERAGE - FEE_PERP * LEVERAGE
year = ts.iloc[i].year
if year not in yearly:
yearly[year] = {"pnls": [], "wins": 0, "total": 0}
yearly[year]["pnls"].append(net)
yearly[year]["total"] += 1
if trade_ret > 0:
yearly[year]["wins"] += 1
return yearly
# =====================================================================
# S1 BEST ALT: Squeeze+ML hybrid ETH 15m
# =====================================================================
# Troppo complesso da ricalcolare (serve ML training). Uso i dati S1 squeeze puro.
# =====================================================================
# S2 BEST: VRP ETH 48h (con IV stimata, unico disponibile su 8 anni)
# =====================================================================
def run_s2_vrp(asset, dte=48):
df = load_data(asset, "1h")
c = df["close"].values
n = len(c)
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
rv_24 = rv_ann(c, 24)
rv_168 = rv_ann(c, 168)
yearly = {}
for i in range(170, n - dte):
if ts.iloc[i].hour != 8:
continue
rv_s, rv_l = rv_24[i], rv_168[i]
if np.isnan(rv_s) or np.isnan(rv_l) or rv_s < 0.05 or rv_l < 0.05:
continue
regime = rv_s / rv_l
iv_pf = 0.9 if regime > 2 else (1.0 if regime > 1.5 else (1.1 if regime > 1 else 1.2))
iv = rv_l * iv_pf
prem = iv * np.sqrt(dte/(24*365)) * 0.8
spot = c[i]
move = abs(c[min(i+dte, n-1)] - spot) / spot
pos = 0.10
raw = (prem - move) * pos if move <= prem else max(-(move-prem)*pos, -pos*0.05)
net = raw - FEE_OPT * pos
year = ts.iloc[i].year
if year not in yearly:
yearly[year] = {"pnls": [], "wins": 0, "total": 0}
yearly[year]["pnls"].append(net)
yearly[year]["total"] += 1
if raw > 0:
yearly[year]["wins"] += 1
return yearly
# =====================================================================
# S2 BEST PERPETUAL: Multi-TF 15m+1h BTC
# =====================================================================
def run_s2_multitf(asset):
df_1h = load_data(asset, "1h")
df_15m = load_data(asset, "15m")
c1h = df_1h["close"].values
ts1h = pd.to_datetime(df_1h["timestamp"], unit="ms", utc=True)
c15 = df_15m["close"].values
ts15 = df_15m["timestamp"].values
n15 = len(c15)
ema_50 = ema(c1h, 50)
rsi_15m = rsi(c15, 14)
yearly = {}
daily_done = set()
for i in range(100, n15 - 12):
ts_dt = pd.Timestamp(ts15[i], unit="ms", tz="UTC")
day = ts_dt.strftime("%Y-%m-%d")
if day in daily_done:
continue
if rsi_15m[i] > 35 and rsi_15m[i] < 65:
continue
h_idx = np.searchsorted(ts1h.values.astype("int64"), ts15[i]) - 1
if h_idx < 50 or h_idx >= len(c1h) or np.isnan(ema_50[h_idx]):
continue
direction = None
if rsi_15m[i] < 30 and c1h[h_idx] > ema_50[h_idx]:
direction = "long"
elif rsi_15m[i] > 70 and c1h[h_idx] < ema_50[h_idx]:
direction = "short"
if direction is None:
continue
entry = c15[i]
exit_price = c15[min(i+12, n15-1)]
trade_ret = (exit_price-entry)/entry if direction == "long" else (entry-exit_price)/entry
net = trade_ret * LEVERAGE - FEE_PERP * LEVERAGE
year = ts_dt.year
if year not in yearly:
yearly[year] = {"pnls": [], "wins": 0, "total": 0}
yearly[year]["pnls"].append(net)
yearly[year]["total"] += 1
if trade_ret > 0:
yearly[year]["wins"] += 1
daily_done.add(day)
return yearly
# =====================================================================
# REPORT
# =====================================================================
strategies = {
"S1: Squeeze BTC 1h": run_s1_squeeze("BTC", "1h"),
"S1: Squeeze ETH 1h": run_s1_squeeze("ETH", "1h"),
"S1: Squeeze ETH 15m": run_s1_squeeze("ETH", "15m"),
"S2: VRP ETH 48h (IV est)": run_s2_vrp("ETH", 48),
"S2: VRP BTC 48h (IV est)": run_s2_vrp("BTC", 48),
"S2: MultiTF BTC 15m+1h": run_s2_multitf("BTC"),
"S2: MultiTF ETH 15m+1h": run_s2_multitf("ETH"),
}
all_years = sorted(set(y for v in strategies.values() for y in v))
print("=" * 120)
print(" MIGLIORI STRATEGIE — ANDAMENTO PER ANNO")
print(" Fee reali. PnL su €1000 flat (no compounding). Dati OHLCV reali 2018-2026.")
print(" ⚠ VRP usa IV STIMATA (non reale) — fidarsi solo dei dati perpetual per backtest lungo")
print("=" * 120)
# Header
hdr = f" {'Anno':>6s}"
for name in strategies:
short = name.split(": ")[1][:18]
hdr += f" | {short:>18s}"
print(hdr)
print(f" {'-' * (len(hdr) - 2)}")
# Per anno: accuracy / PnL totale
for year in all_years:
row_acc = f" {year:>6d}"
row_pnl = f" {'':>6s}"
for name, yearly in strategies.items():
if year in yearly:
d = yearly[year]
acc = d["wins"]/d["total"]*100 if d["total"] > 0 else 0
pnl = sum(d["pnls"]) * INITIAL
tag = "" if acc >= 75 else "" if acc >= 65 else "" if acc >= 55 else " "
row_acc += f" | {acc:>5.1f}% {tag} {d['total']:>3d}t"
row_pnl += f" | €{pnl:>+8.0f} "
else:
row_acc += f" | {'':>18s}"
row_pnl += f" | {'':>18s}"
print(row_acc)
print(row_pnl)
# Totali
print(f" {'-' * (len(hdr) - 2)}")
row_tot = f" {'TOT':>6s}"
for name, yearly in strategies.items():
all_pnls = [p for d in yearly.values() for p in d["pnls"]]
all_wins = sum(d["wins"] for d in yearly.values())
all_total = sum(d["total"] for d in yearly.values())
acc = all_wins/all_total*100 if all_total > 0 else 0
pnl = sum(all_pnls) * INITIAL
row_tot += f" | {acc:>5.1f}% {all_total:>4d}t"
print(row_tot)
row_pnl_tot = f" {'€TOT':>6s}"
for name, yearly in strategies.items():
all_pnls = [p for d in yearly.values() for p in d["pnls"]]
pnl = sum(all_pnls) * INITIAL
row_pnl_tot += f" | €{pnl:>+8.0f} "
print(row_pnl_tot)
# Compounding
print(f"\n {'':>6s}", end="")
for name in strategies:
short = name.split(": ")[1][:18]
print(f" | {short:>18s}", end="")
print()
row_comp = f" {'COMP':>6s}"
for name, yearly in strategies.items():
cap = float(INITIAL)
for year in sorted(yearly):
for pnl in yearly[year]["pnls"]:
cap += cap * pnl
cap = max(cap, 10)
row_comp += f" | €{cap:>12,.0f} "
print(row_comp)
# Drawdown
row_dd = f" {'MAXDD':>6s}"
for name, yearly in strategies.items():
cap = float(INITIAL)
peak = cap
mdd = 0
for year in sorted(yearly):
for pnl in yearly[year]["pnls"]:
cap += cap * pnl
cap = max(cap, 10)
if cap > peak: peak = cap
dd = (peak - cap) / peak
mdd = max(mdd, dd)
row_dd += f" | {mdd*100:>12.1f}% "
print(row_dd)
# Legenda
print(f"\n Legenda: ▓ ≥75% acc ▒ ≥65% acc ░ ≥55% acc")
print(f" ⚠ S2 VRP: IV stimata (rv_long × 1.0-1.2), NON dati reali opzioni")
print(f" S1 Squeeze e S2 MultiTF: dati OHLCV reali al 100%")
@@ -0,0 +1,144 @@
"""Validazione dell'edge del credit-spread di cerbero-bite sui PREZZI REALI.
cerbero-bite (container accanto) vende credit spread su ETH (bull-put primario,
short delta ~0.18, DTE 18, PT 50% / stop 2.5x credito / delta-breach 0.30 / vol-stop
+10 DVOL / time-stop 7 DTE). Domanda: l'edge regge su un CICLO ETH completo, o e'
profittevole solo nei campioni calmi?
Tre analisi (riprendibili):
1) entry_economics() -> economia d'ingresso REALE dalla chain (data/options/eth_chain.parquet):
credit/width effettivo a delta 0.18 dai bid/ask veri, eleggibilita' sotto i gate liquidita'.
2) tail_model_free() -> esito terminale dai prezzi ETH reali (2018-2026), cw reale 0.106,
NESSUN modello opzioni (niente errore BS): win-rate, EV, frequenza max-loss.
3) managed_backtest() -> lifecycle CON management; mark con skew calibrato sulle IV reali.
ESITO (2026-06-09):
- cw reale a delta 0.18 = 0.106 (short ~9.4% OTM, NON 18%), max-loss/credito = 8.4x, eleggibilita' 65%.
- hold-to-expiry @0.106: EV -1.0 crediti/trade, 7/9 anni NEGATIVI, max-loss 17.8% delle volte.
- managed (skew): EV -0.02 cr/trade, win-rate 37% (delta-breach esce sul 62% dei trade a piccola perdita).
- VERDETTO: NON edge robusto su ciclo completo. Il "+0.48%/mese" era artefatto di finestra calma
(mag-giu 2026, no crash). Premium-selling a skew negativo: vince nei campioni calmi, restituisce
tutto (o piu') nei crash. Tune "Profilo B" (vendere a 9.4% OTM) PEGGIORA la frequenza di max-loss.
Coda CONCENTRATA col fade ETH di PythagorasGoal (stesso crash colpisce entrambi).
TODO APERTO (per nail-are l'EV managed esatto): la calibrazione non e' ancora perfetta
(mark mid+skew da cw 0.228 vs 0.106 reale -> sovrastima il credito ~2x). Manca: modellare
bid/ask reale incrociato sulle 2 gambe + griglia strike reale (entrambi nella chain) cosi'
l'entry cw scende a 0.106 e l'EV managed diventa esatto. Allora chiudere il sì/no definitivo.
uv run python scripts/analysis/cerbero_bite_credit_spread.py
"""
from __future__ import annotations
import sys, math, collections
from pathlib import Path
import numpy as np, pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.options_chain import OptionChain, load_market
from scripts.analysis.explore_lab import get_df
from scripts.analysis.option_overlay_lab import bs_put, _ncdf, dvol_for
SHORT_OTM, LONG_OTM, DTE = 0.094, 0.134, 17 # da chain reale (delta 0.18, width 4%)
CW_REAL = 0.106
def entry_economics():
oc = OptionChain("ETH"); ch = oc.df
mk = load_market("ETH")[["ts_ms", "spot"]].dropna().sort_values("ts_ms")
p = ch[ch["option_type"] == "P"].copy()
p = pd.merge_asof(p.sort_values("ts_ms"), mk, on="ts_ms", direction="backward")
cand = p[(p["tenor_d"] >= 14) & (p["tenor_d"] <= 21)].dropna(subset=["delta", "bid", "ask", "strike", "spot"])
rows = []
for (ts, exp), g in cand.groupby(["timestamp", "expiry"]):
spot = g["spot"].iloc[0]
sc = g[(g["delta"] <= -0.12) & (g["delta"] >= -0.22)]
if sc.empty: continue
short = sc.iloc[(sc["delta"] + 0.18).abs().argmin()]
Ks = short["strike"]; longc = g[g["strike"] < Ks]
if longc.empty: continue
longp = longc.iloc[(longc["strike"] - (Ks - spot * 0.04)).abs().argmin()]
W = Ks - longp["strike"]
if W <= 0: continue
credit = short["bid"] - longp["ask"]
def ok(o):
sp = (o["ask"] - o["bid"]) / ((o["ask"] + o["bid"]) / 2) if (o["ask"] + o["bid"]) > 0 else 9
return (o["open_interest"] or 0) >= 100 and sp <= 0.15 and o["bid"] > 0
cw = credit / (W / spot)
rows.append(dict(cw=cw, credit=credit, elig=ok(short) and ok(longp) and cw >= 0.08 and credit > 0,
short_otm=(spot - Ks) / spot, delta=short["delta"]))
r = pd.DataFrame(rows)
print(f"[ENTRY] {len(r)} spread | eleggibili {r['elig'].mean()*100:.0f}% | cw mediano {r['cw'].median():.3f} "
f"| short OTM {r['short_otm'].median()*100:.1f}% | max-loss/credito {((1-r['cw'].median())/r['cw'].median()):.1f}x")
def tail_model_free():
df = get_df("ETH", "1h"); c = df["close"].values; n = len(c)
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True); H = DTE * 24
res = []
for i in range(200, n - H - 1, 24 * 2):
S0 = c[i]; Ks = S0 * (1 - SHORT_OTM); Kl = S0 * (1 - LONG_OTM); W = Ks - Kl
Sx = c[i + H]; intr = min(max(Ks - Sx, 0.0), W); credit = CW_REAL * W
res.append((ts.iloc[i].year, 1 - intr / credit, Sx < Kl))
R = pd.DataFrame(res, columns=["y", "pnl", "maxloss"]); P = R["pnl"].values
print(f"[TAIL model-free @cw0.106] win {(P>0).mean()*100:.0f}% | EV {P.mean():+.2f}cr | max-loss {R['maxloss'].mean()*100:.0f}% "
f"| anni neg {(R.groupby('y')['pnl'].mean()<0).sum()}/{R['y'].nunique()}")
def _skew_fit():
oc = OptionChain("ETH"); ch = oc.df
mk = load_market("ETH")[["ts_ms", "spot"]].dropna().sort_values("ts_ms")
p = ch[ch["option_type"] == "P"].copy()
p = pd.merge_asof(p.sort_values("ts_ms"), mk, on="ts_ms", direction="backward")
p = p.dropna(subset=["iv", "strike", "spot", "delta", "tenor_d"])
p = p[(p["tenor_d"] >= 7) & (p["tenor_d"] <= 35) & (p["iv"] > 0)]
p["dd"] = (p["delta"] + 0.5).abs()
atm = p.sort_values("dd").groupby("timestamp")["iv"].first()
p["atm_iv"] = p["timestamp"].map(atm); p = p.dropna(subset=["atm_iv"])
p["k"] = np.log(p["strike"] / p["spot"]); p["ratio"] = p["iv"] / p["atm_iv"]
p = p[(p["k"] > -0.35) & (p["k"] < 0.15) & (p["ratio"] > 0.5) & (p["ratio"] < 3)]
coef, *_ = np.linalg.lstsq(np.c_[p["k"], p["k"]**2], p["ratio"] - 1.0, rcond=None)
return coef # a, b
def managed_backtest():
a, b = _skew_fit()
def ivol(S, K, atm):
k = math.log(K / S); return max(atm * (1 + a * k + b * k * k), 0.05)
def put_delta(S, K, T, sig):
if T <= 0 or sig <= 0: return -1.0 if S < K else 0.0
return _ncdf((math.log(S / K) + 0.5 * sig * sig * T) / (sig * math.sqrt(T))) - 1.0
def mark(S, Ks, Kl, T, atm):
return bs_put(S, Ks, T, ivol(S, Ks, atm)) - bs_put(S, Kl, T, ivol(S, Kl, atm))
df = get_df("ETH", "1h"); c = df["close"].values; n = len(c)
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True); dvol = dvol_for(df, "ETH")
H = DTE * 24; STEP = 6; cw = []; tr = []
for i in range(200, n - H - 1, 24 * 2):
S0 = c[i]; atm0 = dvol[i] if not np.isnan(dvol[i]) else 0.6
Ks = S0 * (1 - SHORT_OTM); Kl = S0 * (1 - LONG_OTM); W = Ks - Kl
credit = mark(S0, Ks, Kl, DTE / 365.0, atm0)
if credit <= 0: continue
cw.append(credit / W); pnl = why = None
for k in range(STEP, H + 1, STEP):
j = i + k; Trem = max((H - k) / (24 * 365.0), 1e-6); Sj = c[j]
atmj = dvol[j] if not np.isnan(dvol[j]) else atm0; mk = mark(Sj, Ks, Kl, Trem, atmj)
if mk <= 0.5 * credit: pnl, why = 1 - mk / credit, "PT"; break
if mk >= 2.5 * credit: pnl, why = 1 - mk / credit, "stop"; break
if put_delta(Sj, Ks, Trem, ivol(Sj, Ks, atmj)) <= -0.30: pnl, why = 1 - mk / credit, "delta"; break
if atmj - atm0 >= 0.10: pnl, why = 1 - mk / credit, "vol"; break
if k >= (DTE - 7) * 24: pnl, why = 1 - mk / credit, "time"; break
if pnl is None:
Sx = c[i + H]; intr = min(max(Ks - Sx, 0), W); pnl, why = 1 - intr / credit, "expiry"
tr.append((ts.iloc[i].year, pnl, why))
P = np.array([t[1] for t in tr])
print(f"[MANAGED skew] cw@entry {np.median(cw):.3f} (vs 0.106 reale: sovrastima ~2x, EV vero <=) | "
f"win {(P>0).mean()*100:.0f}% | EV {P.mean():+.3f}cr | worst {P.min():.1f} | "
f"uscite {dict(collections.Counter(t[2] for t in tr))}")
R = pd.DataFrame({"y": [t[0] for t in tr], "p": P})
print(f" 2021+: EV {R[R.y>=2021]['p'].mean():+.3f}cr/trade")
if __name__ == "__main__":
entry_economics()
tail_model_free()
managed_backtest()
+140
View File
@@ -0,0 +1,140 @@
"""CLEAN FEED — ripara gli spike-print del feed Deribit/Cerbero coi dati reali di Binance.
Motivo (2026-06-18): la ricerca Price Ladder ha rivelato che data/raw/btc_1h.parquet (e gli
altri TF/asset) contengono barre con WICK FASULLI (es. BTC 2024-02-13: low 38.580 con
close ~49.968, BTC reale ~50k) — lo stesso spike-print testnet documentato in CLAUDE.md
(TP_PHANTOM / feed congelato). Sono pochi (decine per file) ma avvelenano i backtest
(stop/entry su prezzi mai avvenuti) e gonfiano le code (la "FULL DD BTC ~54%" del ladder era
in gran parte questo).
Metodo (conservativo, fonte di verita' = Binance spot via ccxt, gia' cablato nel progetto):
1. DETECT: barra sospetta = high/low che sfora >15% il cluster di close locale [i-1,i,i+1]
(close sano + wick fasullo). Soglia larga: tanto e' Binance ad arbitrare.
2. ARBITRA: per ogni sospetta, scarica la barra Binance reale (BTC/USDT, ETH/USDT) allo
stesso tf/timestamp. Sostituisce O/H/L/C SOLO se Binance dissente materialmente (>2% su
high o low) -> un wick VERO confermato da Binance resta intatto. Volume/timestamp invariati.
3. BACKUP (data/_feed_backup/) + scrittura atomica + VALIDAZIONE (re-scan = 0 sospette,
n righe invariato). Log dettagliato di ogni barra riparata (old OHLC -> new).
uv run python scripts/analysis/clean_feed.py [ASSET_TF ...] # default: tutti BTC/ETH x TF
uv run python scripts/analysis/clean_feed.py BTC_1h # un solo file
"""
from __future__ import annotations
import shutil
import sys
import time
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from src.data.downloader import _parquet_path, DATA_DIR
BACKUP = PROJECT_ROOT / "data" / "_feed_backup"
SYMBOL = {"BTC": "BTC/USDT", "ETH": "ETH/USDT"}
WICK_THR = 0.15 # detect: wick oltre 15% il cluster di close locale
REPLACE_THR = 0.02 # arbitra: sostituisci solo se Binance dissente >2% su high/low
_EX = None
def _binance():
global _EX
if _EX is None:
import ccxt
_EX = ccxt.binance({"enableRateLimit": True})
return _EX
def suspect_mask(df: pd.DataFrame) -> np.ndarray:
c = df["close"].to_numpy(float); h = df["high"].to_numpy(float); l = df["low"].to_numpy(float)
cp = np.roll(c, 1); cp[0] = c[0]; cn = np.roll(c, -1); cn[-1] = c[-1]
locmax = np.maximum.reduce([c, cp, cn]); locmin = np.minimum.reduce([c, cp, cn])
return (h > locmax * (1 + WICK_THR)) | (l < locmin * (1 - WICK_THR))
def _binance_bar(symbol: str, tf: str, ts_ms: int):
"""OHLC reale Binance alla barra ts_ms (None se assente)."""
try:
rows = _binance().fetch_ohlcv(symbol, tf, since=ts_ms - 1, limit=3)
except Exception as e:
print(f" ! binance err: {type(e).__name__}: {str(e)[:80]}")
return None
for r in rows:
if int(r[0]) == ts_ms:
return float(r[1]), float(r[2]), float(r[3]), float(r[4])
return None
def clean_file(asset: str, tf: str) -> dict:
path = _parquet_path(asset, tf)
if not path.exists():
return {"file": f"{asset}_{tf}", "skip": "no-file"}
df = pd.read_parquet(path)
mask = suspect_mask(df)
idx = np.where(mask)[0]
n0 = len(df)
if len(idx) == 0:
return {"file": f"{asset}_{tf}", "suspect": 0, "repaired": 0, "kept_real": 0,
"missing_binance": 0, "rows_before": n0, "rows_after": n0,
"still_suspect": 0, "log": []}
repaired, kept, missing = 0, 0, 0
log = []
for i in idx:
ts = int(df.iloc[i]["timestamp"])
b = _binance_bar(SYMBOL[asset], tf, ts)
oh, ol = float(df.iloc[i]["high"]), float(df.iloc[i]["low"])
if b is None:
missing += 1
continue
bo, bh, bl, bc = b
if abs(oh - bh) / bh > REPLACE_THR or abs(ol - bl) / max(bl, 1e-9) > REPLACE_THR:
df.iat[i, df.columns.get_loc("open")] = bo
df.iat[i, df.columns.get_loc("high")] = bh
df.iat[i, df.columns.get_loc("low")] = bl
df.iat[i, df.columns.get_loc("close")] = bc
repaired += 1
ts_s = pd.to_datetime(ts, unit="ms", utc=True).strftime("%Y-%m-%d %H:%M")
log.append(f" {ts_s} H {oh:,.0f}->{bh:,.0f} L {ol:,.0f}->{bl:,.0f}")
else:
kept += 1 # Binance conferma il wick: barra reale, intatta
if repaired:
BACKUP.mkdir(parents=True, exist_ok=True)
shutil.copy2(path, BACKUP / f"{asset.lower()}_{tf}.parquet.bak")
tmp = path.with_suffix(".parquet.tmp")
df.to_parquet(tmp, index=False)
tmp.replace(path)
# validazione
df2 = pd.read_parquet(path)
still = int(suspect_mask(df2).sum())
return {"file": f"{asset}_{tf}", "suspect": len(idx), "repaired": repaired,
"kept_real": kept, "missing_binance": missing, "rows_before": n0,
"rows_after": len(df2), "still_suspect": still, "log": log}
def main():
targets = sys.argv[1:] or [f"{a}_{tf}" for a in ("BTC", "ETH")
for tf in ("5m", "15m", "30m", "1h")]
print(f"CLEAN FEED — backup in {BACKUP}\n")
grand = 0
for t in targets:
asset, tf = t.split("_", 1)
r = clean_file(asset, tf)
if r.get("skip"):
print(f" {t:<9} SKIP ({r['skip']})"); continue
grand += r.get("repaired", 0)
print(f" {r['file']:<9} sospette={r['suspect']:>3} riparate={r['repaired']:>3} "
f"reali-tenute={r.get('kept_real',0):>3} no-binance={r.get('missing_binance',0):>2} "
f"| righe {r['rows_before']}=={r['rows_after']} residue-sospette={r['still_suspect']}")
for line in r.get("log", [])[:8]:
print(line)
if len(r.get("log", [])) > 8:
print(f" ... (+{len(r['log'])-8} altre)")
print(f"\n TOTALE barre riparate: {grand}")
if __name__ == "__main__":
main()
+164
View File
@@ -0,0 +1,164 @@
"""Studio: combinare TUTTE le strategie (fade + honest) migliora i risultati?
Due famiglie con meccanismi e orizzonti diversi:
FADE (intraday 1h, long/short, BTC/ETH): MR01 boll, MR02 donchian, MR07
return-reversal — tutte col filtro trend 3.0 ATR. (MR03 keltner -> waste.)
HONEST (long-only, multi-regime, multi-crypto): DIP01 (dip-buy 1h BTC),
TR01 (EMA trend 4h basket), ROT02 (dual-momentum rotation 1d).
Metodo: per ogni sleeve si costruisce l'equity GIORNALIERA normalizzata su un
indice comune (2021-01-01 -> 2026-05-26), si passa ai rendimenti giornalieri,
si misura la correlazione cross-famiglia e si confrontano i portafogli
equal-weight (ribilanciati ogni giorno) e inverse-vol. Metriche FULL e OOS
(ultimo 30% della finestra comune): ritorno, CAGR, max DD, Sharpe annualizzato.
Tutto NETTO (fee gia' incluse nelle sleeve), leva 3x, pos 15% per sleeve.
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from src.data.downloader import load_data
from scripts.analysis.risk_management import strats_for, build_trades, INIT
# curve daily honest gia' pronte nell'altra famiglia
from scripts.analysis.honest_improve2 import (
_daily_equity, _norm, dip_market_gated, _tr_basket_daily, _rot_daily_equity,
)
IDX = pd.date_range("2021-01-01", "2026-05-26", freq="1D", tz="UTC")
OOS_FRAC = 0.30
SPLIT = int(len(IDX) * (1 - OOS_FRAC)) # confine OOS sulla finestra comune
OOS_DATE = IDX[SPLIT].date()
ANN = 365.0 # giorni/anno per annualizzare
# ---------------- equity giornaliere ----------------
# SWAP fade 1h -> 15m (2026-06-12, scelta utente). Gate fade15m_port06_gate.py: parametri
# 1h NON ri-tunati trasferiti a 15m, corr 15m-1h media 0.26, SWAP promosso (FULL CAGR
# 74->101% DD 3.46->2.47%, OOS Sh 10.07->10.86; OOS DD 1.48->2.09 accettato), edge ETH
# regge il flat-entry-skip. Il canonico segue il deployato per tenere la parita' delle
# due facce. Diario docs/diary/2026-06-12-fade15m-gate.md.
FADE_TF = "15m"
def fade_daily_equity(asset: str, fn, params, tf: str = FADE_TF) -> pd.Series:
"""Equity giornaliera di uno sleeve fade: trade (filtro trend 3.0) -> equity -> daily."""
df = load_data(asset, tf)
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
trades = build_trades(fn(df, **params), df, trend_max=3.0)
n = len(df); eq = np.full(n, INIT, dtype=float); cap = INIT
for i, j, ret in sorted(trades, key=lambda t: t[1]):
cap = max(cap + cap * 0.15 * ret, 10.0)
eq[j:] = cap
s = pd.Series(eq, index=ts).resample("1D").last().reindex(IDX).ffill().bfill()
return _norm(s)
def build_all_sleeves() -> dict[str, pd.Series]:
sleeves: dict[str, pd.Series] = {}
# --- FADE: 6 sleeve (15m dal 2026-06-12, vedi FADE_TF) ---
for asset in ["BTC", "ETH"]:
for nm, (fn, params) in strats_for(asset).items():
sleeves[f"{nm}_{asset}"] = fade_daily_equity(asset, fn, params)
# --- HONEST: 3 sleeve (riuso le funzioni dell'altra famiglia) ---
d = dip_market_gated("BTC", market_n=0, return_equity=True)
sleeves["DIP01_BTC"] = _norm(_daily_equity(d["eq_ts"], d["eq_v"], IDX))
sleeves["TR01_basket"] = _norm(_tr_basket_daily(["BNB", "BTC", "DOGE", "SOL", "XRP"], IDX))
sleeves["ROT02_rot"] = _norm(_rot_daily_equity(IDX))
return sleeves
# ---------------- metriche ----------------
def metrics(daily_ret: pd.Series, lo: int = 0, hi: int | None = None) -> dict:
r = daily_ret.iloc[lo:hi]
eq = (1 + r).cumprod()
peak = eq.cummax(); dd = float(((peak - eq) / peak).max() * 100)
yrs = len(r) / ANN
tot = (eq.iloc[-1] - 1) * 100
cagr = ((eq.iloc[-1]) ** (1 / yrs) - 1) * 100 if yrs > 0 else 0.0
sharpe = float(r.mean() / r.std() * np.sqrt(ANN)) if r.std() > 0 else 0.0
return dict(ret=tot, cagr=cagr, dd=dd, sharpe=sharpe)
def yearly_returns(daily_ret: pd.Series) -> dict[int, float]:
"""Rendimento % netto per anno solare dai rendimenti giornalieri composti."""
g = daily_ret.groupby(daily_ret.index.year).apply(lambda x: ((1 + x).prod() - 1) * 100)
return {int(y): float(v) for y, v in g.items()}
def port_returns(members: dict[str, pd.Series], weights: dict[str, float] | None = None) -> pd.Series:
"""Rendimenti giornalieri di un portafoglio ribilanciato ogni giorno ai pesi dati."""
dr = pd.DataFrame({k: v.pct_change().fillna(0.0) for k, v in members.items()})
if weights is None:
return dr.mean(axis=1)
w = pd.Series(weights); w = w / w.sum()
return (dr * w).sum(axis=1)
def inv_vol_weights(members: dict[str, pd.Series], lo=0, hi=None) -> dict[str, float]:
"""Pesi inversamente proporzionali alla volatilita' (stimata sulla finestra train)."""
vol = {k: v.pct_change().iloc[lo:hi].std() for k, v in members.items()}
inv = {k: (1.0 / s if s and s > 0 else 0.0) for k, s in vol.items()}
tot = sum(inv.values())
return {k: x / tot for k, x in inv.items()}
# ---------------- report ----------------
def row(label, dr):
f = metrics(dr); o = metrics(dr, lo=SPLIT)
print(f" {label:<26s}{f['ret']:>+9.0f}{f['cagr']:>7.0f}{f['dd']:>7.1f}{f['sharpe']:>7.2f}"
f" | {o['ret']:>+9.0f}{o['cagr']:>7.0f}{o['dd']:>7.1f}{o['sharpe']:>7.2f}")
def main():
print("Costruzione equity giornaliere (puo' richiedere ~1 min)...")
S = build_all_sleeves()
fade = {k: v for k, v in S.items() if k.startswith("MR")}
honest = {k: v for k, v in S.items() if not k.startswith("MR")}
# --- correlazione cross-famiglia ---
dr = pd.DataFrame({k: v.pct_change().fillna(0.0) for k, v in S.items()})
corr = dr.corr()
fade_k, hon_k = list(fade), list(honest)
cross = corr.loc[fade_k, hon_k]
print("\n" + "=" * 92)
print(f" CORRELAZIONE rendimenti giornalieri — FADE (righe) vs HONEST (colonne) | {IDX[0].date()}->{IDX[-1].date()}")
print("=" * 92)
print(f" {'':<12s}" + "".join(f"{c:>13s}" for c in hon_k))
for f in fade_k:
print(f" {f:<12s}" + "".join(f"{cross.loc[f,c]:>13.2f}" for c in hon_k))
intra_fade = corr.loc[fade_k, fade_k].values[np.triu_indices(len(fade_k), 1)].mean()
intra_hon = corr.loc[hon_k, hon_k].values[np.triu_indices(len(hon_k), 1)].mean()
print(f"\n Corr media intra-FADE {intra_fade:+.2f} | intra-HONEST {intra_hon:+.2f} | "
f"cross-famiglia {cross.values.mean():+.2f} (piu' bassa = piu' diversificazione)")
# --- confronto portafogli ---
print("\n" + "=" * 92)
print(f" PORTAFOGLI equal-weight (ribil. giornaliero) | OOS da {OOS_DATE} | leva3x pos15%/sleeve")
print("=" * 92)
print(f" {'portafoglio':<26s}{'Ret%':>9s}{'CAGR':>7s}{'DD%':>7s}{'Shrp':>7s}"
f" | {'oRet%':>9s}{'oCAGR':>7s}{'oDD%':>7s}{'oShrp':>7s}")
print(" " + "-" * 88)
row("FADE only (8 sleeve)", port_returns(fade))
row("HONEST only (3 sleeve)", port_returns(honest))
row("ALL equal-weight (11)", port_returns(S))
# 50/50 fra le due famiglie (ogni famiglia equipesata al suo interno)
fr, hr = port_returns(fade), port_returns(honest)
row("ALL 50/50 famiglie", (fr + hr) / 2)
# inverse-vol sul train, applicato a tutti gli 11 sleeve
w = inv_vol_weights(S, lo=0, hi=SPLIT)
row("ALL inverse-vol", port_returns(S, w))
print(" " + "-" * 88)
print(" Sharpe annualizzato sui rendimenti giornalieri. Confronta DD e Sharpe:")
print(" se il combinato ha DD piu' basso e Sharpe piu' alto delle singole famiglie, combinare conviene.")
if __name__ == "__main__":
main()
+87
View File
@@ -0,0 +1,87 @@
"""Combina i NUOVI edge (pairs + TSM01) col MASTER esistente: migliora il portafoglio?
Aggiunge al MASTER a 9 sleeve (6 fade + 3 honest) due nuove fonti scoperte
nell'esplorazione, poco correlate:
- PAIRS market-neutral (ETH/BTC, LTC/ETH, ADA/ETH) -> corr ~0 col mercato
- TSM01 (TSMOM multi-orizzonte + risk-off) -> corr ~0.53 con ROT02
Misura correlazione delle nuove sleeve vs esistenti e confronta MASTER-9 vs
MASTER-esteso su Ret/CAGR/DD/Sharpe, FULL e OOS (finestra comune 2021-2026).
"""
from __future__ import annotations
import sys
from pathlib import Path
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.combine_portfolio import (
build_all_sleeves, port_returns, metrics, yearly_returns, SPLIT, OOS_DATE, IDX,
)
from scripts.analysis.honest_improve2 import _daily_equity, _norm
from scripts.analysis.pairs_research import pairs_sim
from scripts.analysis.tsmom_research import tsmom_sim
def daily_from(eq_ts, eq_v):
return _norm(_daily_equity(eq_ts, eq_v, IDX))
def main():
print("Costruzione equity (puo' richiedere ~1-2 min)...\n")
S = build_all_sleeves() # 9 sleeve esistenti
# nuove sleeve: i 6 pairs robusti di PR01 + TSM01
from scripts.strategies.PR01_pairs_reversion import PAIRS
new = {}
for a, b, p in PAIRS:
r = pairs_sim(a, b, **p)
new[f"PR_{a}{b}"] = daily_from(r["eq_ts"], r["eq_v"])
t = tsmom_sim()
new["TSM01"] = daily_from(t["eq_ts"], t["eq_v"])
allS = {**S, **new}
# --- correlazione nuove vs esistenti ---
dr = pd.DataFrame({k: v.pct_change().fillna(0.0) for k, v in allS.items()})
corr = dr.corr()
old_k = list(S); new_k = list(new)
print("=" * 88)
print(" CORRELAZIONE rendimenti giornalieri — NUOVE (righe) vs media esistenti")
print("=" * 88)
for nk in new_k:
avg = corr.loc[nk, old_k].mean()
mx = corr.loc[nk, old_k].abs().max()
print(f" {nk:<12s} corr media col MASTER-9 = {avg:+.2f} |max| = {mx:.2f}")
# --- confronto portafogli ---
def line(label, members):
pr = port_returns(members)
f, o = metrics(pr), metrics(pr, lo=SPLIT)
print(f" {label:<26s}{f['ret']:>+9.0f}{f['cagr']:>7.0f}{f['dd']:>7.1f}{f['sharpe']:>7.2f}"
f" | {o['ret']:>+9.0f}{o['dd']:>7.1f}{o['sharpe']:>7.2f}")
return pr
print("\n" + "=" * 96)
print(f" MASTER-9 vs MASTER-ESTESO (con pairs+TSM01) | OOS da {OOS_DATE} | equal-weight daily")
print("=" * 96)
print(f" {'portafoglio':<26s}{'Ret%':>9s}{'CAGR':>7s}{'DD%':>7s}{'Shrp':>7s}"
f" | {'oRet%':>9s}{'oDD%':>7s}{'oShrp':>7s}")
print(" " + "-" * 92)
pairs_only = {k: v for k, v in new.items() if k.startswith('PR_')}
line(f"MASTER-9 (base)", S)
line(f"MASTER +pairs ({len(S)+len(pairs_only)})", {**S, **pairs_only})
line(f"MASTER +TSM01 ({len(S)+1})", {**S, "TSM01": new["TSM01"]})
pr_all = line(f"MASTER-esteso ({len(allS)})", allS)
print(" " + "-" * 92)
pa = yearly_returns(pr_all)
print(" MASTER-esteso per-anno: " + " ".join(f"{y}:{v:+.0f}%" for y, v in pa.items()))
print("\n Se il MASTER-esteso ha DD piu' basso e/o Sharpe piu' alto del MASTER-9, le nuove")
print(" famiglie aggiungono valore (diversificazione da fonti scorrelate).")
if __name__ == "__main__":
main()
-559
View File
@@ -1,559 +0,0 @@
"""Analisi finale — S1 (squeeze puro) vs Script 13 (squeeze+ML GBM).
Metriche: PnL, num trades, DD max, tempo medio a mercato, descrizione.
"""
from __future__ import annotations
import sys
sys.path.insert(0, ".")
import numpy as np
import pandas as pd
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.preprocessing import StandardScaler
from src.data.downloader import load_data
from src.fractal.patterns import encode_candles
FEE_PERP = 0.002
FEE_ML = 0.001
INITIAL = 1000
LEVERAGE = 3
TF_MINUTES = {"1m": 1, "5m": 5, "15m": 15, "1h": 60, "4h": 240, "1d": 1440}
# ── helpers ──────────────────────────────────────────────────────────
def keltner_ratio(close, high, low, window=14):
n = len(close)
r = np.full(n, np.nan)
for i in range(window, n):
wc, wh, wl = close[i-window:i], high[i-window:i], low[i-window:i]
ma = np.mean(wc)
bb_std = np.std(wc)
tr = np.maximum(wh-wl, np.maximum(np.abs(wh-np.roll(wc,1)), np.abs(wl-np.roll(wc,1))))
atr = np.mean(tr[1:])
kc = (ma+1.5*atr)-(ma-1.5*atr)
bb = (ma+2*bb_std)-(ma-2*bb_std)
if kc > 0:
r[i] = bb/kc
return r
def detect_squeezes(close, high, low, kcr, sq_thr=0.8, min_dur=5):
events = []
in_sq = False
sq_start = 0
for i in range(1, len(close)):
if np.isnan(kcr[i]):
continue
is_sq = kcr[i] < sq_thr
if is_sq and not in_sq:
in_sq = True
sq_start = i
elif not is_sq and in_sq:
in_sq = False
dur = i - sq_start
if dur < min_dur:
continue
events.append({"idx": i, "dur": dur, "sq_start": sq_start,
"avg_vol_squeeze": np.mean(close[sq_start:i]),
"kcr_at_release": kcr[i]})
return events
def _build_result(yearly, capital, max_dd, all_t, all_w, time_pct, avg_dur_h):
acc = all_w / all_t * 100
tot_pnl = sum(p for d in yearly.values() for p in d["pnls"])
years_active = len(yearly)
all_pnls = [p for d in yearly.values() for p in d["pnls"]]
sharpe = np.mean(all_pnls) / np.std(all_pnls) * np.sqrt(252) if len(all_pnls) > 1 and np.std(all_pnls) > 0 else 0
year_details = {}
for y in sorted(yearly):
d = yearly[y]
ya = d["w"] / d["t"] * 100 if d["t"] > 0 else 0
yp = sum(d["pnls"])
year_details[y] = {"trades": d["t"], "acc": ya, "pnl": yp}
valid_years = {y: d for y, d in year_details.items() if d["trades"] >= 10}
if valid_years:
worst_y = min(valid_years, key=lambda y: valid_years[y]["acc"])
worst_acc = valid_years[worst_y]["acc"]
elif year_details:
worst_y = min(year_details, key=lambda y: year_details[y]["acc"])
worst_acc = year_details[worst_y]["acc"]
else:
worst_y = "N/A"
worst_acc = 0
daily_pnl = tot_pnl / (years_active * 365) if years_active > 0 else 0
return {
"trades": all_t, "acc": acc, "pnl": tot_pnl, "capital": capital,
"max_dd": max_dd * 100, "sharpe": sharpe, "daily_pnl": daily_pnl,
"time_in_market_pct": time_pct, "avg_dur_h": avg_dur_h,
"years_active": years_active, "worst_year": str(worst_y),
"worst_acc": worst_acc, "year_details": year_details,
}
# ── S1: Squeeze breakout puro ────────────────────────────────────────
def run_s1_squeeze(asset, tf, hold=3):
df = load_data(asset, tf)
c, h, l, v = df["close"].values, df["high"].values, df["low"].values, df["volume"].values
n = len(c)
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
kcr = keltner_ratio(c, h, l, 14)
events = detect_squeezes(c, h, l, kcr)
yearly = {}
capital = float(INITIAL)
peak = capital
max_dd = 0
total_bars = 0
for ev in events:
i = ev["idx"]
if i + hold + 1 >= n:
continue
first_ret = (c[i] - c[i-1]) / c[i-1] if c[i-1] > 0 else 0
if abs(first_ret) < 0.001:
continue
direction = 1 if first_ret > 0 else -1
entry = c[i-1]
exit_price = c[min(i + hold - 1, n - 1)]
actual = (exit_price - entry) / entry * direction
net = actual * LEVERAGE - FEE_PERP * LEVERAGE
capital += capital * 0.15 * net
capital = max(capital, 10)
if capital > peak: peak = capital
dd = (peak - capital) / peak
max_dd = max(max_dd, dd)
total_bars += hold
year = ts.iloc[i].year
if year not in yearly:
yearly[year] = {"w": 0, "t": 0, "pnls": []}
yearly[year]["t"] += 1
if actual > 0: yearly[year]["w"] += 1
yearly[year]["pnls"].append(net * INITIAL)
all_t = sum(d["t"] for d in yearly.values())
all_w = sum(d["w"] for d in yearly.values())
if all_t == 0: return None
return _build_result(yearly, capital, max_dd, all_t, all_w,
total_bars / n * 100, hold * TF_MINUTES.get(tf, 60) / 60)
def run_s1_antifake_vol(asset, tf, hold=3):
df = load_data(asset, tf)
c, h, l, v = df["close"].values, df["high"].values, df["low"].values, df["volume"].values
n = len(c)
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
kcr = keltner_ratio(c, h, l, 14)
events = detect_squeezes(c, h, l, kcr)
yearly = {}
capital = float(INITIAL)
peak = capital
max_dd = 0
total_bars = 0
for ev in events:
i = ev["idx"]
if i + hold + 1 >= n:
continue
first_ret = (c[i] - c[i-1]) / c[i-1] if c[i-1] > 0 else 0
if abs(first_ret) < 0.001:
continue
br = h[i] - l[i]
if br > 0:
if c[i] > c[i-1]:
if (h[i] - c[i]) / br > 0.6:
continue
else:
if (c[i] - l[i]) / br > 0.6:
continue
avg_v = np.mean(v[ev["sq_start"]:i])
if avg_v > 0 and v[i] <= avg_v * 1.3:
continue
direction = 1 if first_ret > 0 else -1
entry = c[i-1]
exit_price = c[min(i + hold - 1, n - 1)]
actual = (exit_price - entry) / entry * direction
net = actual * LEVERAGE - FEE_PERP * LEVERAGE
capital += capital * 0.15 * net
capital = max(capital, 10)
if capital > peak: peak = capital
dd = (peak - capital) / peak
max_dd = max(max_dd, dd)
total_bars += hold
year = ts.iloc[i].year
if year not in yearly:
yearly[year] = {"w": 0, "t": 0, "pnls": []}
yearly[year]["t"] += 1
if actual > 0: yearly[year]["w"] += 1
yearly[year]["pnls"].append(net * INITIAL)
all_t = sum(d["t"] for d in yearly.values())
all_w = sum(d["w"] for d in yearly.values())
if all_t == 0: return None
return _build_result(yearly, capital, max_dd, all_t, all_w,
total_bars / n * 100, hold * TF_MINUTES.get(tf, 60) / 60)
# ── Script 13: Squeeze + ML ibrida (GBM walk-forward) ────────────────
def build_features_at(df, i, squeeze_info):
if i < 100:
return None
o = df["open"].values
h = df["high"].values
l = df["low"].values
c = df["close"].values
v = df["volume"].values
feats = []
for w in [12, 24, 48]:
win_c = c[i-w:i]
win_o = o[i-w:i]
win_h = h[i-w:i]
win_l = l[i-w:i]
win_v = v[i-w:i]
mn, mx = win_l.min(), max(win_h.max(), win_c.max())
rng = mx - mn if mx - mn > 0 else 1e-10
total = win_h - win_l
total = np.where(total == 0, 1e-10, total)
body = np.abs(win_c - win_o) / total
direction = np.sign(win_c - win_o)
log_c = np.log(np.where(win_c == 0, 1e-10, win_c))
rets = np.diff(log_c)
v_mean = np.mean(win_v)
feats.extend([
np.mean(rets) if len(rets) > 0 else 0,
np.std(rets) if len(rets) > 0 else 0,
np.sum(rets) if len(rets) > 0 else 0,
float(pd.Series(rets).skew()) if len(rets) > 2 else 0,
float(pd.Series(rets).kurtosis()) if len(rets) > 3 else 0,
np.mean(body), np.std(body),
np.mean(direction), np.mean(direction[-min(3, w):]),
(win_c[-1] - mn) / rng,
win_v[-1] / v_mean if v_mean > 0 else 1,
np.corrcoef(rets[:-1], rets[1:])[0, 1] if len(rets) > 1 and np.std(rets) > 0 else 0,
])
sq = squeeze_info
feats.extend([
sq["dur"], sq["dur"] / 24, sq["kcr_at_release"],
v[i-1] / sq["avg_vol_squeeze"] if sq["avg_vol_squeeze"] > 0 else 1,
np.mean(v[i:min(i+3, len(v))]) / sq["avg_vol_squeeze"] if sq["avg_vol_squeeze"] > 0 else 1,
])
h48 = np.max(h[max(0, i-48):i])
l48 = np.min(l[max(0, i-48):i])
r48 = h48 - l48
feats.append((c[i-1] - l48) / r48 if r48 > 0 else 0.5)
tr = np.maximum(h[i-14:i] - l[i-14:i],
np.maximum(np.abs(h[i-14:i] - np.roll(c[i-14:i], 1)),
np.abs(l[i-14:i] - np.roll(c[i-14:i], 1))))
atr = np.mean(tr[1:])
feats.append(atr / c[i-1] if c[i-1] > 0 else 0)
first_ret = (c[i] - c[i-1]) / c[i-1] if c[i-1] > 0 else 0
feats.append(first_ret)
return np.nan_to_num(np.array(feats), nan=0, posinf=1e6, neginf=-1e6)
def run_s13_hybrid(asset, tf, bb_w, sq_thr, brk_bars, leverage, pos_pct, ml_thr):
df = load_data(asset, tf)
close = df["close"].values
high = df["high"].values
low = df["low"].values
volume = df["volume"].values
n = len(df)
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
kcr = keltner_ratio(close, high, low, bb_w)
events = detect_squeezes(close, high, low, kcr, sq_thr)
X_all, y_all, ev_all = [], [], []
for ev in events:
i = ev["idx"]
if i + brk_bars >= n or i < 100:
continue
feats = build_features_at(df, i, ev)
if feats is None:
continue
actual_ret = (close[i + brk_bars - 1] - close[i - 1]) / close[i - 1]
X_all.append(feats)
y_all.append(1 if actual_ret > 0 else 0)
ev_all.append(ev)
if len(X_all) < 50:
return None
X = np.array(X_all)
y = np.array(y_all)
TRAIN_SIZE = max(int(len(X) * 0.5), 50)
STEP_SIZE = max(int(len(X) * 0.1), 10)
yearly = {}
capital = float(INITIAL)
peak = capital
max_dd = 0
total_bars = 0
all_t = 0
all_w = 0
start = 0
while start + TRAIN_SIZE + STEP_SIZE <= len(X):
train_end = start + TRAIN_SIZE
test_end = min(train_end + STEP_SIZE, len(X))
X_tr, y_tr = X[start:train_end], y[start:train_end]
X_te = X[train_end:test_end]
if len(np.unique(y_tr)) < 2:
start += STEP_SIZE
continue
scaler = StandardScaler()
X_tr_s = scaler.fit_transform(X_tr)
X_te_s = scaler.transform(X_te)
model = GradientBoostingClassifier(
n_estimators=150, max_depth=4, min_samples_leaf=10,
learning_rate=0.05, subsample=0.8, random_state=42,
)
model.fit(X_tr_s, y_tr)
up_idx = list(model.classes_).index(1) if 1 in model.classes_ else -1
if up_idx < 0:
start += STEP_SIZE
continue
for j in range(len(X_te)):
proba = model.predict_proba(X_te_s[j:j+1])[0]
p_up = proba[up_idx]
ev = ev_all[train_end + j]
i = ev["idx"]
actual_ret = (close[i + brk_bars - 1] - close[i - 1]) / close[i - 1]
direction = None
if p_up >= ml_thr:
direction = 1
elif p_up <= (1 - ml_thr):
direction = -1
if direction is None:
continue
is_correct = (direction == 1 and actual_ret > 0) or (direction == -1 and actual_ret < 0)
trade_ret = actual_ret * direction
net = trade_ret * leverage - FEE_ML * 2 * leverage
capital += capital * pos_pct * net
capital = max(capital, 10)
if capital > peak: peak = capital
dd = (peak - capital) / peak
max_dd = max(max_dd, dd)
total_bars += brk_bars
all_t += 1
if is_correct: all_w += 1
year = ts.iloc[i].year
if year not in yearly:
yearly[year] = {"w": 0, "t": 0, "pnls": []}
yearly[year]["t"] += 1
if is_correct: yearly[year]["w"] += 1
yearly[year]["pnls"].append(net * INITIAL)
start += STEP_SIZE
if all_t == 0:
return None
return _build_result(yearly, capital, max_dd, all_t, all_w,
total_bars / n * 100, brk_bars * TF_MINUTES.get(tf, 60) / 60)
# ═══════════════════════════════════════════════════════════════════
# ESECUZIONE
# ═══════════════════════════════════════════════════════════════════
print("Calcolo in corso...\n")
strategies = []
def add(name, desc, cat, result):
if result and result["trades"] >= 20:
strategies.append({"name": name, "desc": desc, "cat": cat, **result})
# ── S1: Squeeze puro ────────────────────────────────────────────
add("S1 Squeeze BTC 15m", "Squeeze breakout puro, BBw=14, hold 3×15m, leva 3x",
"S1", run_s1_squeeze("BTC", "15m"))
add("S1 Squeeze ETH 15m", "Squeeze breakout puro, BBw=14, hold 3×15m, leva 3x",
"S1", run_s1_squeeze("ETH", "15m"))
add("S1 Squeeze BTC 1h", "Squeeze breakout puro, BBw=14, hold 3×1h, leva 3x",
"S1", run_s1_squeeze("BTC", "1h"))
add("S1 Squeeze ETH 1h", "Squeeze breakout puro, BBw=14, hold 3×1h, leva 3x",
"S1", run_s1_squeeze("ETH", "1h"))
add("S1 AF+Vol BTC 15m", "Squeeze + antifakeout + volume confirm >1.3× media",
"S1", run_s1_antifake_vol("BTC", "15m"))
add("S1 AF+Vol ETH 15m", "Squeeze + antifakeout + volume confirm >1.3× media",
"S1", run_s1_antifake_vol("ETH", "15m"))
add("S1 AF+Vol BTC 1h", "Squeeze + antifakeout + volume confirm >1.3× media",
"S1", run_s1_antifake_vol("BTC", "1h"))
add("S1 AF+Vol ETH 1h", "Squeeze + antifakeout + volume confirm >1.3× media",
"S1", run_s1_antifake_vol("ETH", "1h"))
# ── Script 13: Squeeze + ML (GBM walk-forward) ─────────────────
print(" Training ML models...")
add("S13 ETH 15m bb14 ml70", "Squeeze+GBM walk-forward, BBw=14 sq=0.8 ml≥0.70, 3x leva 15% pos",
"S13", run_s13_hybrid("ETH", "15m", 14, 0.8, 3, 3, 0.15, 0.70))
add("S13 ETH 15m bb14 ml65", "Squeeze+GBM walk-forward, BBw=14 sq=0.8 ml≥0.65, 3x leva 15% pos",
"S13", run_s13_hybrid("ETH", "15m", 14, 0.8, 3, 3, 0.15, 0.65))
add("S13 ETH 15m bb20 ml70", "Squeeze+GBM walk-forward, BBw=20 sq=0.9 ml≥0.70, 3x leva 15% pos",
"S13", run_s13_hybrid("ETH", "15m", 20, 0.9, 3, 3, 0.15, 0.70))
add("S13 BTC 15m bb14 ml70", "Squeeze+GBM walk-forward, BBw=14 sq=0.9 ml≥0.70, 3x leva 15% pos",
"S13", run_s13_hybrid("BTC", "15m", 14, 0.9, 3, 3, 0.15, 0.70))
add("S13 BTC 15m bb14 ml65", "Squeeze+GBM walk-forward, BBw=14 sq=0.9 ml≥0.65, 3x leva 15% pos",
"S13", run_s13_hybrid("BTC", "15m", 14, 0.9, 3, 3, 0.15, 0.65))
add("S13 BTC 1h bb14 ml70", "Squeeze+GBM walk-forward, BBw=14 sq=0.8 ml≥0.70, 3x leva 20% pos",
"S13", run_s13_hybrid("BTC", "1h", 14, 0.8, 3, 3, 0.20, 0.70))
add("S13 BTC 1h bb14 ml65", "Squeeze+GBM walk-forward, BBw=14 sq=0.8 ml≥0.65, 3x leva 20% pos",
"S13", run_s13_hybrid("BTC", "1h", 14, 0.8, 3, 3, 0.20, 0.65))
add("S13 ETH 1h bb14 ml70", "Squeeze+GBM walk-forward, BBw=14 sq=0.8 ml≥0.70, 3x leva 20% pos",
"S13", run_s13_hybrid("ETH", "1h", 14, 0.8, 3, 3, 0.20, 0.70))
strategies.sort(key=lambda x: x["acc"], reverse=True)
# ═══════════════════════════════════════════════════════════════════
# TABELLA 1: Classifica
# ═══════════════════════════════════════════════════════════════════
W = 150
print("=" * W)
print(" S1 (SQUEEZE PURO) vs S13 (SQUEEZE + GBM) — CLASSIFICA FINALE")
print(f" Fee: 0.2% RT. Dati OHLCV reali 2018-2026. Position 15%. Leva 3x.")
print("=" * W)
hdr = (f" {'#':>2s} {'Cat':>3s} {'Nome':<26s} {'Trades':>6s} {'Acc':>6s} "
f"{'PnL€':>9s} {'DD%':>6s} {'€/day':>7s} {'Sharpe':>7s} "
f"{'Mkt%':>5s} {'Dur':>6s} {'Worst':>12s} {'Anni':>4s}")
print(hdr)
print(f" {''*(W-4)}")
for idx, s in enumerate(strategies, 1):
worst = f"{s['worst_year']}({s['worst_acc']:.0f}%)"
dur_str = f"{s['avg_dur_h']:.0f}h" if s['avg_dur_h'] >= 1 else f"{s['avg_dur_h']*60:.0f}m"
tag = " ★★" if s["acc"] >= 78 else "" if s["acc"] >= 76 else ""
print(f" {idx:>2d} {s['cat']:>3s} {s['name']:<26s} {s['trades']:>6d} {s['acc']:>5.1f}% "
f"{s['pnl']:>+8.0f} {s['max_dd']:>5.1f}% {s['daily_pnl']:>+6.2f} {s['sharpe']:>7.2f} "
f"{s['time_in_market_pct']:>4.1f}% {dur_str:>6s} {worst:>12s} {s['years_active']:>4d}{tag}")
# ═══════════════════════════════════════════════════════════════════
# TABELLA 2: Descrizione
# ═══════════════════════════════════════════════════════════════════
print(f"\n\n{'=' * W}")
print(" DESCRIZIONE")
print(f"{'=' * W}")
print(f" {'#':>2s} {'Nome':<26s} {'Descrizione'}")
print(f" {''*(W-4)}")
for idx, s in enumerate(strategies, 1):
print(f" {idx:>2d} {s['name']:<26s} {s['desc']}")
# ═══════════════════════════════════════════════════════════════════
# TABELLA 3: Breakdown per anno
# ═══════════════════════════════════════════════════════════════════
top_n = min(12, len(strategies))
top = strategies[:top_n]
all_years = sorted(set(y for s in top for y in s["year_details"]))
print(f"\n\n{'=' * W}")
print(f" BREAKDOWN PER ANNO — TOP {top_n} (accuracy% / trades)")
print(f"{'=' * W}")
header = f" {'Nome':<26s}"
for y in all_years:
header += f" {y:>10d}"
print(header)
print(f" {''*(W-4)}")
for s in top:
line = f" {s['name']:<26s}"
for y in all_years:
if y in s["year_details"]:
d = s["year_details"][y]
line += f" {d['acc']:>4.0f}%/{d['trades']:<4d}"
else:
line += f" {'':>10s}"
print(line)
# ═══════════════════════════════════════════════════════════════════
# TABELLA 4: Robustezza
# ═══════════════════════════════════════════════════════════════════
print(f"\n\n{'=' * W}")
print(f" ANALISI ROBUSTEZZA")
print(f"{'=' * W}")
print(f" {'#':>2s} {'Nome':<26s} {'MinAcc':>7s} {'MaxAcc':>7s} {'Spread':>7s} "
f"{'AnniOK':>7s} {'€/trade':>8s} {'Verdict':<12s}")
print(f" {''*90}")
for idx, s in enumerate(strategies, 1):
yd = s["year_details"]
valid = {y: d for y, d in yd.items() if d["trades"] >= 10}
accs = [d["acc"] for d in (valid if valid else yd).values()]
if not accs:
continue
min_a, max_a = min(accs), max(accs)
spread = max_a - min_a
years_ok = sum(1 for a in accs if a >= 70)
avg_pnl = s["pnl"] / s["trades"] if s["trades"] > 0 else 0
n_valid = len(valid if valid else yd)
if n_valid < 4:
verdict = "⚠ CORTO"
elif min_a < 60:
verdict = "⚠ FRAGILE"
elif min_a >= 72 and s["acc"] >= 77:
verdict = "✅ SOLIDO"
elif min_a >= 65 and s["acc"] >= 74:
verdict = "~ BUONO"
else:
verdict = "~ OK"
print(f" {idx:>2d} {s['name']:<26s} {min_a:>6.1f}% {max_a:>6.1f}% {spread:>6.1f}% "
f"{years_ok:>3d}/{n_valid:<3d}{avg_pnl:>+7.1f} {verdict:<12s}")
# ═══════════════════════════════════════════════════════════════════
# VERDETTO
# ═══════════════════════════════════════════════════════════════════
print(f"\n\n{'=' * W}")
print(f" VERDETTO FINALE")
print(f"{'=' * W}")
solidi = [s for s in strategies if s["trades"] >= 200 and s["years_active"] >= 5 and s["worst_acc"] >= 65]
solidi_s1 = [s for s in solidi if s["cat"] == "S1"]
solidi_ml = [s for s in solidi if s["cat"] == "S13"]
solidi_s1.sort(key=lambda x: x["acc"], reverse=True)
solidi_ml.sort(key=lambda x: x["daily_pnl"], reverse=True)
if solidi_s1:
b = solidi_s1[0]
print(f"\n MIGLIORE S1 (regole pure, facile da deployare):")
print(f" {b['name']}{b['acc']:.1f}% acc, {b['trades']} trades, DD {b['max_dd']:.1f}%, €{b['daily_pnl']:+.2f}/day, Sharpe {b['sharpe']:.2f}")
if solidi_ml:
m = solidi_ml[0]
print(f"\n MIGLIORE S13 (squeeze+GBM, più complesso):")
print(f" {m['name']}{m['acc']:.1f}% acc, {m['trades']} trades, DD {m['max_dd']:.1f}%, €{m['daily_pnl']:+.2f}/day, Sharpe {m['sharpe']:.2f}")
max_pnl = max(strategies, key=lambda x: x["pnl"])
print(f"\n MAX PnL: {max_pnl['name']} — €{max_pnl['pnl']:+,.0f}")
@@ -0,0 +1,209 @@
"""ANALISI DI IMPATTO (sola lettura, da docs/TODO.md): bug bfill di `_daily_equity`.
IL BUG (scripts/analysis/honest_improve2.py:30):
daily = s.resample("1D").last().reindex(idx).ffill().bfill()
La serie `s` e' a PUNTI-TRADE (un valore di capitale per ogni exit). Il `reindex(idx)`
taglia PRIMA di forward-fillare: i giorni di IDX precedenti al primo trade DENTRO la
finestra restano NaN (il ffill non ha un valore precedente in-finestra da propagare) e
il `.bfill()` finale li riempie col capitale DOPO il primo trade in-finestra. Effetti:
1. l'ancora a idx[0] e' il capitale post-primo-trade-in-finestra, NON il capitale
portato avanti dall'ultimo trade PRIMA della finestra;
2. il rendimento del primo trade in-finestra viene CANCELLATO dalla serie daily
(la testa e' piatta al valore post-trade -> pct_change = 0 anche il giorno del trade).
CORREZIONE (qui, solo per confronto): ffill PRIMA del reindex (carry-forward su tutta la
storia trade) + testa pre-primo-trade-assoluto = capitale iniziale 1000. MAI valori dal futuro.
Sleeve canonici interessati (serie a punti-trade -> testa di IDX scoperta):
DIP01_BTC, PR_ETHBTC, PR_ETHBTC_15M, PR_LTCETH, PR_ADAETH, PR_BTCLTC, PR_ETHSOL,
TSM01, XS01 (questi due quasi-densi: punti daily/12h -> impatto atteso ~0).
TR01_basket / ROT02_rot passano da _daily_equity ma con punti PER-BARRA (densi dal
2018) -> verificati comunque qui via monkeypatch runtime (nessun file canonico toccato).
I fade (combine_portfolio.py:52) e SH01 (shape_ml_validate.py:124) usano lo stesso
pattern reindex+bfill ma su equity PER-BARRA con dati che iniziano prima di IDX[0]
-> il bfill e' un no-op (verificato: nessun NaN in testa).
NB: le metriche OOS canoniche affettano la STESSA serie daily a SPLIT (metrics(dr,
lo=SPLIT)); la distorsione sta solo in testa (2021) -> l'OOS e' invariato per
costruzione se il primo trade in-finestra precede lo SPLIT. Questo script lo misura.
Uso: uv run python scripts/analysis/daily_equity_bfill_impact.py
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
import scripts.analysis.honest_improve2 as hi2
from scripts.analysis.honest_improve2 import _norm, dip_market_gated
from scripts.analysis.combine_portfolio import IDX, SPLIT, OOS_DATE, metrics, port_returns
from scripts.analysis.pairs_research import pairs_sim, pairs_sim_flat
from scripts.analysis.tsmom_research import tsmom_sim
from scripts.strategies.PR01_pairs_reversion import PAIRS as PAIR_DEFS
from scripts.strategies.XS01_cross_sectional import xsec_sim
from scripts.portfolios._defs import PORTFOLIOS
from src.portfolio import weighting as W
INIT = 1000.0
# ---------------- le due convenzioni ----------------
def daily_equity_buggy(ts_list, cap_list, idx):
"""Replica ESATTA di honest_improve2._daily_equity (per parity-check)."""
s = pd.Series(cap_list, index=pd.to_datetime(ts_list, utc=True))
s = s[~s.index.duplicated(keep="last")].sort_index()
return s.resample("1D").last().reindex(idx).ffill().bfill()
def daily_equity_fixed(ts_list, cap_list, idx, init=INIT):
"""CORRETTA: ancora = capitale portato avanti dall'ultimo trade PRIMA della
finestra (ffill prima del reindex); pre-primo-trade assoluto = capitale iniziale."""
s = pd.Series(cap_list, index=pd.to_datetime(ts_list, utc=True))
s = s[~s.index.duplicated(keep="last")].sort_index()
daily = s.resample("1D").last().ffill() # carry-forward su TUTTA la storia
daily = daily.reindex(idx).ffill() # coda oltre l'ultimo trade
return daily.fillna(init) # testa pre-primo-trade: capitale iniziale
def head_info(ts_list, cap_list, idx):
"""(primo giorno con trade dentro IDX, rendimento di testa perso dal bfill %)."""
s = pd.Series(cap_list, index=pd.to_datetime(ts_list, utc=True))
s = s[~s.index.duplicated(keep="last")].sort_index()
raw = s.resample("1D").last().reindex(idx) # senza fill: NaN = nessun trade quel giorno
first = raw.first_valid_index()
if first is None:
return None, 0.0
fixed = daily_equity_fixed(ts_list, cap_list, idx)
lost = (fixed.loc[first] / fixed.iloc[0] - 1) * 100 # ritorno idx[0]->primo trade-day
return first.date(), float(lost)
def m2(eq: pd.Series):
dr = eq.pct_change().fillna(0.0)
return metrics(dr), metrics(dr, lo=SPLIT)
def fmt_pair(label, b, f):
d_sh = f["sharpe"] - b["sharpe"]
d_dd = f["dd"] - b["dd"]
d_rt = f["ret"] - b["ret"]
return (f" {label:<22s}"
f"Sh {b['sharpe']:6.2f}->{f['sharpe']:6.2f} ({d_sh:+.3f}) "
f"DD {b['dd']:6.2f}->{f['dd']:6.2f} ({d_dd:+.3f}pp) "
f"ret {b['ret']:+9.1f}->{f['ret']:+9.1f} ({d_rt:+8.2f}pp)")
def main():
print("=" * 110)
print(" IMPATTO bug bfill _daily_equity (honest_improve2.py:30) — attuale vs corretto")
print(f" IDX {IDX[0].date()} -> {IDX[-1].date()} | OOS da {OOS_DATE} (slice a SPLIT={SPLIT} sui rendimenti daily)")
print("=" * 110)
# ---------------- [1] baseline canonica (bfill cosi' com'e') ----------------
print("\n[1] build_everything() canonico (2-3 min)...")
from scripts.analysis.report_families import build_everything
S, pairs, tsm, shape = build_everything()
base = {**S, **pairs, **tsm, **shape}
# ---------------- [2] ri-simula gli sleeve a punti-trade ----------------
print("[2] ri-simulazione sleeve a punti-trade (parity-check + versione corretta)...")
raw: dict[str, tuple] = {}
d = dip_market_gated("BTC", market_n=0, return_equity=True)
raw["DIP01_BTC"] = (d["eq_ts"], d["eq_v"])
for a, b_, p in PAIR_DEFS:
r = pairs_sim(a, b_, **p)
raw[f"PR_{a}{b_}"] = (r["eq_ts"], r["eq_v"])
r15 = pairs_sim_flat("ETH", "BTC", tf="15m", n=66, z_in=1.674, z_exit=1.0,
max_bars=35, flat_skip=True, pos=0.075)
raw["PR_ETHBTC_15M"] = (r15["eq_ts"], r15["eq_v"])
t = tsmom_sim()
raw["TSM01"] = (t["eq_ts"], t["eq_v"])
x = xsec_sim()
raw["XS01"] = (x["eq_ts"], x["eq_v"])
fixed: dict[str, pd.Series] = {}
print(f"\n {'sleeve':<16s}{'parity(max|diff|)':>18s}{'1o trade in IDX':>17s}{'ret testa perso%':>18s}")
for k, (ts, v) in raw.items():
bug = _norm(daily_equity_buggy(ts, v, IDX))
par = float((bug - base[k]).abs().max())
fixed[k] = _norm(daily_equity_fixed(ts, v, IDX))
first, lost = head_info(ts, v, IDX)
flag = "" if par < 1e-9 else " <-- PARITY FAIL"
print(f" {k:<16s}{par:>18.2e}{str(first):>17s}{lost:>+18.3f}{flag}")
# TR01/ROT02: passano da _daily_equity ma con punti per-barra (densi) ->
# ricalcolo con monkeypatch RUNTIME della funzione (nessun file toccato).
orig_de = hi2._daily_equity
try:
hi2._daily_equity = daily_equity_fixed
tr_f = _norm(hi2._tr_basket_daily(["BNB", "BTC", "DOGE", "SOL", "XRP"], IDX))
rot_f = _norm(hi2._rot_daily_equity(IDX))
finally:
hi2._daily_equity = orig_de
for k, sf in (("TR01_basket", tr_f), ("ROT02_rot", rot_f)):
diff = float((sf - base[k]).abs().max())
print(f" {k:<16s}{'(denso)':>18s}{'':>17s}{diff:>18.2e} (diff fixed-vs-base: atteso ~0)")
fixed[k] = sf
# ---------------- [3] metriche per sleeve: attuale vs corretto ----------------
print("\n" + "=" * 110)
print(" (3) SLEEVE a punti-trade — FULL e OOS, attuale(bfill) -> corretto(carry-forward)")
print("=" * 110)
rows_oos_delta = {}
for k in fixed:
bf, bo = m2(base[k])
ff, fo = m2(fixed[k])
print(fmt_pair(f"{k} FULL", bf, ff))
print(fmt_pair(f"{k} OOS ", bo, fo))
rows_oos_delta[k] = (ff["sharpe"] - bf["sharpe"], ff["dd"] - bf["dd"],
fo["sharpe"] - bo["sharpe"], fo["dd"] - bo["dd"])
# ---------------- [4] PORT06: attuale vs corretto ----------------
print("\n" + "=" * 110)
print(" (4) PORT06 (cap PAIRS 0.33 + SHAPE 0.0588) — attuale vs corretto")
print("=" * 110)
p = PORTFOLIOS["PORT06"]
def port_m(members):
ids = p.sleeve_ids
dr = pd.DataFrame({i: members[i].pct_change().fillna(0.0) for i in ids})
w = W.weight_vector(p.weighting, ids, dr, weights=p.weights,
caps=p.caps, clusters=p.clusters, lookback=p.vol_lookback)
drp = port_returns({i: members[i] for i in ids}, w)
return metrics(drp), metrics(drp, lo=SPLIT)
members_fix = {**base, **fixed}
bf, bo = port_m(base)
ff, fo = port_m(members_fix)
print(fmt_pair("PORT06 FULL", bf, ff))
print(fmt_pair("PORT06 OOS ", bo, fo))
# ---------------- [5] verdetto ----------------
print("\n" + "=" * 110)
print(" (5) VERDETTO (soglie materialita': >0.1 Sharpe o >0.5pp DD su PORT06 OOS)")
print("=" * 110)
d_sh_oos = abs(fo["sharpe"] - bo["sharpe"])
d_dd_oos = abs(fo["dd"] - bo["dd"])
d_sh_full = abs(ff["sharpe"] - bf["sharpe"])
d_dd_full = abs(ff["dd"] - bf["dd"])
materiale = d_sh_oos > 0.1 or d_dd_oos > 0.5
print(f" PORT06 OOS : dSharpe {fo['sharpe']-bo['sharpe']:+.4f} dDD {fo['dd']-bo['dd']:+.4f}pp"
f" -> {'MATERIALE' if materiale else 'NON materiale'}")
print(f" PORT06 FULL: dSharpe {ff['sharpe']-bf['sharpe']:+.4f} dDD {ff['dd']-bf['dd']:+.4f}pp")
worst = sorted(rows_oos_delta.items(), key=lambda kv: -abs(kv[1][0]) - abs(kv[1][1]) / 10)
print(" Sleeve piu' toccati (dSharpe FULL, dDD FULL, dSharpe OOS, dDD OOS):")
for k, (ds, dd_, dso, ddo) in worst[:5]:
print(f" {k:<16s} FULL {ds:+.3f} / {dd_:+.3f}pp OOS {dso:+.3f} / {ddo:+.3f}pp")
print("\n Nota strutturale: l'OOS canonico e' uno slice a SPLIT della stessa serie daily;")
print(" la distorsione bfill vive solo in testa (prima del primo trade in IDX) -> se il")
print(" primo trade in-finestra precede lo SPLIT, l'OOS e' INVARIATO per costruzione.")
if __name__ == "__main__":
main()
+227
View File
@@ -0,0 +1,227 @@
"""GATE DIP01 + PORT06: estendere EXIT-16 (close-confirm SL) a DIP01 (sweep punto 9).
DIP01 e' l'unico sleeve BTC con esecuzione REALE round-trip, e gira ancora col
branch SL intrabar wick-sensitive. EXIT-16 e' stato validato SULLE FADE: estenderlo
a una strategia honest richiede la validazione sul grid proprio di DIP01, con
engine GAP-AWARE (lezione exit-lab: l'engine canonico filla gli stop "al livello"
anche su gap-through -> bias PRO stop intrabar stretti; il confronto onesto filla
lo SL a worse(livello, open)).
Protocollo:
[1] parita': replay engine 'orig' (fill al livello) == equity canonica DIP01_BTC
[2] grid 3x3x2 (z_in x sl_atr x max_bars) su BTC (deployato) ed ETH (robustezza):
orig GAP-AWARE vs EXIT-16(buf 0.5), ret/DD/Sharpe train (pre-OOS) e OOS
[3] plateau buffer {0.4, 0.5, 0.75, 1.0} sulla cella canonica
[4] gate PORT06: DIP01_BTC exit16 innestato nel canonico, pesi cap
-> PROMOSSO se OOS Sharpe non peggiora E FULL/DD non degradano materialmente.
NB hurst_max NON valutato: il gate trendmax (2026-06-07) ha mostrato che il
loss-guard Hurst e' ridondante-dannoso POST-EXIT-16 (stesso regime target).
uv run python scripts/analysis/dip01_exit16_impact.py
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from src.data.downloader import load_data
from scripts.analysis.strategy_research import atr
from scripts.analysis.combine_portfolio import _norm, IDX, metrics, SPLIT, OOS_DATE
from scripts.analysis._port06_gate_common import port_metrics
from scripts.portfolios._defs import PORTFOLIOS
FEE_RT, LEV, POS, INIT = 0.001, 3.0, 0.15, 1000.0
BUFFER = 0.5
GRID_Z = (2.0, 2.5, 3.0)
GRID_SL = (2.0, 2.5, 3.0)
GRID_MB = (24, 48)
CANON = dict(n=50, z_in=2.5, sl_atr=2.5, max_bars=24)
def dip_entries(df, n=50, z_in=2.5, sl_atr=2.5, max_bars=24):
"""Entries DIP01 == honest_improve2.dip_market_gated (market_n=0): crossing
di z sotto -z_in. Ritorna [{i, tp, sl, mb}] (long-only)."""
c = df["close"].values
ma = pd.Series(c).rolling(n).mean().values
sd = pd.Series(c).rolling(n).std().values
a = atr(df, 14)
z = (c - ma) / np.where(sd == 0, np.nan, sd)
out = []
for i in range(n + 14, len(c)):
if np.isnan(z[i]) or np.isnan(a[i]):
continue
if z[i] <= -z_in and z[i - 1] > -z_in:
out.append({"i": i, "tp": ma[i], "sl": c[i] - sl_atr * a[i],
"mb": max_bars})
return out
def dip_trades(ents, df, mode, buffer=BUFFER):
"""Engine exit DIP01 (long-only), non-overlap come il canonico.
mode="orig" : SL intrabar fill AL LIVELLO (== canonico, per la parita')
mode="orig_gap" : SL intrabar fill a worse(livello, open[j]) — gap-aware
mode="exit16" : SL intrabar OFF; TP intrabar al livello (priorita' nel bar);
stop solo se close[j] < sl - buffer*ATR14[j], fill a close[j]
"""
h, l, c, o = df["high"].values, df["low"].values, df["close"].values, df["open"].values
n = len(c)
a = atr(df, 14)
fee = FEE_RT * LEV
out = []
last = -1
for e in ents:
i = e["i"]
if i <= last or i + 1 >= n:
continue
tp, sl, mb = e["tp"], e["sl"], e["mb"]
exit_p = c[min(i + mb, n - 1)]
j = min(i + mb, n - 1)
for k in range(1, mb + 1):
j = i + k
if j >= n:
j = n - 1
exit_p = c[j]
break
if mode in ("orig", "orig_gap"):
if l[j] <= sl:
exit_p = sl if mode == "orig" else min(sl, o[j])
break
if h[j] >= tp:
exit_p = tp
break
if k == mb:
exit_p = c[j]
else: # exit16
if h[j] >= tp:
exit_p = tp
break
aj = a[j] if np.isfinite(a[j]) else 0.0
if c[j] < sl - buffer * aj:
exit_p = c[j]
break
if k == mb:
exit_p = c[j]
ret = (exit_p - c[i]) / c[i] * LEV - fee
out.append((i, j, ret))
last = j
return out
def daily_equity(df, trades):
"""Equity giornaliera con la convenzione CANONICA honest (_daily_equity su punti
trade-exit). NB: la serie a punti-trade reindexata su IDX ancora il primo valore
al PRIMO trade dentro IDX (bfill), non al capitale portato avanti da prima —
convenzione discutibile ma e' quella di build_everything: per la parita' (e il
confronto col PORT06 canonico) va replicata esattamente."""
from scripts.analysis.honest_improve2 import _daily_equity
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
cap = INIT
eq_ts, eq_v = [], []
for i, j, ret in sorted(trades, key=lambda t: t[1]):
cap = max(cap + cap * POS * ret, 10.0)
eq_ts.append(ts.iloc[j])
eq_v.append(cap)
return _norm(_daily_equity(eq_ts, eq_v, IDX))
def cell_metrics(eq):
dr = eq.pct_change().fillna(0.0)
return metrics(dr), metrics(dr, lo=SPLIT)
def main():
p = PORTFOLIOS["PORT06"]
print("=" * 104)
print(" GATE DIP01 EXIT-16 (close-confirm 0.5 ATR) — grid gap-aware + PORT06")
print(f" OOS da {OOS_DATE} | fee {FEE_RT*100:.2f}%RT x lev{LEV:.0f} | pos {POS}")
print("=" * 104)
print("\n[1] build_everything() canonico (cache)...")
from src.portfolio.sleeves import all_sleeve_equities
eq_base = dict(all_sleeve_equities())
dfs = {a: load_data(a, "1h") for a in ("BTC", "ETH")}
# --- parita' ---
ents = dip_entries(dfs["BTC"], **CANON)
rep = daily_equity(dfs["BTC"], dip_trades(ents, dfs["BTC"], "orig"))
base = eq_base["DIP01_BTC"]
corr = base.pct_change().fillna(0).corr(rep.pct_change().fillna(0))
rb = (base.iloc[-1] / base.iloc[0] - 1) * 100
rr = (rep.iloc[-1] / rep.iloc[0] - 1) * 100
print(f"\n[1] PARITA' orig vs canonico: corr={corr:.5f} ret {rb:+.0f}% vs {rr:+.0f}%")
if not (corr > 0.999 and abs(rr - rb) <= max(1.0, abs(rb) * 0.01)):
print(" >>> PARITA' FALLITA: STOP.")
return
# --- [2] grid gap-aware ---
for asset in ("BTC", "ETH"):
df = dfs[asset]
print(f"\n[2] GRID {asset} — orig GAP-AWARE vs EXIT-16 (train | OOS: ret% e Sharpe)")
print(f" {'cella':<16s}{'tr retO':>9s}{'tr retE':>9s} {'oos retO':>9s}{'oos retE':>9s}"
f" {'oos ShO':>8s}{'oos ShE':>8s} {'ddO':>6s}{'ddE':>6s} esito")
wins_tr = wins_oos = cells = 0
for z in GRID_Z:
for slm in GRID_SL:
for mb in GRID_MB:
ents = dip_entries(df, n=50, z_in=z, sl_atr=slm, max_bars=mb)
eo = daily_equity(df, dip_trades(ents, df, "orig_gap"))
ee = daily_equity(df, dip_trades(ents, df, "exit16"))
fo, oo = cell_metrics(eo)
fe, oe = cell_metrics(ee)
tr_o = fo["ret"] - oo["ret"]; tr_e = fe["ret"] - oe["ret"] # ~train (full-oos, approssimato su ret composti: usare segni)
# train ret esatto: equity al SPLIT
tr_o = (eo.iloc[SPLIT] / eo.iloc[0] - 1) * 100
tr_e = (ee.iloc[SPLIT] / ee.iloc[0] - 1) * 100
cells += 1
w_tr = tr_e >= tr_o
w_oos = oe["ret"] >= oo["ret"]
wins_tr += w_tr
wins_oos += w_oos
tag = ("OK" if (w_tr and w_oos) else "tr-" if w_oos else "oos-" if w_tr else "KO")
print(f" z{z} sl{slm} mb{mb:<3d}{tr_o:>9.0f}{tr_e:>9.0f} "
f"{oo['ret']:>9.0f}{oe['ret']:>9.0f} {oo['sharpe']:>8.2f}{oe['sharpe']:>8.2f}"
f" {fo['dd']:>6.1f}{fe['dd']:>6.1f} {tag}")
print(f" -> EXIT-16 >= orig-gap: train {wins_tr}/{cells}, OOS {wins_oos}/{cells}")
# --- [3] plateau buffer (BTC, cella canonica) ---
print("\n[3] Plateau buffer EXIT-16 (BTC, cella canonica):")
ents = dip_entries(dfs["BTC"], **CANON)
for buf in (0.4, 0.5, 0.75, 1.0):
ee = daily_equity(dfs["BTC"], dip_trades(ents, dfs["BTC"], "exit16", buffer=buf))
fe, oe = cell_metrics(ee)
print(f" buf {buf:<5}FULL ret {fe['ret']:>+7.0f}% DD {fe['dd']:>5.1f} Sh {fe['sharpe']:>5.2f}"
f" | OOS ret {oe['ret']:>+6.0f}% DD {oe['dd']:>5.1f} Sh {oe['sharpe']:>5.2f}")
# --- [4] gate PORT06 ---
ee = daily_equity(dfs["BTC"], dip_trades(ents, dfs["BTC"], "exit16"))
members_b = dict(eq_base)
members_e = dict(eq_base)
members_e["DIP01_BTC"] = ee
f_b, o_b = port_metrics(members_b, p)
f_e, o_e = port_metrics(members_e, p)
print("\n" + "=" * 104)
print(f" [4] PORT06 (pesi cap {p.caps}) — DIP01_BTC orig vs EXIT-16")
print("=" * 104)
print(f" {'variante':<10s}{'FULL Sh':>9s}{'FULL DD%':>10s}{'CAGR':>6s} | {'OOS Sh':>7s}{'OOS DD%':>8s}{'CAGR':>6s}")
for nm, (f, o) in (("BASE", (f_b, o_b)), ("EXIT-16", (f_e, o_e))):
print(f" {nm:<10s}{f['sharpe']:>9.2f}{f['dd']:>10.2f}{f['cagr']:>5.0f}% | "
f"{o['sharpe']:>7.2f}{o['dd']:>8.2f}{o['cagr']:>5.0f}%")
oos_ok = o_e["sharpe"] >= o_b["sharpe"] - 0.02 and o_e["dd"] <= o_b["dd"] + 0.20
full_ok = f_e["sharpe"] >= f_b["sharpe"] - 0.02 and f_e["dd"] <= f_b["dd"] + 0.20
promoted = oos_ok and full_ok
print(f"\n GATE: OOS {'OK' if oos_ok else 'KO'} | FULL {'OK' if full_ok else 'KO'}")
print(" VERDETTO: " + (">>> PROMOSSO <<<" if promoted else ">>> BOCCIATO <<<"))
if __name__ == "__main__":
main()
@@ -0,0 +1,156 @@
"""GATE PORT06 del candidato index_comp_disp W=168 (ricerca dispersion 2026-06-08).
Edge confermato avversarialmente: fade della componente idiosincratica di BTC verso
l'indice EW, gated da alta dispersione. Config: rel_len=12, z_win=336, z_thr=1.5,
disp_168 >= quantile rolling 0.7 (win 720), TP=1.0*ATR14, SL=1.5*ATR14, max_bars=24.
Domanda del gate (lezione FR01: robusto != migliora-il-portafoglio):
1) correlazione daily col MASTER e con le fade BTC esistenti (e' un diversificatore?)
2) PORT06 BASE (17 sleeve) vs +DISP (18 sleeve) con pesi cap: DeltaSharpe/DeltaDD FULL e OOS.
PROMOSSO solo se decorrela E migliora (o non degrada) l'OOS.
uv run python scripts/analysis/dispersion_edges/gate_index_comp_disp.py
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.dispersion_lab import features, align_to
from scripts.analysis.explore_lab import get_df, atr
from scripts.analysis.combine_portfolio import _norm, IDX, port_returns, metrics, SPLIT, OOS_DATE
from scripts.analysis.honest_improve2 import _daily_equity
from scripts.portfolios._defs import PORTFOLIOS
from src.portfolio import weighting as W
FEE_RT, LEV, POS, INIT = 0.001, 3.0, 0.15, 1000.0
CFG = dict(rel_len=12, z_win=336, z_thr=1.5, disp_q=0.7, disp_q_win=720,
tp_atr=1.0, sl_atr=1.5, max_bars=24)
def _last_rank(x):
if x.shape[0] < 2:
return np.nan
return float((x[:-1] < x[-1]).mean())
def build_trades(asset="BTC"):
"""Entries CAUSALI + exit intrabar (TP/SL/max_bars) -> [(i, j, ret_netto)]."""
df = get_df(asset, "1h")
F = features()
fa = align_to(F, df)
c, h, l = df["close"].values, df["high"].values, df["low"].values
n = len(c)
a14 = atr(df, 14)
rel = fa[f"rel_{asset}"].values.astype(float)
disp = fa["disp_168"].values.astype(float)
# somma rolling rel su rel_len, z-score causale (mean/std rolling z_win shift 1)
rs = pd.Series(rel).rolling(CFG["rel_len"]).sum()
rmean = rs.rolling(CFG["z_win"]).mean().shift(1)
rstd = rs.rolling(CFG["z_win"]).std().shift(1)
z = ((rs - rmean) / rstd.replace(0, np.nan)).values
dpct = pd.Series(disp).rolling(CFG["disp_q_win"]).apply(_last_rank, raw=True).values
fee = FEE_RT * LEV
out = []
last = -1
for i in range(n - 1):
if i <= last or not np.isfinite(z[i]) or not np.isfinite(dpct[i]):
continue
if dpct[i] < CFG["disp_q"] or abs(z[i]) < CFG["z_thr"]:
continue
ai = a14[i]
if not np.isfinite(ai) or ai <= 0:
continue
d = -1 if z[i] > 0 else 1
tp = c[i] + d * CFG["tp_atr"] * ai
sl = c[i] - d * CFG["sl_atr"] * ai
mb = CFG["max_bars"]
j = min(i + mb, n - 1)
exit_p = c[j]
for k in range(1, mb + 1):
j = i + k
if j >= n:
j = n - 1; exit_p = c[j]; break
if d == 1:
if l[j] <= sl: exit_p = sl; break
if h[j] >= tp: exit_p = tp; break
else:
if h[j] >= sl: exit_p = sl; break
if l[j] <= tp: exit_p = tp; break
if k == mb: exit_p = c[j]
out.append((i, j, (exit_p - c[i]) / c[i] * d * LEV - fee))
last = j
return df, out
def daily_equity(df, trades):
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
cap = INIT; eq_ts, eq_v = [], []
for i, j, ret in sorted(trades, key=lambda t: t[1]):
cap = max(cap + cap * POS * ret, 10.0)
eq_ts.append(ts.iloc[j]); eq_v.append(cap)
return _norm(_daily_equity(eq_ts, eq_v, IDX))
def pmetrics(members, p, extra=None):
ids = list(p.sleeve_ids) + ([extra] if extra else [])
dr = pd.DataFrame({i: members[i].pct_change().fillna(0.0) for i in ids})
if extra:
caps = dict(p.caps); caps["DISP"] = caps.get("DISP", None)
w = W.weight_vector(p.weighting, ids, dr, weights=p.weights,
caps=p.caps, clusters={**{i:(p.clusters or {}).get(i,i) for i in p.sleeve_ids},
**({extra:"disp"} if extra else {})},
lookback=p.vol_lookback)
drp = port_returns({i: members[i] for i in ids}, w)
return metrics(drp), metrics(drp, lo=SPLIT)
def main():
p = PORTFOLIOS["PORT06"]
print("=" * 100)
print(" GATE PORT06 — candidato index_comp_disp W=168 (BTC) | famiglia DISP nuova")
print(f" config {CFG} | OOS da {OOS_DATE}")
print("=" * 100)
from src.portfolio.sleeves import all_sleeve_equities
eq_base = dict(all_sleeve_equities())
df, trades = build_trades("BTC")
disp_eq = daily_equity(df, trades)
fr = (disp_eq.iloc[-1] / disp_eq.iloc[0] - 1) * 100
o = disp_eq.iloc[SPLIT:]; ofr = (o.iloc[-1] / o.iloc[0] - 1) * 100
print(f"\n[1] candidato standalone: {len(trades)} trade | FULL {fr:+.0f}% | OOS {ofr:+.0f}%")
# correlazione daily col MASTER e con le fade BTC
dr_cand = disp_eq.pct_change().fillna(0.0)
print("\n[2] correlazione daily col candidato (decorrela?):")
for sid in ["MR01_BTC", "MR02_BTC", "MR07_BTC", "DIP01_BTC"]:
corr = dr_cand.corr(eq_base[sid].pct_change().fillna(0.0))
print(f" {sid:<12} corr {corr:+.3f}")
master_dr = pd.DataFrame({i: eq_base[i].pct_change().fillna(0.0) for i in p.sleeve_ids}).mean(axis=1)
print(f" {'MASTER(EW)':<12} corr {dr_cand.corr(master_dr):+.3f}")
# PORT06 base vs +DISP
f_b, o_b = pmetrics(eq_base, p)
members = dict(eq_base); members["DISP_BTC"] = disp_eq
f_e, o_e = pmetrics(members, p, extra="DISP_BTC")
print("\n[3] PORT06 BASE (17) vs +DISP (18):")
print(f" {'':<10}{'FULL Sh':>9}{'FULL DD%':>10}{'OOS Sh':>9}{'OOS DD%':>9}")
print(f" {'BASE':<10}{f_b['sharpe']:>9.2f}{f_b['dd']:>10.2f}{o_b['sharpe']:>9.2f}{o_b['dd']:>9.2f}")
print(f" {'+DISP':<10}{f_e['sharpe']:>9.2f}{f_e['dd']:>10.2f}{o_e['sharpe']:>9.2f}{o_e['dd']:>9.2f}")
print(f" {'DELTA':<10}{f_e['sharpe']-f_b['sharpe']:>+9.2f}{f_e['dd']-f_b['dd']:>+10.2f}"
f"{o_e['sharpe']-o_b['sharpe']:>+9.2f}{o_e['dd']-o_b['dd']:>+9.2f}")
promoted = (o_e['sharpe'] >= o_b['sharpe'] - 0.02 and o_e['dd'] <= o_b['dd'] + 0.20
and f_e['sharpe'] >= f_b['sharpe'] - 0.02)
print("\n VERDETTO: " + (">>> PROMOSSO <<<" if promoted else ">>> BOCCIATO (diluisce, come FR01) <<<"))
if __name__ == "__main__":
main()
@@ -0,0 +1,191 @@
"""FAMIGLIA index_comp_disp (W=24) — dispersion trading REALIZZATO.
Idea: l'indice EW vs le componenti. Quando la dispersione cross-sectional rolling
(disp_24) e' ALTA, le componenti idiosincratiche estreme (rel_A = ret_A - ret_idx)
tendono a RIENTRARE verso l'indice -> fade della componente idiosincratica estrema:
- se rel_A e' molto positivo (A ha sovraperformato l'indice oltre soglia) -> SHORT A
- se rel_A e' molto negativo (A ha sottoperformato l'indice oltre soglia) -> LONG A
condizionato a disp_24 sopra una soglia (regime di alta dispersione).
CAUSALE: la decisione a close[i] usa SOLO feature note a i:
- rel_A[i] = log-ret di A meno log-ret indice nella barra [i-1->i] (nota a close[i])
- disp_24[i] = media rolling 24 della disp cross-sectional fino a i (nota a close[i])
Ingresso eseguibile a close[i]. Niente uso di i+1 nella decisione.
Per rendere le soglie comparabili fra asset/tempo, rel_A si normalizza con la sua
deviazione standard rolling CAUSALE (rolling 168h, shiftata di 1 per non includere i).
disp_24 si normalizza col suo quantile rolling causale (percentile rolling).
Exit: time-stop max_bars (con TP/SL ATR opzionali). Il fade verso l'indice e' un
ritorno alla media -> orizzonte breve.
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.analysis.dispersion_lab import features, align_to, UNIVERSE # noqa: E402
from scripts.analysis.explore_lab import get_df, evaluate, robust, atr # noqa: E402
W = 24 # finestra famiglia
def _last_rank(x: np.ndarray) -> float:
"""Frazione dei valori (esclusi l'ultimo) strettamente < dell'ultimo. Causale:
l'ultimo elemento e' la barra i, confrontata coi 719 valori precedenti."""
if x.shape[0] < 2:
return np.nan
return float((x[:-1] < x[-1]).mean())
def _causal_signals(asset: str, df: pd.DataFrame, fa: pd.DataFrame):
"""Precalcola (una volta per asset) rel_z, disp_pctl, atr — feature CAUSALI."""
a14 = atr(df, 14)
rel = fa[f"rel_{asset}"].values.astype(float)
disp = fa["disp_24"].values.astype(float)
# z-score CAUSALE di rel: media/std rolling 168h SHIFTATA di 1 (solo barre <= i-1)
rel_s = pd.Series(rel)
rmean = rel_s.rolling(168).mean().shift(1)
rstd = rel_s.rolling(168).std().shift(1)
rel_z = ((rel_s - rmean) / rstd.replace(0, np.nan)).values
# percentile rolling CAUSALE di disp_24 (rank di disp[i] vs i 720 valori fino a i).
# vettoriale via rank rolling: pos dell'ultimo elemento / (win-1).
win = 720
dr = pd.Series(disp).rolling(win).apply(_last_rank, raw=True)
disp_pctl = dr.values
return rel_z, disp_pctl, a14
def build_entries(asset: str, df: pd.DataFrame, fa: pd.DataFrame,
rel_z_thr: float, disp_pctl_thr: float,
max_bars: int, tp_atr: float | None, sl_atr: float | None,
precomp=None) -> list[dict]:
"""Costruisce entries CAUSALI per il fade della componente idiosincratica."""
n = len(df)
c = df["close"].values
if precomp is None:
rel_z, disp_pctl, a14 = _causal_signals(asset, df, fa)
else:
rel_z, disp_pctl, a14 = precomp
entries: list[dict] = []
for i in range(n - 1):
z = rel_z[i]
dp = disp_pctl[i]
if not np.isfinite(z) or not np.isfinite(dp):
continue
if dp < disp_pctl_thr: # solo regime di alta dispersione
continue
if abs(z) < rel_z_thr: # solo componente idio estrema
continue
d = -1 if z > 0 else +1 # FADE: rel alto -> short A; rel basso -> long A
a = a14[i]
if not np.isfinite(a) or a <= 0:
tp = sl = None
else:
tp = c[i] + d * tp_atr * a if tp_atr else None
sl = c[i] - d * sl_atr * a if sl_atr else None
entries.append({"i": i, "d": d, "max_bars": max_bars, "tp": tp, "sl": sl})
return entries
def check_no_lookahead(asset: str, df: pd.DataFrame, fa: pd.DataFrame) -> bool:
"""Perturba i PREZZI dopo un indice T e verifica che le entries con i<=T non cambino.
Qui ricostruiamo la entry-rule su una copia di df/fa col futuro alterato e confrontiamo
le entries (i, d) con i<=T."""
n = len(df)
T = int(n * 0.6)
base = build_entries(asset, df, fa, rel_z_thr=2.0, disp_pctl_thr=0.6,
max_bars=12, tp_atr=None, sl_atr=None)
# perturba i prezzi dopo T: alza del 50% close/high/low/open
df2 = df.copy()
for col in ("open", "high", "low", "close"):
df2.loc[df2.index > T, col] = df2.loc[df2.index > T, col] * 1.5
# perturba anche le feature riferite a barre > T (rel_<asset>, disp_24)
fa2 = fa.copy()
for col in (f"rel_{asset}", "disp_24"):
fa2.loc[fa2.index > T, col] = fa2.loc[fa2.index > T, col] * 1.5
pert = build_entries(asset, df2, fa2, rel_z_thr=2.0, disp_pctl_thr=0.6,
max_bars=12, tp_atr=None, sl_atr=None)
base_le = {(e["i"], e["d"]) for e in base if e["i"] <= T}
pert_le = {(e["i"], e["d"]) for e in pert if e["i"] <= T}
ok = base_le == pert_le
print(f"[no-look-ahead] entries con i<=T={T} invarianti al futuro: "
f"{'OK' if ok else 'VIOLATO'} (base={len(base_le)} pert={len(pert_le)})")
if not ok:
diff = (base_le ^ pert_le)
print(f" differenze: {sorted(diff)[:10]}")
return ok
def main():
F = features()
print(f"feature caricate: {F.shape[0]} barre")
# asset single-asset da esplorare (i piu' liquidi + qualche alt)
assets = ["BTC", "ETH", "SOL", "ADA", "BNB", "DOGE", "LTC", "XRP"]
# griglia piccola di soglie
rel_z_grid = [1.5, 2.0, 2.5]
disp_pctl_grid = [0.5, 0.7]
mb_grid = [6, 12, 24]
# exit: prima senza tp/sl (puro time-stop), poi con un TP/SL ATR moderato
exit_grid = [
(None, None),
(1.5, 2.0),
]
best = None
# no-look-ahead check una volta (su ETH)
df_eth = get_df("ETH", "1h")
fa_eth = align_to(F, df_eth)
la_ok = check_no_lookahead("ETH", df_eth, fa_eth)
print()
for asset in assets:
df = get_df(asset, "1h")
fa = align_to(F, df)
precomp = _causal_signals(asset, df, fa) # una volta per asset (costoso)
for rz in rel_z_grid:
for dp in disp_pctl_grid:
for mb in mb_grid:
for (tp_a, sl_a) in exit_grid:
ents = build_entries(asset, df, fa, rz, dp, mb, tp_a, sl_a,
precomp=precomp)
if len(ents) < 30:
continue
tag = f"{asset} rz{rz} dp{dp} mb{mb} tp{tp_a} sl{sl_a}"
res = evaluate(tag, ents, df)
rb = robust(res)
# criterio "migliore": OOS ret, poi sharpe; preferisci robuste
score = (rb, res["oos"]["ret"], res["full"]["sharpe"])
if best is None or score > best[0]:
best = (score, tag, res, rb,
dict(asset=asset, rz=rz, dp=dp, mb=mb, tp=tp_a, sl=sl_a))
print("\n=== MIGLIORE ===")
if best is None:
print("nessuna cella con abbastanza trade")
return
score, tag, res, rb, cfg = best
print(f"config: {tag}")
print(f"robust={rb} lookahead_ok={la_ok}")
print(f"FULL ret={res['full']['ret']:+.0f}% OOS ret={res['oos']['ret']:+.0f}% "
f"DD={res['full']['dd']:.0f}% Sharpe={res['full']['sharpe']:.2f}")
print(f"fee0.2% OOS={res['sweep_oos'][0.002]:+.0f}% anniPos={res['pos_yrs']}/{res['n_yrs']}")
if __name__ == "__main__":
main()

Some files were not shown because too many files have changed in this diff Show More