"""Resoconto anno-per-anno della strategia combo (TP01+GTAA) + componenti, da $2.000. Per anno: PnL ($ e %), MaxDD (intra-anno), NumTrades, equity di fine anno (compounding da 2k). Combo = blend 50/50 TP01(Deribit) + GTAA(IB) (crypto compoundato su grid giorni-di-borsa). NumTrades: TP01 = cambi di target BTC/ETH (>0.05); GTAA = ribilanci MENSILI per-gamba (>2%). Onesto: il combo parte dal 2019 (crypto). GTAA-solo dato anche su 10y come contesto. """ 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)); sys.path.insert(0, str(ROOT / "scripts" / "live")) from src.data.downloader import load_data from src.strategies.trend_portfolio import TrendPortfolio, CANONICAL, resample_1d from src.portfolio.sleeves import _tp01_returns from src.portfolio.gtaa import gtaa_returns, _exposure, _close, EQ_UNIVERSE INITIAL = 2000.0 def tp01_trades_per_year(): tp = TrendPortfolio(**CANONICAL); cnt = {} for a in ("BTC", "ETH"): df = resample_1d(load_data(a, "1h")); tgt = tp.target_series(df) idx = pd.DatetimeIndex(pd.to_datetime(df["datetime"])) chg = pd.Series(np.abs(np.diff(tgt, prepend=tgt[0])) > 0.05, index=idx) for y, c in chg.groupby(idx.year).sum().items(): cnt[int(y)] = cnt.get(int(y), 0) + int(c) return cnt def gtaa_trades_per_year(): # pesi giornalieri -> ribilancio MENSILE realistico -> conta gambe cambiate >2% W = {} for a in EQ_UNIVERSE: ex = _exposure(_close(a)) / len(EQ_UNIVERSE) W[a] = ex Wd = pd.concat(W, axis=1).dropna() Wm = Wd.resample("ME").last() # peso a fine mese chg = (Wm.diff().abs() > 0.02).sum(axis=1) # gambe ribilanciate quel mese return chg.groupby(chg.index.year).sum().astype(int).to_dict() def yearly(ret: pd.Series, trades: dict, label: str, start_capital=INITIAL): ret = ret.dropna().sort_index() print(f"\n ===== {label} =====") print(f" {'anno':6}{'eq inizio':>12}{'PnL $':>12}{'PnL %':>9}{'MaxDD %':>9}{'NumTrades':>11}{'eq fine':>12}") eq = start_capital for y in sorted(set(ret.index.year)): r = ret[ret.index.year == y] if len(r) < 5: continue eq0 = eq curve = eq0 * np.cumprod(1 + r.values) peak = np.maximum.accumulate(curve) dd = float(np.max((peak - curve) / peak)) if len(curve) else 0.0 eq = float(curve[-1]) pnl = eq - eq0 nt = trades.get(y, None) print(f" {y:<6}{eq0:>12,.0f}{pnl:>+12,.0f}{(eq/eq0-1)*100:>+8.1f}%{dd*100:>8.1f}%" f"{(str(nt) if nt is not None else '—'):>11}{eq:>12,.0f}") tot = eq / start_capital - 1 yrs = (ret.index[-1] - ret.index[0]).days / 365.25 cagr = (eq / start_capital) ** (1 / yrs) - 1 if yrs > 0 else 0 sh = float(r.mean()) if False else float(ret.mean() / ret.std() * np.sqrt(252)) print(f" {'TOT':<6}{start_capital:>12,.0f}{eq-start_capital:>+12,.0f}{tot*100:>+8.1f}%" f"{'':>9}{sum(v for v in trades.values()) if trades else 0:>11}{eq:>12,.0f}") print(f" -> da ${start_capital:,.0f} a ${eq:,.0f} in {yrs:.1f}y | CAGR {cagr*100:+.1f}% | Sharpe {sh:.2f}") def combo_daily(wc=0.5): tp = _tp01_returns() if tp.index.tz is None: tp.index = tp.index.tz_localize("UTC") eq = gtaa_returns().dropna() grid = eq.index[eq.index >= tp.index[0]] cum = (1 + tp).cumprod() tpg = (cum.reindex(cum.index.union(grid)).ffill().reindex(grid)).pct_change() J = pd.concat({"c": tpg, "e": eq.reindex(grid)}, axis=1).dropna() return (wc * J["c"] + (1 - wc) * J["e"]).dropna() def main(): print("=" * 80) print(" RESOCONTO STRATEGIA — da $2.000, anno per anno") print("=" * 80) tpt = tp01_trades_per_year(); gtt = gtaa_trades_per_year() combo_tr = {y: tpt.get(y, 0) + gtt.get(y, 0) for y in set(tpt) | set(gtt)} # COMBO (la strategia deployata) yearly(combo_daily(), combo_tr, "COMBO TP01+GTAA 50/50 (deployabile, dal 2019)") # componenti tp = _tp01_returns(); tp.index = tp.index.tz_localize("UTC") if tp.index.tz is None else tp.index yearly(tp, tpt, "solo TP01 (crypto, Deribit)") g = gtaa_returns(); g10 = g[g.index >= (g.index[-1] - pd.Timedelta(days=3660))] yearly(g10, gtt, "solo GTAA (equity, IB) — ULTIMI 10 ANNI") if __name__ == "__main__": main()