8c3868cb31
Angolo term-structure DVOL / calendar-vol. Scan di fattibilita' PRIMA del backtest:
la storia per-scadenza NON e' pubblica su Deribit (DVOL solo 30g; trade-history IV
solo per strumenti vivi; il front rotola/espira -> serie continua front-vs-back
irricostruibile). Per la metodologia (niente edge senza OOS su dati certi) -> STOP,
niente backtest su uno snapshot.
Unica via legittima: costruire il dato IN AVANTI. Aggiunto logger forward idempotente
che interpola l'ATM IV a tenor fissi {7,30,60,90,180}g e accumula data/raw/vol_term_*.
Seminati i primi snapshot (oggi: contango lieve su BTC/ETH). Non auto-cablato in cron.
- scripts/research/probe_vol_termstructure.py (scan fattibilita')
- scripts/research/log_vol_termstructure.py (forward dataset builder)
- tests/test_vol_termstructure.py (interpolazione offline)
- docs/diary/2026-06-26-vol-termstructure-feasibility.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
101 lines
4.0 KiB
Python
101 lines
4.0 KiB
Python
"""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()
|