The Volatility Risk Premium, Measured
The volatility risk premium is implied volatility minus the realized volatility that follows. Here it is measured on SPY options, by tenor and strike.
The volatility risk premium is the gap between the volatility an option is priced at and the volatility the underlying delivers over the life of that option. On SPY options from Aug 2021 onward, that gap is positive in most windows and sharply negative in a few. This page measures it rather than describes it: the full distribution, the shape across tenors and strikes, and the size of the losing tail.
What is the volatility risk premium?
Two numbers sit on either side of the definition.
Implied volatility is the volatility input that makes an option pricing model return the option's current market price. It is quoted as an annualized percentage, and it is forward looking by construction: it is what the market charges today for the next stretch of movement.
Realized volatility is what the underlying actually did. Take the daily log returns over a window, take their root mean square, and scale by the square root of 252 trading days. The result is an annualized percentage directly comparable with the implied number. Our guide to realized versus implied volatility walks through that calculation one step at a time.
The premium is the first minus the second, with one condition that does most of the work: the realized number must cover the window that followed the implied reading, not the window before it. An option quoted at a 30 day implied volatility on a Tuesday is a price for the next 30 days. Scoring it against the previous 30 days measures something else.
The premium is a transfer price. An option seller holds the exposure to a gap move for the length of the contract, and the buyer pays an annualized volatility number above the one that usually arrives. Whether that trade works out is a question about the distribution, not the average, which is where the rest of this page goes.
How big is the volatility risk premium on index options?
The panel below takes every trading day in the sample, averages the implied volatility of near the money SPY contracts with 20 to 45 days to expiry, then measures what SPY realized over the 21 sessions that followed. Both volatility lines are annualized percentages. The third line is their difference, in volatility points.
| month | month_label | implied_vol_pct | realized_vol_pct | premium_pct |
|---|---|---|---|---|
| 2021-08-01 | Aug 2021 | 13.33 | 9.57 | 3.76 |
| 2021-09-01 | Sep 2021 | 15.1 | 14.51 | 0.58 |
| 2021-10-01 | Oct 2021 | 14.78 | 8.84 | 5.94 |
| 2021-11-01 | Nov 2021 | 14.59 | 17.36 | -2.77 |
| 2021-12-01 | Dec 2021 | 16.77 | 15.85 | 0.92 |
| 2022-01-01 | Jan 2022 | 19.38 | 21.31 | -1.93 |
| 2022-02-01 | Feb 2022 | 21.85 | 24.14 | -2.29 |
| 2022-03-01 | Mar 2022 | 22.75 | 19.78 | 2.97 |
| 2022-04-01 | Apr 2022 | 20.38 | 31.75 | -11.36 |
| 2022-05-01 | May 2022 | 26.65 | 29.52 | -2.87 |
| 2022-06-01 | Jun 2022 | 25.52 | 23.68 | 1.84 |
| 2022-07-01 | Jul 2022 | 23.01 | 18.98 | 4.03 |
| 2022-08-01 | Aug 2022 | 20.08 | 22.96 | -2.88 |
| 2022-09-01 | Sep 2022 | 25.1 | 27.81 | -2.72 |
| 2022-10-01 | Oct 2022 | 27.84 | 26.83 | 1.01 |
| 2022-11-01 | Nov 2022 | 22.33 | 21.88 | 0.45 |
| 2022-12-01 | Dec 2022 | 20.83 | 17.9 | 2.93 |
| 2023-01-01 | Jan 2023 | 19.37 | 16.36 | 3.01 |
| 2023-02-01 | Feb 2023 | 18.92 | 16.87 | 2.05 |
| 2023-03-01 | Mar 2023 | 20.06 | 14.21 | 5.85 |
The exact SQL behind every number
WITH
closes AS
(
SELECT
date,
toFloat64(any(close)) AS px
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2021-06-01'
GROUP BY date
),
daily_returns AS
(
SELECT
date,
pow(log(px / lagInFrame(px) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)), 2) AS r2
FROM closes
),
forward_rv AS
(
SELECT
date,
100 * sqrt(252 * avg(r2) OVER (ORDER BY date ROWS BETWEEN 1 FOLLOWING AND 21 FOLLOWING)) AS rv_pct,
count(r2) OVER (ORDER BY date ROWS BETWEEN 1 FOLLOWING AND 21 FOLLOWING) AS sessions_ahead
FROM daily_returns
WHERE isFinite(r2)
),
implied AS
(
SELECT
date,
100 * avg(toFloat64(implied_volatility)) AS iv_pct
FROM global_markets.options_greeks
WHERE underlying_symbol = 'SPY'
AND iv_converged = 1
AND volume > 0
AND days_to_expiry BETWEEN 20 AND 45
AND abs(toFloat64(strike_price) / toFloat64(underlying_close) - 1) < 0.05
AND date >= '2021-08-01'
GROUP BY date
HAVING count() >= 20
),
paired AS
(
SELECT
i.date AS date,
i.iv_pct AS iv_pct,
f.rv_pct AS rv_pct,
i.iv_pct - f.rv_pct AS premium_pct
FROM implied AS i
INNER JOIN forward_rv AS f ON f.date = i.date
WHERE f.sessions_ahead = 21
)
SELECT
toString(toStartOfMonth(date)) AS month,
formatDateTime(toStartOfMonth(date), '%b %Y') AS month_label,
round(avg(iv_pct), 2) AS implied_vol_pct,
round(avg(rv_pct), 2) AS realized_vol_pct,
round(avg(premium_pct), 2) AS premium_pct
FROM paired
GROUP BY month, month_label
ORDER BY monthAcross 62 months, the implied line sits above the realized line far more often than below it. In Sep 2026, the most recent month whose forward window has finished, the gap measured 3.59 points. The two lines converge and cross during stress stretches: implied volatility rises in those months, and realized volatility rises further over the same period.
The distribution matters more than the average
An average premium hides the thing that decides whether a short volatility position survives. The next panel sorts every one of those overlapping 21 session windows into buckets of volatility points.
| bucket | windows | share_pct |
|---|---|---|
| under -10 | 35 | 2.7 |
| -10 to -5 | 74 | 5.7 |
| -5 to 0 | 215 | 16.7 |
| 0 to +5 | 706 | 54.8 |
| +5 to +10 | 253 | 19.6 |
| +10 to +15 | 5 | 0.4 |
The exact SQL behind every number
WITH
closes AS
(
SELECT
date,
toFloat64(any(close)) AS px
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2021-06-01'
GROUP BY date
),
daily_returns AS
(
SELECT
date,
pow(log(px / lagInFrame(px) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)), 2) AS r2
FROM closes
),
forward_rv AS
(
SELECT
date,
100 * sqrt(252 * avg(r2) OVER (ORDER BY date ROWS BETWEEN 1 FOLLOWING AND 21 FOLLOWING)) AS rv_pct,
count(r2) OVER (ORDER BY date ROWS BETWEEN 1 FOLLOWING AND 21 FOLLOWING) AS sessions_ahead
FROM daily_returns
WHERE isFinite(r2)
),
implied AS
(
SELECT
date,
100 * avg(toFloat64(implied_volatility)) AS iv_pct
FROM global_markets.options_greeks
WHERE underlying_symbol = 'SPY'
AND iv_converged = 1
AND volume > 0
AND days_to_expiry BETWEEN 20 AND 45
AND abs(toFloat64(strike_price) / toFloat64(underlying_close) - 1) < 0.05
AND date >= '2021-08-01'
GROUP BY date
HAVING count() >= 20
),
paired AS
(
SELECT i.iv_pct - f.rv_pct AS premium_pct
FROM implied AS i
INNER JOIN forward_rv AS f ON f.date = i.date
WHERE f.sessions_ahead = 21
),
totals AS
(
SELECT count() AS all_windows FROM paired
)
SELECT
multiIf(
p.premium_pct < -10, 'under -10',
p.premium_pct < -5, '-10 to -5',
p.premium_pct < 0, '-5 to 0',
p.premium_pct < 5, '0 to +5',
p.premium_pct < 10, '+5 to +10',
p.premium_pct < 15, '+10 to +15',
'over +15') AS bucket,
count() AS windows,
round(100 * count() / any(t.all_windows), 1) AS share_pct
FROM paired AS p
CROSS JOIN totals AS t
GROUP BY bucket
ORDER BY min(p.premium_pct)Two features of that shape carry the lesson. The bulk of the windows sit in the positive buckets, which is the premium doing its ordinary work. The left tail is thin in count and open ended in size: the deepest bucket, under -10 points, holds 35 windows, or 2.7% of the sample, against 0.4% in the richest bucket on the right, +10 to +15 points. A five point bucket caps what a good window pays. Nothing caps the bucket at the far left.
Does the premium change with tenor?
Short dated and long dated options are not selling the same thing. This panel matches each implied reading to a realized window of the same length: contracts with 7 to 14 days to expiry against the next 7 sessions, out to the 61 to 120 day band against the next 62 sessions. Calendar days are converted to sessions at roughly 252 per year.
| tenor | implied_vol_pct | realized_vol_pct | premium_pct | positive_share_pct |
|---|---|---|---|---|
| 07-14 days | 17.3 | 15.05 | 2.24 | 76.2 |
| 15-30 days | 17.05 | 15.48 | 1.58 | 72.6 |
| 31-60 days | 16.89 | 15.82 | 1.07 | 72.9 |
| 61-120 days | 17.37 | 16.08 | 1.3 | 74.3 |
The exact SQL behind every number
WITH
closes AS
(
SELECT
date,
toFloat64(any(close)) AS px
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2021-06-01'
GROUP BY date
),
daily_returns AS
(
SELECT
date,
pow(log(px / lagInFrame(px) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)), 2) AS r2
FROM closes
),
forward_rv AS
(
SELECT
date,
100 * sqrt(252 * avg(r2) OVER (ORDER BY date ROWS BETWEEN 1 FOLLOWING AND 7 FOLLOWING)) AS rv_07_pct,
100 * sqrt(252 * avg(r2) OVER (ORDER BY date ROWS BETWEEN 1 FOLLOWING AND 15 FOLLOWING)) AS rv_15_pct,
100 * sqrt(252 * avg(r2) OVER (ORDER BY date ROWS BETWEEN 1 FOLLOWING AND 31 FOLLOWING)) AS rv_31_pct,
100 * sqrt(252 * avg(r2) OVER (ORDER BY date ROWS BETWEEN 1 FOLLOWING AND 62 FOLLOWING)) AS rv_62_pct,
count(r2) OVER (ORDER BY date ROWS BETWEEN 1 FOLLOWING AND 62 FOLLOWING) AS sessions_ahead
FROM daily_returns
WHERE isFinite(r2)
),
by_day AS
(
SELECT
g.date AS date,
multiIf(g.days_to_expiry <= 14, '07-14 days',
g.days_to_expiry <= 30, '15-30 days',
g.days_to_expiry <= 60, '31-60 days',
'61-120 days') AS tenor,
avg(100 * toFloat64(g.implied_volatility)) AS iv_pct,
any(multiIf(g.days_to_expiry <= 14, f.rv_07_pct,
g.days_to_expiry <= 30, f.rv_15_pct,
g.days_to_expiry <= 60, f.rv_31_pct,
f.rv_62_pct)) AS rv_pct
FROM global_markets.options_greeks AS g
INNER JOIN forward_rv AS f ON f.date = g.date
WHERE g.underlying_symbol = 'SPY'
AND g.iv_converged = 1
AND g.volume > 0
AND g.days_to_expiry BETWEEN 7 AND 120
AND abs(toFloat64(g.strike_price) / toFloat64(g.underlying_close) - 1) < 0.05
AND g.date >= '2021-08-01'
AND f.sessions_ahead = 62
GROUP BY date, tenor
HAVING count() >= 5
)
SELECT
tenor,
round(avg(iv_pct), 2) AS implied_vol_pct,
round(avg(rv_pct), 2) AS realized_vol_pct,
round(avg(iv_pct - rv_pct), 2) AS premium_pct,
round(100 * countIf(iv_pct > rv_pct) / count(), 1) AS positive_share_pct
FROM by_day
GROUP BY tenor
ORDER BY tenorThe average premium is positive at all four tenors in this sample. The one month band, 15-30 days, averaged 1.58 volatility points, with implied at 17.05 against 15.48 realized, and it landed above zero in 72.6% of windows. The shortest band, 07-14 days, averaged 2.24 points. The longest, 61-120 days, averaged 1.3. A seller picking an expiry is picking a point on this curve, with a different hit rate and a different size at each one.
Does the premium change with strike?
The last cut holds tenor fixed at 20 to 45 days and moves across strikes, scoring every strike bucket against the same 21 session realized volatility. Buckets are labelled by strike divided by the underlying price, so 0.90 is a strike 10% below spot. Puts and calls are separated by the sign of delta.
| moneyness | put_premium_pct | call_premium_pct |
|---|---|---|
| 0.85-0.90 | 12.85 | 14.6 |
| 0.90-0.95 | 7.77 | 6.91 |
| 0.95-0.975 | 4.48 | 3.4 |
| 0.975-1.00 | 2.36 | 1.42 |
| 1.00-1.025 | 0.96 | -0.55 |
| 1.025-1.05 | 1.29 | -1.9 |
| 1.05-1.10 | 5.65 | -2.15 |
The exact SQL behind every number
WITH
closes AS
(
SELECT
date,
toFloat64(any(close)) AS px
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2021-06-01'
GROUP BY date
),
daily_returns AS
(
SELECT
date,
pow(log(px / lagInFrame(px) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)), 2) AS r2
FROM closes
),
forward_rv AS
(
SELECT
date,
100 * sqrt(252 * avg(r2) OVER (ORDER BY date ROWS BETWEEN 1 FOLLOWING AND 21 FOLLOWING)) AS rv_pct,
count(r2) OVER (ORDER BY date ROWS BETWEEN 1 FOLLOWING AND 21 FOLLOWING) AS sessions_ahead
FROM daily_returns
WHERE isFinite(r2)
),
contracts AS
(
SELECT
date,
delta,
toFloat64(implied_volatility) AS iv,
toFloat64(strike_price) / toFloat64(underlying_close) AS moneyness_ratio
FROM global_markets.options_greeks
WHERE underlying_symbol = 'SPY'
AND iv_converged = 1
AND volume > 0
AND days_to_expiry BETWEEN 20 AND 45
AND date >= '2021-08-01'
),
by_day AS
(
SELECT
c.date AS date,
multiIf(c.moneyness_ratio < 0.90, '0.85-0.90',
c.moneyness_ratio < 0.95, '0.90-0.95',
c.moneyness_ratio < 0.975, '0.95-0.975',
c.moneyness_ratio < 1.00, '0.975-1.00',
c.moneyness_ratio < 1.025, '1.00-1.025',
c.moneyness_ratio < 1.05, '1.025-1.05',
'1.05-1.10') AS moneyness,
avgIf(100 * c.iv, c.delta < 0) AS put_iv_pct,
avgIf(100 * c.iv, c.delta > 0) AS call_iv_pct,
any(f.rv_pct) AS rv_pct
FROM contracts AS c
INNER JOIN forward_rv AS f ON f.date = c.date
WHERE c.moneyness_ratio BETWEEN 0.85 AND 1.10
AND f.sessions_ahead = 21
GROUP BY date, moneyness
HAVING countIf(c.delta < 0) > 0
AND countIf(c.delta > 0) > 0
)
SELECT
moneyness,
round(avg(put_iv_pct - rv_pct), 2) AS put_premium_pct,
round(avg(call_iv_pct - rv_pct), 2) AS call_premium_pct
FROM by_day
GROUP BY moneyness
ORDER BY moneynessThe put series measured 12.85 points at 0.85-0.90 and 5.65 points at 1.05-1.10. The put premium stayed positive at both ends of the strike range in this sample.
This is where the premium meets skew, and the two are not the same measurement. Skew is a statement about implied volatility alone: low strikes are priced at a higher implied volatility than high strikes, on the same underlying and the same expiry. The panel above subtracts one realized number from every strike, so what it draws is how much of that skew survived contact with what the index actually did. Our note on why anyone sells a put option covers the position that sits at the left end of this curve.
The negative tail, and why sizing outweighs the average
Now the honest part. The panel below takes the deepest 21 session window in each calendar month and ranks the ten worst.
| label | implied_vol_pct | realized_vol_pct | shortfall_pct |
|---|---|---|---|
| Mar 25, 2025 | 15.56 | 51.28 | 35.72 |
| Apr 1, 2025 | 18.84 | 50.72 | 31.89 |
| Apr 20, 2022 | 17.56 | 35.14 | 17.58 |
| Jul 15, 2024 | 12.14 | 21.29 | 9.14 |
| Mar 30, 2022 | 16.49 | 25.5 | 9.01 |
| Sep 9, 2022 | 20.72 | 28.96 | 8.24 |
| Feb 18, 2025 | 12.48 | 20.51 | 8.03 |
| May 17, 2022 | 24.04 | 31.79 | 7.75 |
| Aug 18, 2022 | 17.81 | 25.49 | 7.68 |
| Feb 9, 2022 | 17.33 | 24.84 | 7.51 |
The exact SQL behind every number
WITH
closes AS
(
SELECT
date,
toFloat64(any(close)) AS px
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2021-06-01'
GROUP BY date
),
daily_returns AS
(
SELECT
date,
pow(log(px / lagInFrame(px) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)), 2) AS r2
FROM closes
),
forward_rv AS
(
SELECT
date,
100 * sqrt(252 * avg(r2) OVER (ORDER BY date ROWS BETWEEN 1 FOLLOWING AND 21 FOLLOWING)) AS rv_pct,
count(r2) OVER (ORDER BY date ROWS BETWEEN 1 FOLLOWING AND 21 FOLLOWING) AS sessions_ahead
FROM daily_returns
WHERE isFinite(r2)
),
implied AS
(
SELECT
date,
100 * avg(toFloat64(implied_volatility)) AS iv_pct
FROM global_markets.options_greeks
WHERE underlying_symbol = 'SPY'
AND iv_converged = 1
AND volume > 0
AND days_to_expiry BETWEEN 20 AND 45
AND abs(toFloat64(strike_price) / toFloat64(underlying_close) - 1) < 0.05
AND date >= '2021-08-01'
GROUP BY date
HAVING count() >= 20
),
paired AS
(
SELECT
i.date AS date,
i.iv_pct AS iv_pct,
f.rv_pct AS rv_pct,
i.iv_pct - f.rv_pct AS premium_pct
FROM implied AS i
INNER JOIN forward_rv AS f ON f.date = i.date
WHERE f.sessions_ahead = 21
)
SELECT
formatDateTime(argMin(date, premium_pct), '%b %e, %Y') AS label,
round(argMin(iv_pct, premium_pct), 2) AS implied_vol_pct,
round(argMin(rv_pct, premium_pct), 2) AS realized_vol_pct,
round(-1 * min(premium_pct), 2) AS shortfall_pct
FROM paired
GROUP BY toStartOfMonth(date)
ORDER BY min(premium_pct) ASC
LIMIT 10In the single worst window of the sample, beginning Mar 25, 2025, near the money implied volatility stood at 15.56 and the following 21 sessions realized 51.28, a shortfall of 35.72 volatility points. Set that against the per tenor averages above and the proportions are clear: one month erased many months of ordinary collection.
The arithmetic is worse than the vol points suggest. A short volatility payoff is not linear in volatility. It scales with the difference of squares, which makes a 20 point shortfall far more than four times the pain of a 5 point one. That is the mechanical reason a position sized off the average premium behaves nothing like the average. The premium arrives in small pieces across many months. The tail arrives inside a few weeks. Volatility targeting, which cuts position size as realized volatility rises, and a size limit set against the tail rather than the mean are what determine whether a seller is still present for the next positive window. This page measures the premium. It does not describe a strategy to run, and none of it forecasts the next window.
How this differs from an IV versus RV screener
The number on this page is a systematic property of index options measured over years of history. It is not a ranking of today's names. Our biggest IV versus RV gap screen ranks individual stocks by the current spread between implied volatility and trailing realized volatility, which is a snapshot taken at one moment. IV rank versus IV percentile places one name's implied volatility inside its own history. Both answer the question "what is unusual right now". This page answers a different one: what the gap has paid on average, how often it paid, and how badly it has failed.
Method notes and known limits
- Realized volatility uses the root mean square of daily log returns over the forward window, annualized by the square root of 252. The zero mean estimator keeps the figure deterministic across regenerations.
- Implied volatility is read per contract per day, filtered to converged solves with non zero volume. The term panels keep strikes within 5% of spot. The strike panel widens that to 0.85 through 1.10 of spot. Each day is averaged across qualifying contracts first, then days are averaged, so a heavily quoted day does not outweigh a quiet one.
- Windows overlap. Consecutive trading days share most of their forward window, so the counts in the distribution panel are observations rather than independent trials.
- Every panel drops the recent dates whose forward window has not finished. The tenor panel needs 62 forward sessions, so it stops roughly three months before today.
- The sample begins Aug 2021, the first month of per contract implied volatility history available to these queries, and the front edge carries a one to two day ingest lag.
FAQ
What is the volatility risk premium in simple terms?
It is the amount by which options are priced for more movement than the underlying usually delivers. Take the implied volatility quoted today, wait for the option's life to pass, measure what actually happened, and subtract. The long run average of that difference is the premium.
Is the volatility risk premium always positive?
No. It is positive in the majority of windows in this sample and deeply negative in a minority of them. The distribution panel above shows the split, and the worst window panel shows how far the negative side has run.
How do you calculate the volatility risk premium?
Pick an implied volatility reading with a defined horizon, such as near the money contracts with 20 to 45 days to expiry. Then compute realized volatility from daily closes over the sessions that follow that reading, annualized the same way, and subtract it from the implied number. The forward matching is the part people most often get wrong.
Does the premium exist on single stocks as well as the index?
The same calculation runs on any underlying with per contract implied volatility history. An index is the cleanest place to measure it, since no single scheduled company event dominates a month, and single name results need the earnings calendar handled separately.
Every panel here ships with the SQL that produced it. Open one, swap the underlying or the horizon, and run the same measurement yourself on the Strasmore terminal.