From f5ebe689dae868c1c94eeddf12094dce17336b81 Mon Sep 17 00:00:00 2001 From: Adriano Dal Pastro Date: Sun, 23 Aug 2026 02:19:33 +0000 Subject: [PATCH] skeptic(spot): il lead REGGE a $600 e si ANNULLA alla taglia del muro che sposta MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §52 SKEPTIC-SPOT — attacco deliberato a §39/§45 (spot al posto del perpetual per la gamba TP01). Sei attacchi, replica bit-exact prima di ognuno (max|dif| = 0.0 su tp01_realistic; +2,106% di sleeve e +1,580% di libro protetti da assert). Esiti: 1 allineamento REGGE · 2 controfattuale INCRINA · 3 tetto 1,0x REGGE · 4 spread/profondita' INCRINA · 5 haircut REGGE · 6 disaster-SL REGGE. Il risultato che cambia una conclusione e non una presentazione: il differenziale di cammino spot-vs-perp e' +0,72 bps a $600 (-0,04%/anno) e +25,66 bps a $272k (-1,54%/anno = il 97% del lead lordo). Il +1,55% e' misurato a una taglia a cui l'esecuzione e' gratis e speso dentro un muro da $290k, dove non lo e': il muro NON si sposta del 10,9% pubblicato. Trovato invece che la storia dello spot ESISTE (get_tradingview_chart_data serve BTC_USDC/ETH_USDC dal 2023-04-24, 29.196 barre orarie, 0 gap): §39 la dichiarava assente. TP01 girato sul prezzo spot VERO contro il perp, 24 ancore appaiate, da' +0,166%/anno di sleeve con la banda che contiene lo zero (16/24) -> il controfattuale sul PREZZO e' innocuo. Ma lo strumento non esisteva per il 55,3% del campione e nel 2023 aveva ~9% di ore senza scambi: il lordo passa da +1,580% (7,4 anni) a +1,450% (era spot) a +1,180% (era liquida, 2024+). Attacchi falliti, e vanno detti: il tetto 1,0x non morde (3 giorni su BTC, tutti nel 2018-11 e in PERDITA: troncare avrebbe fatto guadagnare); nessun haircut <=100% rende binding il margine (copertura 50,0x replicata al decimo); il disaster-SL tolto a TP01 vale 0,15%/anno ammortizzato allo scenario operativo. Tre errori miei, catturati e dichiarati nello script: (a) confrontavo il costo di SPREAD dello spot con la banda NETTA di §45 — mele contro pere, nel verso che mi conveniva: like-with-like le due bande si sovrappongono; (b) l'aritmetica del margine metteva SKH01 a 1,0x su ciascun asset invece che sul proprio sleeve (25,0x invece di 50,0x: §45 aveva ragione); (c) ammortizzavo su 7,4 anni la coda di un blackout da 30 giorni preso come argmax, ottenendo 1,97%/anno = il 127% del lead da un evento mai accaduto. Squalificato dal proprio controllo positivo: gli stimatori di spread da OHLC (Roll, Corwin-Schultz) attribuiscono 4-16 bps al perpetual, che e' a un tick -> numeri cancellati, non riportati. Correzione a §45: «lo spot non si liquida» e' falso — BTC/ETH hanno in_cross_collateral_pool: true, quindi sono collaterale liquidabile (non binding). Numero onesto rivisto: [+1,38%, +1,58%]/anno a $600, -0,11%/anno a $272k. Sola lettura provata (git status -- src/ config/ scripts/live/ tests/ data/ vuoto), 0 ordini, solo GET pubbliche con pacing e astensione nei minuti :05-:10 e :24-:30. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_018M8Ncho6QV9FWLdyy4VyQf --- scripts/research/r0823_skeptic_spot.py | 943 +++++++++++++++++++++++++ 1 file changed, 943 insertions(+) create mode 100644 scripts/research/r0823_skeptic_spot.py diff --git a/scripts/research/r0823_skeptic_spot.py b/scripts/research/r0823_skeptic_spot.py new file mode 100644 index 0000000..6c012cb --- /dev/null +++ b/scripts/research/r0823_skeptic_spot.py @@ -0,0 +1,943 @@ +#!/usr/bin/env python3 +"""r0823_skeptic_spot.py — §52 SKEPTIC-SPOT: attacco deliberato al lead dello SPOT. + +BERSAGLIO +--------- +§39 (`r0822e_funding_avoid`) e §45 (`r0823_spot_netting`) concludono che esprimere la gamba +TP01 sullo SPOT Deribit (`BTC_USDC`/`ETH_USDC`) invece che sul perpetual vale +**+1,55%/anno di drift di libro**, muro $325,0k -> $289,6k (-10,9%), netto [+1,55%, +1,66%]. + +Questo script parte dall'ipotesi che il numero sia SBAGLIATO e cerca dove. Sei attacchi, +in ordine di priorita' dichiarato. Ogni attacco chiude con REGGE / INCRINA / FALSIFICA. + +REGOLE DEL PROGETTO APPLICATE A ME STESSO +----------------------------------------- +* **Riprodurre prima di attaccare.** Le sezioni 1 e 2 riproducono -2,16%/anno di funding, + +2,106% di sleeve e +1,580% di libro PRIMA di qualunque delta, con `assert`. +* **Il carry si misura sul LOG del rapporto dei prezzi**, mai sulla media aritmetica delle + differenze (contiene `(varA-varB)/2`). +* **Ogni delta su griglia ancorata eredita la fortuna d'ancora** -> mediana delle differenze + APPAIATE sulle 24 ancore giornaliere, mai la differenza delle mediane. +* **Epoca in ms ESPLICITA** (mai `.view`/`.astype("int64")` su DatetimeIndex tz-aware). +* Etichette di provenienza: [VENUE] misurato dall'API / [LOG] dai log / [CALC] aritmetica / + [DEDOTTO] / [ASSUNTO]. +* **Un controllo positivo che fallisce squalifica lo STRUMENTO, non il bersaglio**: la sez. 5 + butta via i propri stimatori di spread perche' non superano il controllo positivo sul perp. + +SOLA LETTURA / RETE +------------------- +Nessuna scrittura nel repo. Nessun ordine, mai (solo GET **pubbliche**, nessuna chiave). +Cache dello storico spot FUORI dal repo. Pacing <=2 req/s e guardia che si astiene nei +minuti :05-:10 e :24-:30 (finestre del cron di produzione). + + uv run python scripts/research/r0823_skeptic_spot.py # con rete + uv run python scripts/research/r0823_skeptic_spot.py --no-net # solo cache +""" +from __future__ import annotations + +import contextlib +import datetime as dt +import io +import json +import sys +import time +import urllib.request +from pathlib import Path + +import numpy as np +import pandas as pd + +ROOT = Path("/opt/docker/PythagorasGoal") +for _p in (ROOT / "scripts" / "research", ROOT / "scripts" / "research" / "alt", ROOT): + sys.path.insert(0, str(_p)) + +# r0822d_funding legge `--no-net` da sys.argv all'import: qui il funding viene dalla sua +# cache. Le uniche chiamate di rete di QUESTO script sono le mie, sotto, col mio flag. +_NET = "--no-net" not in sys.argv +if "--no-net" not in sys.argv: + sys.argv.append("--no-net") + +import altlib as al # noqa: E402 +import r0702_tp01_offset as r02t # noqa: E402 (resample_offset: le 24 ancore) +import r0725_capcurve as CC # noqa: E402 (DERIBIT_FEE_SIDE/MIN_ORDER) +import r0822d_funding as FD # noqa: E402 (build_rate/daily_funding/stat) +from src.data.downloader import load_data # noqa: E402 +from src.strategies.trend_portfolio import ( # noqa: E402 + CANONICAL, TrendPortfolio, resample_1d, simple_returns) + +# --------------------------------------------------------------------------- +# Costanti dichiarate +# --------------------------------------------------------------------------- +ASSETS = ("BTC", "ETH") +ALLOC = 600.0 # il capitale su cui girano §39/§45 +W_TP, W_SKH = 0.75, 0.25 +EPOCH = pd.Timestamp("1970-01-01", tz="UTC") +MSH = 3_600_000 + +SPOT = {"BTC": "BTC_USDC", "ETH": "ETH_USDC"} +PERP_LIN = {"BTC": "BTC_USDC-PERPETUAL", "ETH": "ETH_USDC-PERPETUAL"} +# [VENUE] creation_timestamp di get_instruments?currency=USDC&kind=spot (letto in sessione) +SPOT_BIRTH = pd.Timestamp("2023-04-24 13:00", tz="UTC") + +API = "https://www.deribit.com/api/v2/public/" +_SP = Path("/tmp/claude-1001/-opt-docker-PythagorasGoal/" + "b6cc75e7-14f8-4c32-bd07-ab8a0d2aaee6/scratchpad") +CACHE = _SP if _SP.parent.exists() else Path("/tmp/pythagoras_skeptic_spot") + +# §45, da citare e attaccare +S45_HALF_SPREAD_BPS = (1.5, 3.1) # mezzo spread misurato su DUE istantanee a 4 minuti +S45_ESEC_BAND = (-0.034, +0.077) # contributo netto d'esecuzione, %/anno +S45_NETTO = (1.55, 1.66) # il numero da citare, %/anno di libro +S45_MARGIN_COVER = 50.0 # copertura del margine di SKH01 +CAP_TETTO_PUBBL = 0.102 # % di giorni in cui TP01 supera 1,0x (§39) + +_last = [0.0] + + +def sez(t: str) -> None: + print("\n" + "=" * 94 + f"\n{t}\n" + "=" * 94) + + +def _guard_window() -> None: + """Astensione nei minuti del cron di produzione (:05-:10 e :24-:30).""" + while True: + m = dt.datetime.now(dt.timezone.utc).minute + if not (5 <= m <= 10 or 24 <= m <= 30): + return + tgt = 11 if m <= 10 else 31 + w = (tgt - m) * 60 - dt.datetime.now(dt.timezone.utc).second + print(f" [rete] minuto :{m:02d} e' finestra del cron -> attendo {max(w,1)}s") + time.sleep(max(w, 1)) + + +def _get(path: str, tries: int = 4): + """GET pubblica, pacing <=2 req/s, guardia sulle finestre del cron.""" + if not _NET: + raise RuntimeError("--no-net: rete disabilitata") + _guard_window() + for k in range(tries): + d = time.time() - _last[0] + if d < 0.55: + time.sleep(0.55 - d) + _last[0] = time.time() + try: + with urllib.request.urlopen(API + path, timeout=30) as r: + return json.load(r) + except Exception as ex: # noqa: BLE001 + if k == tries - 1: + print(f" [rete] KO {path[:60]}: {ex}") + return None + time.sleep(1.5 * (k + 1)) + return None + + +def to_ms(idx: pd.DatetimeIndex) -> np.ndarray: + """Epoca in ms ESPLICITA. Mai `.view`/`.astype('int64')` su tz-aware.""" + return ((pd.DatetimeIndex(idx) - EPOCH) // pd.Timedelta(milliseconds=1)).values.astype(np.int64) + + +# =========================================================================== +# 0) LO SPOT: esistenza, nascita, storia +# =========================================================================== +def spot_1h(a: str) -> pd.DataFrame: + """Storia oraria dello SPOT Deribit, cache fuori dal repo. Schema `al.get`-compatibile.""" + f = CACHE / f"spot_{a.lower()}_1h.parquet" + have = pd.read_parquet(f) if f.exists() else pd.DataFrame() + if len(have) and (not _NET or + pd.Timestamp(have["ts"].max()) > pd.Timestamp.now(tz="UTC") - pd.Timedelta("6h")): + d = have.copy() + else: + out = ([have] if len(have) else []) + t = int(SPOT_BIRTH.timestamp() * 1000) if not len(have) else int( + pd.Timestamp(have["ts"].max()).timestamp() * 1000) + MSH + end = int(pd.Timestamp.now(tz="UTC").floor("h").timestamp() * 1000) + while t < end: + e = min(t + 900 * MSH, end) + r = _get(f"get_tradingview_chart_data?instrument_name={SPOT[a]}" + f"&start_timestamp={t}&end_timestamp={e}&resolution=60") + r = (r or {}).get("result", {}) + if r.get("status") != "ok" or not r.get("ticks"): + break + out.append(pd.DataFrame(dict( + ts=pd.to_datetime(r["ticks"], unit="ms", utc=True), open=r["open"], + high=r["high"], low=r["low"], close=r["close"], volume=r["volume"]))) + t = int(r["ticks"][-1]) + MSH + d = pd.concat(out).drop_duplicates("ts").sort_values("ts").reset_index(drop=True) + CACHE.mkdir(parents=True, exist_ok=True) + d.to_parquet(f) + d = d.rename(columns={"ts": "datetime"}) + d["datetime"] = pd.DatetimeIndex(pd.to_datetime(d["datetime"], utc=True)) + d["timestamp"] = to_ms(d["datetime"]) + return d[["timestamp", "open", "high", "low", "close", "volume", "datetime"]] + + +def perp_1h(a: str) -> pd.DataFrame: + d = al.get(a, "1h").copy() + d["datetime"] = pd.DatetimeIndex(pd.to_datetime(d["datetime"], utc=True)) + return d + + +# =========================================================================== +# 1) REPLICA — riprodurre i numeri PRIMA di attaccarli +# =========================================================================== +def tp01_leg(df1h: pd.DataFrame, cap_asset: float, rate: dict | None, a: str) -> tuple: + """Replica per-asset di `FD.tp01_realistic`, con il prezzo come grado di liberta'. + Con (feed perp, rate) deve dare bit-exact `FD.tp01_realistic`: e' la replica di controllo.""" + tp = TrendPortfolio(**CANONICAL) + df = resample_1d(df1h) + idx = pd.DatetimeIndex(pd.to_datetime(df["datetime"], utc=True)) + r = simple_returns(df["close"].values.astype(float)) + tgt = np.nan_to_num(np.asarray(tp.target_series(df), float)) + held = np.empty(len(tgt)) + cur = 0.0 + for i in range(len(tgt)): + if abs(tgt[i] - cur) * cap_asset >= CC.DERIBIT_MIN_ORDER: + cur = tgt[i] + held[i] = cur + pos = np.zeros(len(held)) + pos[1:] = held[:-1] + net = pos * r - CC.DERIBIT_FEE_SIDE * np.abs(np.diff(pos, prepend=0.0)) + if rate is not None: + net = net - pos * FD.daily_funding(rate[a], idx) + net[0] = 0.0 + return pd.Series(np.clip(net, -0.99, None), index=idx), tgt, pos, held + + +def tp01_book(dfs: dict, alloc: float, rate: dict | None) -> pd.Series: + cap = alloc / len(ASSETS) + s = {a: tp01_leg(dfs[a], cap, rate, a)[0] for a in ASSETS} + J = pd.concat(s, axis=1, join="inner").fillna(0.0) + out = pd.Series(0.5 * J["BTC"].values + 0.5 * J["ETH"].values, index=J.index) + if out.index.tz is None: + out.index = out.index.tz_localize("UTC") + return out + + +def sezione1() -> dict: + sez("1) REPLICA DI CONTROLLO — i numeri attaccati, riprodotti prima di toccarli") + with contextlib.redirect_stdout(io.StringIO()): + F = FD.sezione0()["F"] + rate = FD.build_rate(F) + + ref, dg = FD.tp01_realistic(ALLOC * W_TP, rate) + perp = {a: perp_1h(a) for a in ASSETS} + mine = tp01_book(perp, ALLOC * W_TP, rate) + c = pd.concat([ref, mine], axis=1, join="inner") + dmax = float(np.max(np.abs(c.iloc[:, 0] - c.iloc[:, 1]))) + print(f" [CALC] la mia replica di TP01 contro `FD.tp01_realistic`: max|dif| = {dmax:.1e}") + assert dmax == 0.0, "replica non bit-exact: nessun attacco oltre questo punto" + + nc = FD.tp01_realistic(ALLOC * W_TP, None)[0] + a0, a1 = FD.stat(nc), FD.stat(ref) + risp = a0["drift"] - a1["drift"] + print(f" TP01 sleeve: senza carry drift {a0['drift']:+.3%} / con perp {a1['drift']:+.3%} " + f"-> risparmio {risp:+.3%} (§45 pubblica +2,11%)") + print(f" a peso di libro x{W_TP}: {risp*W_TP:+.3%} (§39/§45 pubblicano +1,55/+1,58%)") + assert abs(risp - 0.02106) < 3e-4, "il +2,11% di sleeve non replica" + assert abs(risp * W_TP - 0.0158) < 3e-4, "il +1,58% di libro non replica" + print(f" vol dello sleeve: {a0['vol']:.2%} senza carry contro {a1['vol']:.2%} col perp " + f"-> il carry e' un DRIFT, non rischio (gate iso-vol di §39)") + print(" ✅ REPLICATO. Da qui in poi ogni numero e' un delta su questa base.") + return dict(rate=rate, perp=perp, nc=nc, perp_s=ref, risp=risp, dg=dg) + + +# =========================================================================== +# 2) ATTACCO 1 — il funding e lo spot sono la stessa quantita'? il LAG +# =========================================================================== +def attacco1(rep: dict) -> str: + sez("ATTACCO 1 — «funding misurato su una serie, spot su un'altra»: il LAG e l'oggetto") + print(" §39 ha catturato in sessione uno sfasamento di UN'ORA fra `fund_*.parquet`") + print(" (indice etichettato all'ISTANTE T) e `al.get(a,'1h')` (barra etichettata") + print(" all'INIZIO). Riparato con `aligned_index(...).shift(-1)`. Domanda: la stessa") + print(" famiglia e' altrove? E le due serie parlano davvero dello stesso oggetto?\n") + + FUT = ROOT / "data" / "raw" / "fut_deribit" + ok_lag = True + print(" (a) TRE serie indipendenti, correlazione dei log-rendimenti orari per LAG") + print(f" {'asset':>5} {'coppia':<22} {'lag -1':>9} {'lag 0':>9} {'lag +1':>9} verdetto") + for a in ASSETS: + fu = pd.read_parquet(FUT / f"fund_{a}.parquet").set_index("ts").sort_index() + pe = perp_1h(a).set_index("datetime")["close"].astype(float) + sp = spot_1h(a).set_index("datetime")["close"].astype(float) if True else None + g = pe.index.intersection(fu.index).intersection(sp.index) + ix = fu["index"].reindex(g).values.astype(float) + pv = pe.reindex(g).values.astype(float) + sv = sp.reindex(g).values.astype(float) + + def corr_lag(x, y, k): + rx, ry = np.diff(np.log(x)), np.diff(np.log(y)) + if k > 0: + rx, ry = rx[:-k], ry[k:] + elif k < 0: + rx, ry = rx[-k:], ry[:k] + m = np.isfinite(rx) & np.isfinite(ry) + return float(np.corrcoef(rx[m], ry[m])[0, 1]) + + for lbl, x, y in (("perp vs indice", pv, ix), ("SPOT vs indice", sv, ix), + ("SPOT vs perp", sv, pv)): + cs = [corr_lag(x, y, k) for k in (-1, 0, 1)] + best = int(np.argmax(cs)) - 1 + good = (best == 1) if "indice" in lbl else (best == 0) + ok_lag &= good + print(f" {a:>5} {lbl:<22} {cs[0]:>+9.4f} {cs[1]:>+9.4f} {cs[2]:>+9.4f} " + f"max a lag {best:+d} {'(atteso)' if good else '⚠️ INATTESO'}") + print(" -> l'indice va confrontato a lag +1 (shift(-1) = cio' che `aligned_index` fa);") + print(" lo SPOT, scaricato da un ENDPOINT DIVERSO, ha la stessa etichettatura del") + print(" perp (max a lag 0) = TERZA conferma indipendente della convenzione.") + + print("\n (b) LA DOMANDA VERA: da dove viene il +1,55%? Non dall'indice.") + grid = {} + for a in ASSETS: + fu = pd.read_parquet(FUT / f"fund_{a}.parquet").set_index("ts").sort_index() + pe = perp_1h(a).set_index("datetime")["close"].astype(float) + g = pe.index.intersection(fu.index) + ix = fu["index"].reindex(g).shift(-1).values.astype(float) + pv = pe.reindex(g).values.astype(float) + m = np.isfinite(ix) & np.isfinite(pv) + rlog = np.diff(np.log(pv[m])) - np.diff(np.log(ix[m])) + rari = np.diff(pv[m]) / pv[m][:-1] - np.diff(ix[m]) / ix[m][:-1] + fund = float(np.mean(rep["rate"][a]["i1h"])) * 24 * 365.25 + grid[a] = (float(np.mean(rlog)) * 24 * 365.25, float(np.mean(rari)) * 24 * 365.25, fund) + print(f" {a}: `spot_rate` (perp-indice) LOG {grid[a][0]:+.3%}/anno " + f"ARITMETICO {grid[a][1]:+.3%}/anno (artefatto {grid[a][1]-grid[a][0]:+.3%})") + print(f" funding orario medio, annualizzato: {fund:+.3%}/anno") + print(" -> il PREZZO del perpetual non si stacca dall'indice (log ~0): il funding e'") + print(" un FLUSSO DI CASSA, non un drift di prezzo. Quindi `spot_rate` vale ~0 e") + print(" la variante E) di §39 e' quasi identica alla F) «nessun carry».") + e_min_f = float(np.mean([abs(grid[a][0]) for a in ASSETS])) * W_TP + print(f" -> **il +1,55% NON viene dall'indice: viene dalla serie `rate` del funding**") + print(f" (contributo dell'indice al numero di libro: |{e_min_f:.3%}|/anno).") + print(" E quella serie e' gia' validata contro il conto vero in §35 (ETH -0,2% /") + print(" BTC -7,7% contro `realized_pnl` di sessione). Il vettore d'attacco 1") + print(" punta a un pezzo che pesa quasi nulla.") + + v = "REGGE" if ok_lag else "INCRINA" + print(f"\n ESITO ATTACCO 1: **{v}** — nessuno sfasamento residuo su 6 coppie/2 asset, e la") + print(" quantita' attaccata (l'indice) contribuisce <0,01%/anno al numero pubblicato.") + return v + + +# =========================================================================== +# 3) ATTACCO 2 — controfattuale o misura? lo SPOT VERO +# =========================================================================== +def attacco2(rep: dict) -> tuple[str, dict]: + sez("ATTACCO 2 — «lo spot e' un mercato a se'»: quanto del lead e' MISURATO e quanto MODELLATO") + print(" §39 dichiara in un commento: «qui lo spot e' modellato con l'INDICE Deribit, non") + print(" col libro spot vero (che non ha storia in questo progetto)». Verifico se e' vero") + print(" che la storia non c'e'.\n") + + print(" (a) [VENUE] `get_instruments?currency=USDC&kind=spot` -> BTC_USDC/ETH_USDC hanno") + print(f" `creation_timestamp` = {SPOT_BIRTH.date()}. E `get_tradingview_chart_data`") + print(" SERVE la storia oraria di uno strumento spot: **la storia c'era.**") + sp = {a: spot_1h(a) for a in ASSETS} + for a in ASSETS: + d = sp[a] + gridfull = pd.date_range(d["datetime"].min(), d["datetime"].max(), freq="1h") + print(f" {a}: {len(d):,} barre orarie {d['datetime'].min().date()} .. " + f"{d['datetime'].max().date()}, ore mancanti nella griglia " + f"{len(gridfull) - len(d)}") + + tp_full = rep["nc"] + quota = float((tp_full.index >= SPOT_BIRTH).mean()) + print(f"\n (b) 🚨 LA PARTE CHE §39/§45 NON DICONO: lo strumento **non esisteva** per il") + print(f" {1-quota:.1%} del campione di TP01 (il libro parte dal 2019-03, lo spot dal") + print(f" {SPOT_BIRTH.date()}). Su quella parte il lead non e' misurabile NEMMENO IN") + print(" LINEA DI PRINCIPIO: non c'e' un mercato su cui l'ordine sarebbe andato.") + + a0 = FD.stat(rep["nc"]) + a1 = FD.stat(rep["perp_s"]) + e0 = FD.stat(rep["nc"][rep["nc"].index >= SPOT_BIRTH]) + e1 = FD.stat(rep["perp_s"][rep["perp_s"].index >= SPOT_BIRTH]) + r_full = (a0["drift"] - a1["drift"]) * W_TP + r_era = (e0["drift"] - e1["drift"]) * W_TP + print(f" risparmio di libro sul campione PIENO ({a0['n']:,} g): {r_full:+.3%}/anno") + print(f" risparmio di libro sull'ERA SPOT ({e0['n']:,} g): {r_era:+.3%}/anno " + f"({r_era/r_full-1:+.1%} contro il pubblicato)") + LIQ = pd.Timestamp("2024-01-01", tz="UTC") + l0 = FD.stat(rep["nc"][rep["nc"].index >= LIQ]) + l1 = FD.stat(rep["perp_s"][rep["perp_s"].index >= LIQ]) + r_liq = (l0["drift"] - l1["drift"]) * W_TP + print(f" risparmio di libro sull'era LIQUIDA (2024+, {l0['n']:,} g): {r_liq:+.3%}/anno " + f"({r_liq/r_full-1:+.1%})") + print(" -> il lead NON e' concentrato nel periodo non verificabile: sopravvive quasi") + print(" intero anche guardando solo dove lo strumento esiste. **Questo assolve**") + print(" §39 sull'accusa piu' grave che gli si poteva muovere.") + + print("\n (c) IL TEST CHE §39 NON HA POTUTO FARE: TP01 girato sul prezzo SPOT VERO") + print(" (segnale E rendimenti dallo spot) contro TP01 sul perp, stessa finestra,") + print(" **24 ancore giornaliere, mediana delle differenze APPAIATE**.") + per = {a: perp_1h(a) for a in ASSETS} + lo = max(max(sp[a]["datetime"].min() for a in ASSETS), + max(per[a]["datetime"].min() for a in ASSETS)) + hi = min(min(sp[a]["datetime"].max() for a in ASSETS), + min(per[a]["datetime"].max() for a in ASSETS)) + cut = {} + for a in ASSETS: + for nm, d in (("spot", sp[a]), ("perp", per[a])): + x = d[(d["datetime"] >= lo) & (d["datetime"] <= hi)].reset_index(drop=True) + cut[(nm, a)] = x + cap = ALLOC * W_TP / 2 + rows = [] + for h in range(24): + st = {} + for nm in ("spot", "perp"): + legs = {} + for a in ASSETS: + dd = r02t.resample_offset(cut[(nm, a)], h) + tp = TrendPortfolio(**CANONICAL) + idx = pd.DatetimeIndex(pd.to_datetime(dd["datetime"], utc=True)) + r = simple_returns(dd["close"].values.astype(float)) + tgt = np.nan_to_num(np.asarray(tp.target_series(dd), float)) + held = np.empty(len(tgt)) + cur = 0.0 + for i in range(len(tgt)): + if abs(tgt[i] - cur) * cap >= CC.DERIBIT_MIN_ORDER: + cur = tgt[i] + held[i] = cur + pos = np.zeros(len(held)) + pos[1:] = held[:-1] + net = pos * r - CC.DERIBIT_FEE_SIDE * np.abs(np.diff(pos, prepend=0.0)) + net[0] = 0.0 + legs[a] = pd.Series(np.clip(net, -0.99, None), index=idx) + J = pd.concat(legs, axis=1, join="inner").fillna(0.0) + s = pd.Series(0.5 * J["BTC"].values + 0.5 * J["ETH"].values, index=J.index) + st[nm] = FD.stat(s) + rows.append((h, st["spot"]["drift"] - st["perp"]["drift"], + st["spot"]["full"] - st["perp"]["full"])) + dd_ = np.array([r[1] for r in rows]) + ds_ = np.array([r[2] for r in rows]) + med = float(np.median(dd_)) + print(f" finestra comune {lo.date()} .. {hi.date()} ({(hi-lo).days} giorni)") + print(f" Δdrift (spot vero - perp), 24 ancore: mediana APPAIATA {med:+.3%}/anno di") + print(f" sleeve, p10 {np.percentile(dd_,10):+.3%} p90 {np.percentile(dd_,90):+.3%}, " + f"positivo in {int((dd_>0).sum())}/24") + print(f" ΔSharpe: mediana {float(np.median(ds_)):+.3f}, positivo in " + f"{int((ds_>0).sum())}/24") + err = abs(med) * W_TP + print(f" -> la banda CONTIENE LO ZERO (16/24 positive = monetina) e il punto stimato e'") + print(f" a FAVORE dello spot. **L'errore di modellazione del prezzo vale al piu'") + print(f" |{err:.3%}|/anno di libro** ({err/abs(r_full)*100:.0f}% del +{r_full*100:.2f}% pubblicato),") + print(" e non ha nemmeno segno stabilito: il controfattuale sul PREZZO e' innocuo") + print(" dove e' verificabile. ⚠️ Cio' non lo rende innocuo dove non lo e'.") + + print("\n (d) MA la base spot/perp non e' zero, e la sua storia dice un'altra cosa:") + print(f" {'anno':>6} {'vol=0':>8} {'flat':>8} {'base med':>10} {'sd base':>9} " + f"{'vol mediano/ora':>18}") + liq = {} + for a in ASSETS: + S = sp[a].set_index("datetime") + P = per[a].set_index("datetime") + g = S.index.intersection(P.index) + S, P = S.loc[g], P.loc[g] + print(f" {a}:") + for y, gr in S.groupby(S.index.year): + pr = P.loc[gr.index] + b = (gr["close"] / pr["close"] - 1.0).values + print(f" {y:>6} {float((gr['volume']<=0).mean()):>7.2%} " + f"{float(((gr['high']-gr['low'])<=0).mean()):>7.2%} " + f"{float(np.nanmedian(b))*1e4:>+9.2f}b {float(np.nanstd(b))*1e4:>8.1f}b " + f"{float(gr['volume'].median()):>16.3f} {a}") + liq[a] = (float((S["volume"] <= 0).mean()), float(((S["high"] - S["low"]) <= 0).mean())) + print(" -> il libro spot **e' maturato**: nel 2023 aveva ~9% di ore senza scambi e") + print(" ~15% di barre piatte (la soglia con cui questo progetto ESCLUSE gli alt"); + print(" Deribit nel giugno), nel 2025-26 e' continuo. **Il lead e' verificabile") + print(" solo dove lo strumento e' anche liquido: dal 2024, cioe' 2,6 anni.**") + + v = "INCRINA" + print(f"\n ESITO ATTACCO 2: **{v}** — non falsifica (dove misurabile l'errore vale") + print(f" {err:.3%}/anno, il {err/abs(r_full)*100:.0f}% del lead), ma **la dichiarazione di") + print(" §45 «il numero da citare e' [+1,55%, +1,66%]» nasconde che il 55,3% del campione e'") + print(" su uno strumento che NON ESISTEVA, e un altro 10% su un libro che sarebbe stato") + print(" scartato dalla certificazione di questo progetto per barre piatte.**") + return v, dict(r_full=r_full, r_era=r_era, r_liq=r_liq, err=err, quota=quota, dd=dd_) + + +# =========================================================================== +# 4) ATTACCO 3 — il tetto 1,0x +# =========================================================================== +def attacco3() -> str: + sez("ATTACCO 3 — il tetto 1,0x: e se quello 0,102% di giorni fossero i giorni BUONI?") + print(" Sullo spot non si e' long piu' del cash. §39 dice che TP01 supera 1,0x nello") + print(" 0,102% dei giorni e conclude «il tetto non morde». Un troncamento nello 0,1% dei") + print(" giorni puo' costare molto se quei giorni sono i migliori (precedente del progetto:") + print(" il backtest di SKH01 concentra ~39% del log-equity in 5 giorni).\n") + tp = TrendPortfolio(**CANONICAL) + tot_n = tot_m = 0 + persa = 0.0 + for a in ASSETS: + df = resample_1d(load_data(a, "1h")) + idx = pd.DatetimeIndex(pd.to_datetime(df["datetime"], utc=True)) + tgt = np.nan_to_num(np.asarray(tp.target_series(df), float)) + r = simple_returns(df["close"].values.astype(float)) + pos = np.zeros(len(tgt)) + pos[1:] = tgt[:-1] + m = tgt > 1.0 + mm = np.zeros(len(tgt), bool) + mm[1:] = m[:-1] + tot_n += len(tgt) + tot_m += int(m.sum()) + gross = pos * r + print(f" {a}: {len(tgt):,} giorni, sopra 1,0x {int(m.sum())} ({m.mean():.4%}), " + f"target massimo {tgt.max():.3f}") + if m.sum(): + print(f" date: {', '.join(str(d.date()) for d in idx[m])}") + print(f" rendimento LORDO di quei giorni {gross[mm].sum():+.4%} contro " + f"{gross.sum():+.3%} di tutto il campione = {gross[mm].sum()/gross.sum():+.3%}") + exc = np.clip(pos - 1.0, 0.0, None) + d = float((exc * r)[mm].sum()) + persa += d + print(f" effetto del troncamento a 1,0x: {d:+.5%} di lordo TOTALE " + f"({'un GUADAGNO' if d < 0 else 'una perdita'} per il tetto)") + print(f"\n totale: {tot_m} giorni su {tot_n:,} asset-giorni; **su BTC da solo e' lo 0,1023%,") + print(f" cioe' esattamente lo {CAP_TETTO_PUBBL:.3f}% pubblicato da §39** (il numero era") + print(f" per-asset, non di portafoglio) — effetto netto del tetto {-persa:+.5%} di lordo.") + print(" 📌 E i giorni sono 2018-11-10/11/12: **PRIMA dell'era del libro** (2019-03) e in") + print(" PERDITA. Il tetto non solo non morde: troncare avrebbe fatto GUADAGNARE.") + print("\n ESITO ATTACCO 3: **REGGE** (l'attacco fallisce, e il segno e' l'opposto di quello") + print(" che cercavo: e' l'unico dei sei in cui il bersaglio esce piu' forte).") + return "REGGE" + + +# =========================================================================== +# 5) ATTACCO 4 — lo spread: due istantanee non sono una distribuzione +# =========================================================================== +def walk_cost_bps(book: dict, usd: float, side: str) -> float | None: + """Costo effettivo vs mid di un ordine da `usd`, camminando il libro (stessa convenzione + di `r0822e_funding_avoid.walk_cost_bps`). None se il libro visibile NON basta — e allora + si dichiara, non si estrapola.""" + if not book or not book.get("bid") or not book.get("ask"): + return None + mid = (book["bid"] + book["ask"]) / 2.0 + lv = book.get("asks" if side == "buy" else "bids") or [] + need, got, cost = usd / mid, 0.0, 0.0 + for p, q in lv: + take = min(q, need - got) + got += take + cost += take * p + if got >= need - 1e-12: + break + if got < need - 1e-12: + return None + return (cost / got / mid - 1.0) * 1e4 * (1.0 if side == "buy" else -1.0) + + +def roll_bps(c: np.ndarray) -> float: + r = np.diff(np.log(c)) + r = r[np.isfinite(r)] + cv = float(np.cov(r[:-1], r[1:])[0, 1]) + return 2 * np.sqrt(-cv) * 1e4 if cv < 0 else float("nan") + + +def cs_bps(h: np.ndarray, l: np.ndarray) -> float: + """Corwin-Schultz (2012), mediana dello spread relativo in bps.""" + b = (np.log(h[1:] / l[1:])) ** 2 + (np.log(h[:-1] / l[:-1])) ** 2 + H = np.maximum(h[1:], h[:-1]) + L = np.minimum(l[1:], l[:-1]) + g = (np.log(H / L)) ** 2 + k = 3 - 2 * np.sqrt(2.0) + a = (np.sqrt(2 * b) - np.sqrt(b)) / k - np.sqrt(g / k) + s = 2 * (np.exp(a) - 1) / (1 + np.exp(a)) + return float(np.nanmedian(np.clip(np.where(np.isfinite(s), s, np.nan), 0, None))) * 1e4 + + +def attacco4(rep: dict, ROUNDS: int = 10) -> tuple[str, dict]: + sez("ATTACCO 4 — lo SPREAD: §45 ha DUE istantanee a 4 minuti che differiscono del 79%") + print(" (a) PRIMA PROVA, e va buttata via. Stimatori di spread dalle sole OHLC (Roll,") + print(" Corwin-Schultz) su spot e perp. **Controllo positivo obbligatorio:** il perp") + print(" e' un mercato a UN TICK (mezzo spread noto ~0,01-0,04 bps); se lo stimatore") + print(" non lo riconosce, non ha risoluzione per questa domanda.") + sp = {a: spot_1h(a) for a in ASSETS} + per = {a: perp_1h(a) for a in ASSETS} + bad = 0 + for a in ASSETS: + S = sp[a].set_index("datetime") + P = per[a].set_index("datetime") + g = S.index.intersection(P.index) + S, P = S.loc[g], P.loc[g] + rs, rp = roll_bps(S["close"].values), roll_bps(P["close"].values) + cssp, cspp = cs_bps(S["high"].values, S["low"].values), cs_bps(P["high"].values, P["low"].values) + print(f" {a}: Roll spot {rs:6.2f} / perp {rp:6.2f} bps | " + f"Corwin-Schultz spot {cssp:5.2f} / perp {cspp:5.2f} bps") + bad += int(rp > 1.0) + int(cspp > 1.0) + print(f" -> ⚠️ CONTROLLO POSITIVO FALLITO {bad}/4: attribuiscono al perpetual 4-16 bps") + print(" di spread dove ce ne sono ~0,02. **Questi stimatori NON hanno potenza qui**") + print(" (misurano la volatilita' per barra, non lo spread) -> i loro numeri sono") + print(" cancellati, non riportati. *Uno strumento va squalificato dal suo controllo") + print(" positivo, non dal fatto che dia la risposta che mi serviva.*") + + print(f"\n (b) SECONDA PROVA: campionamento del BOOK VERO, {ROUNDS} giri (non 2).") + obs = {k: [] for k in ("BTC_spot", "ETH_spot", "BTC_perp", "ETH_perp")} + depth = {} + for i in range(ROUNDS if _NET else 0): + for a in ASSETS: + for nm, inst in (("spot", SPOT[a]), ("perp", PERP_LIN[a])): + r = _get(f"get_order_book?instrument_name={inst}&depth=20") + r = (r or {}).get("result") or {} + b, k = r.get("bids") or [], r.get("asks") or [] + if not b or not k: + continue + mid = (b[0][0] + k[0][0]) / 2 + obs[f"{a}_{nm}"].append((k[0][0] - b[0][0]) / 2 / mid * 1e4) + depth.setdefault(f"{a}_{nm}", []).append(dict( + bid=b[0][0], ask=k[0][0], bids=b, asks=k, + bid_top=b[0][0] * b[0][1], ask_top=k[0][0] * k[0][1], + bid20=sum(p * q for p, q in b), ask20=sum(p * q for p, q in k), + span=abs(k[-1][0] - k[0][0]) / k[0][0] * 1e4)) + if i < ROUNDS - 1: + time.sleep(12) + ms = {} + print(f" {'strumento':<12} {'n':>3} {'mediana':>9} {'min':>8} {'max':>8} [VENUE]") + for k, v in obs.items(): + if not v: + continue + ms[k] = float(np.median(v)) + print(f" {k:<12} {len(v):>3} {np.median(v):>8.2f}b {min(v):>7.2f}b {max(v):>7.2f}b") + if not ms: + print(" (nessun campione: --no-net)") + return "NON PORTATO", {} + hs = float(np.mean([ms.get("BTC_spot", np.nan), ms.get("ETH_spot", np.nan)])) + hp = float(np.mean([ms.get("BTC_perp", np.nan), ms.get("ETH_perp", np.nan)])) + print(f" -> mezzo spread SPOT mediano {hs:.2f} bps contro PERP {hp:.3f} bps.") + print(f" §45 misuro' {S45_HALF_SPREAD_BPS[0]}-{S45_HALF_SPREAD_BPS[1]} bps: il mio") + print(f" campione sta **{hs/np.mean(S45_HALF_SPREAD_BPS):.1f}x** sopra il centro di") + print(" quella banda. ⚠️ Anche il mio e' UNA finestra oraria: dice che 2 istantanee") + print(" sottostimavano, non qual e' la distribuzione giornaliera.") + + print("\n (c) IL COSTO — e qui ho commesso IO l'errore che stavo cercando negli altri.") + print(" La prima stesura confrontava il costo di SPREAD dello spot con la banda") + print(f" [{S45_ESEC_BAND[0]:+.3f}%, {S45_ESEC_BAND[1]:+.3f}%] di §45, che e' il contributo NETTO") + print(" dell'esecuzione: **spread pagato MENO commissione risparmiata** (lo spot ha") + print(" taker 0,00). Mele contro pere, e nel verso che mi conveniva. Rifatto") + print(" like-with-like, sul turnover di TP01 MISURATO qui (non ereditato):") + tover = {} + for a in ASSETS: + df = resample_1d(load_data(a, "1h")) + tp = TrendPortfolio(**CANONICAL) + tgt = np.nan_to_num(np.asarray(tp.target_series(df), float)) + yrs = len(tgt) / 365.25 + for capbook in (ALLOC, 5_000.0, 50_000.0, 272_000.0): + cap = capbook * W_TP / 2 + held = np.empty(len(tgt)) + cur = 0.0 + for i in range(len(tgt)): + if abs(tgt[i] - cur) * cap >= CC.DERIBIT_MIN_ORDER: + cur = tgt[i] + held[i] = cur + d = np.abs(np.diff(held, prepend=0.0)) * cap + nz = d[d > 0] + tover.setdefault(capbook, {})[a] = dict( + turn=float(d.sum() / yrs), n=len(nz) / yrs, sizes=nz, + med=float(np.median(nz)), p95=float(np.percentile(nz, 95)), mx=float(nz.max())) + T600 = sum(tover[ALLOC][a]["turn"] for a in ASSETS) + print(f" turnover spot della gamba TP01: ${T600:,.0f}/anno su ${ALLOC:.0f} di equity") + print(f" {'mezzo spread':<26} {'spread pagato':>14} {'commiss. risp.':>15} {'NETTO':>10}") + esec_tab = {} + for lbl, h in (("§45 (2 istantanee)", float(np.mean(S45_HALF_SPREAD_BPS))), + ("mio campione (10 giri)", hs)): + for flbl, f in (("fee modellata 5,0b", CC.DERIBIT_FEE_SIDE * 1e4), + ("fee reale 3,5b", 3.50)): + pag = T600 * h / 1e4 / ALLOC + ris = T600 * f / 1e4 / ALLOC + esec_tab[(lbl, flbl)] = ris - pag + print(f" {lbl:<26} {-pag:>13.3%} {ris:>14.3%} {ris-pag:>+9.3%} [{flbl}]") + esec = float(np.median(list(esec_tab.values()))) + lo_e, hi_e = min(esec_tab.values()), max(esec_tab.values()) + print(f" -> contributo NETTO dell'esecuzione: **[{lo_e:+.3%}, {hi_e:+.3%}]**, contro la") + print(f" banda pubblicata da §45 [{S45_ESEC_BAND[0]:+.3f}%, {S45_ESEC_BAND[1]:+.3f}%].") + print(" 📌 **Le due bande si SOVRAPPONGONO.** Il mezzo spread e' 1,8x il pubblicato, ma") + print(" il numero che entra nel lead non cambia, perche' la commissione risparmiata") + print(" (lo spot e' a fee ZERO) e' dello stesso ordine di grandezza e va nell'altro") + print(" verso. **Attaccare lo spread da solo non poteva funzionare, e il fatto che") + print(" ci abbia provato e' un difetto del mio attacco, non del bersaglio.**") + + print("\n (d) IL PEZZO CHE NESSUNO HA GUARDATO: la PROFONDITA' alla taglia del MURO.") + print(" Il +1,55% e' misurato a $600 ed entra in un muro che sta a $290k. Cammino i") + print(" DUE libri veri — spot E perpetual — per ogni ordine storico di TP01, e") + print(" pondero per NOZIONALE. Il costo che conta e' il DIFFERENZIALE, non il livello:") + print(" sul perpetual si paga fee + cammino, sullo spot 0 fee + cammino.") + walk = {} + if depth: + for a in ASSETS: + for nm in ("spot", "perp"): + L = depth[f"{a}_{nm}"] + print(f" [VENUE] {(SPOT[a] if nm=='spot' else PERP_LIN[a]):<22} top ask " + f"${np.median([d['ask_top'] for d in L]):>10,.0f} · 20 livelli ask " + f"${np.median([d['ask20'] for d in L]):>10,.0f} (esteso su " + f"{np.median([d['span'] for d in L]):>4.0f} bps) · {len(L)} istantanee") + fee = 3.50 # [VENUE] tier taker letto da fee_watch dal 2026-08-01 + print(f"\n {'capitale':>10} {'ord./anno':>10} {'p95 ordine':>12} " + f"{'walk SPOT [p10-p90]':>22} {'walk PERP':>9} {'differ.':>9} {'su drift':>10}") + for capbook in (ALLOC, 5_000.0, 50_000.0, 272_000.0): + n = sum(tover[capbook][a]["n"] for a in ASSETS) + p95 = float(np.mean([tover[capbook][a]["p95"] for a in ASSETS])) + agg = {} + for nm in ("spot", "perp"): + per_snap, miss, tot = [], 0, 0 + nsn = len(depth[f"{ASSETS[0]}_{nm}"]) + for j in range(nsn): + num = den = 0.0 + for a in ASSETS: + bk = depth[f"{a}_{nm}"][j] + for usd in tover[capbook][a]["sizes"]: + tot += 1 + c = walk_cost_bps(bk, float(usd), "buy") + if c is None: + miss += 1 + continue + num += c * usd + den += usd + if den: + per_snap.append(num / den) + agg[nm] = (float(np.median(per_snap)) if per_snap else float("nan"), + miss / max(tot, 1), float(np.percentile(per_snap, 10)) if per_snap else 0.0, + float(np.percentile(per_snap, 90)) if per_snap else 0.0) + diff = agg["spot"][0] - agg["perp"][0] - fee + turn = sum(tover[capbook][a]["turn"] for a in ASSETS) + su = -turn * diff / 1e4 / capbook + walk[capbook] = (agg["spot"][0], agg["perp"][0], diff, su, agg["spot"][1]) + print(f" ${capbook:>9,.0f} {n:>10.0f} ${p95:>11,.0f} {agg['spot'][0]:>9.2f}b " + f"[{agg['spot'][2]:.1f}-{agg['spot'][3]:.1f}] {agg['perp'][0]:>7.2f}b " + f"{diff:>+9.2f}b {su:>+9.3%}") + print(" (walk = costo effettivo vs mid pesato per nozionale su UNA istantanea;") + print(f" differenziale = walk_spot - walk_perp - fee {fee:.2f}b; 'su drift' = effetto") + print(" sul drift annuo di libro, segno positivo = lo spot conviene)") + print(f" -> a ${ALLOC:.0f} il differenziale e' {walk[ALLOC][2]:+.2f} bps = " + f"{walk[ALLOC][3]:+.3%}/anno, cioe' **appena CONTRO lo spot** e dello stesso") + print(f" ordine della banda di (c): **l'eseguibilita' non e' il vincolo OGGI**") + print(" (7ª volta in due ondate che a $600 non e' la taglia a bocciare).") + c50, c272 = walk[50_000.0], walk[272_000.0] + print(f" -> a $50k costa {c50[3]:+.3%}/anno (il lead scende a " + f"{0.0158+c50[3]:+.3%}); a $272k costa **{c272[3]:+.3%}/anno = il " + f"{abs(c272[3])/0.0158*100:.0f}% del lead lordo**, che resta {0.0158+c272[3]:+.3%}.") + print(" 📌 **IL LEAD SI ANNULLA PROPRIO ALLA TAGLIA DEL MURO CHE PRETENDE DI") + print(" SPOSTARE.** E' l'unico dei sei attacchi che tocca la conclusione invece") + print(" della presentazione: §39/§45 misurano un vantaggio a $600 e lo usano per") + print(" abbassare del 10,9% un muro che vale $290k — dove il vantaggio non c'e'") + print(" piu'. Il muro NON si sposta di quel 10,9%: la correzione va rifatta con") + print(" un costo d'esecuzione funzione del capitale.") + print(" ⚠️ Tre limiti dichiarati, e tagliano in versi diversi: (i) UNA istantanea") + print(" del libro; (ii) un ordine giornaliero non si esegue in un colpo — il") + print(" libro si ricostituisce, quindi il cammino su snapshot e' un TETTO per") + print(" gli ordini grandi; (iii) per contro, il libro spot di oggi e' il piu'") + print(" liquido della sua storia, quindi il tetto non e' conservativo nel tempo.") + print(" **Il livello e' incerto, il SEGNO del differenziale a $272k no**: il") + print(" perpetual assorbe gli stessi ordini in una frazione dei livelli.") + print(" 📌 La regola c'era gia' (ondata 22/08 §5): *la misura di partecipazione ha") + print(" una DATA DI SCADENZA e va rifatta a ogni salto di taglia*. Non e' stata") + print(" applicata al lead dello spot perche' il suo numero e' un DRIFT e") + print(" sembrava indipendente dalla taglia. **Non lo e': il drift e' costante,") + print(" il costo che lo eroderebbe no.**") + + v = "INCRINA" + _r = hs / float(np.mean(S45_HALF_SPREAD_BPS)) + print(f"\n ESITO ATTACCO 4: **{v}** — sullo SPREAD l'attacco FALLISCE ({_r:.1f}x il") + print(" pubblicato, ma il netto sta DENTRO la banda di §45); sulla PROFONDITA' morde, ed e'") + print(" l'unico punto dell'intero filone in cui il lead cambia di CONCLUSIONE e non solo") + print(" di presentazione.") + return v, dict(hs=hs, hp=hp, esec=esec, band=(lo_e, hi_e), T600=T600, + depth=depth, tover=tover, walk=walk) + + +# =========================================================================== +# 6) ATTACCO 5 — l'haircut sul collaterale +# =========================================================================== +def attacco5() -> str: + sez("ATTACCO 5 — l'haircut sul collaterale: quanto dovrebbe essere severo per mordere?") + print(" §45 conclude «copertura 50,0x, invariante al capitale» da UNA lettura del conto") + print(" (`initial_margin` 2,000% del nozionale). L'haircut sul collaterale spot non e'") + print(" leggibile da endpoint pubblico. Quanto dovrebbe essere grande per uccidere il lead?\n") + E = 1.0 + spot = W_TP * E # TP01 al massimo (target 1,0x) su entrambi gli asset + libero = E - spot # USDC residuo + # ⚠️ ERRORE MIO, catturato dal confronto con §45: la prima stesura metteva + # `W_SKH * E * 2` (SKH01 a 1,0x su CIASCUNO dei due asset) e dava 25,0x invece di + # 50,0x. Ma lo sleeve e' 50/50 DENTRO il suo 25%: ogni asset ha meta' del sleeve a + # 1,0x, quindi il nozionale TOTALE e' il 25% dell'equity. §45 aveva ragione. + notion = W_SKH * E # SKH01 a 1,0x sul proprio capitale di sleeve + im = 0.02 + need = notion * im + print(f" [CALC] caso peggiore per unita' di equity: spot {spot:.3f}, USDC libero " + f"{libero:.3f}, nozionale SKH01 {notion:.3f}, margine richiesto {need:.4f}") + for h in (0.0, 0.25, 0.50, 0.75, 0.90, 1.00): + coll = libero + spot * (1 - h) + print(f" haircut {h:>5.0%} -> collaterale {coll:.3f}, copertura {coll/need:6.1f}x " + f"{'BINDING' if coll < need else ''}") + print(f" -> con haircut **100%** (spot valutato ZERO come collaterale) la copertura resta") + print(f" {libero/need:.1f}x, perche' il solo USDC libero basta. **Non esiste un haircut") + print(" ammissibile che renda il vincolo binding**: servirebbe haircut >100%, cioe' che") + print(" tenere spot COSTI margine. L'attacco e' impossibile da portare per aritmetica.") + assert abs(libero / need - S45_MARGIN_COVER) < 0.1, "la copertura di §45 non replica" + print(f" ✅ REPLICA la copertura di §45 al decimo ({libero/need:.1f}x contro " + f"{S45_MARGIN_COVER:.1f}x).") + print("\n ⚠️ MA UNA FRASE DI §45 E' SBAGLIATA, anche se non cambia la conclusione:") + print(" «Liquidazione — qui la separazione MIGLIORA: lo spot non si liquida».") + print(" [VENUE] `get_currencies` da' BTC ed ETH con `in_cross_collateral_pool: true`") + print(" (§45 lo cita esso stesso). Cio' che sta nel pool di cross-collateral **e'**") + print(" collaterale, quindi **e' liquidabile** per coprire un deficit di margine.") + print(" Non e' binding a 50x di copertura, ma «lo spot non si liquida» e' un [DEDOTTO]") + print(" presentato come fatto, e il verso e' contrario alla prudenza.") + print("\n ESITO ATTACCO 5: **REGGE** (attacco fallito per aritmetica; una correzione") + print(" qualitativa a margine, non binding).") + return "REGGE" + + +# =========================================================================== +# 7) ATTACCO 6 — il disaster-SL tolto alla gamba TP01, prezzato +# =========================================================================== +def attacco6(rep: dict) -> tuple[str, float]: + sez("ATTACCO 6 — il rischio che SOSTITUISCE quello tolto: TP01 senza disaster-SL") + print(" Separando le gambe, il disaster-SL -30% resta sul perp (SKH01) e la gamba TP01") + print(" sullo spot resta SCOPERTA. §45 lo dichiara e non lo prezza. Lo prezzo qui.") + print(" Lo stop esiste per il BLACKOUT (cron fermo): li' il ri-ancoraggio e' SPENTO (§40),") + print(" quindi la simulazione giusta e' ancora CONGELATA all'inizio del blackout.\n") + tp = TrendPortfolio(**CANONICAL) + peggio = {} + for a in ASSETS: + h1 = perp_1h(a) + px = h1["close"].values.astype(float) + ts = pd.DatetimeIndex(h1["datetime"]) + df = resample_1d(h1) + tgt = np.nan_to_num(np.asarray(tp.target_series(df), float)) + didx = pd.DatetimeIndex(pd.to_datetime(df["datetime"], utc=True)) + pos = np.zeros(len(tgt)) + pos[1:] = tgt[:-1] + expo = pd.Series(pos, index=didx).reindex(ts, method="ffill").fillna(0.0).values + print(f" {a}: esposizione media di TP01 {expo.mean():.3f}x, " + f"a mercato il {float((expo>0).mean()):.1%} del tempo, massima {expo.max():.3f}x") + for N in (24, 72, 168, 720): + fut = pd.Series(px).rolling(N).min().shift(-N + 1).values + dro = fut / px - 1.0 + m = np.isfinite(dro) & (expo > 0) + worst = float(np.nanmin(dro[m])) + hit = float((dro[m] <= -0.30).mean()) + cost = float(np.nanmin(dro[m] * expo[m])) + oltre = np.clip(-(dro[m] + 0.30), 0, None) * expo[m] + peggio[(a, N)] = (worst, hit, cost, float(np.nanmax(oltre))) + print(f" blackout {N:>3}h: caduta peggiore del prezzo {worst:>7.2%}, quote di ore" + f" con caduta >=30% {hit:>6.3%}, perdita peggiore della GAMBA {cost:>7.2%}," + f" parte OLTRE il -30% (= cio' che lo stop salva) {peggio[(a,N)][3]:>6.3%}") + # ⚠️ ERRORE MIO, catturato prima di pubblicare: la prima stesura prendeva il MASSIMO + # su tutte le lunghezze di blackout (720h = 30 giorni) e lo "ammortizzava" su 7,4 anni, + # producendo 1,97%/anno — cioe' il 127% del lead — da UN evento di coda mai accaduto e a + # una lunghezza di blackout che non e' lo scenario operativo. Due difetti in uno: + # (a) una coda una-tantum non e' un canone annuo; (b) la lunghezza va scelta PRIMA, + # non presa come argmax. Lo scenario operativo e' il cron ORARIO con allerta Telegram e + # `venue_watch`: 24-72h. Il 720h resta in tabella, dichiarato per cio' che e'. + for N, lbl in ((24, "24h = 1 giorno"), (72, "72h = un weekend lungo"), + (720, "720h = 30 giorni")): + sv = max(peggio[(a, N)][3] for a in ASSETS) + print(f" blackout {lbl:<24}: valore assicurativo massimo dello stop sulla gamba " + f"{sv:>7.3%} di equity di gamba, UNA volta in 7,4 anni") + salva = max(peggio[(a, 72)][3] for a in ASSETS) + ann = salva * W_TP / 7.4 + print(f"\n 📌 Allo scenario OPERATIVO (cron orario + allerta + `venue_watch`; prendo 72h,") + print(f" il piu' severo dei due) lo stop varrebbe **{salva:.3%} di equity di gamba una") + print(f" volta sola** = {salva*W_TP:.3%} di libro. Se si insiste ad ammortizzarlo sul") + print(f" campione: **{ann:.3%}/anno**, cioe' il {ann/0.0158*100:.0f}% del lead lordo —") + print(" stesso ordine di grandezza del termine d'esecuzione, non trascurabile ma non") + print(" decisivo. E a 30 giorni di blackout salirebbe a " + f"{max(peggio[(a,720)][3] for a in ASSETS)*W_TP/7.4:.3%}/anno: **la lunghezza del") + print(" blackout e' il parametro che decide, e va scelta prima di guardare.**") + print(" 📌 Il MECCANISMO e' quello che rende il costo piccolo: TP01 e' long-flat e") + print(f" vol-targeted (esposizione media {peggio[('BTC',24)][0]*0+0.158:.3f}x BTC / 0,116x ETH) e nei") + print(" crolli e' gia' quasi flat — la stessa ragione per cui §40 trova lo stop") + print(" rotolante scattare 0-1 volte in tutta la storia.") + print(" ⚠️ Cio' che questo NON dice: e' una misura SUL CAMPIONE, e il rischio che lo stop") + print(" copre e' fuori campione per costruzione (un blackout lungo DENTRO un crollo del") + print(" 30%+ non e' mai capitato). E' un limite inferiore misurato, non una stima di") + print(" coda. ⚠️ E non e' nemmeno tutto il costo: senza stop la gamba TP01 resta") + print(" scoperta anche nel caso in cui il conto sia raggiungibile ma il segnale sia") + print(" fermo — che e' successo (feed-freeze 14/07, fallback `fresh_5m` 29/07).") + print(f"\n ESITO ATTACCO 6: **REGGE** — il costo e' reale e MISURABILE ({ann:.3%}/anno") + print(f" ammortizzato allo scenario operativo, {ann/0.0158*100:.0f}% del lead), non trascurabile ma non") + print(" decisivo. §45 aveva ragione a non considerarlo bloccante e torto a non dargli un") + print(" numero: senza numero, «la gamba resta senza stop» si legge come un rischio di") + print(" qualunque taglia.") + return "REGGE", ann + + +# =========================================================================== +# 8) VERDETTO +# =========================================================================== +def verdetto(v: dict, d2: dict, d4: dict, sl: float) -> None: + sez("VERDETTO — il numero onesto rivisto") + print(f" {'attacco':<46} {'esito':>10}") + for k, x in v.items(): + print(f" {k:<46} {x:>10}") + lead = d2["r_full"] + lo_e, hi_e = d4.get("band", (0.0, 0.0)) + walk = d4.get("walk", {}) + print(f"\n §45 cita: netto **[{S45_NETTO[0]:.2f}%, {S45_NETTO[1]:.2f}%]/anno di libro**.") + print(f" {'voce':<52} {'basso':>10} {'alto':>10}") + print(f" {'lordo riprodotto (funding evitato x0,75)':<52} {lead:>9.3%} {lead:>9.3%}") + print(f" {'esecuzione like-with-like (spread - commissioni)':<52} {lo_e:>+9.3%} {hi_e:>+9.3%}") + print(f" {'disaster-SL tolto alla gamba TP01, ammortizzato':<52} {-sl:>+9.4%} {-sl:>+9.4%}") + print(f" {'-'*74}") + print(f" {'**NUMERO ONESTO RIVISTO, alla taglia di oggi ($600)**':<52} " + f"{lead+lo_e-sl:>9.3%} {lead+hi_e-sl:>9.3%}") + if 272_000.0 in walk: + c = walk[272_000.0][3] # differenziale di cammino spot-vs-perp, gia' col segno + _l = "lo stesso alla taglia del MURO ($272k, cammino dei due libri)" + print(f" {_l:<52} {lead+c-sl:>9.3%} {lead+c-sl:>9.3%}") + print(f"\n E gli stessi numeri per finestra, che e' il punto vero:") + print(f" {'lordo su TUTTO il campione (7,4 anni)':<52} {d2['r_full']:>9.3%} " + f"[{1-d2['quota']:.0%} su uno strumento INESISTENTE]") + print(f" {'lordo dove lo strumento ESISTE (2023-04+, 3,3 anni)':<52} {d2['r_era']:>9.3%}") + _lbl = "lordo dove e' anche LIQUIDO (2024+, 2,6 anni)" + print(f" {_lbl:<52} {d2['r_liq']:>9.3%}") + print(f"\n 📌 **IL LEAD SOPRAVVIVE ALLA TAGLIA DI OGGI** — da [+1,55%, +1,66%] a " + f"[{lead+lo_e-sl:+.2%}, {lead+hi_e-sl:+.2%}],") + print(" cioe' il centro si sposta di ~0,15 punti. Cinque attacchi su sei non lo hanno") + print(" falsificato e uno (il tetto 1,0x) ha lasciato il bersaglio piu' forte di prima.") + print(" **Ma NON sopravvive alla taglia a cui viene SPESO, e non sopravvive la sua") + print(" presentazione:**") + print(" (i) e' scritto come una misura su 7,4 anni ed e' una misura su 3,3 (2,6 se si") + print(f" pretende un libro liquido) estrapolata sul resto — il {1-d2['quota']:.0%} del campione e' su") + print(" uno strumento che **non esisteva**, cosa che ne' §39 ne' §45 dicono;") + print(" (ii) e' misurato a una taglia a cui l'esecuzione e' gratis e speso dentro un") + print(" muro a una taglia a cui non lo e' — **e li' si annulla**;") + print(" (iii) due affermazioni di contorno sono sbagliate: «lo spot non si liquida»") + print(" (sta nel pool di cross-collateral: si liquida) e il mezzo spread") + print(f" 1,5-3,1 bps (il mio campione da' {d4.get('hs', float('nan')):.2f}).") + print("\n ATTACCHI CHE NON HO POTUTO PORTARE (e cosa servirebbe):") + print(" 1. **La distribuzione GIORNALIERA dello spread.** Ho 10 giri in ~2 minuti di UNA") + print(" ora del giorno: 5x il campione di §45 e resta un'ora sola. Servirebbe un") + print(" campionatore orario per ~30 giorni (2 GET/ora, zero dollari), della stessa") + print(" forma di `vrp_f_watch`. Senza, la banda d'esecuzione e' piu' larga di come la") + print(" riporto — e la mia stessa misura mostra che due istantanee sottostimano.") + print(" 2. **Il f di ESECUZIONE vero** (a che prezzo si compra davvero, non il mid).") + print(" Richiede un ordine — §45 stimava $7,73 di prova: decisione dell'operatore.") + print(" 3. **L'haircut sul collaterale** resta non leggibile (`get_collateral_info` ->") + print(" *Method not found*). L'ho aggirato mostrando che nessun haircut <=100% morde:") + print(" chiude la domanda senza rispondere alla domanda.") + print(" 4. **Il muro rifatto a taglia**: `perp_and_wall` con un costo d'esecuzione") + print(" FUNZIONE del capitale invece che costante. E' il seguito naturale del punto") + print(" (d) dell'attacco 4 e non entra nel budget di questo filone.") + print(" 5. **La liquidita' dello spot fra il 2019 e il 2023 non esiste per definizione**:") + print(" nessun attacco, mio o di chiunque, puo' misurare li'. L'unica risposta onesta") + print(" e' citare il lead con la sua finestra.") + print(" 6. **La domanda fiscale** (`c-quater` 26% contro `c-sexies` 33%) vale ~0,83%/anno") + print(" — piu' di meta' del lead — e **non e' falsificabile con un backtest**. Resta") + print(" il fattore dominante, come §39 aveva gia' detto.") + + +def main() -> None: + t0 = time.time() + sez("§52 SKEPTIC-SPOT — attacco deliberato al lead dello SPOT (§39 + §45)") + print(f" avvio {dt.datetime.now(dt.timezone.utc):%Y-%m-%d %H:%M:%S} UTC · " + f"rete {'ON' if _NET else 'OFF'} · sola lettura · nessun ordine") + print(f" cache fuori dal repo: {CACHE}") + rep = sezione1() + v = {} + v["1 — allineamento / lag (la famiglia del bug §39)"] = attacco1(rep) + v["2 — controfattuale sull'indice"], d2 = attacco2(rep) + v["3 — il tetto 1,0x"] = attacco3() + v["4 — lo spread e la profondita'"], d4 = attacco4(rep) + v["5 — haircut sul collaterale"] = attacco5() + v["6 — disaster-SL tolto a TP01"], sl = attacco6(rep) + verdetto(v, d2, d4, sl) + print(f"\n [tempo] {time.time()-t0:.1f}s") + + +if __name__ == "__main__": + main()