Files
PythagorasGoal/docs/diary/2026-06-12-resting-reconcile-feedgap.md
T
2026-06-12 13:04:56 +00:00

6.3 KiB
Raw Blame History

2026-06-12 — Reconcile resting + guard feed↔book + epoca report (v1.1.27)

Contesto: la notte ha validato (di nuovo) il tema osservabilità

Due eventi nelle 24h hanno motivato la tornata:

  1. MR02_BTC (06:00): il TP resting LIMIT ha fillato sul book reale a 60481 nella notte (+6.55 USD reali) mentre il feed sim non è mai sceso sotto 63285 (443 bps di divergenza, sim chiuso a time_limit con 1.25). Il disaster-SL è risultato order_not_found alla cancel (probabile auto-cancel a posizione flat). Caso SPECULARE del TP_PHANTOM: lì il feed stampa wick che il book non ha; qui il book si muove e il feed non lo vede. Real-truth ha bookato il reale (corretto), ma il sistema se n'è accorto solo ORE dopo, al close sim.
  2. Incidente venue (dalle ~09:47): il conto Deribit testnet ha iniziato a rifiutare ordini (locked_by_admin, invalid_reduce_only_order) e le posizioni sul conto sono cambiate da sole (ETH LONG 0.141 con avg 1849.5 — prezzo vecchio di settimane): rollback/reset admin del testnet, non un nostro bug. Difese OK: REAL_CLOSE_PARTIAL, leg-fail con unwind, orfani registrati (ETH 0.026, SOL +0.7), sim_fallback dichiarato. MA: il reconciler delle 11:40 è morto in silenzio su un 502 — il guardiano che non suona è indistinguibile dal tutto-ok.

Fatto

1. Reconcile degli ordini RESTING (cron :40, read-only)

  • cerbero-mcp 9a74052: nuovo endpoint Deribit get_open_orders (private/get_open_orders_by_currency; per i trigger untriggered interrogare anche type='trigger_all' e merge per order_id). Pattern già presente per Hyperliquid/IBKR. Rebuild + smoke su testnet (book vuoto == stato atteso).
  • books.expected_resting(): TP/DSL attesi dai libri dei worker single-leg in posizione reale (i pairs non hanno resting).
  • reconcile_account.compute_resting_drift() — tre classi: FILLED_UNBOOKED (atteso non in book + fill nel trade history col worker ancora in posizione = il caso MR02), MISSING (atteso, non in book, zero fill — per il DSL triggered il fill ha order_id NUOVO → appare qui e il drift posizioni completa), STALE (in book con label di un nostro worker ma nessun libro: fillerebbe a sorpresa). Anti-race come per le posizioni; alert RESTING_DRIFT.
  • RECONCILE_FAIL: il main è ora guardato — su eccezione alert Telegram + exit 2 (fix del crash silenzioso delle 11:40).

2. Guard FEED_BOOK_GAP nel runner

_check_feed_book_gap: ad ogni poll confronta il close della candela in corso col mark dello strumento d'ESECUZIONE (USDC, get_ticker_batch, 1 chiamata); oltre soglia (overrides.feed_book_gap_bps, default 150) → alert per episodio, recovery con isteresi a soglia/2, fail-open su errori di rete. Le decisioni restano sul feed (il sim è la verità che guida): questo dice solo QUANDO i fill reali possono divergere dal sim. Avrebbe segnalato il caso MR02 in tempo reale.

3. Epoca v1.1.26 nell'hourly_report

I conteggi CHIUSI cumulativi erano inquinati dal churn TP-fantasma dell'11-06 17:3217:58 (~24 giri pre-fix). Nuova riga epoca v1.1.26+ (TP_PHANTOM attivo) da EPOCH_V1126 = 2026-06-11T21:40 (deploy del gate): oggi legge 7/1 contro il 18/17 storico — è il numero da confrontare col backtest da qui in poi.

Test e deploy

pytest 121 passed (114 + 7 nuovi: test_reconcile_resting.py, test_feed_book_gap.py). Deploy v1.1.27 (runner nel container; reconciler e hourly_report girano da host → già attivi). cerbero-mcp rebuildato (endpoint).

Aperto / lezioni

  • Drift ETH +0.167 non spiegato dal rollback testnet: quando il conto si sblocca serve reset_flatten + riallineamento ledger (decisione utente).
  • I residui dei close single-leg falliti (REAL_CLOSE_PARTIAL, es. MR07 0.102) NON finiscono in orphan_legs → il reconciler li vede come drift NON spiegato. Estendere la registrazione orfani anche al single-leg è il naturale passo successivo (punto in analisi sviluppi).
  • Il testnet può riscrivere il conto sotto i piedi (lock + rollback): nessuna invariante "il conto cambia solo per nostri ordini" è affidabile. Il reconciler orario è l'unico rilevatore; valutare cadenza più fitta in presenza di posizioni reali aperte.

Addendum (pomeriggio, v1.1.28): orfani single-leg + circuit-breaker venue-lock

I due punti promossi dall'analisi sviluppi, entrambi figli dell'incidente di oggi:

  1. Orfani anche per i close single-leg. REAL_CLOSE_PARTIAL ora registra la quota residua in StrategyWorker.orphan_legs (stessa semantica dei pairs, persistita in status.json, letta da books.real_books) → il reconciler vede il drift come SPIEGATO invece che anomalo (il caso MR07 0.102 ETH delle 09:47 restava invisibile ai libri). Inoltre _close_position ora persiste lo stato a fine booking (prima solo al save del tick: un crash li' in mezzo perdeva capital e orfano). NB: nessun save a meta' chiusura — con real_in_position ancora true l'orfano conterebbe doppio nei libri.
  2. Circuit-breaker venue-lock in ExecutionClient. Dopo lock_trip=3 reject 'locked' consecutivi (es. locked_by_admin di oggi) le APERTURE sono sospese senza toccare l'API (Fill failed → path REAL_OPEN_FAIL/sim_fallback esistente; per i pairs entrambe le gambe rifiutate localmente → niente leg parziali da unwindare, niente fee sprecate). Le CHIUSURE si tentano SEMPRE (path gia' sicuro: partial/orphan/netting) e un loro reject 'locked' rinfresca il cooldown. Riarmo: dopo lock_cooldown_s=900 la prossima apertura fa da probe. Alert VENUE_LOCK al trip + RIENTRATO al primo ordine accettato.

Test: test_single_leg_orphan.py, test_venue_lock_breaker.py (126 passed).

Chiusura incidente (13:05): testnet sbloccato, conto riallineato

Sequenza eseguita a venue sbloccato: stop runner → reset_flatten (chiusi i due artefatti del rollback: ETH long 0.141 @1664.25, SOL long 0.7 @66.651, conto FLAT) → svuotati gli orphan_legs di ETH_SOL (le quote orfane non esistono piu' sul conto post-flatten; edit a container fermo, altrimenti il worker li riscrive dalla memoria) → restart. Reconciler: OK — conto allineato ai libri (posizioni e resting), runner healthy, log puliti. Il ledger NON e' stato toccato: i booking dell'incidente erano gia' dichiarati (sim_fallback/real) e l'equity (€2020.82) resta la verita' contabile del paper.