"""r0701_gtaa_5th_sleeve — EQ-GTAA01 come 5° sleeve del portafoglio attivo? Valutazione ONESTA. Contesto (diari 2026-06-22/23): GTAA (trend difensivo equity, 6 ETF, vt12%, ~30y di storia, netto fee IB 2bps/lato) fu validato come diversificatore strutturale del book crypto (corr ~0.17-0.21, combo Sharpe 1.60->1.81) ma lasciato in paper cross-venue (paper_combo) e MAI valutato come 5° sleeve del portafoglio attivo (il giorno dopo arrivo' SKH01). Qui: gate onesti per lo slot. Metodo: * pannello = 4 sleeve attivi + GTAA (to_daily), outer-join rinormalizzato (combine_outer); * finestra di valutazione ancorata all'inizio del book crypto (TP01 start) — NO gonfiaggio da 23 anni di solo-GTAA pre-2019; * peso GTAA selezionato IN-SAMPLE (Sharpe pre-2025) su griglia {10..30%}, gli altri scalati (1-w) — nessuna scelta sull'hold-out; * verifica: hold-out 2025+, multi-cut (2023-01/2024-01/2024-07/2025-01), finestre DISGIUNTE, per-anno, corr, is_hedge (corr tra Sharpe-anno del book e uplift-anno), plateau in w. """ from __future__ import annotations import sys from pathlib import Path import numpy as np import pandas as pd ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) from src.portfolio.portfolio import HOLDOUT, combine_outer, metrics, to_daily from src.portfolio.sleeves import active_sleeves from src.portfolio.gtaa import gtaa_returns GRID_W = [0.10, 0.15, 0.20, 0.25, 0.30] CUTS = [pd.Timestamp(c, tz="UTC") for c in ("2023-01-01", "2024-01-01", "2024-07-01", "2025-01-01")] DISJOINT = [("2019-03-01", "2023-01-01"), ("2023-01-01", "2025-01-01"), ("2025-01-01", "2027-01-01")] def sh(s: pd.Series, lo=None, hi=None) -> float: v = s if lo is not None: v = v[v.index >= lo] if hi is not None: v = v[v.index < hi] r = np.asarray(v.dropna().values, float) return float(r.mean() / r.std() * np.sqrt(365.25)) if len(r) > 1 and r.std() > 0 else 0.0 def main() -> None: sl = active_sleeves() cols = {s.name: s.daily() for s in sl} w_cur = {s.name: s.weight for s in sl} g = to_daily(gtaa_returns()) lo_book = min(c.index.min() for c in cols.values()) # inizio era-crypto del book cols5 = dict(cols, GTAA01=g) base = combine_outer(cols, w_cur, lo=lo_book) print(f"book start {lo_book.date()} | GTAA storia {g.index.min().date()}->{g.index.max().date()}" f" | GTAA standalone: FULL {sh(g):.2f}, pre-2025 {sh(g, hi=HOLDOUT):.2f}, " f"era-crypto {sh(g[g.index >= lo_book]):.2f}, HOLD {sh(g, lo=HOLDOUT):.2f}") print(f"corr(GTAA, book) era-crypto: {g.reindex(base.index).corr(base):+.3f} | " f"hold-out: {g.reindex(base.index).loc[HOLDOUT:].corr(base.loc[HOLDOUT:]):+.3f}") print(f"\nBASE 4-sleeve : FULL {sh(base):.3f} IS {sh(base, hi=HOLDOUT):.3f} " f"HOLD {sh(base, lo=HOLDOUT):.3f} DD {metrics(base)['maxdd']:.1%} " f"CAGR {metrics(base)['cagr']:+.1%}") # griglia peso GTAA — selezione IN-SAMPLE rows = [] for w in GRID_W: w5 = {k: v * (1 - w) for k, v in w_cur.items()} | {"GTAA01": w} c5 = combine_outer(cols5, w5, lo=lo_book) rows.append(dict(w=w, IS=sh(c5, hi=HOLDOUT), FULL=sh(c5), HOLD=sh(c5, lo=HOLDOUT), DD=metrics(c5)["maxdd"], CAGR=metrics(c5)["cagr"])) print(f" +GTAA {w:.0%}: IS {rows[-1]['IS']:.3f} FULL {rows[-1]['FULL']:.3f} " f"HOLD {rows[-1]['HOLD']:.3f} DD {rows[-1]['DD']:.1%} CAGR {rows[-1]['CAGR']:+.1%}") best = max(rows, key=lambda r: r["IS"]) w = best["w"] print(f"\ncella IN-SAMPLE: w={w:.0%} (IS {best['IS']:.3f}); plateau IS: " + ", ".join(f"{r['w']:.0%}:{r['IS']:.2f}" for r in rows)) w5 = {k: v * (1 - w) for k, v in w_cur.items()} | {"GTAA01": w} c5 = combine_outer(cols5, w5, lo=lo_book) print("\nmulti-cut (Sharpe OOS dal taglio, book+GTAA vs book):") for cut in CUTS: print(f" {cut.date()}: {sh(c5, lo=cut):.3f} vs {sh(base, lo=cut):.3f} " f"delta {sh(c5, lo=cut) - sh(base, lo=cut):+.3f}") print("finestre DISGIUNTE:") for a, b in DISJOINT: la, lb = pd.Timestamp(a, tz="UTC"), pd.Timestamp(b, tz="UTC") print(f" {a[:7]}..{b[:7]}: delta {sh(c5, lo=la, hi=lb) - sh(base, lo=la, hi=lb):+.3f}") print("per-anno (ret book -> ret book+GTAA):") ups, shs = [], [] for y in sorted(set(base.index.year)): by, cy = base[base.index.year == y], c5[c5.index.year == y] rb, rc = float((1 + by).prod() - 1), float((1 + cy).prod() - 1) ups.append(sh(cy) - sh(by)); shs.append(sh(by)) print(f" {y}: {rb:+7.1%} -> {rc:+7.1%} dSh {sh(cy) - sh(by):+.2f}") ih = float(np.corrcoef(shs, ups)[0, 1]) if len(ups) > 2 else float("nan") print(f"is_hedge check — corr(Sharpe-anno book, uplift-anno): {ih:+.2f} " f"(molto negativa = hedge, non alpha)") if __name__ == "__main__": main()