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:
Adriano Dal Pastro
2026-07-24 22:46:59 +00:00
parent 4c1961d77b
commit 00996640fb
6 changed files with 1045 additions and 0 deletions
+473
View File
@@ -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()