Files
PythagorasGoal/Old/docs/diary/2026-06-01-tp-min-edge.md
Adriano Dal Pastro 14522262e6 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>
2026-06-19 15:20:59 +00:00

4.6 KiB
Raw Permalink Blame History

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:

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.