research: MAT01 e generalizzazione STATARB — 2 refutazioni pulite + addendum al gate 27/09

MAT01 (r0725_mat01_multiasset_trend.py, r0725_mat01b_regime.py) — SCARTATO.
Allargare il trend difensivo da 6 a 18 ETF multi-asset-class a meccanismo GTAA
CONGELATO: MAT18 perde 3/4 finestre disgiunte e, a PARI VOLATILITA', il suo
minor drawdown si INVERTE (-20.6% vs -15.4%) -> era solo de-levering. Terza
occorrenza del null de-levering dopo VRP-DD e TP01xDVOL. Corr->crypto invariata
(0.110 vs 0.116): zero guadagno di diversificazione.
Risultato positivo conservato: la curva d'ampiezza e' monotona (mediana OOS 0.44
a k=1 -> 0.80 a k=18, DD -16.0% -> -6.9%, satura a k~10-12), ma GTAA6 sta gia' al
95° pctl dei 6-subset casuali — e non e' cherry-picked (contiene TLT, il peggiore
dei 18) -> niente piu' ampiezza da raccogliere su quello sleeve.

STATARB-MULTI (r0725_statarb_multi.py) — il meccanismo congelato (W=45, sgn=+1)
su tutte le 50 coppie alt/BTC, out-of-pair-sample. REGGE: 82% Sharpe>0, 0/50
degeneri (mono 53%), perm p<0.05 nel 18% (atteso 5%). E ETH/BTC e' al rango
22/50 (58° pctl): la coppia scopritrice NON e' un outlier -> per il gate 27/09
l'ipotesi "fortuna di una coppia" e' refutata. NON e' uno sleeve: ampiezza
effettiva ~4.5 (corr media 0.204, gamba BTC condivisa), paniere IC95%
[-0.12, 1.72] con t 1.31.
Errore di metodo corretto in-sessione: il primo null statico usava
sign(mean(segnale)) sull'intero campione = look-ahead; rifatto causale + a priori.

STATARB-EQ (r0725_statarb_eq.py) — SCARTATO. Stesso meccanismo su 12 coppie ETF
a priori, 28.5 anni, ampiezza effettiva 9.9: paniere Sharpe -1.00 (t -5.33),
negativo in 4/4 decadi. Ma la lettura sta nel LORDO (-0.17, non -1.00): due terzi
sono drag di turnover -> non esiste strategia specchio. Il segno lordo dice che
sulle azioni il residuo REVERTE mentre sul crypto CONTINUA: meccanismo
plausibilmente crypto-specifico. Il gate 27/09 non puo' appoggiarsi
all'argomento "fenomeno universale".

r0724_statarb_deploy_gate.py: addendum PRE-REGISTRATO oggi (forward-day 26 di 90,
64 giorni prima della decisione) — riporta anche lo Sharpe della statica
"sempre short ETH vs BTC" a pari vol-target sulla stessa finestra. Le soglie del
2026-07-24 NON sono toccate: e' diagnostica non binding. Lettura odierna
(26 barre): STATARB +5.70 vs benchmark -4.98, posizione corrente LONG lo spread
= opposta alla statica.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-07-25 09:47:47 +00:00
parent 741bbc2c09
commit 9af6693e4f
5 changed files with 1101 additions and 0 deletions
@@ -20,6 +20,23 @@ REGOLA (immutabile — ogni modifica va motivata nel diario come violazione):
* config invariata (W=45, sgn=+1) — ogni ritocco azzera la finestra;
* maxDD forward < 10% (coerenza col profilo di ricerca, DD backtest ~11%).
ADDENDUM PRE-REGISTRATO IL 2026-07-25 (forward-day 26 di 90, 64 giorni PRIMA della decisione —
quindi pre-registrazione vera, non selezione a posteriori). Motivo: il test out-of-pair-sample
`r0725_statarb_multi.py` ha applicato il meccanismo congelato alle 50 coppie alt/BTC di Hyperliquid
e ha trovato che, su 60% delle coppie, una posizione STATICA "sempre short alt vs BTC" a pari
vol-target batte il segnale (uplift medio -0.11). Su ETH/BTC il pareggio e' quasi esatto:
Sharpe segnale 0.46 vs statica-short 0.48 sulla finestra HL 2024-2026. Tradotto: una parte di cio'
che STATARB-RESID guadagna potrebbe essere il beta di regime "ETH sottoperforma BTC", non timing.
Si aggiunge quindi UNA misura da riportare alla decisione: lo Sharpe, sulla STESSA finestra
forward, della statica sempre-short a pari vol-target (BENCH_SHORT). Non e' una soglia binding:
le soglie pre-registrate il 2026-07-24 (Sharpe>=0.5, DD<10%, haircut<0.5pp) restano quelle e
NON sono state toccate. E' una diagnostica obbligatoria da leggere insieme, e la decisione se
renderla binding spetta esplicitamente all'operatore, non allo script.
NB confortante gia' oggi: la posizione forward corrente e' LONG lo spread (+0.61 = long ETH /
short BTC), cioe' OPPOSTA alla scommessa statica -> la finestra forward sta testando il segnale
in un modo che la statica non spiega.
Uso: `uv run python scripts/research/r0724_statarb_deploy_gate.py`
(si puo' lanciare in qualsiasi momento: ricorda la data-decisione e valuta lo stato).
"""
@@ -42,6 +59,38 @@ HAIRCUT_MAX_PP = 0.5
DD_MAX = 0.10
def _bench_static_short(rows: list[dict]) -> tuple[float, int] | None:
"""Addendum 2026-07-25: Sharpe di una posizione STATICA short-spread (short ETH / long BTC)
a pari vol-target, sulla STESSA finestra forward. Stessa meccanica di costo del monitor
(fee su 2 gambe per |delta pos|), ma qui il turnover viene solo dal vol-target."""
import sys as _sys
from pathlib import Path as _P
_sys.path.insert(0, str(_P(__file__).resolve().parents[2]))
_sys.path.insert(0, str(_P(__file__).resolve().parents[1] / "research"))
try:
import pandas as pd
from orthogonal_signals import build_joint, spread_ret
except Exception:
return None
try:
j = build_joint("1d")
except Exception:
return None
ts = {int(r["ts"]) for r in rows}
s = spread_ret(j)
vol = pd.Series(s).rolling(30, min_periods=30).std().values * np.sqrt(365)
scal = np.where((vol > 0) & np.isfinite(vol), 0.20 / vol, 0.0)
pos = np.clip(-1.0 * np.nan_to_num(scal), -2.0, 2.0) # SEMPRE short lo spread
held = np.concatenate([[0.0], pos[:-1]])
net = held * s - 2.0 * 0.0005 * np.abs(np.diff(held, prepend=0.0))
mask = np.array([int(t) in ts for t in j["timestamp"].values])
r = net[mask]
r = r[np.isfinite(r)]
if len(r) < 10 or r.std() == 0:
return None
return float(r.mean() / r.std() * np.sqrt(365)), int(len(r))
def main() -> None:
rows = [json.loads(x) for x in RETURNS.read_text().splitlines() if x.strip()]
rm = np.array([r["net_modeled"] for r in rows])
@@ -61,6 +110,18 @@ def main() -> None:
print(f" maxDD fwd : {dd:.1%} (guardia < {DD_MAX:.0%})")
print(f" fill-haircut cum : {haircut_pp:.2f} pp (guardia < {HAIRCUT_MAX_PP} pp)")
print(f" data decisione : {DECISION} (proroga unica: {DECISION_EXT})")
bench = _bench_static_short(rows)
if bench is not None:
sh_b, n_b = bench
print(f"\n [addendum 2026-07-25, diagnostico NON binding]")
print(f" benchmark statico 'sempre short ETH vs BTC' a pari vol-target, stessa finestra:")
print(f" Sharpe benchmark {sh_b:+.2f} su {n_b} barre -> STATARB {sh:+.2f}, "
f"differenza {sh - sh_b:+.2f}")
print(" se STATARB non batte questo benchmark, il suo guadagno e' beta di regime")
print(" (ETH sottoperforma BTC), non timing. Da leggere PRIMA di deliberare il deploy.")
else:
print("\n [addendum: benchmark statico non calcolabile — dati BTC/ETH non disponibili]")
if today < DECISION:
print(f"\n -> NESSUNA DECISIONE OGGI ({today}): mancano {(DECISION - today).days} giorni.")
print(" Il numero corrente NON autorizza deploy anticipato (regola pre-registrata).")