venue: la taratura ri-misurata — «zero falsi allarmi in 8 anni» non descriveva la produzione

B1 (memoria) + B2 (misura). Il 19/08 stava solo in un diario e in un commit: la
sessione che ha cambiato il tripwire di venue, la tabella _CONTRACT e le referenze
non era in CLAUDE.md — 2a occorrenza dopo edge_watch, e riguarda di nuovo qualcosa
che gira con soldi veri. Ora c'e', insieme al debito che dichiarava.

B2: il debito era «la taratura non e' ri-misurata a tre referenze». Ri-misurandola
(scripts/research/r0821_venue_refs.py, riusa dislocation/episodes/zero_fp_frontier
di r0726) il problema non era dove ci si aspettava.

1. «Zero falsi allarmi in 8 anni» veniva dal consenso Coinbase+Bitstamp+BITFINEX
   dello script di ricerca; il sorvegliante live gira su Coinbase+Bitstamp. Due
   liste di referenze in due posti diversi, e nessun test poteva accorgersene.
   Sull'insieme reale i falsi allarmi sono 1: 2020-03-13 07:00-10:00 UTC, 4 ore a
   -418 bps di picco su BTC = il crash COVID, cioe' proprio l'evento che CLAUDE.md
   elencava come esempio di cio' su cui NON scattava. La firma che lo dimostra: le
   "65.043 ore BTC" citate in memoria sono esattamente le ore utili del set con
   bitfinex; sul set reale sono 69.633.

2. Kraken non lo ripara: su 8 anni porta un mese. Il tetto ~700 candele
   dell'endpoint pubblico e' stato ri-verificato oggi sulla rete (704 barre su
   70.286 richieste = 1,00%), non creduto da un commento del 26/07.

3. Perche' spariva a 3 referenze, ed e' il punto trasferibile: con bitfinex dentro
   1 delle 4 ore diventa BLIND, lo streak si azzera e l'episodio non esiste — ma la
   dislocazione e' ancora li' (mediana -343 bps). Lo zero non veniva da un consenso
   piu' accurato, veniva da un'ora buttata (ore utilizzabili 93,4% vs 100,0%).

4. La direzione dichiarata il 19/08 ("piu' BLIND, allerta di meno") e' confermata ma
   la sua taglia dipende dal venue, non dal numero tre: confronto appaiato sulle
   stesse ore, con bitfinex -13,91 pp di ore utilizzabili, con kraken +0,00 pp.

5. Controllo positivo intatto: Bitfinex 2018-19, 22 episodi, il piu' lungo 2.324h,
   picco 1.136 bps = 11,4x la soglia.

DECISIONE: (100 bps, 4h) NON si tocca, ma la giustificazione cambia. 1 falso allarme
in 8 anni costa 0,031%/anno di equity attesa contro il 100% che un vero positivo
evita (ogni p plausibile e' 16-160x sopra il break-even); alzare a 150 bps per
ripristinare lo zero porterebbe il margine su FTX da 3,0x a 2,0x, sotto la gamba (b)
del criterio dichiarato prima di guardare i dati. La memoria si contraddiceva da
sola — «zero falsi allarmi» al punto (2), «a 1 ogni 8 anni serve p > 0,031%» al
punto (4) — e la meta' giusta era quella dell'economia.

Congelato il MECCANISMO, non il numero: tests/test_venue_watch.py 35 -> 37, due test
puri (lo spread max-min e' monotono nel numero di referenze; una sola ora BLIND
spezza lo streak, con un caso di controllo che DEVE scattare).

Soglie, book, pesi, config, cron: INVARIATI. Suite 621 verdi, 1 rosso noto
(test_gtaa_band_gate, altro asse). Diario: docs/diary/2026-08-21-venue-taratura-*.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-08-21 17:55:16 +00:00
parent e69cc5bd81
commit fac9978d87
4 changed files with 493 additions and 10 deletions
+81 -8
View File
@@ -1120,25 +1120,31 @@ Prima ondata di ricerca onesta su BTC/ETH certificati (5 track, harness condivis
decisione appena presa (100% Deribit fino a $20k): se non si puo' ridurre l'ESPOSIZIONE, l'unica
leva e' il **TEMPO**. Script `r0726_venue_tripwire.py` (segnale) + `r0726_venue_response.py`
(costo della risposta); produzione `src/live/venue_watch.py` + `scripts/live/venue_watch.py` in
`cron_book.sh`; test `tests/test_venue_watch.py` (23); diario `2026-07-26-venue-tripwire.md`.
`cron_book.sh`; test `tests/test_venue_watch.py` (**37**); diari `2026-07-26-venue-tripwire.md`,
`2026-08-19-venue-watch-disciplina-allarmi.md`, `2026-08-21-venue-taratura-tre-referenze.md`.
**Book, pesi, config INVARIATI.**
(1) **Il segnale:** un venue che gata i prelievi **rompe l'arbitraggio** → il suo prezzo si stacca
dal consenso e ci RESTA. Il segnale e' **|scarto|, non il segno** (Mt.Gox andava a *premio*, un
venue in fuga a *sconto*: dicono la stessa cosa). Consenso = venue **USD indipendenti**
(Coinbase, Bitstamp); mai USDT (depeg 2022 → falsi allarmi giganti). Deribit sta a **3 bps** dal
consenso in mediana su 8 anni (65.043 ore BTC + 64.541 ETH) — fondo di rumore bassissimo, ed e'
cio' che rende possibile una soglia con margine.
venue in fuga a *sconto*: dicono la stessa cosa). Consenso = **mediana** di venue **USD
indipendenti** (Coinbase, Bitstamp, **e Kraken dal 19/08** — vedi sotto); mai USDT (depeg 2022 →
falsi allarmi giganti). Deribit sta a **3 bps** dal consenso in mediana su 8 anni — fondo di
rumore bassissimo, ed e' cio' che rende possibile una soglia con margine. ⚠️ Le "65.043 ore BTC"
citate qui fino al 21/08 erano le ore del consenso **con bitfinex dentro**, che la produzione non
ha mai avuto: sull'insieme reale sono **69.633** (ETH 64.541 invariato).
(2) **Taratura CONGELATA = 100 bps persistenti 4h a segno costante.** Criterio **dichiarato
prima**, perche' i due ovvi sbagliano in versi opposti (provati entrambi): *minimi bps* → 25bps/24h
consuma 24 delle ~72h che diede FTX; *minime ore* → 500bps/2h **manca FTX** (margine 0.6x).
Regola adottata: (a) zero falsi allarmi su entrambi gli asset in 8 anni; (b) margine ≥3x sul caso
storico **piu' debole** (FTX ~300bps) → soglia ≤100bps; (c) a quei vincoli, minima latenza.
Margine finale **3x FTX / 5x QuadrigaCX / 10-20x Mt.Gox**, con **zero falsi allarmi** inclusi
crash COVID 2020-03, maggio 2021, LUNA e novembre 2022.
Margine finale **3x FTX / 5x QuadrigaCX / 10-20x Mt.Gox**.
⚠️ **CORREZIONE 2026-08-21 — la gamba (a) NON e' soddisfatta dalla configurazione che gira, e la
frase "zero falsi allarmi inclusi crash COVID 2020-03" era FALSA: il falso allarme E' il COVID.**
Vedi il blocco **(8)** in fondo. Il punto (100 bps, 4h) **resta**, per economia e per la gamba (b).
(3) ✅ **CONTROLLO POSITIVO superato** (obbligatorio: un rilevatore tarato per non segnalare e'
indistinguibile da uno rotto). Puntato su **Bitfinex 2018-19** (problemi bancari/Tether):
**22 episodi**, il piu' lungo **2.324 ore consecutive** a +447bps di picco, altri a 1.151h/+663bps
e 496h/+1136bps. **22 scatti dove il problema c'era, 0 su Deribit in 8 anni.** E la DURATA
e 496h/+1136bps. **22 scatti dove il problema c'era, 1 solo su Deribit in 8 anni**
(era dichiarato 0 — vedi **(8)**). E la DURATA
risponde alla domanda vera: un venue gated resta dislocato per **settimane** → 4h di latenza
costano una frazione trascurabile del preavviso.
(4) **Economia della risposta:** costo ATTESO di un falso allarme (flat 3 giorni, misurato sul
@@ -1168,6 +1174,73 @@ Prima ondata di ricerca onesta su BTC/ETH certificati (5 track, harness condivis
**l'outer-join con referenze di lunghezza diversa e' una trappola ricorrente**); (f) un controllo
positivo finito dentro il ramo `else` **non gira mai** (trovato in sessione: era esattamente il
difetto che doveva prevenire).
**(7) DISCIPLINA DEGLI ALLARMI + TERZA REFERENZA + SPECIFICHE CONTRATTO (2026-08-19)** —
*(bullet scritto il 21/08: la sessione era in un diario e in un commit e NON in memoria operativa,
2ª occorrenza dopo `edge_watch`)*. Nato da 4 🚨 identici il 18/08 per una **manutenzione Deribit
annunciata** (14/08 per il 18/08 09:00 UTC, downtime dichiarato 15-30 min) che ha **sforato fino
alle 14:07 UTC**. **Il book si era comportato bene** (astensione alle 09:07 e 10:07, *"conto non
leggibile → non eseguo a cieco"*): il difetto era negli allarmi. (a) Il blocco piattaforma era un
`if` secco **senza memoria**, accanto a un rilevatore che allerta una volta per streak → ora passa
da **`lock_step()`** pura: manutenzione entro `MAINT_GRACE_HOURS`=2 → ⚠️ **una volta**; che sfora
→ 🚨 una volta («ha SFORATO»); blocco **senza** manutenzione dichiarata → 🚨 subito; rientro
annunciato una volta; **`public/status` illeggibile → NIENTE** (dichiarare «e' rientrato» perche'
non si e' riusciti a guardare sarebbe la bugia peggiore). *Un allarme massimo speso per un evento
atteso e' un allarme che non verra' letto il giorno che e' vero.* (b) Il messaggio stampava
`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
grezzo. (c) ⚠️ **Il difetto che poteva costare:** il 18/08 Deribit ha cambiato **tick e size dei
perpetual lineari USDC** (BTC tick 0.5→**0.1**, ETH 0.05→**0.01**, ETH min/step 0.001→**0.0001**)
e la tabella `_CONTRACT` di `deribit.py`, **cablata a mano**, non se n'e' accorta. Nessun ordine
rifiutato **e non per merito nostro**: erano *riduzioni*, e un valore piu' grosso resta conforme
(costo effettivo: granularita', incremento minimo ETH $1.92 invece di $0.19). **Il giorno che
Deribit ALZA un minimo la stessa cecita' fa rifiutare gli ordini.** Cablato **`check_specs()`**
nel venue_watch orario, **fuori dal percorso ordini**: la tabella dichiarata resta l'AUTORITA'
per costruire un ordine, il venue e' il **controllore** (prendere i valori dall'API dentro
l'esecuzione renderebbe l'ordine dipendente da come ha risposto una GET = non ricostruibile).
Verdetti: `granularita'` (dichiarato piu' grosso → conforme, ⚠️) vs `rifiuto` (dichiarato piu'
fine → 🚨). Uno strumento **non letto** finisce in `non_letti`, non conta come «combacia».
(d) 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.
⚠️ **(8) LA TARATURA RI-MISURATA (2026-08-21) — «zero falsi allarmi in 8 anni» non ha mai
descritto la produzione, e il falso allarme e' il COVID.** Script `r0821_venue_refs.py`, diario
`2026-08-21-venue-taratura-tre-referenze.md`. **Soglie, book, config INVARIATI.**
**(i)** Il numero pubblicato veniva dal consenso **Coinbase+Bitstamp+Bitfinex** dello script di
ricerca; il sorvegliante live girava su **Coinbase+Bitstamp**. Due liste di referenze in due
posti diversi, e **nessun test poteva accorgersene**. Sull'insieme reale: **1 falso allarme in
8 anni**, il **2020-03-13 07:00-10:00 UTC**, 4 ore a **418 bps** di picco su BTC — cioe' proprio
l'evento che questo bullet elencava come esempio di cio' su cui NON scattava. La soglia minima a
zero falsi allarmi a 4h e' **150 bps** (BTC), non 100.
**(ii) Kraken non lo ripara:** su 8 anni porta **un mese** (tetto ~700 candele dell'endpoint
pubblico, **ri-verificato oggi**: 704 barre su 70.286 richieste = 1,00%) → LIVE-post ≡ LIVE-pre
sulla storia lunga, numeri **identici**.
**(iii) Perche' spariva a 3 referenze, ed e' il punto trasferibile:** con bitfinex dentro **1
delle 4 ore diventa BLIND** → lo streak si azzera e l'episodio non esiste, **ma la dislocazione
e' ancora li'** (mediana 343 bps). **Lo zero non veniva da un consenso piu' accurato, veniva da
un'ora buttata** (ore utilizzabili 93,4% contro 100,0%).
**(iv) La direzione dichiarata il 19/08 e' confermata, la sua TAGLIA dipende dal venue** (confronto
appaiato, stesse ore): con **bitfinex** le ore non-BLIND passano 99,95% → 86,04% = **13,91 pp**;
con **kraken** **+0,00 pp** (|scarto| mediano appaiato 0,04 bps). Non e' una proprieta' del
numero tre: e' **quanto la terza referenza e' d'accordo con le altre**.
**(v) Controllo positivo intatto:** Bitfinex 2018-19 → 22 episodi, il piu' lungo 2.324h, picco
**1.136 bps = 11,4x** la soglia.
**(vi) DECISIONE: (100 bps, 4h) NON si tocca, ma la giustificazione cambia.** 1 falso allarme in
8 anni = 0,125/anno × 0,248% = **0,031%/anno** di equity attesa contro il **100%** che un vero
positivo evita, e ogni `p` plausibile (0,5-5%) e' **16-160x sopra** il break-even; alzare a 150 bps
porterebbe il margine su FTX da **3,0x a 2,0x**, sotto la gamba (b) dichiarata prima di guardare i
dati. **Il numero da citare e' «1 in 8 anni»** — che e' esattamente l'esempio gia' usato al punto
(4): **la memoria si contraddiceva da sola e la meta' giusta era quella dell'economia.**
**REGOLE NUOVE:** (g) **un numero di taratura si etichetta con la CONFIGURAZIONE, non solo con la
finestra** — «zero falsi allarmi in 8 anni» era vero e inutile perche' descriveva un consenso che
la produzione non ha mai avuto; (h) **uno "zero" si legge accanto alla quota di campione
utilizzabile**: meno falsi allarmi perche' si vede meglio e meno perche' si vede di meno hanno lo
stesso valore stampato e valore opposto; (i) **quando due affermazioni della stessa memoria si
contraddicono, la contraddizione e' informazione** — una delle due e' stata scritta guardando i
dati; (j) **una nota che dichiara un debito puo' sottodimensionarlo**: il 19/08 diceva «il numero
non e' ri-misurato», e il numero era sbagliato **prima** della modifica che lo aveva fatto
dichiarare. **RESTA APERTO:** il Rulebook Deribit del 12/08 (ADL, perdita socializzata, *emergency
powers*, conti dormienti) non lo sorveglia nessuno; `MAINT_GRACE_HOURS`=2 **presume** gli annunci
invece di leggerli (due `locked=true` in 4 giorni: 18/08 ~4h, 21/08 ~1h); la terza referenza non
e' validabile sulla storia.
- ⚖️ **RIVALUTAZIONE DELLA STRATEGIA (2026-07-26, fine giornata) — 0 cambi, e il peso 75/25
confermato per la TERZA volta.** Script `r0726_reeval_live_weight.py`, test
`tests/test_reeval_live_weight.py` (6), diario `2026-07-26-reeval-strategia.md`.
@@ -0,0 +1,156 @@
# 2026-08-21 — «Zero falsi allarmi in 8 anni» non descriveva il sistema che gira
**Richiesta:** *"analizza e fai B1 e B2"* — B1: portare in memoria operativa la sessione del 19/08,
che stava solo nel diario e nel commit. B2: ri-misurare la taratura del tripwire di venue dopo
l'aggiunta della terza referenza, debito dichiarato quel giorno e mai chiuso.
**Toccati:** `scripts/research/r0821_venue_refs.py` (nuovo), `tests/test_venue_watch.py` (35 → 37),
`CLAUDE.md`. **Book, pesi, `config/live.json`, `THRESHOLD_BPS`, `PERSIST_HOURS`: INVARIATI.**
---
## 0. Il debito, come era stato dichiarato
Il 19/08 e' stata aggiunta **Kraken** come terza referenza, perche' Coinbase ha comprato Deribit e
una referenza che e' la casa madre non misura piu' se Deribit scolla dal mondo. Il diario fu onesto
sul limite: *«"Zero falsi allarmi in 8 anni" e' stato misurato con l'insieme di referenze di allora
… quel numero non e' stato ri-misurato»*, e dichiarava la sola **direzione** dell'errore — con la
mediana di tre il consenso e' piu' robusto e lo spread max-min si allarga, quindi si va piu' spesso
in BLIND, che e' lo stato morbido: il modo di sbagliare e' «allerta di meno».
La direzione era giusta. Ma ri-misurando salta fuori un fatto piu' grosso, che non era nel verso
previsto perche' non riguardava la modifica del 19/08 **ma la taratura originale**.
## 1. Il fatto che struttura tutto: la profondita' delle referenze
| asset | venue | barre | da | a |
|---|---|---|---|---|
| BTC | coinbase | 69.643 | 2018-08-14 | 2026-07-26 |
| BTC | bitstamp | 69.663 | 2018-08-14 | 2026-07-26 |
| BTC | **bitfinex** | 33.000 | 2018-08-14 | **2022-05-21** |
| BTC | **kraken** | **702** | 2026-06-26 | 2026-07-26 |
| ETH | coinbase | 64.554 | 2019-03-14 | 2026-07-26 |
| ETH | bitstamp | 64.572 | 2019-03-14 | 2026-07-26 |
| ETH | **bitfinex** | **0** | — | — |
| ETH | kraken | 702 | 2026-06-26 | 2026-07-26 |
Il tetto di Kraken e' stato **ri-verificato oggi sulla rete**, non creduto da un commento del 26/07:
chieste 70.286 ore, ricevute **704** (l'1,00% del richiesto). L'endpoint OHLC pubblico ritorna le
ultime ~700 candele qualunque `since`.
**Conseguenza: la taratura a 8 anni con Kraken dentro non e' ottenibile.** Non per pigrizia — per
struttura della fonte. E non e' un problema per il sorvegliante live, a cui servono le ultime ore.
## 2. Il falso allarme che c'era, e che nessuno aveva visto
Frontiera a zero falsi allarmi, per insieme di referenze, punto in produzione (100 bps, 4h):
| insieme | asset | ore utili | non-BLIND | soglia minima a 4h | falsi allarmi a (100, 4h) |
|---|---|---|---|---|---|
| **LIVE-pre (CB+BS)** | BTC | 69.633 | 100,0% | **150 bps** | **1** |
| | ETH | 64.541 | 100,0% | 100 bps | 0 |
| CALIB (CB+BS+BF) | BTC | **65.043** | 93,4% | 75 bps | **0** |
| | ETH | 64.541 | 100,0% | 100 bps | 0 |
| **LIVE-post (CB+BS+KR)** | BTC | 69.633 | 100,0% | **150 bps** | **1** |
| | ETH | 64.541 | 100,0% | 100 bps | 0 |
Due cose in questa tabella.
**(a) «Zero falsi allarmi in 8 anni» non ha mai descritto la configurazione in produzione.** Quel
numero viene dalla riga CALIB, che ha `bitfinex` nel consenso — e il sorvegliante live non l'ha mai
avuta. Il 65.043 e' la firma: e' esattamente il numero di ore che CLAUDE.md cita al punto (1) del
bullet VENUE WATCH. Sul consenso reale le ore sono 69.633 e i falsi allarmi sono **1**.
**(b) Aggiungere Kraken non lo ripara**, perche' su 8 anni Kraken porta un mese: LIVE-post e
LIVE-pre danno numeri **identici** sulla storia lunga.
Il falso allarme e':
```
2020-03-13 07:00 -> 10:00 UTC 4 ore picco -418,7 bps segno -1
```
Il **crash COVID**. Cioe' proprio l'evento che la memoria elencava come esempio di cio' su cui il
tripwire *non* scattava: *«zero falsi allarmi inclusi crash COVID 2020-03, maggio 2021, LUNA e
novembre 2022»*.
## 3. Perche' spariva a tre referenze — e perche' non e' una buona notizia
Nella stessa finestra, con `bitfinex` nel consenso, **1 delle 4 ore diventa BLIND** (utilizzabili
75%) → il run si spezza, non arriva mai a 4 ore consecutive, l'episodio non esiste. Ma la
dislocazione **e' ancora li'**: mediana **343 bps** su quelle ore.
⚠️ **Lo zero non veniva da un consenso piu' accurato, veniva da un'ora buttata.** «Zero falsi
allarmi» puo' voler dire *piu' preciso* oppure *piu' cieco*, e le due si distinguono solo guardando
la quota di ore utilizzabili — che nella riga CALIB e' 93,4% contro il 100,0% delle altre.
Congelato in due test puri (`tests/test_venue_watch.py`), perche' e' il meccanismo e non il numero
a essere trasferibile:
- `test_una_terza_referenza_non_puo_aumentare_le_ore_utilizzabili` — lo spread max-min e' monotono
nel numero di referenze, quindi «piu' referenze» non e' gratis: sposta verso BLIND;
- `test_una_sola_ora_BLIND_spezza_lo_streak_e_impedisce_l_allarme` — con un caso di controllo che
DEVE scattare, altrimenti il test non proverebbe nulla.
## 4. La direzione dichiarata il 19/08 e' confermata, e la sua TAGLIA dipende dal venue
Confronto **appaiato** (stesse ore, mai due campioni diversi):
| | ore | non-BLIND 2 ref | 3 ref | Δ | \|scarto\| mediano, differenza appaiata |
|---|---|---|---|---|---|
| BTC + bitfinex | 33.000 | 99,95% | 86,04% | **13,91 pp** | 0,04 bps |
| BTC + kraken | 702 | 100,00% | 100,00% | **+0,00 pp** | 0,04 bps |
| ETH + kraken | 702 | 100,00% | 100,00% | +0,00 pp | +0,03 bps |
«Piu' referenze → piu' BLIND» e' vero, ma con Bitfinex costa **14 punti** di ore utilizzabili e con
Kraken **zero**. Non e' una proprieta' del numero tre: e' una proprieta' di **quanto la terza
referenza e' d'accordo con le altre**. Bitfinex nel 2018-2022 era il venue con i problemi bancari —
metterlo nel consenso e' metterci dentro il rumore che il controllo positivo usa come *segnale*.
## 5. Controllo positivo: intatto
Bitfinex 2018-19 come bersaglio, consenso Coinbase+Bitstamp: **22 episodi**, il piu' lungo **2.324
ore**, picco **1.136 bps = 11,4x** la soglia. Il rilevatore vede i casi veri.
## 6. Decisione: la soglia NON si tocca, ma la giustificazione cambia
Il criterio dichiarato in anticipo il 26/07 aveva tre gambe: **(a) zero falsi allarmi in 8 anni**,
(b) margine ≥3x sul caso storico piu' debole (FTX ~300 bps) → soglia ≤100 bps, (c) minima latenza.
**La gamba (a) non e' soddisfatta** dalla configurazione reale. Le altre due reggono, e l'economia
del 26/07 risolve da sola:
- 1 falso allarme in 8 anni = 0,125/anno × 0,248% di costo = **0,031%/anno** di equity attesa,
contro il **100%** che un vero positivo evita → break-even a `p > 0,031%`, e ogni `p` plausibile
(0,5-5%) e' 16-160 volte sopra;
- alzare a 150 bps per ripristinare lo zero porterebbe il margine su FTX da **3,0x a 2,0x**, sotto
il vincolo (b) dichiarato prima di guardare i dati.
**Quindi (100 bps, 4h) resta**, ma perche' **un falso allarme ogni 8 anni e' economico**, non
perche' non ce ne siano. E il numero da citare e' **1 in 8 anni** — che e', ironicamente, proprio
l'esempio che il punto (4) del bullet usava per il break-even: la memoria si contraddiceva da sola
e la meta' giusta era quella dell'economia.
## 7. Cio' che resta aperto
- **La terza referenza non e' validabile sulla storia.** Kraken e' verificata su un mese e su quel
mese non cambia nulla. Se serve una validazione lunga, va cercata una fonte che pagini davvero
(il tetto ~700 e' dell'endpoint pubblico, non di ccxt).
- **Il Rulebook Deribit del 12/08 non lo sorveglia nessuno** (ADL, perdita socializzata, *emergency
powers*, conti dormienti) — invariato dal 19/08.
- **`MAINT_GRACE_HOURS = 2` presume gli annunci invece di leggerli.** Non e' teorico: due
`locked=true` in quattro giorni (18/08 ~4h, 21/08 ~1h).
## REGOLE
- **Un numero di taratura va etichettato con la CONFIGURAZIONE su cui e' stato misurato**, non solo
con la finestra. «Zero falsi allarmi in 8 anni» era vero e inutile: descriveva un consenso che la
produzione non ha mai avuto, e nessun test poteva accorgersene perche' i due percorsi (ricerca e
live) tenevano la lista delle referenze in due posti diversi.
- **Uno "zero" si legge sempre accanto alla quota di campione utilizzabile.** Meno falsi allarmi
perche' si vede meglio e meno perche' si vede di meno hanno lo stesso valore stampato e valore
opposto.
- **Quando due affermazioni della stessa memoria si contraddicono** (qui «zero falsi allarmi» e «a
1 ogni 8 anni serve p > 0,031%»), la contraddizione e' un'informazione: una delle due e' stata
scritta guardando i dati.
- **Il debito dichiarato il 19/08 era corretto ma sottodimensionato:** diceva «il numero non e'
ri-misurato» e il numero era sbagliato **prima** della modifica che lo aveva fatto dichiarare.
+192
View File
@@ -0,0 +1,192 @@
#!/usr/bin/env python
"""r0821_venue_refs.py — la taratura del tripwire di venue, RI-MISURATA a tre referenze.
DEBITO CHIUSO QUI (dichiarato nel diario 2026-08-19 §4 e nel commento di `src/live/venue_watch.py`):
il 19/08 e' stata aggiunta **Kraken** come terza referenza, perche' 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 non furono toccati, ma la frase «zero falsi allarmi in 8 anni» era stata misurata con
l'insieme di referenze di allora — e **quel numero non e' stato ri-misurato**. Il diario dichiarava
solo la DIREZIONE dell'errore («allerta di meno», perche' la mediana e' robusta e lo spread max-min
si allarga -> piu' BLIND, che e' lo stato morbido).
COSA MISURA (riusa il nucleo di r0726_venue_tripwire: `dislocation`, `episodes`,
`zero_fp_frontier` sono IMPORTATE, non riscritte — c'e' un test d'identita' in tests/):
1. la PROFONDITA' REALE di ogni referenza, che e' il fatto che struttura tutto il resto;
2. il limite di Kraken verificato OGGI sulla rete, non creduto da un commento del 26/07;
3. il confronto APPAIATO 2 referenze vs 3 sulle STESSE ore (mai due campioni diversi:
lezione del 26/07, la statistica e' la differenza appaiata, non la differenza fra due mediane)
su: quota di ore utilizzabili (BLIND), distribuzione di |scarto|, frontiera a zero falsi allarmi;
4. il controllo POSITIVO (Bitfinex 2018-19) sotto la regola nuova: un rilevatore tarato per NON
segnalare e' indistinguibile da uno rotto finche' non gli si mette davanti un caso vero.
uv run python scripts/research/r0821_venue_refs.py [--refresh-kraken]
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.research.r0726_venue_tripwire import ( # noqa: E402
_fetch_1h, dislocation, episodes, load_refs, zero_fp_frontier,
)
from src.data.downloader import load_data # noqa: E402
from src.live.venue_watch import PERSIST_HOURS, THRESHOLD_BPS # noqa: E402
ASSETS = ("BTC", "ETH")
# insiemi di consenso da confrontare. 'LIVE-pre' e 'LIVE-post' sono le due configurazioni
# REALI del sorvegliante, prima e dopo il 19/08.
SETS = {
"LIVE-pre (CB+BS)": ("coinbase", "bitstamp"),
"CALIB (CB+BS+BF)": ("coinbase", "bitstamp", "bitfinex"),
"LIVE-post (CB+BS+KR)": ("coinbase", "bitstamp", "kraken"),
}
def _ts(idx) -> pd.DatetimeIndex:
return pd.to_datetime(idx, unit="ms", utc=True)
def profondita(refs: dict) -> None:
print("\n [1/5] PROFONDITA' REALE delle referenze (e' il fatto che struttura tutto il resto)")
print(f"\n {'asset':<6}{'venue':<11}{'barre':>9} {'da':>12} {'a':>12}")
for a in ASSETS:
for eid in ("coinbase", "bitstamp", "bitfinex", "kraken"):
s = refs.get((a, eid), pd.Series(dtype=float))
if len(s):
i = _ts(s.index)
print(f" {a:<6}{eid:<11}{len(s):>9,} {str(i[0].date()):>12} {str(i[-1].date()):>12}")
else:
print(f" {a:<6}{eid:<11}{0:>9} {'':>12} {'':>12} <-- VUOTA")
print("\n ⚠️ Da leggere prima di ogni numero sotto: su ETH `bitfinex` e' VUOTA e `kraken` copre")
print(" un mese -> sulla storia lunga il consenso di ETH e' SEMPRE a due referenze; su BTC")
print(" e' a tre solo fino al 2022-05 (fine della serie bitfinex).")
def limite_kraken(refresh: bool) -> None:
print("\n [2/5] IL LIMITE DI KRAKEN, verificato OGGI sulla rete (non creduto da un commento)")
if not refresh:
print(" saltato (--refresh-kraken per misurarlo); resta il dato in cache del 26/07: 702 barre")
return
d = load_data("BTC", "1h")
s_ms, e_ms = int(d["timestamp"].iloc[0]), int(d["timestamp"].iloc[-1])
s = _fetch_1h("kraken", "BTC/USD", s_ms, e_ms, 720)
if not len(s):
print(" kraken: nessuna barra (fetch fallito) — nessuna conclusione")
return
i = _ts(s.index)
attese = (e_ms - s_ms) // 3_600_000
print(f" chieste {attese:,} ore dal {str(_ts([s_ms])[0].date())}; ricevute {len(s):,} barre "
f"({i[0].date()} -> {i[-1].date()}) = {len(s)/attese*100:.2f}% del richiesto")
print(" -> il tetto ~700 candele e' CONFERMATO: la storia lunga con Kraken non e' ottenibile,")
print(" e questo non e' un problema per il LIVE (gli servono le ultime ore), lo e' per la TARATURA.")
def _frame(a: str, refs: dict, venues: tuple[str, ...]) -> pd.DataFrame:
d = load_data(a, "1h")
der = pd.Series(d["close"].astype(float).values, index=d["timestamp"].astype(int).values)
rr = [refs[(a, e)] for e in venues if len(refs.get((a, e), []))]
return dislocation(der, rr) if len(rr) >= 2 else pd.DataFrame()
def confronto_appaiato(refs: dict) -> None:
"""2 referenze vs 3, sulle STESSE ore. Mai due campioni diversi."""
print("\n [3/5] CONFRONTO APPAIATO 2 referenze vs 3 — sulle stesse ore, mai due campioni diversi")
for a in ASSETS:
for nome3, v3 in (("BF (2018-2022)", ("coinbase", "bitstamp", "bitfinex")),
("KR (1 mese)", ("coinbase", "bitstamp", "kraken"))):
f2 = _frame(a, refs, ("coinbase", "bitstamp"))
f3 = _frame(a, refs, v3)
terza = v3[2]
s3 = refs.get((a, terza), pd.Series(dtype=float))
if not len(f3) or not len(s3):
print(f"\n {a} + {nome3}: terza referenza assente -> confronto NON possibile")
continue
# ore in cui la TERZA referenza esiste davvero: e' li' che il confronto ha senso
comune = f2.index.intersection(f3.index).intersection(s3.index)
if len(comune) < 100:
print(f"\n {a} + {nome3}: solo {len(comune)} ore in comune -> non si conclude")
continue
A, B = f2.loc[comune], f3.loc[comune]
u2, u3 = A["usable"].mean(), B["usable"].mean()
ent = A["usable"] & B["usable"]
d_bps = (B.loc[ent, "bps"].abs() - A.loc[ent, "bps"].abs())
print(f"\n {a} + {nome3} ore in comune {len(comune):,} "
f"({_ts(comune)[0].date()} -> {_ts(comune)[-1].date()})")
print(f" utilizzabili (non-BLIND) 2 ref {u2*100:6.2f}% 3 ref {u3*100:6.2f}%"
f" -> {(u3-u2)*100:+.2f} pp")
print(f" |scarto| mediano 2 ref {A.loc[ent,'bps'].abs().median():6.2f} "
f"3 ref {B.loc[ent,'bps'].abs().median():6.2f} bps"
f" -> differenza APPAIATA mediana {d_bps.median():+.2f} bps")
print(f" |scarto| p99 2 ref {A.loc[ent,'bps'].abs().quantile(.99):6.2f} "
f"3 ref {B.loc[ent,'bps'].abs().quantile(.99):6.2f} bps")
for et, F in (("2 ref", A), ("3 ref", B)):
ep = episodes(F["bps"], F["usable"], THRESHOLD_BPS, PERSIST_HOURS)
print(f" episodi a ({THRESHOLD_BPS:.0f} bps, {PERSIST_HOURS}h) {et}: {len(ep)}")
def frontiera(refs: dict) -> None:
print(f"\n [4/5] FRONTIERA A ZERO FALSI ALLARMI, per insieme di referenze "
f"(punto in produzione: {THRESHOLD_BPS:.0f} bps / {PERSIST_HOURS}h)")
for nome, v in SETS.items():
print(f"\n {nome}")
for a in ASSETS:
f = _frame(a, refs, v)
if not len(f) or f["usable"].sum() < 100:
print(f" {a}: campione insufficiente ({0 if not len(f) else int(f['usable'].sum())} ore utili)")
continue
i = _ts(f.index)
front = dict((h, b) for b, h in zero_fp_frontier(f["bps"], f["usable"]))
ep = episodes(f["bps"], f["usable"], THRESHOLD_BPS, PERSIST_HOURS)
b4 = front.get(PERSIST_HOURS)
print(f" {a}: {int(f['usable'].sum()):,} ore utili "
f"({i[0].date()}->{i[-1].date()}, {f['usable'].mean()*100:.1f}% non-BLIND) "
f"soglia minima a {PERSIST_HOURS}h = {b4 if b4 else 'nessuna'} bps "
f"falsi allarmi a ({THRESHOLD_BPS:.0f},{PERSIST_HOURS}h) = {len(ep)}")
def controllo_positivo(refs: dict) -> None:
print("\n [5/5] CONTROLLO POSITIVO — Bitfinex 2018-19 come BERSAGLIO (consenso CB+BS)")
print(" Un rilevatore tarato per non segnalare e' indistinguibile da uno rotto.")
a = "BTC"
tgt = refs.get((a, "bitfinex"), pd.Series(dtype=float))
base = [refs[(a, e)] for e in ("coinbase", "bitstamp") if len(refs.get((a, e), []))]
if not len(tgt) or len(base) < 2:
print(" serie insufficienti -> controllo NON eseguito (e questo e' un fallimento, non un ok)")
return
f = dislocation(tgt, base)
ep = episodes(f["bps"], f["usable"], THRESHOLD_BPS, PERSIST_HOURS)
if not ep:
print(" ✋ ZERO episodi su Bitfinex: il rilevatore NON vede un caso noto -> taratura da rivedere")
return
dur = sorted((e["hours"] for e in ep), reverse=True)
pk = max(abs(e["peak_bps"]) for e in ep)
print(f" {len(ep)} episodi · piu' lungo {dur[0]:,} ore · picco {pk:,.0f} bps "
f"= {pk/THRESHOLD_BPS:.1f}x la soglia")
print(f" durate delle prime 5: {dur[:5]}")
def main() -> None:
refresh = "--refresh-kraken" in sys.argv
print("=" * 100)
print(" r0821 — TARATURA DEL TRIPWIRE DI VENUE, RI-MISURATA A TRE REFERENZE")
print("=" * 100)
print(f" regola in produzione: |scarto| > {THRESHOLD_BPS:.0f} bps persistente {PERSIST_HOURS}h "
"a segno costante, consenso = MEDIANA delle referenze concordi (>=2).")
refs = load_refs()
profondita(refs)
limite_kraken(refresh)
confronto_appaiato(refs)
frontiera(refs)
controllo_positivo(refs)
print("\n" + "=" * 100)
if __name__ == "__main__":
main()
+64 -2
View File
@@ -1,8 +1,16 @@
"""Test del tripwire di venue (src/live/venue_watch.py + scripts/research/r0726_venue_tripwire.py).
Il test che conta di piu' e' `test_controllo_positivo_*`: un rilevatore che non segnala mai nulla
e' indistinguibile da uno rotto, e questo qui e' TARATO per non segnalare (zero falsi allarmi su
8 anni). Senza un controllo positivo, "non e' mai scattato" non e' una buona notizia.
e' indistinguibile da uno rotto, e questo qui e' TARATO per non segnalare. Senza un controllo
positivo, "non e' mai scattato" non e' una buona notizia.
⚠️ CORREZIONE 2026-08-21 (`scripts/research/r0821_venue_refs.py`): la taratura NON e' "zero falsi
allarmi in 8 anni" sull'insieme di referenze che gira in PRODUZIONE. Quel numero fu misurato con
`bitfinex` nel consenso, che il sorvegliante live non ha mai avuto; sul consenso reale (Coinbase +
Bitstamp, e da oggi + Kraken) i falsi allarmi in 8 anni sono **1**, il 2020-03-13 (crash COVID,
4 ore a -418 bps di picco su BTC) — cioe' proprio l'evento che la memoria citava come esempio di
cio' su cui NON scattava. Il punto (100 bps, 4h) resta invariato per economia, non per assenza di
falsi allarmi: 1 ogni 8 anni costa ~0.031%/anno di equity attesa contro il 100% che evita.
"""
from __future__ import annotations
@@ -354,3 +362,57 @@ def test_la_tabella_dichiarata_combacia_col_venue_oggi():
"BTC-PERPETUAL": {"tick_size": 0.5, "min_trade_amount": 10.0, "contract_size": 10.0},
"ETH-PERPETUAL": {"tick_size": 0.05, "min_trade_amount": 1.0, "contract_size": 1.0},
}) == []
# ===========================================================================
# MECCANICA scoperta il 2026-08-21 ri-misurando la taratura a tre referenze.
# Sono i due fatti PURI che spiegano i numeri di r0821_venue_refs.py; i numeri
# empirici stanno nel diario, riprodotti da quello script.
# ===========================================================================
def test_una_terza_referenza_non_puo_aumentare_le_ore_utilizzabili():
"""Aggiungere una referenza puo' solo ALLARGARE lo spread max-min, mai stringerlo ->
la quota di ore utilizzabili e' monotona NON crescente nel numero di referenze.
E' la ragione strutturale per cui "piu' referenze" non e' gratis: la modifica del 19/08
sposta il sistema verso BLIND, che e' lo stato morbido (allerta di MENO, non di piu').
"""
base = [100.0, 100.4] # spread 40 bps: concordi
ok, n, spread = dislocation_bps(100.2, base)
assert ok is not None and n == 2 and spread < THRESHOLD_BPS
# una terza referenza lontana allarga lo spread oltre la soglia -> BLIND, non allarme
fuori, n3, spread3 = dislocation_bps(100.2, base + [102.0])
assert n3 == 3 and spread3 > spread, "lo spread deve essere monotono nel numero di referenze"
assert fuori is None, "referenze in disaccordo -> si SCARTA il campione, non si allerta"
# e una terza referenza CONCORDE non toglie nulla: il caso Kraken misurato il 21/08
dentro, n3b, spread3b = dislocation_bps(100.2, base + [100.2])
assert dentro is not None and n3b == 3 and spread3b >= spread
def test_una_sola_ora_BLIND_spezza_lo_streak_e_impedisce_l_allarme():
"""⚠️ Il fatto che rende ambiguo un "zero falsi allarmi" misurato con piu' referenze.
Lo streak si AZZERA su un'ora BLIND (non si accumula evidenza su dati che non parlano).
Quindi un insieme di referenze che manda in BLIND una sola ora dentro una finestra di
dislocazione fa SPARIRE l'episodio senza che la dislocazione sia diminuita: e' esattamente
cio' che e' successo al falso allarme del 2020-03-13, presente a 2 referenze e assente a 3
perche' 1 delle 4 ore diventava BLIND (dislocazione mediana ancora -343 bps).
Morale: "zero falsi allarmi" puo' voler dire "piu' accurato" oppure "piu' cieco", e le due
si distinguono solo guardando la quota di ore utilizzabili.
"""
st = AssetState()
# 4 ore consecutive oltre soglia a segno costante -> ALERT alla quarta
for _ in range(PERSIST_HOURS - 1):
st, lvl = step(st, -400.0)
assert lvl == "WATCH"
st_alert, lvl = step(st, -400.0)
assert lvl == "ALERT", "il caso di controllo deve scattare, altrimenti il test non prova nulla"
# stessa dislocazione, ma con UNA ora cieca in mezzo -> nessun allarme
st2 = AssetState()
for bps in (-400.0, -400.0, None, -400.0):
st2, lvl2 = step(st2, bps)
assert lvl2 != "ALERT"
assert st2.streak_hours < PERSIST_HOURS, "l'ora BLIND deve azzerare lo streak"