Overview
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.
Key capabilities
- 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:
$$w(k) = a + b\left(\rho(k - m) + \sqrt{(k - m)^2 + \sigma^2}\right)$$
where $k = \ln(K/F)$ is log-moneyness and $w$ is total implied variance.
- Black76 pricing — Vectorised call/put pricing and explicit closed-form implied vol inversion via
black76_call,black76_put, andblack76_implied_vol. The inverter uses the inverse-Gaussian quantile representation of Schadner (2026), which recovers implied vol to machine precision in a single non-iterative evaluation. - Plotting — All chain types have a
.plot()method for bid/ask error-bar charts (requiresqsmile[plot]).
Installation
pip install qsmile # core
pip install "qsmile[plot]" # with matplotlib plotting
For development:
git clone https://github.com/markrichardson/qsmile.git
cd qsmile
make install
Quick Start
From bid/ask prices (full pipeline)
import 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 error
From mid implied vols
import 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
API Reference
Data containers
| 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 |
Coordinate transforms
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 |
Smile fitting
| 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) |
Black76 pricing
| 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) |
Closed-form implied vol inversion via the inverse-Gaussian quantile (Schadner, 2026) |
Development
make install # Set up environment
make test # Run tests with coverage
make fmt # Format and lint
make marimo # Launch interactive notebooks
License
MIT — see LICENSE for details.
Metadata
Release files for qsmile 0.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| qsmile-0.0.2.tar.gz | 445.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| qsmile-0.0.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 477.5 kB
Release files / qsmile-0.0.2.tar.gz
| Download URL | qsmile-0.0.2.tar.gz |
|---|---|
| Size | 445.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3c981382438e04455cc6c14ff6888234215a65bcb6f9cb1c1bf0498aafd93782
|
|
BLAKE2b-256 checksum How to use checksums |
291c538d8c1f5c3ec0c051a1794591081c1fb18caa89ae6b1efaf6fdd191eff9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 1, 2026.
Transparency logRelease files / qsmile-0.0.2-py3-none-any.whl
| Download URL | qsmile-0.0.2-py3-none-any.whl |
|---|---|
| Size | 32.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
18bff086de4f5e75494afde806709026a8b2941e8f453c5ca8af19f27ce2ca25
|
|
BLAKE2b-256 checksum How to use checksums |
7be93f03c6dc97c3fb86c3b1a6bf95078f92a1cbc180373444b97fe95a012f08
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 1, 2026.
Transparency log