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>
67 lines
3.8 KiB
Markdown
67 lines
3.8 KiB
Markdown
# 2026-06-16 — Guard TP-invertito (churn MR02_BTC 15m)
|
||
|
||
## Sintomo
|
||
Burst di **8 trade in 15 minuti** su `MR02_donchian_fade__BTC__15m` (09:00–09:15 UTC),
|
||
tutti `long`, tutti chiusi `take_profit` a `bars_held=0-1` **in perdita**, accuracy
|
||
crollata 60%→27%. TP **fisso** a 66189.25, **sotto** ogni entry (66700–66834):
|
||
|
||
```
|
||
09:00 OPEN long 66780.5 tp=66189.25 sl=64218.21 ← tp E sl ENTRAMBI sotto l'entry
|
||
09:02 CLOSE take_profit exit=66189.25 held=0 sim=-2.63 real=-0.12
|
||
... ×8 ... +7× REAL_TP_FAIL: invalid_reduce_only_order
|
||
```
|
||
|
||
I trade MR02 sani prima del cluster hanno il TP dal lato giusto (long → tp>entry:
|
||
65033>63414, 64148>63775, 66079>65717). Il cluster delle 09:00 è l'unico **invertito**.
|
||
|
||
## Causa radice — segnale da wick transitorio (NON feed congelato, NON spike d'ingresso)
|
||
BTC alle 09:00 era **davvero** ~66780 (MR01 short@66919, MR07 short@66780.5 nello stesso
|
||
istante, slippage reale 0.7–2.5 bps → feed sano). Il difetto è a monte:
|
||
|
||
1. Una barra con **wick verso il basso** (~64218 = il livello SL) fa scattare il donchian
|
||
"rottura canale basso" → segnale **LONG**, con `tp`=centro canale (66189) e `sl`=64218
|
||
calcolati **dal contesto wickato**.
|
||
2. Il wick rientra; il worker entra `long` al prezzo **recuperato** 66780 — cioè **sopra
|
||
il proprio TP=66189**.
|
||
3. L'exit intrabar (`bar_high >= tp`) è banalmente vera all'istante → chiude "take_profit"
|
||
a 66189 = **perdita garantita a held=0**. Si ripete 8× perché il livello donchian
|
||
wickato resta nel lookback.
|
||
|
||
Differenza chiave da TP_PHANTOM: lì il wick è sull'**exit** (tocco fantasma del TP);
|
||
qui il wick produce un **tp dal lato sbagliato dell'entry** → la posizione non doveva
|
||
proprio aprirsi.
|
||
|
||
## Perché i guard esistenti non l'hanno preso
|
||
- **TP_PHANTOM** richiede un limit reduce-only resting come oracolo. Qui il limit è
|
||
**respinto** (`invalid_reduce_only_order`: una sell reduce-only a 66189 sotto il mercato
|
||
è invalida) → niente `real_tp_order_id` → fail-open → l'exit passa. E il prezzo corrente
|
||
(66780) ha **realmente** superato il livello → non è un tocco fantasma.
|
||
- **Freeze-gate (v1.1.32)**: BTC non è congelato (prezzi che si muovono) → non scatta.
|
||
|
||
## Danno reale: contenuto (il real-truth ledger ha funzionato)
|
||
- Σ **reale** del cluster ≈ **−€2.29** (solo slippage+fee: il fill reale è al prezzo vero
|
||
~66700-66730, non al TP fantasma).
|
||
- Σ **sim** sarebbe stato ≈ **−€17.9** → il real-truth ledger ha evitato che ~**€15.6** di
|
||
perdita fantasma colpisse l'equity.
|
||
- Costi collaterali: 8 round-trip di fee, 7 TP reduce-only respinti, 8 disaster-SL da
|
||
piazzare/cancellare, sim accuracy/report inquinati.
|
||
- Auto-esaurito alle 09:15 (sleeve poi flat).
|
||
|
||
## Fix — guard "TP invertito" (v1.1.33, zero parametri)
|
||
`StrategyWorker._open_position`: **prima** di aprire, se il TP è già sfondato all'ingresso
|
||
(`long & tp<=entry` oppure `short & tp>=entry`) il segnale è malformato (artefatto wick)
|
||
→ **NON si apre** (niente ordine reale, niente disaster-SL). Verità d'esecuzione, non un
|
||
filtro di strategia (stessa filosofia di TP_PHANTOM). Log `INVERTED_TP_SKIP` (dedup
|
||
per-barra) + alert Telegram una-tantum per processo. Agisce **solo sul path live**: il
|
||
backtest canonico entra al close del bar del segnale (entry == base di tp/sl → mai
|
||
invertito) e resta non filtrato → il live farà meglio del backtest, come loss-guard /
|
||
trend / TP_PHANTOM.
|
||
|
||
Test: `tests/portfolio/test_inverted_tp.py` (long/short invertiti skippati, controlli
|
||
positivi long/short validi aprono, no-tp apre, dedup per-barra). Suite portfolio 146/146.
|
||
|
||
## Lezione
|
||
Cerotto testnet (il feed wicka): il fix vero è **mainnet**, dove l'arbitraggio elimina i
|
||
wick-print. Ma il guard è corretto in assoluto — un segnale con TP dal lato sbagliato
|
||
dell'entry non va mai eseguito, su qualsiasi venue.
|