research(report): resoconto PROTETTO (soft-guard DD -4%) anno-per-anno -> combo + TP01 + GTAA singoli
Report che mette combo (TP01+GTAA 50/50), TP01 e GTAA sulla stessa griglia giorni-di-borsa (esposizione 1x, come dentro al combo), applica la guardia-DD -4% a ciascuna serie e tira fuori per anno: NL (net liquidation da $2000), DD intra-anno, rendimento, Sharpe + riga TOT con CAGR. Combo protetto: CAGR +9.1% / DD 5.8% / Sh 1.38 (2022 -1.8%); baseline +11.3% / 8.4% / 1.48. Aggiunto data/paper_combo/ al .gitignore (stato paper runtime, come gli altri paper dir). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -65,3 +65,4 @@ scripts/research/blind/leaderboard.json
|
|||||||
|
|
||||||
# forward-monitor runtime state (regenerable, forward-only)
|
# forward-monitor runtime state (regenerable, forward-only)
|
||||||
data/paper_prevday/
|
data/paper_prevday/
|
||||||
|
data/paper_combo/
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
"""Resoconto anno-per-anno CON PROTEZIONE (soft-guard DD -4%) — combo e singoli.
|
||||||
|
|
||||||
|
Mette combo (TP01+GTAA 50/50), TP01 e GTAA sulla STESSA griglia giorni-di-borsa (come dentro al
|
||||||
|
combo), applica la guardia-DD -4% a ciascuna serie (de-risk 1.0->0.4 a -4% dal picco, ri-rischia a
|
||||||
|
-1.6%), e per ogni anno riporta: NL (net liquidation da $2000), DD intra-anno, rendimento (=CAGR
|
||||||
|
1y), Sharpe. Riga TOT con CAGR e Sharpe complessivi.
|
||||||
|
"""
|
||||||
|
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))
|
||||||
|
from src.portfolio.sleeves import _tp01_returns
|
||||||
|
from src.portfolio.gtaa import gtaa_returns
|
||||||
|
|
||||||
|
INITIAL = 2000.0
|
||||||
|
ANN = np.sqrt(252.0)
|
||||||
|
DD_TRIG = 0.04
|
||||||
|
|
||||||
|
|
||||||
|
def dd_guard(r, dd_trigger=DD_TRIG):
|
||||||
|
"""De-risk: esposizione 1.0->0.4 se DD da picco > dd_trigger; ri-rischia a dd_trigger*0.4."""
|
||||||
|
r = r.values; n = len(r); eq = np.cumprod(1 + r); pk = np.maximum.accumulate(eq)
|
||||||
|
expo = np.ones(n); on = True
|
||||||
|
for i in range(1, n):
|
||||||
|
ddi = (pk[i - 1] - eq[i - 1]) / pk[i - 1]
|
||||||
|
if ddi > dd_trigger: on = False
|
||||||
|
if ddi < dd_trigger * 0.4: on = True
|
||||||
|
expo[i] = 1.0 if on else 0.4
|
||||||
|
return pd.Series(expo * r, index=_idx) # set below
|
||||||
|
|
||||||
|
|
||||||
|
def legs_on_grid(wc=0.5):
|
||||||
|
"""TP01(crypto, compoundato sul grid) e GTAA(equity) sulla stessa griglia giorni-di-borsa."""
|
||||||
|
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()
|
||||||
|
combo = wc * J["c"] + (1 - wc) * J["e"]
|
||||||
|
return combo, J["c"], J["e"]
|
||||||
|
|
||||||
|
|
||||||
|
def sh(r): r = r.dropna().values; return float(np.mean(r) / np.std(r) * ANN) if len(r) > 5 and np.std(r) > 0 else 0.0
|
||||||
|
def maxdd(curve): pk = np.maximum.accumulate(curve); return float(np.max((pk - curve) / pk)) if len(curve) else 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def yearly(ret, label):
|
||||||
|
ret = ret.dropna().sort_index()
|
||||||
|
print(f"\n ===== {label} (guardia-DD -4%) =====")
|
||||||
|
print(f" {'anno':6}{'NL inizio':>11}{'NL fine':>11}{'rend%':>9}{'DD%':>8}{'Sharpe':>9}")
|
||||||
|
eq = INITIAL
|
||||||
|
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)
|
||||||
|
eq = float(curve[-1])
|
||||||
|
print(f" {y:<6}{eq0:>11,.0f}{eq:>11,.0f}{(eq/eq0-1)*100:>+8.1f}%{maxdd(curve)*100:>7.1f}%{sh(r):>9.2f}")
|
||||||
|
yrs = (ret.index[-1] - ret.index[0]).days / 365.25
|
||||||
|
cagr = (eq / INITIAL) ** (1 / yrs) - 1 if yrs > 0 else 0
|
||||||
|
full_curve = INITIAL * np.cumprod(1 + ret.values)
|
||||||
|
print(f" {'TOT':<6}{INITIAL:>11,.0f}{eq:>11,.0f}{(eq/INITIAL-1)*100:>+8.1f}%{maxdd(full_curve)*100:>7.1f}%{sh(ret):>9.2f}"
|
||||||
|
f" | CAGR {cagr*100:+.1f}% ({yrs:.1f}y)")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
global _idx
|
||||||
|
print("=" * 78)
|
||||||
|
print(" RESOCONTO PROTETTO (soft-guard DD -4%) — da $2.000, anno per anno")
|
||||||
|
print(" Tutte e tre sulla griglia giorni-di-borsa del combo (dal 2019), esposizione 1x.")
|
||||||
|
print("=" * 78)
|
||||||
|
combo, tp, g = legs_on_grid()
|
||||||
|
for ret, lbl in [(combo, "COMBO TP01+GTAA 50/50"), (tp, "solo TP01 (crypto)"), (g, "solo GTAA (equity)")]:
|
||||||
|
_idx = ret.index
|
||||||
|
yearly(dd_guard(ret), lbl)
|
||||||
|
# confronto NON protetto (baseline) in coda, una riga TOT per riferimento
|
||||||
|
print("\n --- riferimento NON protetto (baseline, TOT) ---")
|
||||||
|
for ret, lbl in [(combo, "COMBO"), (tp, "TP01"), (g, "GTAA")]:
|
||||||
|
yrs = (ret.index[-1] - ret.index[0]).days / 365.25
|
||||||
|
eqf = INITIAL * np.prod(1 + ret.values)
|
||||||
|
print(f" {lbl:6} CAGR {((eqf/INITIAL)**(1/yrs)-1)*100:>+5.1f}% DD {maxdd(INITIAL*np.cumprod(1+ret.values))*100:>4.1f}% Sharpe {sh(ret):.2f}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user