feat(chain): assorbita la raccolta catena opzioni, cerbero-bite dismesso
cerbero-bite viene eliminato. L'unica sua parte irreversibile e' il DATO:
una catena opzioni non si ricostruisce a posteriori (Deribit non serve book
storici, non c'e' un secondo venue). Il codice si riscrive; le ore non
raccolte no.
ASSORBITO
- scripts/live/collect_chain.py + scripts/cron_chain.sh (cron 25 * * * *):
raccolta propria, ~570 strumenti/giro, ~3 min.
- scripts/analysis/import_cb_archive.py: archivio 1.23M righe (2026-05-01+)
+ market_snapshots 17.402 righe (2026-03-26+: dealer gamma, gamma flip,
rischio liquidazioni, funding cross — dati che non abbiamo altrove).
- snapshot sqlite integrale in /opt/docker/backups/manual/ (SHA256).
NON ASSORBITO, con motivo: motore credit-spread ETH (regola "niente
short-vol da modello in deploy", conto a $52 contro minimo $720), GUI, kill
switch/dead-man/audit (abbiamo venue_watch/edge_watch/monitor_health/
fee_watch), dvol_history (fetch_dvol.py ha storia PIU' LUNGA: 2020+ contro
2026-05), decisions/positions (0 posizioni).
TRE DIFETTI DI BITE NON REPLICATI, tutti misurati il 30/07:
1. una chiamata per strumento invece di due (get_order_book?depth=3 da' gia'
quote+greche+IV+OI+book+underlying) + prefiltro OI in una chiamata sola:
551 -> ~290 chiamate per asset;
2. pacing invece di raffica. Il carico non e' mai stato il problema: 570
chiamate/ora = 0.16/s DISTRIBUITE; bite le sparava in 26s (~44/s) e si
auto-saturava il rate limit per-IP (12.186 risposte 429 in 26h, 96% al
minuto :00). Primo giro reale: 574 chiamate, 0 risposte 429. Il minuto :25
e' scelto: :00 era la raffica, :07 e' cron_book (feed 5m di SKH01).
3. quote_status esplicito {ok, no_quote, error} e book_depth NULL su errore
mai 0. "Book vuoto" e "chiamata fallita" sono cose diverse: e' per questo
che il guasto del 29/07 (50% di quote perse, 38 ore) non produsse alcun
segnale. Le righe ereditate restano 'unknown': bite non lo registrava e a
posteriori non e' ricostruibile.
Battuta di cuore in data/chain_collect/runs.jsonl anche a giro fallito,
sorvegliata da monitor_health (1h, max_age 3h): un collettore fermo non
produce niente, e il niente si legge come "nessun dato quel giorno".
Difetto trovato per strada: due formati ISO nella stessa colonna (92 righe
di backfill senza microsecondi). pd.to_datetime senza `format` ne inferisce
uno solo e manda gli altri a NaT -> il dropna a valle li toglieva in
silenzio, e la serie di contesto perdeva 5 settimane slittando dal 26/03 al
01/05. Corretto con format="ISO8601" e scarto RUMOROSO.
Book, pesi, config, strategia INVARIATI. 537 test verdi.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# 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 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.py` — `load_chain()` ora unisce archivio + raccolta
|
||||
- `src/live/monitor_health.py` — nuovo spec `collect_chain`
|
||||
- `tests/test_collect_chain.py` (11 casi)
|
||||
Reference in New Issue
Block a user