14522262e6
Reset del progetto su fondamenta verificate dopo la scoperta che l'intera libreria "validata OOS" era artefatto di feed contaminato (print fantasma del feed Cerbero TESTNET + storico Binance/USDT). - Storico ricostruito da Deribit MAINNET (ccxt pubblico, tokenless) e CERTIFICATO (certify_feed.py): BTC/ETH puliti su TUTTA la storia (mediana 2-6 bps vs Coinbase USD), integrita' OHLC + coerenza resample (maxΔ 0.00) + cross-venue OK. Alt esclusi (illiquidi/divergenti: LTC/DOGE 50-82% barre flat; XRP/BNB non certificabili). - Verdetto sul feed pulito: FADE / PAIRS / XS01 / TSM01 morti (ogni portafoglio Sharpe -2.3..-3.0, DD ~40%); solo SH01 e frammenti HONEST con segnale residuo, da ri-validare in isolamento. - Cleanup "restart pulito": strategie, stack live (src/live, src/portfolio, runner/executor, yml, docker), ~100 script ricerca/gate, waste/games/ portfolios, dati non certificati + cache e 60+ diari -> archiviati in Old/ (preservati, non cancellati). Diario consolidato in un unico documento. - Skeleton ricerca tenuto: Strategy ABC + indicatori + src/fractal + src/backtest/engine + load_data; tool dati certificati (rebuild_history, certify_feed, audit_feed, multi_source_check). - Universo dati ATTIVO: solo BTC/ETH (5m/15m/1h); guardrail fisico (load_data su alt -> FileNotFoundError). Esecuzione DISABILITATA, conto flat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
147 lines
6.0 KiB
Python
147 lines
6.0 KiB
Python
"""PIANO STORICO DERIBIT — quanta storia copre davvero il venue dove ESEGUIAMO?
|
|
|
|
Obiettivo: scegliere la fonte migliore per ricostruire lo storico di backtest, dato
|
|
che si esegue su Deribit. Principio (gia' misurato in multi_source_check): l'ancora
|
|
giusta e' il VENUE DI ESECUZIONE, non Binance/USDT. Qui rispondo con i numeri a:
|
|
|
|
1. COPERTURA: da quando esiste OHLCV su Deribit MAINNET (ccxt pubblico, no token) per
|
|
gli strumenti che tradiamo — inverse (BTC/ETH-PERPETUAL) e lineari USDC.
|
|
2. TIMEFRAME nativi disponibili su Deribit.
|
|
3. FEDELTA' inverse-vs-lineare (stesso indice? -> posso usare l'inverse, storia lunga,
|
|
come price-series e i lineari recenti sono ridondanti per il PREZZO).
|
|
4. GAP pre-Deribit: quanto indietro vanno le strategie e cosa manca -> da gap-fillare
|
|
con Coinbase USD (spot, NON USDT).
|
|
|
|
Tutto via ccxt pubblico Deribit (= api.deribit.com mainnet, reale). Non modifica nulla.
|
|
|
|
uv run python scripts/analysis/deribit_history_plan.py
|
|
"""
|
|
from __future__ import annotations
|
|
import sys
|
|
from pathlib import Path
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
import numpy as np
|
|
import pandas as pd
|
|
import ccxt
|
|
|
|
DERIBIT = ccxt.deribit({"enableRateLimit": True})
|
|
COINBASE = ccxt.coinbase({"enableRateLimit": True})
|
|
|
|
|
|
def earliest(symbol: str, tf: str = "1d") -> tuple[str | None, int, str | None]:
|
|
"""Trova la prima candela disponibile (probe since 2016) + n candele totali stimate."""
|
|
since = int(pd.Timestamp("2016-01-01", tz="UTC").timestamp() * 1000)
|
|
try:
|
|
rows = DERIBIT.fetch_ohlcv(symbol, tf, since=since, limit=5000)
|
|
except Exception as e:
|
|
return None, 0, f"{type(e).__name__}: {str(e)[:60]}"
|
|
if not rows:
|
|
return None, 0, "no-data"
|
|
first = pd.to_datetime(int(rows[0][0]), unit="ms", utc=True)
|
|
last = pd.to_datetime(int(rows[-1][0]), unit="ms", utc=True)
|
|
return f"{first.date()} -> {last.date()}", len(rows), None
|
|
|
|
|
|
def list_perps() -> dict:
|
|
"""Risolve i simboli ccxt reali dei perp Deribit per BTC/ETH (inverse + lineari)."""
|
|
DERIBIT.load_markets()
|
|
found = {}
|
|
for sym, m in DERIBIT.markets.items():
|
|
if not m.get("swap"):
|
|
continue
|
|
base = m.get("base")
|
|
if base not in ("BTC", "ETH"):
|
|
continue
|
|
settle = m.get("settle")
|
|
kind = "inverse" if m.get("inverse") else "linear"
|
|
found[f"{base}-{kind}({settle})"] = sym
|
|
return found
|
|
|
|
|
|
def fetch_series(ex, symbol, tf, start, end, limit=1000):
|
|
start_ms = int(pd.Timestamp(start, tz="UTC").timestamp() * 1000)
|
|
end_ms = int(pd.Timestamp(end, tz="UTC").timestamp() * 1000)
|
|
tf_ms = ex.parse_timeframe(tf) * 1000
|
|
out, since = {}, start_ms
|
|
while since <= end_ms:
|
|
try:
|
|
rows = ex.fetch_ohlcv(symbol, tf, since=since, limit=limit)
|
|
except Exception:
|
|
break
|
|
if not rows:
|
|
break
|
|
for r in rows:
|
|
if start_ms <= int(r[0]) <= end_ms and r[4]:
|
|
out[int(r[0])] = float(r[4])
|
|
nxt = int(rows[-1][0]) + tf_ms
|
|
if nxt <= since:
|
|
break
|
|
since = nxt
|
|
if len(rows) < limit and since > end_ms:
|
|
break
|
|
return pd.Series(out)
|
|
|
|
|
|
def dev_bps(a: pd.Series, b: pd.Series) -> tuple[int, float, float, float]:
|
|
df = pd.concat([a.rename("a"), b.rename("b")], axis=1, join="inner")
|
|
if len(df) == 0:
|
|
return 0, 0, 0, 0
|
|
d = (df["a"] - df["b"]).abs() / df["b"] * 1e4
|
|
return len(df), float(d.median()), float(d.quantile(.95)), float(d.max())
|
|
|
|
|
|
def main():
|
|
print("=" * 84)
|
|
print(" PIANO STORICO DERIBIT MAINNET (ccxt pubblico, reale)")
|
|
print("=" * 84)
|
|
|
|
print("\n[1] Simboli perp Deribit BTC/ETH risolti:")
|
|
perps = list_perps()
|
|
for k, v in perps.items():
|
|
print(f" {k:<22s} -> {v}")
|
|
|
|
print("\n[2] COPERTURA storica (1d, probe da 2016):")
|
|
print(f" {'strumento':<22s}{'range disponibile':<28s}{'giorni':>8s}")
|
|
cov = {}
|
|
for k, sym in perps.items():
|
|
rng, n, err = earliest(sym, "1d")
|
|
cov[k] = (sym, rng, n)
|
|
print(f" {k:<22s}{(rng or err or '-'):<28s}{n:>8d}")
|
|
|
|
print("\n[3] TIMEFRAME nativi Deribit (test su BTC inverse, oggi):")
|
|
bsym = next((s for k, s in perps.items() if k.startswith("BTC-inverse")), "BTC/USD:BTC")
|
|
tfs = []
|
|
for tf in ("1m", "3m", "5m", "10m", "15m", "30m", "1h", "2h", "3h", "4h", "6h", "12h", "1d"):
|
|
try:
|
|
r = DERIBIT.fetch_ohlcv(bsym, tf, limit=3)
|
|
tfs.append(tf if r else f"{tf}:vuoto")
|
|
except Exception:
|
|
tfs.append(f"{tf}:NO")
|
|
print(f" ok: {[t for t in tfs if ':' not in t]}")
|
|
print(f" ko: {[t for t in tfs if ':' in t]}")
|
|
|
|
print("\n[4] FEDELTA' inverse-vs-lineare USDC (close 1h, ultimi ~40g):")
|
|
for base in ("BTC", "ETH"):
|
|
inv = next((s for k, s in perps.items() if k.startswith(f"{base}-inverse")), None)
|
|
lin = next((s for k, s in perps.items() if k.startswith(f"{base}-linear")), None)
|
|
if not inv or not lin:
|
|
print(f" {base}: manca inverse o lineare"); continue
|
|
a = fetch_series(DERIBIT, inv, "1h", "2026-04-15", "2026-05-27")
|
|
b = fetch_series(DERIBIT, lin, "1h", "2026-04-15", "2026-05-27")
|
|
n, med, p95, mx = dev_bps(a, b)
|
|
print(f" {base}: barre={n} inverse-vs-lineare med {med:.1f} bps p95 {p95:.1f} max {mx:.1f}")
|
|
|
|
print("\n[5] GAP pre-Deribit: Deribit inverse vs Coinbase USD su finestra PROFONDA (2020-06, 1d):")
|
|
for base in ("BTC", "ETH"):
|
|
inv = next((s for k, s in perps.items() if k.startswith(f"{base}-inverse")), None)
|
|
a = fetch_series(DERIBIT, inv, "1d", "2020-06-01", "2020-09-01")
|
|
b = fetch_series(COINBASE, f"{base}/USD", "1d", "2020-06-01", "2020-09-01", limit=300)
|
|
n, med, p95, mx = dev_bps(a, b)
|
|
cov_first = cov.get(f"{base}-inverse(BTC)" if base == "BTC" else f"{base}-inverse(ETH)", (None, "?", 0))[1]
|
|
print(f" {base}: Deribit-vs-Coinbase barre={n} med {med:.1f} bps p95 {p95:.1f} max {mx:.1f}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|