API Reference¶
qsmile
¶
Quantitative Smile Modelling.
XCoord
¶
Bases: Enum
X-coordinate (strike) representations.
YCoord
¶
Bases: Enum
Y-coordinate (value) representations.
DayCount
¶
Bases: Enum
Day-count convention for computing year fractions.
Parameters¶
value : str Human-readable convention name.
SampleDataReader
¶
Read option chain parquet files from a directory.
Parameters¶
root : str | Path | None
Directory containing chains/*.parquet files.
Defaults to <project_root>/parquet.
__init__(root: str | Path | None = None) -> None
¶
Create a reader backed by a parquet directory.
Parameters¶
root : str | Path | None
Directory containing chains/*.parquet files.
Defaults to <project_root>/parquet.
get_chain(underlying: str, fetch_date: str, expiry_date: str) -> OptionChain
¶
Load an option chain from parquet and return an OptionChain.
Parameters¶
underlying : str
Ticker symbol, e.g. "SPX".
fetch_date : str
Fetch / pricing date in YYYY-MM-DD format.
expiry_date : str
Expiry date in YYYY-MM-DD format.
Returns:¶
OptionChain Fully constructed option chain with metadata and strike data.
SmileMetadata
dataclass
¶
Parameters needed by coordinate transforms.
Parameters¶
date : pd.Timestamp
Valuation / pricing date.
expiry : pd.Timestamp
Expiry date. Must be strictly after date.
daycount : DayCount
Day-count convention for computing the year fraction.
Defaults to DayCount.ACT365.
forward : float | None
Forward price. Must be positive when provided.
discount_factor : float | None
Discount factor. Must be positive when provided.
sigma_atm : float | None
ATM implied volatility. Must be positive when provided.
Required for StandardisedStrike transforms.
OptionChain
dataclass
¶
Bid/ask option price chain for a single expiry.
Parameters¶
strikedata : StrikeArray
Strike-indexed columnar data containing at least call_bid,
call_ask, put_bid, and put_ask columns.
Optional volume and open_interest columns are supported.
metadata : SmileMetadata
Smile metadata. expiry must be provided.
forward and discount_factor are calibrated from
put-call parity if None.
strikes: NDArray[np.float64]
property
¶
Strike prices.
call_bid: NDArray[np.float64]
property
¶
Call bid prices.
call_ask: NDArray[np.float64]
property
¶
Call ask prices.
put_bid: NDArray[np.float64]
property
¶
Put bid prices.
put_ask: NDArray[np.float64]
property
¶
Put ask prices.
volume: NDArray[np.float64] | None
property
¶
Per-strike traded volume, or None.
open_interest: NDArray[np.float64] | None
property
¶
Per-strike open interest, or None.
call_mid: NDArray[np.float64]
property
¶
Midpoint of call bid and ask prices.
put_mid: NDArray[np.float64]
property
¶
Midpoint of put bid and ask prices.
__post_init__() -> None
¶
Validate inputs and calibrate forward/DF if needed.
__repr__() -> str
¶
Compact repr with date, expiry, forward, and discount factor.
to_vols() -> VolData
¶
Convert to a VolData with (FixedStrike, Volatility) using delta-blended vols.
Inverts both call and put prices to implied vols at every strike, then blends them using Black76 undiscounted call-delta weights. OTM options dominate in each wing; ATM is approximately equal-weighted.
Strikes where neither call nor put vol can be computed are excluded.
filter() -> OptionChain
¶
Return a cleaned copy with stale and implausible quotes removed.
Applies five filters in sequence:
- Zero-bid filter -- removes strikes where either the call or put bid is zero (no genuine two-sided market).
- Put-call parity monotonicity -- C_mid - P_mid must be strictly decreasing in strike (since it equals D*(F - K)). Strikes that break monotonicity carry stale or mismarked quotes and are dropped.
- Call- and put-mid monotonicity -- call mids must be non-increasing and put mids non-decreasing in strike. Any remaining violations are removed.
- Sub-intrinsic filter -- removes strikes where the call or put bid falls below intrinsic value (using the calibrated forward), which indicates illiquid deep-ITM or stale quotes.
- Parity residual filter -- removes strikes where the put-call parity residual |C_mid - P_mid - D*(F - K)| exceeds 3x the combined half bid-ask spread, indicating a stale or mispriced deep-ITM quote.
Returns:¶
OptionChain
A new OptionChain with the noisy strikes removed and
forward / discount_factor re-calibrated on the clean data.
plot(*, title: str = 'Option Chain Prices', ax=None, **kwargs) -> matplotlib.figure.Figure
¶
Plot bid/ask prices as error bars for calls and puts.
StrikeArray
¶
A mutable collection of named columns indexed by strike price.
Columns are stored in a pd.DataFrame with a two-level MultiIndex
on columns (level-0 = category, level-1 = field). Callers address
columns directly via tuple[str, str] keys such as ("call", "bid").
When a new column is added whose strike index differs from the current global index, all columns are reindexed to the sorted union of strikes.
strikes: NDArray[np.float64]
property
¶
Common strike index as a sorted NDArray.
columns: list[tuple[str, str]]
property
¶
Column keys in insertion order.
__init__() -> None
¶
Create an empty StrikeArray.
set(key: tuple[str, str], series: pd.Series) -> None
¶
Add or replace a column, updating the global strike index.
values(key: tuple[str, str]) -> NDArray[np.float64]
¶
Get column values as an NDArray. Raises KeyError if absent.
get_values(key: tuple[str, str]) -> NDArray[np.float64] | None
¶
Get column values as an NDArray, or None if absent.
has(key: tuple[str, str]) -> bool
¶
Check whether a column exists.
__len__() -> int
¶
Return the number of strikes.
filter(mask: NDArray[np.bool_]) -> StrikeArray
¶
Apply a boolean mask to all columns, returning a new StrikeArray.
to_dataframe() -> pd.DataFrame
¶
Return a copy of the internal DataFrame with hierarchical columns.
VolData
dataclass
¶
Coordinate-labelled smile data with bid/ask.
Parameters¶
strikearray : StrikeArray
Strike-indexed data containing at least y_bid and y_ask
columns. Optional volume and open_interest columns are
supported.
current_x_coord : XCoord
Which X-coordinate system the data is currently expressed in.
current_y_coord : YCoord
Which Y-coordinate system the data is currently expressed in.
metadata : SmileMetadata
Parameters needed by coordinate transforms.
native_x_coord: XCoord
property
¶
X-coordinate system the data was originally constructed in.
native_y_coord: YCoord
property
¶
Y-coordinate system the data was originally constructed in.
x: NDArray[np.float64]
property
¶
X-coordinate values in current coordinate system.
y_bid: NDArray[np.float64]
property
¶
Y-coordinate bid values in current coordinate system.
y_ask: NDArray[np.float64]
property
¶
Y-coordinate ask values in current coordinate system.
volume: NDArray[np.float64] | None
property
¶
Per-point traded volume, or None.
open_interest: NDArray[np.float64] | None
property
¶
Per-point open interest, or None.
y_mid: NDArray[np.float64]
property
¶
Midpoint of bid and ask Y values in current coordinate system.
__post_init__() -> None
¶
Validate inputs and record native coordinates.
transform(target_x: XCoord, target_y: YCoord) -> VolData
¶
Return a copy expressed in the target coordinate system.
This is lightweight: it shares the same underlying StrikeArray and only updates the current coordinate labels. Property accessors apply transforms lazily on access.
Parameters¶
target_x : XCoord Target X-coordinate system. target_y : YCoord Target Y-coordinate system.
Returns:¶
VolData New VolData in the target coordinates.
from_mid_vols(strikes: NDArray[np.float64], ivs: NDArray[np.float64], metadata: SmileMetadata) -> VolData
classmethod
¶
Create from mid implied vols (setting y_bid = y_ask = ivs).
Parameters¶
strikes : NDArray[np.float64]
Strike prices.
ivs : NDArray[np.float64]
Mid implied volatilities.
metadata : SmileMetadata
Smile metadata. metadata.forward must not be None.
sigma_atm is always recomputed from the data.
evaluate(x: ArrayLike) -> NDArray[np.float64]
¶
plot(*, title: str = 'Smile Data', ax=None, color='k', **kwargs) -> matplotlib.figure.Figure
¶
Plot bid/ask Y-values as error bars vs X.
Axis labels are derived from coordinate names.
SmileModel
dataclass
¶
Bases: ABC
Abstract base for dataclass-based smile models.
Provides coordinate-aware evaluation, transformation, plotting, and default serialisation. Subclasses must define:
- Dataclass fields for the fitted parameters
native_x_coord,native_y_coord,param_names,boundsClassVars_evaluate(x)instance method (raw formula in native coordinates)initial_guess(x, y)static method__post_init__()for validation (optional)
params: dict[str, float]
property
¶
Parameter name-to-value mapping.
__post_init__() -> None
¶
Set current coords to native if not already set.
to_array() -> NDArray[np.float64]
¶
Pack fitted parameters into a flat array using param_names order.
from_array(x: NDArray[np.float64], *, metadata: SmileMetadata) -> Self
classmethod
¶
Reconstruct an instance from a flat parameter array.
Fitted parameters are mapped from x using param_names.
initial_guess(x: NDArray[np.float64], y: NDArray[np.float64]) -> NDArray[np.float64]
abstractmethod
staticmethod
¶
Compute a heuristic initial guess from observed data.
evaluate(x: ArrayLike) -> NDArray[np.float64] | np.float64
¶
Evaluate at x in current coordinates, transforming as needed.
transform(target_x: XCoord, target_y: YCoord) -> Self
¶
Return a copy expressed in the target coordinate system.
plot(*, title: str = 'Smile Model', n_points: int = 200, std_range: tuple[float, float] = (-5.0, 2.0), ax=None, **kwargs) -> matplotlib.figure.Figure
¶
Plot the model curve in current coordinates.
Parameters¶
std_range : tuple[float, float] Plot range in standardised-strike units (sigma * sqrt(T)) as (lo, hi). Default (-5.0, 2.0).
SmileResult
dataclass
¶
Result of a smile model fit.
Attributes:¶
model : SmileModel Fitted model instance (coordinate-aware). residuals : NDArray[np.float64] Per-observation residuals (model minus observed values in native coordinates). rmse : float Root mean square error of the fit. success : bool Whether the optimiser converged.
SABRModel
dataclass
¶
Bases: SmileModel
SABR model with Hagan (2002) lognormal implied volatility approximation.
The SABR model describes the dynamics of a forward rate F and its stochastic volatility alpha via:
dF = alpha * F^beta * dW_1
dalpha = nu * alpha * dW_2
<dW_1, dW_2> = rho * dt
The Hagan et al. (2002) formula maps these parameters to a closed-form lognormal implied volatility approximation.
Fitted parameters (included in the parameter vector): alpha, beta, rho, nu
Context (provided via metadata):
expiry and forward are read from metadata.texpiry
and metadata.forward.
Parameters¶
alpha : float Initial volatility. Must be > 0. beta : float CEV exponent. Must be in [0, 1]. rho : float Correlation between forward and vol. Must be in (-1, 1). nu : float Vol-of-vol. Must be >= 0. metadata : SmileMetadata Market context containing expiry, forward, etc.
__post_init__() -> None
¶
Validate SABR parameter constraints.
SVIModel
dataclass
¶
Bases: SmileModel
Raw SVI parameterisation: model definition and fitted parameters.
The SVI raw parameterisation models total implied variance as:
w(k) = a + b * (rho * (k - m) + sqrt((k - m)^2 + sigma^2))
where k = ln(K/F) is log-moneyness.
Pass this class to fit() as the model, and receive instances
back as fitted parameters::
result = fit(sd, model=SVIModel)
result.params # → SVIModel instance
result.params.evaluate(k)
Parameters¶
a : float Vertical translation (overall variance level). b : float Slope of the wings. Must be >= 0. rho : float Correlation / rotation. Must be in (-1, 1). m : float Horizontal translation (log-moneyness shift). sigma : float Curvature at the vertex. Must be > 0.
__post_init__() -> None
¶
Validate SVI parameter constraints.
black76_call(forward: ArrayLike, strike: ArrayLike, discount_factor: ArrayLike, vol: ArrayLike, expiry: float) -> NDArray[np.float64] | np.floating
¶
Compute Black76 call option price.
C = D * [F * Phi(d1) - K * Phi(d2)]
Parameters¶
forward : ArrayLike Forward price. Must be positive. strike : ArrayLike Strike price. Must be positive. discount_factor : ArrayLike Discount factor. Must be positive. vol : ArrayLike Volatility. Must be non-negative. expiry : float Time to expiry in years. Must be positive.
black76_implied_vol(price: float, forward: float, strike: float, discount_factor: float, expiry: float, *, is_call: bool, tol: float = 1e-12, max_vol: float = 10.0) -> float
¶
Invert Black76 to recover implied volatility.
Uses the explicit closed-form solution of Schadner (2026), "An Explicit Solution to Black-Scholes Implied Volatility" (arXiv:2604.24480), which expresses implied volatility as a direct transform of the option price via the inverse Gaussian quantile function -- no root finding required.
For a call with normalized price c = C / (D F) and forward
log-moneyness k = log(K/F)::
sigma = 2 / sqrt(T * F_IG^{-1}((1-c)/m; 2/|k|, 1))
where m = 1 if K > F and m = K/F if K < F. At k = 0
the formula collapses to sigma = (2/sqrt(T)) * Phi^{-1}((c+1)/2).
The put case follows from put-call parity.
Parameters¶
price : float
Observed option price.
forward : float
Forward price. Must be positive.
strike : float
Strike price. Must be positive.
discount_factor : float
Discount factor. Must be positive.
expiry : float
Time to expiry in years. Must be positive.
is_call : bool
True for call, False for put.
tol : float
Tolerance for arbitrage-bound checks and the intrinsic-value
short-circuit (returns 0.0).
max_vol : float
Retained for backward compatibility. The closed-form solution does
not perform a search, so this argument is unused.
black76_put(forward: ArrayLike, strike: ArrayLike, discount_factor: ArrayLike, vol: ArrayLike, expiry: float) -> NDArray[np.float64] | np.floating
¶
Compute Black76 put option price.
P = D * [K * Phi(-d2) - F * Phi(-d1)]
Parameters¶
forward : ArrayLike Forward price. Must be positive. strike : ArrayLike Strike price. Must be positive. discount_factor : ArrayLike Discount factor. Must be positive. vol : ArrayLike Volatility. Must be non-negative. expiry : float Time to expiry in years. Must be positive.
delta_blend_ivols(call_bid_ivols: NDArray[np.float64], call_ask_ivols: NDArray[np.float64], put_bid_ivols: NDArray[np.float64], put_ask_ivols: NDArray[np.float64], strikes: NDArray[np.float64], forward: float, expiry: float) -> tuple[NDArray[np.float64], NDArray[np.float64]]
¶
Blend call and put implied vols using Black76 undiscounted call-delta weights.
At each strike K, the blending weight is w(K) = Phi(d1) where d1 = [ln(F/K) + 0.5 * sigma_C^2 * t] / (sigma_C * sqrt(t)) and sigma_C is the mid call-implied vol at that strike.
The blended vol is: sigma = w * sigma_C + (1 - w) * sigma_P. Bid and ask are blended independently with the same weights.
NaN values in input vols indicate inversion failures. At such strikes the blended vol falls back to the available option type. If neither is available, the strike is excluded (NaN in output).
Parameters¶
call_bid_ivols : NDArray[np.float64] Call-implied bid vols (NaN where inversion failed). call_ask_ivols : NDArray[np.float64] Call-implied ask vols (NaN where inversion failed). put_bid_ivols : NDArray[np.float64] Put-implied bid vols (NaN where inversion failed). put_ask_ivols : NDArray[np.float64] Put-implied ask vols (NaN where inversion failed). strikes : NDArray[np.float64] Strike prices. forward : float Forward price. expiry : float Time to expiry in years.
Returns:¶
tuple[NDArray[np.float64], NDArray[np.float64]] (blended_bid_ivols, blended_ask_ivols). Strikes where neither call nor put vol is available will have NaN.
fit(chain: VolData, model: type[SmileModel], initial_guess: SmileModel | None = None) -> SmileResult
¶
Fit a smile model to market data.
Parameters¶
chain : VolData
Market data to fit. Uses mid values for fitting.
Internally transforms to the model's native coordinates.
model : type[SmileModel]
A model class (e.g. SVIModel) that defines native coordinates,
bounds, evaluation, and initial-guess heuristic.
initial_guess : SmileModel, optional
Initial parameter guess (e.g. an SVIModel(...) instance).
If None, the model's heuristic initial guess is computed from data.
Returns:¶
SmileResult Fitted model, residuals, RMSE, and convergence status.
black76
¶
Black76 forward option pricing and implied volatility inversion.
black76_call(forward: ArrayLike, strike: ArrayLike, discount_factor: ArrayLike, vol: ArrayLike, expiry: float) -> NDArray[np.float64] | np.floating
¶
Compute Black76 call option price.
C = D * [F * Phi(d1) - K * Phi(d2)]
Parameters¶
forward : ArrayLike Forward price. Must be positive. strike : ArrayLike Strike price. Must be positive. discount_factor : ArrayLike Discount factor. Must be positive. vol : ArrayLike Volatility. Must be non-negative. expiry : float Time to expiry in years. Must be positive.
black76_put(forward: ArrayLike, strike: ArrayLike, discount_factor: ArrayLike, vol: ArrayLike, expiry: float) -> NDArray[np.float64] | np.floating
¶
Compute Black76 put option price.
P = D * [K * Phi(-d2) - F * Phi(-d1)]
Parameters¶
forward : ArrayLike Forward price. Must be positive. strike : ArrayLike Strike price. Must be positive. discount_factor : ArrayLike Discount factor. Must be positive. vol : ArrayLike Volatility. Must be non-negative. expiry : float Time to expiry in years. Must be positive.
black76_implied_vol(price: float, forward: float, strike: float, discount_factor: float, expiry: float, *, is_call: bool, tol: float = 1e-12, max_vol: float = 10.0) -> float
¶
Invert Black76 to recover implied volatility.
Uses the explicit closed-form solution of Schadner (2026), "An Explicit Solution to Black-Scholes Implied Volatility" (arXiv:2604.24480), which expresses implied volatility as a direct transform of the option price via the inverse Gaussian quantile function -- no root finding required.
For a call with normalized price c = C / (D F) and forward
log-moneyness k = log(K/F)::
sigma = 2 / sqrt(T * F_IG^{-1}((1-c)/m; 2/|k|, 1))
where m = 1 if K > F and m = K/F if K < F. At k = 0
the formula collapses to sigma = (2/sqrt(T)) * Phi^{-1}((c+1)/2).
The put case follows from put-call parity.
Parameters¶
price : float
Observed option price.
forward : float
Forward price. Must be positive.
strike : float
Strike price. Must be positive.
discount_factor : float
Discount factor. Must be positive.
expiry : float
Time to expiry in years. Must be positive.
is_call : bool
True for call, False for put.
tol : float
Tolerance for arbitrage-bound checks and the intrinsic-value
short-circuit (returns 0.0).
max_vol : float
Retained for backward compatibility. The closed-form solution does
not perform a search, so this argument is unused.
coords
¶
daycount
¶
maps
¶
Coordinate transform maps and composition.
compose_x_maps(source: XCoord, target: XCoord) -> list[tuple[XCoord, XCoord, XMapFn]]
¶
Return the chain of X-maps needed to go from source to target.
compose_y_maps(source: YCoord, target: YCoord) -> list[tuple[YCoord, YCoord, YMapFn]]
¶
Return the chain of Y-maps needed to go from source to target.
apply_x_chain(x: NDArray[np.float64], chain: list[tuple[XCoord, XCoord, XMapFn]], meta: SmileMetadata) -> NDArray[np.float64]
¶
Apply a chain of X-maps sequentially.
apply_y_chain(y: NDArray[np.float64], x: NDArray[np.float64], chain: list[tuple[YCoord, YCoord, YMapFn]], meta: SmileMetadata, x_coord: XCoord, target_x: XCoord) -> NDArray[np.float64]
¶
Apply a chain of Y-maps sequentially.
For the Price↔Volatility step, X must be in FixedStrike. If needed, temporarily converts X to FixedStrike and back.
plot
¶
Plotting utilities for option chain representations.
plot_bid_ask(x, mid, lower, upper, *, xlabel: str = '', ylabel: str = '', title: str = '', label: str | None = None, color: str | None = None, fmt: str = 'none', ax=None, **kwargs) -> matplotlib.figure.Figure
¶
Plot bid/ask as error bars around mid values.
Parameters¶
x : array-like X-axis values (e.g., strikes or unitised k). mid : array-like Mid values. lower : array-like Lower bound (bid). upper : array-like Upper bound (ask). xlabel, ylabel, title : str Axis labels and title. label : str, optional Legend label. color : str, optional Color for the series. ax : matplotlib Axes, optional Axes to plot on. If None, creates a new figure.
Returns:¶
matplotlib.figure.Figure
plot_line(x, y, *, xlabel: str = '', ylabel: str = '', title: str = '', label: str | None = None, color: str | None = None, ax=None, **kwargs) -> matplotlib.figure.Figure
¶
Plot a single curve.
Parameters¶
x : array-like X-axis values. y : array-like Y-axis values. xlabel, ylabel, title : str Axis labels and title. label : str, optional Legend label. color : str, optional Color for the line. ax : matplotlib Axes, optional Axes to plot on. If None, creates a new figure.
Returns:¶
matplotlib.figure.Figure
meta
¶
Smile metadata for coordinate transforms.
SmileMetadata
dataclass
¶
Parameters needed by coordinate transforms.
Parameters¶
date : pd.Timestamp
Valuation / pricing date.
expiry : pd.Timestamp
Expiry date. Must be strictly after date.
daycount : DayCount
Day-count convention for computing the year fraction.
Defaults to DayCount.ACT365.
forward : float | None
Forward price. Must be positive when provided.
discount_factor : float | None
Discount factor. Must be positive when provided.
sigma_atm : float | None
ATM implied volatility. Must be positive when provided.
Required for StandardisedStrike transforms.
prices
¶
Bid/ask option price chain with forward/DF calibration.
OptionChain
dataclass
¶
Bid/ask option price chain for a single expiry.
Parameters¶
strikedata : StrikeArray
Strike-indexed columnar data containing at least call_bid,
call_ask, put_bid, and put_ask columns.
Optional volume and open_interest columns are supported.
metadata : SmileMetadata
Smile metadata. expiry must be provided.
forward and discount_factor are calibrated from
put-call parity if None.
strikes: NDArray[np.float64]
property
¶
Strike prices.
call_bid: NDArray[np.float64]
property
¶
Call bid prices.
call_ask: NDArray[np.float64]
property
¶
Call ask prices.
put_bid: NDArray[np.float64]
property
¶
Put bid prices.
put_ask: NDArray[np.float64]
property
¶
Put ask prices.
volume: NDArray[np.float64] | None
property
¶
Per-strike traded volume, or None.
open_interest: NDArray[np.float64] | None
property
¶
Per-strike open interest, or None.
call_mid: NDArray[np.float64]
property
¶
Midpoint of call bid and ask prices.
put_mid: NDArray[np.float64]
property
¶
Midpoint of put bid and ask prices.
__post_init__() -> None
¶
Validate inputs and calibrate forward/DF if needed.
__repr__() -> str
¶
Compact repr with date, expiry, forward, and discount factor.
to_vols() -> VolData
¶
Convert to a VolData with (FixedStrike, Volatility) using delta-blended vols.
Inverts both call and put prices to implied vols at every strike, then blends them using Black76 undiscounted call-delta weights. OTM options dominate in each wing; ATM is approximately equal-weighted.
Strikes where neither call nor put vol can be computed are excluded.
filter() -> OptionChain
¶
Return a cleaned copy with stale and implausible quotes removed.
Applies five filters in sequence:
- Zero-bid filter -- removes strikes where either the call or put bid is zero (no genuine two-sided market).
- Put-call parity monotonicity -- C_mid - P_mid must be strictly decreasing in strike (since it equals D*(F - K)). Strikes that break monotonicity carry stale or mismarked quotes and are dropped.
- Call- and put-mid monotonicity -- call mids must be non-increasing and put mids non-decreasing in strike. Any remaining violations are removed.
- Sub-intrinsic filter -- removes strikes where the call or put bid falls below intrinsic value (using the calibrated forward), which indicates illiquid deep-ITM or stale quotes.
- Parity residual filter -- removes strikes where the put-call parity residual |C_mid - P_mid - D*(F - K)| exceeds 3x the combined half bid-ask spread, indicating a stale or mispriced deep-ITM quote.
Returns:¶
OptionChain
A new OptionChain with the noisy strikes removed and
forward / discount_factor re-calibrated on the clean data.
plot(*, title: str = 'Option Chain Prices', ax=None, **kwargs) -> matplotlib.figure.Figure
¶
Plot bid/ask prices as error bars for calls and puts.
delta_blend_ivols(call_bid_ivols: NDArray[np.float64], call_ask_ivols: NDArray[np.float64], put_bid_ivols: NDArray[np.float64], put_ask_ivols: NDArray[np.float64], strikes: NDArray[np.float64], forward: float, expiry: float) -> tuple[NDArray[np.float64], NDArray[np.float64]]
¶
Blend call and put implied vols using Black76 undiscounted call-delta weights.
At each strike K, the blending weight is w(K) = Phi(d1) where d1 = [ln(F/K) + 0.5 * sigma_C^2 * t] / (sigma_C * sqrt(t)) and sigma_C is the mid call-implied vol at that strike.
The blended vol is: sigma = w * sigma_C + (1 - w) * sigma_P. Bid and ask are blended independently with the same weights.
NaN values in input vols indicate inversion failures. At such strikes the blended vol falls back to the available option type. If neither is available, the strike is excluded (NaN in output).
Parameters¶
call_bid_ivols : NDArray[np.float64] Call-implied bid vols (NaN where inversion failed). call_ask_ivols : NDArray[np.float64] Call-implied ask vols (NaN where inversion failed). put_bid_ivols : NDArray[np.float64] Put-implied bid vols (NaN where inversion failed). put_ask_ivols : NDArray[np.float64] Put-implied ask vols (NaN where inversion failed). strikes : NDArray[np.float64] Strike prices. forward : float Forward price. expiry : float Time to expiry in years.
Returns:¶
tuple[NDArray[np.float64], NDArray[np.float64]] (blended_bid_ivols, blended_ask_ivols). Strikes where neither call nor put vol is available will have NaN.
strikes
¶
Strike-indexed columnar data with hierarchical MultiIndex columns.
StrikeArray
¶
A mutable collection of named columns indexed by strike price.
Columns are stored in a pd.DataFrame with a two-level MultiIndex
on columns (level-0 = category, level-1 = field). Callers address
columns directly via tuple[str, str] keys such as ("call", "bid").
When a new column is added whose strike index differs from the current global index, all columns are reindexed to the sorted union of strikes.
strikes: NDArray[np.float64]
property
¶
Common strike index as a sorted NDArray.
columns: list[tuple[str, str]]
property
¶
Column keys in insertion order.
__init__() -> None
¶
Create an empty StrikeArray.
set(key: tuple[str, str], series: pd.Series) -> None
¶
Add or replace a column, updating the global strike index.
values(key: tuple[str, str]) -> NDArray[np.float64]
¶
Get column values as an NDArray. Raises KeyError if absent.
get_values(key: tuple[str, str]) -> NDArray[np.float64] | None
¶
Get column values as an NDArray, or None if absent.
has(key: tuple[str, str]) -> bool
¶
Check whether a column exists.
__len__() -> int
¶
Return the number of strikes.
filter(mask: NDArray[np.bool_]) -> StrikeArray
¶
Apply a boolean mask to all columns, returning a new StrikeArray.
to_dataframe() -> pd.DataFrame
¶
Return a copy of the internal DataFrame with hierarchical columns.
vols
¶
Unified smile data container with coordinate transforms.
VolData
dataclass
¶
Coordinate-labelled smile data with bid/ask.
Parameters¶
strikearray : StrikeArray
Strike-indexed data containing at least y_bid and y_ask
columns. Optional volume and open_interest columns are
supported.
current_x_coord : XCoord
Which X-coordinate system the data is currently expressed in.
current_y_coord : YCoord
Which Y-coordinate system the data is currently expressed in.
metadata : SmileMetadata
Parameters needed by coordinate transforms.
native_x_coord: XCoord
property
¶
X-coordinate system the data was originally constructed in.
native_y_coord: YCoord
property
¶
Y-coordinate system the data was originally constructed in.
x: NDArray[np.float64]
property
¶
X-coordinate values in current coordinate system.
y_bid: NDArray[np.float64]
property
¶
Y-coordinate bid values in current coordinate system.
y_ask: NDArray[np.float64]
property
¶
Y-coordinate ask values in current coordinate system.
volume: NDArray[np.float64] | None
property
¶
Per-point traded volume, or None.
open_interest: NDArray[np.float64] | None
property
¶
Per-point open interest, or None.
y_mid: NDArray[np.float64]
property
¶
Midpoint of bid and ask Y values in current coordinate system.
__post_init__() -> None
¶
Validate inputs and record native coordinates.
transform(target_x: XCoord, target_y: YCoord) -> VolData
¶
Return a copy expressed in the target coordinate system.
This is lightweight: it shares the same underlying StrikeArray and only updates the current coordinate labels. Property accessors apply transforms lazily on access.
Parameters¶
target_x : XCoord Target X-coordinate system. target_y : YCoord Target Y-coordinate system.
Returns:¶
VolData New VolData in the target coordinates.
from_mid_vols(strikes: NDArray[np.float64], ivs: NDArray[np.float64], metadata: SmileMetadata) -> VolData
classmethod
¶
Create from mid implied vols (setting y_bid = y_ask = ivs).
Parameters¶
strikes : NDArray[np.float64]
Strike prices.
ivs : NDArray[np.float64]
Mid implied volatilities.
metadata : SmileMetadata
Smile metadata. metadata.forward must not be None.
sigma_atm is always recomputed from the data.
evaluate(x: ArrayLike) -> NDArray[np.float64]
¶
plot(*, title: str = 'Smile Data', ax=None, color='k', **kwargs) -> matplotlib.figure.Figure
¶
Plot bid/ask Y-values as error bars vs X.
Axis labels are derived from coordinate names.
io
¶
Load option chain data from parquet files.
SampleDataReader
¶
Read option chain parquet files from a directory.
Parameters¶
root : str | Path | None
Directory containing chains/*.parquet files.
Defaults to <project_root>/parquet.
__init__(root: str | Path | None = None) -> None
¶
Create a reader backed by a parquet directory.
Parameters¶
root : str | Path | None
Directory containing chains/*.parquet files.
Defaults to <project_root>/parquet.
get_chain(underlying: str, fetch_date: str, expiry_date: str) -> OptionChain
¶
Load an option chain from parquet and return an OptionChain.
Parameters¶
underlying : str
Ticker symbol, e.g. "SPX".
fetch_date : str
Fetch / pricing date in YYYY-MM-DD format.
expiry_date : str
Expiry date in YYYY-MM-DD format.
Returns:¶
OptionChain Fully constructed option chain with metadata and strike data.
base
¶
SmileModel abstract base class.
SmileModel
dataclass
¶
Bases: ABC
Abstract base for dataclass-based smile models.
Provides coordinate-aware evaluation, transformation, plotting, and default serialisation. Subclasses must define:
- Dataclass fields for the fitted parameters
native_x_coord,native_y_coord,param_names,boundsClassVars_evaluate(x)instance method (raw formula in native coordinates)initial_guess(x, y)static method__post_init__()for validation (optional)
params: dict[str, float]
property
¶
Parameter name-to-value mapping.
__post_init__() -> None
¶
Set current coords to native if not already set.
to_array() -> NDArray[np.float64]
¶
Pack fitted parameters into a flat array using param_names order.
from_array(x: NDArray[np.float64], *, metadata: SmileMetadata) -> Self
classmethod
¶
Reconstruct an instance from a flat parameter array.
Fitted parameters are mapped from x using param_names.
initial_guess(x: NDArray[np.float64], y: NDArray[np.float64]) -> NDArray[np.float64]
abstractmethod
staticmethod
¶
Compute a heuristic initial guess from observed data.
evaluate(x: ArrayLike) -> NDArray[np.float64] | np.float64
¶
Evaluate at x in current coordinates, transforming as needed.
transform(target_x: XCoord, target_y: YCoord) -> Self
¶
Return a copy expressed in the target coordinate system.
plot(*, title: str = 'Smile Model', n_points: int = 200, std_range: tuple[float, float] = (-5.0, 2.0), ax=None, **kwargs) -> matplotlib.figure.Figure
¶
Plot the model curve in current coordinates.
Parameters¶
std_range : tuple[float, float] Plot range in standardised-strike units (sigma * sqrt(T)) as (lo, hi). Default (-5.0, 2.0).
result
¶
Smile fitting engine.
SmileResult
dataclass
¶
Result of a smile model fit.
Attributes:¶
model : SmileModel Fitted model instance (coordinate-aware). residuals : NDArray[np.float64] Per-observation residuals (model minus observed values in native coordinates). rmse : float Root mean square error of the fit. success : bool Whether the optimiser converged.
fit(chain: VolData, model: type[SmileModel], initial_guess: SmileModel | None = None) -> SmileResult
¶
Fit a smile model to market data.
Parameters¶
chain : VolData
Market data to fit. Uses mid values for fitting.
Internally transforms to the model's native coordinates.
model : type[SmileModel]
A model class (e.g. SVIModel) that defines native coordinates,
bounds, evaluation, and initial-guess heuristic.
initial_guess : SmileModel, optional
Initial parameter guess (e.g. an SVIModel(...) instance).
If None, the model's heuristic initial guess is computed from data.
Returns:¶
SmileResult Fitted model, residuals, RMSE, and convergence status.
sabr
¶
SABR stochastic volatility model — Hagan et al. (2002) lognormal approximation.
SABRModel
dataclass
¶
Bases: SmileModel
SABR model with Hagan (2002) lognormal implied volatility approximation.
The SABR model describes the dynamics of a forward rate F and its stochastic volatility alpha via:
dF = alpha * F^beta * dW_1
dalpha = nu * alpha * dW_2
<dW_1, dW_2> = rho * dt
The Hagan et al. (2002) formula maps these parameters to a closed-form lognormal implied volatility approximation.
Fitted parameters (included in the parameter vector): alpha, beta, rho, nu
Context (provided via metadata):
expiry and forward are read from metadata.texpiry
and metadata.forward.
Parameters¶
alpha : float Initial volatility. Must be > 0. beta : float CEV exponent. Must be in [0, 1]. rho : float Correlation between forward and vol. Must be in (-1, 1). nu : float Vol-of-vol. Must be >= 0. metadata : SmileMetadata Market context containing expiry, forward, etc.
__post_init__() -> None
¶
Validate SABR parameter constraints.
svi
¶
SVI (Stochastic Volatility Inspired) raw parameterisation.
SVIModel
dataclass
¶
Bases: SmileModel
Raw SVI parameterisation: model definition and fitted parameters.
The SVI raw parameterisation models total implied variance as:
w(k) = a + b * (rho * (k - m) + sqrt((k - m)^2 + sigma^2))
where k = ln(K/F) is log-moneyness.
Pass this class to fit() as the model, and receive instances
back as fitted parameters::
result = fit(sd, model=SVIModel)
result.params # → SVIModel instance
result.params.evaluate(k)
Parameters¶
a : float Vertical translation (overall variance level). b : float Slope of the wings. Must be >= 0. rho : float Correlation / rotation. Must be in (-1, 1). m : float Horizontal translation (log-moneyness shift). sigma : float Curvature at the vertex. Must be > 0.
__post_init__() -> None
¶
Validate SVI parameter constraints.