Expand description
§sanos
Smooth, arbitrage-aware option surface calibration for Rust.
sanos is a Rust crate for building smooth option surfaces from quoted option
markets while keeping tight control over arbitrage consistency, strike-grid design,
and calibration behavior.
It is aimed at developers building:
- derivatives analytics backends
- research pipelines for smile and surface fitting
- pricing or risk tools that need a queryable calibrated surface
- Rust-native alternatives to ad hoc notebook calibration stacks
API docs: https://docs.rs/sanos
§Why use sanos
What you get out of the box:
- validated market data types (
CallQuote,OptionChain,OptionBook) - a high-level calibration API (
calibrate,calibrate_with_stats) - a reusable calibrated surface object (
SanosSurface) - support for smooth term structures and strike interpolation
- no-arbitrage diagnostics you can expose in your own tooling
- optional serialization support via
serde
Why it fits well in a Rust stack:
- strong type-level validation at the market-data boundary
- no Python dependency in the crate itself
- easy embedding in services, CLIs, batch jobs, or research binaries
- feature-gated optional components instead of a monolithic runtime
§What it looks like in practice
The examples below show the kind of calibration outputs you can generate with
sanos: price fit, IV fit, density reconstruction, and compact quality diagnostics.
§sabr_catalog_02_equity_like_moderate_skew with tight_spread
A clean equity-like skew fit.
Price fit:

IV fit:

Density:

Performance summary:

§svi_raw_catalog_06_shifted_smile_center_right_long_end_focus with strong_wings
A shifted smile with long-end structure.
Price fit:

IV fit:

Density:

Performance summary:

§tv_catalog_07_inverted_term_structure with tight_spread
An inverted term-structure example with a clean global fit.
Price fit:

IV fit:

Density:

Performance summary:

§Installation
[dependencies]
sanos = "0.2"§30-second example
use sanos::calibration::{calibrate, CalibrationConfig};
use sanos::error::SanosResult;
use sanos::market::OptionBook;
fn run(book: &OptionBook, cfg: &CalibrationConfig) -> SanosResult<f64> {
let surface = calibrate(book, cfg)?;
surface.call(1.0, 1.0)
}§Core workflow
sanos is designed for workflows where you need more than a pointwise smile fit:
- enforce calendar / convexity / monotonicity consistency at the surface level
- keep control over the strike grid and regularization regime
- inspect fit quality through residual, density, and smoothness diagnostics
- serialize configs and surface outputs for offline research pipelines
§Input Conventions
- Quotes are forward-normalized call prices in
[0, 1]. - Strikes are forward moneyness (
k > 0). - Maturities must be strictly increasing in
OptionBook. - Use at least 2 maturities if you want to query interpolated surface prices (
surface.call).
§Public API at a glance
market: validated option quotes, chains, and bookscalibration: top-level calibration entry points and runtime configsurface: calibratedSanosSurfacequeriesbackbone: backbone models and ATM term-structure helpersfit: fitting config, weighting, regularization, and warm start controlsgrid: strike-grid policies for calibration and exportinterp: time interpolation choicesdensity: marginal and martingale density diagnosticsterm: term-structure utilities
§Typical use cases
- Calibrate a surface once and query prices at arbitrary maturities and strikes.
- Build a research pipeline that compares calibration policies or regularization regimes.
- Export calibrated surfaces and diagnostics into your own storage or reporting layer.
- Validate whether a quoted book is fit for downstream pricing or risk calculations.
§End-to-End Example (OptionBook + Config)
use sanos::backbone::{bs_call_forward_norm, BackboneConfig, BsTimeChangedConfig};
use sanos::calibration::{calibrate, CalibrationConfig, ConvexOrderValidationMode};
use sanos::error::SanosResult;
use sanos::fit::FitConfig;
use sanos::grid::StrikeGridPolicyConfig;
use sanos::interp::TimeInterpConfig;
use sanos::market::{CallQuote, OptionBook, OptionChain};
fn build_synthetic_book() -> SanosResult<OptionBook> {
// Two maturities with synthetic ATM total variances.
let maturities = [0.5, 1.0];
let total_vars = [0.04, 0.09];
let strikes = [0.8, 0.9, 1.0, 1.1, 1.2];
let mut chains = Vec::new();
for (t, w) in maturities.into_iter().zip(total_vars) {
let mut quotes = Vec::new();
for k in strikes {
let mid = bs_call_forward_norm(k, w)?;
let spread = 0.01;
let bid = (mid - 0.5 * spread).clamp(0.0, 1.0);
let ask = (mid + 0.5 * spread).clamp(0.0, 1.0);
quotes.push(CallQuote::new(k, bid, ask, 1.0)?);
}
chains.push(OptionChain::new(t, quotes)?);
}
OptionBook::new(chains)
}
fn build_config() -> CalibrationConfig {
CalibrationConfig {
backbone: BackboneConfig::BsTimeChanged(BsTimeChangedConfig::default()),
grid: StrikeGridPolicyConfig::default(),
fit: FitConfig::default(),
time_interp: TimeInterpConfig::default(),
convex_order_validation: ConvexOrderValidationMode::Error,
}
}
fn main() -> SanosResult<()> {
let book = build_synthetic_book()?;
let cfg = build_config();
let surface = calibrate(&book, &cfg)?;
let c = surface.call(0.75, 1.0)?;
println!("Interpolated call(T=0.75, K=1.0) = {c:.6}");
Ok(())
}§Config Example
use sanos::backbone::{BackboneConfig, BsTimeChangedConfig};
use sanos::calibration::{CalibrationConfig, ConvexOrderValidationMode};
use sanos::fit::FitConfig;
use sanos::grid::StrikeGridPolicyConfig;
use sanos::interp::TimeInterpConfig;
let cfg = CalibrationConfig {
backbone: BackboneConfig::BsTimeChanged(BsTimeChangedConfig::default()),
grid: StrikeGridPolicyConfig::default(),
fit: FitConfig::default(), // default solver = Microlp
time_interp: TimeInterpConfig::default(),
convex_order_validation: ConvexOrderValidationMode::Error,
};§Feature Flags
lp-microlp(default): pure-Rust LP solver backend.lp-cbc: CBC backend viagood_lp/lp-solvers(requires CBC runtime).iv-jaeckel(default): implied-vol inversion support.serde: serialization support for config/runtime types.
When selecting a solver in configuration, the matching crate feature must be enabled.
§Diagnostics you can expose in your application
The crate focuses on the calibration engine, and is designed to surface metrics that are useful in production and research:
- inside bid/ask ratio for positive-spread books
- normalized residuals versus half-spread
- implied-vol MAE / RMSE
- total variation and max second difference of the model IV smile
- strike monotonicity, convexity, and calendar no-arbitrage violations
- density reconstruction quality and projection diagnostics
§Scope
This crate is self-contained and usable as-is for building option books, calibrating SANOS surfaces, and querying interpolated prices.
§Known limitations
- The current public crate is the core library. The JSON schema, I/O helpers, and CLI remain workspace crates and are not yet published as stable crates.io APIs.
- Complex stressed markets can still require tuning of weighting, warm-start, and grid policies.
- For production rollout, you should keep your own acceptance thresholds on fit quality and no-arbitrage diagnostics.
§Research Attribution
This crate is an independent implementation of the SANOS methodology described in:
- “SANOS: Smooth strictly Arbitrage-free Non-parametric Option Surfaces” (arXiv:2601.11209)
- URL: https://arxiv.org/abs/2601.11209
The code in this repository is original Rust code released under MIT (LICENSE), and is not a copy of the paper text.
§Non-Affiliation
This project is not affiliated with, endorsed by, or maintained by the authors of the SANOS paper.