"""r0702_alb_claims — Audit STATISTICO delle claim "strategia Albimarini" (video didattici). CLAIM DICHIARATE (input, dal video): - Struttura: double diagonal DEEP OTM su SPX/SPY (short strangle ~6DTE + long strangle a scadenza successiva, strike piu' lontani), durata trade 3-6 giorni di calendario. - Track record: n=28 trade, win-rate 82% (23W/5L), P&L medio +$113/trade, perdite totali $765 (loss medio $153), profit factor 5.16, capitale ~$10k, estrapolazione "420% annuo", sizing a compounding 1->2->3->4 contratti. Derivati: totale +$3.164; gross win $3.929 (avg win $171); PF = 3929/765 = 5.14 ok. OBIETTIVO: QUANTIFICARE la critica (win-rate strutturale, coda non campionata, PF non significativo su n=28), non riformularla. 6 test, ognuno con numeri. DATI: SOLO locali gia' certificati/presenti nel progetto: - data/raw/eq_spy_1d.parquet (SPY daily 1996-2026, fetch IB del filone GTAA) - BTC/ETH 1d via altlib (Deribit mainnet certificato) Nessuna rete. Nessun file scritto fuori dallo scratchpad. Nessun DatetimeIndex.view. Convenzioni oneste: - 6 giorni di calendario ~= 4 trading day (h=4 primario; h=2..5 come robustezza). - Le probabilita' empiriche usano finestre rolling OVERLAPPING (stima della prob. per-finestra); i test di significativita' usano le 28 finestre NON sovrapposte del track record e cicli non sovrapposti nel replay. - Il replay del Test 3 e' CLAIM-ANCHORED: usa l'economia per-trade dichiarata dal video (massimo beneficio al claim); l'unico parametro nostro e' la severita' della coda, calibrata in due modi (cap BS della diagonale / EV=0). - BS senza skew nei Test 4-5: sottostima il premio degli OTM (a favore del claim). Uso: uv run python scripts/research/r0702_alb_claims.py """ from __future__ import annotations import json import sys from pathlib import Path import numpy as np import pandas as pd from scipy.stats import norm, t as student_t sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "research" / "alt")) import altlib as al # noqa: E402 RNG = np.random.default_rng(20260702) ROOT = Path(__file__).resolve().parents[2] # ---- claim dichiarate ------------------------------------------------------ N_TRADES = 28 WINRATE_CLAIM = 23 / 28 # 0.821 AVG_PNL = 113.0 # $/trade TOT_LOSS = 765.0 # $ totali (5 loss -> avg $153) AVG_WIN = (N_TRADES * AVG_PNL + TOT_LOSS) / 23 # $171 AVG_LOSS = TOT_LOSS / 5 # $153 PF_CLAIM = (23 * AVG_WIN) / TOT_LOSS # 5.14 CAPITAL = 10_000.0 H_TD = 4 # 6 giorni calendario ~ 4 trading day OUT: dict = {} def p(msg=""): print(msg, flush=True) # =========================================================================== # BS helpers (no dividendi/tassi — orizzonte 6-13 giorni) # =========================================================================== def bs_put(S, K, T, sigma): T = max(T, 1e-9) d1 = (np.log(S / K) + 0.5 * sigma**2 * T) / (sigma * np.sqrt(T)) d2 = d1 - sigma * np.sqrt(T) return K * norm.cdf(-d2) - S * norm.cdf(-d1) def bs_call(S, K, T, sigma): T = max(T, 1e-9) d1 = (np.log(S / K) + 0.5 * sigma**2 * T) / (sigma * np.sqrt(T)) d2 = d1 - sigma * np.sqrt(T) return S * norm.cdf(d1) - K * norm.cdf(d2) # =========================================================================== # DATA # =========================================================================== def load_spy() -> pd.DataFrame: df = pd.read_parquet(ROOT / "data" / "raw" / "eq_spy_1d.parquet") df = df.sort_values("timestamp").reset_index(drop=True) df["datetime"] = pd.to_datetime(df["timestamp"].astype("int64"), unit="ms", utc=True) return df def hmoves(close: np.ndarray, h: int) -> np.ndarray: return close[h:] / close[:-h] - 1.0 def cal_moves(df: pd.DataFrame, days: int = 6) -> np.ndarray: """Move su ESATTAMENTE `days` giorni di calendario (ultimo close <= t+days). Epoca ms esplicita (mai .view su DatetimeIndex).""" ts = df["timestamp"].to_numpy(dtype="int64") # ms c = df["close"].to_numpy(dtype=float) tgt = ts + days * 86_400_000 j = np.searchsorted(ts, tgt, side="right") - 1 # ultimo close <= t+days ok = j > np.arange(len(ts)) return c[j[ok]] / c[ok] - 1.0 # =========================================================================== # TEST 1 — WIN-RATE STRUTTURALE # =========================================================================== def test1(): p("=" * 88) p("TEST 1 — WIN-RATE STRUTTURALE: P(move >9% in ~6 giorni) e strike impliciti dall'82%") p("=" * 88) spy = load_spy() c, lo, hi = spy["close"].values, spy["low"].values, spy["high"].values r1 = np.diff(np.log(c)) vol_ann = r1.std() * np.sqrt(252) res = {} for h in (2, 3, 4, 5): m = hmoves(c, h) res[h] = dict(p_abs9=float(np.mean(np.abs(m) > 0.09)), p_dn9=float(np.mean(m < -0.09)), sigma=float(m.std()), p_2sig=float(np.mean(np.abs(m) > 2 * m.std()))) m6c = cal_moves(spy, 6) m4 = hmoves(c, H_TD) # touch (management intra-finestra): min low / max high nei prossimi h giorni n = len(c) - H_TD lo_w = np.array([lo[i + 1:i + 1 + H_TD].min() for i in range(n)]) hi_w = np.array([hi[i + 1:i + 1 + H_TD].max() for i in range(n)]) p_touch9 = float(np.mean((lo_w < c[:n] * 0.91) | (hi_w > c[:n] * 1.09))) # analitico: normale a vol 16% e alla vol campione; t-Student fittata sui move 4td sig6_16 = 0.16 * np.sqrt(6 / 365) p_norm16 = 2 * (1 - norm.cdf(0.09 / sig6_16)) sig4 = res[4]["sigma"] p_norm_emp = 2 * (1 - norm.cdf(0.09 / sig4)) tdf, tloc, tscale = student_t.fit(m4) p_t = float(2 * student_t.sf((0.09 - tloc) / tscale, tdf)) # strike implicito dall'82% di win (quantile 82% di |move|) e win-rate a 9% OTM q82_4 = float(np.quantile(np.abs(m4), WINRATE_CLAIM)) q82_6c = float(np.quantile(np.abs(m6c), WINRATE_CLAIM)) win_at_9 = 1 - res[4]["p_abs9"] p(f"SPY 1996-2026 ({len(spy)} barre, vol ann {vol_ann:.1%})") for h in (2, 3, 4, 5): r = res[h] p(f" h={h}td: P(|m|>9%)={r['p_abs9']:.4%} P(m<-9%)={r['p_dn9']:.4%} " f"sigma={r['sigma']:.2%} P(|m|>2sig)={r['p_2sig']:.2%}") p(f" 6 giorni CALENDARIO esatti: P(|m|>9%)={np.mean(np.abs(m6c) > 0.09):.4%} " f"sigma={m6c.std():.2%}") p(f" TOUCH intra-finestra (h=4td, strike a +-9%): P={p_touch9:.4%}") p(f" Analitico: normale vol16% -> {p_norm16:.2e} | normale vol camp. -> {p_norm_emp:.2e} | " f"t-Student fit (df={tdf:.2f}) -> {p_t:.4%} | empirico {res[4]['p_abs9']:.4%}") p(f" -> fat-tail factor empirico/normale = {res[4]['p_abs9'] / p_norm_emp:,.0f}x") p() p(f" INCONSISTENZA QUANTIFICATA delle claim:") p(f" (a) se gli strike sono DAVVERO ~9% OTM: win-rate strutturale a scadenza = " f"{win_at_9:.2%} (>=99%), non 82% -> le 5 perdite non sono breach, sono gestione;") p(f" (b) se il win-rate 82% e' letterale (breach a scadenza): gli strike distano " f"q82(|m4|) = {q82_4:.2%} (6gg cal: {q82_6c:.2%}) ~= 1.2 sigma, NON 'deep OTM'.") p(f" In entrambi i casi il win-rate e' il quantile della distribuzione (il delta " f"venduto), non skill: qualunque venditore agli stessi strike ottiene lo stesso numero.") # BTC/ETH dai NOSTRI dati certificati crypto = {} for a in ("BTC", "ETH"): d = al.get(a, "1d") cc = d["close"].values m6 = hmoves(cc, 6) crypto[a] = dict(p_abs9=float(np.mean(np.abs(m6) > 0.09)), sigma=float(m6.std()), p_2sig=float(np.mean(np.abs(m6) > 2 * m6.std())), q82=float(np.quantile(np.abs(m6), WINRATE_CLAIM)), span=f"{d['datetime'].iloc[0].date()}->{d['datetime'].iloc[-1].date()}", last=float(cc[-1])) p(f" {a} (certificato, {crypto[a]['span']}): P(|m6d|>9%)={crypto[a]['p_abs9']:.1%} " f"sigma6d={crypto[a]['sigma']:.1%} P(>2sig)={crypto[a]['p_2sig']:.1%} " f"strike per win82% = {crypto[a]['q82']:.1%} OTM") p(f" -> la stessa struttura trasposta su crypto: strike a 9% OTM = win-rate " f"{1 - crypto['BTC']['p_abs9']:.0%} (BTC) / {1 - crypto['ETH']['p_abs9']:.0%} (ETH), " f"cioe' il 9% che su SPY e' 'deep' su crypto e' ~1 sigma.") OUT["test1"] = dict(spy=res, spy_6cal_p9=float(np.mean(np.abs(m6c) > 0.09)), p_touch9=p_touch9, p_norm16=float(p_norm16), p_norm_emp=float(p_norm_emp), p_t=p_t, t_df=float(tdf), q82_4td=q82_4, q82_6cal=q82_6c, win_at_9otm=float(win_at_9), crypto=crypto) return spy, m4, q82_4 # =========================================================================== # TEST 2 — SIGNIFICATIVITA' DEL PF 5.16 SU n=28 (null EV=0) # =========================================================================== def test2(spy: pd.DataFrame, q82: float): p("\n" + "=" * 88) p("TEST 2 — PF 5.16 su n=28: distribuzione sotto il null 'vendita premio a EV=0 netto'") p("=" * 88) c = spy["close"].values m4 = hmoves(c, H_TD) NSIM = 200_000 W = 126 # 6 mesi di trading day def branch(name: str, thr: float) -> dict: """Null EV=0: win +$171 (p=0.821), small loss -$153, tail loss -L con p_tail = P(|m4|>thr) empirica; L calibrata perche' EV=0 netto.""" p_loss = 1 - WINRATE_CLAIM p_tail = float(np.mean(np.abs(m4) > thr)) p_small = max(p_loss - p_tail, 0.0) L_tail = (WINRATE_CLAIM * AVG_WIN - p_small * AVG_LOSS) / p_tail wins = RNG.normal(AVG_WIN, 60, (NSIM, N_TRADES)).clip(20, None) smalls = RNG.normal(AVG_LOSS, 60, (NSIM, N_TRADES)).clip(30, None) u = RNG.random((NSIM, N_TRADES)) is_win = u < min(WINRATE_CLAIM, 1 - p_tail) is_tail = u > 1 - p_tail pnl = np.where(is_win, wins, np.where(is_tail, -L_tail, -smalls)) gp = np.where(pnl > 0, pnl, 0).sum(1) gl = -np.where(pnl < 0, pnl, 0).sum(1) pf = gp / np.maximum(gl, 1e-9) wr = is_win.mean(1) p_joint = float(np.mean((pf >= PF_CLAIM) & (wr >= WINRATE_CLAIM - 1e-9))) p_notail = float(np.mean(~is_tail.any(1))) tail_day = np.abs(m4) > thr frac_clean = float(np.mean([not tail_day[i:i + W].any() for i in range(0, len(tail_day) - W)])) r = dict(name=name, thr=thr, p_tail=p_tail, p_small=p_small, L_tail=float(L_tail), ev_check=float(pnl.mean()), pf_median=float(np.median(pf)), p_pf_ge=float(np.mean(pf >= PF_CLAIM)), p_joint=p_joint, p_no_tail_28=p_notail, p_no_tail_analytic=float((1 - p_tail) ** N_TRADES), frac_6m_clean=frac_clean) p(f" Branch {name}:") p(f" tail = |m4| > {thr:.2%}: P(tail/trade)={p_tail:.2%} -> L_tail per EV=0 = " f"${L_tail:,.0f} ({L_tail / CAPITAL:.0%} del capitale per unita' di size); " f"EV MC {pnl.mean():+.1f}$/trade") p(f" PF su 28 trade: mediana {r['pf_median']:.2f} | P(PF>=5.16)={r['p_pf_ge']:.1%} | " f"P(PF>=5.16 E win>=82%)={p_joint:.1%}") p(f" P(zero tail in 28 trade)={p_notail:.1%} (analitico {r['p_no_tail_analytic']:.1%}); " f"quota storica finestre 6 mesi SENZA tail = {frac_clean:.1%}") return r p(f"Null 'venditore di premio a EV=0 netto' (tanti +$171, -$153 gestite, code rare):") b_obs = branch(f"OBS (strike {q82:.2%} OTM, breach oltre il long +2%)", q82 + 0.02) b_deep = branch("DEEP (strike 9% OTM come dichiarato)", 0.09) p(f" -> Il track record dichiarato (PF 5.16, win 82%, zero code) e' un esito da " f"P={b_obs['p_joint']:.0%} (lettura OBS) a P={b_deep['p_joint']:.0%} (lettura DEEP) " f"sotto ZERO skill: tra 'comune' e 'esito mediano'.") p(f" Il PF senza coda campionata misura solo 23/5 * avg_win/avg_smallloss = " f"{(23 * AVG_WIN) / (5 * AVG_LOSS):.2f}: e' un rapporto STRUTTURALE, non una statistica " f"di edge (n=28 non tocca mai la coda che paga tutto).") OUT["test2"] = dict(obs=b_obs, deep=b_deep) return b_obs["L_tail"] # =========================================================================== # TEST 3 — IL "420% ANNUO" ATTRAVERSO UNA CODA REALE (replay claim-anchored) # =========================================================================== def _replay(dates, moves, q82: float, width: float, L_tail: float, win_amt: float, small_amt: float, sizing: str = "video", start: float = CAPITAL, label: str = ""): """Replay CLAIM-ANCHORED sulla sequenza REALE di move (cicli non sovrapposti). PER CONTRATTO: |m| <= q82 -> +win_amt (win, calibrato sui numeri del video) q82 < |m| <= q82+w -> -small_amt (perdita gestita, come le 5 del video) |m| > q82+w -> -L_tail (breach oltre il long: coda) sizing: 'video' = +1 contratto ogni +$1000 di profitto (1->2->3->4 come dichiarato), cap margine equity/$2500; 'prop' = leva costante iniziale (n = equity//10k); 'fixed' = 1 contratto. Ruin se equity <= 0.""" eq = start path, pnl_hist, npos = [], [], [] ruined = None for i, m in enumerate(moves): if abs(m) <= q82: out = win_amt elif abs(m) <= q82 + width: out = -small_amt else: out = -L_tail if sizing == "video": n = int(np.clip(1 + max(eq - start, 0) // 1000, 1, max(1, eq // 2500))) elif sizing == "prop": n = int(max(1, eq // 10_000)) else: n = 1 pnl = n * out eq += pnl pnl_hist.append(pnl) npos.append(n) path.append(eq) if eq <= 0: ruined = dates[i] break path = np.asarray(path, dtype=float) peak = np.maximum.accumulate(np.maximum(path, 1e-9)) maxdd = float((path / peak - 1.0).min()) if len(path) else 0.0 last_i = len(path) - 1 yrs = max(1e-9, (pd.Timestamp(dates[last_i]) - pd.Timestamp(dates[0])).days / 365.25) cagr = float((max(path[-1], 1e-9) / start) ** (1 / yrs) - 1) if len(path) else 0.0 wi = int(np.argmin(pnl_hist)) if pnl_hist else 0 return dict(label=label, final=float(path[-1]) if len(path) else start, cagr=cagr, maxdd=maxdd, ruined=str(pd.Timestamp(ruined).date()) if ruined is not None else None, worst_cycle=float(min(pnl_hist)) if pnl_hist else 0.0, worst_date=str(pd.Timestamp(dates[wi]).date()) if pnl_hist else None, n_at_worst=int(npos[wi]) if npos else 0, n_cycles=len(pnl_hist), n_tails=int(np.sum(np.abs(np.asarray(moves[:len(pnl_hist)])) > q82 + width)), avg_pnl_cycle=float(np.mean(pnl_hist)) if pnl_hist else 0.0, winrate=float(np.mean(np.asarray(pnl_hist) > 0)) if pnl_hist else 0.0, years=float(yrs)) def _cycles(df: pd.DataFrame, h: int): c = df["close"].values idx = np.arange(0, len(c) - h, h) moves = c[idx + h] / c[idx] - 1.0 dates = df["datetime"].values[idx + h] return dates, moves def _calib_win_per_contract() -> float: """Calibra il win PER CONTRATTO cosi' che 28 cicli TUTTI win con il sizing video (+1 contratto/$1000, 1->2->3->4) riproducano il totale dichiarato (+$3.164): i +$171/+$113 del video sono medie a livello CONTO su 1-4 contratti, non per contratto.""" lo, hi = 10.0, 171.0 for _ in range(60): w = 0.5 * (lo + hi) eq, tot = CAPITAL, 0.0 for _k in range(N_TRADES): n = int(np.clip(1 + max(eq - CAPITAL, 0) // 1000, 1, max(1, eq // 2500))) eq += n * w tot += n * w if tot > N_TRADES * AVG_PNL: hi = w else: lo = w return round(0.5 * (lo + hi), 1) def test3(spy: pd.DataFrame, q82: float): p("\n" + "=" * 88) p("TEST 3 — '420% ANNUO': la stessa strategia attraverso le code reali, con compounding") p("=" * 88) # Replay CLAIM-ANCHORED: economia PER CONTRATTO calibrata sui numeri del video, # guidata dalla sequenza REALE dei move ~6gg. Unico parametro nostro: la # severita' della coda per contratto, boxata da due letture convergenti: # cap fisico = width 2% x notional $60k = $1.200 (cap della diagonale, Test 4) # EV=0 = la severita' che rende il gioco fair alle p empiriche w_c = _calib_win_per_contract() s_c = round(w_c * AVG_LOSS / AVG_WIN, 1) m4 = hmoves(spy["close"].values, H_TD) p_tail = float(np.mean(np.abs(m4) > q82 + 0.02)) p_small = max(1 - WINRATE_CLAIM - p_tail, 0.0) L_ev0 = round((WINRATE_CLAIM * w_c - p_small * s_c) / p_tail, 0) L_bs = 1200.0 p(f"Replay claim-anchored (cicli 4td non sovrapposti, strike q82={q82:.2%}, long +2%):") p(f"PER CONTRATTO: win +${w_c} (|m|<=q82), gestita -${s_c} (<=q82+2%), coda -$L") p(f"(|m|>q82+2%). Calibrazione: 28 cicli senza coda col sizing video 1->4 = +$3.164") p(f"(riproduce il video PER COSTRUZIONE: win 82%, PF 5.1). Severita' coda boxata:") p(f"cap fisico width2%x$60k = ${L_bs:,.0f} | EV=0 = ${L_ev0:,.0f} (convergenti).") p(f"Sizing 'video' = +1 contratto/$1000 profitto (cap eq/$2500); 'prop' = eq//10k.") windows = [("FULL 1996-2026", None, None), ("2019-2026 (era book)", "2019-01-01", None), ("H2-2023 (finestra tipo video)", "2023-07-01", "2023-12-31"), ("2020 (COVID)", "2020-01-01", "2020-12-31"), ("2022 (bear)", "2022-01-01", "2022-12-31"), ("2008 (GFC)", "2008-01-01", "2008-12-31")] rows = [] for sizing, L, tag in (("video", L_bs, "video, cap fisico $1.200"), ("video", L_ev0, f"video, EV=0 ${L_ev0:,.0f}"), ("prop", L_bs, "leva costante, cap fisico")): p(f"\n -- sizing {tag} --") for label, a, b in windows: d = spy if a: d = d[d["datetime"] >= pd.Timestamp(a, tz="UTC")] if b: d = d[d["datetime"] <= pd.Timestamp(b, tz="UTC")] dates, moves = _cycles(d.reset_index(drop=True), H_TD) out = _replay(dates, moves, q82, 0.02, L, w_c, s_c, sizing=sizing, label=f"{label} [{tag}]") rows.append(out) ann = f"{out['cagr']:+.0%}" if not out["ruined"] else "RUIN" p(f" {label:30s} finale ${out['final']:>8,.0f} CAGR {ann:>7s} " f"maxDD {out['maxdd']:>5.0%} win {out['winrate']:.0%} " f"code {out['n_tails']:>2d} worst ${out['worst_cycle']:>7,.0f} " f"({out['worst_date']}, {out['n_at_worst']}c)" + (f" ** ROVINA {out['ruined']} **" if out["ruined"] else "")) # controllo: size FISSA 1 contratto, e scenario 'premio generoso' (+20% sui win) dates, moves = _cycles(spy, H_TD) fx = _replay(dates, moves, q82, 0.02, L_ev0, w_c, s_c, sizing="fixed", label="FULL fixed-1c EV=0") gen_fx = _replay(dates, moves, q82, 0.02, L_ev0, w_c * 1.2, s_c, sizing="fixed", label="FULL fixed-1c premio+20%") gen = _replay(dates, moves, q82, 0.02, L_ev0, w_c * 1.2, s_c, sizing="video", label="FULL video premio+20%") p(f"\n Controlli (FULL 1996-2026, coda EV=0):") p(f" - size FISSA 1 contratto: finale ${fx['final']:>8,.0f} CAGR {fx['cagr']:+.1%} " f"maxDD {fx['maxdd']:.0%}" + (f" ROVINA {fx['ruined']}" if fx['ruined'] else "")) p(f" - fixed-1c, premio +20%: finale ${gen_fx['final']:>8,.0f} CAGR " f"{gen_fx['cagr']:+.1%} maxDD {gen_fx['maxdd']:.0%}" + (f" ROVINA {gen_fx['ruined']}" if gen_fx['ruined'] else "")) p(f" - sizing video, premio +20%: finale ${gen['final']:>8,.0f} CAGR " f"{gen['cagr']:+.1%} maxDD {gen['maxdd']:.0%}" + (f" ROVINA {gen['ruined']}" if gen['ruined'] else "")) p(f" -> anche regalando un VRP persistente del +20%, il CAGR onesto multi-anno e'") p(f" a una cifra, NON 420%; e il sizing del video lo converte comunque in rovina") p(f" (scala dopo i win -> la coda colpisce sempre vicino alla size massima).") rows += [fx, gen_fx, gen] # BTC/ETH dai NOSTRI dati: stesso 82% strutturale (q82 proprio), width vol-scalata, # severita' EV=0 ricalibrata sulla p_tail dell'asset. p() for aname in ("BTC", "ETH"): d = al.get(aname, "1d") m6 = hmoves(d["close"].values, 6) q82a = float(np.quantile(np.abs(m6), WINRATE_CLAIM)) wa = float(0.9 * m6.std()) p_tail_a = float(np.mean(np.abs(m6) > q82a + wa)) La = round((WINRATE_CLAIM * w_c - max(1 - WINRATE_CLAIM - p_tail_a, 0) * s_c) / p_tail_a, 0) dates_a, moves_a = _cycles(d, 6) out = _replay(dates_a, moves_a, q82a, wa, La, w_c, s_c, sizing="video", label=f"{aname} strike {q82a:.0%} width {wa:.0%} EV=0") rows.append(out) ann = f"{out['cagr']:+.0%}" if not out["ruined"] else "RUIN" p(f" {out['label']:38s} P(coda)={p_tail_a:.1%} L=${La:,.0f} -> finale " f"${out['final']:>8,.0f} CAGR {ann:>6s} maxDD {out['maxdd']:>5.0%} " f"code {out['n_tails']}" + (f" ** ROVINA {out['ruined']} **" if out["ruined"] else "")) p() p(" -> Il '420%' e' l'annualizzazione della finestra senza coda con il sizing gia'") p(" scalato. Sulla storia intera, con la STESSA economia dichiarata dal video e") p(" una coda fair, il compounding del video incontra la coda alla size massima.") OUT["test3"] = dict(win_per_contract=w_c, small_per_contract=s_c, L_bs=L_bs, L_ev0=L_ev0, p_tail=p_tail, rows=rows) # =========================================================================== # TEST 4 — QUANTO COPRE LA DIAGONALE IL GIORNO DEL CRASH # =========================================================================== def test4(q82: float): p("\n" + "=" * 88) p("TEST 4 — DIAGONALE COME PROTEZIONE: loss_con_diagonale / loss_naked (BS, gap+vol spike)") p("=" * 88) S0 = 100.0 iv0 = 0.16 def scenario(d_short, width, gap, iv1): """Ritorna (loss_naked, loss_diag, ratio) in frazione di S0 (positivi=perdita).""" Kp_s = S0 * (1 - d_short) Kp_l = S0 * (1 - d_short - width) v_s0 = bs_put(S0, Kp_s, 6 / 365, iv0) v_l0 = bs_put(S0, Kp_l, 13 / 365, iv0) S1 = S0 * (1 + gap) v_s1 = bs_put(S1, Kp_s, 5 / 365, iv1) v_l1 = bs_put(S1, Kp_l, 12 / 365, iv1) loss_naked = float(v_s1 - v_s0) # mark contro lo short loss_diag = float((v_s1 - v_s0) - (v_l1 - v_l0)) # long compensa ratio = loss_diag / loss_naked if loss_naked > 1e-9 else np.nan return loss_naked, loss_diag, ratio rows = [] p(f"Struttura put-side branch OBS: short K={100 * (1 - q82):.1f} T=6g, long T=13g piu' OTM.") p(f"Scenario: gap overnight (resta 1g di vita in meno), IV 16% -> IV_crash. Perdite % notional.") p(f" {'gap':>5s} {'IV->':>5s} {'width':>6s} | {'naked':>7s} {'diag':>7s} {'ratio':>6s} " f"{'equity hit @6x':>14s}") for width in (0.02, 0.03): for gap in (-0.05, -0.10, -0.15): for iv1 in (0.16, 0.40, 0.50, 0.70): ln, ld, ratio = scenario(q82, width, gap, iv1) rows.append(dict(d=q82, width=width, gap=gap, iv1=iv1, naked=ln, diag=ld, ratio=ratio)) p(f" {gap:>5.0%} {iv1:>5.0%} {width:>6.0%} | {ln / S0:>7.2%} " f"{ld / S0:>7.2%} {ratio:>6.0%} {6 * ld / S0:>13.1%}") # branch DEEP (9% OTM come dichiarato), width 3% deep = [] for gap in (-0.10, -0.15): for iv1 in (0.40, 0.50, 0.70): ln, ld, ratio = scenario(0.09, 0.03, gap, iv1) deep.append(dict(d=0.09, width=0.03, gap=gap, iv1=iv1, naked=ln, diag=ld, ratio=ratio)) p() p(" Branch DEEP (short 9% OTM, long 12% OTM, width 3%):") for r in deep: p(f" {r['gap']:>5.0%} IV->{r['iv1']:.0%} | naked {r['naked'] / S0:>6.2%} " f"diag {r['diag'] / S0:>6.2%} ratio {r['ratio']:>4.0%} " f"equity hit @6x {6 * r['diag'] / S0:>6.1%}") r10 = [r for r in rows if r["gap"] == -0.10 and r["iv1"] == 0.50] r10_novol = [r for r in rows if r["gap"] == -0.10 and r["iv1"] == 0.16 and r["width"] == 0.02][0] p() p(f" -> La diagonale NON e' un hedge pieno e NON e' gratis: a gap -10%/IV 50 lascia") p(f" passare il {r10[0]['ratio']:.0%} (width 2%) / {r10[1]['ratio']:.0%} (width 3%) " f"della perdita naked; SENZA vol spike (IV ferma) il {r10_novol['ratio']:.0%}.") p(f" A 6x notional un -10% costa {6 * r10[0]['diag'] / S0:.0%}-" f"{6 * r10_novol['diag'] / S0:.0%} di equity PER UNITA' (x4 unita' post-scaling = " f"{24 * r10[0]['diag'] / S0:.0%}-{24 * r10_novol['diag'] / S0:.0%}).") p(f" La protezione dipende dal vol-spike che gonfia il long (vega del T+7g): e'") p(f" un cap alla ROVINA, non alla perdita — e il costo del cap (long ricomprato") p(f" ogni ciclo) e' il motivo per cui l'EV netto resta ~0 (Test 2/3).") OUT["test4"] = dict(obs=rows, deep=deep) # =========================================================================== # TEST 5 — ESEGUIBILITA' PER NOI (Deribit BTC/ETH, $600) # =========================================================================== def test5(): p("\n" + "=" * 88) p("TEST 5 — ESEGUIBILITA' su Deribit BTC/ETH al NOSTRO capitale ($600)") p("=" * 88) res = {} for a, minsz in (("BTC", 0.1), ("ETH", 0.1)): d = al.get(a, "1d") S = float(d["close"].values[-1]) m6 = hmoves(d["close"].values, 6) q82a = float(np.quantile(np.abs(m6), WINRATE_CLAIM)) leg_notional = minsz * S sig = 0.55 if a == "BTC" else 0.70 w = float(0.9 * m6.std()) # width vol-scalata (come il 2% su SPY) short0 = bs_put(S, S * (1 - q82a), 6 / 365, sig) + bs_call(S, S * (1 + q82a), 6 / 365, sig) long0 = (bs_put(S, S * (1 - q82a - w), 13 / 365, sig) + bs_call(S, S * (1 + q82a + w), 13 / 365, sig)) long7 = (bs_put(S, S * (1 - q82a - w), 7 / 365, sig) + bs_call(S, S * (1 + q82a + w), 7 / 365, sig)) # raccolto theta atteso per ciclo a mercato FERMO (spread 5%/gamba) harvest = (0.95 * short0 - 1.05 * long0 + 0.95 * long7) / S * leg_notional # fee Deribit opzioni: min(0.0003*S, 12.5% premio) per gamba; 8 esecuzioni/ciclo avg_leg = (short0 / 2 + long0 / 2) / 2 / S * leg_notional fee = 8 * min(0.0003 * S * minsz, 0.125 * max(avg_leg, 1e-9)) # margine short (standard margin, senza netting sulle diagonali): # ~max(0.15-OTM, 0.10)*S*size + mark, x2 gambe short margin = 2 * (max(0.15 - q82a, 0.10) * S * minsz + short0 / 2 * minsz) # tail netta: full-breach del cap (gap = -(q82+w), IV x1.5), lato put gap_full = -(q82a + w) Sc = S * (1 + gap_full) vs = bs_put(Sc, S * (1 - q82a), 1 / 365, 1.5 * sig) vl = bs_put(Sc, S * (1 - q82a - w), 8 / 365, 1.5 * sig) vs0 = bs_put(S, S * (1 - q82a), 6 / 365, sig) vl0 = bs_put(S, S * (1 - q82a - w), 13 / 365, sig) tail = float(((vs - vs0) - (vl - vl0)) / S * leg_notional) net_cycle = harvest - fee fee_pct = f"{fee / harvest:.0%} del raccolto" if harvest > 0 else "raccolto <=0!" res[a] = dict(S=S, q82=q82a, width=w, leg_notional=leg_notional, harvest_cycle=float(harvest), fee_rt=float(fee), net_cycle=float(net_cycle), margin=float(margin), tail_net=tail, gap_full=float(gap_full)) p(f" {a} (close {S:,.0f}, strike {q82a:.0%} OTM per win 82%, width {w:.0%}): " f"min {minsz}/gamba -> {leg_notional:,.0f}$/gamba, 4 gambe = " f"{4 * leg_notional:,.0f}$ notional") p(f" raccolto theta/ciclo (mercato fermo, netto spread 5%) ~${harvest:.2f}, " f"fee Deribit 8 gambe ~${fee:.2f} ({fee_pct}) -> NETTO ${net_cycle:+.2f}/ciclo;") p(f" margine short ~${margin:,.0f}; tail netta (full-breach {gap_full:.0%} 6d, " f"IV x1.5) ~${tail:,.0f}") p() b, e = res["BTC"], res["ETH"] p(f" Fee strutturale: le fee Deribit sono PROPORZIONALI alla size -> il rapporto") p(f" fee/raccolto ({b['fee_rt'] / max(b['harvest_cycle'], 1e-9):.0%} BTC, " f"{e['fee_rt'] / max(e['harvest_cycle'], 1e-9):.0%} ETH) NON migliora scalando:") p(f" a questi strike la DOUBLE DIAGONAL e' fee-negativa su Deribit A QUALSIASI size") p(f" (netto {b['net_cycle']:+.2f}$/ciclo BTC, {e['net_cycle']:+.2f}$/ciclo ETH per " f"unita' minima). Il capitale sposta il muro di margine, non questo.") p() # ---- soglie di capitale: $600 oggi, 2k/3.5k/5k prospettici -------------- # Granularita' sensata = >=3 posizioni concorrenti + scalabile 1->2 contratti # (>=6 slot di margine), budget margine 25% equity, rischio/posizione <=5%. # ETH CREDIT SPREAD: numeri del filone gemello ALB-A (margine ~$107/spread = # max-loss defined-risk, credito ~$2/spread) + fee 2 gambe. sp_margin, sp_credit = 107.0, 2.0 sp_fee = 2 * min(0.0003 * e["S"] * 0.1, 0.125 * sp_credit) # entry 2 gambe, 0.1 ETH p(f" SOGLIE DI CAPITALE (budget margine 25%, rischio/pos <=5%, granularita' sensata") p(f" = 3 posizioni concorrenti scalabili 1->2 = 6 slot):") p(f" {'conto':>7s} | {'DD BTC (m.$' + format(b['margin'], ',.0f') + ')':>22s} | " f"{'DD ETH min (m.$' + format(e['margin'], ',.0f') + ')':>22s} | " f"{'spread ETH (m.$107)':>20s}") tiers = {} for cap in (600.0, 2000.0, 3500.0, 5000.0): mbud = 0.25 * cap n_btc = int(mbud // b["margin"]) n_eth = int(mbud // e["margin"]) n_sp = int(mbud // sp_margin) risk_ok_sp = sp_margin <= 0.05 * cap # max-loss spread <= 5% equity risk_ok_eth = e["tail_net"] <= 0.05 * cap tiers[int(cap)] = dict(margin_budget=mbud, n_btc=n_btc, n_eth_dd=n_eth, n_spread=n_sp, spread_risk_ok=bool(risk_ok_sp), eth_dd_risk_ok=bool(risk_ok_eth), spread_pct_per_pos=sp_margin / cap, spread_income_yr=n_sp * (sp_credit - sp_fee) * 52) p(f" {cap:>7,.0f} | {n_btc:>4d} unita' ({b['margin'] / cap:>4.0%}/u) | " f"{n_eth:>4d} unita' ({e['margin'] / cap:>4.0%}/u) | " f"{n_sp:>3d} spread ({sp_margin / cap:>4.0%}/u, risk5% " f"{'OK' if risk_ok_sp else 'NO'})") p() p(f" Lettura (con upgrade conto 2-5k):") p(f" - DOUBLE DIAGONAL BTC: margine ${b['margin']:,.0f}/unita' -> 1a unita' dentro il") p(f" budget 25% solo da ~${4 * b['margin']:,.0f}; 'granularita' sensata' (6 slot) da " f"~${24 * b['margin'] / 1000:,.0f}k. Nemmeno 5k basta: resta il muro gamma-scalp") p(f" (diario 2026-06-26: 'opzione BTC min $5.968 >> $600'), che si sposta a ~10-30k.") p(f" - DOUBLE DIAGONAL ETH min 0.1: gia' a 2k il margine non e' il vincolo " f"({int(0.25 * 2000 / e['margin'])} unita' nel budget), ma resta FEE-NEGATIVA " f"({e['net_cycle']:+.2f}$/ciclo): eseguibile != conveniente, a ogni soglia.") p(f" - CREDIT SPREAD ETH (ALB-A): il PRIMO oggetto con soglia reale. A $600: 1 spread") p(f" = {sp_margin / 600:.0%} del conto (max-loss {sp_margin / 600:.0%} > 5% -> NO). " f"A 2k: {int(0.25 * 2000 // sp_margin)} spread nel budget, max-loss {sp_margin / 2000:.1%}") p(f" ~ok ma niente scalata 1->2 su 3 posizioni; a 3.5k: {int(0.25 * 3500 // sp_margin)} slot " f"(3 pos x2 non ancora); a 5k: {int(0.25 * 5000 // sp_margin)} slot -> granularita' OK") p(f" (3 concorrenti scalabili 1->2, {6 * sp_margin / 5000:.0%} del conto impegnato).") p(f" SOGLIA ~${int(np.ceil(6 * sp_margin / 0.25 / 100) * 100):,} per il set completo; " f"~$2.1k per 5 slot senza scalata. Income lordo a 5k: " f"{tiers[5000]['spread_income_yr']:.0f}$/anno ({tiers[5000]['spread_income_yr'] / 5000:.1%}) " f"PRIMA delle code (Test 2-3: EV~0).") p(f" - Confronto scala video: 10k su SPX = 1 contratto SPY (~$60k notional, 6x leva).") p(f" L'equivalente Deribit a parita' di prudenza (tail 5%) e' ~${20 * e['tail_net']:,.0f}+") p(f" su ETH e ~${20 * b['tail_net']:,.0f}+ su BTC: anche a 5k il conto e' SOTTO la") p(f" scala minima del gioco del video su BTC, e ci sta su ETH solo in versione") p(f" credit-spread (che e' VRP01, non 'Albimarini').") OUT["test5"] = dict(assets=res, tiers=tiers, spread=dict(margin=sp_margin, credit=sp_credit, fee=float(sp_fee))) # =========================================================================== # TEST 6 — CONFRONTO CON VRP01 (perche' un theta-harvest sopravvive) # =========================================================================== def test6(): p("\n" + "=" * 88) p("TEST 6 — Che cosa distingue un theta-harvest che sopravvive (VRP01) da uno che no") p("=" * 88) p(" VRP01 (audit superato, diario 2026-06-20): (1) DEFINED-RISK come struttura, non") p(" come accessorio: il long wing cappa worst-week -16.6% -> -7.4% e DD 33% -> 21%;") p(" (2) GATE di regime IV-rank>0.30 (vende solo vol RICCA): ribalta l'HOLD-OUT da") p(" -0.25 a +0.28 — l'alpha e' il filtro, non il theta; (3) SIZING: sleeve al 12%") p(" del book, niente compounding del numero di contratti; positivo/flat anche 2022.") p(" La 'Albimarini' e' l'opposto quantificato: vende SEMPRE (nessun gate: incassa il") p(" premio anche quando IV