"""GATE — XS01 phase-tranching (K sub-book sfasati di hold/K, capitale comune). `xs01_tranche_research.py` ha misurato timing-luck di fase reale (FULL Sharpe 1.87-2.87, DD 13.8-33.1% a seconda della fase di partenza, che live e' arbitraria). Qui il confronto ONESTO su equity GIORNALIERA (stessa convenzione dei gate del progetto: combine_portfolio.metrics) + gate PORT06 swap-sleeve: [1] standalone: base (fase canonica) vs K=2 vs K=3, FULL e OOS, daily Sharpe/DD; in piu' il range delle 12 fasi singole (il rischio che il tranching elimina). [2] PORT06: members canonici con XS01 sostituito dalla variante tranched. Criterio (tutti): OOS Sharpe portafoglio non peggiora (>-0.02) E DD non sale; K=2 e K=3 devono essere ENTRAMBI >= base (plateau, non best-pick di K). uv run python scripts/analysis/xs01_tranche_gate.py """ from __future__ import annotations import sys from pathlib import Path import numpy as np import pandas as pd PROJECT_ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(PROJECT_ROOT)) from scripts.analysis.combine_portfolio import port_returns, metrics, SPLIT, OOS_DATE from scripts.analysis.report_families import daily_from from scripts.analysis.xs01_tranche_research import xsec_trades from scripts.strategies.XS01_cross_sectional import aligned_panel, HOLD, POS, LEV from scripts.portfolios._defs import PORTFOLIOS from src.portfolio.sleeves import all_sleeve_equities from src.portfolio import weighting as W def daily_equity_for(phases, M, ts): """Trade di tutte le fasi su capitale comune (peso 1/K) -> equity daily.""" K = len(phases) allt = sorted([t for ph in phases for t in xsec_trades(phase=ph, M=M)], key=lambda t: t[1]) cap = 1000.0 eq_ts, eq_v = [], [] for i, j, net in allt: cap = max(cap + cap * POS * LEV * net / K, 10.0) eq_ts.append(ts[j]) eq_v.append(cap) return daily_from(eq_ts, eq_v) def port_metrics(members, ids, clusters, caps): dr = pd.DataFrame({i: members[i].pct_change().fillna(0.0) for i in ids}) w = W.weight_vector("cap", ids, dr, caps=caps, clusters=clusters) drp = port_returns({i: members[i] for i in ids}, w) return metrics(drp), metrics(drp, lo=SPLIT) def main(): M = aligned_panel() ts = pd.to_datetime(M.index, unit="ms", utc=True) p = PORTFOLIOS["PORT06"] print("=" * 92) print(" GATE — XS01 phase-tranching | equity daily, OOS da", OOS_DATE) print("=" * 92) variants = {"base (fase 0)": [0], "K=2 (fasi 0,6)": [0, 6], "K=3 (fasi 0,4,8)": [0, 4, 8]} eqs = {k: daily_equity_for(v, M, ts) for k, v in variants.items()} print(f"\n [1] STANDALONE daily — {'config':<18}{'FULL Sh':>8}{'FULL DD%':>9}{'OOS Sh':>8}{'OOS DD%':>8}") for k, e in eqs.items(): r = e.pct_change().fillna(0.0) f, o = metrics(r), metrics(r, lo=SPLIT) print(f" {k:<22}{f['sharpe']:>8.2f}{f['dd']:>9.2f}{o['sharpe']:>8.2f}{o['dd']:>8.2f}") # range delle 12 fasi singole (daily): il rischio di fase che il tranching elimina fs, os_ = [], [] for ph in range(HOLD): r = daily_equity_for([ph], M, ts).pct_change().fillna(0.0) fs.append(metrics(r)["sharpe"]) os_.append(metrics(r, lo=SPLIT)["sharpe"]) print(f" 12 fasi singole: FULL Sh {min(fs):.2f}-{max(fs):.2f} | OOS Sh {min(os_):.2f}-{max(os_):.2f}") eq_base = dict(all_sleeve_equities()) ids, cl, caps = list(p.sleeve_ids), p.clusters, p.caps print(f"\n [2] PORT06 swap-sleeve — {'config':<18}{'FULL Sh':>8}{'FULL DD%':>9}{'OOS Sh':>8}{'OOS DD%':>8}") f0, o0 = port_metrics(eq_base, ids, cl, caps) print(f" {'ATTUALE (base)':<22}{f0['sharpe']:>8.2f}{f0['dd']:>9.2f}{o0['sharpe']:>8.2f}{o0['dd']:>8.2f}") verdicts = [] for k in ("K=2 (fasi 0,6)", "K=3 (fasi 0,4,8)"): mem = dict(eq_base) mem["XS01"] = eqs[k] f1, o1 = port_metrics(mem, ids, cl, caps) ok = (o1["sharpe"] >= o0["sharpe"] - 0.02 and o1["dd"] <= o0["dd"] + 1e-9 and f1["sharpe"] >= f0["sharpe"] - 0.02 and f1["dd"] <= f0["dd"] + 1e-9) verdicts.append(ok) print(f" {k:<22}{f1['sharpe']:>8.2f}{f1['dd']:>9.2f}{o1['sharpe']:>8.2f}{o1['dd']:>8.2f}" f" {'OK' if ok else 'NO'}") print(f"\n => {'PROMOSSO (plateau K=2 e K=3)' if all(verdicts) else 'NON promosso (serve plateau su entrambi i K)'}") if __name__ == "__main__": main()