Files
Adriano Dal Pastro e6657fcb16 feat(portfolio): GTAA01 promosso a 5° sleeve @20% — FULL 2.12→2.24, HOLD 2.21→2.46, DD 7.8→6.2%
Il diversificatore strutturale validato il 2026-06-22 (trend difensivo equity
6-ETF su IB, 30y storia, OOS 2015+ indipendente dall'hold-out crypto, corr al
book ~+0.10) era rimasto in paper_combo senza mai essere valutato come sleeve.
Valutazione onesta (r0701_gtaa_5th_sleeve): uplift positivo in-sample e su
TUTTE le finestre disgiunte (+0.05/+0.19/+0.25), multi-cut +0.21..+0.25,
plateau monotono w10-30% — passa dove EW-STR era morto. Ingresso @20% (IS-best
30%, scelta strutturale dichiarata). Convenzioni: weekend equity=0 (capitale
IB fermo, non riciclato), attivazione all'era book 2019-03. Il book live
Deribit (TP01+SKH01) NON cambia. Suite 168/168.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-01 23:31:38 +00:00

104 lines
4.8 KiB
Python

"""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()