chore(reset): v2.0.0 — storico certificato Deribit mainnet, ripartenza pulita

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>
This commit is contained in:
Adriano Dal Pastro
2026-06-19 15:16:03 +00:00
parent 8401a280b9
commit 14522262e6
383 changed files with 1971 additions and 779 deletions
+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.