Skip to main content
VolSurface extends single-expiry smile fitting to the term structure. It fits an SVI smile at each expiry in your chain, builds a total-variance interpolator between pillars, and exposes maturities within the fitted range as VolCurve slices. The guide below covers initialization, fitting, slicing, export, and conversion to a probability surface.
1

Initialize

VolSurface takes the same constructor arguments as VolCurve. Configure the method and pricing engine once; the same settings apply to every expiry pillar.
from oipd import MarketInputs, VolSurface, sources

vol_surface = VolSurface(method="svi", pricing_engine="black76")
2

Fetch data

Use sources.fetch_chain with a horizon argument to pull all expiries within your target window.
ticker = "PLTR"
chain_surface, snapshot_surface = sources.fetch_chain(ticker, horizon="12m")

surface_market = MarketInputs(
    valuation_date=snapshot_surface.asof,
    underlying_price=snapshot_surface.underlying_price,
    risk_free_rate=0.04,
)
3

Fit

Call vol_surface.fit(chain, market). The method requires at least two distinct expiries. By default it uses failure_policy="skip_warn", which skips any expiry that fails to calibrate and records a WorkflowWarning rather than aborting the entire fit.
vol_surface.fit(chain_surface, surface_market)
failure_policy options
ValueBehavior
"skip_warn" (default)Skips failing expiries and continues fitting the surface.
"raise"Raises a CalculationError on the first expiry that fails.
horizon filteringPass horizon directly to fit to discard expiries beyond a cutoff, even if your chain already contains them:
vol_surface.fit(chain_surface, surface_market, horizon="6m")
Accepted formats: "30d", "6m", "1y", or an explicit future date string or datetime.date.
4

Slice

vol_surface.expiries returns a sorted tuple of pd.Timestamp objects for every successfully fitted pillar. vol_surface.slice(expiry) returns a VolCurve at that maturity.
print(vol_surface.expiries)            # (Timestamp(...), ...)

# Exact pillar — returns the original parametric curve
curve = vol_surface.slice(vol_surface.expiries[0])
print(curve.atm_vol)
print(curve.implied_vol([90, 100, 110]))

# Maturity between pillars — returns a synthetic interpolated VolCurve
midpoint = vol_surface.expiries[0] + (vol_surface.expiries[1] - vol_surface.expiries[0]) / 2
interp_curve = vol_surface.slice(midpoint)
print(interp_curve.atm_vol)
When you slice at a maturity that is not an exact fitted pillar, VolSurface derives a synthetic VolCurve from the total-variance interpolator. The interpolated curve supports implied_vol, price, greeks, and implied_distribution just like a directly fitted curve.
5

Export

iv_results returns a long-format DataFrame spanning all fitted pillars. Supply start, end, and step_days to control the time grid.
# Default: daily grid from first to last fitted pillar
df = vol_surface.iv_results()

# Weekly grid over a custom range
df_range = vol_surface.iv_results(
    start=vol_surface.expiries[0],
    end=vol_surface.expiries[-1],
    step_days=7,
)

# Pillar-only export
df_pillars = vol_surface.iv_results(step_days=None)
The DataFrame includes an expiry column. Set include_observed=True to add observed market bid/ask/mid IV columns from each pillar.
6

Plot

plot overlays the fitted smiles across the expiries that survived calibration.
import matplotlib.pyplot as plt

fig = vol_surface.plot(x_axis="strike", y_axis="iv", label_format="days")
plt.show()
Example output:Example VolSurface implied volatility plot
7

Probability surface

Convert the fitted VolSurface to a ProbSurface using either the constructor or the from_chain convenience method. Both approaches produce identical results; the constructor is useful when you already have a fitted VolSurface you want to reuse.
from oipd import ProbSurface

# From a fitted VolSurface directly
prob_surface = ProbSurface(vol_surface=vol_surface)

# Or as a one-liner from raw chain data
prob_surface = ProbSurface.from_chain(chain_surface, surface_market)
VolSurface.fit requires at least two unique expiries. A single-expiry chain raises a CalculationError. Use VolCurve.fit for single-expiry chains. Surface queries are limited to positive maturities up to the last fitted expiry.

Column mapping

If your DataFrame uses non-standard column names, pass column_mapping before fitting. The mapping direction is {"your_column": "oipd_column"}. See Standard columns for the target names.
vol_surface.fit(
    chain_surface,
    surface_market,
    column_mapping={
        "type": "option_type",
        "expiration": "expiry",
        "last": "last_price",
    },
)
Public OIPD input names: strike, expiry, option_type, bid, ask, last_price, volume, and last_trade_date.