From 8e72d7ad02851d883024cc00bc5a5b85004ed2df Mon Sep 17 00:00:00 2001 From: Adriano Dal Pastro Date: Tue, 2 Jun 2026 16:58:14 +0000 Subject: [PATCH] feat(live): fallback OHLCV a Deribit MAINNET quando Cerbero/testnet e' giu' Deribit testnet (test.deribit.com) va giu' periodicamente (502) e Cerbero lo rilancia -> il runner si bloccava senza dati. Aggiunto CerberoClient.get_historical_mainnet (Deribit MAINNET public, NO-AUTH, paginato sotto il cap ~5000 candele/chiamata) e fallback nel runner: try Cerbero -> on fail/empty usa mainnet. Prezzi REALI (meglio del testnet farlocco per il paper). Verificato durante l'outage: tutti gli 8 strumenti (BTC/ETH + alt _USDC) coperti su mainnet. Log una-tantum all'attivazione/disattivazione del fallback. Caveat: testnet e mainnet hanno prezzi diversi (~9%) -> al primo switch le posizioni aperte su prezzi testnet vanno resettate (transizione pulita). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/live/cerbero_client.py | 35 +++++++++++++++++++++++++++++++++++ src/portfolio/runner.py | 21 +++++++++++++++++++-- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/live/cerbero_client.py b/src/live/cerbero_client.py index 91676b2..a3f16db 100644 --- a/src/live/cerbero_client.py +++ b/src/live/cerbero_client.py @@ -59,6 +59,41 @@ class CerberoClient: }) return data.get("candles", []) + def get_historical_mainnet(self, instrument: str, start_ms: int, end_ms: int, + resolution: str = "60") -> list[dict]: + """FALLBACK: OHLCV da Deribit MAINNET public (www.deribit.com, NO-AUTH) quando Cerbero + (testnet) è giù — es. 502 da test.deribit.com. Prezzi REALI (meglio del testnet per il + paper). Stesso shape di get_historical_v2: [{timestamp(ms),open,high,low,close,volume}]. + Pagina all'indietro (cap Deribit ~5000 candele/chiamata).""" + base = "https://www.deribit.com/api/v2/public/get_tradingview_chart_data" + span = 4900 * int(resolution) * 60 * 1000 # ~4900 candele/chiamata (< cap 5000) + out: list[dict] = [] + cur = end_ms + while cur > start_ms: + s = max(start_ms, cur - span) + try: + resp = requests.get(base, params={"instrument_name": instrument, "resolution": resolution, + "start_timestamp": s, "end_timestamp": cur}, timeout=TIMEOUT) + resp.raise_for_status() + r = resp.json().get("result", {}) + except Exception: + break + if r.get("status") != "ok" or not r.get("ticks"): + break + t = r["ticks"] + out += [{"timestamp": t[i], "open": r["open"][i], "high": r["high"][i], + "low": r["low"][i], "close": r["close"][i], "volume": r["volume"][i]} + for i in range(len(t))] + oldest = min(t) + if oldest >= cur: + break + cur = oldest - 1 + seen, ded = set(), [] + for x in sorted(out, key=lambda d: d["timestamp"]): + if x["timestamp"] not in seen: + seen.add(x["timestamp"]); ded.append(x) + return ded + def get_instruments(self, currency: str, kind: str = "future", exchange: str = "deribit", limit: int = 100) -> list[dict]: """Enumera gli strumenti reali (v2). Usato per risolvere il naming senza hardcoding.""" diff --git a/src/portfolio/runner.py b/src/portfolio/runner.py index d59dbba..0992c9b 100644 --- a/src/portfolio/runner.py +++ b/src/portfolio/runner.py @@ -39,6 +39,9 @@ _LOOKBACK_DAYS = {"1h": 90, "4h": 220, "1d": 440} # margine ampio per il walk-forward. Difensivo: non dipende dal fetch 440g di TSM01/ROT02. _ML_LOOKBACK_DAYS = 365 +# stato del fallback dati: True quando Cerbero (testnet) è giù e usiamo Deribit MAINNET public +_MAINNET_FALLBACK = {"on": False} + def build_worker_for(spec: SleeveSpec, alloc_capital: float, leverage: float, data_dir: Path = DATA_DIR, position_size: float = 0.15): @@ -182,8 +185,22 @@ def run(config_path: str = "portfolios.yml"): for asset, days in asset_days.items(): inst = inst_map.get(asset, f"{asset}-PERPETUAL") start = end - timedelta(days=days) - candles = client.get_historical_v2(inst, start.strftime("%Y-%m-%d"), - end.strftime("%Y-%m-%d"), "1h") + candles = None + try: + candles = client.get_historical_v2(inst, start.strftime("%Y-%m-%d"), + end.strftime("%Y-%m-%d"), "1h") + except Exception: + candles = None + if not candles: + # FALLBACK: Cerbero (testnet) giù -> OHLCV reale da Deribit MAINNET public + candles = client.get_historical_mainnet( + inst, int(start.timestamp() * 1000), int(end.timestamp() * 1000), "60") + if candles and not _MAINNET_FALLBACK["on"]: + _MAINNET_FALLBACK["on"] = True + print("[runner] FALLBACK attivo: OHLCV da Deribit MAINNET (Cerbero/testnet non disponibile)") + elif _MAINNET_FALLBACK["on"]: + _MAINNET_FALLBACK["on"] = False + print("[runner] Cerbero tornato disponibile: fallback mainnet disattivato") if candles: df = pd.DataFrame(candles) df["timestamp"] = df["timestamp"].astype("int64")