Files
PythagorasGoal/docs/diary/2026-08-19-venue-watch-disciplina-allarmi.md
T
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

155 lines
8.8 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-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:
```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 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.