"""r0909 — L'UNIVERSO DERIBIT LETTO OGGI: quali "altre monete" esistono davvero sul venue. Domanda dell'operatore: "anche piu' monete, ma sempre in Deribit". Prima di misurare una moneta si legge il venue (N10: la verifica esterna a €0 si fa PRIMA, e sul venue). Questo script interroga l'API PUBBLICA Deribit (nessuna credenziale, nessun ordine) e stampa: 1. i perpetual USDC-lineari aperti, con data di listing, lotto minimo in $, volume 24h, spread top-of-book, open interest e funding — ordinati per volume; 2. quante delle 19 gambe di XS01 sono quotate (il 23/08 erano 14/19, 11 con ≥1 anno); 3. le famiglie di opzioni (inverse BTC/ETH; USDC-lineari per sottostante). Una lettura e' UN istante (come r0822_alt_options): il file salva il JSON grezzo in `data/_cache/` con l'ora della lettura, cosi' il numero stampato ha la sua fonte. Cosa NON fa: nessun backtest, nessuna proposta. Dice solo quali strumenti esistono e quanto sono negoziabili oggi — il filtro che decide cosa vale la pena misurare (C6). """ from __future__ import annotations import json import sys import time import urllib.request from collections import Counter from datetime import datetime, timezone from pathlib import Path ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) from src.portfolio.sleeves import XS_UNIVERSE # noqa: E402 (P1: l'universo si importa) API = "https://www.deribit.com/api/v2/public/" CACHE = ROOT / "data" / "_cache" / "r0909_deribit_universo.json" SOGLIA_LIQUIDO_USD = 10e6 # volume 24h da cui una gamba e' "liquida" per un libro da $4-5k def get(path: str) -> dict: req = urllib.request.Request(API + path, headers={"User-Agent": "PythagorasGoal-research"}) with urllib.request.urlopen(req, timeout=30) as r: return json.load(r)["result"] def leggi(usa_cache: bool = True) -> dict: if usa_cache and CACHE.exists(): return json.loads(CACHE.read_text()) d = dict(letto_a=datetime.now(timezone.utc).isoformat(timespec="seconds"), fut=get("get_instruments?currency=USDC&kind=future"), bs=get("get_book_summary_by_currency?currency=USDC&kind=future"), opt_usdc=get("get_instruments?currency=USDC&kind=option"), opt_inv={c: len(get(f"get_instruments?currency={c}&kind=option")) for c in ("BTC", "ETH")}) CACHE.parent.mkdir(parents=True, exist_ok=True) CACHE.write_text(json.dumps(d)) return d def tabella(d: dict) -> list[dict]: perps = {i["base_currency"]: i for i in d["fut"] if i["instrument_name"].endswith("PERPETUAL") and i["state"] == "open"} bs = {b["instrument_name"]: b for b in d["bs"]} rows = [] for c, i in perps.items(): b = bs.get(i["instrument_name"], {}) bid, ask, mark = b.get("bid_price"), b.get("ask_price"), b.get("mark_price") or 0.0 rows.append(dict( coin=c, listato=time.strftime("%Y-%m-%d", time.gmtime(i["creation_timestamp"] / 1000)), min_amt=i["min_trade_amount"], min_usd=i["min_trade_amount"] * mark, vol_usd=b.get("volume_usd") or 0.0, spread_bps=((ask - bid) / ((ask + bid) / 2) * 1e4) if bid and ask else float("nan"), oi_usd=(b.get("open_interest") or 0.0) * mark, funding_8h=b.get("funding_8h"), xs01=c in XS_UNIVERSE)) return sorted(rows, key=lambda r: -r["vol_usd"]) def sintesi(rows: list[dict], d: dict) -> dict: listati = {r["coin"] for r in rows} liquidi = [r["coin"] for r in rows if r["vol_usd"] >= SOGLIA_LIQUIDO_USD] bs = {b["instrument_name"]: b for b in d["bs"]} inattivi = sorted(i["instrument_name"] for i in d["fut"] if i["instrument_name"].endswith("PERPETUAL") and i["state"] != "open") datati = [i for i in d["fut"] if not i["instrument_name"].endswith("PERPETUAL")] dvol_fut = [] for i in datati: if "DVOL" not in i["instrument_name"]: continue b = bs.get(i["instrument_name"], {}) dvol_fut.append(dict(nome=i["instrument_name"], stato=i["state"], min_amt=i["min_trade_amount"], vol_usd=b.get("volume_usd") or 0.0, oi=b.get("open_interest") or 0.0, bid=b.get("bid_price"), ask=b.get("ask_price"))) return dict( n_perp=len(rows), liquidi=liquidi, inattivi=inattivi, n_datati=len(datati), dvol_fut=dvol_fut, xs01_listate=sorted(c for c in XS_UNIVERSE if c in listati), xs01_mancanti=[c for c in XS_UNIVERSE if c not in listati], opt_inv=d["opt_inv"], opt_usdc=Counter(i["base_currency"] for i in d["opt_usdc"]).most_common(), letto_a=d["letto_a"]) def verdetto(s: dict) -> str: return (f"{s['n_perp']} perpetual USDC; LIQUIDI (≥${SOGLIA_LIQUIDO_USD/1e6:.0f}M/g): {', '.join(s['liquidi'])}; " f"XS01 {len(s['xs01_listate'])}/19 quotate, mancano {', '.join(s['xs01_mancanti'])}; " f"opzioni USDC-lineari su {len(s['opt_usdc'])} sottostanti — lettura del {s['letto_a']}") def main() -> None: d = leggi(usa_cache="--fresh" not in sys.argv) rows = tabella(d); s = sintesi(rows, d) print("=" * 110) print(f" r0909 — UNIVERSO DERIBIT (API pubblica, lettura {s['letto_a']}; --fresh per rileggere)") print("=" * 110) print(f"\n [1] PERPETUAL USDC-LINEARI APERTI: {s['n_perp']}") print(f" {'coin':<9}{'listato':>11}{'min $':>8}{'vol 24h M$':>12}{'spread bps':>12}{'OI M$':>8}{'funding 8h':>12} XS01") for r in rows: print(f" {r['coin']:<9}{r['listato']:>11}{r['min_usd']:>8.2f}{r['vol_usd']/1e6:>12.2f}{r['spread_bps']:>12.1f}" f"{r['oi_usd']/1e6:>8.2f}{(r['funding_8h'] or 0)*1e4:>+11.2f}bp {'<--' if r['xs01'] else ''}") print(f" perpetual NON aperti (stato ≠ open): {s['inattivi'] or 'nessuno'}") print(f"\n [1bis] FUTURE USDC DATATI: {s['n_datati']} — fra cui il future sul DVOL, l'unico strumento long-vol DIRETTO del venue " f"(visto dalla revisione del 09/09: non in memoria):") for f in s["dvol_fut"]: spr = (f["ask"] - f["bid"]) / ((f["ask"] + f["bid"]) / 2) * 100 if f["bid"] and f["ask"] else float("nan") print(f" {f['nome']:<28} {f['stato']:<8} min {f['min_amt']} vol24h ${f['vol_usd']/1e6:.2f}M OI {f['oi']:.0f} " f"bid/ask {f['bid']}/{f['ask']} (spread {spr:.0f}%) → non negoziabile a questi numeri; e in contango sanguina come la put") print(f"\n [2] XS01: {len(s['xs01_listate'])}/19 gambe quotate; mancano {s['xs01_mancanti']}") print(f" (23/08, §50: 14/19 quotate, 11 con ≥1 anno; sulle 11 il meccanismo collassa 1,265 → 0,116 — breadth, non capitale)") print(f"\n [3] OPZIONI: inverse {s['opt_inv']}; USDC-lineari per sottostante: {s['opt_usdc']}") print("\n VERDETTO: " + verdetto(s)) print("=" * 110) if __name__ == "__main__": main()