"""TAIL-HEDGE LAB — proteggere il DD del combo (TP01+GTAA) e gli anni tipo 2022. Valuta OPZIONI. Il DD del combo (8.4%) e' grind-lento (2022), non crash. Le PUT proteggono i CRASH (rischio latente gap/overnight + leva), non il grind. Quindi confronto: (A) OPZIONI: put-spread LONG su indice 50/50 BTC/ETH (mirror di VRP01), always-on vs GATED (hold l'hedge solo quando esposti al trend / quando IV economica). Premio BS su DVOL reale, payoff sul path. Misura il BLEED nei calmi + il payoff nei crash (+ stress sintetico -30% overnight). (B) GUARDIA DRAWDOWN: de-risk il combo quando il DD da picco supera X% (o vol spike). (C) VOL-TARGET: cappa la vol del combo a un livello piu' basso. Metriche: Sharpe, MaxDD, anno 2022, drag medio nei calmi, e payoff a uno shock -30%. """ 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.data.downloader import load_data from src.strategies.trend_portfolio import resample_1d from src.portfolio.sleeves import _bs_put, _strike_from_delta, _HL_DIR, _tp01_returns from scipy.stats import norm ANN = np.sqrt(252.0) def _sh(r): r = np.asarray(pd.Series(r).dropna(), float); return float(np.mean(r)/np.std(r)*ANN) if len(r)>5 and np.std(r)>0 else 0.0 def _dd(r): eq=np.cumprod(1+np.asarray(r,float)); pk=np.maximum.accumulate(eq); return float(np.max((pk-eq)/pk)) if len(eq) else 0.0 def _yr(r,y): return float(np.prod(1+r[r.index.year==y].values)-1) if (r.index.year==y).any() else 0.0 def crypto_index(): """Indice 50/50 BTC/ETH (prezzo daily) + DVOL medio, per il pricing dell'hedge.""" px={}; dv={} for a in ("BTC","ETH"): df=resample_1d(load_data(a,"1h")); s=pd.Series(df["close"].values.astype(float),index=pd.to_datetime(df["datetime"])) if s.index.tz is None: s.index=s.index.tz_localize("UTC") px[a]=s d=pd.read_parquet(_HL_DIR/f"dvol_{a.lower()}.parquet") dv[a]=pd.Series(d["close"].values.astype(float),index=pd.to_datetime(d["timestamp"],unit="ms",utc=True)) P=pd.concat(px,axis=1,join="inner").dropna(); D=pd.concat(dv,axis=1,join="inner").dropna() idx_ret=0.5*P["BTC"].pct_change()+0.5*P["ETH"].pct_change() dvol=(0.5*D["BTC"]+0.5*D["ETH"]).reindex(P.index).ffill()/100.0 return idx_ret.dropna(), dvol def hedge_overlay(idx_ret, dvol, kind="spread", buy_delta=-0.30, sell_delta=-0.10, tenor_d=7, gate_ivr=None): """P&L settimanale per unita' di nozionale hedge di una protezione LONG comprata ogni settimana sull'indice. kind='spread' = put-spread debit (compra strike vicino buy_delta, vende deep-OTM sell_delta); kind='put' = long put nuda (buy_delta). gate_ivr: compra solo se IV-rank < soglia. Ritorna (pnl_daily, premio_annuo_per_unita).""" idx=idx_ret.index; r=idx_ret.values; sig=dvol.reindex(idx).ffill().values S=np.cumprod(1+r); n=len(S) ivr=pd.Series(sig,index=idx).rolling(252,min_periods=60).apply(lambda x:(x[-1]>=x).mean(),raw=True).values out=np.zeros(n); prem_tot=0.0; i=30; T=tenor_d/365.25 while i+tenor_dgate_ivr): i+=tenor_d; continue S0=S[i]; vol=sig[i]; ST=S[i+tenor_d] Kb=_strike_from_delta(S0,T,vol,buy_delta) if kind=="put": prem=_bs_put(S0,Kb,T,vol); payoff=max(Kb-ST,0) else: Ks=_strike_from_delta(S0,T,vol,sell_delta) # Kb>Ks (compra vicino, vende lontano) prem=_bs_put(S0,Kb,T,vol)-_bs_put(S0,Ks,T,vol); payoff=max(Kb-ST,0)-max(Ks-ST,0) fee=0.0005*S0 out[i+tenor_d]=(payoff-prem-fee)/S0; prem_tot+=(prem+fee)/S0 i+=tenor_d yrs=(idx[-1]-idx[0]).days/365.25 return pd.Series(out,index=idx), (prem_tot/yrs if yrs>0 else 0.0) def dd_guard(combo, dd_trigger=0.05, look=20): """De-risk: se il DD da picco supera dd_trigger -> esposizione 0.5 finche' non recupera meta'.""" r=combo.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 ddi5.2f} MaxDD {_dd(r.values)*100:>4.1f}% 2022 {_yr(r,2022)*100:>+5.1f}% " f"CAGR {((np.prod(1+r.values))**(252/len(r))-1)*100:>+5.1f}%{extra}") def main(): print("="*100); print(" TAIL-HEDGE LAB — proteggere DD/2022 del combo (TP01+GTAA)"); print("="*100) combo=combo_daily() idx_ret,dvol=crypto_index() print("\n BASELINE") line("combo (1x)", combo) print("\n (A) OPZIONI — protezione LONG su crypto, sovrapposta al combo (size = spendi ~3%/anno)") for kind,gate,lbl in [("spread",None,"put-spread sempre"),("spread",0.4,"put-spread gate IVR<0.4"), ("put",None,"long put -0.30 sempre"),("put",0.4,"long put gate IVR<0.4")]: ov,annprem=hedge_overlay(idx_ret,dvol,kind=kind,gate_ivr=gate) ov=ov.reindex(combo.index).fillna(0.0) size=0.03/annprem if annprem>0 else 0.0 # nozionale hedge per ~3%/anno di premio line(f"+{lbl} (~3%/y, size {size:.2f}x)", combo+size*ov, base=combo) print(" (stress -30% overnight dell'indice crypto, per unita' di nozionale hedge):") S0=1.0; vol=float(dvol.iloc[-1]); T=7/365.25 for kind in ("spread","put"): Kb=_strike_from_delta(S0,T,vol,-0.30) if kind=="put": prem=_bs_put(S0,Kb,T,vol); pay=max(Kb-0.7,0) else: Ks=_strike_from_delta(S0,T,vol,-0.10); prem=_bs_put(S0,Kb,T,vol)-_bs_put(S0,Ks,T,vol); pay=max(Kb-0.7,0)-max(Ks-0.7,0) print(f" {kind:7}: premio {prem*100:.2f}% -> payoff a -30% {pay*100:.2f}% (netto {(pay-prem)*100:+.2f}%)") print("\n (B) GUARDIA DRAWDOWN (de-risk a -X% dal picco)") for t in (0.04,0.06): line(f"+dd-guard {t*100:.0f}%", dd_guard(combo,t), base=combo) print("\n (C) VOL-TARGET del combo") for tv in (0.05,0.07): line(f"+vol-target {tv*100:.0f}%", voltarget(combo,tv), base=combo) if __name__ == "__main__": main()