DEV Community

Cover image for Volatility Risk Premium in Python: The Formula Is One Line, the Conventions Are Everything
tomasz dobrowolski
tomasz dobrowolski

Posted on Originally published at flashalpha.com

Volatility Risk Premium in Python: The Formula Is One Line, the Conventions Are Everything

A VRP reading on a dashboard can be calculated correctly and still answer a different question from the one the reader thinks they are asking.

VRP is implied volatility minus realised volatility. One subtraction. But which implied vol, which realised vol, over which window, measured when, and in which units? Change any of those and you get a different number with a different meaning.

I build the volatility analytics at FlashAlpha. This is the engineering cut of our full VRP guide: the formula, the conventions that move it, a worked SPY example you can reproduce, and what an 11-symbol study from 2017 to 2026 says about using VRP as a signal.

The formula

VRPt=σIV,t−RV,RV=100252n−1∑i=1n(ri−rˉ)2,ri=ln⁡CiCi−1 \text{VRP}t = \sigma{IV,t} - RV, \qquad RV = 100\sqrt{\frac{252}{n-1}\sum_{i=1}^{n}(r_i - \bar r)^2}, \quad r_i = \ln\frac{C_i}{C_{i-1}}

The variance version squares both inputs first: σIV2−RV2\sigma_{IV}^2 - RV^2 .

import numpy as np

def realized_vol(closes, window=20):
    """Annualised close-to-close realised vol (%) over the last `window` returns."""
    r = np.diff(np.log(np.asarray(closes, float)))[-window:]
    return 100 * np.std(r, ddof=1) * np.sqrt(252)

def vrp(iv_30d_pct, rv_pct):
    """Volatility premium (vol points) and variance premium (vol points squared)."""
    return iv_30d_pct - rv_pct, iv_30d_pct ** 2 - rv_pct ** 2
Enter fullscreen mode Exit fullscreen mode

Units matter more than people expect. 20% IV minus 15% RV is 5 volatility points. In variance it is 20² − 15² = 175 vol points squared, or 0.0175 in decimal variance. Neither is a 5% investment return.

Four numbers called "VRP"

The same label covers four different quantities. They differ in units and in timing.

Known today (trailing RV) Known after the window (forward RV)
Volatility IV minus last 20 sessions' RV. SPY, 1 Oct 2025: +6.72 vol pts IV minus next 21 sessions' RV. SPY, 1 Oct 2025: +0.34 vol pts
Variance IV² minus trailing RV². SPY: 130.0 vol pts² IV² minus forward RV². SPY: 8.6 vol pts²

Dashboards and screeners show the trailing cells. Research on whether the premium "pays" uses the forward cells. Most arguments about VRP are two people standing in different cells.

The academic variance risk premium is a third thing again. It uses option-strip implied variance (a synthesised variance-swap rate in Carr & Wu 2009, VIX-implied variance in Bollerslev, Tauchen & Zhou 2009) rather than squared ATM IV, so the ATM numbers here are observable proxies, not that quantity.

Worked example: SPY at the 1 October 2025 close

SPY had a quiet September. Then on 10 October it fell 2.7%.

  1. Implied vol. 30-day constant-maturity ATM IV from the daily SVI fit: 13.03%.
  2. Trailing RV. 20 daily log returns of 16:00 closing midpoints, 4 Sep to 1 Oct. Sample std dev 0.003977, so RV = 0.003977 × √252 × 100 = 6.31%.
  3. Trailing premium. 13.03 − 6.31 = +6.72 vol points.
  4. Forward RV. Next 21 sessions (2 to 30 Oct), each day's variance the sum of squared 5-minute returns plus the squared overnight return. Mean daily variance 0.00006398, annualised 12.70%.
  5. Forward premium. 13.03 − 12.70 = +0.34 vol points, from unrounded inputs (the displayed two-decimal values give 0.33).
# trailing_closes: the 21 closes from 3 Sep to 1 Oct 2025 (worked-example CSV on flashalpha.com)
# realized_vol(trailing_closes) -> 6.31
# vrp(13.03, 6.31) -> (+6.72, 130.0)
# forward premium from unrounded inputs: +0.34 vol points, 8.6 vol points squared
Enter fullscreen mode Exit fullscreen mode

Same implied vol, two windows. The +6.72 mostly described a calm past. It did not survive the volatility that arrived in October.

What 23,104 symbol-days show

Sample: SPY, QQQ, IWM, GLD, TLT, XLF, XLK, EEM, AAPL, NVDA and TSLA, March 2017 to October 2026. 30-calendar-day ATM IV against 20-session trailing and 21-session forward RV, the latter from a 5-minute benchmark.

  • The premium is common. IV exceeded forward RV on 65% of symbol-days. Median forward premium +1.4 vol points, mean only +0.22. The pooled mean's calendar-block bootstrap 95% interval is −1.48 to +1.49.
  • In variance units the average flips. The ATM variance difference was negative on average for 10 of 11 symbols. SPY's worst reading was −8,270 vol pts² on 26 Feb 2020. The sign is sensitive to 2020: excluding it leaves 4 of 11 negative.
  • The trailing spread did not forecast the forward spread. Correlation between them ran from −0.11 to +0.14. Out-of-sample R² against an expanding-mean benchmark was below zero for all 11 symbols.
  • IV beats trailing RV as a predictor of future RV on marginal correlation for 11 of 11 symbols. Adding trailing RV to an IV-only forecast lowered MSE on 3 of 11, with no improvement statistically clear of zero.
  • A simple high-reading rule did not survive correction. Top-third trailing readings (ranked against the prior 252 sessions) beat bottom-third on 8 of 11 symbols, significantly on 1, and on 0 after Holm correction.
  • It depends on the year. The mean forward premium was negative in 2018, 2020, 2022, 2024 and 2025.

None of this is an options backtest. These are overlapping measurement windows, not trades, and they test specific models rather than every possible VRP rule.

Why your platform's VRP differs from mine

Two platforms can both be right and still disagree. Here is how much the realised-vol choice alone moves the pooled trailing premium:

Trailing RV Mean premium Positive days Median distance from CC20
Close-to-close, 5 sessions +1.84 66% 4.57
Close-to-close, 10 sessions +0.92 64% 2.70
Close-to-close, 20 sessions +0.33 61% 0.00
Close-to-close, 30 sessions +0.07 60% 1.64
Yang-Zhang, 20 sessions +0.30 63% 1.72

Other conventions that change the number:

  • IV tenor. One-week vs 30-day ATM vol on SPY, 1 Oct 2025: 10.03% vs 13.03%.
  • Whether today's close is in the window. 6.31% through 1 Oct vs 6.39% through 30 Sep.
  • Forward RV from closes or intraday data. Median difference 1.65 points over 21 sessions.
  • Annualisation. 365 instead of 252 raises RV by √(365/252) − 1, about 20%.
  • Calendar vs trading days. 21 sessions approximates 30 calendar days. A field called rv_30d may mean 30 trading returns.

Rule of thumb: reconcile estimator, window and timestamp before comparing numbers. A convention that produces a bigger spread has not produced a better forecast.

Pulling VRP from an API

On FlashAlpha's Alpha plan, /v1/vrp/{symbol} returns the trailing measures in one call, and the historical API replays the same response for any past minute, which is how you test a VRP rule without lookahead.

import httpx

H = {"X-Api-Key": "YOUR_API_KEY"}
live = httpx.get("https://lab.flashalpha.com/v1/vrp/SPY", headers=H, timeout=30).json()
past = httpx.get("https://historical.flashalpha.com/v1/vrp/SPY",
                 params={"at": "2025-10-01T15:55:00"}, headers=H, timeout=60).json()
for d in (live, past):
    v = d["vrp"]
    t30 = next(t for t in d["term_vrp"] if t["dte"] == 30)
    print(d["as_of"], "vrp_20d", v["vrp_20d"], "| 30-day bucket", t30["iv"], "-", t30["rv"], "=", t30["vrp"])
Enter fullscreen mode Exit fullscreen mode

Field conventions, so you can reconcile against your own numbers:

  • vrp.atm_iv: ATM IV of the first expiry at least 7 days out, the mean of the four nearest OTM contracts. Not a 30-day measure.
  • vrp.rv_5d to rv_30d: close-to-close, sample std dev, × √252. Replay uses closes through the previous session.
  • vrp.vrp_20d: atm_iv − rv_20d. At 15:55 ET on 1 Oct 2025: 10.03 − 6.39 = 3.64.
  • variance_risk_premium: atm_iv² − rv_20d² in decimal variance (0.005977 in the same replay).
  • term_vrp 30 DTE: roughly 30-calendar-day IV minus rv_30d from 30 trading returns (13.25 − 8.16 = 5.09). The durations differ, so state both.
  • z_score and percentile: today's premium against its own history. A description, not a forecast.

Docs: live VRP and historical VRP.

Takeaways

  1. Always state the IV measure and tenor, the RV estimator and window, the timing, and the units.
  2. Treat trailing VRP as a measurement of current conditions. A wide reading can just mean the last month was quiet.
  3. If you want a timing rule, test it point-in-time, then test the actual strategy with real contracts, fills and costs. A high reading does not do those steps for you.

The full guide has an interactive calculator, every table, the sensitivity checks and the literature comparison. The methods, aggregate results and code are public in the v1.2 evidence record.

Originally published at flashalpha.com.

Top comments (0)

⌂ 换网址