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>
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
# 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:
|
||||
|
||||
```python
|
||||
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 = 2` non legge gli
|
||||
annunci, li **presume**. Un feed degli annunci Deribit renderebbe la tolleranza esatta invece
|
||||
che ragionevole.
|
||||
Reference in New Issue
Block a user