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:
@@ -27,7 +27,7 @@ from scipy.stats import norm
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
RAW = ROOT / "data" / "raw"
|
||||
CHAIN = RAW / "cb_chain.parquet"
|
||||
STORE = RAW / "cb_chain" # bite_archive.parquet (una-tantum) + YYYY-MM-DD.parquet (nostri)
|
||||
|
||||
# finestra "settimanale" di VRP01 (tenor_d=7): si accettano 4..10 DTE come in options_vrp_calibrate
|
||||
DTE_LO, DTE_HI = 4.0, 10.0
|
||||
@@ -38,14 +38,26 @@ SHORT_DELTA, LONG_DELTA = -0.28, -0.10
|
||||
|
||||
@lru_cache(maxsize=4)
|
||||
def load_chain() -> pd.DataFrame:
|
||||
if not CHAIN.exists():
|
||||
"""Tutta la catena: archivio ereditato da cerbero-bite + raccolta propria, in una serie sola.
|
||||
|
||||
Le due fonti restano distinguibili dalla colonna `source` (`bite:live`, `bite:research`, `pyg`)
|
||||
e da `quote_status` (`unknown` per l'archivio, che non registrava il perche' di una quota
|
||||
assente). La deduplica su (ts, instrument_name) tiene l'ULTIMA occorrenza: se un giorno e'
|
||||
coperto da entrambe le fonti vince la piu' recente, cioe' la nostra.
|
||||
"""
|
||||
if not STORE.exists():
|
||||
raise FileNotFoundError(
|
||||
f"{CHAIN} assente — estrai prima con scripts/analysis/fetch_cb_chain.py"
|
||||
f"{STORE} assente — importa l'archivio con scripts/analysis/import_cb_archive.py "
|
||||
f"e raccogli con scripts/live/collect_chain.py"
|
||||
)
|
||||
df = pd.read_parquet(CHAIN)
|
||||
parts = sorted(STORE.glob("*.parquet"))
|
||||
if not parts:
|
||||
raise FileNotFoundError(f"{STORE} vuoto")
|
||||
df = pd.concat([pd.read_parquet(p) for p in parts], ignore_index=True)
|
||||
df["ts"] = pd.to_datetime(df["ts"], utc=True)
|
||||
df["exp"] = pd.to_datetime(df["exp"], utc=True)
|
||||
return df
|
||||
df["dte"] = (df["exp"] - df["ts"]).dt.total_seconds() / 86400.0
|
||||
return df.drop_duplicates(subset=["ts", "instrument_name"], keep="last").reset_index(drop=True)
|
||||
|
||||
|
||||
def puts(asset: str, df: pd.DataFrame | None = None) -> pd.DataFrame:
|
||||
|
||||
Reference in New Issue
Block a user