"""STRATO TREND MULTI-ASSET sui 52 alt Hyperliquid certificati (diversificazione del trend). TP01 e' TSMOM vol-target long-flat su BTC+ETH (2 asset). Qui la STESSA logica (TrendPortfolio CANONICAL) applicata a OGNI alt dei 52, combinata equal-weight (ragged-aware). Idea: un trend piu' diversificato. Test onesto: e' correlato a TP01 (entrambi trend)? aggiunge al portafoglio TP01+XS01 nel hold-out? Causale, netto fee. uv run python scripts/portfolio/trend_multiasset.py """ from __future__ import annotations import sys, glob from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(PROJECT_ROOT)) import numpy as np, pandas as pd from src.strategies.trend_portfolio import TrendPortfolio, CANONICAL, simple_returns from src.portfolio.portfolio import to_daily, metrics, HOLDOUT, Sleeve, StrategyPortfolio from src.portfolio.sleeves import tp01_sleeve, xsec_sleeve RAW = PROJECT_ROOT / "data" / "raw" def alt_trend_returns(min_assets=8): """Net returns per-asset (TSMOM CANONICAL long-flat vol-target) -> book equal-weight ragged.""" eng = TrendPortfolio(**CANONICAL) series = {} for p in sorted(glob.glob(str(RAW / "hl_*_1d.parquet"))): sym = Path(p).stem.replace("hl_", "").replace("_1d", "").upper() d = pd.read_parquet(p) d = d.copy(); d["datetime"] = pd.to_datetime(d["timestamp"], unit="ms", utc=True) c = d["close"].values.astype(float) r = simple_returns(c); tgt = eng.target_series(d) held = np.zeros(len(tgt)); held[1:] = tgt[:-1] net = held * r - eng.fee_side * np.abs(np.diff(held, prepend=0.0)); net[0] = 0.0 series[sym] = pd.Series(np.clip(net, -0.99, None), index=d["datetime"]) M = pd.concat(series, axis=1, join="outer").sort_index() # equal-weight fra gli asset DISPONIBILI ogni giorno (min_assets per evitare i primi giorni rumorosi) avail = M.notna().sum(axis=1) book = M.mean(axis=1, skipna=True).where(avail >= min_assets) return book.dropna(), M def ev(d, label): f = metrics(d); h = metrics(d[d.index >= HOLDOUT]) yr = [float((1 + g).prod() - 1) for _, g in d.groupby(d.index.year)] pct = sum(v > 0 for v in yr) / len(yr) if yr else 0 print(f" {label:<28} FULL Sh {f['sharpe']:>5.2f} ret {f['ret']*100:>+6.0f}% DD {f['maxdd']*100:>4.0f}% | " f"HOLD Sh {h['sharpe']:>5.2f} | anni+ {pct*100:.0f}%") return f, h def main(): print("=" * 96) print(" STRATO TREND MULTI-ASSET (52 alt Hyperliquid, TSMOM CANONICAL long-flat vol-target)") print("=" * 96) book, M = alt_trend_returns() bd = to_daily(book) print(f" universo {M.shape[1]} alt, book [{bd.index[0].date()} -> {bd.index[-1].date()}]\n") ev(bd, "TREND-52alt standalone") tp = tp01_sleeve().daily(); xs = xsec_sleeve().daily() def corr(a, b): J = pd.concat({"a": a, "b": b}, axis=1, join="inner").dropna() return float(J["a"].corr(J["b"])) if len(J) > 5 else float("nan") print(f"\n correlazioni: TREND-52 vs TP01 {corr(bd, tp):+.2f} | vs XS01 {corr(bd, xs):+.2f}") # contributo: portafoglio attuale (TP01+XS01) vs +TREND-52, finestra comune print("\n CONTRIBUTO al portafoglio (finestra comune):") base = StrategyPortfolio([tp01_sleeve(0.70), xsec_sleeve(0.30)]).backtest() J = pd.concat({"tp": tp, "xs": xs, "tr": bd}, axis=1, join="inner").dropna() print(f" [comune {J.index[0].date()} -> {J.index[-1].date()}]") # baseline sulla finestra comune (TP01 0.7 + XS 0.3 rinormalizzato) base_c = 0.7 * J["tp"] + 0.3 * J["xs"] bf, bh = metrics(base_c), metrics(base_c[base_c.index >= HOLDOUT]) print(f" TP01 70 + XS 30 (attuale) FULL Sh {bf['sharpe']:.2f} DD {bf['maxdd']*100:.0f}% | HOLD Sh {bh['sharpe']:.2f}") for wtr in (0.2, 0.3): wt, wx = 0.7 * (1 - wtr), 0.3 * (1 - wtr) comb = wt * J["tp"] + wx * J["xs"] + wtr * J["tr"] cf, ch = metrics(comb), metrics(comb[comb.index >= HOLDOUT]) print(f" +TREND-52 w{wtr:.0%} FULL Sh {cf['sharpe']:.2f} ({cf['sharpe']-bf['sharpe']:+.2f}) DD {cf['maxdd']*100:.0f}% | HOLD Sh {ch['sharpe']:.2f} ({ch['sharpe']-bh['sharpe']:+.2f})") print("\n -> aggiungere se: scorrelato a TP01/XS01 e migliora FULL E HOLD. Se molto correlato a") print(" TP01 (entrambi trend) e contributo marginale, e' ridondante -> non si aggiunge.") if __name__ == "__main__": main()