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>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user