Il 18/08 sono usciti quattro 🚨 identici con 'PRIMO PASSO: prelievo di prova' per una manutenzione Deribit annunciata, e tutti e quattro DOPO che il book aveva gia' ripreso a eseguire. Il book si era comportato bene (due giri di astensione, 'non eseguo a cieco'); il difetto era negli allarmi. 1. Il blocco piattaforma era un if secco senza memoria, accanto a un rilevatore tarato con cura che allerta una volta per streak. Ora passa da lock_step(), pura e testata: manutenzione entro tolleranza = un solo avviso morbido, che sfora = un solo 🚨 ('ha SFORATO'), blocco inspiegato = 🚨 subito, rientro annunciato una volta. public/status illeggibile NON e' un rientro. 2. Il messaggio diceva 'locked=true' cablato mentre il parser accetta anche 'partial': dichiarava un valore che non aveva letto, e il runbook manda a controllare proprio quel campo. Ora stampa e salva il valore grezzo. 3. Deribit ha cambiato tick e size dei perpetual lineari USDC il 18/08 e la tabella _CONTRACT, cablata a mano, non se n'era accorta. Nessun ordine rifiutato solo perche' i cambi erano riduzioni: e' andata bene per la direzione, non perche' ce ne fossimo accorti. Tabella aggiornata ai valori verificati e aggiunto check_specs(), che gira nel venue_watch orario (fuori dal percorso ordini) e dichiara la direzione: 'granularita'' = conforme, 'rifiuto' = il venue rifiuta. La tabella dichiarata resta l'autorita' per costruire un ordine, il venue e' il controllore. 4. Aggiunta Kraken come terza referenza: Coinbase ha comprato Deribit, e una referenza che e' la casa madre non misura piu' se Deribit scolla dal mondo. THRESHOLD_BPS e PERSIST_HOURS invariati, ma il consenso ora e' su tre serie e quel numero non e' ri-misurato: dichiarato nel codice e nel diario. La direzione dell'errore e' 'allerta di meno', non 'grida al lupo'. Test 23 -> 35 su venue_watch, suite intera 618 verdi. Book, pesi e config invariati; dry-run del book verificato. Diario: docs/diary/2026-08-19-*. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
8.8 KiB
2026-08-19 — Quattro 🚨 per una manutenzione annunciata, e cosa c'era dietro
Richiesta: "su Adp_VPS mi sono arrivati questi messaggi" — quattro allarmi VENUE WATCH del 18/08, incollati senza domanda. La domanda vera era: e' successo qualcosa di grave?
No. Ma il modo in cui l'ho scoperto ha rivelato tre difetti, e uno riguarda gli ordini.
Toccati: src/live/venue_watch.py, src/live/deribit.py (tabella _CONTRACT + check_specs),
scripts/live/venue_watch.py. Test: tests/test_venue_watch.py 23 → 35; suite intera
618 verdi. Book, pesi, config/live.json, THRESHOLD_BPS, PERSIST_HOURS: INVARIATI.
0. Cosa era successo davvero
Manutenzione Deribit annunciata il 14/08 per il 18/08 alle 09:00 UTC, downtime dichiarato
15–30 minuti. Il book si e' astenuto ai giri delle 09:07 e 10:07 UTC — conto non leggibile (offline) -> stop, non eseguo a cieco — e alle 11:07 eseguiva di nuovo. Il sistema ha fatto la
cosa giusta.
I 572 minuti di "feed SKH stantio" non erano una deriva nascosta: con la coda 5m non attaccabile
il book e' ripiegato sul feed certificato, che e' di suo vecchio di ~9,5 ore. Prima (08:07) e dopo
(11:07) diceva fresco (0 min).
Ma venue_watch ha visto la piattaforma bloccata fino alle 14:07 UTC: la manutenzione ha
sforato l'annuncio di ore, non di minuti. E ha mandato quattro 🚨 identici, tutti dopo che
il book aveva gia' ripreso.
1. Perche' quattro messaggi uguali: l'allarme non aveva memoria
Il rilevatore di dislocazione e' tarato con cura — 100 bps, 4 ore a segno costante, «zero falsi
allarmi in 8 anni», controllo positivo su Bitfinex 2018-19 — e la macchina a stati step()
allerta una volta per streak e poi tace.
Accanto, il blocco piattaforma era un if secco senza stato:
if obs["platform_locked"]:
report["alerts"].append("Deribit public/status: PIATTAFORMA BLOCCATA (locked=true)")
Nessuna persistenza, nessuna deduplica, nessuna nozione di manutenzione. Un rilevatore disciplinato con accanto uno che grida al lupo ogni ora, e con lo stesso titolo: «PRIMO PASSO: prelievo di prova». Il costo non e' il fastidio: e' che un allarme massimo speso per un evento atteso e' un allarme che non verra' letto il giorno che e' vero — l'unico giorno che conta.
Ora il lock passa da lock_step(), pura e testata, con gli stessi livelli degli altri stati:
| condizione | esito |
|---|---|
bloccata + manutenzione dichiarata, entro MAINT_GRACE_HOURS (2) |
⚠️ una volta, «attesa, non allarme» |
| manutenzione che sfora la tolleranza | 🚨 una volta, «ha SFORATO 2h» |
| bloccata senza manutenzione dichiarata | 🚨 subito, una volta |
| rientrata | avviso di rientro, una volta |
public/status illeggibile |
niente: le ore restano dove sono |
Rigiocando il 18/08: quattro messaggi identici diventano ⚠️ → 🚨 (ha sforato) → rientrato. Tre messaggi che dicono tre cose diverse.
⚠️ L'ultima riga della tabella e' quella che mi premeva: dichiarare «e' rientrato» perche' non si
e' riusciti a guardare sarebbe la bugia peggiore possibile in questo file. locked is None non
azzera niente e non annuncia niente.
⚠️ E la tolleranza non e' un'assoluzione: la manutenzione declassa l'allarme solo dentro la finestra, e oltre lo rialza. Il 18/08 e' la ragione per cui la regola non poteva essere «se e' manutenzione, stai zitto»: Deribit aveva annunciato mezz'ora ed e' rimasta bloccata per ore. Una manutenzione che dura sei volte l'annuncio e' di nuovo una notizia.
2. Il messaggio dichiarava un valore che non aveva letto
Il parser accetta qualunque valore diverso da false/none — Deribit risponde anche partial —
e il testo era cablato: (locked=true). Il runbook al passo 2 manda a controllare proprio quel
campo: mandarci qualcuno con in testa la stringa sbagliata e' peggio che non dirgliela.
Adesso platform_status() restituisce il valore grezzo, il messaggio lo stampa e lo stato lo
salva. Del 18/08 non e' piu' ricostruibile quale fosse: nessuno lo registrava.
3. Il difetto che poteva costare: le specifiche contratto
Cercando altro, il confronto con gli annunci Deribit ha trovato una cosa che nessun allarme
sorvegliava. Il 18/08 dopo le 09:00 UTC Deribit ha aggiornato tick, contract size e minimum
order size dei perpetual lineari USDC (annuncio del 14/08). La tabella _CONTRACT di
deribit.py, che e' quella con cui si costruiscono gli ordini, era cablata a mano e non se
n'e' accorta:
| dichiarato | venue dal 18/08 | |
|---|---|---|
| BTC tick | 0.5 | 0.1 |
| ETH tick | 0.05 | 0.01 |
| ETH min/step | 0.001 | 0.0001 |
Nessun ordine e' stato rifiutato, e la ragione non e' merito nostro: i cambi erano riduzioni, e un valore piu' grosso resta conforme. Il costo effettivo era di granularita' — su ETH l'incremento minimo restava $1.92 invece di $0.19 su un conto da $597 (0,32% contro 0,03%).
⚠️ Il giorno che Deribit alza un minimo, la stessa cecita' fa rifiutare gli ordini, e non c'e' niente che lo dica prima.
Scelta di disegno: la tabella dichiarata resta l'autorita' per costruire un ordine; il venue
diventa il controllore, non la fonte. Prendere i valori dall'API dentro il percorso di
esecuzione avrebbe reso l'ordine dipendente da come ha risposto una GET — cioe' non ricostruibile
dopo. Invece check_specs() gira nel venue_watch orario (sola lettura, fuori dal percorso
ordini) e confronta; la tabella si aggiorna a mano, di proposito, in git.
Il confronto dichiara la direzione, che e' l'unica cosa che serve per decidere se correre:
granularita'— dichiarato piu' grosso: conforme, si perde precisione → ⚠️, si aggiorna con calma;rifiuto— dichiarato piu' fine: il venue rifiuta → 🚨, si corregge subito.
⚠️ Uno strumento non letto non e' una divergenza e non viene contato come «combacia»: finisce
in non_letti. Silenzio e uguaglianza non sono la stessa cosa, e solo una delle due e' rassicurante.
Tabella aggiornata ai valori veri e verificata: check_specs() risponde divergenze: [],
non_letti: []. Il floor min_order_usd = $5 del book e' indipendente, quindi lo step piu' fine
non apre la porta a ordini da 19 centesimi.
4. Il difetto piu' lento: la referenza non e' piu' indipendente
venue_watch misura Deribit contro un consenso di referenze USD. Erano due: Coinbase e
Bitstamp. Ma le comunicazioni del 12–18/08 dicono «Deribit by Coinbase», mettono il Coinbase
Index come riferimento per una parte dei perpetual lineari, e instradano lo spot Deribit su
Coinbase Exchange.
Il rilevatore esiste per rispondere a «Deribit sta scollando dal mondo?». Se una delle due referenze e' la casa madre, uno shock di Coinbase muove Deribit e la referenza insieme, e lo scarto in bps resta piccolo proprio nell'ora in cui dovrebbe aprirsi. Con due referenze, il consenso indipendente si riduceva di fatto a Bitstamp.
Aggiunta Kraken come terza. BTC ed ETH restano sul Deribit Index (hanno opzioni quotate), quindi oggi la contaminazione e' di proprieta', non ancora di calcolo: si interviene finche' e' teorica.
⚠️ Onesta' sulla taratura. «Zero falsi allarmi in 8 anni» e' stato misurato con l'insieme di
referenze di allora. THRESHOLD_BPS e PERSIST_HOURS non sono stati toccati, ma il consenso ora
si calcola su tre serie invece di due, e quel numero non e' stato ri-misurato. Quello che si
puo' affermare leggendo il codice e' la direzione: con tre referenze il consenso e' la mediana
(robusta a un outlier) invece della media di due, e lo spread max-min si allarga → si va piu'
spesso in BLIND, che e' lo stato morbido. Cioe' il modo in cui questa modifica puo' sbagliare
e' «allerta di meno», non «grida al lupo». Per il numero vero si rilancia
scripts/research/r0726_venue_tripwire.py con la terza serie.
Misurato subito dopo: BTC +3.0 bps, ETH +2.5 bps, 3 referenze, spread fra referenze 1.5 e 0.7 bps. Le tre concordano strettamente: nessuna deriva verso BLIND nella pratica.
5. Cosa NON e' stato fatto, e resta aperto
- Il Rulebook del 12/08 non lo sorveglia nessuno. Introduce ADL e perdita socializzata, emergency powers in qualifying circumstances, la disciplina dei conti dormienti, e la precisazione che Deribit ha «limited administrative authority, rather than direct control» sugli asset custoditi da terzi. Per un sistema il cui runbook dice «l'azione giusta e' prelevare», questa e' materia di rischio-venue pura — e il watcher guarda solo i prezzi.
- La taratura a tre referenze non e' ri-misurata (vedi §4).
- Il verso della finestra di manutenzione e' euristico:
MAINT_GRACE_HOURS = 2non legge gli annunci, li presume. Un feed degli annunci Deribit renderebbe la tolleranza esatta invece che ragionevole.