f983cc2447
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
100 lines
4.3 KiB
Python
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()
|