"""CROSS-CHECK MULTI-FONTE — "chi ci dice che Binance e' corretto?" Risponde col numero, non con la fede: scarica BTC/ETH 1h da 4 venue INDIPENDENTI (Binance USDT, Coinbase USD, Kraken USD, Deribit perp mainnet REALE via ccxt), li allinea per timestamp e misura di quanto divergono dal CONSENSO (mediana cross-venue). Due finestre: A) regime CALMO recente -> baseline di concordanza B) STRESS depeg USDT (mag 2022) -> dove l'assunzione "Binance=verita'" si crepa (BTC/USDT != BTC/USD quando USDT perde il peg; noi eseguiamo USDC) NON modifica nulla. Solo lettura + report. Deribit via ccxt = API pubblica MAINNET (reale). uv run python scripts/analysis/multi_source_check.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 TF = "1h" TF_MS = 60 * 60 * 1000 # (label, ccxt_id, {asset: symbol}, per_request_limit) SOURCES = [ ("binance(USDT)", "binance", {"BTC": "BTC/USDT", "ETH": "ETH/USDT"}, 1000), ("okx(USDT)", "okx", {"BTC": "BTC/USDT", "ETH": "ETH/USDT"}, 100), ("coinbase(USD)", "coinbase", {"BTC": "BTC/USD", "ETH": "ETH/USD"}, 300), ("kraken(USD)", "kraken", {"BTC": "BTC/USD", "ETH": "ETH/USD"}, 720), ("deribit(perp)", "deribit", {"BTC": "BTC/USD:BTC", "ETH": "ETH/USD:ETH"}, 1000), ] WINDOWS = [ ("CALMO recente", "2026-04-15", "2026-05-27"), ("STRESS depeg USDT","2022-05-08", "2022-05-16"), ] def _ex(eid): return getattr(ccxt, eid)({"enableRateLimit": True}) def fetch(eid, symbol, start_ms, end_ms, limit): """OHLCV 1h paginato -> dict ts_ms -> close. Tollerante agli errori per-venue.""" try: ex = _ex(eid) ex.load_markets() except Exception as e: return None, f"load_markets {type(e).__name__}: {str(e)[:60]}" if symbol not in ex.markets: # prova l'id grezzo (es. BTC-PERPETUAL) come fallback alt = symbol.split("/")[0] + "-PERPETUAL" symbol = alt if alt in ex.markets else symbol out, since, last_err = {}, start_ms, None while since <= end_ms: try: rows = ex.fetch_ohlcv(symbol, TF, since=since, limit=limit) except Exception as e: last_err = f"{type(e).__name__}: {str(e)[:60]}" break if not rows: break for r in rows: t = int(r[0]) if start_ms <= t <= end_ms and r[4]: out[t] = 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 if not out: return None, last_err or "no-data (storia non servita dalla venue)" return out, None def analyze(asset, wname, start, end): start_ms = int(pd.Timestamp(start, tz="UTC").timestamp() * 1000) end_ms = int(pd.Timestamp(end, tz="UTC").timestamp() * 1000) series, notes = {}, {} for label, eid, syms, lim in SOURCES: d, err = fetch(eid, syms[asset], start_ms, end_ms, lim) if d: series[label] = pd.Series(d, name=label) else: notes[label] = err if len(series) < 2: print(f" {asset} [{wname}] insufficienti venue ({list(series)}) — {notes}") return df = pd.concat(series.values(), axis=1, join="inner").sort_index() if len(df) == 0: print(f" {asset} [{wname}] nessun timestamp comune fra {list(series)}") return cons = df.median(axis=1) # consenso = mediana cross-venue print(f"\n {asset} [{wname}] barre comuni={len(df)} venue={len(df.columns)}") if notes: for k, v in notes.items(): print(f" (assente: {k} — {v})") print(f" {'venue':<15s}{'med.bps':>9s}{'p95.bps':>9s}{'max.bps':>9s}" f"{'>10bps':>8s}{'>50bps':>8s}") for col in df.columns: dev = (df[col] - cons).abs() / cons * 1e4 # bps di scostamento dal consenso print(f" {col:<15s}{dev.median():>9.1f}{dev.quantile(.95):>9.1f}" f"{dev.max():>9.1f}{(dev>10).mean()*100:>7.1f}%{(dev>50).mean()*100:>7.1f}%") # spread max-min fra venue, barra per barra (quanto e' "largo" il disaccordo) spread = (df.max(axis=1) - df.min(axis=1)) / cons * 1e4 print(f" -> spread cross-venue (max-min): med {spread.median():.1f} bps, " f"p95 {spread.quantile(.95):.1f} bps, max {spread.max():.1f} bps") def main(): print("=" * 78) print(" CROSS-CHECK MULTI-FONTE — scostamento dal CONSENSO (mediana cross-venue), in bps") print(" 1 bps = 0.01%. 'Binance corretto' == vicino al consenso di venue indipendenti?") print("=" * 78) for wname, start, end in WINDOWS: print(f"\n--- FINESTRA: {wname} ({start} -> {end}) ---") for asset in ("BTC", "ETH"): analyze(asset, wname, start, end) if __name__ == "__main__": main()