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
@@ -0,0 +1,92 @@
# 2026-06-04 — Shadow execution: divergenza sim/reale misurata + TODO limit reduce-only
## Deploy v1.0.6
Fix float deployata (`74670da` + release): `_quantize_step` con `Decimal` in
`src/live/execution.py``72*0.001 = 0.07200000000000001` causava `Invalid params`
su Deribit (1 `REAL_OPEN_FAIL` su MR07 il 2026-06-03). Resume pulito al restart.
## Misura della divergenza sim vs reale (primi 7 close shadow)
**Bilancio: PnL sim +11.85 USD vs reale +0.62 USD → delta 11.22 USD.**
Le fee NON sono il problema ($0.63 totali = 0.05%/lato, esattamente l'assunto del
backtest). Il problema è tutto nel **timing delle uscite**.
- **Aperture allineate**: slippage da 8 a +27 bps, mediana ~±7 bps. OK.
- **Chiusure take-profit: +235 bps di slippage medio** (MR07 ETH short: +257/+267/+283/+333 bps).
Causa strutturale, non liquidità: il sim esce *al livello TP* intrabar (come il
backtest), il reale chiude **a mercato solo al poll del tick**, quando il prezzo è
già rientrato (TP toccato a ~1790, fill reale a ~1840). Sim +3 USD a trade, reale ~0.
- **Stop-loss: stessa meccanica ma a favore** (MR02 SL: 0.68 reale vs 2.87 sim).
Le fade però hanno più TP che SL → netto negativo.
- **2 `REAL_CLOSE` non verificate** (order_id null, fallback su stima sim). Venue flat
(`get_positions()=[]`), nessuna posizione orfana — ma sorvegliare i prossimi close.
**Implicazione onesta: l'edge di MR07 non sopravvive all'esecuzione market-on-poll.**
Il backtest assume il fill al livello TP; il reale incassa il prezzo post-rimbalzo.
## TODO (richiesto, da implementare)
Nello shadow-execution (`src/live/execution.py` + `StrategyWorker`): all'apertura
della posizione reale piazzare su Deribit un **LIMIT reduce-only appoggiato al
livello TP** (e idealmente stop-market per lo SL), invece di chiudere a mercato al
poll — così il fill reale replica l'assunzione intrabar del backtest.
Dettagli da considerare:
- cancellare/aggiornare l'ordine resting se il sim esce per `max_bars` o al restart (resume);
- i 3 fade BTC condividono lo strumento (posizioni nettate per conto): il limit
reduce-only deve coprire la SOLA quota del worker, come già fa `close_amount`;
- verificare il fill del resting order via `get_trade_history` (order_id), come per i market.
## IMPLEMENTATO (sera): LIMIT reduce-only al TP
**Flusso nuovo.** A ogni `REAL_OPEN` verificato il worker piazza subito un
**limit reduce-only al livello TP** (lato opposto, stesso `amount` della SOLA
quota del worker — strumento condiviso fra i 3 fade per coin) → evento
`REAL_TP_RESTING` (o `REAL_TP_FAIL` → si resta sul vecchio market-on-poll).
Alla chiusura sim, qualunque sia la ragione, `_real_close` ora:
1. **cancella** il resting (innocuo se già fillato — e dopo il cancel nessun
fill può più arrivare, niente race);
2. **riconcilia i fill** (anche parziali) dal `get_trade_history` per order_id
(stessa fonte autorevole dei market), con retry se il cancel risulta
"già fillato" ma la history è in ritardo;
3. chiude a **market reduce-only solo la quota residua** (exit non-TP:
stop_loss/time_limit, o TP non raggiunto sullo strumento d'esecuzione);
4. ledger reale su **prezzo d'uscita combinato** (media pesata fill-TP +
market) e fee sommate. `REAL_CLOSE` logga `tp_order_id`,
`tp_filled_amount`, `market_amount`.
**Dettagli del TODO coperti:**
- `max_bars`/`stop_loss` → il cancel è il passo 1 di ogni chiusura;
- restart → `real_tp_order_id` è **persistito** in `status.json`: al resume il
resting resta in book e la prima chiusura lo riconcilia normalmente;
- quota del worker → il limit usa lo stesso `amount` dell'apertura (mai
`close_position`); il residuo è ri-quantizzato allo step (Decimal) dentro
`close_amount` (niente artefatti float tipo `0.072-0.024`);
- prezzi limit quantizzati al **tick** dello strumento (`quantize_price`,
BTC_USDC 0.5 / ETH_USDC 0.05 — Deribit rifiuta prezzi off-tick).
**SL: niente stop-market, decisione deliberata.** (a) Su Deribit un trigger
order, quando scatta, genera l'ordine eseguito con un **nuovo order_id** → il
fill non sarebbe verificabile via `get_trade_history` per order_id (il
requisito di verifica); (b) la misura di stamattina mostra che sul SL il
market-on-poll lavora **a favore** (il rimbalzo riduce la perdita). Lo SL resta
quindi market-on-poll.
**Bonus fee.** Quando il TP filla da resting il fill è **maker** (~0% su
Deribit) invece di taker 0.05% — il reale farà meglio dell'assunto 0.10% RT
sulle uscite TP.
**Verifica.** `live_shadow_smoke.py` esteso ai due percorsi e passato su
testnet: (A) resting in book → exit `time_limit` → cancel + market fallback,
conto flat; (B) TP già crossato → fill immediato del limit, chiusura
riconciliata dalla history **senza ordine market** (`market_amount=0`),
prezzo persino migliore del TP (+48 bps, taker sul cross). 54 test pytest OK.
**Caveat residuo onesto.** Se il processo muore DOPO il fill del resting e
PRIMA della chiusura sim, il worker al resume riconcilia al prossimo exit sim
(corretto); ma nell'intervallo la quota reale è già chiusa mentre il sim la
crede aperta — finestra breve e solo contabile. Inoltre il TP del sim è sul
feed (`BTC-PERPETUAL` inverse) mentre il limit vive sul lineare USDC: la base
fra i due può far fillare/non fillare il limit a cavallo del livello — il
fallback market copre il caso.