A Python research pipeline that documents the variance risk premium (VRP) on SPX / SPY using publicly available VIX and price data (2010-2024), constructs a systematic short-vol signal with a regime-based tail-risk filter, and honestly measures both its predictive content and the gap between signal quality and tradable P&L.
Thesis. Implied vol (VIX) systematically exceeds subsequently-realized 21-day vol — the empirical variance risk premium. The signal has predictive content for forward RV, but a naïve raw-VIX P&L proxy is dominated by single-day gap losses, which is itself the economically interesting finding: short-vol is insurance, and the premium is compensation for the tail.
This is Phase 1 of a three-phase build; Phase 2 upgrades to real SPX option data and delta-hedged P&L decomposition. See § Roadmap.
Empirical VRP facts (2010-2024, 3,480 valid days after warm-up):
| Fact | Value |
|---|---|
| Mean forward VRP (IV − fwd 21d RV) | +3.57 vol pts |
| Fraction of days with forward VRP > 0 | 83.5% |
| Mean contemporaneous VRP (IV − trailing RV) | +3.57 vol pts |
Consistent with Bollerslev, Tauchen & Zhou (2009): the premium is real, positive, and economically sized.
Backtest headline metrics (signal-quality proxy — see caveats):
| Metric | Value |
|---|---|
| Total return | -616.37% |
| Annualized return | -41.18% |
| Annualized vol | 89.85% |
| Sharpe ratio | -0.46 |
| Max drawdown | -717.01% |
| Hit rate (in-market) | 52.09% |
| Number of trades | 48 |
| Avg holding (days) | 25.9 |
| % time in market | 32.98% |
| Info coefficient | 0.068 |
Key reads:
- Hit rate 52% — the classic short-vol profile: roughly half the in-market days make money. The arithmetic mean is dominated by a handful of gap days.
- IC = +0.068 — small but positive; VRP_z has real predictive content for forward vol direction (consistent with published VRP literature).
- Negative headline Sharpe — comes entirely from the raw-VIX P&L proxy over-weighting single-day spikes (Feb 5 2018: spot VIX +115.6% in one day, so a -1 short-vol unit takes a -115.6% daily "return"). A tradable instrument (VXX, VIX futures) moves much less per day and collects roll carry; see § Limitations.
All five plots are reproducible by running notebooks/analysis.ipynb.
The IV line sits above RV most of the time. The gap is the premium.
VRP with 252-day rolling mean ± 1σ. Red-shaded strips are days where VIX ≥ 30 forced the position flat.
Arithmetic cumulative P&L of the short-vol signal proxy. Named drops annotated.
Distance below running-max equity. Visualizes the gap-risk story.
Hexbin of VRP_z vs change in RV over the next 21 days, with an OLS best-fit line. Negative slope ⇒ high VRP_z is followed by falling RV — the economic content of the premium.
- SPY (split/dividend-adjusted close) and ^VIX from Yahoo Finance
via
yfinance, cached todata/raw.parquet. - Period: 2010-01-01 to 2024-12-31 (covers 2018 Volmageddon + 2020 COVID).
- Daily frequency, business-day aligned, inner-joined on dates where both series print.
log_ret = ln(SPY_t / SPY_{t-1})RV_t = std(log_ret_{t-20:t}) · √252(21-day trailing, annualized)IV_t = VIX_t / 100(vol-point to decimal)VRP_t = IV_t − RV_t(contemporaneous, real-time)VRP_z_t = (VRP_t − roll_mean_{252}) / roll_std_{252}fwd_RV_t = std(log_ret_{t+1:t+21}) · √252(forward, ex-post eval only)fwd_VRP_t = IV_t − fwd_RV_t(the academic VRP)
Stateful rule, decision at close of day t, held from open of day t+1:
if VIX_t ≥ 30 → position = 0 (regime kill switch)
elif VRP_z_t > +1.0 → position = -1 (short vol)
elif VRP_z_t < -0.5 → position = 0 (flat)
else → carry previous position
Single 1-day execution lag is applied as a .shift(1) on the position
series before multiplying by returns — the only place look-ahead could sneak
in, tightly guarded and commented in code.
daily_pnl_t = position_eff_t · vix_ret_t
where position_eff_t = position_{t-1} (lag) and vix_ret_t = VIX.pct_change().
A short (-1) position LOSES when VIX rises. This is the signs-correct form;
the spec text uses position · (-vix_ret) which double-negates with the
-1 encoding — we flag this directly in the code comments.
See compute_metrics in src/vrp/backtest.py for exact definitions.
Sharpe is annualized via √252. The IC is
corr(VRP_z_t, −VIX.pct_change(21)_{t+21}) — the degree to which high VRP
predicts subsequent VIX declines.
The two canonical short-vol disaster windows:
| Window | Short days | Flat days | Max VIX | Total P&L | Worst day |
|---|---|---|---|---|---|
| 2018 Volmageddon (Jan 15 – Mar 15) | 7 | 35 | 37.3 | -155.2% | -115.6% |
| COVID crash (Feb 15 – Apr 30) | 1 | 51 | 82.7 | -42.1% | -42.1% |
Read: The regime filter worked partially. It held us flat for 83% and 98% of each window respectively, but could not prevent the single-day gap losses — by the time VIX closes above 30, damage is done intraday. This is the defining weakness of reactive regime filters and is the primary motivation for the Phase 2 upgrades.
Listed explicitly, because this is where interview questions live.
- VIX as IV proxy. VIX is a model-free, variance-swap-style 30-day measure. True ATM SPX implied vol differs by the volatility smile and term structure — our VRP is therefore a variance-risk-swap-ish premium, not a pure Black-Scholes IV − RV gap. Material but not directional.
- Raw VIX return as P&L proxy. VIX is not tradable. A real short-vol position would use VIX futures (roll carry applies) or VXX (an inverse- VIX ETF with roll decay). Both have smaller daily moves than spot VIX, so the proxy over-weights tail days and the headline Sharpe underestimates a tradable version of the signal. This is the single biggest simplification — Phase 2 replaces it.
- No transaction costs, no slippage, no financing, no margin.
- No position sizing / vol targeting. Position is {0, −1}. A real strategy would scale by inverse-vol or target a fixed ex-ante vol.
- Single asset. SPX only. A real vol book diversifies across NDX, RUT, and single names.
- Regime filter is reactive. Threshold on close-of-day VIX; cannot anticipate an intraday spike. Phase 2 explores HMM / vol-of-vol predictive filters.
- Look-ahead hygiene. All decisions at time
tuse only information available at close oft; execution lag of 1 day applied before P&L. Thefwd_VRPcolumn is tagged and used only for ex-post evaluation, never for signal generation.
# 1. Clone and enter
git clone https://github.com/rohankrgupta/Variance-Risk-Premium.git
cd Variance-Risk-Premium
# 2. Env
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# 3. Run the notebook end-to-end (fetches + caches data on first run)
jupyter nbconvert --to notebook --execute notebooks/analysis.ipynb --output analysis.ipynbAll plots land in figures/; metrics land in figures/metrics.md.
src/vrp/
data.py # yfinance fetch + parquet cache + sanity checks
signals.py # RV, IV, VRP, z-score, forward RV, build_features
backtest.py # positions, P&L sim with lag, metrics dataclass
notebooks/
analysis.ipynb # thin reporting wrapper around the package
data/
raw.parquet # cached SPY+VIX (gitignored; regenerable)
figures/ # png outputs + metrics.md
requirements.txt
README.md
- Phase 2 (20-30 hrs): Replace VIX with a 30-day constant-maturity ATM IV from the SPX option chain; add a delta-hedged short-straddle simulation with theta / gamma / vega P&L attribution.
- Phase 3 (40+ hrs, optional, one of): dispersion trading (index vs single-name IV), SVI surface fitting + arb, or a C++ vol-engine re-implementation of the Phase 2 hot path.
- Bollerslev, T., Tauchen, G., & Zhou, H. (2009). Expected Stock Returns and Variance Risk Premia. Review of Financial Studies, 22(11), 4463–4492.
- CBOE. VIX White Paper: The CBOE Volatility Index (VIX)®. Chicago Board Options Exchange.
- Sinclair, E. (2013). Volatility Trading (2nd ed.), Chapter 1. Wiley.
Variance Risk Premium Study — SPX (2010–2024) · Apr 2026
- Built a Python research pipeline (yfinance, pandas, matplotlib) isolating the variance risk premium on SPX; documented a +3.6 vol-point mean with 83.5% positive-day frequency and information coefficient +0.07 against forward realized vol change, consistent with Bollerslev–Tauchen–Zhou.
- Implemented a z-score signal with VIX-level regime kill switch; backtested across 2018 Volmageddon and 2020 COVID stress windows, diagnosing that reactive filters neutralize trend regimes but cannot prevent single-day gap losses — motivating the Phase 2 upgrade to real option-chain IV and delta-hedged P&L attribution.




