RustyQLib 0.0.3

RustyQLib is a lightweight yet robust quantitative finance library designed to price derivatives and perform risk analysis
Documentation

Build and Tests License: MIT Crates.io Crates.io codecov

RustyQLib — Pricing Options using JSON or XML

RustyQLib is a lightweight quantitative finance library written entirely in Rust. It prices equity derivatives through JSON or XML contracts (a stateless pricing service in a single binary) or as a Rust library, with an emphasis on numerically validated implementations: every pricer is cross-checked against independent oracles, put-call parity, replication identities and cross-engine agreement in the test suite.

Highlights

  • Six pricing engines — analytic closed forms, two analytic American approximations (Barone-Adesi-Whaley and Bjerksund-Stensland 2002), binomial tree, finite difference (log-spot Crank-Nicolson with Rannacher smoothing), and parallel Monte Carlo — behind one dispatch, so the same contract prices on any suitable engine.
  • Four volatility frameworks — Black-Scholes, Dupire local volatility (calibrated non-parametrically from an implied vol surface), Heston stochastic volatility (semi-analytic characteristic-function pricing + Monte Carlo) with Bates jump-diffusion extensions (Heston + lognormal Merton jumps, and Heston + Kou double-exponential jumps, both priced semi-analytically through the shared characteristic-function machinery and calibrated by the same Levenberg-Marquardt transform-space pattern), stochastic local volatility (Heston-style variance times a leverage function calibrated to the Dupire surface by the particle / binning method, so vanillas reprice while forward smiles stay stochastic), and SVI / SSVI parametric implied surfaces (Gatheral, Gatheral-Jacquier) — Levenberg-Marquardt smile and surface calibration, Gatheral-Jacquier butterfly (g(k)) and calendar no-arbitrage checks, and sampling into the pricing vol surface.
  • JSON and XML contracts and results, over a single shared schema.
  • Market-standard infrastructure — discount curves with discount factors as the source of truth (flat / zero rates / discount factors / forward rates in, any compounding), volatility surfaces (flat, strike x expiry, moneyness, FX-style delta quotes), robust implied vol, day counts, term-structure-consistent PDE discounting, and holiday calendars with business-day conventions (weekends-only, TARGET, NYSE, UK bank holidays, custom lists — rule-based, any year): date adjustment (following / modified following / preceding), settlement lags (add_business_days), and periodic schedule generation (forward/backward with stubs). Autocallable observation dates can be calendar-generated (.autocall_schedule(3, Calendar::UsNyse)) or supplied explicitly (autocall_observation_dates in JSON), so observations never land on weekends or holidays.
  • Options on futures: European vanillas priced with Black-76, both standard (discounted premium) and futures-style (margined, undiscounted).
  • Payoffs: European & American vanillas, cash- and asset-or-nothing binaries, all eight barrier types (knock-in/out, up/down) with rebates (at hit or at expiry) and double-barrier corridors (Ikeda-Kunitomo), Asian options (arithmetic / geometric, fixed / floating strike), forward-start options, autocallable notes with coupons and knock-in protection (incl. Phoenix certificates with conditional and memory coupons), lookback options (floating and fixed strike, Goldman-Sosin-Gatto / Conze-Viswanathan closed forms), accumulators / decumulators (daily geared accrual with knock-out, priced as a strip of barrier-option pairs or by Monte Carlo), variance, gamma and corridor variance swaps (model-free replication over any smile: DDKZ log contract, spot-weighted S ln S contract with exact carry adjustment, Carr-Lewis corridor truncation with exact corridor additivity; seasoned MtM with accrued realized variance and the exact GBM volatility-swap strike), cliquets / ratchets, reverse cliquets and Napoleons (capped-floored return strips, coupon-minus-losses and coupon-plus-worst-month structures; closed forms under Black-Scholes where the payoff permits, Monte Carlo under GBM or Heston), and multi-asset rainbow options (best-of, worst-of, spread, basket, exchange) on n correlated assets. Carry handles dividend yield, discrete cash dividends and stock borrow cost.

Products and engines

Payoff Analytic Binomial Finite difference Monte Carlo
Vanilla European Black-Scholes / Heston CF yes yes (grid Greeks) yes (+ stderr)
Vanilla on a future Black-76 (discounted / margined)
Vanilla American Barone-Adesi-Whaley / Bjerksund-Stensland 2002 (approx.) yes Brennan-Schwartz two-pass Longstaff-Schwartz
Vanilla Bermudan (discrete exercise dates) yes Brennan-Schwartz on exercise dates LSMC restricted to exercise dates
Perpetual American Merton closed form (exact)
Binary (cash / asset) closed form / Heston CF yes yes (Rannacher + cell averaging) yes
Barrier (8 types, + rebates at hit / at expiry) Reiner-Rubinstein + E/F rebate terms absorbing boundary / parity Brownian-bridge corrected (rebate at expiry)
Double barrier (knock-in / knock-out corridor) Ikeda-Kunitomo image series discrete corridor monitoring
Asian (arith / geo, fixed / floating) Turnbull-Wakeman (fixed + Henderson-Wojakowski average-strike) / exact geometric (fixed + average-strike) geometric control variate
Forward-start Rubinstein (BS) yes (incl. Heston forward smile)
Autocallable / Phoenix (conditional + memory coupons) multi-date discounting; GBM / local vol / Heston
Rainbow (best/worst-of, spread, basket, exchange) Margrabe / Kirk / moment matching correlated terminal GBM
Accumulator / decumulator (geared, knock-out) strip of Reiner-Rubinstein knock-out pairs discrete daily knockout
Lookback (floating / fixed strike) Goldman-Sosin-Gatto / Conze-Viswanathan (continuous) discrete monitoring
Variance / gamma / corridor variance swap (+ GBM vol-swap strike) model-free replication: DDKZ 1/K^2 kernel, S ln S contract with carry adjustment, Carr-Lewis corridor truncation
Cliquet / ratchet / reverse cliquet / Napoleon forward-start call/put spreads (no global clamp; Napoleon MC-only) per-period GBM or Heston paths

Model availability: local vol runs on the FD and MC engines; Heston runs on the analytic (vanilla + binary) and MC engines (all payoffs above except American and rainbow). Rainbow options are a separate product type ("product_type": "rainbow_option") with per-asset spots/vols/dividends and a correlation matrix; outputs include per-asset deltas and vegas.

Engine details

  • Binomial lattice: six parameterizations behind one enum — Leisen-Reimer (the default: strike-aware Peizer-Pratt, second-order smooth convergence, at ~100 steps it matches CRR at 1000 — ~90x faster), Cox-Ross-Rubinstein, Jarrow-Rudd, Tian, Trigeorgis and the additive equal-probability tree — selected per contract (tree_type, tree_steps). The engine lives in core::lattice, asset-class agnostic (payoffs and early exercise enter as closures), with two implementations: an optimized rolling-array engine (O(n) memory) and a diagnostic engine that keeps the full spot/value trees, the early-exercise boundary per layer, tree Greeks and wall-clock timing, plus a convergence_study helper for step-ladder analysis. A trinomial lattice rounds out core::lattice for fixed income: per-node branching (Hull-White edge-switching included), per-node discounting (the short rate lives on the node), and Arrow-Debreu state prices by forward induction — validated by repricing the Vasicek zero-coupon bond closed form on a mean-reverting clamped tree. A term-structure lattice (tree_term_structure in JSON, .tree_term_structure() on the builder) applies time-dependent parameters directly on the tree: a variance-equal time grid keeps the tree recombining under a vol term structure, while per-step probabilities and discount factors take each step's forward rate and carry from the curve — so rate timing (not just the zero rate to maturity) prices into early exercise.
  • Finite difference: theta-scheme in log-spot with per-node, per-step coefficients (local vol ready), forward rates from the discount curve per time step, cell-averaged terminal conditions for digitals, barrier-aligned absorbing boundaries, and delta/gamma/theta read directly off the grid. Grid sizes are configurable per contract. The numerical kernels live in a reusable core::fd_solvers toolkit covering 1-D to 3-D problems: Thomas tridiagonal, Brennan-Schwartz and PSOR obstacle solvers (one- and two-sided), tensor-grid axis operators, and Douglas / Hundsdorfer-Verwer ADI time steppers with explicit mixed-derivative (correlation) terms — the machinery a 2-D Heston or hybrid three-factor PDE needs.
  • Monte Carlo: deterministic per-path RNG streams (bit-reproducible under rayon parallelism), low-discrepancy sampling through a Brownian bridge, exact/Euler/Milstein stepping, antithetic + moment matching, geometric control variates for Asians, Brownian-bridge barrier monitoring, and standard errors reported with every price. Greeks via common-random-number bumps.
  • Analytic American approximations: Barone-Adesi-Whaley (pricer: "BAW", quadratic approximation with a Newton solve for the exercise boundary) and Bjerksund-Stensland 2002 (pricer: "BS2002", two-step flat exercise boundary priced in closed form via the cumulative bivariate normal — a lower bound on the true price, generally the tighter of the two). Both price in under a microsecond, within a few cents of a fine tree, and return true American Greeks (unlike the tree, whose Greeks fall back to the European closed form). Use them when speed matters more than the last basis point. Perpetual American calls and puts have exact Merton closed forms (equity::perpetual), verified against the stationary pricing ODE, smooth pasting and the finite-maturity limit.
  • Calibration workflow: quoted option prices -> robust implied vols (safeguarded Newton with arbitrage bounds) -> implied surface -> Dupire local vol -> reprice anything, including barriers under smile dynamics. Heston calibration fits all five parameters to vanilla quotes by Levenberg-Marquardt in an unconstrained transform space (log/atanh). The Heston/Bates calibration objectives price through the COS method (Fang-Oosterlee Fourier-cosine expansion): one characteristic-function sweep per expiry prices the whole strike strip, making a 20-strike smile ~90x faster than per-strike integration (heston::cos_smile). The truncation range comes from numerically estimated cumulants (including the kurtosis term, so Feller-violated fat tails stay covered), deep ITM prices recover via put-call parity from the CF-implied forward, and the legacy P1/P2 integration is kept as the independent cross-check oracle — the two methods agree to ~1e-6 across the test grid.
  • Risk analytics (risk): Value-at-Risk and Expected Shortfall in the standard flavors — historical, parametric normal, Cornish-Fisher higher-moment corrected, and delta-normal multi-asset VaR with the exact Euler component / marginal decomposition — plus scenario VaR/ES for an options book (delta-gamma-vega-theta from aggregated Greeks and full revaluation through the portfolio repricer, on shared scenarios so the difference isolates the Taylor error), EWMA / realized volatility, max drawdown, Sharpe / Sortino, the Kupiec VaR backtest, and TOML-configured stress MtM: named shock scenarios (relative / absolute bumps on spot, vol, rates and time, with per-underlying filters and key-rate tenors restricting a rate shock to part of the curve) prepared into bumped market data, checked against a configurable no-arbitrage guard (allow / warn / reject on the minimum implied forward) and fully revalued, reported per trade and aggregated per scenario.
  • Adjoint Algorithmic Differentiation (core::aad): a tape-based reverse-mode differentiator with operator overloading — write a pricer over Var and one backward sweep returns every input sensitivity at a fixed small multiple of pricing cost. Ships with the Black-Scholes closed form on tape (all six first-order Greeks from one sweep, validated to the library's closed forms) and pathwise Monte Carlo Greeks (delta / vega / rho differentiated straight through the simulation).
  • Numerical toolkits in core: 1-D root finding (solvers: bisection, Newton-Raphson, secant, Halley, safeguarded Newton — pluggable by enum), multi-dimensional optimization (optimization: Levenberg-Marquardt, BFGS, conjugate gradient, steepest descent, Nelder-Mead, differential evolution — the fitting layer for Heston today, SABR / Nelson-Siegel tomorrow), FD linear solvers (fd_solvers, 1-D to 3-D), asset-agnostic Monte Carlo machinery (montecarlo: reproducible per-path RNG streams, Sobol with digital-shift scrambling, Halton with Cranley-Patterson rotation, Brownian bridge, stratified / Latin-hypercube sampling, control variates, moment matching, Welford statistics), an interpolation toolkit (interpolation: linear, cubic splines with natural / clamped / not-a-knot boundaries, shape-preserving PCHIP and Akima, bilinear grids and thin-plate splines for 2-D vol surfaces), and linear algebra (linalg: Cholesky / QR / SVD / symmetric-eigen decompositions with least-squares and pseudo-inverse solves, plus correlation repair — PSD-tolerant Cholesky and Higham's nearest-correlation projection, auto-applied to non-PSD rainbow correlation inputs).

Feature flags

The default build is the lean pricing library — no CLI, no XML, ~40% fewer transitive dependencies. Opt in to what you need:

Feature Adds Extra deps
(default) pricing, calibration, risk, JSON contracts
xml XML contract input/output quick-xml
stress-config TOML stress-scenario files toml
cli the rustyqlib binary (implies xml, stress-config) clap, csv
cargo add rustyqlib                    # library only
cargo install rustyqlib --features cli # the command-line tool

Benchmarks

cargo bench runs a criterion suite over the public API (one benchmark per engine, implied vol, and a 1,000-option batch), with fixed seeds and grids so runs are comparable; criterion flags statistically significant regressions against the previous run. Indicative single-threaded numbers: a fully-loaded Black-Scholes npv() (discount curve and vol surface lookups included) runs in ~0.7 µs — about 1.5M prices/sec; a 20-strike Heston smile prices in ~1.6 ms via the COS method vs ~137 ms by per-strike integration (~90x, the ratio the calibration loop inherits); an American put on a Leisen-Reimer 101-step tree prices in ~26 µs vs ~2.3 ms on the CRR-1000 tree at comparable accuracy.

Running the CLI

cargo build --release --features cli
# price a single JSON file of contracts
rustyqlib file --input contracts.json --output results.json
# price every JSON file in a directory (parallel)
rustyqlib dir --input contracts/ --output results/
# build an implied vol surface from quoted options
rustyqlib build --input quotes.json --output out/
# guided pricing in the terminal
rustyqlib interactive

Contract examples

Vanilla European call priced analytically (a flat rate builds a flat curve):

{
  "asset": "EQ",
  "contracts": [{
    "action": "PV", "asset": "EQ",
    "product_type": {
      "product_type": "option", "symbol": "ABC",
      "underlying_price": 100.0, "put_or_call": "C", "payoff_type": "vanilla",
      "strike_price": 100.0, "volatility": 0.3, "maturity": "2027-07-17",
      "risk_free_rate": 0.05, "dividend": 0.0, "pricer": "Analytical"
    }
  }]
}

The same contract can carry richer market data and model choices:

{
  "discount_curve": { "type": "zero_rates", "tenors": [0.25, 1.0, "2029-07-17"],
                      "rates": [0.045, 0.05, 0.055], "compounding": "continuous" },
  "vol_surface":    { "type": "strike_expiry", "expiries": [0.5, 1.0],
                      "strikes": [90.0, 100.0, 110.0],
                      "vols": [[0.32, 0.30, 0.28], [0.33, 0.31, 0.30]] },
  "mc_model": "heston",
  "heston": { "v0": 0.09, "kappa": 2.0, "theta": 0.09, "vol_of_vol": 0.4, "rho": -0.7 },
  "pricer": "MC", "simulation": 100000
}

Selected fields (all optional unless noted):

Field Meaning
valuation_date pricing as-of date YYYY-MM-DD; defaults to today — set it for reproducible pricing and historical re-marking
pricer Analytical, Binomial, FD, MC, BAW, BS2002 (analytic American)
payoff_type vanilla, binary, barrier, asian, forward_start, autocallable
exercise_style European (default), American, Bermudan (needs exercise_dates)
exercise_dates Bermudan exercise dates ["YYYY-MM-DD", ...], strictly increasing; expiry is always exercisable
binary_type, cash_amount cash / asset, cash paid when ITM
barrier_type, barrier_level up_in, up_out, down_in, down_out
averaging_type, asian_strike_type arithmetic/geometric, fixed/floating
rainbow_type, assets, correlations, weights rainbow options: best_of, worst_of, spread, basket, exchange
forward_start_date, strike_fraction forward-start options
autocall_barrier, protection_barrier, autocall_coupon, autocall_observations, notional autocallable notes
borrow_cost continuous stock borrow (repo) cost, part of the carry
futures_settlement option on a future (Black-76): discounted (standard) or margined (futures-style); underlying_price is then the futures price
cash_dividends discrete dividends [{"date", "amount"}]; escrowed model on analytic/tree/terminal-MC, jumps on path-MC and FD
discount_curve flat, zero_rates, discount_factors, forward_rates
vol_surface flat, strike_expiry, moneyness_expiry, delta_expiry
mc_model gbm (default), local_vol, heston (needs heston params)
simulation, mc_time_steps, mc_scheme, mc_sampler, mc_seed Monte Carlo controls
fd_spot_steps, fd_time_steps finite difference grid
tree_type, tree_steps binomial lattice: LeisenReimer (default), CRR, JarrowRudd, Tian, Trigeorgis, EQP; steps default 1000
tree_term_structure apply the discount curve's forward rates and the vol term structure per tree step (variance-equal grid)

Working examples for every product live in src/examples/EQ/. Monte Carlo outputs include the standard error (std_err) alongside price and Greeks.

XML contracts

Every contract can equally be written in XML — the input format is detected from the content and the output format from the output file extension, so -o results.xml writes XML and -o results.json writes JSON regardless of the input:

<contracts>
  <asset>EQ</asset>
  <contracts>
    <item>
      <action>PV</action>
      <asset>EQ</asset>
      <product_type product_type="option">
        <symbol>ABC</symbol>
        <underlying_price>100.0</underlying_price>
        <put_or_call>C</put_or_call>
        <payoff_type>vanilla</payoff_type>
        <strike_price>100.0</strike_price>
        <volatility>0.3</volatility>
        <maturity>2027-07-17</maturity>
        <risk_free_rate>0.05</risk_free_rate>
        <pricer>Analytical</pricer>
      </product_type>
    </item>
  </contracts>
</contracts>

Conventions: elements are object fields; attributes are fields too (convenient for enum tags such as type="flat"); <item> children make an array, including single-element ones; scalars are inferred, so numbers become numbers while 2027-07-17 and C stay strings. See src/examples/EQ/equity_option.xml, and convert between formats with cargo run --features xml --example convert_format -- in.json out.xml.

XML is a syntax over the same data model — documents are transcoded to serde_json::Value and deserialized with the same derives, so both formats share one schema, one set of defaults and one set of validation rules, and every new product supports both automatically.

Runnable examples

One file per product under examples/, each pricing across every applicable engine and model with identity checks:

cargo run --release --example vanilla_option     # all four engines, European + American
cargo run --release --example barrier_option     # eight barrier types, in-out parity
cargo run --release --example heston_option      # char. function vs MC, smile shape
cargo run --release --example local_vol_calibration  # quotes -> surface -> Dupire -> reprice

See examples/README.md for the full list.

Using it as a library

Build contracts with the fluent builder:

use rustyqlib::equity::builder::EquityOptionBuilder;
use rustyqlib::equity::utils::Engine;
use rustyqlib::core::trade::PutOrCall;
use rustyqlib::Instrument;

// build() validates every input and the engine/payoff combination:
// an option that builds is guaranteed to price
let option = EquityOptionBuilder::new()
    .spot(100.0)
    .strike(100.0)
    .flat_vol(0.30)
    .flat_rate(0.05)
    .dividend_yield(0.02)
    .years_to_maturity(1.0)
    .vanilla(PutOrCall::Call)
    .engine(Engine::FiniteDifference)
    .build()?;

// one call returns value, all Greeks and (on MC engines) the std error
let result = option.price()?;
println!(
    "pv {:.6}  delta {:.4}  vanna {:.4}  charm {:.4}",
    result.pv, result.greeks.delta, result.greeks.vanna, result.greeks.charm
);

// ...or use the per-Greek accessors individually
println!("npv {:.6}  gamma {:.4}", option.npv(), option.gamma());

...or deserialize the same JSON the CLI consumes:

use rustyqlib::equity::vanilla_option::EquityOption;
use rustyqlib::core::data_models::EquityOptionData;

let contract: EquityOptionData = serde_json::from_str(json)?;
let option = EquityOption::from_json(&contract);

Lower-level building blocks are exported directly: YieldCurve, VolSurface, DayCountConvention, the Payoff trait, Dupire LocalVol, HestonParams, and the engine modules (blackscholes, binomial, finite_difference, montecarlo).

Design principles

  • Contracts and market state are separable — instruments embed the market they were built with (the stateless-service shape), and a MarketData snapshot provides the desk shape: capture a book's market once, bump() it with named shocks (relative/absolute spot, vol, rate and time, per-underlying filters), and reprice the whole book under the bumped snapshot (book.npv_under(&market)), each position revaluing fully on its own engine. The TOML stress runner is a consumer of these primitives.
  • Discount factors are state, rates are views — curves store pillar dfs; zero/forward rates in any compounding are derived on demand. Vol surfaces canonicalize every quoting style into per-expiry smiles with total-variance time interpolation.
  • One payoff trait, every engine — payoffs implement payoff(spot, strike) and (for path dependence) path_payoff(path, strike); adding a payoff makes it price on every compatible engine without engine changes.
  • Validated numerics — golden values against independently coded oracles, parity and replication identities at 1e-10, cross-engine agreement tests, and bit-reproducible Monte Carlo. Engines refuse unsupported combinations with a clear error instead of silently mispricing.
  • Typed errors, no panics at the boundary — every fallible operation returns RustyQLibError (InvalidInput naming the offending field, UnsupportedEngine, CalibrationFailed with iterations and residual, NumericalError, ParseError). Pricing is fallible via Instrument::try_npv() / try_from_json(); the infallible npv() / from_json() remain as conveniences for validated instruments. Instrument::price() returns a structured PricingResult { pv, greeks, std_err } — value, all nine Greeks and the Monte Carlo standard error from a single call. EquityOptionBuilder::build() validates every field and the engine/payoff combination up front, so an option that builds is guaranteed to price — and setter order never matters. Batch pricing reports failures per contract in the output's error field and keeps going instead of aborting the file.

Roadmap

  • Andersen QE scheme and American exercise (LSMC) under Heston; 2-D ADI finite difference for stochastic vol
  • Barrier rebates, double/window barriers, seasoned Asians
  • Rates: curve bootstrapping from deposits/FRAs/swaps onto the core curve type, swaps and swaptions; FX (Garman-Kohlhagen)
  • Stulz closed forms for two-asset best-of/worst-of; per-asset smiles and path-dependent multi-asset payoffs; SVI smile parameterization with no-arbitrage checks; pathwise / likelihood-ratio Greeks

License

MIT — see License.