From 6f86c644bf45e0b5bb31401c9e6310a3dab526b6 Mon Sep 17 00:00:00 2001 From: Adriano Dal Pastro Date: Thu, 4 Jun 2026 21:58:04 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20CLAUDE.md=20+=20README=20aggiornati=20?= =?UTF-8?q?=E2=80=94=20PORT06=20canonici=20correnti=20(6.47/8.82,=20con=20?= =?UTF-8?q?EXIT-16=207.84/10.06),=20shadow=20reale=207=20sleeve=20(DIP01?= =?UTF-8?q?=20incluso),=20sezione=20esecuzione=20reale=20nel=20README,=20f?= =?UTF-8?q?amiglia=20SHAPE=20in=20tabella,=20exit=5Flab=20in=20struttura/c?= =?UTF-8?q?omandi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 11 +++++++---- README.md | 43 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index cc0d242..89d5edc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,14 +47,16 @@ src/portfolio/ → portafogli di prima classe (capitale-pool, backtest+l 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) +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) + 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) 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 @@ -83,6 +85,7 @@ uv run python scripts/analysis/smoke_portfolio.py # smoke live data lay 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 ./scripts/deploy.sh [patch|minor|major] # DEPLOY: bump versione + commit + rebuild Docker uv run pytest # test ``` @@ -326,13 +329,13 @@ queste fade, ma va confermato col paper trader live prima di rischiare capitale - 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). -- **Default `portfolios.yml`:** PORT06 (master+shape), `weighting=cap pairs 0.33`, leva 2x, ribilancio 1D. Backtest PORT06: FULL Sharpe 6.07 / OOS Sharpe 8.19, DD 4.9% full / 2.3% OOS. +- **Default `portfolios.yml`:** PORT06 (master+shape), `weighting=cap pairs 0.33`, leva 2x, ribilancio 1D. Backtest PORT06 canonico (dati al 2026-05-28): FULL Sharpe 6.47 DD 4.10% / OOS Sharpe 8.82 DD 1.30%; **con EXIT-16 close-confirm (config live attuale): FULL 7.84 / 2.60%, OOS 10.06 / 1.15%** (i vecchi 6.07/8.19 erano pre-loss-guard/pre-refresh dati). - **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 stesso ordine — differenza di convenzione capitale-unico vs media-equity). - **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 — 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], instruments:{BTC:BTC_USDC-PERPETUAL, ETH:ETH_USDC-PERPETUAL}}`; pairs/rotation/tsmom/shape/dip restano **simulati**. **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. +- **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. - **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`. - **Limite noto:** al ribilancio le posizioni APERTE restano sul loro notional (non travasate); fedele al backtest daily-rebalanced entro il turnover infragiornaliero. diff --git a/README.md b/README.md index c733eaa..041392f 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Partendo da un capitale iniziale di €1.000, raggiungere un profitto medio di > a fee zero. Dettagli e prove: `scripts/analysis/oos_validation.py`. Dopo una validazione **out-of-sample, fee-aware** di molte famiglie di strategie, -emergono quattro famiglie con edge netto reale, tutte radicate nella stessa lezione +emergono cinque famiglie con edge netto reale, tutte radicate nella stessa lezione (in cripto la **mean-reversion** funziona, la continuazione no) o nella diversificazione: | Famiglia | Meccanismo | Strategie | Profilo (netto OOS) | @@ -25,6 +25,7 @@ emergono quattro famiglie con edge netto reale, tutte radicate nella stessa lezi | **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 @@ -42,6 +43,7 @@ portafoglio equipesato il drawdown crolla sotto quello di ogni singola sleeve: | 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 @@ -154,12 +156,18 @@ Le strategie single-asset estendono `src.strategies.base.Strategy` | **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) | -Le fade applicano due filtri di regime opzionali: un **filtro trend** (`trend_max`/`ema_long`, -salta i segnali col prezzo troppo esteso rispetto alla EMA200) e un **loss-guard Hurst** +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). Più un filtro `min_tp_frac` -che scarta i micro-scalp col take-profit entro il costo delle fee. Portafogli pronti: `PORT01` +— 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, @@ -272,7 +280,7 @@ Il modulo `weighting.py` mette a disposizione cinque schemi: `equal` (default), ### 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. Risultati del backtest: Sharpe 6.07 (FULL) / 8.19 (OOS), drawdown massimo 4.9% (FULL) / 2.3% (OOS), leva 2×. +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 @@ -291,6 +299,29 @@ sono validati == backtest. > 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