research(wave-0822): ORTHO-SCREEN 7/7 scartato (e uno screen largo non puo' passare il proprio DSR); XS-LITE falsifica il muro dei 20k di XS01

This commit is contained in:
Adriano Dal Pastro
2026-08-22 17:12:15 +00:00
parent 14c1a75a7e
commit bd5b634209
6 changed files with 256 additions and 79 deletions
+1 -1
View File
@@ -254,7 +254,7 @@ def sezione2(snap: pd.DataFrame) -> pd.DataFrame:
g["p_x24"] = [bboot_p(d[d.odec == k].x24.values, block=48) for k in g.index]
print(g.round(3).to_string())
print(" av4/av24 = |ret| futuro medio: e' la gamba 'squeeze/cascata' dell'ipotesi")
print(" (piu' vol dopo un estremo di OI). E' QUASI PIATTA (0.59-0.75%% a 4h, solo il decile 0")
print(" (piu' vol dopo un estremo di OI). E' QUASI PIATTA (0.59-0.75% a 4h, solo il decile 0")
print(" leggermente sopra): la 'cascata' prevedibile non c'e'.")
print("\n--- CONTROLLO: e' solo 'il prezzo si e' appena mosso'? ---")
+6 -5
View File
@@ -453,9 +453,10 @@ def main() -> None:
# ---------------------------------------------------------------- 4. NULL LOCATION-MATCHED
P("\n[4] NULL LOCATION-MATCHED (regola 02/07: e' la POSIZIONE o e' l'OI?)")
P(" Confronto APPAIATO per evento: tw(livello speciale) - tw(placebo alla stessa distanza).")
P(" placebo: rand=strike uniforme (== permutazione dell'OI, equivalenza esatta) · adj=strike")
P(" adiacente al max-OI · jit=strike arbitrario a distanza 0.5-1.5x (analogo Fib±jitter) ·")
P(" mid=centro della griglia quotata (zero info OI) · mr7=media 7g dello spot (mean-reversion).")
P(" placebo PER REGOLA: adj_r = strike adiacente a K_r · jit_r = strike arbitrario a")
P(" distanza 0.5-1.5x |K_r - S| (analogo del Fib±jitter del 02/07).")
P(" placebo GLOBALI: rand = strike uniforme (== permutazione dell'OI, equivalenza esatta) ·")
P(" mid = centro della griglia quotata (zero info OI) · mr7 = media 7g dello spot.")
P(f" {'hte':>4} {'rule':>5} {'Δ vs adj_r':>12} {'Δ vs jit_r':>12}"
f" {'Δ vs rand':>12} {'Δ vs mid':>12} {'Δ vs mr7':>12}")
npos = ntot = 0
@@ -518,7 +519,7 @@ def main() -> None:
P("\n [6-bis] LA STESSA MACCHINA SUI LIVELLI PLACEBO (stesse finestre, stessa fee, stesso")
P(" sizing): se un placebo regge quanto il livello 'speciale', l'OI non aggiunge nulla.")
nulls = []
for rl in ("mp", "oi", "mid", "mr7", "rand", "jit", "maj", "short", "long"):
for rl in ("mp", "oi", "mid", "mr7", "rand", "jit_mp", "maj", "short", "long"):
for hte in (3, 6, 12, 24):
for bn, bd in bands.items():
d, _i = strat_daily(ev, rl, hte, bd, "sign", "all", FEE_SIDE_REAL)
@@ -527,7 +528,7 @@ def main() -> None:
cagr=mm["cagr"], dd=mm["dd"]))
N = pd.DataFrame(nulls)
piv = N.pivot_table(index=["hte", "band"], columns="rule", values="sharpe")
P(piv.reindex(columns=["mp", "oi", "mid", "mr7", "rand", "jit", "maj", "short", "long"])
P(piv.reindex(columns=["mp", "oi", "mid", "mr7", "rand", "jit_mp", "maj", "short", "long"])
.to_string(float_format=lambda v: f"{v:6.2f}"))
P(f" celle placebo valutate: {len(N)} (contano come trial: la griglia dichiarata sale)")
+46 -16
View File
@@ -360,7 +360,10 @@ def sezione2(curve: pd.DataFrame) -> dict:
print("\n (c) IL CONFOUND DI QUESTO FILONE — la pendenza e' informazione NUOVA o RV riciclata?")
rvp = (r1.rolling(24).std() * np.sqrt(24 * 365) * 100).reindex(idx)
rvf = (r1.shift(-24).rolling(24).std().shift(-23) * np.sqrt(24 * 365) * 100).reindex(idx)
# RV su (t, t+24h]: rolling(24) etichettata a t+24 e riportata indietro a t. (Una prima
# stesura aveva uno shift di troppo e misurava la finestra (+24h, +47h): corretto in
# sessione confrontando con una seconda implementazione a serie rovesciata.)
rvf = (r1.rolling(24).std().shift(-24) * np.sqrt(24 * 365) * 100).reindex(idx)
cf = curve[curve.asset == a].copy()
cf["hr"] = pd.to_datetime(cf["hr"], utc=True).dt.as_unit("ns")
ivf = cf.set_index("hr")[f"iv_{FRONT}"].reindex(idx)
@@ -563,9 +566,14 @@ def sezione3(chain: pd.DataFrame, atm_exp: pd.DataFrame, curve: pd.DataFrame) ->
f"({prem/CAPITALE:.0%} del conto), vega ${vega:.2f}/pt-vol, fee ${fee:.2f}/gamba")
print(f" carry lordo ${carry_g:+.3f}/giorno per struttura; attrito per giro "
f"${friz:.2f} => {friz/carry_g:.0f} giorni per andare in pari")
print(f"\n Un calendar spread e' DUE gambe: a $635 una sola struttura ETH impegna il")
print(f" ~15-30% del conto e rende centesimi al giorno. BTC e' fuori dal lotto minimo.")
print(f" Il bersaglio dichiarato e' 50 EUR/giorno: qui si parla di due ordini di grandezza sotto.")
print("\n Un calendar spread e' DUE gambe. Cio' che la tabella mostra e' il PREMIO di UNA")
print(" gamba al lotto minimo: BTC 26% del conto, ETH 10%. Il premio non e' tutto il")
print(" fabbisogno — la gamba CORTA vuole margine, e la misura del 2026-07-30 su una")
print(" struttura BTC a rischio definito dava $6.210 di collaterale per lotto. Quindi:")
print(" BTC resta fuori per il margine, ETH entra ma una sola struttura impegna il")
print(" 10-25% del conto per un carry LORDO di centesimi al giorno.")
print(" Il bersaglio dichiarato e' 50 EUR/giorno: qui siamo 2-3 ordini di grandezza sotto,")
print(" e il conto non regge la SECONDA struttura che servirebbe per diversificare.")
TRIALS.append(("Q2: 2 asset x eseguibilita' al lotto minimo", 2))
@@ -671,17 +679,31 @@ def sezione4(curve: pd.DataFrame) -> None:
hold = tgt.shift(1).fillna(0.0)
net = (hold * d["r"] - A.FEE_SIDE * hold.diff().abs().fillna(0.0)).dropna()
dd_c = A._dd_ret(net)
# ⚠️ il k giusto e' il PIU' GRANDE che sta sotto il DD del candidato, non il primo che
# trovi salendo da 0.05: partendo dal basso si confronta il candidato con un baseline
# quasi spento, che ha un DD ridicolo e uno Sharpe gonfiato dal rapporto. (Errore
# commesso e corretto in sessione: dava "candidato sopra il null" con k=0.05.)
ks = np.arange(0.05, 3.01, 0.05)
best = None
for k in np.arange(0.05, 1.01, 0.05):
for k in ks[::-1]:
b = k * d["r"].reindex(net.index)
if A._dd_ret(b) <= dd_c:
best = (k, A._sh(b), A._dd_ret(b))
best = (float(k), A._sh(b), A._dd_ret(b))
break
print(f" {a}: candidato Sharpe {A._sh(net):+.2f} maxDD {dd_c:.2%} | "
+ (f"buy&hold de-leverato k={best[0]:.2f} -> stesso DD ({best[2]:.2%}) con Sharpe "
f"{best[1]:+.2f} => {'IL CANDIDATO NON SOPRAVVIVE' if best[1] >= A._sh(net) else 'candidato sopra il null'}"
if best else "nessun k<=1 raggiunge quel DD"))
TRIALS.append((f"Q3 {a}: 20 valori di k nel null de-levering", 20))
sh_c = A._sh(net)
if best is None:
print(f" {a}: candidato Sharpe {sh_c:+.2f} maxDD {dd_c:.2%} | nessun k in "
f"[0.05,3.00] raggiunge quel DD")
else:
verdict = ("IL CANDIDATO NON SOPRAVVIVE (il de-levering fa meglio)"
if best[1] >= sh_c else "candidato sopra il null di de-levering")
print(f" {a}: candidato Sharpe {sh_c:+.2f} maxDD {dd_c:.2%} | buy&hold a leva "
f"k={best[0]:.2f} -> stesso o minor DD ({best[2]:.2%}) con Sharpe {best[1]:+.2f}"
f" => {verdict}")
imp = A.implausible_sharpe(net)
print(f" implausible_sharpe sul candidato di finestra: implausible="
f"{imp['implausible']} {imp['reasons']}")
TRIALS.append((f"Q3 {a}: 60 valori di k nel null de-levering", 60))
# ===========================================================================================
@@ -745,15 +767,24 @@ def sezione5(curve: pd.DataFrame) -> None:
if k in rep:
print(f" {k:20s} = {rep[k]}")
imp = A.implausible_sharpe(era)
print(f" implausible_sharpe: flagged={imp.get('flagged')} ({imp.get('reasons')})")
print(f" implausible_sharpe (proxy, era DVOL): implausible={imp['implausible']} "
f"{imp['reasons']}")
print(f" earns_slot = (assoluto non-FAIL) AND (marginale ADDS): qui il marginale e' "
f"{rep.get('marginal_verdict')} -> **earns_slot = False**.")
TRIALS.append(("Sez.5: 2 varianti proxy (long-only, L/S) x 1 soglia", 2))
sub("(c) il precedente che questo replica")
print(" `dvol_directional.py` (2026-06-29) testo' VRP-Z = z-score causale di (IV-RV) come")
print(" segnale DIREZIONALE su 5 anni, con la griglia completa: verdetto **HEDGE,")
print(" earns_slot=False** — paga solo quando TP01 e' debole, quindi non e' alpha.")
print(" La pendenza della term structure e', su questa lente, la stessa variabile con la")
print(" gamba realizzata sostituita da una implicita. Il filone non e' nuovo: e' quello.")
print(" earns_slot=False**. Qui, con la variante a SOGLIA BINARIA invece che a z-score,")
print(" il verdetto misurato e' NEUTRAL con uplift NEGATIVO — un verdetto diverso, la")
print(" stessa conclusione (earns_slot=False in entrambi). La differenza sta nella")
print(" variante, non nell'esito, e va riportata cosi': non sto dichiarando una replica")
print(" che non ho fatto.")
print(" Il punto strutturale resta: la pendenza e' il premio di vol con la gamba")
print(" REALIZZATA sostituita da una IMPLICITA (SEZIONE 2c: corr(pendenza, RV passata)")
print(" = -0.50/-0.57). Non e' una variabile nuova per questo progetto: e' quella, con")
print(" un rumore di misura piu' basso e 5 anni di storia in meno.")
# ===========================================================================================
@@ -778,7 +809,6 @@ def sezione6() -> None:
def main() -> None:
hr("r0822_term_structure — TERM STRUCTURE DELLA VOL IMPLICITA BTC/ETH")
print(__doc__.split("METODO.")[0].strip()[:0] or "", end="")
chain = load_quoted_chain()
sezione0(chain)
atm_exp = build_atm_by_expiry(chain)
+58 -30
View File
@@ -303,6 +303,20 @@ def sizes_from_L(ex: dict, L: pd.Series) -> np.ndarray:
# --------------------------------------------------------------------------- libro
def sizes_of(nm, ex: dict, SZ: dict, VTL: dict, ACT: dict, Lbook: pd.Series | None = None):
"""Vettore di size per-trade di una cella qualsiasi. Un solo posto in cui e' definita:
§8 (eseguibilita'), §9 (deriva) e §10 (null di permutazione) devono usare LA STESSA."""
if nm is None or nm == "BASE":
return size_flat(ex)
if nm in SZ:
return SZ[nm](ex)
if nm in VTL:
return sizes_from_L(ex, leverage_series(ex["base_daily"], **VTL[nm]))
if nm in ACT:
return sizes_from_L(ex, activity_L(ex["base_daily"], **ACT[nm]))
return sizes_from_L(ex, Lbook)
def book(tp: pd.Series, skh: pd.Series) -> pd.Series:
return combine_outer({"TP01": tp, "SKH01": skh}, {"TP01": W_TP, "SKH01": W_SKH})
@@ -498,7 +512,8 @@ def main() -> None:
print(" de-levering (k<1 sul BASELINE che pareggia il maxDD della variante)")
print("-" * 112)
print(f" {'variante':<28}{'wSKH eff':>9}{'dShFULL':>9}{'pos/n':>8}{'dShHOLD':>9}{'pos/n':>8}"
f"{'| ISO dF':>10}{'pos/n':>8}{'ISO dH':>9}{'pos/n':>8}{'dDDpp':>8}{'k_iso':>8}")
f"{'| ISO dF':>10}{'pos/n':>8}{'ISO dH':>9}{'pos/n':>8}{'dDDpp':>8}{'k_iso':>8}"
f"{'null de-lev':>14}")
base_b = BK["BASE"]
RES = {}
for nm in ALL:
@@ -525,7 +540,13 @@ def main() -> None:
print(f" {nm:<28}{we:>9.3f}{dF['median_paired']:>+9.3f}{dF['n_positive']:>5}/{len(offs):<3}"
f"{dH['median_paired']:>+9.3f}{dH['n_positive']:>5}/{len(offs):<3}"
f"{iF['median_paired']:>+10.3f}{iF['n_positive']:>5}/{len(offs):<3}"
f"{iH['median_paired']:>+9.3f}{iH['n_positive']:>5}/{len(offs):<3}{dd:>+8.2f}{ks:>8}")
f"{iH['median_paired']:>+9.3f}{iH['n_positive']:>5}/{len(offs):<3}{dd:>+8.2f}{ks:>8}"
f"{vd:>14}")
print(" ⚠️ NOTA sul null del de-levering: `sh(k*base)` e' IDENTICO a `sh(base)` perche' lo")
print(" Sharpe e' invariante a una costante -> in questa forma il null COINCIDE col confronto")
print(" degli Sharpe, ed e' per questo che qui lo Sharpe e' la metrica che decide e il DD no.")
print(" 'k_iso' resta come diagnostica: quanta leva dovrebbe cedere il baseline per pareggiare")
print(" il maxDD della variante. 'DE-LEVERING' = la variante NON batte quel baseline ridotto.")
print(" wSKH eff = peso EFFETTIVO di SKH nel libro (i nominali restano 75/25: la size lo")
print(" muove). Colonne 'ISO' = confronto vs il baseline RI-SCALATO a quello stesso peso:")
print(" e' li' che si legge la FORMA. Le colonne non-ISO contengono anche il cambio di peso.")
@@ -687,24 +708,10 @@ def main() -> None:
f" anni positivi {npos}/{len(yrs)}")
print()
for nm in top:
if nm in SZ:
szs = np.concatenate([SZ[nm](EX[can][a]) for a in ASSETS])
dys = pd.DatetimeIndex(np.concatenate([EX[can][a]["day_ent"].values for a in ASSETS]))
elif nm in VTL or nm in ACT:
mk = ((lambda b: leverage_series(b, **VTL[nm])) if nm in VTL
else (lambda b: activity_L(b, **ACT[nm])))
szs, dys = [], []
for a in ASSETS:
szs.append(sizes_from_L(EX[can][a], mk(EX[can][a]["base_daily"])))
dys.append(EX[can][a]["day_ent"].values)
szs = np.concatenate(szs); dys = pd.DatetimeIndex(np.concatenate(dys))
else:
L = leverage_series(BK["BASE"][can], BVT[nm]["tv"], BVT[nm]["w"], BVT[nm]["cap"])
szs, dys = [], []
for a in ASSETS:
szs.append(sizes_from_L(EX[can][a], L))
dys.append(EX[can][a]["day_ent"].values)
szs = np.concatenate(szs); dys = pd.DatetimeIndex(np.concatenate(dys))
Lb = (leverage_series(BK["BASE"][can], BVT[nm]["tv"], BVT[nm]["w"], BVT[nm]["cap"])
if nm in BVT else None)
szs = np.concatenate([sizes_of(nm, EX[can][a], SZ, VTL, ACT, Lb) for a in ASSETS])
dys = pd.DatetimeIndex(np.concatenate([EX[can][a]["day_ent"].values for a in ASSETS]))
s = pd.Series(szs, index=dys)
med = s.groupby(s.index.year).median()
rho = float(np.corrcoef(np.arange(len(med)), med.values)[0, 1]) if len(med) > 2 else float("nan")
@@ -715,6 +722,33 @@ def main() -> None:
print(" (la vol crypto e' scesa lungo il campione -> qualunque 1/vol pesa di piu' il")
print(" periodo recente, che e' anche l'hold-out). La riga 'corr col tempo' la misura.")
# noqa
print("\n" + "-" * 112)
print(" 10. NULL DI PERMUTAZIONE DELLA SIZE — la stessa MULTINSIEME di size, riassegnata a")
print(" caso ai trade. Se il vantaggio a iso-peso sopravvive al null, conta QUALE trade")
print(" riceve quale size (informazione); se no, e' solo la DISTRIBUZIONE delle size.")
print("-" * 112)
rng = np.random.default_rng(20260822)
a10 = list(offs[:: max(1, len(offs) // 3)])[:3]
NDRAW = 150
for nm in dict.fromkeys([best_iso, best_sleeve]):
real = float(np.median([sh(BK[nm][o]) - sh(CTRL[nm][o]) for o in a10]))
nul = []
for _ in range(NDRAW):
v = []
for o in a10:
szp = {a: rng.permutation(sizes_of(nm, EX[o][a], SZ, VTL, ACT)) for a in ASSETS}
lg = leg_daily(EX[o], szp)
m_s = vol(lg) / vol(SLE["BASE"][o]) if vol(SLE["BASE"][o]) > 0 else 1.0
v.append(sh(book(TP, lg)) - sh(book(TP, SLE["BASE"][o] * m_s)))
nul.append(float(np.median(v)))
nul = np.asarray(nul)
pc = float((nul < real).mean()) * 100
print(f" {nm:<26} reale {real:+.3f} null mediana {np.median(nul):+.3f} "
f"p90 {np.percentile(nul, 90):+.3f} max {nul.max():+.3f} "
f"-> percentile {pc:.1f} (p ~ {(1 - pc / 100):.3f})")
print(f" {NDRAW} permutazioni x {len(a10)} ancore {a10}; seme fisso 20260822.")
print("\n" + "=" * 112)
print(f" fatto in {time.time()-t0:.0f}s")
print("=" * 112)
@@ -728,19 +762,13 @@ def netted_target(asset: str, ex: dict, nm, SZ, VTL, ACT, BVT, SLE, TP, can, df1
ts1 = df1h["timestamp"].values.astype(np.int64) + 3_600_000
pos = np.zeros(len(ts1))
L1h = np.ones(len(ts1))
if nm is None:
sizes = size_flat(ex)
elif nm in SZ:
sizes = SZ[nm](ex)
elif nm in VTL:
sizes = sizes_from_L(ex, leverage_series(ex["base_daily"], **VTL[nm]))
elif nm in ACT:
sizes = sizes_from_L(ex, activity_L(ex["base_daily"], **ACT[nm]))
else: # BOOKVT
if nm is not None and nm in BVT: # BOOKVT: leva anche sulla gamba TP01
L = leverage_series(book(TP, SLE["BASE"][can]), BVT[nm]["tv"], BVT[nm]["w"], BVT[nm]["cap"])
sizes = sizes_from_L(ex, L)
sizes = sizes_of(nm, ex, SZ, VTL, ACT, L)
idx1h = pd.DatetimeIndex(pd.to_datetime(df1h["timestamp"].values, unit="ms", utc=True))
L1h = L.reindex(idx1h.floor("D")).ffill().fillna(1.0).values
else:
sizes = sizes_of(nm, ex, SZ, VTL, ACT)
t_in, t_out = ex["ts_close"][ex["i_ent"]], ex["ts_close"][ex["i_ex"]]
for k in range(len(t_in)):
j0 = int(np.searchsorted(ts1, t_in[k], side="left"))
+96 -27
View File
@@ -382,13 +382,19 @@ def main() -> None:
thr = float(np.quantile(vals[:-1], 0.30))
last_open = ser[ser > 0.30]
since = (V.index[-1] - last_open.index[-1]).days if len(last_open) else None
inwin = ser[(ser.index >= puts["ts"].min()) & (ser.index <= puts["ts"].max())]
near = inwin.max() if len(inwin) else float("nan")
print(f" {a}: quota di giorni col gate APERTO per anno — "
+ " ".join(f"{y}:{v:.0%}" for y, v in per_year.items()))
print(f" DVOL oggi {vals[-1]:.1f} · serve DVOL > {thr:.1f} "
f"(+{thr / vals[-1] - 1:.0%}) per riaprire il gate · ultimo giorno aperto "
f"{last_open.index[-1]:%Y-%m-%d} ({since} giorni fa)")
print(f" quanto vicino c'e' andato DENTRO la finestra: IV-rank max "
f"{near:.4f} il {inwin.idxmax():%Y-%m-%d}"
f"{'mancato per ' + format(0.30 - near, '.4f') if near <= 0.30 else 'aperto'}")
print(" → il campione non e' 'sfortunato': cade dentro un regime di vol bassa lungo e\n"
" persistente. La misura giusta si potra' rifare quando quel numero sara' superato,\n"
" persistente. Ma il margine NON e' comodo: il gate ha sfiorato l'apertura, e\n"
" bastano ~+10% di DVOL per riaprirlo. La misura giusta si potra' rifare quando quel numero sara' superato,\n"
" e da allora servira' comunque un anno di settimane per avere un campione.")
ret = {a: (canon[a]["ST"].iloc[-1] / canon[a]["spot"].iloc[0] - 1.0) for a in ASSETS}
@@ -668,6 +674,23 @@ def main() -> None:
f"MEDIANA ONESTA {band['median']:.2f} · banda [{band['lo']:.2f}, {band['hi']:.2f}] · "
f"positiva in {band['frac_positive']:.0%} di {band['n_anchors']} ancore · "
f"fortuna {band['luck']:+.2f}")
# DOVE vive l'instabilita': nel numeratore (il guadagno) o nel denominatore (la vol)?
det = []
for o in offsets:
w = by_off(o)
if len(w) > 2 and w.std(ddof=1) > 0:
det.append((o, float(w.mean()), float(w.std(ddof=1)), len(w)))
if det:
mm = np.array([d[1] for d in det])
ss = np.array([d[2] for d in det])
print(f" media settimanale fra le ancore: [{mm.min():+.2%}, {mm.max():+.2%}] "
f"(rapporto {mm.max() / max(mm.min(), 1e-9):.1f}x) · "
f"deviazione standard: [{ss.min():.2%}, {ss.max():.2%}] "
f"(rapporto {ss.max() / max(ss.min(), 1e-9):.1f}x)")
print(f" → l'ancora NON cambia quanto si guadagna, cambia il DENOMINATORE: con\n"
f" {det[0][3]} settimane e nessuna perdita la vol campionaria e' quasi\n"
f" arbitraria, e lo Sharpe con lei. Non e' che le 08:00 siano un'ora\n"
f" magica — e' che questa statistica non porta informazione.")
print("\n (d) null del de-levering / beta — 'e' reddito da volatilita' o crypto travestito?'")
print(" Lo spread ha delta positivo (≈ +0.18 all'ingresso): in un campione che sale del")
@@ -697,8 +720,15 @@ def main() -> None:
dly = comb.resample("1D").sum().fillna(0.0)
rep = A.marginal_vs_tp01(dly)
act = int((dly != 0).sum())
print(f" verdetto={rep.get('verdict')} corr={rep.get('corr'):.3f} "
f"(barre ATTIVE {act}/{len(dly)} = {act / len(dly):.0%})")
def _f(k):
v = rep.get(k)
return f"{v:.3f}" if isinstance(v, (int, float)) and v is not None else str(v)
print(f" verdetto={rep.get('verdict')} corr={_f('corr')} "
f"insample_edge={rep.get('has_insample_edge')} robust_oos={rep.get('robust_oos')} "
f"is_hedge={rep.get('is_hedge')}")
print(f" barre ATTIVE {act}/{len(dly)} = {act / len(dly):.0%} — un `None` qui non e'\n"
f" un bug: le sotto-misure che richiedono un hold-out non hanno dati per\n"
f" esistere, e la funzione lo dichiara invece di inventare un numero.")
print(f" ⚠️ {len(comb)} settimane su un hold-out che non esiste: il verdetto e' "
f"rumore, si riporta per non ometterlo.")
except Exception as exc: # noqa: BLE001
@@ -719,36 +749,75 @@ def main() -> None:
# ------------------------------------------------------------ §7 VERDETTO
hr("§7 VERDETTO")
print(""" SCARTATO — su questi dati la domanda non e' decidibile, e i tre motivi sono
indipendenti l'uno dall'altro:
print(""" SCARTATO — su questi dati la domanda non e' decidibile. Quattro motivi, indipendenti
l'uno dall'altro: ciascuno da solo basterebbe.
1. IL CAMPIONE NON CONTIENE LA STRATEGIA. 0 settimane su 19 passano il gate IV-rank>0.30
che E' l'alpha di VRP01. Il campione e' vol implicita al 24-28 percentile E un rialzo
forte del sottostante: il regime migliore possibile per vendere put, non uno neutro.
Le 10 settimane per asset (non 16: prima del 2026-06-09 la finestra 4-10 DTE non
esiste) danno 10/10 vincenti su BTC. La lente giusta non e' lo Sharpe, e' la regola
del tre: 0 perdite su 10 lascia un tasso vero fino al 30%, su un payoff che perde
~7x il credito.
che E' l'alpha di VRP01 il sleeve sarebbe stato FLAT per l'intero campione. La DVOL
mediana sta al 7 (BTC) / 11 (ETH) percentile della storia 2021+, e il sottostante e'
salito del +25% / +44%: vol implicita bassa E rialzo forte = il regime MIGLIORE
possibile per vendere put, non uno neutro. Le settimane sono 10 per asset, non 16
(prima del 2026-06-09 l'archivio seguiva solo la finestra 13-20 DTE: quella 4-10 DTE
non esiste). Esito: 10/10 vincenti su BTC, e la lente giusta non e' lo Sharpe ma la
regola del tre 0 perdite su 10 lascia un tasso vero fino al 30%, su un payoff che
perde ~7x il credito.
Sfumatura che va detta: il gate non e' lontano. Ha mancato l'apertura per 0.0002 di
IV-rank il 2026-06-07, e bastano ~+10% di DVOL per riaprirlo. Il campione e' dentro un
regime di vol bassa persistente, non davanti a un muro.
2. LA FAMIGLIA CHE HA STORIA NON E' QUELLA CHE SI PUO' ESEGUIRE. Le quote raccolte
sono le opzioni INVERSE (margine e regolamento in BTC/ETH); il conto e' in USDC.
La famiglia USDC-lineare esiste per BTC ed ETH ed ha un lotto minimo 10x piu' piccolo
(BTC 0.01 invece di 0.1) quindi il muro «BTC fuori a $3.000» del 30/07, congelato
in un test, e' misurato sulla famiglia sbagliata. Ma di quella famiglia abbiamo ZERO
ore di storia e l'open interest e' ~0.
2. IL TITOLO E' QUASI TUTTO FORTUNA D'ANCORA. L'ora d'ingresso e' un max-of-k mai
dichiarato, ed e' la prima volta che si misura su questa struttura: su 9 ancore lo
Sharpe canonico BTC 32.58 sta all'89 percentile e la MEDIANA ONESTA e' 1.45, con la
banda che tocca il NEGATIVO (-0.35). Fortuna +31.13. Su ETH 3.90 -> 2.41.
Il numero da citare per BTC e' 1.45, non 32.58 — e a quel punto non c'e' piu' niente
di straordinario da spiegare.
3. LO SPREAD E' IL COSTO DOMINANTE E NON E' MODELLABILE DA UN MID. Attraversare il
bid/ask costa da solo una quota grossa del credito, e l'ala che si COMPRA e' la gamba
dove il libro e' piu' largo lo stesso meccanismo che il 30/07 misurava come f=0.73.
3. LA FAMIGLIA CHE HA STORIA NON E' QUELLA CHE SI PUO' ESEGUIRE. Le quote raccolte sono
le opzioni INVERSE (margine e regolamento in BTC/ETH); il conto e' in USDC e il book
esegue sui perpetual lineari USDC. Esiste una famiglia USDC-lineare per BTC ed ETH col
lotto minimo 10x piu' piccolo (BTC 0.01 = $773 di nozionale invece di 0.1 = $7.728),
quindi il muro «BTC fuori a $3.000» del 30/07 congelato in
tests/test_vrp_profit_take.py e' misurato sulla famiglia che il conto non margina.
E' la 3a occorrenza della stessa forma dopo fee_watch (21/08) e la taratura di
venue_watch: un numero puntato su una configurazione diversa da quella che gira.
Ma di quella famiglia abbiamo ZERO ore di storia.
Cio' che NON si conclude: che la struttura non funzioni. Non e' stata osservata nel regime
in cui lavora. La regola «niente short-vol da MODELLO in deploy» NON diventa «short-vol da
QUOTE»: le quote ora ci sono, ma sono quote della famiglia sbagliata, in un regime solo.
4. LO SPREAD E' IL COSTO DOMINANTE, E SULLA FAMIGLIA ESEGUIBILE E' PEGGIO. Attraversare
il bid/ask costa ~10% del credito sugli INVERSE; nella regione di strike che serve lo
spread relativo mediano e' 24-37% sugli inverse e 29-59% sulle USDC, con open interest
~0. Il f del credito netto si replica indipendentemente a 0.714 (IC95 [0.690, 0.779],
0/19 osservazioni >= 1.0) conferma dello 0.73 del 30/07 su un campione piu' lungo e
con un percorso diverso.
AZIONE RACCOMANDATA (non eseguita tocca la produzione, e la decisione e' dell'operatore):
far raccogliere a `collect_chain.py` anche `currency=USDC` (famiglie BTC_USDC/ETH_USDC).
Costa ~2 chiamate in piu' per giro; senza, fra sei mesi ci ritroveremo con un anno di
storia della famiglia che non possiamo tradare e zero di quella che potremmo.""")
CIO' CHE NON SI CONCLUDE: che la struttura non funzioni. Non e' stata osservata nel regime
in cui lavora. E un risultato a favore c'e', va detto: su BTC il null «e' solo crypto
travestito» e' REFUTATO (beta +0.019, t=0.18) — il PnL non viene dal rialzo. Non basta,
perche' il punto 2 toglie il titolo comunque.
LA REGOLA NON CAMBIA. «Niente short-vol da MODELLO in deploy» non diventa «short-vol da
QUOTE»: le quote adesso ci sono, ma sono quote della famiglia sbagliata, in un regime solo,
e con l'ancora che spiega il risultato. La sostituzione della regola resta NON DIMOSTRATA.
DUE AZIONI, NESSUNA ESEGUITA (toccano la produzione o il conto: decide l'operatore).
(A) DATO far raccogliere a `collect_chain.py` anche `currency=USDC`. Costo MISURATO col
filtro OI>=100 gia' in vigore: ETH_USDC porterebbe 119 strumenti per giro (+21% di
chiamate, da ~570 a ~690), BTC_USDC ne porterebbe 5 su 686 (0.7%) = nulla di
utilizzabile. Cioe': l'unica struttura USDC-regolata con un libro vero e' su ETH,
lotto minimo 0.1 ETH = $242. Senza questa raccolta, fra sei mesi avremo un anno di
storia della famiglia che non possiamo marginare e zero di quella che potremmo che
e' esattamente la posizione di oggi, un'ondata dopo l'altra.
(B) CONTO una domanda che nessun dato pubblico chiude e che viene PRIMA di ogni
backtest: un saldo in USDC puo' marginare un'opzione INVERSE? Se no, la famiglia con
3 mesi di storia non e' negoziabile da questo conto a nessun capitale, e il punto 3
smette di essere un dettaglio di lotto per diventare un muro. Si risolve come si
risolse il PRIIPs il 26/07: guardando il conto, non i dati.
SE E QUANDO RIAPRIRLO. Non «quando ci sara' un crash» (criterio senza numero) ma: DVOL BTC
> 48.3 o ETH > 62.8 in modo persistente, PIU' ~un anno di settimane raccolte in quel
regime sulla famiglia eseguibile. Prima di allora ogni misura su questa struttura
descrivera' la sua astensione.""")
if __name__ == "__main__":