"""R0822 VRP-QUOTE-VERE — esiste una struttura di reddito in opzioni ESEGUIBILE a $600-3.000 che sopravvive a fee reali, spread reali e lotto minimo? NON e' "rifare VRP01". La domanda e' se la regola dura del progetto — *niente short-vol da MODELLO in deploy* (19/06, riconfermata 3 volte) — si possa sostituire con *short-vol da QUOTE*, che e' un'altra cosa: il 30/07 il premio era ancora prezzato Black-Scholes su DVOL ATM e la misura sulle quote reali dava f = 0.73 del modello, con il difetto NON nella gamba venduta (f_short 1.01) ma nell'ALA che si compra (f_long 2.23). Da allora la condizione e' cambiata: la catena vera la raccogliamo noi, oraria, per strike, con bid/ask. ORDINE DELLE DOMANDE (deliberato). L'eseguibilita' viene PRIMA del rendimento — regola del 30/07: "prima di misurare il rendimento a un capitale dato, misurare il lotto minimo del venue". Se non si esegue, il resto e' accademia. §1 LIMITE DI REGIME — risultato di prima riga, non nota a pie' di pagina §2 IL CAMPIONE — quante settimane ci sono DAVVERO, e perche' §3 ESEGUIBILITA' — lotti, famiglie, e su quale delle due si esegue §4 BACKTEST SU QUOTE REALI — griglia dichiarata, fill al bid/ask, fee di listino §5 IL RISCHIO STA NEL MINIMO — non nelle chiusure settimanali §6 GATE — inclusi quelli che NON si possono far girare §7 VERDETTO CONVENZIONI CHE DECIDONO IL RISULTATO (dichiarate qui, non sepolte nel codice) (a) FILL: si vende al BID e si compra all'ASK, sempre. Il mid e' il modello sotto mentite spoglie e compare solo come diagnostica, mai come cella selezionabile. (b) FEE: listino Deribit vero — `min(0.03% del sottostante, 12.5% del premio della SINGOLA opzione)` per gamba, piu' la fee di consegna sulle gambe ITM a scadenza. Lo sleeve usa invece un forfait del 12.5% del credito NETTO: qui si misura di quanto sbaglia. (c) SETTLEMENT: indice reale alla scadenza dal feed certificato. Deribit regola sulla media dell'indice nei 30 minuti prima delle 08:00 UTC; qui si usa la barra 1h di chiusura — approssimazione dichiarata, non nascosta. (d) CAPITALE IMPEGNATO: il denominatore e' il rischio massimo della struttura `(K_short - K_long) x lotto - credito`. E' il limite INFERIORE del capitale richiesto; quello vero dipende dal regime di margine del conto e NON e' leggibile da dati pubblici. Riportato anche il limite superiore (cash-secured, la convenzione del sleeve). RAM: la catena si legge filtrata in LETTURA (solo put, solo le colonne che servono) con pyarrow.dataset — `bite_archive.parquet` intero esplode in memoria e ucciderebbe gli altri agenti. nice -n 19 timeout 900 uv run python scripts/research/r0822_vrp_real_quotes.py ... --no-live # salta le letture dal venue (usa la cache se c'e') """ from __future__ import annotations import argparse import json import sys import time import urllib.request from pathlib import Path import numpy as np import pandas as pd ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT / "scripts" / "research")) sys.path.insert(0, str(ROOT / "scripts" / "research" / "alt")) import altlib as A # noqa: E402 import cblib as CB # noqa: E402 CHAIN_DIR = ROOT / "data" / "raw" / "cb_chain" SNAP_JSONL = ROOT / "data" / "options_daily" / "snapshots.jsonl" CACHE = Path("/tmp/claude-1001/-opt-docker-PythagorasGoal/" "b6cc75e7-14f8-4c32-bd07-ab8a0d2aaee6/scratchpad/r0822_venue.json") # --- listino Deribit (fonte: campo `taker_commission` letto dal venue + schema fee pubblico) --- FEE_UNDERLYING = 0.0003 # 0.03% del sottostante per gamba, per contratto FEE_PREMIUM_CAP = 0.125 # cap: 12.5% del premio della SINGOLA opzione FEE_DELIVERY = 0.00015 # 0.015% del sottostante sulle gambe ITM a scadenza SLEEVE_FEE_FRAC = 0.125 # forfait dello sleeve: 12.5% del credito NETTO DTE_LO, DTE_HI, DTE_TARGET = 4.0, 10.0, 7.0 WEEKS_PER_YEAR = 52.0 # ---------------------------------------------------------------- griglia DICHIARATA # Si conta al RIALZO: ogni variante provata e' un trial, anche quelle scartate. SHORT_DELTAS = (-0.35, -0.28, -0.20, -0.12) LONG_DELTAS = (-0.20, -0.10, -0.05) CELLS = [(s, l) for s in SHORT_DELTAS for l in LONG_DELTAS if abs(l) < abs(s)] FILLS = ("bid_ask", "mid") # `mid` = diagnostica, NON selezionabile ASSETS = ("BTC", "ETH") CANON = (-0.28, -0.10) # la cella di VRP01 def hr(t: str = "", ch: str = "=") -> None: print("\n" + ch * 100) if t: print(t) print(ch * 100) # ================================================================== dati def load_puts() -> pd.DataFrame: """Solo le PUT, solo le colonne che servono, filtrate in LETTURA.""" import pyarrow.dataset as ds cols = ["ts", "asset", "instrument_name", "strike", "option_type", "exp", "bid", "ask", "mid", "iv", "delta", "open_interest", "quote_status"] d = ds.dataset(str(CHAIN_DIR), format="parquet") df = d.to_table(columns=cols, filter=(ds.field("option_type") == "P")).to_pandas() df["ts"] = pd.to_datetime(df["ts"], utc=True).dt.as_unit("ns") df["exp"] = pd.to_datetime(df["exp"], utc=True) df["dte"] = (df["exp"] - df["ts"]).dt.total_seconds() / 86400.0 return df.drop_duplicates(subset=["ts", "instrument_name"], keep="last").reset_index(drop=True) def venue_read(live: bool) -> dict: """Specifiche e liquidita' lette DAL VENUE. Un fallimento si dichiara, non si inventa.""" if not live and CACHE.exists(): return json.loads(CACHE.read_text()) def api(path, **kw): q = "&".join(f"{k}={v}" for k, v in kw.items()) with urllib.request.urlopen( f"https://www.deribit.com/api/v2/public/{path}?{q}", timeout=25) as r: return json.load(r)["result"] out = {"ok": False, "ts": pd.Timestamp.now(tz="UTC").isoformat()} try: out["index"] = {c: api("get_index_price", index_name=f"{c}_usd")["index_price"] for c in ("btc", "eth")} specs, summ = {}, [] for cur in ("BTC", "ETH", "USDC"): for i in api("get_instruments", currency=cur, kind="option", expired="false"): fam = i["instrument_name"].split("-")[0] if fam not in specs: specs[fam] = {k: i.get(k) for k in ("min_trade_amount", "contract_size", "settlement_currency", "quote_currency", "taker_commission", "base_currency")} summ += api("get_book_summary_by_currency", currency=cur, kind="option") time.sleep(0.4) out["specs"] = specs out["summary"] = [{k: r.get(k) for k in ("instrument_name", "bid_price", "ask_price", "mid_price", "mark_price", "mark_iv", "open_interest", "volume", "underlying_price")} for r in summ] out["ok"] = True CACHE.parent.mkdir(parents=True, exist_ok=True) CACHE.write_text(json.dumps(out)) except Exception as exc: # noqa: BLE001 out["error"] = f"{type(exc).__name__}: {exc}" if CACHE.exists(): cached = json.loads(CACHE.read_text()) cached["stale"] = out["error"] return cached return out def specs_from_disk() -> dict: """Le specifiche gia' registrate dal progetto (`data/options_daily/snapshots.jsonl`). Serve come controllo indipendente della lettura live: due percorsi, stesso numero.""" mins, fees, last_ts = {}, {}, {} if not SNAP_JSONL.exists(): return {} with SNAP_JSONL.open() as f: for line in f: try: r = json.loads(line) except Exception: # noqa: BLE001, S112 continue if r.get("rec") != "chain": continue c = r["currency"] mins.setdefault(c, set()).add(r.get("min_trade_amount")) fees.setdefault(c, set()).add(r.get("taker_comm")) last_ts[c] = r["snap_ts"] return {c: {"min": sorted(x for x in mins[c] if x is not None), "taker": sorted(x for x in fees[c] if x is not None), "ts": pd.Timestamp(last_ts[c], unit="ms", tz="UTC")} for c in mins} # ================================================================== struttura def leg_fee_usd(prem_base: float, spot: float) -> float: """Fee di listino per UNA gamba, per contratto (1 unita' di sottostante), in USD.""" return min(FEE_UNDERLYING * spot, FEE_PREMIUM_CAP * prem_base * spot) def entry_snapshots(puts: pd.DataFrame, asset: str, offset_h: float = 0.0) -> dict: """Per ogni scadenza settimanale: lo snapshot piu' vicino a (7 giorni + offset) dalla scadenza. L'offset e' l'ANCORA: entrare alle 08:00 del venerdi' precedente e' UNA delle scelte possibili, e questo progetto ha misurato 4 volte che l'ancora e' un max-of-k non dichiarato. """ p = puts[(puts["asset"] == asset) & (puts["dte"] >= DTE_LO) & (puts["dte"] <= DTE_HI)] tgt = DTE_TARGET + offset_h / 24.0 out = {} for exp, g in p.groupby("exp"): ts_all = np.array(sorted(g["ts"].unique())) if not len(ts_all): continue dte = (exp - pd.DatetimeIndex(ts_all)).total_seconds() / 86400.0 ok = (dte >= DTE_LO) & (dte <= DTE_HI) if not ok.any(): continue cand = ts_all[ok][np.argsort(np.abs(dte[ok] - tgt))] for ts in cand[:6]: # al piu' 6 tentativi per scadenza snap = g[g["ts"] == ts] if snap["bid"].notna().sum() >= 2: out[exp] = (pd.Timestamp(ts), snap) break return out def build_trades(puts: pd.DataFrame, asset: str, short_d: float, long_d: float, fill: str = "bid_ask", offset_h: float = 0.0, lot: float = 1.0, today: pd.Timestamp | None = None) -> pd.DataFrame: """Un trade per scadenza: ingresso su quote REALI, tenuto a scadenza, regolato sull'indice.""" today = today or pd.Timestamp.now(tz="UTC") S = CB.spot_series(asset) V = CB.dvol_series(asset) rows, rejected = [], {"gambe_mancanti": 0, "credito_non_positivo": 0} for exp, (ts, snap) in entry_snapshots(puts, asset, offset_h).items(): if exp >= today: # non ancora regolata: non e' un trade continue legs = CB.pick_legs(snap, short_d, long_d) if legs is None: rejected["gambe_mancanti"] += 1 continue spot = float(S.asof(ts)) dte = (exp - ts).total_seconds() / 86400.0 dvol = float(V.asof(ts)) hist = V[V.index < ts] ivr = float((hist < dvol).mean()) if len(hist) else np.nan if fill == "bid_ask": ps, pl = legs["bid_short"], legs["ask_long"] # vendi al bid, compra all'ask else: ps, pl = legs["mid_short"], legs["mid_long"] if not (np.isfinite(ps) and np.isfinite(pl)): rejected["gambe_mancanti"] += 1 continue cred = (ps - pl) * spot # USD per 1 unita' di sottostante if cred <= 0: # premio non monotono nello strike (difetto certificato di `certify_cb_chain`): # una put piu' OTM quotata sopra una meno OTM. Non e' una struttura, e' una quota rotta. rejected["credito_non_positivo"] += 1 continue ST = float(S.asof(exp)) payoff = max(legs["k_short"] - ST, 0.0) - max(legs["k_long"] - ST, 0.0) fee = leg_fee_usd(ps, spot) + leg_fee_usd(pl, spot) for K in (legs["k_short"], legs["k_long"]): # consegna sulle gambe ITM intr = max(K - ST, 0.0) if intr > 0: fee += min(FEE_DELIVERY * ST, FEE_PREMIUM_CAP * intr) width = legs["k_short"] - legs["k_long"] mod = CB.f_factors(legs, spot, dvol / 100.0, dte) rows.append(dict( asset=asset, ts=ts, exp=exp, dte=dte, spot=spot, ST=ST, inst_short=legs["inst_short"], inst_long=legs["inst_long"], cred_real=cred, # per 1 unita': lo vuole CB.close_cost_path k_short=legs["k_short"], k_long=legs["k_long"], width=width, d_short=legs["d_short"], d_long=legs["d_long"], cred=cred * lot, payoff=payoff * lot, fee=fee * lot, pnl=(cred - payoff - fee) * lot, fee_sleeve=SLEEVE_FEE_FRAC * mod["cred_mod"] * lot, fee_sleeve_su_reale=SLEEVE_FEE_FRAC * cred * lot, cred_mod=mod["cred_mod"] * lot, risk=width * lot - cred * lot, dvol=dvol, ivrank=ivr, f_net=mod["f_net"], und_ret=ST / spot - 1.0, )) df = pd.DataFrame(rows) if not df.empty: df = df.sort_values("exp").reset_index(drop=True) df.attrs["rejected"] = rejected return df def stats(tr: pd.DataFrame) -> dict: """Metriche di una serie di trade settimanali. Il denominatore e' il RISCHIO impegnato.""" if tr.empty or len(tr) < 2: return dict(n=len(tr), sharpe=np.nan, mean=np.nan, t=np.nan) r = (tr["pnl"] / tr["risk"]).to_numpy(float) # ritorno sul capitale a rischio sd = float(np.std(r, ddof=1)) m = float(np.mean(r)) eq = np.cumprod(1.0 + r) dd = float(np.max((np.maximum.accumulate(eq) - eq) / np.maximum.accumulate(eq))) return dict(n=len(tr), mean=m, sd=sd, sharpe=(m / sd * np.sqrt(WEEKS_PER_YEAR)) if sd > 0 else np.nan, t=(m / (sd / np.sqrt(len(r)))) if sd > 0 else np.nan, maxdd_close=dd, wins=int((tr["pnl"] > 0).sum()), usd_week=float(tr["pnl"].mean()), usd_tot=float(tr["pnl"].sum())) def weekly_series(tr: pd.DataFrame) -> pd.Series: return pd.Series((tr["pnl"] / tr["risk"]).to_numpy(float), index=pd.DatetimeIndex(tr["exp"])).sort_index() def boot_ci(x: np.ndarray, n: int = 4000, seed: int = 822) -> tuple[float, float]: rng = np.random.default_rng(seed) if len(x) < 2: return (np.nan, np.nan) bs = rng.choice(x, size=(n, len(x)), replace=True).mean(axis=1) return float(np.percentile(bs, 2.5)), float(np.percentile(bs, 97.5)) # ================================================================== main def main() -> None: ap = argparse.ArgumentParser() ap.add_argument("--no-live", action="store_true", help="non interrogare il venue") args = ap.parse_args() print(__doc__.split("\n\n")[0]) puts = load_puts() today = pd.Timestamp.now(tz="UTC") venue = venue_read(not args.no_live) # ------------------------------------------------------------ §1 REGIME hr("§1 LIMITE DI REGIME — il campione descrive il regime in cui la strategia sta FLAT") print("Non e' una nota a pie' di pagina: VRP01 entra SOLO se IV-rank > 0.30 (il gate e' l'alpha,\n" "misurato 4 volte). Se nel campione quel gate non scatta mai, il campione non contiene\n" "la strategia — contiene la sua astensione.\n") for a in ASSETS: V = CB.dvol_series(a) win = V[(V.index >= puts["ts"].min()) & (V.index <= puts["ts"].max())] hist = V[V.index < win.index[0]].to_numpy(float) print(f" {a}: DVOL nel campione min {win.min():.1f} / mediana {win.median():.1f} / " f"max {win.max():.1f}") print(f" vs storia 2021+ ({len(hist)} giorni): la MEDIANA del campione sta al " f"{float((hist < win.median()).mean()):.0%} pctl, il suo MASSIMO al " f"{float((hist < win.max()).mean()):.0%} pctl") print("\n \u26a0\ufe0f Due statistiche diverse, da tenere distinte: CLAUDE.md cita «DVOL max al 24\u00b0/28\u00b0\n" " pctl» dal campione del 30/07. Un MASSIMO cresce con la finestra — e' una statistica\n" " di ESTREMO, non di livello. Per dire in che regime siamo la lente e' la MEDIANA, ed\n" " e' bassissima.") # ------------------------------------------------------------ §2 CAMPIONE hr("§2 IL CAMPIONE — quante settimane ci sono DAVVERO") pre = puts[puts["ts"] < pd.Timestamp("2026-06-09", tz="UTC")] print(f" catena su disco: {puts['ts'].min():%Y-%m-%d} → {puts['ts'].max():%Y-%m-%d} " f"({(puts['ts'].max() - puts['ts'].min()).days} giorni, {len(puts):,} righe put)") if not pre.empty: print(f" ma PRIMA del 2026-06-09 l'archivio ereditato seguiva UNA sola scadenza, in " f"finestra {pre['dte'].min():.0f}-{pre['dte'].max():.0f} DTE:") print(f" → la finestra settimanale {DTE_LO:.0f}-{DTE_HI:.0f} DTE NON ESISTE prima " f"della raccolta full-chain.") print("\n Trade REGOLATI utilizzabili (cella canonica VRP01 δ-0.28/-0.10, fill bid/ask):") canon: dict[str, pd.DataFrame] = {} for a in ASSETS: lot = 1.0 tr = build_trades(puts, a, *CANON, lot=lot, today=today) canon[a] = tr rej = tr.attrs.get("rejected", {}) if not tr.empty else {} print(f" {a}: {len(tr)} settimane regolate ({tr['exp'].min():%Y-%m-%d} → " f"{tr['exp'].max():%Y-%m-%d}) scartate: {rej}") print("\n ⚠️ «3,7 mesi ≈ 16 scadenze settimanali» e' FALSO su questi dati: sono 10 per asset.") print("\n Gate IV-rank>0.30 di VRP01 sulle settimane utilizzabili:") tot = passed = 0 for a in ASSETS: tr = canon[a] n = int((tr["ivrank"] > 0.30).sum()) tot += len(tr) passed += n print(f" {a}: {n}/{len(tr)} passano (IV-rank min {tr['ivrank'].min():.3f} / " f"max {tr['ivrank'].max():.3f})") print(f" TOTALE: {passed}/{tot}. Il sleeve sarebbe stato FLAT per l'intero campione.") print("\n QUANDO TORNERA' MISURABILE — il gate non e' chiuso per sempre, e la soglia e' un numero.") print(" Regola del 19/06: «rivalutare quando la catena cattura un crash». Un criterio senza\n" " numero non e' un criterio, quindi eccolo (IV-rank espandente causale, come nel sleeve):") for a in ASSETS: V = CB.dvol_series(a) vals = V.to_numpy(float) ivr = np.full(len(vals), np.nan) for i in range(1, len(vals)): ivr[i] = (vals[:i] < vals[i]).mean() ser = pd.Series(ivr, index=V.index).dropna() per_year = ser.groupby(ser.index.year).apply(lambda x: float((x > 0.30).mean())) thr = float(np.quantile(vals[:-1], 0.30)) last_open = ser[ser > 0.30] since = (V.index[-1] - last_open.index[-1]).days if len(last_open) else None inwin = ser[(ser.index >= puts["ts"].min()) & (ser.index <= puts["ts"].max())] near = inwin.max() if len(inwin) else float("nan") print(f" {a}: quota di giorni col gate APERTO per anno — " + " ".join(f"{y}:{v:.0%}" for y, v in per_year.items())) print(f" DVOL oggi {vals[-1]:.1f} · serve DVOL > {thr:.1f} " f"(+{thr / vals[-1] - 1:.0%}) per riaprire il gate · ultimo giorno aperto " f"{last_open.index[-1]:%Y-%m-%d} ({since} giorni fa)") print(f" quanto vicino c'e' andato DENTRO la finestra: IV-rank max " f"{near:.4f} il {inwin.idxmax():%Y-%m-%d} — " f"{'mancato per ' + format(0.30 - near, '.4f') if near <= 0.30 else 'aperto'}") print(" → il campione non e' 'sfortunato': cade dentro un regime di vol bassa lungo e\n" " persistente. Ma il margine NON e' comodo: il gate ha sfiorato l'apertura, e\n" " bastano ~+10% di DVOL per riaprirlo. La misura giusta si potra' rifare quando quel numero sara' superato,\n" " e da allora servira' comunque un anno di settimane per avere un campione.") ret = {a: (canon[a]["ST"].iloc[-1] / canon[a]["spot"].iloc[0] - 1.0) for a in ASSETS} print(f"\n E il sottostante nel campione: BTC {ret['BTC']:+.1%}, ETH {ret['ETH']:+.1%} — " f"vol implicita bassa E rialzo forte\n = il regime IDEALE per vendere put. " f"Il campione e' il migliore possibile per questa struttura, non uno neutro.") # ------------------------------------------------------------ §3 ESEGUIBILITA' hr("§3 ESEGUIBILITA' — e qui il filone cambia domanda") fams = sorted({n.split("-")[0] for n in puts["instrument_name"].head(50_000)}) print(f" La catena che raccogliamo contiene le famiglie: {fams}") print(" → sono le opzioni INVERSE: quotate, marginate e regolate in BTC/ETH.") print(" Il conto e' in USDC ($635) e il book esegue sui perpetual LINEARI USDC.\n") disk = specs_from_disk() if disk: print(" Specifiche gia' registrate dal progetto (data/options_daily/snapshots.jsonl):") for c, v in sorted(disk.items()): print(f" {c:6s} min_trade_amount={v['min']} taker={v['taker']} ({v['ts']:%Y-%m-%d %H:%M})") if venue.get("ok"): idx = venue["index"] print(f"\n Lette dal venue ora — indice BTC ${idx['btc']:,.0f} / ETH ${idx['eth']:,.0f}:") print(f" {'famiglia':11s} {'min lotto':>10s} {'settle':>7s} {'quote':>7s} " f"{'$ nozionale/lotto':>18s}") for fam in ("BTC", "ETH", "BTC_USDC", "ETH_USDC"): s = venue["specs"].get(fam) if not s: continue base = (s.get("base_currency") or fam.split("_")[0]).lower() notion = float(s["min_trade_amount"]) * idx[base] print(f" {fam:11s} {s['min_trade_amount']:>10} {str(s['settlement_currency']):>7s} " f"{str(s['quote_currency']):>7s} {notion:>17,.0f}$") print("\n 📌 Esiste una famiglia USDC-lineare per BTC ed ETH, con lotto minimo 10x PIU'") print(" PICCOLO e regolata nella valuta del conto. Il muro «BTC fuori a $3.000» del") print(" 30/07 — congelato in tests/test_vrp_profit_take.py — e' stato misurato sulla") print(" famiglia che il conto NON margina.") print(" E' la 3a occorrenza della stessa forma dopo fee_watch (21/08, sorvegliava gli") print(" INVERSE mentre il book trada i LINEARI) e la taratura di venue_watch.") # --- ma la famiglia eseguibile e' quotata davvero? --- d = pd.DataFrame(venue["summary"]) parts = d["instrument_name"].str.split("-") d["fam"] = parts.str[0] d["typ"] = parts.str[3] d["strike"] = pd.to_numeric(parts.str[2].str.replace("d", ".", regex=False), errors="coerce") d["expd"] = pd.to_datetime(parts.str[1], format="%d%b%y", utc=True) + pd.Timedelta(hours=8) d["dte"] = (d["expd"] - today).dt.total_seconds() / 86400.0 d["base"] = d["fam"].str.replace("_USDC", "", regex=False).str.lower() d = d[d["typ"].eq("P") & d["base"].isin(["btc", "eth"])].copy() d["S"] = d["base"].map(idx) d["mny"] = d["strike"] / d["S"] q = d[(d["bid_price"] > 0) & (d["ask_price"] > 0)].copy() q["relsp"] = (q["ask_price"] - q["bid_price"]) / q["mid_price"] # regione delle put OTM usate dalla struttura (δ -0.35..-0.05 ≈ moneyness 0.78-0.97) w = q[(q["dte"] > 3) & (q["dte"] < 11) & q["mny"].between(0.78, 0.97)] print("\n Liquidita' REALE nella regione di strike che serve (scadenza settimanale, ora):") print(f" {'famiglia':11s} {'n quotate':>9s} {'spread rel. med':>16s} " f"{'p75':>7s} {'OI med':>9s} {'vol 24h':>10s}") for fam, g in w.groupby("fam"): allf = d[d["fam"] == fam] print(f" {fam:11s} {len(g):>9d} {g['relsp'].median():>15.0%} " f"{g['relsp'].quantile(.75):>6.0%} {g['open_interest'].median():>9.1f} " f"{allf['volume'].sum():>10,.0f}") print("\n Lo spread relativo e' ENORME in entrambe le famiglie (il mid non e' un prezzo\n" " eseguibile: e' la media di due prezzi lontani), e sulla famiglia USDC — l'unica\n" " marginabile dal conto — l'open interest e' ~0: quotata da un market maker,\n" " praticamente non tradata. Comprare l'ala li' dentro costa il doppio.") else: print(f"\n ⚠️ LETTURA DAL VENUE NON RIUSCITA: {venue.get('error', 'n/d')}") print(" Le specifiche delle famiglie USDC restano NON MISURATE in questo giro.") print("\n Capitale per UN lotto minimo, cella canonica (limiti dichiarati, vedi docstring):") print(f" {'asset':6s} {'lotto':>7s} {'width med':>10s} {'rischio max':>12s} " f"{'cash-secured':>13s} {'credito/sett':>13s}") minlot = {"BTC": 0.1, "ETH": 1.0} if venue.get("ok"): for fam in ("BTC", "ETH"): if fam in venue["specs"]: minlot[fam] = float(venue["specs"][fam]["min_trade_amount"]) for a in ASSETS: tr, lot = canon[a], minlot[a] wid = float(tr["width"].median()) print(f" {a:6s} {lot:>7.2f} {wid:>10,.0f} {wid * lot - tr['cred'].median() * lot:>11,.0f}$ " f"{tr['k_short'].median() * lot:>12,.0f}$ {tr['cred'].median() * lot:>12,.2f}$") print(" Sul rischio DEFINITO un lotto minimo inverse sta dentro $600. Sul cash-secured no.\n" " Quale dei due valga dipende dal regime di margine del conto (standard vs portfolio),\n" " che NON e' leggibile da dati pubblici.") print(" ⚠️ E c'e' una domanda PRIMA di quella, che nessun dato pubblico chiude: un saldo in\n" " USDC puo' marginare un'opzione INVERSE (collaterale in BTC/ETH)? Se no, la famiglia\n" " con 3 mesi di storia non e' negoziabile da questo conto a NESSUN capitale.\n" " Precedente esatto: il 26/07 GTAA01 aveva 5 settimane di misure sopra l'assunzione\n" " che i 6 ETF fossero comprabili — il blocco PRIIPs si scopri' con UN ordine di prova.\n" " La regola che ne usci' («la negoziabilita' sul conto REALE si verifica quando lo\n" " sleeve entra in RICERCA, non quando entra nel book») si applica qui identica, e\n" " questo script NON puo' eseguirla: richiede il conto, non i dati.") # ------------------------------------------------------------ §4 BACKTEST hr("§4 BACKTEST SU QUOTE REALI — griglia dichiarata") n_trials = len(CELLS) * len(FILLS) * (len(ASSETS) + 1) print(f" celle δ (short,long): {len(CELLS)} fill: {len(FILLS)} " f"asset: {len(ASSETS)} + combinato 50/50") print(f" → TRIAL DICHIARATI AL RIALZO: {n_trials}. Nessuna griglia ridotta per budget.") print(f" (Il fill `mid` non e' selezionabile — e' la diagnostica di quanto costa lo spread —\n" f" ma conta come trial: e' stato guardato.)\n") grid = [] for fill in FILLS: for (sd, ld) in CELLS: per, comb = {}, [] for a in ASSETS: tr = build_trades(puts, a, sd, ld, fill=fill, lot=1.0, today=today) per[a] = stats(tr) if not tr.empty: comb.append(weekly_series(tr)) row = dict(fill=fill, sd=sd, ld=ld) for a in ASSETS: row[f"{a}_sh"] = per[a]["sharpe"] row[f"{a}_n"] = per[a]["n"] if comb: c = pd.concat(comb, axis=1).mean(axis=1).dropna() row["comb_sh"] = (float(c.mean() / c.std(ddof=1) * np.sqrt(WEEKS_PER_YEAR)) if c.std(ddof=1) > 0 else np.nan) row["comb_n"] = len(c) grid.append(row) G = pd.DataFrame(grid) print(G.round(2).to_string(index=False)) print("\n ⚠️ COME SI LEGGE LA TABELLA SOPRA. Gli 'Sharpe' a due cifre NON sono un risultato:\n" " sono la firma del campione. Annualizzare (x√52) 10 osservazioni settimanali in cui\n" " la coda sinistra non si e' manifestata produce numeri privi di significato — e il\n" " fatto che l'INTERA griglia li produca e' esattamente il segnale, non un caso da\n" " selezionare. E' la 4a occorrenza della firma '0-perdite / Sharpe implausibile'\n" " dopo CC01, le celle deep-OTM Albimarini e meta' della griglia VRP del 03/07.\n" " Per lo stesso motivo NON viene riportato un CAGR: comporre 10 settimane a 52\n" " sarebbe una fabbricazione, non una stima.") print("\n Cella canonica di VRP01 (δ-0.28/-0.10), fill conservativo, per asset — numeri\n" " CONCRETI (dollari per lotto minimo), che a questa taglia dicono piu' di un rapporto:") print(f" {'asset':6s} {'n':>3s} {'vinte':>7s} {'$/sett':>9s} {'$ tot':>9s} " f"{'% rischio/sett':>15s} {'t':>6s} {'maxDD chiusure':>15s}") st = {} for a in ASSETS: tr = canon[a].copy() tr[["cred", "payoff", "fee", "pnl", "risk", "cred_mod", "fee_sleeve", "fee_sleeve_su_reale"]] *= minlot[a] st[a] = stats(tr) s = st[a] print(f" {a:6s} {s['n']:>3d} {s['wins']:>3d}/{s['n']:<3d} {s['usd_week']:>8.2f}$ " f"{s['usd_tot']:>8.2f}$ {s['mean']:>14.2%} {s['t']:>6.2f} {s['maxdd_close']:>14.1%}") print(" maxDD 0.0% su BTC significa che NON C'E' STATA una settimana in perdita: e' una\n" " proprieta' del campione, non della struttura.") print("\n Costo dello SPREAD, misurato (stesso ingresso, fill al mid vs al bid/ask):") for a in ASSETS: ba = build_trades(puts, a, *CANON, fill="bid_ask", today=today) md = build_trades(puts, a, *CANON, fill="mid", today=today) j = ba.merge(md, on="exp", suffixes=("_ba", "_md")) # appaiato per INGRESSO if j.empty: continue print(f" {a}: credito medio al mid ${j['cred_md'].mean():.2f} → al bid/ask " f"${j['cred_ba'].mean():.2f} = si perde il " f"{1 - j['cred_ba'].mean() / j['cred_md'].mean():.0%} del credito solo attraversando " f"lo spread") print("\n Il f del 30/07, ri-misurato su 10 settimane invece di 8 (fill conservativo):") for a in ASSETS: f = canon[a]["f_net"].dropna() lo, hi = boot_ci(f.to_numpy()) print(f" {a}: f_net mediana {f.median():.3f} media {f.mean():.3f} " f"IC95 [{lo:.3f}, {hi:.3f}] ≥1.0 in {int((f >= 1).sum())}/{len(f)}") allf = pd.concat([canon[a]["f_net"] for a in ASSETS]).dropna() lo, hi = boot_ci(allf.to_numpy()) print(f" pooled: {allf.median():.3f} (IC95 [{lo:.3f}, {hi:.3f}], n={len(allf)}) — " f"replica indipendente dello 0.73 del 30/07") print("\n Fee: listino vero contro il forfait dello sleeve (12.5% del credito NETTO).") print(" ⚠️ Il confronto giusto e' col credito MODELLATO — e' quello a cui il sleeve applica\n" " il forfait. Misurarlo sul credito reale (piu' piccolo di ~30%) sottostima l'errore:") for a in ASSETS: tr = canon[a] print(f" {a}: fee reale ${tr['fee'].mean():.2f}/settimana per contratto vs forfait sul\n" f" credito MODELLATO ${tr['fee_sleeve'].mean():.2f} (= {tr['fee_sleeve'].mean() / tr['fee'].mean():.2f}x)" f" · sul credito reale ${tr['fee_sleeve_su_reale'].mean():.2f} " f"(= {tr['fee_sleeve_su_reale'].mean() / tr['fee'].mean():.2f}x)") print(" Il '~2x' pubblicato il 30/07 non si replica qui: su queste 19 settimane il forfait\n" " sovrastima le fee di 1.1-1.9x a seconda dell'asset e della base di confronto.\n" " Resta vero il segno (il forfait e' CONSERVATIVO), non la taglia.") # ------------------------------------------------------------ §5 IL MINIMO hr("§5 IL RISCHIO STA NEL MINIMO — le chiusure settimanali non lo vedono") print(" Regola del 30/07: un rischio valutato sul minimo non si misura sulle chiusure.\n" " Qui il mark infra-settimana viene dalle standing quotes ORARIE delle STESSE due gambe\n" " (ricomprare la corta all'ask, rivendere la lunga al bid: di nuovo conservativo).\n") worst_rows = [] for a in ASSETS: print(f" {a} (lotto {minlot[a]})") for _, r in canon[a].iterrows(): path = CB.close_cost_path(r, puts) if path.empty: continue lot = minlot[a] risk = r["width"] * lot - r["cred"] * lot worst = float(path["pnl_aperto"].min()) * lot worst_rows.append(dict(asset=a, exp=r["exp"], worst=worst, risk=risk, frac=worst / risk, pnl=r["pnl"] * lot, n_marks=len(path))) flag = " ← chiusa in UTILE" if r["pnl"] > 0 and worst < 0 else "" print(f" {r['exp']:%Y-%m-%d} marks={len(path):3d} " f"peggior mark {worst:>9.2f}$ = {worst / risk:>7.1%} del rischio " f"esito {r['pnl'] * lot:>7.2f}${flag}") W = pd.DataFrame(worst_rows) if not W.empty: print(f"\n Peggior mark infra-settimana: mediana {W['frac'].median():.1%} del rischio, " f"minimo {W['frac'].min():.1%}") wmin = W.loc[W['frac'].idxmin()] print(f" Denominatore, per non fabbricare una discrepanza: qui il rischio e' " f"`width - credito`.\n" f" CLAUDE.md cita «-81,7% del capitale» per la STESSA settimana " f"({wmin['asset']} {wmin['exp']:%Y-%m-%d}):\n" f" e' lo stesso mark (${wmin['worst']:.2f}) diviso per la width nuda. Due " f"denominatori, un solo fatto.") wl = W[W["pnl"] > 0] print(f" Fra le sole settimane chiuse in UTILE ({len(wl)}/{len(W)}): mediana " f"{wl['frac'].median():.1%}, minimo {wl['frac'].min():.1%}") print(" → il maxDD sulle chiusure e' una lente che non vede il rischio che si e' corso.") # ------------------------------------------------------------ §6 GATE hr("§6 GATE — compresi quelli che NON si possono far girare") print(" (a) implausible_sharpe — il gate del 26/07 sul rischio FUORI dal campione") for a in ASSETS: tr = canon[a] daily = pd.Series((tr["pnl"] / tr["risk"]).to_numpy(float), index=pd.DatetimeIndex(tr["exp"])) rep = A.implausible_sharpe(daily, n_trades=len(tr), n_losing_trades=int((tr["pnl"] <= 0).sum())) print(f" {a}: implausible={rep['implausible']} vinte {int((tr['pnl'] > 0).sum())}/{len(tr)}") for why in rep["reasons"]: print(f" · {why}") if int((tr["pnl"] <= 0).sum()) == 0: print(f" · regola del tre: 0 perdite su {len(tr)} lascia un tasso VERO fino a " f"{3 / len(tr):.0%}. Su un payoff che perde {tr['risk'].median() / tr['cred'].median():.1f}x " f"il credito, basta a ribaltare l'expectancy.") print("\n (b) deflated_sharpe — NON CALCOLABILE, e il perche' e' il risultato") for a in ASSETS: s = weekly_series(canon[a]) dsr, _ = A.deflated_sharpe(st[a]["sharpe"], [r["comb_sh"] for r in grid if np.isfinite(r.get("comb_sh", np.nan))], s, dpy=WEEKS_PER_YEAR) print(f" {a}: DSR = {dsr} (T={len(s)} osservazioni settimanali; la funzione " f"richiede T>=30)") print(" Costruire una serie GIORNALIERA per superare il T>=30 sarebbe barare: uno sleeve\n" " settimanale su griglia giornaliera e' ~94% di zeri (lezione 26/07 su VRP01), e il\n" " T gonfiato entra nel deflated-Sharpe come se fossero osservazioni indipendenti.\n" " Statistica onesta a questa taglia = il t con il suo intervallo:") for a in ASSETS: r = (canon[a]["pnl"] / canon[a]["risk"]).to_numpy(float) lo, hi = boot_ci(r) print(f" {a}: media settimanale {r.mean():+.2%} del rischio, IC95 " f"[{lo:+.2%}, {hi:+.2%}], t={st[a]['t']:.2f}, n={len(r)}") print("\n (c) anchor_luck_band — l'ora d'ingresso e' un'ancora (max-of-k mai dichiarato)") offsets = list(range(-24, 25, 6)) for a in ASSETS: def by_off(o, _a=a): return weekly_series(build_trades(puts, _a, *CANON, offset_h=float(o), today=today)) band = A.anchor_luck_band( by_off, offsets, canonical=0, metric=lambda s: (float(s.mean() / s.std(ddof=1) * np.sqrt(WEEKS_PER_YEAR)) if len(s) > 2 and s.std(ddof=1) > 0 else 0.0)) if band.get("reason"): print(f" {a}: {band['reason']}") continue print(f" {a}: canonica {band['canonical']:.2f} (pctl {band['canonical_pctl']:.0%}) · " f"MEDIANA ONESTA {band['median']:.2f} · banda [{band['lo']:.2f}, {band['hi']:.2f}] · " f"positiva in {band['frac_positive']:.0%} di {band['n_anchors']} ancore · " f"fortuna {band['luck']:+.2f}") # DOVE vive l'instabilita': nel numeratore (il guadagno) o nel denominatore (la vol)? det = [] for o in offsets: w = by_off(o) if len(w) > 2 and w.std(ddof=1) > 0: det.append((o, float(w.mean()), float(w.std(ddof=1)), len(w))) if det: mm = np.array([d[1] for d in det]) ss = np.array([d[2] for d in det]) # il rapporto si stampa SOLO se il minimo e' positivo: max/min con min<0 # e' un numero senza significato (prima stesura: '112.240.864x'). rm = (f"{mm.max() / mm.min():.1f}x" if mm.min() > 0 else "SEGNO CHE CAMBIA") print(f" media settimanale fra le ancore: [{mm.min():+.2%}, {mm.max():+.2%}] " f"({rm}) · deviazione standard: [{ss.min():.2%}, {ss.max():.2%}] " f"({ss.max() / ss.min():.1f}x)") print(f" → si muovono ENTRAMBI, numeratore e denominatore" f"{' — e sulla media il segno CAMBIA' if mm.min() <= 0 else ''}.\n" f" Il motivo e' meccanico e vale la pena dirlo: spostare l'ora d'ingresso\n" f" cambia lo SNAPSHOT, quindi quali strike combaciano col delta bersaglio\n" f" — non e' lo stesso trade un'ora dopo, e' un'altra struttura. Con\n" f" {det[0][3]} settimane questo parametro di disturbo domina tutto il resto.") print("\n (d) null del de-levering / beta — 'e' reddito da volatilita' o crypto travestito?'") print(" Lo spread ha delta positivo (≈ +0.18 all'ingresso): in un campione che sale del") print(" 20-50% guadagna PER COSTRUZIONE. Regressione del PnL settimanale sul ritorno") print(" del sottostante nella stessa settimana:") for a in ASSETS: tr = canon[a] x = tr["und_ret"].to_numpy(float) y = (tr["pnl"] / tr["risk"]).to_numpy(float) if len(x) < 4: continue b, alpha = np.polyfit(x, y, 1) resid = y - (b * x + alpha) se = float(np.std(resid, ddof=2) / (np.std(x) * np.sqrt(len(x)))) print(f" {a}: beta {b:+.3f} (t={b / se:.2f}) alpha {alpha:+.2%}/settimana " f"corr {np.corrcoef(x, y)[0, 1]:+.2f}") k = abs(b) lev = pd.Series(k * x, index=pd.DatetimeIndex(tr["exp"])) sh_lev = (float(lev.mean() / lev.std(ddof=1) * np.sqrt(WEEKS_PER_YEAR)) if lev.std(ddof=1) > 0 else np.nan) print(f" null: essere semplicemente LONG il sottostante a leva {k:.2f} " f"(stesso beta) da' Sharpe {sh_lev:.2f} contro {st[a]['sharpe']:.2f} della struttura") print("\n (e) marginal_vs_tp01 — girato, ma NON interpretabile a questa taglia") try: comb = pd.concat([weekly_series(canon[a]) for a in ASSETS], axis=1).mean(axis=1).dropna() dly = comb.resample("1D").sum().fillna(0.0) rep = A.marginal_vs_tp01(dly) act = int((dly != 0).sum()) def _f(k): v = rep.get(k) return f"{v:.3f}" if isinstance(v, (int, float)) and v is not None else str(v) print(f" verdetto={rep.get('verdict')} corr={_f('corr')} " f"insample_edge={rep.get('has_insample_edge')} robust_oos={rep.get('robust_oos')} " f"is_hedge={rep.get('is_hedge')}") print(f" barre ATTIVE {act}/{len(dly)} = {act / len(dly):.0%} — un `None` qui non e'\n" f" un bug: le sotto-misure che richiedono un hold-out non hanno dati per\n" f" esistere, e la funzione lo dichiara invece di inventare un numero.") print(f" ⚠️ {len(comb)} settimane su un hold-out che non esiste: il verdetto e' " f"rumore, si riporta per non ometterlo.") except Exception as exc: # noqa: BLE001 print(f" NON GIRATO: {type(exc).__name__}: {exc}") print("\n (f) causality_ok — non girato su questo candidato, e perche'") print(" `A.causality_ok` valuta target_fn su OHLCV BTC/ETH; qui il segnale non e' una") print(" serie di prezzo ma una selezione di strumenti da uno snapshot. Verifica") print(" equivalente eseguita per costruzione e controllata qui sotto:") bad = 0 for a in ASSETS: for _, r in canon[a].iterrows(): if r["ts"] >= r["exp"] or not (DTE_LO <= r["dte"] <= DTE_HI): bad += 1 print(f" ingressi con ts >= scadenza o fuori finestra DTE: {bad}/" f"{sum(len(canon[a]) for a in ASSETS)} (le gambe si scelgono da UNO snapshot allo\n" f" stesso ts; il regolamento usa l'indice alla scadenza, mai prima)") # ------------------------------------------------------------ §7 VERDETTO hr("§7 VERDETTO") print(""" SCARTATO — su questi dati la domanda non e' decidibile. Quattro motivi, indipendenti l'uno dall'altro: ciascuno da solo basterebbe. 1. IL CAMPIONE NON CONTIENE LA STRATEGIA. 0 settimane su 19 passano il gate IV-rank>0.30 che E' l'alpha di VRP01 — il sleeve sarebbe stato FLAT per l'intero campione. La DVOL mediana sta al 7 (BTC) / 11 (ETH) percentile della storia 2021+, e il sottostante e' salito del +25% / +44%: vol implicita bassa E rialzo forte = il regime MIGLIORE possibile per vendere put, non uno neutro. Le settimane sono 10 per asset, non 16 (prima del 2026-06-09 l'archivio seguiva solo la finestra 13-20 DTE: quella 4-10 DTE non esiste). Esito: 10/10 vincenti su BTC, e la lente giusta non e' lo Sharpe ma la regola del tre — 0 perdite su 10 lascia un tasso vero fino al 30%, su un payoff che perde ~7x il credito. ⚠️ Sfumatura che va detta: il gate non e' lontano. Ha mancato l'apertura per 0.0002 di IV-rank il 2026-06-07, e bastano ~+10% di DVOL per riaprirlo. Il campione e' dentro un regime di vol bassa persistente, non davanti a un muro. 2. IL TITOLO NON SOPRAVVIVE ALL'ORA D'INGRESSO. E' un max-of-k mai dichiarato, misurato qui per la prima volta su questa struttura: su 9 ancore lo Sharpe canonico BTC 32.58 sta all'89 percentile, la MEDIANA ONESTA e' 1.45 e la banda tocca il NEGATIVO (-0.35). Su ETH 3.90 -> 2.41. Il numero da citare per BTC e' 1.45, non 32.58. ⚠️ E il meccanismo non e' quello che avevo scritto prima di guardarlo. Non e' solo la vol campionaria a ballare: fra le ancore la MEDIA settimanale BTC va da -1.35% a +11.22% del rischio — CAMBIA SEGNO — e la deviazione standard di 11x (ETH: media 2.9x, sd 2.4x). Spostare l'ora d'ingresso cambia lo snapshot e quindi quali strike combaciano col delta bersaglio: non e' lo stesso trade un'ora dopo, e' UN'ALTRA STRUTTURA. A 10 settimane questo parametro di disturbo domina il segnale che si vorrebbe misurare. 3. LA FAMIGLIA CHE HA STORIA NON E' QUELLA CHE SI PUO' ESEGUIRE. Le quote raccolte sono le opzioni INVERSE (margine e regolamento in BTC/ETH); il conto e' in USDC e il book esegue sui perpetual lineari USDC. Esiste una famiglia USDC-lineare per BTC ed ETH col lotto minimo 10x piu' piccolo (BTC 0.01 = $773 di nozionale invece di 0.1 = $7.728), quindi il muro «BTC fuori a $3.000» del 30/07 — congelato in tests/test_vrp_profit_take.py — e' misurato sulla famiglia che il conto non margina. E' la 3a occorrenza della stessa forma dopo fee_watch (21/08) e la taratura di venue_watch: un numero puntato su una configurazione diversa da quella che gira. Ma di quella famiglia abbiamo ZERO ore di storia. 4. LO SPREAD E' IL COSTO DOMINANTE, E SULLA FAMIGLIA ESEGUIBILE E' PEGGIO. Attraversare il bid/ask costa ~10% del credito sugli INVERSE; nella regione di strike che serve lo spread relativo mediano e' 24-37% sugli inverse e 29-59% sulle USDC, con open interest ~0. Il f del credito netto si replica indipendentemente a 0.714 (IC95 [0.690, 0.779], 0/19 osservazioni >= 1.0) — conferma dello 0.73 del 30/07 su un campione piu' lungo e con un percorso diverso. CIO' CHE NON SI CONCLUDE: che la struttura non funzioni. Non e' stata osservata nel regime in cui lavora. E un risultato a favore c'e', va detto: su BTC il null «e' solo crypto travestito» e' REFUTATO (beta +0.019, t=0.18) — il PnL non viene dal rialzo. Non basta, perche' il punto 2 toglie il titolo comunque. LA REGOLA NON CAMBIA. «Niente short-vol da MODELLO in deploy» non diventa «short-vol da QUOTE»: le quote adesso ci sono, ma sono quote della famiglia sbagliata, in un regime solo, e con l'ancora che spiega il risultato. La sostituzione della regola resta NON DIMOSTRATA. DUE AZIONI, NESSUNA ESEGUITA (toccano la produzione o il conto: decide l'operatore). (A) DATO — far raccogliere a `collect_chain.py` anche `currency=USDC`. Costo MISURATO col filtro OI>=100 gia' in vigore: ETH_USDC porterebbe 119 strumenti per giro (+21% di chiamate, da ~570 a ~690), BTC_USDC ne porterebbe 5 su 686 (0.7%) = nulla di utilizzabile. Cioe': l'unica struttura USDC-regolata con un libro vero e' su ETH, lotto minimo 0.1 ETH = $242. Senza questa raccolta, fra sei mesi avremo un anno di storia della famiglia che non possiamo marginare e zero di quella che potremmo — che e' esattamente la posizione di oggi, un'ondata dopo l'altra. (B) CONTO — una domanda che nessun dato pubblico chiude e che viene PRIMA di ogni backtest: un saldo in USDC puo' marginare un'opzione INVERSE? Se no, la famiglia con 3 mesi di storia non e' negoziabile da questo conto a nessun capitale, e il punto 3 smette di essere un dettaglio di lotto per diventare un muro. Si risolve come si risolse il PRIIPs il 26/07: guardando il conto, non i dati. SE E QUANDO RIAPRIRLO. Non «quando ci sara' un crash» (criterio senza numero) ma: DVOL BTC > 48.3 o ETH > 62.8 in modo persistente, PIU' ~un anno di settimane raccolte in quel regime sulla famiglia eseguibile. Prima di allora ogni misura su questa struttura descrivera' la sua astensione.""") hr("COSA MI SMENTIREBBE", "-") print(""" Tre osservazioni, tutte ottenibili, che ribalterebbero questo verdetto — e una che NON lo ribalterebbe, messa qui perche' e' quella che arrivera' per prima. 1. IL CONTO. Se un saldo USDC puo' marginare le opzioni INVERSE, il punto 3 si sgonfia da muro a dettaglio: la struttura ETH inverse (1 lotto, rischio ~$88, credito ~$12 la settimana) e' gia' eseguibile a $600 oggi. Resterebbero i punti 1, 2 e 4 — sufficienti a tenere il verdetto, ma il filone tornerebbe misurabile SUBITO invece che fra un anno. Costo della verifica: un ordine di prova, come per il PRIIPs. 2. IL REGIME. Se, raccolte ~un anno di settimane col gate IV-rank APERTO, la media settimanale restasse positiva CON la banda d'ancora che non contiene lo zero, allora «short-vol da QUOTE» sarebbe dimostrato e la regola del 19/06 andrebbe riscritta. E' la sola prova che conta: la banda d'ancora, non la media. 3. IL LIBRO. Se l'open interest di ETH_USDC salisse stabilmente sopra ~100 sugli strike a δ -0.28/-0.10 con spread relativo sotto ~15%, esisterebbe una struttura regolata in USDC ed eseguibile a $242 per lotto: il filone si riaprirebbe sul DATO invece che sulla teoria. Ma senza raccoglierla adesso non lo sapremo mai — vedi azione (A). ⚠️ COSA NON MI SMENTIREBBE, ed e' quello che succedera' per primo: un'altra finestra di 10 settimane di vol bassa con 10/10 vincenti e uno Sharpe a due cifre. Sarebbe lo STESSO campione, piu' lungo — non evidenza nuova. Un campione che non contiene il modo di perdere non diventa informativo aggiungendo settimane in cui non si perde.""") if __name__ == "__main__": main()