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:
@@ -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).")
|
||||
|
||||
Reference in New Issue
Block a user