From 719509fb2f27e9ded09b1157d1570497a3dc5315 Mon Sep 17 00:00:00 2001 From: Adriano Dal Pastro Date: Wed, 3 Jun 2026 10:20:08 +0000 Subject: [PATCH] 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) --- CLAUDE.md | 9 ++++++++- src/live/strategy_worker.py | 7 +++++++ src/live/telegram_notifier.py | 3 +++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index f55ca8f..69ad07e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -33,7 +33,11 @@ src/live/ → paper trading live multi-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) + 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 src/portfolio/ → portafogli di prima classe (capitale-pool, backtest+live) @@ -76,6 +80,8 @@ uv run python -m src.live.multi_runner # paper trading live multi 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/ ./scripts/deploy.sh [patch|minor|major] # DEPLOY: bump versione + commit + rebuild Docker uv run pytest # test @@ -309,6 +315,7 @@ queste fade, ma va confermato col paper trader live prima di rischiare capitale - **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. - **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 diff --git a/src/live/strategy_worker.py b/src/live/strategy_worker.py index 5322fcf..ae01f00 100644 --- a/src/live/strategy_worker.py +++ b/src/live/strategy_worker.py @@ -55,6 +55,7 @@ class StrategyWorker: self.real_entry_notional = 0.0 # USD effettivi esposti all'entrata self.real_order_id = "" self.real_trades = 0 + self.real_first_notified = False # alert Telegram "esecuzione viva" una tantum self.worker_id = f"{strategy.name}__{asset}__{tf}" self.work_dir = data_dir / self.worker_id @@ -116,6 +117,7 @@ class StrategyWorker: self.real_entry_notional = state.get("real_entry_notional", 0.0) self.real_order_id = state.get("real_order_id", "") self.real_trades = state.get("real_trades", 0) + self.real_first_notified = state.get("real_first_notified", False) self._log("RESUME", {"capital": round(self.capital, 2), "total_trades": self.total_trades, @@ -147,6 +149,7 @@ class StrategyWorker: "real_entry_notional": self.real_entry_notional, "real_order_id": self.real_order_id, "real_trades": self.real_trades, + "real_first_notified": self.real_first_notified, "last_update": datetime.now(timezone.utc).isoformat(), } with open(self.status_path, "w") as f: @@ -228,8 +231,12 @@ class StrategyWorker: if linear else fill.amount) self.real_order_id = fill.order_id or "" self._log("REAL_OPEN", data) + if not self.real_first_notified: # conferma una-tantum: l'esecuzione reale e' viva + self._notify("REAL_EXEC_LIVE", data) + self.real_first_notified = True else: self._log("REAL_OPEN_FAIL", {**data, "note": fill.notes}) + self._notify("REAL_OPEN_FAIL", {**data, "note": fill.notes}) def _real_close(self, sim_exit: float, reason: str, sim_pnl: float): """Chiusura REALE (reduce-only della quota worker) + confronto col sim.""" diff --git a/src/live/telegram_notifier.py b/src/live/telegram_notifier.py index efa247b..fb2b2bb 100644 --- a/src/live/telegram_notifier.py +++ b/src/live/telegram_notifier.py @@ -14,6 +14,9 @@ CHAT_ID = os.environ.get("TELEGRAM_CHAT_ID", "") NOTIFY_EVENTS = { "SIGNAL", "OPENED", "CLOSED", "OPEN_FAILED", "CLOSE_FAILED", "ERROR", "STARTUP", "SHUTDOWN", "TRAINING_FAILED", + # esecuzione REALE (shadow su Deribit testnet) + "REAL_EXEC_LIVE", # primo ordine reale verificato di un worker (conferma "e' vivo") + "REAL_OPEN_FAIL", # un'apertura reale NON si e' verificata (problema da guardare) }