76120b59c2
Sei agenti, nessun sopravvissuto:
- ELL-A range-cycle: rumore (0/24 Bonferroni; nessuna cella weekly regge
a tutte le 7 ancore). Lezione pandas: resample("7D", origin) IGNORA
origin -> usare "168h" per le bande d'ancora weekly.
- ELL-B Fibonacci: l'edge apparente e' la POSIZIONE dei livelli, non i
numeri (null location-matched: pctl 0.39-0.68); confluenza FAIL 4/4.
- ELL-C canale: Donchian travestito (non batte il Donchian equivalente,
DSR 0.685, IS 1.40 -> HOLD -0.87; target 1.618 = caso; anchor-luck 4h).
- ALB-A diagonale: il condor stessa-scadenza la batte a ogni f; senza
gate IV-rank tutte le strutture perdono (3a conferma: l'alpha del VRP
e' il gate); fee-negativa su Deribit a qualsiasi size; 2o caso
"0-perdite = Sharpe implausibile" dopo CC01.
- ALB-B claims: 82%/PF 5.16/"420%" consistente con zero skill (P=20-45%,
78.6% delle finestre 6-mesi lo produce); replay con code reali =
rovina 1998/2002/2020; la diagonale passa il 12-40% della perdita naked.
- Capital scaling 600->2-5k: unico vincolo binding = cap $300/asset
(a 5k book al 49% del target) -> AL DEPOSITO alzare a equity/2;
min_order $5 lasciare; XS01 ~20k confermata; aspettativa onesta
de-luckata 2k ~EUR 0.6-0.8/g, 5k ~EUR 1.4-2/g.
Nessun nuovo sleeve, book live invariato. 168 test verdi.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
663 lines
34 KiB
Python
663 lines
34 KiB
Python
"""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<RV), il rischio e' cappato solo dalla width della diagonale")
|
|
p(" (a 6x notional un gap -10% = ~-5/-12% equity PER UNITA', x4 post-scaling, Test 4),")
|
|
p(" e il sizing SCALA sui profitti proprio mentre la coda si avvicina (Test 3).")
|
|
p(" Win-rate 82% e PF 5.16 su 28 trade sono la FIRMA STRUTTURALE del venditore di")
|
|
p(" quantile a EV=0 in una finestra senza code (P 20-45%, Test 2), non un edge.")
|
|
|
|
|
|
# ===========================================================================
|
|
def main():
|
|
spy, m4, q82 = test1()
|
|
test2(spy, q82)
|
|
test3(spy, q82)
|
|
test4(q82)
|
|
test5()
|
|
test6()
|
|
p("\n" + "=" * 88)
|
|
p("JSON")
|
|
p("=" * 88)
|
|
p(json.dumps(OUT, indent=1, default=float))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|