research(tail-hedge): protezione DD combo (incl. opzioni) -> vince la guardia-drawdown
Goal: sleeve/overlay protettivo per il combo (TP01+GTAA), anni tipo 2022, valutare opzioni. Diagnosi: DD combo 8.4% e' grind-lento (2022 -4.4%), non crash -> il doppio trend gia' taglia i crash. Test (tail_hedge_lab.py): guardia-DD -4% -> MaxDD 8.4->5.8%, 2022 -4.4->-1.8%, Sharpe 1.48->1.38, CAGR 9.2%. Vol-target NON aiuta (2022 non e' vol-spike). OPZIONI (put/put-spread LONG su BTC/ETH, premio BS su DVOL): sempre-on ~50%/anno -> con budget 3%/y effetto ~nullo, e nel grind 2022 sanguinano; pagano solo nei crash secchi (stress -30%: put +25% netto). -> black-swan insurance cara, fuori bersaglio per il 2022. A leva: guard rende il 2x sopportabile (2022 -10.9%), il 3x resta margin-call. RACCOMANDAZIONE: aggiungere guardia-drawdown di portafoglio (no premio); opzioni solo eventuale micro-hedge black-swan. Costo onesto del guard: -2.1pp CAGR per dimezzare il MaxDD. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
|||||||
|
# 2026-06-23 — Tail-hedge / protezione DD del combo (incl. OPZIONI): vince la guardia-drawdown
|
||||||
|
|
||||||
|
## Obiettivo (goal utente)
|
||||||
|
Trovare uno sleeve/overlay da AGGIUNGERE al combo (TP01+GTAA) per proteggere il drawdown e gli anni
|
||||||
|
tipo 2022. Valutare anche le opzioni.
|
||||||
|
|
||||||
|
## Diagnosi del rischio (decisiva)
|
||||||
|
Il MaxDD del combo (1x) e' **8.4%** e il 2022 fu **-4.4%**: NON un crash, un **grind lento** (peggior
|
||||||
|
giorno -2.8%, peggior mese 2022 -1.6%). Il doppio trend (TP01+GTAA long-flat) gia' taglia i crash
|
||||||
|
veloci. Il tail residuo = (a) whipsaw da mercato choppy (2022), (b) gap/crash overnight LATENTE (TP01
|
||||||
|
non reagisce intraday, non nel campione storico), (c) la LEVA.
|
||||||
|
|
||||||
|
## Candidati testati (`tail_hedge_lab.py`)
|
||||||
|
| protezione | MaxDD | 2022 | Sharpe | CAGR |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| combo baseline | 8.4% | -4.4% | 1.48 | 11.3% |
|
||||||
|
| **+ guardia-DD -4%** | **5.8%** | **-1.8%** | 1.38 | 9.2% |
|
||||||
|
| + vol-target 5% | 8.4% | -5.9% | 1.46 | 8.0% |
|
||||||
|
| + opzioni (put/put-spread, budget 3%/y) | 8.4% | -4.4% | 1.48 | 11.3% |
|
||||||
|
|
||||||
|
### OPZIONI (valutate): NON adatte al 2022
|
||||||
|
- Put-spread/long-put LONG su indice 50/50 BTC/ETH (mirror di VRP01), premio BS su DVOL reale, payoff
|
||||||
|
sul path. Strike corretti (compra -0.30delta, vende -0.10delta).
|
||||||
|
- Sempre-on costa **~50%/anno** di premio -> con budget 3%/anno size ~0.06-0.10x = effetto ~nullo.
|
||||||
|
- Nel 2022 (grind, niente crash settimanali) **sanguinano** (scadono inutili) -> Δ2022 ~0.
|
||||||
|
- Pagano SOLO nei crash secchi: stress -30% overnight -> put paga **+25% netto**, put-spread +3.8%.
|
||||||
|
- Verdetto: assicurazione BLACK-SWAN cara e fuori-bersaglio per il grind. Utile solo come piccola
|
||||||
|
copertura del gap overnight latente, NON come fix del 2022.
|
||||||
|
|
||||||
|
### GUARDIA DRAWDOWN: centra il rischio
|
||||||
|
De-risk (esposizione 1.0->0.4) quando il DD da picco supera -4%, ri-rischia a -1.6%. Targetizza il
|
||||||
|
grind: MaxDD 8.4%->5.8%, 2022 -4.4%->-1.8%, ogni anno DD intra <=5.4%. Costo: Sharpe 1.48->1.38,
|
||||||
|
CAGR -2.1pp (de-risca sui cali, perde rimbalzo — prezzo onesto della protezione).
|
||||||
|
|
||||||
|
### A LEVA (dove il tail morde)
|
||||||
|
guard applicato pre-leva: 2x 2022 -15.6%->-10.9%, MaxDD 28%->24%; 3x resta MARGIN-CALL (DD 39%>=33%).
|
||||||
|
-> la protezione rende il 2x sopportabile; il 3x va evitato comunque.
|
||||||
|
|
||||||
|
## Raccomandazione
|
||||||
|
AGGIUNGERE una **guardia-drawdown a livello di portafoglio** al combo (overlay, niente premio):
|
||||||
|
e' la protezione che colpisce il rischio reale (grind/2022) a costo Sharpe minimo. Le opzioni NO come
|
||||||
|
fix del 2022; eventualmente una micro-allocazione deep-OTM come assicurazione black-swan separata.
|
||||||
|
Vol-target NON aiuta (il 2022 non e' uno spike di vol).
|
||||||
|
|
||||||
|
## Onesta'
|
||||||
|
- Il guard e' REATTIVO (de-risca dopo l'inizio del DD, restituisce un po' di rimbalzo) -> costa CAGR.
|
||||||
|
- Trade: -2.1pp CAGR per dimezzare il MaxDD e azzerare quasi il 2022. Sensato se la priorita' e' il DD.
|
||||||
|
- Parametri (-4% trigger) semplici; il meccanismo (non la soglia esatta) e' la sostanza.
|
||||||
|
Script: tail_hedge_lab.py.
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
"""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_d<n:
|
||||||
|
if gate_ivr is not None and (not np.isfinite(ivr[i]) or ivr[i]>gate_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 ddi<dd_trigger*0.4: on=True
|
||||||
|
expo[i]=1.0 if on else 0.4
|
||||||
|
return pd.Series(expo*r,index=combo.index)
|
||||||
|
|
||||||
|
|
||||||
|
def voltarget(combo, tv=0.07):
|
||||||
|
rv=combo.rolling(30,min_periods=15).std().shift(1)*ANN
|
||||||
|
sc=np.clip(np.nan_to_num(tv/rv.replace(0,np.nan).values,nan=0.0),0,1.5)
|
||||||
|
return pd.Series(combo.values*sc,index=combo.index)
|
||||||
|
|
||||||
|
|
||||||
|
def line(name, r, base=None):
|
||||||
|
r=r.dropna()
|
||||||
|
extra=""
|
||||||
|
if base is not None:
|
||||||
|
b=base.reindex(r.index).dropna(); r2=r.reindex(b.index)
|
||||||
|
extra=f" Δ2022 {(_yr(r2,2022)-_yr(b,2022))*100:+.1f}pp"
|
||||||
|
print(f" {name:34} Sh {_sh(r):>5.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()
|
||||||
Reference in New Issue
Block a user