14522262e6
Reset del progetto su fondamenta verificate dopo la scoperta che l'intera libreria "validata OOS" era artefatto di feed contaminato (print fantasma del feed Cerbero TESTNET + storico Binance/USDT). - Storico ricostruito da Deribit MAINNET (ccxt pubblico, tokenless) e CERTIFICATO (certify_feed.py): BTC/ETH puliti su TUTTA la storia (mediana 2-6 bps vs Coinbase USD), integrita' OHLC + coerenza resample (maxΔ 0.00) + cross-venue OK. Alt esclusi (illiquidi/divergenti: LTC/DOGE 50-82% barre flat; XRP/BNB non certificabili). - Verdetto sul feed pulito: FADE / PAIRS / XS01 / TSM01 morti (ogni portafoglio Sharpe -2.3..-3.0, DD ~40%); solo SH01 e frammenti HONEST con segnale residuo, da ri-validare in isolamento. - Cleanup "restart pulito": strategie, stack live (src/live, src/portfolio, runner/executor, yml, docker), ~100 script ricerca/gate, waste/games/ portfolios, dati non certificati + cache e 60+ diari -> archiviati in Old/ (preservati, non cancellati). Diario consolidato in un unico documento. - Skeleton ricerca tenuto: Strategy ABC + indicatori + src/fractal + src/backtest/engine + load_data; tool dati certificati (rebuild_history, certify_feed, audit_feed, multi_source_check). - Universo dati ATTIVO: solo BTC/ETH (5m/15m/1h); guardrail fisico (load_data su alt -> FileNotFoundError). Esecuzione DISABILITATA, conto flat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
72 lines
3.6 KiB
Markdown
72 lines
3.6 KiB
Markdown
# 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,17–0,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: 766–1786 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.
|