feat: VENUE WATCH — tripwire di fallimento exchange, cablato live

Risposta a "trova un sistema di protezione da fallimento exchange" SOTTO IL VINCOLO
della decisione appena presa (100% Deribit fino a $20k). Se non si puo' ridurre
l'ESPOSIZIONE, l'unica leva e' il TEMPO: il modello di rischio del mattino assumeva il
salto a zero istantaneo, ma i fallimenti reali non lo sono (Mt.Gox mesi, FTX ~72h, e
misurato qui: Bitfinex 2018-19 dislocato per 2.324 ore consecutive).

SEGNALE: un venue che gata i prelievi rompe l'ARBITRAGGIO -> il prezzo si stacca dal
consenso e ci resta. E' |scarto|, non il segno (Mt.Gox a premio, un venue in fuga a
sconto: stessa cosa). Consenso = venue USD indipendenti (Coinbase, Bitstamp), mai USDT.
Deribit sta a 3 bps dal consenso in mediana su 8 anni (65.043 ore BTC + 64.541 ETH).

TARATURA CONGELATA: 100 bps persistenti 4h a segno costante. Criterio DICHIARATO PRIMA,
perche' i due ovvi sbagliano in versi opposti (provati entrambi): "minimi bps" -> 25/24h
consuma 24 delle ~72h di FTX; "minime ore" -> 500/2h MANCA FTX (margine 0.6x). Regola:
zero falsi allarmi in 8 anni + margine >=3x sul caso storico piu' debole -> soglia
<=100bps -> poi minima latenza. Margine 3x FTX / 5x Quadriga / 10-20x Mt.Gox, zero falsi
allarmi con crash COVID, maggio 2021, LUNA e novembre 2022 inclusi.

CONTROLLO POSITIVO SUPERATO (un rilevatore tarato per non segnalare e' indistinguibile da
uno rotto): puntato su Bitfinex 2018-19 scatta 22 volte, episodio piu' lungo 2.324h a
+447bps. 22 dove il problema c'era, 0 su Deribit. E la durata risponde alla domanda vera:
un venue gated resta dislocato per settimane, quindi 4h di latenza sono trascurabili.

ECONOMIA: falso allarme = 0.248% atteso (flat 3g misurato sul book reale a ogni data
d'inizio); vero positivo = 100% salvato. Break-even p > (falsi/anno) x 0.00248: a 1 ogni
8 anni serve p > 0.031%. Il valore sta nella SPECIFICITA', non nella sensibilita'.

CABLATO: src/live/venue_watch.py (nucleo puro) + scripts/live/venue_watch.py, in
cron_book.sh PRIMA di book_execute (se Deribit e' in stress l'allarme deve partire anche
quando l'esecuzione fallisce per la stessa ragione). Tre stati OK/ALERT/BLIND — "non
vedo" non e' "va bene". ALLERTA, NON BLOCCA: l'azione e' prelevare (manuale; una chiave
con permesso di prelievo sarebbe essa stessa un rischio) e bloccare non protegge un saldo
che e' a rischio anche stando flat. Runbook pre-deciso nel docstring.

NON COPRE, e non e' un argomento per riaprire il 26/07: un fallimento SENZA finestra
(furto chiavi, sequestro, exit-scam) non lo prende nessun tripwire.

ERRORI CATTURATI IN SESSIONE:
- break-even calcolato sul p5 invece che sulla media (8.3x piu' severo, conclusione
  ribaltata);
- ipotesi meccanica sbagliata: credevo che i crash dislocassero a segno ALTERNATO. Falso,
  sono a segno costante anche loro (perp sotto spot per ore in cascata). A separare sono
  ampiezza e durata, non il segno;
- la prima corsa tronco' il campione da 8 anni a 29 GIORNI per un inner-join con Kraken
  (che serve solo ~700 candele) e la copertura era gia' stampata a video: una diagnostica
  stampata NON e' un controllo. Ora c'e' una guardia che ferma lo script. 2a occorrenza
  in un giorno dopo GTAA01;
- il controllo positivo era finito dentro il ramo `else` -> non girava mai, cioe'
  esattamente il difetto che doveva prevenire.

Book, pesi, config INVARIATI. 359 test verdi (+23).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-07-26 19:38:19 +00:00
parent 0ec6f8b761
commit ab5bcace16
9 changed files with 1299 additions and 0 deletions
+182
View File
@@ -0,0 +1,182 @@
# 2026-07-26 — Un sistema di protezione dal fallimento exchange, a capitale concentrato
**Richiesta:** *"trova un sistema di protezione da fallimento exchange"* — subito dopo la decisione
di restare **100% su Deribit fino a $20k**.
Il vincolo e' quello che rende il problema interessante: **la riduzione dell'esposizione e' fuori
discussione**. Lo split di venue e' stato valutato e rifiutato con motivo (§5 del diario
`2026-07-26-venue-risk.md`). Quindi non si puo' ridurre *quanto* si perde: si puo' solo ridurre
**la probabilita' di essere ancora dentro quando succede**.
**Script:** `r0726_venue_tripwire.py` (il segnale), `r0726_venue_response.py` (il costo di
rispondere). **Produzione:** `src/live/venue_watch.py` + `scripts/live/venue_watch.py`, cablato in
`cron_book.sh`. **Test:** `tests/test_venue_watch.py` (23). **Book, pesi, config: INVARIATI.**
---
## 0. L'idea in una riga
Il modello di rischio del mattino assumeva il **salto a zero istantaneo**. I fallimenti reali non
lo sono: Mt.Gox gato' i prelievi per mesi, FTX ebbe ~72 ore, e — misurato qui — **Bitfinex nel
2018-19 resto' dislocato dal consenso per 2.324 ore consecutive**. Se dentro quella finestra il
saldo esce, la perdita non e' totale. Il sistema e' quindi un **rilevatore di finestra**, non una
riduzione di esposizione.
## 1. Il segnale, e perche' e' falsificabile
Quando un exchange gata i prelievi **l'arbitraggio si rompe**: non si puo' piu' comprare li' e
vendere altrove per chiudere lo scarto, quindi il suo prezzo si stacca dal consenso e ci resta.
Il segnale e' **|scarto|, non il segno** — su Mt.Gox il BTC andava a *premio* (si comprava BTC per
far uscire valore), su un venue in fuga si vede lo *sconto*. Dicono la stessa cosa.
Misurato su 8 anni di feed certificato, consenso = mediana di venue **USD indipendenti** (Coinbase,
Bitstamp; mai USDT — il depeg 2022 sposta BTC/USDT fino al 3% e genererebbe falsi allarmi giganti):
```
asset ore mediana p95 p99 p99.9 max >50bps >100bps
BTC 65,043 3.1 11.4 18.0 34.4 712.9 0.06% 0.018%
ETH 64,541 3.3 13.3 22.2 44.3 453.1 0.07% 0.017%
```
Deribit sta a **3 bps** dal consenso in mediana. E' un fondo di rumore bassissimo, ed e' cio' che
rende possibile una soglia con margine.
## 2. L'ipotesi meccanica era in parte SBAGLIATA
Avevo scritto, prima di misurare, che il discriminante sarebbe stato la **costanza del segno**:
un venue gated resta da un lato, un crash rimbalza fra i due. **Falso.** Gli episodi peggiori su
Deribit sono tutti a segno costante anche loro:
```
BTC 2020-03-13 10:00 12h picco 158 bps segno -1 (crash COVID)
ETH 2022-09-14 20:00 10h picco 97 bps segno -1 (Merge)
ETH 2020-03-12 23:00 3h picco 453 bps segno -1
BTC 2021-07-01 13:00 2h picco 150 bps segno +1
```
In un cascata di liquidazioni il perp sta **sotto** lo spot per ore di fila: e' un run a segno
costante come quello di un venue gated. Il segno costante resta nel rilevatore (toglie il rumore
simmetrico), ma **non e' lui a separare i due casi**: separano **ampiezza e durata**. Ipotesi
corretta nella conclusione, sbagliata nel meccanismo.
## 3. La taratura va DICHIARATA, perche' due criteri ovvi sbagliano in versi opposti
Provati entrambi in sessione:
| criterio ingenuo | esito | perche' e' sbagliato |
|---|---|---|
| **minimi bps** | 25 bps / 24h | consuma 24 delle ~72 ore che diede FTX |
| **minime ore** | 500 bps / 2h | **manca FTX** (300 bps): margine 0.6x |
Criterio adottato, in quest'ordine: **(1)** zero falsi allarmi su entrambi gli asset in 8 anni;
**(2)** margine ≥3x sul caso storico **piu' debole** (FTX ~300 bps) → soglia ≤100 bps; **(3)** a
quei vincoli, **minima latenza**.
```
2h -> 500 bps (scartata: 0.6x < 3x su FTX)
3h -> 150 bps (scartata: 2.0x < 3x su FTX)
4h -> 100 bps <- SCELTA
6h -> 75 bps
24h -> 25 bps
```
**→ 100 bps persistenti 4 ore a segno costante.** Margine 3x su FTX, 5x su QuadrigaCX, 10-20x su
Mt.Gox. Zero falsi allarmi in 8 anni, **crash COVID / maggio 2021 / LUNA / novembre 2022 inclusi**.
## 4. Il controllo positivo — senza, "non scatta mai" non e' una buona notizia
Un rilevatore tarato per non segnalare e' indistinguibile da uno rotto. Puntato su **Bitfinex
2018-19** (problemi bancari/Tether, premio persistente), consenso Coinbase+Bitstamp con Bitfinex
escluso dal proprio consenso:
```
BTC: 33.000 ore, scarto mediano 11 bps, max 1136 bps -> 22 EPISODI a 100bps/4h
2018-12-26 2324h picco 447 bps segno +1
2018-11-08 1151h picco 663 bps segno +1
2018-10-10 496h picco 1136 bps segno +1
2019-04-25 363h picco 734 bps segno +1
```
**22 scatti su un venue realmente in stress, zero su Deribit in 8 anni.** E la *durata* di quegli
episodi risponde alla domanda che conta davvero: un venue gated resta dislocato per **settimane**,
quindi 4 ore di latenza di rilevamento costano una frazione trascurabile del preavviso.
## 5. Il lato risposta: quanto costa sbagliarsi
Un allarme senza il costo della risposta non e' un sistema. Misurato sul book Deribit reale
(TP01+SKH01 75/25) forzando flat una finestra a **ogni** data d'inizio possibile:
```
giorni costo medio mediana p5 (peggio) p95 (meglio) % dannosi
1 -0.149% -0.100% -1.093% 0.590% 76.4%
3 -0.248% -0.100% -2.065% 1.006% 67.9%
7 -0.442% -0.100% -4.072% 1.505% 62.4%
30 -1.537% -0.455% -9.236% 2.993% 57.3%
```
⚠️ **Errore mio, corretto:** avevo impostato il break-even sul **p5** (la coda). Per un valore
atteso ripetuto nel tempo si usa la **media** — con la coda il numero esce 8.3x piu' severo e la
conclusione si ribalta. Il p5 serve a sapere quanto puo' bruciare *una* volta, non a decidere.
```
Il tripwire conviene se p_annua > (falsi allarmi/anno) x 0.00248:
1 ogni 8 anni -> p > 0.031% conviene per ogni p del ventaglio
1/anno -> p > 0.248% conviene per ogni p del ventaglio
4/anno -> p > 0.991% conviene da p=1%
12/anno -> p > 2.974% conviene solo a p=5%
52/anno -> p > 12.889% NON conviene mai
```
**Il valore del sistema sta tutto nella SPECIFICITA', non nella sensibilita'.** Un tripwire che
strilla 12 volte l'anno sarebbe dannoso; questo, a zero falsi allarmi misurati su 8 anni, ha un
break-even di **p > 0.031%** — un ordine di grandezza sotto l'ipotesi piu' ottimistica del
venue-risk (0.5%).
## 6. Cosa e' stato cablato
`src/live/venue_watch.py` (nucleo puro, testabile) + `scripts/live/venue_watch.py` (runner), in
`cron_book.sh` **prima** di `book_execute` — se Deribit e' in stress l'allarme deve partire anche
quando l'esecuzione fallisce per la stessa ragione.
**Tre stati, e il terzo non e' il primo:** `OK` (misurato, sotto soglia) / `ALERT` (misurato,
sopra soglia e persistente → Telegram) / **`BLIND`** (non misurabile: referenze irraggiungibili o
in disaccordo fra loro). *"Non vedo" non e' "va tutto bene"*: dopo 12 ore di cecita' scatta un
allarme suo. Stessa lezione della contabilita' a 3 stati di `paper_dvolspread`.
**Allerta, non blocca.** L'azione giusta a un vero positivo e' *prelevare*, che richiede comunque
un intervento manuale — una chiave API con permesso di prelievo sarebbe essa stessa un rischio.
E bloccare l'esecuzione non protegge il saldo: il saldo e' a rischio anche stando flat.
**Runbook pre-deciso** (nel docstring del modulo, per non doverlo decidere nel momento sbagliato):
1. escludere il guasto delle referenze (`n_refs`, `ref_spread_bps`);
2. `public/status` (`platform_locked`) e canali ufficiali;
3. **prelievo di prova piccolo** — l'unica evidenza DIRETTA; se non passa in tempi normali
l'allarme e' vero qualunque altra spiegazione esista;
4. se non passa: flat + prelievo totale. Sbagliarsi costa **0.248%**, aver ragione salva il 100%.
## 7. Cio' che questo sistema NON copre — e va detto
Il tripwire prezza la finestra, non la sua esistenza. **Un fallimento senza finestra non lo prende
nessuno**: furto di chiavi, sequestro, exit-scam notturno. Quella parte di `p` resta scoperta, e la
sola difesa e' quella che l'operatore ha rifiutato fino a $20k — lo split. Il sistema **riduce** il
rischio di venue, non lo elimina, e non e' un argomento per cambiare la decisione del 26/07.
Secondo limite dichiarato: il preavviso storico di 200-2300 ore viene da **un** caso osservato
(Bitfinex). E' un'ancora, non una distribuzione.
## 8. Regole trasferibili
1. **Un rilevatore tarato per non segnalare va validato su un controllo positivo**, o "non e' mai
scattato" e' indistinguibile da "e' rotto". Qui: 22 scatti su Bitfinex, zero su Deribit.
2. **Una soglia si sceglie con un criterio dichiarato PRIMA**, perche' i criteri ovvi sbagliano in
versi opposti: "minimi bps" perde preavviso, "minime ore" perde il caso da prendere.
3. **La persistenza richiesta e' latenza, e la latenza va sottratta al preavviso.** Va confrontata
con la durata del fenomeno da rilevare, non scelta in astratto.
4. **Un break-even si calcola sulla media, non sulla coda.** La coda dice quanto fa male una volta;
la media dice se conviene ripeterlo.
5. **Un outer-join con una referenza corta e' una trappola** (2ª volta oggi, dopo GTAA01): la
prima corsa tronco' il campione da 8 anni a 29 giorni *in silenzio*, e la diagnostica di
copertura era gia' stampata. **Una diagnostica stampata non e' un controllo** — ora c'e' una
guardia che ferma lo script.
6. **Un controllo positivo dentro il ramo `else` non gira mai.** Trovato in sessione: era finito
nel ramo di fallimento, cioe' esattamente il difetto che doveva prevenire.