437cf11199
(1) GTAA: trend difensivo long-flat su SPY/QQQ/IWM/TLT/GLD/HYG (EW sugli asset disponibili). GTAA lf vt12%: Sharpe 0.64 (OOS 0.89), maxDD 15% (8% sui 6-asset 2016+), corr SPY 0.64. Migliore sleeve equity: rischio-aggiustato > mono-SPY, DD bassissimo, diversificatore migliore. Difensiva (CAGR basso). Bear DD: GFC 14% vs 55%, COVID 10% vs 34%. (2) COMBO cross-mercato: crypto (TP01+XS01+VRP01) x equity (GTAA vt12), finestra 2019-2026. corr crypto<->equity = +0.17 (bassissima). blend 50/50 Sharpe 1.81 > crypto solo 1.60 > equity 1.12; maxDD dimezzato 14%->7%. DIVERSIFICA: primo miglioramento STRUTTURALE del rischio-aggiustato complessivo della ricerca post-reset (diversificazione vera, non alpha). CAVEAT: finestra crypto corta/favorevole (Sharpe assoluti ottimistici), cross-venue Deribit+IB, XS01/VRP01 STAT-MODE -> il combo deployable reale e' ~TP01+GTAA. Non risolve EUR50/g (capitale). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
110 lines
5.7 KiB
Python
110 lines
5.7 KiB
Python
"""EQ-GTAA01 — Trend difensivo MULTI-ASSET (GTAA) sull'universo ETF.
|
|
|
|
EQ-TREND01 ha mostrato che il trend long-flat su SPY taglia il DD (analogo TP01). La diversificazione
|
|
delle SORGENTI di trend (azioni US/tech/small + bond + oro + high-yield) di solito migliora il
|
|
rischio-aggiustato del trend mono-asset. Qui: ogni asset gestito col proprio trend long-flat
|
|
(TSMOM multi-orizzonte), equal-weight tra gli asset DISPONIBILI (la quota "off" o assente -> cash).
|
|
|
|
DATI: cache eqlib (ADJUSTED, nessun IB). Start diversi -> outer-join con peso rinormalizzato sugli
|
|
asset esistenti (come gli sleeve crypto). Finestra lunga: SPY/QQQ/IWM da ~2000; TLT(2016)/GLD(2004)/
|
|
HYG(2007) entrano dopo. Riporto anche la finestra 6-asset comune (2016+).
|
|
|
|
GIUDIZIO: vs SPY buy&hold, vs EW statico (isola il valore del TIMING di trend), vs SPY-trend mono;
|
|
Sharpe full/pre15/OOS + maxDD + plateau. Causale, netto fee.
|
|
"""
|
|
import sys
|
|
from pathlib import Path
|
|
import numpy as np, pandas as pd
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
|
import eqlib
|
|
from eqlib import load_eq
|
|
from eq_sector_momentum import _sh, _cagr, _dd, EQ_HOLDOUT, spy_bh
|
|
from eq_spy_trend import tsmom_exposure, backtest, _series
|
|
|
|
ASSETS = ["SPY", "QQQ", "IWM", "TLT", "GLD", "HYG"]
|
|
|
|
|
|
def gated_returns(sym, horizons=(21, 63, 126, 252), fee_side=0.0002, target_vol=None, lev_cap=1.0):
|
|
"""Rendimenti netti daily di UN asset gestito col proprio trend long-flat (cash quando off)."""
|
|
px = _series(sym)
|
|
ex = tsmom_exposure(px, horizons=horizons, target_vol=target_vol, lev_cap=lev_cap)
|
|
return backtest(px, ex, fee_side=fee_side)
|
|
|
|
|
|
def gtaa(assets=ASSETS, horizons=(21, 63, 126, 252), fee_side=0.0002, target_vol=None):
|
|
"""Portafoglio GTAA: media (equal-weight) dei rendimenti trend-gated sugli asset disponibili
|
|
ogni giorno (outer-join). La quota di asset assenti/in-cash resta in cash."""
|
|
cols = {a: gated_returns(a, horizons, fee_side, target_vol) for a in assets}
|
|
R = pd.concat(cols, axis=1).sort_index()
|
|
return R.mean(axis=1, skipna=True) # EW sugli asset esistenti quel giorno
|
|
|
|
|
|
def ew_buyhold(assets=ASSETS):
|
|
cols = {a: _series(a).pct_change() for a in assets}
|
|
return pd.concat(cols, axis=1).sort_index().mean(axis=1, skipna=True)
|
|
|
|
|
|
def _row(name, r, common=None, bench=None):
|
|
r = r.dropna() if common is None else r.reindex(common).fillna(0.0)
|
|
h = r[r.index >= EQ_HOLDOUT]; ii = r[r.index < EQ_HOLDOUT]
|
|
tim = float((r != 0).mean()) * 100
|
|
extra = ""
|
|
if bench is not None:
|
|
J = pd.concat({"r": r, "b": bench}, axis=1, join="inner").dropna()
|
|
extra = f" corrSPY {J['r'].corr(J['b']):+.2f}"
|
|
print(f" {name:26} CAGR {_cagr(r.values, r.index)*100:>5.1f}% Sh {_sh(r):>5.2f} "
|
|
f"(pre15 {_sh(ii):>5.2f}|OOS {_sh(h):>5.2f}) maxDD {_dd(r.values)*100:>4.0f}% inMkt {tim:>3.0f}%{extra}")
|
|
|
|
|
|
def main():
|
|
print("=" * 100)
|
|
print(" EQ-GTAA01 — Trend difensivo MULTI-ASSET (GTAA)")
|
|
print("=" * 100)
|
|
spy = spy_bh()
|
|
g = gtaa() # outer-join, finestra lunga
|
|
gl = g.dropna()
|
|
print(f" finestra lunga (outer-join) {gl.index[0].date()}..{gl.index[-1].date()} ({len(gl)}g) OOS {EQ_HOLDOUT.date()}+\n")
|
|
|
|
print(" --- BASELINE & confronti (finestra lunga) ---")
|
|
cl = gl.index
|
|
_row("SPY buy&hold", spy.reindex(cl).fillna(0))
|
|
_row("EW statico (no trend)", ew_buyhold().reindex(cl).fillna(0), bench=spy.reindex(cl).fillna(0))
|
|
_row("SPY-trend mono (TREND01)", backtest(_series("SPY"), tsmom_exposure(_series("SPY"))).reindex(cl).fillna(0), bench=spy.reindex(cl).fillna(0))
|
|
print("\n --- GTAA (multi-asset trend) ---")
|
|
_row("GTAA lf", gl, bench=spy.reindex(cl).fillna(0))
|
|
_row("GTAA lf vt12%", gtaa(target_vol=0.12).reindex(cl).fillna(0), bench=spy.reindex(cl).fillna(0))
|
|
|
|
# finestra 6-asset comune (tutti gli ETF esistono): 2016+
|
|
tlt0 = _series("TLT").index[0]
|
|
c6 = gl.index[gl.index >= tlt0]
|
|
print(f"\n --- finestra 6-asset comune ({c6[0].date()}+) ---")
|
|
_row("SPY buy&hold (6a win)", spy.reindex(c6).fillna(0))
|
|
_row("GTAA lf (6a win)", g.reindex(c6).fillna(0), bench=spy.reindex(c6).fillna(0))
|
|
_row("GTAA lf vt12 (6a win)", gtaa(target_vol=0.12).reindex(c6).fillna(0), bench=spy.reindex(c6).fillna(0))
|
|
|
|
# MARGINALE vs SPY
|
|
print("\n --- MARGINALE vs SPY (GTAA lf, finestra lunga) ---")
|
|
J = pd.concat({"spy": spy, "c": gl}, axis=1, join="inner").dropna(); JH = J[J.index >= EQ_HOLDOUT]
|
|
print(f" corr full {J['spy'].corr(J['c']):+.3f} | OOS {JH['spy'].corr(JH['c']):+.3f}")
|
|
for wt in (0.5, 1.0):
|
|
bf = _sh((1-wt)*J['spy']+wt*J['c'])-_sh(J['spy']); bh = _sh((1-wt)*JH['spy']+wt*JH['c'])-_sh(JH['spy'])
|
|
lbl = "100% GTAA" if wt == 1.0 else "50/50 SPY/GTAA"
|
|
print(f" {lbl:16}: uplift Sharpe FULL {bf:+.3f} OOS {bh:+.3f}")
|
|
print(" DD nei bear (GTAA vs SPY):")
|
|
for lo, hi, lbl in [("2000-03-01","2002-12-31","dot-com"),("2007-10-01","2009-06-30","GFC"),
|
|
("2020-02-01","2020-04-30","COVID"),("2022-01-01","2022-12-31","2022")]:
|
|
seg=lambda s: _dd(s.reindex(cl).fillna(0)[(cl>=pd.Timestamp(lo,tz='UTC'))&(cl<=pd.Timestamp(hi,tz='UTC'))].values)*100
|
|
print(f" {lbl:8} GTAA {seg(gl):.0f}% | SPY {seg(spy):.0f}%")
|
|
|
|
print("\n --- PLATEAU (Sharpe FULL/pre15/OOS, DD, CAGR) GTAA lf, finestra lunga ---")
|
|
print(f" {'horizons':22} {'FULL':>6} {'pre15':>6} {'OOS':>6} {'DD%':>5} {'CAGR%':>6}")
|
|
for hz in [(63,126,252),(21,63,126,252),(126,252),(252,)]:
|
|
r = gtaa(horizons=hz).reindex(cl).fillna(0); h=r[r.index>=EQ_HOLDOUT]; ii=r[r.index<EQ_HOLDOUT]
|
|
print(f" {'x'.join(map(str,hz)):22} {_sh(r):>6.2f} {_sh(ii):>6.2f} {_sh(h):>6.2f} {_dd(r.values)*100:>5.0f} {_cagr(r.values,r.index)*100:>6.1f}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|