Skip to main content

qsmile

License: MIT Python versions PyPI - Version

Github Linux macOS Code style: ruff uv Hatch project

CI MARIMO CodeFactor

Volatility smile modelling for option trading


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 — OptionChain stores 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 — VolData is 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, and black76_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 (requires qsmile[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)

Source distribution for qsmile 0.0.2
File Size Uploaded
qsmile-0.0.2.tar.gz 445.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for qsmile 0.0.2
File Interpreter ABI Platform
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 log

Release 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
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page