Files
PythagorasGoal/scripts/research/r0724_daily_opt_snapshot.py
T
Adriano Dal Pastro 00996640fb research(cases): 3 agenti live sui 3 case — PM<->Deribit SKIP (gap=spec non alpha), HLP SKIP/WATCH (carry ex-evento 0.2%/a), 0DTE capture avviata
PM: 34 binarie riconciliate vs daily options; il gap 11pp si decompone in basis USDT
(+4-5pp ATM), tempo 8h (-+5pp) e replica su book morti; residuo tail 2-3pp sotto costi;
trade coperto reale: lock /bin/bash.1-5, P(conflitto gambe) 2-22%, margine SM domina -> SKIP.
HLP: serie daily trovata (wHLP): Sharpe 0.69 non ~2, +12% 2026 = 1 evento, ex-evento
+0.2%/a, coda JELLY = inventario ereditato troncato da voto discrezionale, Kelly f*=0
-> SKIP/WATCH con trigger meccanici. 0DTE: fee cap binding = drag 2-3x weekly, IV<RV
al front stanotte; snapshot pipeline scritta e primo capture su disco; decisione
pre-registrata a 90g di serie. Book INVARIATO.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 22:46:59 +00:00

151 lines
6.8 KiB
Python

"""SNAPSHOT catena opzioni DAILY (0-1DTE) Deribit BTC/ETH — cattura quote REALI per il dossier 0DTE.
CONTESTO (2026-07-24). VRP01 (put credit spread settimanale) e' deploy-bloccato dalla regola
"niente short-vol da modello": serve una serie di premi REALI, non BS-su-DVOL. Le scadenze DAILY
accumulano 365 expiry/anno (7x le weekly): in ~6 mesi di cattura si ottiene una serie di ~180
premi reali a tenor giornaliero + ~26 weekly, abbastanza per stimare il VRP daily NETTO di
bid/ask e fee (misura live 2026-07-24: haircut mid->netto ~30-50% a tenor daily, fee cap 12.5%
quasi sempre binding perche' i premi daily sono < 0.0024 base ccy).
COSA FA (sola lettura, API pubblica, nessun ordine, nessun token):
- per BTC e ETH: public/get_instruments (kind=option) -> tutte le scadenze entro --max-dte-h
(default 50h = la daily 0DTE + la daily 1DTE appena listata);
- per ogni strumento nel ladder di moneyness (default 75-125%): public/ticker ->
bid/ask/depth, mark, mark_iv, bid_iv/ask_iv, greche, OI, volume, underlying;
- 1 riga JSON per strumento ("rec":"chain") + 1 riga meta per valuta ("rec":"meta", con
index price e DVOL corrente) APPESE a data/options_daily/snapshots.jsonl.
CADENZA RACCOMANDATA (quando/se si decide di cablarla — NON e' in cron adesso):
- 08:05 UTC: subito dopo il listing della nuova daily (~24h DTE) = il premio "vendibile";
- 07:55 UTC: subito prima del settle (08:00 UTC) = chiude il ciclo (payoff realizzato).
Con 2 run/giorno: ~160 strumenti/run, ~350 byte/riga -> ~120 KB/giorno, ~40 MB/anno. Banale.
Ogni run extra (es. 12:00/20:00) aggiunge la dimensione intraday dello spread: opzionale.
USO:
uv run python scripts/research/r0724_daily_opt_snapshot.py
uv run python scripts/research/r0724_daily_opt_snapshot.py --currencies BTC --max-dte-h 30
NB ONESTO: questo script MISURA, non decide. La serie che produce serve a rispondere fra ~6 mesi
a: (1) IV daily vs RV daily netto haircut, (2) quanto spesso il gate IV-rank aprirebbe a tenor
daily, (3) f di stress reale quando capita un crash dentro la finestra di cattura.
"""
from __future__ import annotations
import argparse
import json
import sys
import time
from pathlib import Path
import requests
PROJECT_ROOT = Path(__file__).resolve().parents[2]
OUT_DIR = PROJECT_ROOT / "data" / "options_daily"
OUT_FILE = OUT_DIR / "snapshots.jsonl"
API = "https://www.deribit.com/api/v2/public"
SESSION = requests.Session()
def api(endpoint: str, **params):
"""GET pubblico con retry breve. Ritorna result o solleva."""
last = None
for _ in range(4):
try:
r = SESSION.get(f"{API}/{endpoint}", params=params, timeout=20)
j = r.json()
if "result" in j:
return j["result"]
last = j.get("error")
except Exception as e: # rete/JSON: ritenta
last = str(e)
time.sleep(0.7)
raise RuntimeError(f"Deribit API fail {endpoint} {params}: {last}")
def snapshot_currency(cur: str, max_dte_h: float, mny_lo: float, mny_hi: float) -> list[dict]:
now_ms = int(time.time() * 1000)
snap_ts = now_ms
rows: list[dict] = []
instruments = api("get_instruments", currency=cur, kind="option", expired="false")
spot = api("get_index_price", index_name=f"{cur.lower()}_usd")["index_price"]
try:
dvol_data = api("get_volatility_index_data", currency=cur,
start_timestamp=now_ms - 3_600_000, end_timestamp=now_ms,
resolution=3600).get("data", [])
dvol = float(dvol_data[-1][4]) if dvol_data else None
except Exception:
dvol = None
chain = [i for i in instruments
if (i["expiration_timestamp"] - now_ms) / 3.6e6 <= max_dte_h
and mny_lo * spot <= i["strike"] <= mny_hi * spot]
chain.sort(key=lambda i: (i["expiration_timestamp"], i["strike"], i["option_type"]))
expiries = sorted({i["expiration_timestamp"] for i in chain})
rows.append({
"rec": "meta", "snap_ts": snap_ts, "currency": cur, "index_price": spot,
"dvol": dvol, "n_instruments": len(chain), "expiries": expiries,
"max_dte_h": max_dte_h, "moneyness": [mny_lo, mny_hi],
})
for inst in chain:
name = inst["instrument_name"]
try:
t = api("ticker", instrument_name=name)
except RuntimeError as e:
print(f" WARN ticker {name}: {e}", file=sys.stderr)
continue
g = t.get("greeks") or {}
st = t.get("stats") or {}
rows.append({
"rec": "chain", "snap_ts": snap_ts, "currency": cur, "instrument": name,
"expiry_ts": inst["expiration_timestamp"],
"dte_h": round((inst["expiration_timestamp"] - snap_ts) / 3.6e6, 3),
"strike": inst["strike"], "type": inst["option_type"],
"settlement_period": inst.get("settlement_period"),
"min_trade_amount": inst.get("min_trade_amount"),
"taker_comm": inst.get("taker_commission"),
"bid": t.get("best_bid_price"), "ask": t.get("best_ask_price"),
"bid_amount": t.get("best_bid_amount"), "ask_amount": t.get("best_ask_amount"),
"mark": t.get("mark_price"), "mark_iv": t.get("mark_iv"),
"bid_iv": t.get("bid_iv"), "ask_iv": t.get("ask_iv"),
"delta": g.get("delta"), "gamma": g.get("gamma"),
"vega": g.get("vega"), "theta": g.get("theta"),
"oi": t.get("open_interest"), "volume_24h": st.get("volume"),
"underlying": t.get("underlying_price"), "index_price": t.get("index_price"),
})
time.sleep(0.05) # rate-limit gentile (pubblico: 20 req/s, stiamo larghi)
return rows
def main():
ap = argparse.ArgumentParser(description="Snapshot catena daily-expiry Deribit -> JSONL")
ap.add_argument("--currencies", nargs="+", default=["BTC", "ETH"])
ap.add_argument("--max-dte-h", type=float, default=50.0,
help="cattura tutte le scadenze entro N ore (default 50 = 0DTE+1DTE)")
ap.add_argument("--moneyness", nargs=2, type=float, default=[0.75, 1.25],
metavar=("LO", "HI"), help="ladder strike in frazione dello spot")
args = ap.parse_args()
OUT_DIR.mkdir(parents=True, exist_ok=True)
total = 0
with OUT_FILE.open("a") as f:
for cur in args.currencies:
try:
rows = snapshot_currency(cur, args.max_dte_h, *args.moneyness)
except Exception as e:
print(f"ERRORE {cur}: {e}", file=sys.stderr)
continue
for r in rows:
f.write(json.dumps(r, separators=(",", ":")) + "\n")
total += len(rows)
n_chain = sum(1 for r in rows if r["rec"] == "chain")
print(f"{cur}: {n_chain} strumenti (+1 meta) appesi")
print(f"OK: {total} righe -> {OUT_FILE}")
if __name__ == "__main__":
main()