research(sweep): 5 thread paralleli — 0 nuovi sleeve, STATARB-RESID LEAD ortogonale+eseguibile
Ricerca onesta su aree inesplorate (harness altlib+xsec_v2_nonmom, tutti i gate incl. study_family_honest anti-selection-on-holdout). Branch main, nessun impatto live, test 143/143. 1 XSEC low-risk cousins (MAX/idio-vol/Amihud) -> 1 LEAD (IVOL), STAT-MODE, DSR 0.37<0.95 2 XSEC momentum-structure vs XS01 -> tutto REDUNDANT (sostituire XS01 distrugge hold) 3 Meta-allocazione dinamica (4 sleeve) -> pesi fissi vincono (gia quasi risk-parity) 4 Segnali ortogonali ETH/BTC (2 gambe) -> STATARB-RESID + DVOLSPREAD LEAD 5 1-gamba a segnale (MACD/RSI/Supertrend/...) -> 0/12 earns_slot (trend=TP01, MR morta, hedge) LEAD principale STATARB-RESID (mean-rev residuo ETH-b*BTC, OLS rolling, 2 gambe): primo stream INSIEME ortogonale (corr->book 0.027, beta-mkt 0.013) ED eseguibile a $600 (haircut ~0, NON STAT-MODE) -> cadono i 2 muri di XS01/opzioni. Resta solo il muro dell'edge (Sharpe 0.84, DSR 0.929 same-sign <0.95). Causalita+fee verificate dal coordinatore. Forward-monitor, non sleeve. Soffitto direzionale ~1.3 riconfermato. Diario 2026-06-29-strategy-search-5threads.md, CLAUDE.md agg. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,417 @@
|
||||
"""XSEC v3 — varianti STRUTTURALI di momentum cross-sectional su Hyperliquid (STAT-MODE).
|
||||
|
||||
TESI (filone XS). XS01 (sleeve attivo) e' momentum cross-sectional sui 19 major: blend di lookback
|
||||
[30,90] (z-score cross-sectional mediato) + gate di dispersione, vol-target 20%. Lezione del
|
||||
progetto (diari 2026-06-19/20): "i margini su XS sono nella STRUTTURA DEL SEGNALE, non nel numero
|
||||
di asset". Quindi NON allarghiamo l'universo: testiamo 4 COSTRUZIONI di momentum STRUTTURALMENTE
|
||||
diverse e chiediamo se MIGLIORANO o DIVERSIFICANO XS01 (o se sono solo XS01 travestito).
|
||||
|
||||
Varianti (tutte L/S dollar-neutral, top-k/bottom-k, CAUSALI; long alto score / short basso score):
|
||||
RAMOM - RISK-ADJUSTED momentum: score = ritorno cumulato su L / vol realizzata su L
|
||||
(momentum "Sharpe-like", non grezzo). Penalizza i trend rumorosi.
|
||||
ACCEL - momentum ACCELERATION: score = mom(L_breve) - mom(L_lungo), la curvatura/2a differenza
|
||||
del trend relativo (chi sta accelerando vs chi sta decelerando).
|
||||
FIP - FROG-IN-THE-PAN / information discreteness: score = sign(mom) * ID, dove
|
||||
ID = |%giorni-su - %giorni-giu| su L. Privilegia i trend LISCI (path consistente).
|
||||
VOLSC - VOLATILITY-MANAGED momentum (Moreira-Muir): selezione = momentum, ma la LEVA del book
|
||||
e' scalata dall'inverso della vol di MERCATO cross-section recente (rischia di piu' a
|
||||
mercato calmo, meno in tempesta) invece del vol-target sulla vol della STRATEGIA.
|
||||
|
||||
GIUDIZIO = MARGINALE vs XS01, non assoluto. Una variant con corr~0.9 a XS01 e Sharpe simile NON
|
||||
aggiunge nulla (e' XS01 travestito). Per ognuna calcolo: (a) corr vs XS01 e TP01; (b) uplift del
|
||||
PORTAFOGLIO 4->5 sleeve a 10%/15%; (c) SOSTITUZIONE di XS01 con la variant a parita' di peso. Vince
|
||||
solo se DIVERSIFICA (corr<0.7) E migliora l'hold-out aggiunta, OPPURE DOMINA XS01 a parita' di slot.
|
||||
|
||||
GATE (CLAUDE.md, metodologia obbligatoria):
|
||||
1. griglia L in {30,60,90} (Ls/Ll per ACCEL), H in {5,10}, k in {5,8}, ENTRAMBI universi (51/19).
|
||||
2. CAUSALE: score a close[i], peso tenuto in i+1 (engine shifta); vol=0 gata; prefix-check ok.
|
||||
3. NETTO fee 0.10% RT su ogni gamba/ribilancio + turnover; sweep fee monotona (test).
|
||||
4. DEFLATED Sharpe sul best con TUTTI gli Sharpe FULL come trial (multiple-testing; serve >0.95).
|
||||
5. per-anno + HOLD-OUT 2025-01-01. ANTI selection-on-holdout: riporto best per IN-SAMPLE(<2025)
|
||||
E best per HOLD, e verifico col deflated-Sharpe.
|
||||
6. CAVEAT IMMUTABILE: book a molte gambe NON eseguibile a $600 -> STAT-MODE, MAI deploy.
|
||||
|
||||
uv run python scripts/research/xsec_v3_momstruct.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(PROJECT_ROOT / "scripts" / "research"))
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
import xsec_v2_nonmom as xv # harness collaudato (load_matrix, xs_engine, evalcfg, ...)
|
||||
from src.portfolio.sleeves import XS_UNIVERSE
|
||||
|
||||
DPY = xv.DPY
|
||||
TV = xv.TV
|
||||
FEE = xv.FEE
|
||||
HOLDOUT = xv.HOLDOUT
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# SCORE BUILDERS — closure score_at(i)->(score[A], valid[A]) + warmup. CAUSALI (dati <= i).
|
||||
# Modellati su make_mom/make_resid di xsec_v2_nonmom.py.
|
||||
# ===========================================================================
|
||||
def make_ramom(PX, L):
|
||||
"""Risk-adjusted momentum: score = (px[i]/px[i-L]-1) / std(ritorni giornalieri su L)."""
|
||||
px, n, A, DR, _ = xv._precompute(PX)
|
||||
RVL = DR.rolling(L, min_periods=int(0.8 * L)).std().values
|
||||
def score_at(i):
|
||||
if i - L < 0:
|
||||
return np.full(A, np.nan), np.zeros(A, bool)
|
||||
r = px[i] / px[i - L] - 1.0
|
||||
rv = RVL[i]
|
||||
with np.errstate(invalid="ignore", divide="ignore"):
|
||||
score = r / rv
|
||||
valid = np.isfinite(score) & np.isfinite(px[i]) & np.isfinite(px[i - L]) & (rv > 0)
|
||||
return score, valid
|
||||
return score_at, L + 1
|
||||
|
||||
|
||||
def make_accel(PX, Ls, Ll):
|
||||
"""Acceleration: score = mom(Ls) - mom(Ll) (Ls<Ll), entrambi a close[i]."""
|
||||
px, n, A, *_ = xv._precompute(PX)
|
||||
def score_at(i):
|
||||
if i - Ll < 0:
|
||||
return np.full(A, np.nan), np.zeros(A, bool)
|
||||
r_s = px[i] / px[i - Ls] - 1.0
|
||||
r_l = px[i] / px[i - Ll] - 1.0
|
||||
score = r_s - r_l
|
||||
valid = np.isfinite(px[i]) & np.isfinite(px[i - Ls]) & np.isfinite(px[i - Ll])
|
||||
return score, valid
|
||||
return score_at, Ll + 1
|
||||
|
||||
|
||||
def make_fip(PX, L):
|
||||
"""Frog-in-the-pan / information discreteness: score = sign(mom_L) * |%up - %down| su L.
|
||||
%up/%down calcolati sui SOLI giorni osservati (NaN pre-listing esclusi). Trend lisci -> |.| alto."""
|
||||
px, n, A, DR, _ = xv._precompute(PX)
|
||||
up = (DR > 0).astype(float).where(DR.notna())
|
||||
dn = (DR < 0).astype(float).where(DR.notna())
|
||||
mp = int(0.8 * L)
|
||||
UPc = up.rolling(L, min_periods=mp).sum().values
|
||||
DNc = dn.rolling(L, min_periods=mp).sum().values
|
||||
CNT = DR.rolling(L, min_periods=mp).count().values
|
||||
def score_at(i):
|
||||
if i - L < 0:
|
||||
return np.full(A, np.nan), np.zeros(A, bool)
|
||||
r = px[i] / px[i - L] - 1.0
|
||||
c = CNT[i]
|
||||
with np.errstate(invalid="ignore", divide="ignore"):
|
||||
pu = UPc[i] / c
|
||||
pdn = DNc[i] / c
|
||||
idd = np.abs(pu - pdn)
|
||||
score = np.sign(r) * idd
|
||||
valid = (np.isfinite(px[i]) & np.isfinite(px[i - L]) & np.isfinite(idd) & (c >= mp))
|
||||
return score, valid
|
||||
return score_at, L + 1
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# ENGINE volatility-managed (VOLSC): selezione momentum top-k/bottom-k IDENTICA a xs_engine, ma il
|
||||
# vol-target NON e' sulla vol della STRATEGIA bensi' sull'inverso della vol di MERCATO cross-section
|
||||
# (equal-weight) recente (Moreira-Muir). Distinzione strutturale unica da XS01. CAUSALE (shift(1)).
|
||||
# ===========================================================================
|
||||
def xs_engine_mktvol(PX, VOL, score_at, H, k, B_mkt=20, target_vol=TV, fee=FEE, min_assets=10,
|
||||
warmup=0, cap=3.0):
|
||||
px = PX.values
|
||||
vol = VOL.values
|
||||
n, A = px.shape
|
||||
dret = np.full((n, A), np.nan)
|
||||
dret[1:] = px[1:] / px[:-1] - 1.0
|
||||
W = np.zeros((n, A))
|
||||
w = np.zeros(A)
|
||||
for i in range(n):
|
||||
if i >= warmup and i % H == 0:
|
||||
score, valid = score_at(i)
|
||||
valid = valid & np.isfinite(score) & (vol[i] > 0)
|
||||
idxv = np.where(valid)[0]
|
||||
if len(idxv) >= min_assets:
|
||||
kk = min(k, len(idxv) // 2)
|
||||
order = idxv[np.argsort(score[idxv])]
|
||||
lo, hi = order[:kk], order[-kk:]
|
||||
w = np.zeros(A)
|
||||
w[hi] = 0.5 / kk
|
||||
w[lo] = -0.5 / kk
|
||||
else:
|
||||
w = np.zeros(A)
|
||||
W[i] = w
|
||||
gross = np.zeros(n)
|
||||
gross[1:] = np.nansum(W[:-1] * np.nan_to_num(dret[1:]), axis=1)
|
||||
turn = np.zeros(n)
|
||||
turn[0] = np.abs(W[0]).sum()
|
||||
turn[1:] = np.abs(np.diff(W, axis=0)).sum(axis=1)
|
||||
net = gross - turn * (fee / 2.0)
|
||||
s = pd.Series(net, index=PX.index)
|
||||
# vol-target sulla vol di MERCATO (equal-weight), causale (shift 1): leva alta a mercato calmo
|
||||
mkt = PX.pct_change().mean(axis=1)
|
||||
sig_mkt = mkt.rolling(B_mkt, min_periods=int(0.6 * B_mkt)).std().shift(1) * np.sqrt(DPY)
|
||||
scale = np.clip(np.nan_to_num(target_vol / sig_mkt.replace(0, np.nan).values, nan=0.0), 0, cap)
|
||||
turn_py = float(turn.sum() / (n / DPY)) if n else 0.0
|
||||
return pd.Series(s.values * scale, index=PX.index), turn_py
|
||||
|
||||
|
||||
def caus_check_mktvol(PX, VOL, builder, cfg, B_mkt=20, frac=0.85, tail=60, tol=1e-9):
|
||||
"""Prefix-check di causalita' per il pipeline VOLSC (engine custom): ricostruisce su un prefisso
|
||||
e confronta la coda con la run completa. Look-ahead -> divergenza."""
|
||||
sa, warm = builder(PX, cfg)
|
||||
full, _ = xs_engine_mktvol(PX, VOL, sa, cfg["H"], cfg["k"], B_mkt=B_mkt, warmup=warm)
|
||||
cut = int(len(PX) * frac)
|
||||
PXc, VOLc = PX.iloc[:cut], VOL.iloc[:cut]
|
||||
sa2, warm2 = builder(PXc, cfg)
|
||||
pre, _ = xs_engine_mktvol(PXc, VOLc, sa2, cfg["H"], cfg["k"], B_mkt=B_mkt, warmup=warm2)
|
||||
lo = max(0, cut - tail)
|
||||
a = full.values[lo:cut]
|
||||
b = pre.values[lo:cut]
|
||||
worst = float(np.max(np.abs(a - b))) if len(a) else float("nan")
|
||||
return dict(ok=bool(worst <= tol), max_tail_diff=worst, cut=cut, tail=len(a))
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# REGISTRY varianti: builder(PX,p)->(score_at,warm), griglia config, engine.
|
||||
# ===========================================================================
|
||||
def variants():
|
||||
Lg = (30, 60, 90)
|
||||
Hk = [dict(H=H, k=k) for H in (5, 10) for k in (5, 8)]
|
||||
accel_pairs = [(30, 60), (30, 90), (60, 90)]
|
||||
return {
|
||||
"RAMOM": dict(
|
||||
builder=lambda PX, p: make_ramom(PX, p["L"]),
|
||||
cfgs=[dict(L=L, **hk) for L in Lg for hk in Hk],
|
||||
engine="std", B_mkt=None),
|
||||
"ACCEL": dict(
|
||||
builder=lambda PX, p: make_accel(PX, p["Ls"], p["Ll"]),
|
||||
cfgs=[dict(Ls=ls, Ll=ll, **hk) for (ls, ll) in accel_pairs for hk in Hk],
|
||||
engine="std", B_mkt=None),
|
||||
"FIP": dict(
|
||||
builder=lambda PX, p: make_fip(PX, p["L"]),
|
||||
cfgs=[dict(L=L, **hk) for L in Lg for hk in Hk],
|
||||
engine="std", B_mkt=None),
|
||||
"VOLSC": dict(
|
||||
builder=lambda PX, p: xv.make_mom(PX, p["L"], +1),
|
||||
cfgs=[dict(L=L, **hk) for L in Lg for hk in Hk],
|
||||
engine="mktvol", B_mkt=20),
|
||||
}
|
||||
|
||||
|
||||
def run_variant_cfg(PX, VOL, v, p):
|
||||
sa, warm = v["builder"](PX, p)
|
||||
if v["engine"] == "mktvol":
|
||||
s, turn = xs_engine_mktvol(PX, VOL, sa, p["H"], p["k"], B_mkt=v["B_mkt"], warmup=warm)
|
||||
else:
|
||||
s, turn = xv.xs_engine(PX, VOL, sa, p["H"], p["k"], warmup=warm)
|
||||
return xv.to_daily(s), turn
|
||||
|
||||
|
||||
def tag(p):
|
||||
return " ".join(f"{kk}{vv}" for kk, vv in p.items())
|
||||
|
||||
|
||||
def run_grid(PX, VOL, v, xs_daily, tp_daily, uname):
|
||||
rows = []
|
||||
for p in v["cfgs"]:
|
||||
daily, turn = run_variant_cfg(PX, VOL, v, p)
|
||||
if daily.std() == 0 or len(daily) < 60:
|
||||
continue
|
||||
f, h, pct = xv.evalcfg(daily)
|
||||
ins = xv.metrics(daily[daily.index < HOLDOUT])["sharpe"]
|
||||
rows.append(dict(cfg=p, uni=uname, daily=daily, full=f["sharpe"], hold=h["sharpe"],
|
||||
ins=ins, dd=f["maxdd"], ret=f["ret"], pct=pct,
|
||||
corrXS=xv._corr(daily, xs_daily), corrTP=xv._corr(daily, tp_daily),
|
||||
turn=turn))
|
||||
return rows
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# PORTAFOGLIO — base cablata una sola volta (cache sleeve riusate per uplift+sostituzione).
|
||||
# ===========================================================================
|
||||
_BASE = None
|
||||
_BASE_M = None
|
||||
|
||||
|
||||
def _base():
|
||||
global _BASE, _BASE_M
|
||||
if _BASE is None:
|
||||
_BASE = xv.active_sleeves()
|
||||
pf = xv.StrategyPortfolio(_BASE)
|
||||
pf.backtest() # warma le cache degli sleeve
|
||||
_BASE_M = (xv.metrics(pf.combined_daily()), xv.metrics(pf.combined_daily(lo=HOLDOUT)))
|
||||
return _BASE, _BASE_M
|
||||
|
||||
|
||||
def add_uplift(daily, fr):
|
||||
base, _ = _base()
|
||||
wraw = fr / (1.0 - fr)
|
||||
cand = xv.Sleeve("XSV3_cand", wraw, lambda d=daily: d)
|
||||
pf = xv.StrategyPortfolio(base + [cand])
|
||||
return (xv.metrics(pf.combined_daily()), xv.metrics(pf.combined_daily(lo=HOLDOUT)),
|
||||
pf.weights().get("XSV3_cand", 0.0))
|
||||
|
||||
|
||||
def substitute_xs01(daily):
|
||||
base, _ = _base()
|
||||
sub = [xv.Sleeve("XSV3_sub", s.weight, lambda d=daily: d) if s.name == "XS01_xsec_hl" else s
|
||||
for s in base]
|
||||
pf = xv.StrategyPortfolio(sub)
|
||||
return xv.metrics(pf.combined_daily()), xv.metrics(pf.combined_daily(lo=HOLDOUT))
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# REPORT
|
||||
# ===========================================================================
|
||||
def per_year(daily):
|
||||
out = []
|
||||
for y, g in daily.groupby(daily.index.year):
|
||||
out.append((int(y), round(float((1 + g).prod() - 1), 3)))
|
||||
return out
|
||||
|
||||
|
||||
def variant_verdict(pick, up_best, sub_full_d, sub_hold_d, caus_ok):
|
||||
cx = abs(pick["corrXS"])
|
||||
if not caus_ok:
|
||||
return "SCARTATO", "non causale (prefix-check fallito)"
|
||||
if pick["full"] <= 0.3 or pick["hold"] <= 0:
|
||||
return "SCARTATO", f"standalone debole (FULL {pick['full']:+.2f}, HOLD {pick['hold']:+.2f})"
|
||||
dominates = (sub_full_d > 0.02 and sub_hold_d > 0.05)
|
||||
diversifies = (cx < 0.7) and (up_best[1] > 0.05) # up_best=(Δfull,Δhold)
|
||||
if dominates:
|
||||
return "MIGLIORA-XS01", f"sostituendo XS01 il book sale FULL {sub_full_d:+.2f} / HOLD {sub_hold_d:+.2f}"
|
||||
if diversifies:
|
||||
return "DIVERSIFICA", f"corrXS {pick['corrXS']:+.2f}<0.7 e uplift HOLD aggiunta {up_best[1]:+.2f}"
|
||||
if cx >= 0.7:
|
||||
return "REDUNDANT", f"corrXS {pick['corrXS']:+.2f} alta -> momentum XS01 travestito"
|
||||
return "REDUNDANT", f"scorrelata (corrXS {pick['corrXS']:+.2f}) ma non additiva (uplift HOLD {up_best[1]:+.2f}, sub HOLD {sub_hold_d:+.2f})"
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 104)
|
||||
print(" XSEC v3 — VARIANTI STRUTTURALI di momentum cross-sectional (RAMOM/ACCEL/FIP/VOLSC) — STAT-MODE")
|
||||
print("=" * 104)
|
||||
|
||||
tp_daily = xv.tp01_sleeve().daily()
|
||||
xs_daily = xv.xsec_sleeve().daily()
|
||||
print(" riferimenti: XS01 (momentum blend+gate, sleeve attivo) e TP01 (trend BTC/ETH).")
|
||||
xs_f = xv.metrics(xs_daily)
|
||||
xs_h = xv.metrics(xs_daily[xs_daily.index >= HOLDOUT])
|
||||
print(f" XS01 standalone: FULL Sh {xs_f['sharpe']:.2f} DD {xs_f['maxdd']*100:.0f}% | "
|
||||
f"HOLD Sh {xs_h['sharpe']:.2f}")
|
||||
|
||||
universes = {"51-all": None, "19-major": XS_UNIVERSE}
|
||||
mats = {}
|
||||
for uname, u in universes.items():
|
||||
PX, VOL = xv.load_matrix(u)
|
||||
mats[uname] = (PX, VOL)
|
||||
print(f" universo {uname:<9}: {PX.shape[1]} asset, {PX.shape[0]} giorni "
|
||||
f"[{PX.index[0].date()} -> {PX.index[-1].date()}]")
|
||||
|
||||
vdefs = variants()
|
||||
all_full = []
|
||||
per_var_rows = {}
|
||||
for vname, v in vdefs.items():
|
||||
rows_all = []
|
||||
for uname, (PX, VOL) in mats.items():
|
||||
rows = run_grid(PX, VOL, v, xs_daily, tp_daily, uname)
|
||||
rows_all += rows
|
||||
all_full += [r["full"] for r in rows]
|
||||
per_var_rows[vname] = rows_all
|
||||
|
||||
base, (bf, bh) = _base()
|
||||
print(f"\n BASE portafoglio (4 sleeve attivi): FULL Sh {bf['sharpe']:.2f} DD {bf['maxdd']*100:.0f}%"
|
||||
f" | HOLD Sh {bh['sharpe']:.2f} DD {bh['maxdd']*100:.0f}%")
|
||||
|
||||
summary = []
|
||||
for vname, v in vdefs.items():
|
||||
rows = per_var_rows[vname]
|
||||
if not rows:
|
||||
print(f"\n [{vname}] nessuna config valida.")
|
||||
continue
|
||||
n = len(rows)
|
||||
pos_full = sum(r["full"] > 0 for r in rows)
|
||||
pos_hold = sum(r["hold"] > 0 for r in rows)
|
||||
pick_ins = max(rows, key=lambda r: (r["ins"], r["full"])) # selezione ONESTA (in-sample)
|
||||
pick_hold = max(rows, key=lambda r: r["hold"]) # ceiling ottimistico
|
||||
|
||||
print("\n" + "#" * 104)
|
||||
print(f"# {vname} | {n} config x2 universi | plateau FULL>0 {pos_full}/{n} | HOLD>0 {pos_hold}/{n}")
|
||||
print("#" * 104)
|
||||
print(f" {'pick':<12}{'cfg':<24}{'uni':<10}{'FULL':>6}{'INS':>6}{'HOLD':>6}{'DD%':>6}"
|
||||
f"{'ret%':>7}{'an+':>6}{'crXS':>7}{'crTP':>7}{'t/y':>7}")
|
||||
for lbl, r in (("by-INS<2025", pick_ins), ("by-HOLD", pick_hold)):
|
||||
print(f" {lbl:<12}{tag(r['cfg']):<24}{r['uni']:<10}{r['full']:>6.2f}{r['ins']:>6.2f}"
|
||||
f"{r['hold']:>6.2f}{r['dd']*100:>6.0f}{r['ret']*100:>+7.0f}{r['pct']*100:>5.0f}%"
|
||||
f"{r['corrXS']:>+7.2f}{r['corrTP']:>+7.2f}{r['turn']:>7.0f}")
|
||||
# top-3 per IN-SAMPLE per leggere il plateau
|
||||
print(" --- top-3 by IN-SAMPLE Sharpe (plateau) ---")
|
||||
for r in sorted(rows, key=lambda r: -r["ins"])[:3]:
|
||||
print(f" {tag(r['cfg']):<24}{r['uni']:<10}FULL {r['full']:+.2f} INS {r['ins']:+.2f}"
|
||||
f" HOLD {r['hold']:+.2f} corrXS {r['corrXS']:+.2f}")
|
||||
|
||||
# ---- gate sul pick_ins (selezione onesta) ----
|
||||
pick = pick_ins
|
||||
v_uni = pick["uni"]
|
||||
PX, VOL = mats[v_uni]
|
||||
if v["engine"] == "mktvol":
|
||||
caus = caus_check_mktvol(PX, VOL, v["builder"], pick["cfg"], B_mkt=v["B_mkt"])
|
||||
else:
|
||||
caus = xv.causality_prefix_check(PX, VOL, v["builder"], pick["cfg"])
|
||||
dsr, sr0 = xv.deflated_sharpe(pick["full"], all_full, pick["daily"])
|
||||
print(f" CAUSALITA' (prefix-check) ok={caus['ok']} max_tail_diff={caus['max_tail_diff']:.2e}")
|
||||
print(f" DEFLATED Sharpe (N={len([s for s in all_full if np.isfinite(s)])} trial GLOBALI): "
|
||||
f"{dsr:.3f} | soglia Sharpe-max-null {sr0:.2f} (serve >0.95)")
|
||||
print(f" per-anno (pick-INS): {per_year(pick['daily'])}")
|
||||
|
||||
# ---- portafoglio: uplift 4->5 e SOSTITUZIONE di XS01 (a parita' di peso) ----
|
||||
print(" UPLIFT (aggiunta come 5o sleeve):")
|
||||
up_best = (-9.0, -9.0)
|
||||
for fr in (0.10, 0.15):
|
||||
cf, ch, wgt = add_uplift(pick["daily"], fr)
|
||||
df_, dh_ = cf["sharpe"] - bf["sharpe"], ch["sharpe"] - bh["sharpe"]
|
||||
print(f" @{wgt*100:>4.1f}% FULL {cf['sharpe']:.2f} ({df_:+.2f}) DD {cf['maxdd']*100:.0f}%"
|
||||
f" | HOLD {ch['sharpe']:.2f} ({dh_:+.2f})")
|
||||
if (df_ + dh_) > (up_best[0] + up_best[1]):
|
||||
up_best = (df_, dh_)
|
||||
sf, sh = substitute_xs01(pick["daily"])
|
||||
sub_full_d, sub_hold_d = sf["sharpe"] - bf["sharpe"], sh["sharpe"] - bh["sharpe"]
|
||||
print(f" SOSTITUZIONE XS01->{vname} (peso {base[1].weight:.4f}): "
|
||||
f"FULL {sf['sharpe']:.2f} ({sub_full_d:+.2f}) DD {sf['maxdd']*100:.0f}%"
|
||||
f" | HOLD {sh['sharpe']:.2f} ({sub_hold_d:+.2f})")
|
||||
|
||||
verdict, why = variant_verdict(pick, up_best, sub_full_d, sub_hold_d, caus["ok"])
|
||||
print(f" >>> VERDETTO {vname}: {verdict} — {why}")
|
||||
summary.append(dict(name=vname, pick=pick, dsr=dsr, caus=caus["ok"], up=up_best,
|
||||
sub=(sub_full_d, sub_hold_d), verdict=verdict))
|
||||
|
||||
# ---- SINTESI ----
|
||||
print("\n" + "=" * 104)
|
||||
print(" SINTESI — giudizio MARGINALE vs XS01 (sleeve attivo)")
|
||||
print("=" * 104)
|
||||
print(f" {'variant':<8}{'FULL':>6}{'HOLD':>6}{'DD%':>6}{'corrXS':>8}{'corrTP':>8}{'DSR':>7}"
|
||||
f"{'+upHOLD':>9}{'subHOLD':>9} verdetto")
|
||||
for s in summary:
|
||||
p = s["pick"]
|
||||
print(f" {s['name']:<8}{p['full']:>6.2f}{p['hold']:>6.2f}{p['dd']*100:>6.0f}"
|
||||
f"{p['corrXS']:>+8.2f}{p['corrTP']:>+8.2f}{s['dsr']:>7.3f}{s['up'][1]:>+9.2f}"
|
||||
f"{s['sub'][1]:>+9.2f} {s['verdict']}")
|
||||
|
||||
winners = [s for s in summary if s["verdict"] in ("MIGLIORA-XS01", "DIVERSIFICA")]
|
||||
print("\n CONCLUSIONE:")
|
||||
if not winners:
|
||||
print(" NESSUNA variante batte o diversifica davvero XS01. Tutte sono momentum-family ad")
|
||||
print(" alta corr con XS01 e/o non additive al portafoglio -> REDUNDANT/SCARTATO. La")
|
||||
print(" struttura del segnale (risk-adj/accel/smoothness/vol-timing) NON apre uno slot nuovo.")
|
||||
else:
|
||||
for s in winners:
|
||||
print(f" {s['name']}: {s['verdict']} (forward-monitor). corrXS {s['pick']['corrXS']:+.2f}, "
|
||||
f"+upHOLD {s['up'][1]:+.2f}, subHOLD {s['sub'][1]:+.2f}, DSR {s['dsr']:.3f}.")
|
||||
print("\n CAVEAT (immutabili): storia ~2.5 anni (deflated-Sharpe + multiple-testing); book a molte")
|
||||
print(" gambe NON eseguibile a $600 -> STAT-MODE / forward-monitor, MAI deploy. Nessuno sleeve")
|
||||
print(" registrato: e' lavoro statistico (vincoli del filone XS).")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user