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) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-02 16:58:14 +00:00
parent 87b420f459
commit 8e72d7ad02
2 changed files with 54 additions and 2 deletions
+35
View File
@@ -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."""
+19 -2
View File
@@ -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")