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>
4.6 KiB
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=Truemapnlnegativo. Due problemi distinti, entrambi risolti.
Problema 1 — la metrica win mentiva (lordo invece di netto)
In strategy_worker.py::_close_position:
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 > 0live; contatori riconciliati (MR01/DIP01 BTC 1/9).- Filtro
min_tp_frac=0.0015live 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
- Una metrica di "win" deve essere netto fee, altrimenti l'accuracy è teatro.
- Quando il TP è dentro il costo di transazione, la trade è persa in partenza: meglio non prenderla che ritoccare il TP.
- Per i worker live in Docker: rebuild, non restart. Il restart ricarica solo lo stato dal volume, non il codice.