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

109 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.