Files
PythagorasGoal/scripts/research/combo_yearly_report.py
2026-06-23 12:37:23 +00:00

100 lines
4.3 KiB
Python

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