Files
PythagorasGoal/docs/diary/2026-08-19-venue-watch-disciplina-allarmi.md
Adriano Dal Pastro c932fab304 venue_watch: disciplina degli allarmi, specifiche contratto controllate, terza referenza
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>
2026-08-19 10:10:55 +00:00

8.8 KiB
Raw Permalink Blame History

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 1530 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 1218/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 = 2 non legge gli annunci, li presume. Un feed degli annunci Deribit renderebbe la tolleranza esatta invece che ragionevole.