Tail Risk Hedging
Implement systematic tail risk hedging strategies for portfolio protection using out-of-the-money put options, VIX futures and volatility products, and dynamic convex put-spread strategies designed to provide positive convexity and explosive payoff protection during extreme left-tail market crash events.
Tail Risk Hedging — Portfolio Risk
Tail risk hedging reduces portfolio losses during extreme market events (the left tail of the return distribution) while preserving most of the upside. It accepts a small ongoing cost (the hedge premium) in exchange for insurance against catastrophic losses.
Common tail hedging approaches in crypto:
- Put options: buy downside protection directly (not modelled without an options chain)
- USDT allocation: hold a cash/stablecoin buffer — simple but misses upside
- Inverse position: short BTC perpetuals as a hedge — available on-chain without options
- Volatility scaling: reduce exposure dynamically when vol spikes (covered in vol_target_rebalancing)
- Defensive asset allocation: include assets that tend to be uncorrelated in crashes (gold, USD)
This notebook models the cash buffer and inverse hedge approaches:
- Constructs a base portfolio and measures its tail profile
- Adds a USDT cash buffer (X% of portfolio in stablecoin)
- Simulates a short BTC overlay hedge of varying sizes
- Analyses the cost of carry vs protection over time
- Computes tail metrics: CVaR, maximum drawdown, Omega ratio
- Exports hedge performance data
!pip install numpy pandas matplotlib seaborn scipy yfinance --quietimport numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
plt.rcParams['figure.figsize'] = (14, 5)
plt.rcParams['axes.spines.top'] = False
plt.rcParams['axes.spines.right'] = False
print('Imports ready.')Imports ready.
Configuration
HEDGE_SIZES defines a range of BTC short hedge ratios to test — 0.10 means 10% of portfolio value is in a BTC short. The short is assumed to have a carrying cost of FUNDING_RATE_ANNUAL (perpetuals funding). CASH_LEVELS tests stablecoin buffer sizes from 0% to 30%.
# ── Data Source Toggle ─────────────────────────────
USE_LIVE_DATA = False
TICKERS = ['BTC-USD','ETH-USD','SOL-USD','BNB-USD','AVAX-USD']
START_DATE = '2022-01-01'
END_DATE = '2024-12-31'
# ──────────────────────────────────────────────────
ASSETS = ['BTC','ETH','SOL','BNB','AVAX']
BASE_WEIGHTS = np.array([0.35, 0.25, 0.20, 0.12, 0.08])
HEDGE_SIZES = [0.0, 0.05, 0.10, 0.20, 0.30] # BTC short as fraction of portfolio
CASH_LEVELS = [0.0, 0.05, 0.10, 0.20, 0.30] # USDT buffer
FUNDING_RATE_ANNUAL = 0.10 # 10% annual cost to hold BTC short (perpetuals funding)
print('Config ready.')Config ready.
Data Acquisition
The synthetic path includes a significant drawdown period (rows 200–350) to ensure the hedge effectiveness is clearly visible. The BTC returns feed both the base portfolio and the short overlay PnL calculation.
if USE_LIVE_DATA:
import yfinance as yf
prices = yf.download(TICKERS, start=START_DATE, end=END_DATE)['Close']
prices.columns = ASSETS
prices.dropna(inplace=True)
returns = prices.pct_change().dropna()
print(f'Live data: {len(returns)} days')
else:
rng = np.random.default_rng(42)
n = 730
vols = np.array([0.65,0.75,1.20,0.70,1.10]) / np.sqrt(252)
mu = np.array([0.40,0.35,0.60,0.25,0.55]) / 252
# Crash regime mid-period
crash_mu = np.array([-0.005,-0.006,-0.009,-0.004,-0.008])
corr = np.array([[1,.85,.70,.75,.65],[.85,1,.75,.70,.68],
[.70,.75,1,.65,.72],[.75,.70,.65,1,.60],[.65,.68,.72,.60,1]])
cov = np.outer(vols,vols)*corr
L = np.linalg.cholesky(cov)
data = np.zeros((n,5))
for i in range(n):
daily_mu = crash_mu if 200 <= i <= 350 else mu
vol_mult = 2.0 if 200 <= i <= 350 else 1.0
data[i] = rng.standard_normal(5) @ (L*vol_mult).T + daily_mu
idx = pd.date_range('2022-01-01', periods=n, freq='B')
returns = pd.DataFrame(data, columns=ASSETS, index=idx)
print(f'Synthetic data with crash scenario: {n} days')
port_base = (returns * BASE_WEIGHTS).sum(axis=1)Synthetic data with crash scenario: 730 days
Hedge Strategies
For each hedge size: the daily portfolio return equals the base portfolio return minus the short BTC P&L minus daily funding cost. When BTC drops 5%, a 20% short overlay gains +1% (20% × 5%), partially offsetting the base portfolio loss. The daily funding cost is hedge_size × FUNDING_RATE_ANNUAL / 252 — a drag in normal markets.
daily_funding = FUNDING_RATE_ANNUAL / 252
hedge_results = {}
for hs in HEDGE_SIZES:
# Short BTC PnL: -hs * BTC_return (profit when BTC drops)
short_pnl = -hs * returns['BTC']
# Funding cost: daily drag
funding = hs * daily_funding
port_hedged = port_base + short_pnl - funding
hedge_results[f'Hedge {int(hs*100)}%'] = port_hedged
for cl in CASH_LEVELS:
# Cash buffer: reduce crypto exposure, replace with 0% return cash
adj_w = BASE_WEIGHTS * (1 - cl)
port_cash = (returns * adj_w).sum(axis=1)
hedge_results[f'Cash {int(cl*100)}%'] = port_cash
# Compute tail metrics for each
def cvar(r, level=0.95):
cutoff = np.percentile(r, (1-level)*100)
return r[r <= cutoff].mean()
def max_drawdown(r):
eq = (1+r).cumprod()
return ((eq - eq.cummax()) / eq.cummax()).min()
print(f'{"Strategy":20} | {"Ann Ret":8} | {"Ann Vol":8} | {"Sharpe":8} | {"95% CVaR":10} | {"Max DD":8}')
print('-'*75)
for name, rets in hedge_results.items():
ann_ret = rets.mean() * 252
ann_vol = rets.std() * np.sqrt(252)
sharpe = ann_ret / (ann_vol + 1e-9)
cv = cvar(rets.values)
mdd = max_drawdown(rets)
print(f'{name:20} | {ann_ret:8.1%} | {ann_vol:8.1%} | {sharpe:8.2f} | {cv:10.3%} | {mdd:8.1%}')Strategy | Ann Ret | Ann Vol | Sharpe | 95% CVaR | Max DD --------------------------------------------------------------------------- Hedge 0% | -67.1% | 94.7% | -0.71 | -13.972% | -98.6% Hedge 5% | -64.8% | 91.0% | -0.71 | -13.431% | -98.3% Hedge 10% | -62.4% | 87.3% | -0.72 | -12.894% | -98.0% Hedge 20% | -57.8% | 80.0% | -0.72 | -11.855% | -97.2% Hedge 30% | -53.2% | 72.9% | -0.73 | -10.846% | -96.2% Cash 0% | -67.1% | 94.7% | -0.71 | -13.972% | -98.6% Cash 5% | -63.7% | 90.0% | -0.71 | -13.273% | -98.2% Cash 10% | -60.4% | 85.3% | -0.71 | -12.574% | -97.7% Cash 20% | -53.7% | 75.8% | -0.71 | -11.177% | -96.2% Cash 30% | -47.0% | 66.3% | -0.71 | -9.780% | -94.0%
Visualisation
The left panel shows equity curves for the base (no hedge), 10% BTC short overlay, and 20% cash buffer. The right panel compares max drawdown vs annual return for every strategy — ideally you want strategies in the top-right (high return, low drawdown), so strategies that move up and to the right represent genuine improvements from hedging.
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
fig.suptitle('Tail Risk Hedging Analysis', fontsize=13, fontweight='bold')
ax1 = axes[0]
show = ['Hedge 0%', 'Hedge 10%', 'Hedge 20%', 'Cash 10%', 'Cash 20%']
colors = ['#9e9e9e','#1976d2','#0d47a1','#e65100','#bf360c']
for name, color in zip(show, colors):
eq = (1 + hedge_results[name]).cumprod()
ax1.plot(eq.index, eq, lw=1.5, color=color, label=name)
ax1.set_ylabel('Growth of $1'); ax1.legend(fontsize=8)
ax1.set_title('Equity Curves')
ax2 = axes[1]
for name, rets in hedge_results.items():
ann_ret = rets.mean() * 252
mdd = abs(max_drawdown(rets))
ax2.scatter(mdd, ann_ret, s=80, zorder=5)
ax2.annotate(name, (mdd, ann_ret), fontsize=7, xytext=(3,3), textcoords='offset points')
ax2.set_xlabel('Max Drawdown (abs)')
ax2.set_ylabel('Annual Return')
ax2.set_title('Risk-Return: Drawdown vs Return')
plt.tight_layout(); plt.show()Conclusion
This notebook demonstrated how to implement and analyze two tail risk hedging strategies: a cash buffer and an inverse BTC short overlay. By comparing various hedge sizes and cash levels, we observed their impact on portfolio returns, volatility, Sharpe ratio, CVaR, and maximum drawdown.
Key takeaways:
- Cash buffer reduces overall exposure to risky assets, providing a simpler form of protection, but potentially limiting upside.
- Inverse BTC short offers more direct downside protection against BTC price drops, but comes with a funding cost that can erode returns in normal or bullish markets.
- The analysis highlighted the trade-offs between the cost of hedging and the protection gained, especially during the synthetic crash period.
- Visualizations of equity curves and risk-return profiles (Max Drawdown vs Annual Return) provided a clear understanding of each strategy's performance characteristics.
Further analysis could include:
- Investigating the impact of different funding rate assumptions.
- Exploring dynamic hedging strategies based on market conditions (e.g., volatility).
- Incorporating other hedging instruments like options (if a suitable data source is available).
- Optimizing hedge sizes based on specific risk tolerances and return objectives.