Calibrate an arbitrage-checked implied volatility surface across multiple expiries, by fitting a raw SVI (Stochastic Volatility Inspired, Gatheral 2004) curve to the market-implied smile (see get_implied_volatility) at each expiry, and checking the fitted surface for calendar-spread arbitrage.

A single per-expiry smile only tells you the shape of the market’s volatility skew at that one maturity. Stitching several calibrated SVI slices together instead gives a full surface, which is what is needed to price/interpolate options at maturities or strikes that don’t trade directly, and to check for term-structure inconsistencies (see Notes).

Before fitting, strikes whose market-implied volatility is a statistical outlier relative to its neighbors (a common symptom of a stale or wide-bid/ask illiquid quote) are dropped via a median-absolute-deviation filter, since a single bad quote can otherwise dominate the least-squares SVI fit for that whole expiry.

See: Gatheral, J. (2004), “A parsimonious arbitrage-free implied volatility parameterization with application to the valuation of volatility derivatives”, and Gatheral, J., & Jacquier, A. (2014), “Arbitrage-free SVI volatility surfaces”, Quantitative Finance, 14(1), 59-71.

Also known as: SVI surface, implied volatility surface.

Notes: A warning is logged (not raised) if the fitted surface has any calendar-spread arbitrage violations, i.e. total implied variance decreasing with time to expiration at some log-moneyness – this reflects genuine inconsistency in the underlying market quotes across expiries, not a fitting error, and is only checked, not corrected.

Volatility Surface in Python

Install the Finance Toolkit with:

pip install financetoolkit -U

Then call get_volatility_surface as shown below.

from financetoolkit import Toolkit

toolkit = Toolkit(["AAPL"], api_key="FINANCIAL_MODELING_PREP_KEY")

volatility_surface = toolkit.options.get_volatility_surface(number_of_expirations=3)

volatility_surface.loc["AAPL"]

Which returns:

Strike Price 2026-08-05 2026-08-07
277.5 0.5647 nan
285 0.5235 nan
287.5 0.5136 nan
290 0.5057 0.4245
292.5 0.5001 0.4166
295 0.4968 0.4098
297.5 0.4958 0.4043
300 0.4971 0.4001

Parameters

The function accepts the following parameters:

  • expiration_dates (list[str] | None, optional): The expiration dates to fit the surface over. Defaults to None, meaning the first number_of_expirations available expiration dates.
  • put_option (bool, optional): Whether to use put options instead of call options. Defaults to False.
  • risk_free_rate (float, optional): The risk free rate to use for the calculation. Defaults to None which means it will use the current risk free rate.
  • dividend_yield (float, optional): The dividend yield to use for the calculation. Defaults to None which means it will use the dividend yield as obtained through annual historical data.
  • number_of_expirations (int, optional): The number of near-term expiration dates to fit when expiration_dates is not given. Defaults to 6.
  • outlier_threshold (float, optional): The number of median absolute deviations from the median implied volatility beyond which a quote is treated as an outlier and dropped before fitting. Defaults to 5.0.
  • rounding (int | None, optional): The number of decimals to round the results to. Defaults to None.
Share