Il giro delle 21:25 (primo con bite gia' cancellato): 576 chiamate, 0 risposte 429, 0 errori. E' il controllo che conta, perche' il guasto del 29/07 era la raffica di bite che saturava il rate limit per-IP. Aggiunta la tabella dei 5 giri della giornata, che mostra invarianza prima/dopo. ⚠️ E una precisazione sul nostro stesso strumento: 'ok' significa ALMENO UN LATO del book, non quota completa. Con ok=572 le righe a due lati sono 432 (75.5%). Che sia strutturale (opzioni molto OTM) e non un degrado si vede dall'invarianza fra i giri, non dal fatto che il numero sembri alto. E' 'una riga presente non e' un dato presente' un livello piu' in giu', applicata allo strumento costruito per quella lezione: la battuta di cuore vedrebbe il guasto del 29/07 (quote vuote) ma non una deriva verso book a un lato solo. Nessuna soglia cablata: con 5 giri di storia sarebbe inventata, e una soglia inventata e' peggio di nessuna soglia. La colonna da guardare e' 'due lati %'. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
12 KiB
2026-07-30 (2º filone) — cerbero-bite viene eliminato: assorbita la raccolta, non il motore
Decisione dell'operatore: /opt/docker/cerbero-bite viene smantellato e il suo lavoro passa a
PythagorasGoal. Esito: book, pesi, config e strategia INVARIATI. Cambia solo chi raccoglie
la catena opzioni — e cambia in meglio su tre assi misurati.
Cosa di bite valeva la pena, e cosa no
L'unica parte irreversibile è il dato: una catena opzioni non si ricostruisce a posteriori (Deribit non serve book storici, non esiste un secondo venue). Tutto il resto è codice, e il codice si riscrive.
| parte | assorbita? | perché |
|---|---|---|
option_chain_snapshots (1.23M righe) |
sì | ha appena falsificato il f di VRP01; irrecuperabile |
market_snapshots (17.402 righe, 26/03+) |
sì | dealer net gamma, gamma flip, rischio liquidazioni, funding cross: dati che il progetto non ha altrove |
| raccolta continua | sì, riscritta | senza, l'archivio si congela e "aspettare il crash" muore |
dvol_history |
no | fetch_dvol.py ha storia più lunga (2020+ vs 2026-05): copiarla sarebbe una seconda copia peggiore |
| motore credit-spread ETH | no | il progetto ha già la regola niente short-vol da modello in deploy; e il conto era a $52 contro un minimo di $720 |
| GUI, kill switch, dead-man, audit chain | no | PythagorasGoal ha già venue_watch, edge_watch, monitor_health, fee_watch |
decisions / positions |
no | 59 valutazioni d'ingresso, 0 posizioni |
Prima di toccare qualsiasi cosa: snapshot VACUUM INTO del DB (461 MB) + audit.log + config in
/opt/docker/backups/manual/cerbero-bite-20260730/, con SHA256.
Il collettore nuovo — tre difetti di bite non replicati
scripts/live/collect_chain.py, cron 25 * * * *.
1. Una chiamata per strumento invece di due. public/get_order_book?depth=3 restituisce già
quote, greche, IV, open interest, volume, book e underlying_price. Bite chiamava ticker e
orderbook separatamente: doppio costo, e i due potevano riferirsi a istanti diversi.
Un get_book_summary_by_currency iniziale dà l'OI di tutta la catena in una chiamata e
prefiltra sotto soglia: 551 → ~290 chiamate per asset.
2. Pacing invece di raffica. ⚠️ Il carico non è mai stato il problema. ~570 chiamate/ora
sono 0.16/s se distribuite; bite le sparava in ~26 secondi (~44/s) e si auto-saturava il
rate limit per-IP — 12.186 risposte 429 in 26 ore, il 96% nel minuto :00. Token bucket a 4/s +
backoff: primo giro reale 574 chiamate, 0 risposte 429.
Il minuto :25 è una scelta, non un default: :00 era la raffica di bite, :07 è cron_book
(da lì passa il feed 5m di SKH01, la cosa che non deve trovare l'IP occupato).
3. Stato esplicito della quota. quote_status ∈ {ok, no_quote, error}:
no_quote= il venue ha risposto, il book è vuoto → fatto di mercatoerror= la chiamata è fallita → fatto di infrastruttura
Bite li faceva collassare entrambi su "riga con bid/ask NULL", ed è per questo che il guasto del
29/07 (50% di quote perse per 38 ore) non ha prodotto nessun segnale: il conteggio righe non
cambiava. Per lo stesso motivo book_depth_top3 ora è NULL su errore, mai 0 — bite scriveva 0,
e "chiamata fallita" diventava indistinguibile da "book vuoto".
⚠️ Le righe ereditate portano quote_status='unknown'. Bite non registrava il perché e a
posteriori non è ricostruibile: si dichiara l'ignoranza invece di inventare uno stato.
Il silenzio, di nuovo
Un collettore fermo non produce niente, e il niente si legge come "nessun dato quel giorno" invece
che come "raccolta rotta" — su una serie irrecuperabile è il modo più caro di sbagliare. Ogni giro
scrive una riga in data/chain_collect/runs.jsonl, anche quando fallisce, e
monitor_health la sorveglia con cadenza 1h e max_age_h=3 (due giri persi). È la stessa lezione
di paper_dvolspread e di fresh_5m, applicata prima che serva invece che dopo.
Un difetto trovato per strada: due formati ISO nella stessa colonna
L'import del contesto restituiva 17.310 righe dal 2026-05-01 contro le 17.402 dal 2026-03-26
del database. 92 righe di backfill sono scritte senza microsecondi
(2026-03-26T12:00:00+00:00), le altre con (2026-05-01T15:45:00.062918+00:00).
pd.to_datetime senza format inferisce un formato solo dal primo elemento e manda gli altri
a NaT, che il dropna a valle rimuoveva in silenzio: la serie perdeva i suoi 5 settimane più
vecchi e la data d'inizio slittava di un mese senza un messaggio.
Corretto con format="ISO8601" e con uno scarto rumoroso (_drop_unparsed stampa quante righe
cadono e perché). REGOLA: dropna dopo un parsing è un rilevatore di difetti travestito da
pulizia — se toglie righe deve dirlo. Un import silenzioso non è un import pulito, è un import
che non sai se ha funzionato.
Eliminazione (stessa sera, 21:20 UTC)
Container rimossi, volume cerbero-bite_bite-data rimosso, immagine cerbero-bite:dev rimossa,
/opt/docker/cerbero-bite cancellata. 12 GB liberati (73G → 61G di disco usato).
Prima di cancellare, quattro verifiche — nessuna delle quali era "il backup esiste":
- Lo snapshot è completo, non solo integro. SHA256
OKsu entrambi i file, ma soprattutto conteggi confrontati tabella per tabella fra il volume vivo e lo snapshot:option_chain_snapshots1.232.212,market_snapshots17.406,decisions59,positions0 — 4 su 4 identici, e identici anche ai parquet importati. Un hash prova che il file non è corrotto, non che contenga tutto. - I 10,6 GB di backup interni al volume non contenevano dati unici. 32 snapshot sqlite storici:
la domanda giusta non era la loro dimensione ma se bite potasse lo storico. Tutti e tre i
campioni controllati (09/06, 01/07, finale) hanno la stessa riga più vecchia
(
2026-05-01T20:53:49.294928) e conteggi monotoni crescenti (218.500 → 665.949 → 946.990 → 1.232.212) → nessuna potatura, sottoinsiemi stretti del DB finale. - Nessuna dipendenza a runtime. Né cron, né systemd, né route traefik, né altri progetti: i
soli riferimenti rimasti sono documentazione (questo diario, CLAUDE.md, un commento in
backup.she uno inhealth-watch.py) — cioè memoria storica, che è corretto lasciare. - ⚠️
cerbero-mcpè un progetto DIVERSO e serve a PythagorasGoal (universo Hyperliquid, e il percorso del conto): progetto compose separato, e la retetraefikche condividono è dichiarataexternal:nel compose di bite →down -vnon la tocca. Verificato dopo:Up 41 hours (healthy). Due servizi con lo stesso prefisso nel nome sono un incidente che aspetta.
Il codice non è stato cancellato: era già su Gitea (Adriano/Cerbero-Bite), e l'unica modifica
locale pendente — il ripiego del collector da :00 a :20, superato dal successore — è stata
committata e pushata come commit di dismissione. Il dato era l'unica parte irreversibile.
REGOLA: prima di cancellare una sorgente si verifica che la copia sia completa, non che esista — e "quanto è grande" non è la domanda da fare a un backup, "cosa contiene che l'originale non ha più" sì.
Il primo giro dopo l'eliminazione, e un modo di leggere male il nostro stesso indicatore
Verificato invece che dedotto (21:25 UTC, il primo giro con bite già cancellato): 576 chiamate, 0 risposte 429, 0 errori. È il controllo che conta, perché il guasto del 29/07 era esattamente la raffica di bite che saturava il rate limit per-IP.
| giro | righe | ok | no_quote | error | due lati |
|---|---|---|---|---|---|
| 20:00 | 570 | 552 | 17 | 1 | 71.6% |
| 20:06 | 570 | 570 | 0 | 0 | 75.8% |
| 20:22 | 570 | 570 | 0 | 0 | 75.6% |
| 20:25 | 570 | 570 | 0 | 0 | 75.6% |
| 21:25 | 572 | 572 | 0 | 0 | 75.5% |
(Il 1 error / 17 no_quote delle 20:00 è il primo giro in assoluto, con bite ancora acceso a
sparare al minuto :00: la firma del problema, catturata nel suo ultimo momento di vita.)
⚠️ ok = 572 non significa "572 quote complete". Nel collettore ok vuol dire almeno un lato
del book (bid is not None or ask is not None); le righe con entrambi i lati sono 432 su
572 (75.5%). Il resto sono book a un lato solo, normali sulle opzioni molto OTM. Che sia
strutturale e non un degrado si vede dal fatto che la percentuale è identica a tutti i giri,
prima e dopo l'eliminazione — non dal fatto che sembri alta.
È la lezione «una riga presente non è un dato presente» un livello più in giù, applicata allo
strumento costruito per quella lezione: la battuta di cuore distingue ok/no_quote/error,
quindi vedrebbe il guasto del 29/07 (quote vuote), ma non vedrebbe una deriva più sottile —
book che diventano progressivamente a un lato solo. Non è cablata una soglia: con cinque giri di
storia sarebbe inventata, e una soglia inventata è peggio di nessuna soglia. La colonna da
guardare quando ci saranno settimane di serie è "due lati %".
File
scripts/live/collect_chain.py+scripts/cron_chain.sh— la raccolta (cron25 * * * *)scripts/analysis/import_cb_archive.py— import una-tantum dell'archivioscripts/analysis/certify_cb_chain.py— certificazione dello store (erafetch_cb_chain.py)scripts/research/cblib.py—load_chain()ora unisce archivio + raccoltasrc/live/monitor_health.py— nuovo speccollect_chaintests/test_collect_chain.py(11 casi)
Addendum (stessa sera) — spegnimento, backup, ripresa dopo un riavvio
Spegnimento. Prima di fermare i container ho preso uno snapshot finale (bite aveva raccolto
fino alle 20:15, oltre il backup delle 19:55) e ho reimportato: archivio a 1.232.212 righe.
Consegna verificata: ultima riga di bite 20:15:00, prima nostra 20:00:18 → sovrapposizione, nessun
buco. docker compose stop (non down -v: i volumi restano). Effetto misurato: 0 risposte 429
su cerbero-mcp nei minuti successivi, contro ~650 per ogni giro orario.
Backup. data/raw/ è gitignored e il backup rotativo della VPS non copriva PythagorasGoal: dopo
lo spegnimento di bite l'archivio esisteva in due copie sullo stesso disco. Aggiunta
do_pythagoras a /opt/docker/scripts/backup.sh con un criterio dichiarato: si salva ciò che non
si può riscaricare. Dentro: catena + contesto, data/paper_* e data/chain_collect (serie
forward-only che alimentano i gate pre-registrati — non ricalcolabili per costruzione),
options_daily, live, venue_watch, fee_watch, config/live.json → 28 MB. Fuori: i ~110 MB
che si riscaricano dai venue.
Due guardie, entrambe provate nei due versi: la funzione fallisce se la catena è assente o
vuota (invece di produrre un archivio che sembra a posto), e verifica che il tar prodotto contenga
davvero la catena. Il caso negativo produce 0 file. Per poterlo provare la radice è
sovrascrivibile via PYG_ROOT — una guardia che non si riesce a far scattare non è distinguibile
da una rotta.
⚠️ /opt/docker/scripts non è un repo git: quella modifica vive solo su disco, e questo diario
è l'unico posto in cui è scritta.
Riavvio. La raccolta riprende da sola: cron è enabled e active, e il collettore non
dipende da docker né da cerbero-mcp (API pubblica Deribit diretta) — il contrario di bite, che era un
container. Verificato girando cron_chain.sh con env -i PATH=/usr/bin:/bin: 574 chiamate, 0
errori, dato e battuta di cuore scritti. ⚠️ Finestra scoperta: fino al :25 successivo, senza
recupero. Il collettore non fa catch-up e non potrebbe: le quote passate non sono richiedibili.