Files
PythagorasGoal/docs/diary/2026-07-30-assorbimento-cerbero-bite.md
T
Adriano Dal Pastro 444b804415 ops(backup): PythagorasGoal nel backup rotativo, solo il dato non ricostruibile
data/raw/ e' gitignored e /opt/docker/scripts/backup.sh non copriva questo
progetto: dopo lo spegnimento di cerbero-bite la catena opzioni esisteva in
due copie SULLO STESSO DISCO.

Aggiunta do_pythagoras con criterio dichiarato — si salva cio' che non si
puo' riscaricare:
  dentro (28 MB): catena + contesto, data/paper_* e data/chain_collect
    (serie forward-only che alimentano i gate pre-registrati: non sono
    ricalcolabili, sono un registro di cosa si sapeva e quando),
    options_daily, live, venue_watch, fee_watch, config/live.json
  fuori (~110 MB): quanto si riscarica dai venue (rebuild_history, fetch_dvol,
    fetch_hyperliquid, fetch_ib_equities) + cache

Due guardie provate nei DUE versi: fallisce se la catena e' assente o vuota
invece di produrre un archivio che sembra a posto, e verifica che il tar
contenga davvero la catena (caso negativo: 0 file prodotti). La radice e'
sovrascrivibile via PYG_ROOT solo per poter far scattare la guardia.

Verificato che dopo un riavvio la raccolta riprenda da sola: cron enabled +
active, nessuna dipendenza da docker o cerbero-mcp; giro provato con env -i
(574 chiamate, 0 errori). Finestra scoperta fino al :25 successivo, senza
catch-up.

/opt/docker/scripts NON e' un repo git: la modifica vive solo su disco, il
diario e' l'unico posto in cui e' scritta.

Book, pesi, config, strategia INVARIATI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 20:25:08 +00:00

7.7 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) ha appena falsificato il f di VRP01; irrecuperabile
market_snapshots (17.402 righe, 26/03+) 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 mercato
  • error = 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.

Cosa resta da fare fuori dal repo

Il container di bite è ancora acceso mentre scrivo: la sovrapposizione fra le due raccolte è voluta (nessun buco alla consegna). Quando verrà spento, l'unica cosa che si ferma è la raffica al minuto :00.

File

  • scripts/live/collect_chain.py + scripts/cron_chain.sh — la raccolta (cron 25 * * * *)
  • scripts/analysis/import_cb_archive.py — import una-tantum dell'archivio
  • scripts/analysis/certify_cb_chain.py — certificazione dello store (era fetch_cb_chain.py)
  • scripts/research/cblib.pyload_chain() ora unisce archivio + raccolta
  • src/live/monitor_health.py — nuovo spec collect_chain
  • tests/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.json28 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_ROOTuna 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.