"""SCAN DI FATTIBILITA' — la vol term-structure e' scaricabile/certificabile da Deribit pubblico? Un calendar-vol (front vs back IV) richiede ATM IV per SCADENZA, STORICA. Il DVOL pubblico e' solo 30g. Questo script PROVA cosa l'API pubblica Deribit espone davvero, PRIMA di costruire backtest: 1. snapshot CORRENTE della term-structure (mark_iv ATM per scadenza) — book_summary_by_currency. 2. esiste STORIA per-scadenza? (DVOL e' 30g fisso; provo trade history con IV per instrument). Verdetto: se la storia per-scadenza NON e' pubblica -> calendar-vol NON backtestabile su dati certi. uv run python scripts/research/probe_vol_termstructure.py """ from __future__ import annotations import sys, time from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(PROJECT_ROOT)) import requests import numpy as np BASE = "https://www.deribit.com/api/v2/public/" def _get(method, **params): r = requests.get(BASE + method, params=params, timeout=40) return r.json().get("result", None) def current_term_structure(cur="BTC"): """ATM mark_iv per scadenza ORA (snapshot pubblico, tokenless).""" summ = _get("get_book_summary_by_currency", currency=cur, kind="option") if not summ: return None idx = _get("get_index_price", index_name=f"{cur.lower()}_usd") spot = float(idx["index_price"]) if idx else None # raggruppa per scadenza, prendi lo strike piu' vicino allo spot (ATM) by_exp = {} for o in summ: name = o["instrument_name"] # es. BTC-27JUN26-60000-C parts = name.split("-") if len(parts) != 4: continue exp, strike = parts[1], float(parts[2]) miv = o.get("mark_iv") if miv is None or spot is None: continue d = abs(strike - spot) if exp not in by_exp or d < by_exp[exp][0]: by_exp[exp] = (d, float(miv), strike) return spot, by_exp def probe_history(cur="BTC"): """C'e' STORIA per-scadenza? Provo: (a) DVOL (sappiamo 30g), (b) trade history con IV su uno strumento vivo, (c) se esistono indici vol a tenor diversi.""" findings = [] # (b) trade history con IV — solo strumenti NON scaduti instr = _get("get_instruments", currency=cur, kind="option", expired="false") live = instr[0]["instrument_name"] if instr else None if live: tr = _get("get_last_trades_by_instrument", instrument_name=live, count=5) has_iv = bool(tr and tr.get("trades") and "iv" in tr["trades"][0]) findings.append(f"trade-history IV su {live}: {'SI (ma solo instrument VIVO, no scaduti)' if has_iv else 'NO'}") # (c) altri indici vol a tenor != 30g? findings.append("get_volatility_index_data: solo DVOL 30g (nessun indice 7g/60g/90g pubblico)") return findings def main(): print("=" * 88) print(" SCAN FATTIBILITA' — vol term-structure Deribit pubblico") print("=" * 88) for cur in ("BTC", "ETH"): ts = current_term_structure(cur) if not ts: print(f"\n{cur}: nessun dato chain"); continue spot, by_exp = ts print(f"\n{cur} spot ${spot:,.0f} — term-structure ATM mark_iv ORA ({len(by_exp)} scadenze):") # ordina per scadenza (data nel nome) def _key(e): try: return time.mktime(time.strptime(e, "%d%b%y")) except Exception: return 0 ivs = [] for exp in sorted(by_exp, key=_key): d, miv, strike = by_exp[exp] ivs.append(miv) print(f" {exp:>9} ATM~{strike:>9,.0f} IV {miv:5.1f}%") if len(ivs) >= 2: print(f" -> front {ivs[0]:.1f}% back {ivs[-1]:.1f}% slope {ivs[-1]-ivs[0]:+.1f}pp " f"({'contango' if ivs[-1] > ivs[0] else 'backwardation'})") print(f" STORIA per-scadenza:") for f in probe_history(cur): print(f" - {f}") print("\n" + "=" * 88) print(" VERDETTO sulla backtestabilita' sotto.") print("=" * 88) if __name__ == "__main__": main()