"""Simulazione LEVA 1x/2x/3x su COMBO (TP01+GTAA) e TP01-solo, da $2k e $5k. Leva modellata onestamente: ritorno_giorno = L*r - (L-1)*financing/252 (costo del nozionale preso a prestito ~8%/anno blended: perp funding crypto + margin IB). MaxDD calcolato sul PERCORSO LEVATO REALE (non scalato: il compounding peggiora il DD oltre ×L). Check RUINA/margin-call: se l'equity tocca la soglia di liquidazione (perdita cumulata >= 1/L del picco -> margin call). CLAUDE.md: la leva NON e' la scorciatoia; raddoppia (e oltre) il drawdown. Caso base = 1x. """ 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" / "research")) from combo_yearly_report import combo_daily from src.portfolio.sleeves import _tp01_returns FIN = 0.08 # costo finanziamento annuo sul nozionale preso a prestito (perp funding + margin IB), blended def lever(ret: pd.Series, L: float) -> pd.Series: return L * ret - (L - 1) * FIN / 252.0 def analyze(ret: pd.Series, L: float, cap0: float): r = lever(ret.dropna().sort_index(), L) curve = cap0 * np.cumprod(1 + r.values) peak = np.maximum.accumulate(curve) dd = (peak - curve) / peak maxdd = float(np.max(dd)) # margin call: perdita dal picco >= 1/L (a leva L, un drawdown del sottostante di 1/L azzera il margine) ruin = bool(np.any(dd >= 1.0 / L - 1e-9)) if L > 1 else False yrs = (r.index[-1] - r.index[0]).days / 365.25 cagr = (curve[-1] / cap0) ** (1 / yrs) - 1 if yrs > 0 and curve[-1] > 0 else -1 sh = float(r.mean() / r.std() * np.sqrt(252)) if r.std() > 0 else 0 worst_y = min((np.prod(1 + r[r.index.year == y].values) - 1) for y in sorted(set(r.index.year))) return dict(L=L, final=float(curve[-1]), cagr=cagr, maxdd=maxdd, sharpe=sh, ruin=ruin, worst_y=float(worst_y), perday=(curve[-1] - cap0) / yrs / 365) def main(): print("=" * 92) print(" LEVA su COMBO vs TP01-solo — percorso reale (fin 8%/anno sul prestito), 2019-2026 (~7.3y)") print("=" * 92) strat = {"COMBO TP01+GTAA": combo_daily(), "TP01 solo (crypto)": _tp01_returns()} for nm, r in strat.items(): if r.index.tz is None: r.index = r.index.tz_localize("UTC") for cap0 in (2000.0, 5000.0): print(f"\n ##### capitale iniziale ${cap0:,.0f} #####") print(f" {'strategia':20}{'leva':>5}{'CAGR':>8}{'MaxDD':>8}{'Sharpe':>8}{'pegg.anno':>10}{'$/giorno':>10}{'eq fine':>12}{' RUINA?':>9}") for nm, r in strat.items(): for L in (1, 2, 3): a = analyze(r, L, cap0) flag = "MARGIN-CALL" if a["ruin"] else "ok" print(f" {nm:20}{L:>4}x{a['cagr']*100:>7.1f}%{a['maxdd']*100:>7.1f}%{a['sharpe']:>8.2f}" f"{a['worst_y']*100:>9.1f}%{('$'+format(a['perday'],',.0f')):>10}{('$'+format(a['final'],',.0f')):>12}{flag:>11}") # per-anno del COMBO a 2x e 3x da 2k (dettaglio) print(f"\n ##### COMBO per-anno a leva, da $2.000 #####") cd = combo_daily() for L in (2, 3): print(f"\n --- COMBO {L}x ---") print(f" {'anno':6}{'PnL %':>9}{'MaxDD %':>9}{'eq fine':>11}") eq = 2000.0; r = lever(cd, L) for y in sorted(set(r.index.year)): ry = r[r.index.year == y] if len(ry) < 5: continue eq0 = eq; curve = eq0 * np.cumprod(1 + ry.values); peak = np.maximum.accumulate(curve) ddp = float(np.max((peak - curve) / peak)); eq = float(curve[-1]) print(f" {y:<6}{(eq/eq0-1)*100:>+8.1f}%{ddp*100:>8.1f}%{('$'+format(eq,',.0f')):>11}") if __name__ == "__main__": main()