"""FEED LIVE EFFIMERO per il segnale SKH01 (book a 230m) — NON tocca i dati certificati su disco. SKH01 decide su griglia 230m: per eseguirlo fedelmente il segnale serve fresco all'ultima barra chiusa. Il rebuild certificato (rebuild_history.py) gira 1×/giorno e fa un rebuild COMPLETO (pesante): girarlo ogni ora sarebbe sbagliato e violerebbe la regola "aggiornare lo storico SOLO con rebuild_history + certificare". Quindi qui NON scriviamo su disco: carichiamo il 5m CERTIFICATO e gli appendiamo IN MEMORIA una coda recente presa da Deribit PUBBLICO (ccxt, tokenless, STESSO simbolo inverse del feed certificato -> prezzi entro ~3 bps). I dati certificati restano la verità su disco; questa estensione vive solo nel processo live e per il calcolo del segnale. Robusto ai fallimenti: qualunque errore di rete/fetch -> ritorna il feed certificato invariato (il runner degrada a "fermo all'ultimo dato certificato", mai opera a cieco). Solo SKH01 ne ha bisogno: TP01 è giornaliero e gira bene sul feed certificato. """ from __future__ import annotations import time import pandas as pd from src.data.downloader import load_data # STESSO simbolo del feed certificato (vedi scripts/analysis/rebuild_history.DERIBIT_INSTR): # inverse USD perp, storia lunga, entro ~3 bps dal lineare USDC su cui eseguiamo. DERIBIT_SYMBOL = {"BTC": "BTC/USD:BTC", "ETH": "ETH/USD:ETH"} SCHEMA = ["timestamp", "open", "high", "low", "close", "volume"] def _fetch_recent_5m(symbol: str, lookback_days: int) -> pd.DataFrame: """Coda recente di 5m da Deribit pubblico (ccxt). Paginazione in avanti. Solo letture pubbliche.""" import ccxt ex = ccxt.deribit({"enableRateLimit": True}) tf_ms = 5 * 60 * 1000 since = int((time.time() - lookback_days * 86400) * 1000) rows: dict[int, list] = {} guard = 0 while guard < 200: guard += 1 try: r = ex.fetch_ohlcv(symbol, "5m", since=since, limit=1000) except Exception: break r = [x for x in r if int(x[0]) >= since] if not r: break for x in r: t = int(x[0]) rows[t] = [t, float(x[1]), float(x[2]), float(x[3]), float(x[4]), float(x[5] or 0)] nxt = int(r[-1][0]) + tf_ms if nxt <= since: break since = nxt if not rows: return pd.DataFrame(columns=SCHEMA) return pd.DataFrame(rows.values(), columns=SCHEMA).sort_values("timestamp").reset_index(drop=True) def merge_tail(base: pd.DataFrame, tail: pd.DataFrame) -> pd.DataFrame: """Fonde la coda fresca sul feed certificato: concat, dedup per timestamp (la coda VINCE sui duplicati, ma le barre certificate storiche restano), riordina. Mantiene lo schema di load_data (inclusa 'datetime' se presente). PURA, testabile senza rete.""" if tail is None or tail.empty: return base cols = [c for c in SCHEMA if c in base.columns] t = tail[[c for c in SCHEMA if c in tail.columns]].copy() merged = pd.concat([base[cols], t], ignore_index=True) merged = merged.drop_duplicates("timestamp", keep="last").sort_values("timestamp").reset_index(drop=True) # ricostruisci 'datetime' coerente (build_frames non la usa, ma load_data la espone) merged["datetime"] = pd.to_datetime(merged["timestamp"], unit="ms", utc=True) return merged def fresh_5m(asset: str, lookback_days: int = 12) -> pd.DataFrame: """Feed 5m certificato + coda recente effimera (in-memory). Fallback al certificato su errore.""" base = load_data(asset, "5m") sym = DERIBIT_SYMBOL.get(asset) if sym is None: return base try: tail = _fetch_recent_5m(sym, lookback_days) except Exception: return base return merge_tail(base, tail)