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:
@@ -73,3 +73,4 @@ data/live/
|
|||||||
|
|
||||||
# dati esterni di ricerca (on-chain CoinMetrics community, F&G) — non certificati, non in git
|
# dati esterni di ricerca (on-chain CoinMetrics community, F&G) — non certificati, non in git
|
||||||
data/external/
|
data/external/
|
||||||
|
data/options_daily/
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# 2026-07-24 — Dossier 0DTE: economia LIVE delle daily options Deribit (BTC/ETH)
|
||||||
|
|
||||||
|
**Goal.** Quantificare stanotte, da quote pubbliche LIVE Deribit, l'economia del premio a
|
||||||
|
scadenza DAILY (0-1DTE) — il dossier 0DTE parcheggiato. VRP01 resta deploy-bloccato dalla regola
|
||||||
|
"niente short-vol da modello"; le daily (365 expiry/anno) accumulerebbero una serie di premi
|
||||||
|
REALI 7x più in fretta delle weekly. Domanda: il tenor daily è quotato/eseguibile/conveniente,
|
||||||
|
o muore di fee (lezione Albimarini)?
|
||||||
|
|
||||||
|
**Misura (3 snapshot ~22:35 UTC, ven 24 lug):** catena 0DTE (settle 25JUL 08:00, DTE 9.4h),
|
||||||
|
daily "fresca" (26JUL, DTE 33.4h), weekly (31JUL, DTE 153h). Fee verificate: taker=maker
|
||||||
|
0.0003 base/contratto, **cap 12.5% del premio**, daily ESENTI da delivery fee. Min size:
|
||||||
|
BTC 0.1 contratto, ETH 1.0. Spot 64.1k / 1859.
|
||||||
|
|
||||||
|
## Numeri chiave (put credit spread -0.28d/-0.10d, prezzi al tocco, fee taker 2 gambe)
|
||||||
|
|
||||||
|
| tenor | credito lordo | cross-spread (vs mid) | fee (% credito) | NETTO (% width) | %width/giorno |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| BTC 9.4h | $38.5 | 25% | 25.0% | 5.8% | — |
|
||||||
|
| BTC 33h | $96.2 (0.1 lot: $9.6) | 12% | 20.8% | 7.6% | 5.5%/g |
|
||||||
|
| BTC weekly | $224.6 | 16% | 17.1% | 9.3% | 1.5%/g |
|
||||||
|
| ETH 9.4h | $1.12 | 29% | 29.2% | 3.2% | — |
|
||||||
|
| ETH 33h | $3.53 | 14% | 23.0% | 10.9% | 7.8%/g |
|
||||||
|
| ETH weekly | $13.39 | 8% | **8.3%** | 12.3% | 1.9%/g |
|
||||||
|
|
||||||
|
- **Il cap 12.5% è quasi sempre BINDING a tenor daily** (premi < 0.0024 base) → fee ≈ 10-12.5%
|
||||||
|
del premio PER GAMBA, sempre. A weekly la fissa 0.0003 = 3-5% del premio. Il drag fee del
|
||||||
|
daily è **2-3x** quello weekly per unità di premio: la legge di scala Albimarini vista live
|
||||||
|
(premio ~ √T, fee ~ costante).
|
||||||
|
- **Haircut totale mid→netto**: daily 33h ≈ 30-35% del credito mid; 0DTE 9.4h ≈ 45-50%;
|
||||||
|
weekly ETH ≈ 16%. Richezza di pareggio a tenor daily ≈ **IV/RV ~1.4x**; a weekly ~1.1x.
|
||||||
|
- **IV vs RV stanotte**: ATM daily mark_iv BTC **12.8%** / ETH **21.5%** ann. contro RV20
|
||||||
|
29.6% / 39.8% → **IV_daily/RV20 = 0.43x / 0.54x: il premio daily era SOTTO la realized**
|
||||||
|
(VRP negativo al front). Weekly: 33.9% / 44.9% = 1.13-1.14x RV (il VRP vive lì, coerente
|
||||||
|
con VRP01). DVOL-30d 37.0 / 50.2. Caveat onesto: 1 snapshot, finestra weekend (ven→dom) =
|
||||||
|
IV bassa in parte razionale; NON generalizzabile — esattamente il perché della cattura.
|
||||||
|
- **EV check ETH 33h**: credito netto $2.72 < fair-value mid ~$4.1 e < E[payout] alla STESSA
|
||||||
|
IV implicita (~$4.9) → EV negativo *anche al pricing del mercato*, prima di ogni view di vol.
|
||||||
|
- **Quote sulle ali**: sì, 2-sided su tutto il ladder anche a 9h; depth MM ETH 400-1150
|
||||||
|
contratti al tocco, BTC più sottile (0.4-49). MA la griglia strike daily è GROSSA (step
|
||||||
|
1.3% ETH): i delta -0.28/-0.10 non esistono, si salta da -0.36 a -0.12; smile ripidissimo
|
||||||
|
(vendi bid_iv 13-20, compri ask_iv 22-38); le ali <5d hanno spread mediano 200% del mid =
|
||||||
|
di fatto non prezzate. Tick 0.0001 base = 3-10% del premio daily: anche la granularità quota.
|
||||||
|
|
||||||
|
## Margine = la realtà che uccide la taglia
|
||||||
|
|
||||||
|
Default Deribit = **standard margin SENZA netting fra gambe** (verificato docs): lo spread
|
||||||
|
"defined-risk" è margginato come naked short + long pagata intera. Il netting (margine ≈ width)
|
||||||
|
richiede Portfolio Margin, **min equity ~0.5 BTC ≈ $32k**. Quindi:
|
||||||
|
- ETH 1-lot PCS daily: margine SM ~$276, credito netto $2.72, max loss ~$22 → *meccanicamente*
|
||||||
|
eseguibile a $600 (1-2 lot), rendimento su margine ~1%/g SE zero perdite (e l'EV è negativo).
|
||||||
|
- BTC 0.1-lot: margine SM ~$963 → FUORI a $600, dentro a $2.6k.
|
||||||
|
- La soglia "$2.6k ETH" vale per strutture long/covered; per credit spread le soglie oneste
|
||||||
|
sono: $600 = ETH 1-lot (SM), $2.6k = entrambi sottili, **$32k = defined-risk vero (PM)**.
|
||||||
|
|
||||||
|
## Pipeline di cattura — FATTA (non in cron)
|
||||||
|
|
||||||
|
`scripts/research/r0724_daily_opt_snapshot.py`: pubblico/read-only, appende 1 riga JSON per
|
||||||
|
strumento (+1 meta con spot e DVOL) a `data/options_daily/snapshots.jsonl`; scadenze ≤50h,
|
||||||
|
moneyness 75-125%, ~160 righe/run, ~40 MB/anno a 2 run/g. Cadenza raccomandata quando la si
|
||||||
|
cabla: **08:05 UTC** (daily appena listata, ~24h DTE = premio vendibile) + **07:55 UTC**
|
||||||
|
(pre-settle = chiude il ciclo payoff). Primo snapshot già nel file. Esisteva solo
|
||||||
|
`options_real_quote_check.py` (weekly, one-shot, dipende da EXT path) e `fetch_dvol.py` (IV
|
||||||
|
30d, non quote): nessuna cattura daily preesistente.
|
||||||
|
|
||||||
|
## Verdetto: **CAPTURE-AND-WAIT con prior negativo sul daily**
|
||||||
|
|
||||||
|
Il tenor daily NON è morto-di-quotazione (mercato 2-sided, depth reale) ma stanotte era
|
||||||
|
**morto-di-economia**: haircut fee+spread 2-3x il weekly E IV<RV al front → servono entrambe
|
||||||
|
le gambe della condizione (IV/RV>~1.4x al daily) che oggi non c'è. Nessun deploy, nessun
|
||||||
|
cambio a book/pesi. Regola di decisione futura: dopo **≥90 giorni di cattura 2x/g**, calcolare
|
||||||
|
mediana(credito_netto/fair_mid) e mediana(IV_daily/RV_forward); il dossier si riapre SOLO se
|
||||||
|
il VRP daily mediano netto-haircut > 0 (e poi passa i gate standard). Il f di stress reale
|
||||||
|
arriva gratis se un crash cade nella finestra di cattura — lo stesso prerequisito che sblocca
|
||||||
|
VRP01. Fino ad allora: le daily servono da **strumento di misura**, non da strategia.
|
||||||
|
|
||||||
|
Script: `scripts/research/r0724_daily_opt_snapshot.py`. Dati: `data/options_daily/snapshots.jsonl`.
|
||||||
@@ -105,3 +105,57 @@ capture, rivisita ETF 2028), e 2 regole metodologiche nuove a diario (vintage-ri
|
|||||||
delle fonti esterne; scettico plateau+lag obbligatorio sugli EARNS_SLOT con DSR<0.95).
|
delle fonti esterne; scettico plateau+lag obbligatorio sugli EARNS_SLOT con DSR<0.95).
|
||||||
|
|
||||||
Book/pesi INVARIATI.
|
Book/pesi INVARIATI.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Addendum (sera, goal "un agente per ogni case") — i 3 verdetti
|
||||||
|
|
||||||
|
Tre agenti dedicati, uno per case, tutti su DATI LIVE (nessun claim di seconda mano).
|
||||||
|
Script: `r0724_pm_deribit_probe.py`, `r0724_hlp_deepdive.py`, `r0724_daily_opt_snapshot.py`.
|
||||||
|
|
||||||
|
### 1. Polymarket ↔ Deribit (con opzioni in copertura) — **SKIP come trade; il lead "11pp" è REFUTATO dalla misura**
|
||||||
|
34 binarie PM matchate alle daily options Deribit, spec verificata su 66 mercati (PM
|
||||||
|
risolve su candela 1m Binance USDT alle 16:00 UTC; Deribit TWAP 30m indice USD alle
|
||||||
|
08:00). Il gap si decompone in: **fonte USDT +9,3bps di basis = +4-5pp di probabilità
|
||||||
|
ATM**, tempo 8h ∓5pp, e replica ingenua su book ITM morti (la banda sub/super-replica
|
||||||
|
ATM è [39%,91%] con strike ogni $500 su orizzonti a 1σ≈$350). Residuo spec-adjusted:
|
||||||
|
ATM −5…+6pp a segno instabile DENTRO la banda di replica; tail +1,8-3,1pp
|
||||||
|
(favorite-longshot) sotto i costi. Trade coperto reale a size minima: lock netto
|
||||||
|
$0,1-5, capitale $200-1.000 (il margine SM del leg corto domina: Deribit non netta i
|
||||||
|
vertical), **P(gambe in conflitto 08→16 UTC) 2-22%** → non è un arb. Più geoblock/ToS
|
||||||
|
PM per residenti IT. Facoltativo: WATCH a costo zero (cron dello script per la serie
|
||||||
|
del gap tail). Lezione: anche il "best lead" accademico muore alla riconciliazione di
|
||||||
|
spec — la parte vera del gap era spec, non alpha.
|
||||||
|
|
||||||
|
### 2. HLP vault — **SKIP come income, WATCH con trigger meccanici; il carry 2026 ex-evento è +0,2%/anno**
|
||||||
|
Trovata la serie daily vera (wrapper wHLP via CoinGecko, 364 giorni + thunderhead per
|
||||||
|
i tail di marzo 2025): il daily smaschera la lens 14g — Sharpe 0,69 (non ~2), maxDD
|
||||||
|
−7,9%, worst day −5,96%; e l'uscita-in-stress via wrapper costa ~5% di sconto NAV.
|
||||||
|
**Il finding killer: il +12% headline 2026 è UN SOLO periodo evento (feb-2026 +7%);
|
||||||
|
gli altri 15 periodi sommano +0,10%** → carry ex-evento ~0,2%/anno, PnL-$ piatto vs
|
||||||
|
TVL (ritorno ∝ 1/TVL, e i depositanti lo sanno: TVL −59% dal picco). Coda JELLY
|
||||||
|
ricostruita: −4,7% MtM su inventario EREDITATO illimitato, troncato SOLO da un voto
|
||||||
|
discrezionale dei validator in 2 minuti — il crash-long può morire NEL crash
|
||||||
|
(pattern is_hedge-che-fallisce). Kelly onesto con code: **f*=0 al carry corrente**.
|
||||||
|
Trigger di ri-arma meccanici: 90g return-on-AUM ≥8% ann. AND TVL<$350M AND nessun
|
||||||
|
evento di socializzazione in 180g. Legale: HL accessibile dall'IT ma non autorizzato
|
||||||
|
MiCA → rischio geoblock non copribile con lock 4g.
|
||||||
|
|
||||||
|
### 3. Opzioni DAILY Deribit (0DTE) — **CAPTURE-AND-WAIT con prior negativo; cattura AVVIATA**
|
||||||
|
Misura live (22:35 UTC): mercato 2-sided reale anche sulle ali, MA il cap fee 12,5%
|
||||||
|
è quasi sempre binding a tenor daily → **drag fee 2-3× il weekly** (legge Albimarini
|
||||||
|
dal vivo: premio ~√T, fee ~costante; haircut mid→netto 0DTE ~45-50%) e stanotte
|
||||||
|
**IV_daily/RV = 0,43-0,54× (VRP NEGATIVO al front)** mentre il weekly fa 1,13×
|
||||||
|
(coerente con VRP01: il premio vive lì). Capitale minimo per defined-risk vero
|
||||||
|
(Portfolio Margin) ≈ $32k; a $600 = 1 lotto ETH a EV negativo. **Pipeline scritta e
|
||||||
|
attiva**: `r0724_daily_opt_snapshot.py` → `data/options_daily/snapshots.jsonl`
|
||||||
|
(primo snapshot 160 righe già su disco; raccomandate 2 run/g 08:05+07:55 UTC — da
|
||||||
|
cablare in cron alla merge, read-only). Regola pre-registrata: riaprire il dossier
|
||||||
|
solo dopo ≥90g di serie E mediana(IV_daily/RV_forward) netto-haircut > 0.
|
||||||
|
Diario dedicato: `2026-07-24-daily-options-0dte.md`.
|
||||||
|
|
||||||
|
### Bilancio dei 3 case
|
||||||
|
0 trade nuovi (ed è il punto: due lead famosi refutati coi numeri, non con le
|
||||||
|
opinioni), 1 pipeline di cattura reale avviata che tra 90 giorni decide da sola,
|
||||||
|
regole di ri-arma meccaniche per HLP, e il metodo confermato: ogni claim muore o
|
||||||
|
sopravvive alla RICONCILIAZIONE DI SPEC prima che al backtest. Book/pesi INVARIATI.
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -0,0 +1,291 @@
|
|||||||
|
"""r0724_hlp_deepdive — Deep-dive ALLOCATION sul vault HLP di Hyperliquid.
|
||||||
|
|
||||||
|
Contesto (2026-07-24): il probe HLP (89-95 punti ~14g dall'API nativa, vedi
|
||||||
|
`data/external/hlp_vault.json`) ha mostrato l'unico stream crash-long mai visto dal
|
||||||
|
progetto (corr col book -0.19, +10.4% nel cascade ott-2025). Questo script risponde
|
||||||
|
alle domande aperte con dati reali:
|
||||||
|
|
||||||
|
1. GRANULARITA' DAILY — l'API nativa (`vaultDetails`/`portfolio`) e' downsampled:
|
||||||
|
allTime ~14g (95 pti), month ~10h ma SOLO ultimi 30g, week ~2.4h ultimi 7g
|
||||||
|
→ nessun daily storico ufficiale. Fonti esterne trovate:
|
||||||
|
- CoinGecko `wrapped-hlp` (wHLP di Hyperbeat, redimibile a NAV): prezzo DAILY
|
||||||
|
dal 2025-07-25 → oggi. E' un prezzo di mercato (puo' fare sconto sotto stress),
|
||||||
|
ma e' l'unico mark giornaliero di lungo periodo liberamente accessibile.
|
||||||
|
- DefiLlama `protocol/hyperliquid-hlp`: TVL DAILY dal 2024-12 (per il modello
|
||||||
|
di diluizione return-vs-TVL).
|
||||||
|
- Thunderhead (cloudfront d2v1fiwobg9w6): `hlp_liquidator_pnl` DAILY, ma solo
|
||||||
|
2025-03-05 → 2025-07-12 (stantio; copre pero' il 12-mar e JELLY 26-mar-2025)
|
||||||
|
e `hlp_positions` (esposizione daily per coin dal 2023-06, per la leva).
|
||||||
|
- MORTI: ASXN api-hyperliquid.asxn.xyz `/hlp_pnl` (daily completo, dietro
|
||||||
|
Turnstile anti-bot); DefiLlama yields (HLP non ha token → non e' un pool);
|
||||||
|
stats-data.hyperliquid.xyz espone solo `Mainnet/vaults` (stessi dati downsampled).
|
||||||
|
|
||||||
|
2. CODA — ricostruzione numerica 12-mar-2025 (whale ETH, -$4.28M realizzati in
|
||||||
|
1 giorno dal liquidator) e JELLY 26-mar-2025 (unrealized peak ~-$13.5M su TVL
|
||||||
|
~$240M ≈ -5.6%, salvato da delist+settlement dei validator a $0.0095 → +$703k
|
||||||
|
realizzati). Vedi diario per il worst-case ragionato.
|
||||||
|
|
||||||
|
3-5. Fisco/accesso e allocation math: nel diario. Qui i numeri.
|
||||||
|
|
||||||
|
Uso:
|
||||||
|
uv run python scripts/research/r0724_hlp_deepdive.py # usa cache se c'e'
|
||||||
|
uv run python scripts/research/r0724_hlp_deepdive.py --refresh # ri-scarica
|
||||||
|
|
||||||
|
I fetch sono salvati in data/external/hlp_deepdive/ (gitignored di fatto: non
|
||||||
|
committare i dati). Nessuna azione live: SOLO analisi.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import urllib.request
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
EXT = ROOT / "data" / "external"
|
||||||
|
CACHE = EXT / "hlp_deepdive"
|
||||||
|
CACHE.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
VAULT = "0xdfc24b077bc1425ad1dea75bcb6f8158e10df303"
|
||||||
|
|
||||||
|
SOURCES = {
|
||||||
|
# prezzo daily wHLP (Hyperbeat Wrapped HLP) — mark di mercato del NAV HLP
|
||||||
|
"whlp_coingecko.json": (
|
||||||
|
"https://api.coingecko.com/api/v3/coins/wrapped-hlp/market_chart"
|
||||||
|
"?vs_currency=usd&days=365&interval=daily"
|
||||||
|
),
|
||||||
|
# TVL daily del vault HLP
|
||||||
|
"llama_hlp_tvl.json": "https://api.llama.fi/protocol/hyperliquid-hlp",
|
||||||
|
# PnL daily del liquidator HLP (stantio: 2025-03-05 → 2025-07-12, copre JELLY)
|
||||||
|
"thunderhead_liq_pnl.json": "https://d2v1fiwobg9w6.cloudfront.net/hlp_liquidator_pnl",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def fetch(name: str, url: str, refresh: bool) -> dict | list | None:
|
||||||
|
p = CACHE / name
|
||||||
|
if p.exists() and not refresh:
|
||||||
|
return json.loads(p.read_text())
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(url, headers={"User-Agent": "pythagoras-research/1.0"})
|
||||||
|
raw = urllib.request.urlopen(req, timeout=60).read()
|
||||||
|
p.write_bytes(raw)
|
||||||
|
return json.loads(raw)
|
||||||
|
except Exception as e: # noqa: BLE001
|
||||||
|
print(f" [WARN] fetch {name} fallito ({e}); uso cache se esiste")
|
||||||
|
return json.loads(p.read_text()) if p.exists() else None
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_vault_details(refresh: bool) -> dict:
|
||||||
|
"""Snapshot vaultDetails dall'API nativa (stesso formato del probe originale)."""
|
||||||
|
p = EXT / "hlp_vault.json"
|
||||||
|
if p.exists() and not refresh:
|
||||||
|
return json.loads(p.read_text())
|
||||||
|
body = json.dumps({"type": "vaultDetails", "vaultAddress": VAULT}).encode()
|
||||||
|
req = urllib.request.Request(
|
||||||
|
"https://api.hyperliquid.xyz/info", data=body,
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
raw = urllib.request.urlopen(req, timeout=60).read()
|
||||||
|
p.write_bytes(raw)
|
||||||
|
return json.loads(raw)
|
||||||
|
|
||||||
|
|
||||||
|
def series_from_period(d: dict, period: str) -> pd.DataFrame:
|
||||||
|
for per, pdata in d["portfolio"]:
|
||||||
|
if per == period:
|
||||||
|
av = pd.DataFrame(pdata["accountValueHistory"], columns=["ts", "av"])
|
||||||
|
pnl = pd.DataFrame(pdata["pnlHistory"], columns=["ts", "pnl"])
|
||||||
|
df = av.merge(pnl, on="ts")
|
||||||
|
df["t"] = pd.to_datetime(df["ts"], unit="ms", utc=True)
|
||||||
|
df["av"] = df["av"].astype(float)
|
||||||
|
df["pnl"] = df["pnl"].astype(float) # PnL CUMULATIVO
|
||||||
|
return df.set_index("t")[["av", "pnl"]]
|
||||||
|
raise KeyError(period)
|
||||||
|
|
||||||
|
|
||||||
|
def ret_stats(r: pd.Series, periods_per_year: float, label: str) -> dict:
|
||||||
|
r = r.replace([np.inf, -np.inf], np.nan).dropna()
|
||||||
|
mu, sd = r.mean(), r.std()
|
||||||
|
sharpe = mu / sd * np.sqrt(periods_per_year) if sd > 0 else np.nan
|
||||||
|
eq = (1 + r).cumprod()
|
||||||
|
dd = (eq / eq.cummax() - 1).min()
|
||||||
|
ann = (1 + mu) ** periods_per_year - 1
|
||||||
|
out = dict(label=label, n=len(r), ann_ret=ann, sharpe=sharpe, maxdd=dd,
|
||||||
|
worst=r.min(), worst_t=str(r.idxmin())[:10], best=r.max())
|
||||||
|
print(f" {label:28s} n={out['n']:4d} ann~{ann*100:6.1f}% Sh={sharpe:5.2f} "
|
||||||
|
f"maxDD={dd*100:5.1f}% worst={r.min()*100:6.2f}% ({out['worst_t']})")
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--refresh", action="store_true")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
print("=" * 78)
|
||||||
|
print("HLP DEEP-DIVE — dati al", datetime.now(timezone.utc).isoformat()[:16])
|
||||||
|
print("=" * 78)
|
||||||
|
|
||||||
|
d = refresh_vault_details(args.refresh)
|
||||||
|
print(f"\nVault: {d['name']} APR corrente dichiarato: {d['apr']*100:.3f}%")
|
||||||
|
print(f"isClosed={d['isClosed']} allowDeposits={d['allowDeposits']} "
|
||||||
|
f"maxDistributable=${d['maxDistributable']/1e6:.0f}M")
|
||||||
|
|
||||||
|
# ---------------- A. serie nativa 14g (allTime) ----------------
|
||||||
|
print("\n[A] Serie NATIVA allTime (~14g/punto) — return su AUM di inizio periodo")
|
||||||
|
at = series_from_period(d, "allTime")
|
||||||
|
at["dpnl"] = at["pnl"].diff()
|
||||||
|
at["ret"] = at["dpnl"] / at["av"].shift(1)
|
||||||
|
at.loc[at["av"].shift(1) < 5e6, "ret"] = np.nan # AUM<5M: return non significativo
|
||||||
|
ret14 = at["ret"].iloc[1:]
|
||||||
|
ret_stats(ret14, 365.25 / 14, "HLP 14g FULL (AUM>5M)")
|
||||||
|
for y in (2023, 2024, 2025, 2026):
|
||||||
|
sub = ret14[ret14.index.year == y]
|
||||||
|
if len(sub) > 3:
|
||||||
|
ret_stats(sub, 365.25 / 14, f" anno {y}")
|
||||||
|
print(" Traiettoria 2026 (per periodo 14g):",
|
||||||
|
" ".join(f"{x*100:+.1f}" for x in ret14[ret14.index.year == 2026].dropna()))
|
||||||
|
worst5 = ret14.dropna().nsmallest(5)
|
||||||
|
print(" 5 peggiori periodi 14g:", [(str(i.date()), f"{v*100:+.2f}%") for i, v in worst5.items()])
|
||||||
|
|
||||||
|
# month nativo (~10h granularita', ultimi 30g)
|
||||||
|
mo = series_from_period(d, "month")
|
||||||
|
mo_ret = mo["pnl"].diff() / mo["av"].shift(1)
|
||||||
|
tot30 = (1 + mo_ret.dropna()).prod() - 1
|
||||||
|
print(f" Ultimi 30g (periodo 'month', {len(mo)} pti ~10h): tot {tot30*100:+.2f}%")
|
||||||
|
|
||||||
|
# ---------------- B. wHLP daily (CoinGecko) ----------------
|
||||||
|
print("\n[B] wHLP (CoinGecko, prezzo DAILY di mercato ~ NAV; sconto possibile)")
|
||||||
|
cg = fetch("whlp_coingecko.json", SOURCES["whlp_coingecko.json"], args.refresh)
|
||||||
|
if cg and cg.get("prices"):
|
||||||
|
pr = pd.DataFrame(cg["prices"], columns=["ts", "px"])
|
||||||
|
pr["t"] = pd.to_datetime(pr["ts"], unit="ms", utc=True).dt.normalize()
|
||||||
|
pr = pr.drop_duplicates("t").set_index("t")["px"].astype(float)
|
||||||
|
r1d = pr.pct_change()
|
||||||
|
ret_stats(r1d, 365.25, "wHLP daily FULL (12 mesi)")
|
||||||
|
for lbl, a, b in [
|
||||||
|
("cascade 2025-10-09→13", "2025-10-09", "2025-10-13"),
|
||||||
|
("feb-2026 (liq event)", "2026-02-01", "2026-02-28"),
|
||||||
|
("ultimi 45g", str(pr.index[-1] - pd.Timedelta(days=45))[:10], None),
|
||||||
|
]:
|
||||||
|
w = pr.loc[a:b] if b else pr.loc[a:]
|
||||||
|
if len(w) > 1:
|
||||||
|
print(f" {lbl:26s} {w.iloc[0]:.4f} → {w.iloc[-1]:.4f} "
|
||||||
|
f"({(w.iloc[-1]/w.iloc[0]-1)*100:+.2f}%) min {w.min():.4f}")
|
||||||
|
peak = pr.cummax()
|
||||||
|
cur_dd = pr.iloc[-1] / peak.iloc[-1] - 1
|
||||||
|
print(f" Drawdown CORRENTE dal max ({pr.idxmax().date()} {pr.max():.4f}): "
|
||||||
|
f"{cur_dd*100:+.2f}%")
|
||||||
|
|
||||||
|
# ---------------- C. TVL daily (DefiLlama) → diluizione ----------------
|
||||||
|
print("\n[C] TVL daily (DefiLlama hyperliquid-hlp) + fit return-vs-TVL")
|
||||||
|
ll = fetch("llama_hlp_tvl.json", SOURCES["llama_hlp_tvl.json"], args.refresh)
|
||||||
|
if ll and ll.get("tvl"):
|
||||||
|
tvl = pd.DataFrame(ll["tvl"])
|
||||||
|
tvl["t"] = pd.to_datetime(tvl["date"], unit="s", utc=True).dt.normalize()
|
||||||
|
tvl = tvl.drop_duplicates("t").set_index("t")["totalLiquidityUSD"].astype(float)
|
||||||
|
print(f" TVL {tvl.index[0].date()} ${tvl.iloc[0]/1e6:.0f}M → picco "
|
||||||
|
f"{tvl.idxmax().date()} ${tvl.max()/1e6:.0f}M → oggi ${tvl.iloc[-1]/1e6:.0f}M "
|
||||||
|
f"({(tvl.iloc[-1]/tvl.max()-1)*100:+.0f}% dal picco)")
|
||||||
|
# fit sul nativo: PnL$ 14g vs AUM inizio periodo (tutta la storia)
|
||||||
|
x = at["av"].shift(1).iloc[1:] / 1e6
|
||||||
|
y = at["dpnl"].iloc[1:] / 1e6
|
||||||
|
ok = x.notna() & y.notna()
|
||||||
|
b1, b0 = np.polyfit(x[ok], y[ok], 1)
|
||||||
|
corr_xy = np.corrcoef(x[ok], y[ok])[0, 1]
|
||||||
|
print(f" Fit PnL$_14g = {b0:+.2f}M {b1:+.4f}·TVL(M) corr={corr_xy:+.2f} "
|
||||||
|
f"(b1≈0 ⇒ PnL$ NON scala col TVL ⇒ return-on-AUM ∝ 1/TVL = diluizione)")
|
||||||
|
for lo, hi in [(0, 150), (150, 300), (300, 450), (450, 700)]:
|
||||||
|
m = (x >= lo) & (x < hi)
|
||||||
|
if m.sum() >= 4:
|
||||||
|
impl = (y[m].mean() / x[m].mean()) * (365.25 / 14) * 100
|
||||||
|
print(f" TVL {lo:3d}-{hi:3d}M: n={m.sum():3d} PnL medio "
|
||||||
|
f"${y[m].mean()*1000:+7.0f}k/14g → ~{impl:+.1f}%/anno su AUM")
|
||||||
|
|
||||||
|
# ---------------- D. liquidator daily (thunderhead) — la coda ----------------
|
||||||
|
print("\n[D] Liquidator PnL DAILY (thunderhead, 2025-03-05→2025-07-12 — copre JELLY)")
|
||||||
|
th = fetch("thunderhead_liq_pnl.json", SOURCES["thunderhead_liq_pnl.json"], args.refresh)
|
||||||
|
if th and th.get("chart_data"):
|
||||||
|
liq = pd.DataFrame(th["chart_data"]).dropna()
|
||||||
|
liq["t"] = pd.to_datetime(liq["time"], utc=True)
|
||||||
|
liq = liq.set_index("t")["total_pnl"].astype(float)
|
||||||
|
w = liq.sort_values()
|
||||||
|
print(" 5 peggiori giorni:", [(str(i.date()), f"{v/1e6:+.2f}M") for i, v in w.head(5).items()])
|
||||||
|
print(" 5 migliori giorni:", [(str(i.date()), f"{v/1e6:+.2f}M") for i, v in w.tail(5).items()])
|
||||||
|
for day, note in [("2025-03-12", "whale ETH 50x"), ("2025-03-26", "JELLY settle")]:
|
||||||
|
if day in liq.index.strftime("%Y-%m-%d").tolist():
|
||||||
|
v = liq[liq.index.strftime("%Y-%m-%d") == day].iloc[0]
|
||||||
|
print(f" {day} ({note}): {v/1e6:+.2f}M")
|
||||||
|
|
||||||
|
# ---------------- F. decomposizioni oneste ----------------
|
||||||
|
print("\n[F] Decomposizioni oneste")
|
||||||
|
# carry 2026 ex-evento: togli il singolo periodo +7% (evento liquidazione feb-2026)
|
||||||
|
r26 = ret14[ret14.index.year == 2026].dropna()
|
||||||
|
if len(r26):
|
||||||
|
ev = r26.idxmax()
|
||||||
|
ex = r26.drop(ev)
|
||||||
|
cum_ex = (1 + ex).prod() - 1
|
||||||
|
ann_ex = (1 + cum_ex) ** (365.25 / (14 * len(ex))) - 1
|
||||||
|
print(f" 2026: evento {ev.date()} {r26.max()*100:+.1f}% | ex-evento cum "
|
||||||
|
f"{cum_ex*100:+.2f}% su {len(ex)} periodi ≈ {ann_ex*100:+.1f}%/anno "
|
||||||
|
f"→ IL CARRY E' MORTO, resta solo il crash-alpha")
|
||||||
|
# TVL al giorno JELLY (per scalare il -13.5M unrealized)
|
||||||
|
if ll and ll.get("tvl"):
|
||||||
|
for day in ("2025-03-12", "2025-03-26", "2025-10-10", "2026-02-08"):
|
||||||
|
ts = pd.Timestamp(day, tz="UTC")
|
||||||
|
i = tvl.index.get_indexer([ts], method="nearest")[0]
|
||||||
|
print(f" TVL {day}: ${tvl.iloc[i]/1e6:.0f}M "
|
||||||
|
f"(-13.5M JELLY = {-13.5e6/tvl.iloc[i]*100:.1f}% MtM)" if day == "2025-03-26"
|
||||||
|
else f" TVL {day}: ${tvl.iloc[i]/1e6:.0f}M")
|
||||||
|
# sconto wrapper: wHLP mercato vs NAV nativo su finestra comune (da max wHLP a oggi)
|
||||||
|
if cg and cg.get("prices"):
|
||||||
|
w_from = pr.idxmax()
|
||||||
|
nav_win = ret14[ret14.index >= w_from].dropna()
|
||||||
|
nav_chg = (1 + nav_win).prod() - 1
|
||||||
|
whlp_chg = pr.iloc[-1] / pr.max() - 1
|
||||||
|
print(f" Da {w_from.date()}: wHLP mercato {whlp_chg*100:+.2f}% vs NAV nativo "
|
||||||
|
f"{nav_chg*100:+.2f}% → sconto wrapper ≈ {(whlp_chg-nav_chg)*100:+.1f}pt "
|
||||||
|
f"(costo di un exit-in-stress via wrapper)")
|
||||||
|
|
||||||
|
# ---------------- E. allocation math (Kelly log-utility, outcome discreti) ----------------
|
||||||
|
print("\n[E] Allocation math — Kelly log-utility su outcome annui discreti")
|
||||||
|
# Outcome annui dell'ALLOCAZIONE (non del bankroll): base 2 scenari di coda:
|
||||||
|
# - evento socializzazione stile-JELLY-senza-salvataggio: -20% dell'allocazione
|
||||||
|
# - morte protocollo/bridge (hack, insolvenza, socializzazione totale): -100%
|
||||||
|
# mu base = run-rate onesto 2026 (decay in corso: usare 6%, non il 19% del 2025)
|
||||||
|
scenarios = [
|
||||||
|
("base mu=6%", [(0.06, 0.83), (-0.20, 0.15), (-1.00, 0.02)]),
|
||||||
|
("ottimista mu=12%", [(0.12, 0.87), (-0.20, 0.10), (-1.00, 0.03)]),
|
||||||
|
("pessimista mu=3%", [(0.03, 0.80), (-0.25, 0.17), (-1.00, 0.03)]),
|
||||||
|
]
|
||||||
|
for name, outs in scenarios:
|
||||||
|
mu_adj = sum(x * p for x, p in outs)
|
||||||
|
fgrid = np.linspace(0.001, 0.999, 999)
|
||||||
|
util = [sum(p * np.log(1 + f * x) for x, p in outs) for f in fgrid]
|
||||||
|
f_star = float(fgrid[int(np.argmax(util))])
|
||||||
|
print(f" {name:20s} mu_adj={mu_adj:+.1%} Kelly pieno f*={f_star:.2f} "
|
||||||
|
f"0.25·Kelly={0.25*f_star:.1%} del bankroll")
|
||||||
|
f_use = 0.10 # ~0.25·Kelly dello scenario base, cap operativo
|
||||||
|
print(f" → allocazione difendibile ~{f_use:.0%} del bankroll (VRP01 fu ~0.27 Kelly):")
|
||||||
|
for cap in (600, 2000, 5000, 20000):
|
||||||
|
a = f_use * cap
|
||||||
|
print(f" capitale ${cap:>6,}: ~${a:,.0f} → atteso ~${a*0.06:,.0f}/anno; "
|
||||||
|
f"coda -${a*0.20:,.0f} (JELLY-like) / -${a:,.0f} (protocollo)")
|
||||||
|
|
||||||
|
print("\nFonti dati:")
|
||||||
|
for k, v in SOURCES.items():
|
||||||
|
print(f" {k:28s} {v}")
|
||||||
|
print(" hlp_vault.json https://api.hyperliquid.xyz/info "
|
||||||
|
'{"type":"vaultDetails","vaultAddress":"' + VAULT + '"}')
|
||||||
|
print(" MORTI: ASXN /api/hlp_pnl (Turnstile), DefiLlama yields (HLP non-token), "
|
||||||
|
"stats-data.hyperliquid.xyz (downsampled)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,473 @@
|
|||||||
|
"""r0724_pm_deribit_probe — Riconciliazione LIVE Polymarket <-> Deribit su digitali BTC/ETH.
|
||||||
|
|
||||||
|
Domanda: quanto del "gap ~11pp" (arXiv 2606.19517, PM vs prob implicite Deribit) sopravvive
|
||||||
|
OGGI a una riconciliazione onesta che aggiusta i mismatch di SPEC, e il trade hedged
|
||||||
|
(lato cheap su PM + digitale opposta via VERTICAL di opzioni Deribit) ha senso a ~$600?
|
||||||
|
|
||||||
|
Mismatch di spec quantificati (non assunti):
|
||||||
|
1. TEMPO: i PM "above $K on <date>" risolvono sulla candela 1m Binance delle 12:00 ET
|
||||||
|
(=16:00 UTC in estate); le daily Deribit scadono 08:00 UTC (settlement TWAP 30m
|
||||||
|
dell'indice) -> 8h di varianza extra lato PM. dP_time = N(d2;T_pm) - N(d2;T_der).
|
||||||
|
2. FONTE: PM risolve su Binance BTC/USDT; Deribit su indice BTC-USD. Basis misurato
|
||||||
|
live (= sconto USDT/USD): pochi bps di spot MA a orizzonte daily con IV ~15%
|
||||||
|
vale svariati pp di probabilita' ATM. dP_src = N(d2; S*(1+basis)) - N(d2; S).
|
||||||
|
3. REPLICA: la digitale via vertical ha bound sub/super-replicanti:
|
||||||
|
call-spread [K,K+w] <= 1{S>K} <= call-spread [K-w,K] (identico coi put OTM).
|
||||||
|
Lato ITM i book Deribit sono MORTI -> si replica SEMPRE dal lato OTM
|
||||||
|
(K>=S: call; K<S: put, P_above = 1 - put_spread/w). Stima centrale = spread
|
||||||
|
centrato [K-w,K+w] sui mark; banda = [sub_mark, super_mark]; eseguibile =
|
||||||
|
sub-replica incrociando bid/ask REALI.
|
||||||
|
4. COSTI: PM fee 0 ma spread+depth CLOB; Deribit taker 0.0003 ccy/contratto cap
|
||||||
|
12.5% del premio per gamba + delivery 0.00015 (cap 12.5%) sul leg ITM; margine
|
||||||
|
del leg corto in standard margin (nessun netting del vertical in SM).
|
||||||
|
5. RISCHIO NON HEDGIABILE: P(cross) = probabilita' che S stia da lati OPPOSTI di K
|
||||||
|
alle 08:00 e alle 16:00 UTC (le due gambe risolvono in conflitto -> payoff 0 o 2):
|
||||||
|
MC bivariato con sigma implicita. Vicino all'ATM e' grande -> il "lock" non esiste.
|
||||||
|
|
||||||
|
Tutto live, API pubbliche tokenless, zero storage; se un endpoint e' vuoto lo dichiara.
|
||||||
|
Riproducibile: uv run python scripts/research/r0724_pm_deribit_probe.py
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
import urllib.request
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
UA = {"User-Agent": "Mozilla/5.0 (research probe)"}
|
||||||
|
|
||||||
|
|
||||||
|
def get(url: str, timeout: int = 20, retries: int = 2):
|
||||||
|
for k in range(retries + 1):
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(url, headers=UA)
|
||||||
|
with urllib.request.urlopen(req, timeout=timeout) as r:
|
||||||
|
return json.load(r)
|
||||||
|
except Exception as e: # noqa: BLE001
|
||||||
|
if k == retries:
|
||||||
|
print(f" [FETCH FAIL] {url[:100]} -> {e}")
|
||||||
|
return None
|
||||||
|
time.sleep(1.0)
|
||||||
|
|
||||||
|
|
||||||
|
def ncdf(x: float) -> float:
|
||||||
|
return 0.5 * (1.0 + math.erf(x / math.sqrt(2.0)))
|
||||||
|
|
||||||
|
|
||||||
|
def bs_digital(S: float, K: float, sig: float, T_yr: float) -> float:
|
||||||
|
if T_yr <= 0 or sig <= 0:
|
||||||
|
return 1.0 if S > K else 0.0
|
||||||
|
d2 = (math.log(S / K) - 0.5 * sig * sig * T_yr) / (sig * math.sqrt(T_yr))
|
||||||
|
return ncdf(d2)
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------- Polymarket
|
||||||
|
PM_EVENTS = [
|
||||||
|
("BTC", "bitcoin-above-on-july-25-2026", "25JUL26"),
|
||||||
|
("ETH", "ethereum-above-on-july-25-2026", "25JUL26"),
|
||||||
|
("BTC", "bitcoin-above-on-july-26-2026", "26JUL26"),
|
||||||
|
("ETH", "ethereum-above-on-july-26-2026", "26JUL26"),
|
||||||
|
("BTC", "bitcoin-above-on-july-27-2026", "27JUL26"),
|
||||||
|
("ETH", "ethereum-above-on-july-27-2026", "27JUL26"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_strike(question: str) -> float | None:
|
||||||
|
import re
|
||||||
|
|
||||||
|
m = re.search(r"\$([\d,]+(?:\.\d+)?)", question)
|
||||||
|
return float(m.group(1).replace(",", "")) if m else None
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_pm():
|
||||||
|
out = []
|
||||||
|
for asset, slug, dexp in PM_EVENTS:
|
||||||
|
ev = get(f"https://gamma-api.polymarket.com/events?slug={slug}")
|
||||||
|
if not ev:
|
||||||
|
print(f"[PM] evento {slug}: VUOTO/bloccato")
|
||||||
|
continue
|
||||||
|
ev = ev[0]
|
||||||
|
for m in ev["markets"]:
|
||||||
|
K = parse_strike(m["question"])
|
||||||
|
if K is None:
|
||||||
|
continue
|
||||||
|
desc = m.get("description", "")
|
||||||
|
tok = json.loads(m.get("clobTokenIds", "[]"))
|
||||||
|
out.append(
|
||||||
|
dict(
|
||||||
|
asset=asset,
|
||||||
|
dexp=dexp,
|
||||||
|
end=ev["endDate"],
|
||||||
|
K=K,
|
||||||
|
yes_bid=float(m["bestBid"]) if m.get("bestBid") else None,
|
||||||
|
yes_ask=float(m["bestAsk"]) if m.get("bestAsk") else None,
|
||||||
|
vol24=float(m.get("volume24hr") or 0),
|
||||||
|
spec_ok=("Binance" in desc and "12:00 in the ET" in desc),
|
||||||
|
yes_token=tok[0] if tok else None,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def pm_book_depth(token_id: str):
|
||||||
|
b = get(f"https://clob.polymarket.com/book?token_id={token_id}")
|
||||||
|
if not b or not b.get("bids") or not b.get("asks"):
|
||||||
|
return None
|
||||||
|
bids = [(float(x["price"]), float(x["size"])) for x in b["bids"]]
|
||||||
|
asks = [(float(x["price"]), float(x["size"])) for x in b["asks"]]
|
||||||
|
bb, ba = max(p for p, _ in bids), min(p for p, _ in asks)
|
||||||
|
mid = 0.5 * (bb + ba)
|
||||||
|
d = {}
|
||||||
|
for w in (0.01, 0.02):
|
||||||
|
d[w] = (
|
||||||
|
sum(s * p for p, s in bids if p >= mid - w),
|
||||||
|
sum(s * p for p, s in asks if p <= mid + w),
|
||||||
|
)
|
||||||
|
return dict(bb=bb, ba=ba, depth=d)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------- Deribit
|
||||||
|
def fetch_deribit(ccy: str):
|
||||||
|
bs = get(
|
||||||
|
"https://www.deribit.com/api/v2/public/get_book_summary_by_currency"
|
||||||
|
f"?currency={ccy}&kind=option"
|
||||||
|
)
|
||||||
|
idx = get(
|
||||||
|
f"https://www.deribit.com/api/v2/public/get_index_price?index_name={ccy.lower()}_usd"
|
||||||
|
)
|
||||||
|
ins = get(
|
||||||
|
f"https://www.deribit.com/api/v2/public/get_instruments?currency={ccy}"
|
||||||
|
"&kind=option&expired=false"
|
||||||
|
)
|
||||||
|
if not bs or not idx or not ins:
|
||||||
|
return None
|
||||||
|
books = {row["instrument_name"]: row for row in bs["result"]}
|
||||||
|
expts = {}
|
||||||
|
for i in ins["result"]:
|
||||||
|
expts[i["instrument_name"].split("-")[1]] = i["expiration_timestamp"] / 1000.0
|
||||||
|
return dict(books=books, index=idx["result"]["index_price"], expts=expts)
|
||||||
|
|
||||||
|
|
||||||
|
def _q(books, ccy, dexp, strike, typ, index):
|
||||||
|
"""(bid_usd, ask_usd, mark_usd) di uno strumento; None se assente."""
|
||||||
|
n = f"{ccy}-{dexp}-{int(strike)}-{typ}"
|
||||||
|
r = books.get(n)
|
||||||
|
if not r:
|
||||||
|
return None
|
||||||
|
return dict(
|
||||||
|
n=n,
|
||||||
|
bid=(r["bid_price"] or 0.0) * index,
|
||||||
|
ask=(r["ask_price"] * index) if r["ask_price"] else None,
|
||||||
|
mark=(r.get("mark_price") or 0.0) * index,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def deribit_digital(books, ccy, dexp, K, index):
|
||||||
|
"""Stima onesta di P(S_der > K) via vertical OTM-side.
|
||||||
|
|
||||||
|
Ritorna: mark centrato, banda [sub_mark, super_mark], eseguibili
|
||||||
|
(exec_buy = costo per COMPRARE la sub-replica dell'above-digitale ai prezzi reali;
|
||||||
|
exec_sell = incasso per VENDERLA), gambe usate.
|
||||||
|
"""
|
||||||
|
typ = "C" if K >= index else "P"
|
||||||
|
strikes = sorted(
|
||||||
|
float(n.split("-")[2])
|
||||||
|
for n in books
|
||||||
|
if n.split("-")[1] == dexp and n.endswith(f"-{typ}")
|
||||||
|
)
|
||||||
|
if K not in strikes:
|
||||||
|
return None # tutti gli strike PM qui coincidono con strike listati
|
||||||
|
i = strikes.index(K)
|
||||||
|
if i == 0 or i == len(strikes) - 1:
|
||||||
|
return None
|
||||||
|
Km, Kp = strikes[i - 1], strikes[i + 1]
|
||||||
|
qm, q0, qp = (
|
||||||
|
_q(books, ccy, dexp, Km, typ, index),
|
||||||
|
_q(books, ccy, dexp, K, typ, index),
|
||||||
|
_q(books, ccy, dexp, Kp, typ, index),
|
||||||
|
)
|
||||||
|
if not (qm and q0 and qp):
|
||||||
|
return None
|
||||||
|
|
||||||
|
if typ == "C":
|
||||||
|
centered = (qm["mark"] - qp["mark"]) / (Kp - Km)
|
||||||
|
sub = (q0["mark"] - qp["mark"]) / (Kp - K) # <= P
|
||||||
|
sup = (qm["mark"] - q0["mark"]) / (K - Km) # >= P
|
||||||
|
# comprare sub-replica: buy C(K), sell C(Kp)
|
||||||
|
exec_buy = (
|
||||||
|
(q0["ask"] - qp["bid"]) / (Kp - K) if q0["ask"] is not None else None
|
||||||
|
)
|
||||||
|
exec_sell = (
|
||||||
|
(q0["bid"] - qp["ask"]) / (Kp - K) if qp["ask"] is not None else None
|
||||||
|
)
|
||||||
|
legs = (q0, qp)
|
||||||
|
else:
|
||||||
|
# P_above = 1 - put_spread/w ; [K,Kp] put spread super-replica il below
|
||||||
|
centered = 1.0 - (qp["mark"] - qm["mark"]) / (Kp - Km)
|
||||||
|
sub = 1.0 - (qp["mark"] - q0["mark"]) / (Kp - K) # <= P_above
|
||||||
|
sup = 1.0 - (q0["mark"] - qm["mark"]) / (K - Km) # >= P_above
|
||||||
|
# comprare (sinteticamente) l'above = VENDERE il put spread [K,Kp]:
|
||||||
|
# incasso bid(Kp)-ask(K); prezzo implicito pagato = 1 - incasso/w
|
||||||
|
exec_buy = (
|
||||||
|
1.0 - (qp["bid"] - q0["ask"]) / (Kp - K) if q0["ask"] is not None else None
|
||||||
|
)
|
||||||
|
exec_sell = (
|
||||||
|
1.0 - (qp["ask"] - q0["bid"]) / (Kp - K) if qp["ask"] is not None else None
|
||||||
|
)
|
||||||
|
legs = (q0, qp)
|
||||||
|
return dict(
|
||||||
|
typ=typ,
|
||||||
|
Km=Km,
|
||||||
|
Kp=Kp,
|
||||||
|
w=Kp - K,
|
||||||
|
centered=centered,
|
||||||
|
sub=sub,
|
||||||
|
sup=sup,
|
||||||
|
exec_buy=exec_buy,
|
||||||
|
exec_sell=exec_sell,
|
||||||
|
legs=legs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
IV_CACHE: dict = {}
|
||||||
|
|
||||||
|
|
||||||
|
def deribit_iv(ccy: str, dexp: str, name: str) -> float | None:
|
||||||
|
if name in IV_CACHE:
|
||||||
|
return IV_CACHE[name]
|
||||||
|
t = get(f"https://www.deribit.com/api/v2/public/ticker?instrument_name={name}")
|
||||||
|
iv = None
|
||||||
|
if t:
|
||||||
|
iv = t["result"].get("mark_iv")
|
||||||
|
iv = iv / 100.0 if iv else None
|
||||||
|
IV_CACHE[name] = iv
|
||||||
|
return iv
|
||||||
|
|
||||||
|
|
||||||
|
def p_cross(S, K, sig, T1_yr, T2_yr, n=200_000, seed=7):
|
||||||
|
"""MC: P(lati opposti di K a T1 e T2) e P(sopra a T2 ma sotto a T1) ecc."""
|
||||||
|
rng = random.Random(seed)
|
||||||
|
lo_hi = hi_lo = 0
|
||||||
|
lnK = math.log(K / S)
|
||||||
|
s1 = sig * math.sqrt(T1_yr)
|
||||||
|
s2x = sig * math.sqrt(max(T2_yr - T1_yr, 1e-12))
|
||||||
|
for _ in range(n):
|
||||||
|
x1 = -0.5 * s1 * s1 + s1 * rng.gauss(0, 1)
|
||||||
|
x2 = x1 - 0.5 * s2x * s2x + s2x * rng.gauss(0, 1)
|
||||||
|
a, b = x1 > lnK, x2 > lnK
|
||||||
|
if not a and b:
|
||||||
|
lo_hi += 1
|
||||||
|
elif a and not b:
|
||||||
|
hi_lo += 1
|
||||||
|
return lo_hi / n, hi_lo / n
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------------- run
|
||||||
|
def main():
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
print(f"=== PROBE PM<->DERIBIT {now.isoformat(timespec='seconds')} ===\n")
|
||||||
|
|
||||||
|
pm = fetch_pm()
|
||||||
|
nbad = sum(1 for m in pm if not m["spec_ok"])
|
||||||
|
print(f"[PM] {len(pm)} mercati above/below; spec Binance/12:00ET non confermata su {nbad}")
|
||||||
|
|
||||||
|
der = {c: fetch_deribit(c) for c in ("BTC", "ETH")}
|
||||||
|
for c, d in der.items():
|
||||||
|
if d:
|
||||||
|
print(f"[Deribit] {c} index={d['index']:.2f}")
|
||||||
|
|
||||||
|
def binance_spot(sym):
|
||||||
|
for host in ("https://api.binance.com", "https://data-api.binance.vision"):
|
||||||
|
r = get(f"{host}/api/v3/ticker/price?symbol={sym}", timeout=10, retries=0)
|
||||||
|
if r and "price" in r:
|
||||||
|
return float(r["price"])
|
||||||
|
return None
|
||||||
|
|
||||||
|
binance = {"BTC": binance_spot("BTCUSDT"), "ETH": binance_spot("ETHUSDT")}
|
||||||
|
kr = get("https://api.kraken.com/0/public/Ticker?pair=USDTZUSD", timeout=10, retries=1)
|
||||||
|
usdtusd = None
|
||||||
|
try:
|
||||||
|
usdtusd = float(list(kr["result"].values())[0]["c"][0])
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
pass
|
||||||
|
print(f"[Binance] BTCUSDT={binance['BTC']} ETHUSDT={binance['ETH']} | USDT/USD={usdtusd}")
|
||||||
|
basis = {}
|
||||||
|
for c in ("BTC", "ETH"):
|
||||||
|
basis[c] = (binance[c] / der[c]["index"] - 1.0) if (der[c] and binance[c]) else 0.0
|
||||||
|
print(f" basis {c} BinanceUSDT vs indice Deribit: {basis[c]*1e4:+.1f} bps")
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
for m in pm:
|
||||||
|
c, d = m["asset"], der[m["asset"]]
|
||||||
|
if not d or m["dexp"] not in d["expts"]:
|
||||||
|
continue
|
||||||
|
S = d["index"]
|
||||||
|
dig = deribit_digital(d["books"], c, m["dexp"], m["K"], S)
|
||||||
|
if dig is None:
|
||||||
|
continue
|
||||||
|
T_der = max(d["expts"][m["dexp"]] - now.timestamp(), 0) / (365.25 * 86400)
|
||||||
|
t_pm = datetime.fromisoformat(m["end"].replace("Z", "+00:00")).timestamp()
|
||||||
|
T_pm = max(t_pm - now.timestamp(), 0) / (365.25 * 86400)
|
||||||
|
near = abs(math.log(m["K"] / S)) < 0.09
|
||||||
|
sig = deribit_iv(c, m["dexp"], dig["legs"][0]["n"]) if near else None
|
||||||
|
if sig is None:
|
||||||
|
sig = 0.45
|
||||||
|
dP_time = bs_digital(S, m["K"], sig, T_pm) - bs_digital(S, m["K"], sig, T_der)
|
||||||
|
dP_src = bs_digital(S * (1 + basis[c]), m["K"], sig, T_pm) - bs_digital(
|
||||||
|
S, m["K"], sig, T_pm
|
||||||
|
)
|
||||||
|
pm_mid = (
|
||||||
|
0.5 * (m["yes_bid"] + m["yes_ask"])
|
||||||
|
if (m["yes_bid"] is not None and m["yes_ask"] is not None)
|
||||||
|
else (m["yes_ask"] or m["yes_bid"])
|
||||||
|
)
|
||||||
|
raw = pm_mid - dig["centered"] if pm_mid is not None else None
|
||||||
|
resid = raw - dP_time - dP_src if raw is not None else None
|
||||||
|
depth = pm_book_depth(m["yes_token"]) if (m["yes_token"] and near) else None
|
||||||
|
rows.append(
|
||||||
|
dict(
|
||||||
|
m=m,
|
||||||
|
dig=dig,
|
||||||
|
sig=sig,
|
||||||
|
T_der=T_der,
|
||||||
|
T_pm=T_pm,
|
||||||
|
dP_time=dP_time,
|
||||||
|
dP_src=dP_src,
|
||||||
|
pm_mid=pm_mid,
|
||||||
|
raw=raw,
|
||||||
|
resid=resid,
|
||||||
|
depth=depth,
|
||||||
|
S=S,
|
||||||
|
near=near,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
print("\n=== RICONCILIAZIONE (prob %, gap pp; digitale = vertical OTM-side) ===")
|
||||||
|
hdr = (
|
||||||
|
f"{'mkt':<22}{'PM b/a':>12}{'Der mark[sub,sup]':>20}{'exec b/s':>13}"
|
||||||
|
f"{'raw':>7}{'dT':>6}{'dSrc':>6}{'resid':>7}{'iv%':>5}"
|
||||||
|
)
|
||||||
|
print(hdr)
|
||||||
|
print("-" * len(hdr))
|
||||||
|
for r in rows:
|
||||||
|
m, g = r["m"], r["dig"]
|
||||||
|
name = f"{m['asset']} >{int(m['K'])} {m['dexp'][:5]}"
|
||||||
|
pmba = (
|
||||||
|
f"{(m['yes_bid'] or 0)*100:.1f}/{(m['yes_ask'] or 0)*100:.1f}"
|
||||||
|
if (m["yes_bid"] is not None or m["yes_ask"] is not None)
|
||||||
|
else "n/a"
|
||||||
|
)
|
||||||
|
ex = (
|
||||||
|
f"{g['exec_buy']*100:.0f}/{g['exec_sell']*100:.0f}"
|
||||||
|
if (g["exec_buy"] is not None and g["exec_sell"] is not None)
|
||||||
|
else "n/q"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"{name:<22}{pmba:>12}"
|
||||||
|
f"{g['centered']*100:>8.1f}[{g['sub']*100:.0f},{g['sup']*100:.0f}]".ljust(42)
|
||||||
|
+ f"{ex:>13}"
|
||||||
|
f"{(r['raw'] or 0)*100:>+7.1f}{r['dP_time']*100:>+6.1f}"
|
||||||
|
f"{r['dP_src']*100:>+6.1f}{(r['resid'] or 0)*100:>+7.1f}"
|
||||||
|
f"{r['sig']*100:>5.0f}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"\n Der mark = spread centrato sui mark, [sub,sup] = bound di replica;"
|
||||||
|
" exec b/s = comprare/vendere la sub-replica ai bid/ask REALI."
|
||||||
|
"\n raw = PM_mid - Der_mark; resid = raw - dT - dSrc"
|
||||||
|
" (>0: PM sovraprezza il lato above vs Deribit spec-adjusted)."
|
||||||
|
)
|
||||||
|
|
||||||
|
# sistematicita' per bucket di moneyness
|
||||||
|
print("\n=== RESIDUO PER BUCKET (solo quote PM a doppio lato) ===")
|
||||||
|
buckets = {"ITM(K<S-1.5%)": [], "ATM(|1.5%|)": [], "OTM(K>S+1.5%)": []}
|
||||||
|
for r in rows:
|
||||||
|
if r["resid"] is None or r["m"]["yes_bid"] is None or r["m"]["yes_ask"] is None:
|
||||||
|
continue
|
||||||
|
lm = math.log(r["m"]["K"] / r["S"])
|
||||||
|
b = "ITM(K<S-1.5%)" if lm < -0.015 else ("OTM(K>S+1.5%)" if lm > 0.015 else "ATM(|1.5%|)")
|
||||||
|
buckets[b].append(r["resid"])
|
||||||
|
for b, v in buckets.items():
|
||||||
|
if v:
|
||||||
|
pos = sum(1 for x in v if x > 0)
|
||||||
|
print(
|
||||||
|
f" {b:<15} n={len(v):>2} resid medio {sum(v)/len(v)*100:+.1f}pp"
|
||||||
|
f" mediana {sorted(v)[len(v)//2]*100:+.1f}pp >0: {pos}/{len(v)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
print("\n=== DEPTH CLOB PM (vicino allo spot) ===")
|
||||||
|
for r in rows:
|
||||||
|
if r["depth"]:
|
||||||
|
m, dp = r["m"], r["depth"]
|
||||||
|
d1, d2 = dp["depth"][0.01], dp["depth"][0.02]
|
||||||
|
print(
|
||||||
|
f" {m['asset']} >{int(m['K'])} {m['dexp']}: book {dp['bb']:.3f}/{dp['ba']:.3f}"
|
||||||
|
f" depth±1c ${d1[0]:,.0f}/${d1[1]:,.0f} ±2c ${d2[0]:,.0f}/${d2[1]:,.0f}"
|
||||||
|
f" vol24h ${m['vol24']:,.0f}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ trade hedged demo
|
||||||
|
print("\n=== TRADE HEDGED a size minima (0.1 BTC / 1 ETH) — numeri VERI ===")
|
||||||
|
min_amt = {"BTC": 0.1, "ETH": 1.0}
|
||||||
|
fee_rate, dlv_rate = 0.0003, 0.00015
|
||||||
|
for r in rows:
|
||||||
|
m, g = r["m"], r["dig"]
|
||||||
|
if r["resid"] is None or not r["near"] or abs(r["resid"]) < 0.01:
|
||||||
|
continue
|
||||||
|
if g["exec_buy"] is None or g["exec_sell"] is None:
|
||||||
|
continue
|
||||||
|
c, S = m["asset"], r["S"]
|
||||||
|
amt = min_amt[c]
|
||||||
|
W = g["w"] * amt # notional digitale $
|
||||||
|
if r["resid"] > 0:
|
||||||
|
# PM ricco sul lato above: buy PM NO al (1-yes_bid) + buy digitale-above Deribit
|
||||||
|
pm_px = 1 - (m["yes_bid"] or 0)
|
||||||
|
der_px = g["exec_buy"]
|
||||||
|
side = "BUY PM NO + LONG vertical OTM (sub-replica above)"
|
||||||
|
else:
|
||||||
|
pm_px = m["yes_ask"] or 1
|
||||||
|
der_px = 1 - g["exec_sell"]
|
||||||
|
side = "BUY PM YES + SHORT vertical OTM (sub-replica below)"
|
||||||
|
cost = (pm_px + der_px) * W
|
||||||
|
lock = W - cost
|
||||||
|
fees = 0.0
|
||||||
|
for leg in g["legs"]:
|
||||||
|
fees += min(fee_rate * amt * S, 0.125 * leg["mark"] * amt)
|
||||||
|
dlv = min(dlv_rate * amt * S, 0.125 * max(le["mark"] for le in g["legs"]) * amt)
|
||||||
|
otm_frac = abs(g["Kp"] - S) / S
|
||||||
|
mark_short = g["legs"][1]["mark"] / S
|
||||||
|
im = (max(0.15 - otm_frac, 0.10) + mark_short) * amt
|
||||||
|
pc = p_cross(S, m["K"], r["sig"], r["T_der"], r["T_pm"])
|
||||||
|
capital = cost + im * S
|
||||||
|
net = lock - fees - dlv
|
||||||
|
days = r["T_pm"] * 365.25
|
||||||
|
print(f"\n {m['asset']} >{int(m['K'])} {m['dexp']} resid {r['resid']*100:+.1f}pp -> {side}")
|
||||||
|
print(
|
||||||
|
f" gambe Deribit {g['legs'][0]['n']}/{g['legs'][1]['n']} amount {amt} {c}"
|
||||||
|
f" -> payout digitale ${W:,.0f}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f" costo PM ${pm_px*W:,.2f} + Deribit ${der_px*W:,.2f} = ${cost:,.2f};"
|
||||||
|
f" lock lordo ${lock:,.2f}; fee entry ${fees:.2f} + delivery ${dlv:.2f}"
|
||||||
|
f" -> netto ${net:,.2f}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f" margine leg corto (SM, no netting) ~${im*S:,.0f};"
|
||||||
|
f" capitale impegnato ~${capital:,.0f};"
|
||||||
|
f" ritorno se lock regge: {net/capital*100:.2f}% in {days:.1f}g"
|
||||||
|
f" (~{net/capital*365.25/days*100:.0f}%/anno)"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f" RISCHIO CROSS 08->16 UTC (MC, iv {r['sig']*100:.0f}%):"
|
||||||
|
f" P(sotto@08,sopra@16)={pc[0]*100:.1f}% P(sopra@08,sotto@16)={pc[1]*100:.1f}%"
|
||||||
|
f" -> P(gambe in conflitto)={sum(pc)*100:.1f}%"
|
||||||
|
)
|
||||||
|
|
||||||
|
print("\n[NOTE] Deribit settle = TWAP 30m pre-08:00 UTC vs PM candela 1m 16:00 UTC;")
|
||||||
|
print("la sub-replica paga <1 nella rampa [K,K+w] -> il 'lock' e' un bound inferiore")
|
||||||
|
print("solo FUORI dalla rampa; ATM con w=500 (BTC) la rampa e' ~1-1.5 sigma daily.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user