Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

Stocks With the Biggest IV-RV Gap

Which stocks have the biggest IV-RV gap? We rank 30-day implied volatility against the realized volatility that followed, with the full method shown.

Stocks with the biggest IV-RV gap are the names whose 30-day implied volatility sat far above the realized volatility that actually arrived over the following month. This page ranks that spread, which options traders call the variance risk premium. It answers a different question from our other three volatility leaderboards: highest implied volatility stocks ranks the level of implied volatility, highest IV rank stocks ranks that level against a name's own history, and highest option premiums ranks the dollar price of the contract. A stock can lead all three and still show no gap at all, if it moved as much as its options implied.

What the IV-RV gap measures

Implied volatility is the annualized move an option's price implies for the stock underneath it, backed out of the option price. Realized volatility is the annualized move the stock actually printed, measured from its closing prices after the fact. One is a forecast. The other is the scoreboard. Our historical volatility vs implied volatility primer walks through both definitions slowly.

The gap is the first minus the second, in annualized volatility points. A name with 30-day IV of 40% whose following month realized 28% carries a 12-point gap. Positive means the options were priced for more motion than arrived. Negative means the stock out-moved its own option market. The average across broad equity samples has been positive over long stretches, and that persistence is what gave the spread a name of its own.

The method, in full

A ranking like this is only as credible as its definition, so here is the whole recipe before any number.

  • The implied leg: per-contract daily IV for call contracts with 20 to 45 days to expiry, struck within 5% of the stock's close that day, keeping only rows where the solver returned a positive volatility. Moneyness is measured against the same daily closing series that feeds the realized leg, so one price defines both the strike band and the returns. Each name's reading is the plain average of those contract days.
  • The realized leg: close-to-close daily log returns, sample standard deviation, multiplied by the square root of 252 to annualize. Both legs print in the same units.
  • The forward shift: a 30-day implied reading covers the next 30 days, so the realized window is pushed forward one month. The implied leg spans October 2025 through June 2026. The realized leg spans November 2025 through July 2026.
  • The filters: a name needs at least 120 days with a qualifying quote and 1,000 qualifying contract days, which keeps the list to actively traded option classes, and its close has to sit above $10. Any ticker with a stock split in the window is dropped, since an unadjusted split prints as a fake 50% session.

Stocks with the biggest IV-RV gap, October 2025 to June 2026

QueryLargest gap between 30-day implied volatility and the realized volatility that followed
symbolimplied_vol_pctrealized_vol_pctgap_pct
VKTX79.258.320.9
TTD71.853.917.9
UPST83.868.615.2
VXX71.75813.7
CORZ87.674.213.4
UUUU101.488.113.3
LYFT63.350.213.1
WBD35.722.713
DPST78.565.513
DUOL79.267.112.1
GME48.536.412.1
KVUE32.920.812.1
The exact SQL behind every number
WITH
    split_tickers AS
    (
        SELECT DISTINCT ticker
        FROM global_markets.stocks_splits
        WHERE execution_date >= '2025-10-01'
          AND execution_date <  '2026-08-01'
    ),
    px AS
    (
        SELECT
            ticker                AS symbol,
            date                  AS session,
            max(toFloat64(close)) AS px
        FROM global_markets.stocks_daily_aggs
        WHERE date >= '2025-10-01'
          AND date <  '2026-08-01'
          AND ticker NOT IN (SELECT ticker FROM split_tickers)
          AND ticker NOT IN ('SPCX')
        GROUP BY symbol, session
    ),
    iv_rows AS
    (
        SELECT
            g.symbol  AS symbol,
            g.session AS session,
            g.iv      AS iv
        FROM
        (
            SELECT
                underlying_symbol       AS symbol,
                toDate(date)            AS session,
                toFloat64(strike_price) AS strike,
                implied_volatility      AS iv
            FROM global_markets.options_greeks
            WHERE date >= '2025-10-01'
              AND date <  '2026-07-01'
              AND lower(toString(option_type)) IN ('c', 'call')
              AND days_to_expiry BETWEEN 20 AND 45
              AND implied_volatility > 0
        ) AS g
        INNER JOIN px AS p ON p.symbol = g.symbol AND p.session = g.session
        WHERE p.px > 10
          AND abs(g.strike / p.px - 1) < 0.05
    ),
    implied AS
    (
        SELECT
            symbol,
            round(100 * avg(iv), 1) AS implied_vol_pct
        FROM iv_rows
        GROUP BY symbol
        HAVING countDistinct(session) >= 120
           AND count() >= 1000
    ),
    daily_returns AS
    (
        SELECT
            symbol,
            arrayJoin(arrayMap((a, b) -> log(tupleElement(a, 2) / tupleElement(b, 2)),
                               arraySlice(series, 2),
                               arraySlice(series, 1, length(series) - 1))) AS ret
        FROM
        (
            SELECT
                symbol,
                arraySort(groupArray((session, px))) AS series
            FROM px
            WHERE session >= '2025-11-01'
              AND symbol IN (SELECT symbol FROM implied)
            GROUP BY symbol
        )
    ),
    realized AS
    (
        SELECT
            symbol,
            round(100 * sqrt(252) * stddevSamp(ret), 1) AS realized_vol_pct
        FROM daily_returns
        GROUP BY symbol
        HAVING count() >= 120
    )
SELECT
    implied.symbol                                                AS symbol,
    implied.implied_vol_pct                                       AS implied_vol_pct,
    realized.realized_vol_pct                                     AS realized_vol_pct,
    round(implied.implied_vol_pct - realized.realized_vol_pct, 1) AS gap_pct
FROM implied
INNER JOIN realized ON realized.symbol = implied.symbol
WHERE implied.implied_vol_pct > realized.realized_vol_pct
ORDER BY gap_pct DESC
LIMIT 12
Run this yourself

The widest name on the list, VKTX, averaged 79.2% implied against 58.3% realized, a gap of 20.9 points. All 12 names shown carried positive gaps, down to 12.1 points for KVUE at the bottom. Read the bars in pairs: the implied bar is the forecast the option market published, the realized bar is what the tape delivered over the month after.

A worked example: SPY, month by month

One name over many months shows the mechanics better than any cross-section. The panel below pairs each month's average 30-day IV on SPY, the S&P 500 ETF, with the realized volatility of the month that came next.

QuerySPY: monthly 30-day implied volatility against the next month's realized volatility
24 rows (showing 20)
monthimplied_vol_pctrealized_vol_pctgap_pct
2024-0712.919.2-6.3
2024-081513.81.2
2024-0913.611.22.4
2024-1016.411.84.6
2024-111314.1-1.1
2024-1211.613.9-2.3
2025-0113.913.20.7
2025-0212.720.7-8
2025-0317.751.9-34.2
2025-0426.416.89.6
2025-0517.410.27.2
2025-0614.76.68.1
2025-0714.4122.4
2025-0812.67.15.5
2025-0911.313.8-2.5
2025-1015.315.4-0.1
2025-1115.58.47.1
2025-1212.410.32.1
2026-0113.413.40
2026-0215.318.2-2.9
The exact SQL behind every number
WITH
    px AS
    (
        SELECT
            date                  AS session,
            max(toFloat64(close)) AS px
        FROM global_markets.stocks_daily_aggs
        WHERE ticker = 'SPY'
          AND date >= '2024-07-01'
          AND date <  '2026-08-01'
        GROUP BY session
    ),
    iv_rows AS
    (
        SELECT
            g.session AS session,
            g.iv      AS iv
        FROM
        (
            SELECT
                toDate(date)            AS session,
                toFloat64(strike_price) AS strike,
                implied_volatility      AS iv
            FROM global_markets.options_greeks
            WHERE underlying_symbol = 'SPY'
              AND date >= '2024-07-01'
              AND date <  '2026-07-01'
              AND lower(toString(option_type)) IN ('c', 'call')
              AND days_to_expiry BETWEEN 20 AND 45
              AND implied_volatility > 0
        ) AS g
        INNER JOIN px AS p ON p.session = g.session
        WHERE abs(g.strike / p.px - 1) < 0.05
    ),
    daily_returns AS
    (
        SELECT
            arrayJoin(arrayMap((a, b) -> (tupleElement(a, 1), log(tupleElement(a, 2) / tupleElement(b, 2))),
                               arraySlice(series, 2),
                               arraySlice(series, 1, length(series) - 1))) AS ret
        FROM
        (
            SELECT arraySort(groupArray((session, px))) AS series
            FROM px
        )
    ),
    realized AS
    (
        SELECT
            toStartOfMonth(tupleElement(ret, 1))                         AS rv_month,
            round(100 * sqrt(252) * stddevSamp(tupleElement(ret, 2)), 1) AS realized_vol_pct
        FROM daily_returns
        GROUP BY rv_month
        HAVING count() >= 15
    ),
    implied AS
    (
        SELECT
            iv_month,
            addMonths(iv_month, 1) AS forward_month,
            implied_vol_pct
        FROM
        (
            SELECT
                toStartOfMonth(session) AS iv_month,
                round(100 * avg(iv), 1) AS implied_vol_pct
            FROM iv_rows
            GROUP BY iv_month
            HAVING countDistinct(session) >= 15
        )
    )
SELECT
    formatDateTime(implied.iv_month, '%Y-%m')                     AS month,
    implied.implied_vol_pct                                       AS implied_vol_pct,
    realized.realized_vol_pct                                     AS realized_vol_pct,
    round(implied.implied_vol_pct - realized.realized_vol_pct, 1) AS gap_pct
FROM implied
INNER JOIN realized ON realized.rv_month = implied.forward_month
ORDER BY month
Run this yourself

That is 24 monthly pairs on one name, starting at 2024-07. In the last month in view, 2026-06, implied averaged 14.7% against a realized 12.1%. Follow the two lines and the arithmetic of the gap becomes concrete: the implied line is fairly smooth, the realized line spikes, and the gap_pct column carries the sign of each month.

How often is the IV-RV gap positive?

A top-twelve list is selected on the outcome it reports, so it cannot tell you how common a positive gap is. The panel below drops the ranking and counts instead. For every month in the window it pairs each qualifying name's implied reading with the realized volatility of the next month, then reports the share of names where the implied number came out higher.

QueryShare of liquid names whose 30-day IV exceeded the realized volatility that followed
monthnames_measuredpositive_gap_share_pctmedian_gap_pct
2025-1043269.95.2
2025-1132087.89.3
2025-1236933.9-4
2026-0139534.4-5.1
2026-0231959.92.6
2026-0333872.56.7
2026-0435863.73.2
2026-0539638.6-1.9
2026-0635758.82.4
The exact SQL behind every number
WITH
    split_tickers AS
    (
        SELECT DISTINCT ticker
        FROM global_markets.stocks_splits
        WHERE execution_date >= '2025-10-01'
          AND execution_date <  '2026-08-01'
    ),
    px AS
    (
        SELECT
            ticker                AS symbol,
            date                  AS session,
            max(toFloat64(close)) AS px
        FROM global_markets.stocks_daily_aggs
        WHERE date >= '2025-10-01'
          AND date <  '2026-08-01'
          AND ticker NOT IN (SELECT ticker FROM split_tickers)
          AND ticker NOT IN ('SPCX')
        GROUP BY symbol, session
    ),
    iv_rows AS
    (
        SELECT
            g.symbol  AS symbol,
            g.session AS session,
            g.iv      AS iv
        FROM
        (
            SELECT
                underlying_symbol       AS symbol,
                toDate(date)            AS session,
                toFloat64(strike_price) AS strike,
                implied_volatility      AS iv
            FROM global_markets.options_greeks
            WHERE date >= '2025-10-01'
              AND date <  '2026-07-01'
              AND lower(toString(option_type)) IN ('c', 'call')
              AND days_to_expiry BETWEEN 20 AND 45
              AND implied_volatility > 0
        ) AS g
        INNER JOIN px AS p ON p.symbol = g.symbol AND p.session = g.session
        WHERE p.px > 10
          AND abs(g.strike / p.px - 1) < 0.05
    ),
    implied AS
    (
        SELECT
            symbol,
            iv_month,
            addMonths(iv_month, 1) AS forward_month,
            implied_vol_pct
        FROM
        (
            SELECT
                symbol,
                toStartOfMonth(session) AS iv_month,
                round(100 * avg(iv), 1) AS implied_vol_pct
            FROM iv_rows
            GROUP BY symbol, iv_month
            HAVING countDistinct(session) >= 15
               AND count() >= 150
        )
    ),
    daily_returns AS
    (
        SELECT
            symbol,
            arrayJoin(arrayMap((a, b) -> (tupleElement(a, 1), log(tupleElement(a, 2) / tupleElement(b, 2))),
                               arraySlice(series, 2),
                               arraySlice(series, 1, length(series) - 1))) AS ret
        FROM
        (
            SELECT
                symbol,
                arraySort(groupArray((session, px))) AS series
            FROM px
            WHERE symbol IN (SELECT symbol FROM implied)
            GROUP BY symbol
        )
    ),
    realized AS
    (
        SELECT
            symbol,
            toStartOfMonth(tupleElement(ret, 1))                         AS rv_month,
            round(100 * sqrt(252) * stddevSamp(tupleElement(ret, 2)), 1) AS realized_vol_pct
        FROM daily_returns
        GROUP BY symbol, rv_month
        HAVING count() >= 15
    )
SELECT
    formatDateTime(implied.iv_month, '%Y-%m') AS month,
    count()                                   AS names_measured,
    round(100 * countIf(implied.implied_vol_pct > realized.realized_vol_pct) / count(), 1) AS positive_gap_share_pct,
    round(quantileDeterministic(0.5)(implied.implied_vol_pct - realized.realized_vol_pct,
                                     cityHash64(implied.symbol)), 1) AS median_gap_pct
FROM implied
INNER JOIN realized ON realized.symbol = implied.symbol
                   AND realized.rv_month = implied.forward_month
GROUP BY month
ORDER BY month
Run this yourself

In the first month of the window, 2025-10, the gap came out positive for 69.9% of the 432 names that cleared the filters, with a median of 5.2 points. By 2026-06 the positive share was 58.8% across 357 names. The line across all 9 months matters more than any single reading: the share is not a constant, and each dip marks a month when realized volatility came in above implied for much of the list.

What a persistent gap does not imply

Sort the same monthly pairs the other way and the left tail appears.

QueryThe most negative monthly pairs: realized volatility far above implied
labelimplied_vol_pctrealized_vol_pctgap_pct
SOXL Jun 2026136.9243.2-106.3
SLV Jan 202647.8139.7-91.9
APP Feb 202672.2137.6-65.4
SNDK Jul 2026104.6159.7-55.1
DELL Feb 202646.392.2-45.9
HOOD Feb 202659.6102.1-42.5
IBIT Feb 202640.281.8-41.6
CRWV Feb 202693.4130.8-37.4
MU Jun 202689.1126.4-37.3
SOXX Jun 202645.182.1-37
The exact SQL behind every number
WITH
    split_tickers AS
    (
        SELECT DISTINCT ticker
        FROM global_markets.stocks_splits
        WHERE execution_date >= '2025-10-01'
          AND execution_date <  '2026-08-01'
    ),
    px AS
    (
        SELECT
            ticker                AS symbol,
            date                  AS session,
            max(toFloat64(close)) AS px
        FROM global_markets.stocks_daily_aggs
        WHERE date >= '2025-10-01'
          AND date <  '2026-08-01'
          AND ticker NOT IN (SELECT ticker FROM split_tickers)
          AND ticker NOT IN ('SPCX')
        GROUP BY symbol, session
    ),
    iv_rows AS
    (
        SELECT
            g.symbol  AS symbol,
            g.session AS session,
            g.iv      AS iv
        FROM
        (
            SELECT
                underlying_symbol       AS symbol,
                toDate(date)            AS session,
                toFloat64(strike_price) AS strike,
                implied_volatility      AS iv
            FROM global_markets.options_greeks
            WHERE date >= '2025-10-01'
              AND date <  '2026-07-01'
              AND lower(toString(option_type)) IN ('c', 'call')
              AND days_to_expiry BETWEEN 20 AND 45
              AND implied_volatility > 0
        ) AS g
        INNER JOIN px AS p ON p.symbol = g.symbol AND p.session = g.session
        WHERE p.px > 10
          AND abs(g.strike / p.px - 1) < 0.05
    ),
    implied AS
    (
        SELECT
            symbol,
            iv_month,
            addMonths(iv_month, 1) AS forward_month,
            implied_vol_pct
        FROM
        (
            SELECT
                symbol,
                toStartOfMonth(session) AS iv_month,
                round(100 * avg(iv), 1) AS implied_vol_pct
            FROM iv_rows
            GROUP BY symbol, iv_month
            HAVING countDistinct(session) >= 18
               AND count() >= 500
        )
    ),
    daily_returns AS
    (
        SELECT
            symbol,
            arrayJoin(arrayMap((a, b) -> (tupleElement(a, 1), log(tupleElement(a, 2) / tupleElement(b, 2))),
                               arraySlice(series, 2),
                               arraySlice(series, 1, length(series) - 1))) AS ret
        FROM
        (
            SELECT
                symbol,
                arraySort(groupArray((session, px))) AS series
            FROM px
            WHERE symbol IN (SELECT symbol FROM implied)
            GROUP BY symbol
        )
    ),
    realized AS
    (
        SELECT
            symbol,
            toStartOfMonth(tupleElement(ret, 1))                         AS rv_month,
            round(100 * sqrt(252) * stddevSamp(tupleElement(ret, 2)), 1) AS realized_vol_pct
        FROM daily_returns
        GROUP BY symbol, rv_month
        HAVING count() >= 15
    )
SELECT
    concat(implied.symbol, ' ', formatDateTime(implied.forward_month, '%b %Y')) AS label,
    implied.implied_vol_pct                                                     AS implied_vol_pct,
    realized.realized_vol_pct                                                   AS realized_vol_pct,
    round(implied.implied_vol_pct - realized.realized_vol_pct, 1)               AS gap_pct
FROM implied
INNER JOIN realized ON realized.symbol = implied.symbol
                   AND realized.rv_month = implied.forward_month
WHERE realized.realized_vol_pct > implied.implied_vol_pct
ORDER BY gap_pct ASC
LIMIT 10
Run this yourself

The most negative pair in the window is SOXL Jun 2026: 136.9% implied against 243.2% realized, a gap of -106.3 points. Each of the 10 rows is a liquid option class where one month's move dwarfed the size its options were priced for. Four things sit between a positive average spread and what any account would experience.

  • Costs come off the top. Every leg pays the bid-ask spread and a commission in both directions, and the thinner the option class, the larger that haul against a 10-point gap. Our guide to what it costs to trade options puts numbers on it.
  • The average is not the path. A positive mean across hundreds of names and nine months says nothing about one position in one month.
  • The tail is fat on one side. The panel above is the left edge of the distribution, and a short option's loss in such a month runs to many times the premium taken in.
  • Overnight gaps ignore exit plans. A stock can reopen far from its last print with nothing tradeable in between, so the worst outcome on a short position is not bounded by where a trader intended to get out.

FAQ

What is the IV-RV gap?

It is the difference between an option market's 30-day implied volatility and the realized volatility the stock went on to print over the next month, measured in annualized percentage points. A positive gap means the options were priced for a larger move than the one that arrived. Traders also call it the variance risk premium.

Is the biggest IV-RV gap the same as the highest implied volatility?

No. A name at 90% IV that then realizes 85% has a 5-point gap, while a name at 25% IV that realizes 15% has a 10-point gap. The level is ranked in highest implied volatility stocks. The spread is what this page ranks.

How is realized volatility calculated here?

From close-to-close daily log returns over the forward month, taking the sample standard deviation and multiplying by the square root of 252, the approximate count of trading sessions in a year. That puts it in the same annualized units as implied volatility.

Does a persistent positive gap mean selling options makes money?

No. A positive average across many names and months is a property of the sample, not a result for any single trade. Costs and the fat left tail shown above sit between the measured spread and any account's outcome.

Method notes and caveats
  • The implied leg uses call contracts only. Put-side IV at the same strike and expiry tracks close to it for most names, and averaging the two blends two quote surfaces into one number.
  • IV rows are per contract per day, so a name with a dense strike ladder contributes more contract days than a thin one. The averages here are unweighted across qualifying contracts, and a contract counts whether or not it traded that session.
  • Both legs read the same daily closing series, which is why a name with no price history in the window drops out of the ranking entirely rather than carrying an implied reading with no realized partner.
  • Daily price aggregates carry a one to two day ingest lag at the front edge, which is why the window closes in July 2026 rather than at today's date.
  • Sourcing these series yourself is covered in where to get historical implied volatility data.

Every panel here ships with the exact SQL beneath it. Open one, move the date window or widen the days-to-expiry band, and the ranking recomputes on the Strasmore terminal.

#implied volatility#realized volatility#variance risk premium#options#screener