3a3bdd5c7b
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
75 lines
3.6 KiB
Python
75 lines
3.6 KiB
Python
"""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()
|