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>
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user