qsmile is a Python library for fitting parametric volatility smile models to option chain data. It provides bid/ask-aware data containers, Black76 pricing, forward/discount-factor calibration, and least-squares SVI calibration out of the box.
- Bid/ask option prices —
OptionChainstores bid/ask call and put prices, and automatically calibrates the forward and discount factor from put-call parity using quasi-delta weighted least squares. - Coordinate transforms —
VolDatais a unified container with.transform(x, y)to freely convert between any combination of X-coordinates (Strike, Moneyness, Log-Moneyness, Standardised) and Y-coordinates (Price, Volatility, Variance, Total Variance) via composable, invertible maps. - SVI fitting — Fit the SVI raw parameterisation to
VolData:
where
- Black76 pricing — Vectorised call/put pricing and implied vol inversion via
black76_call,black76_put, andblack76_implied_vol. - Plotting — All chain types have a
.plot()method for bid/ask error-bar charts (requiresqsmile[plot]).
pip install qsmile # core
pip install "qsmile[plot]" # with matplotlib plottingFor development:
git clone https://github.com/markrichardson/qsmile.git
cd qsmile
make installimport numpy as np
import pandas as pd
from qsmile import OptionChain, SmileMetadata, StrikeArray, SVIModel, VolData, XCoord, YCoord, fit
# Bid/ask prices — forward and DF are calibrated automatically
strikes = np.array([80, 90, 95, 100, 105, 110, 120], dtype=float)
idx = pd.Index(strikes, dtype=np.float64)
sa = StrikeArray()
sa.set(("call", "bid"), pd.Series([20.5, 11.8, 7.5, 4.2, 2.0, 0.8, 0.1], index=idx))
sa.set(("call", "ask"), pd.Series([21.5, 12.4, 8.0, 4.6, 2.3, 1.0, 0.2], index=idx))
sa.set(("put", "bid"), pd.Series([0.1, 0.6, 1.5, 3.1, 5.8, 9.6, 18.8], index=idx))
sa.set(("put", "ask"), pd.Series([0.2, 0.8, 1.8, 3.5, 6.2, 10.2, 19.6], index=idx))
prices = OptionChain(
strikedata=sa,
metadata=SmileMetadata(date=pd.Timestamp("2024-01-01"), expiry=pd.Timestamp("2024-07-01")),
)
print(prices.metadata.forward) # Calibrated forward
print(prices.metadata.discount_factor) # Calibrated discount factor
# Enter the coordinate transform framework
sd = prices.to_vols() # (FixedStrike, Volatility)
sd_unit = sd.transform(XCoord.StandardisedStrike, YCoord.TotalVariance) # → unitised
# Fit SVI directly from VolData
result = fit(sd, model=SVIModel)
print(result.model) # Fitted SVIModel
print(result.rmse) # Root mean square errorimport numpy as np
import pandas as pd
from qsmile import SmileMetadata, SVIModel, VolData, fit
meta = SmileMetadata(
date=pd.Timestamp("2024-01-01"),
expiry=pd.Timestamp("2024-07-01"),
forward=100.0,
)
sd = VolData.from_mid_vols(
strikes=np.array([80, 90, 100, 110, 120], dtype=float),
ivs=np.array([0.28, 0.22, 0.18, 0.17, 0.19]),
metadata=meta,
)
result = fit(sd, model=SVIModel)
print(result.model) # Fitted SVIModel
print(result.rmse) # Root mean square error| Class | Description |
|---|---|
OptionChain |
Bid/ask call and put prices with automatic forward/DF calibration |
VolData |
Unified coordinate-labelled container with .transform(x, y) and .from_mid_vols() factory |
OptionChain ─── .to_vols() ──→ VolData ─── .transform(x, y) ──→ VolData
VolData.from_mid_vols(...) ──→ VolData ───────────────────────────┘
| Coordinate type | Values |
|---|---|
| X-coordinates | FixedStrike, MoneynessStrike, LogMoneynessStrike, StandardisedStrike |
| Y-coordinates | Price, Volatility, Variance, TotalVariance |
| Function / Class | Description |
|---|---|
fit(chain, model) |
Fit any SmileModel to VolData — generic entry point |
SmileModel |
Abstract base dataclass for pluggable smile models (native coords, bounds, evaluate, etc.) |
SmileResult |
Fitted result with .model, .residuals, .rmse, .success |
SVIModel |
SVI model and parameter values (a, b, rho, m, sigma) with .evaluate(k) and .implied_vol(k, T) |
SABRModel |
SABR model (alpha, beta, rho, nu) with Hagan (2002) lognormal implied vol .evaluate(k) |
| Function | Description |
|---|---|
black76_call(F, K, D, σ, T) |
Vectorised Black76 call price |
black76_put(F, K, D, σ, T) |
Vectorised Black76 put price |
black76_implied_vol(price, F, K, D, T) |
Implied vol inversion via Brent's method |
make install # Set up environment
make test # Run tests with coverage
make fmt # Format and lint
make marimo # Launch interactive notebooksMIT — see LICENSE for details.