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 —
SmileDatais 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
SmileData:
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
from qsmile import OptionChain, SmileData, SVIModel, XCoord, YCoord, fit
# Bid/ask prices — forward and DF are calibrated automatically
prices = OptionChain(
strikes=np.array([80, 90, 95, 100, 105, 110, 120], dtype=float),
call_bid=np.array([20.5, 11.8, 7.5, 4.2, 2.0, 0.8, 0.1]),
call_ask=np.array([21.5, 12.4, 8.0, 4.6, 2.3, 1.0, 0.2]),
put_bid=np.array([0.1, 0.6, 1.5, 3.1, 5.8, 9.6, 18.8]),
put_ask=np.array([0.2, 0.8, 1.8, 3.5, 6.2, 10.2, 19.6]),
expiry=0.5,
)
print(prices.forward) # Calibrated forward
print(prices.discount_factor) # Calibrated discount factor
# Enter the coordinate transform framework
sd = prices.to_smile_data() # (FixedStrike, Price)
sd_vols = sd.transform(XCoord.FixedStrike, YCoord.Volatility) # → implied vols
sd_unit = sd_vols.transform(XCoord.StandardisedStrike, YCoord.TotalVariance) # → unitised
# Fit SVI directly from SmileData
result = fit(sd_vols, model=SVIModel)
print(result.params) # Fitted SVIModel
print(result.rmse) # Root mean square errorimport numpy as np
from qsmile import SmileData, SVIModel, fit
sd = SmileData.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]),
forward=100.0,
expiry=0.5,
)
result = fit(sd, model=SVIModel)
print(result.params) # Fitted SVIModel
print(result.rmse) # Root mean square error| Class | Description |
|---|---|
OptionChain |
Bid/ask call and put prices with automatic forward/DF calibration |
SmileData |
Unified coordinate-labelled container with .transform(x, y) and .from_mid_vols() factory |
OptionChain ─── .to_smile_data() ──→ SmileData ─── .transform(x, y) ──→ SmileData
SmileData.from_mid_vols(...) ──→ SmileData ───────────────────────────┘
| 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 SmileData — generic entry point |
SmileModel |
Protocol for pluggable smile models (native coords, bounds, evaluate, etc.) |
AbstractSmileModel |
Abstract base dataclass with default to_array()/from_array() derived from param_names |
SmileResult |
Fitted result with .params, .residuals, .rmse, .success, .evaluate(x) |
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.