Pricing, IV, Greeks,
Volatility.

Institutional-grade options pricing and volatility analytics, in pure Rust — one shared evaluation path from a single contract to a live option chain.

7 models
BSM · Black-76 · Bachelier · Heston · Jumps · Local-Vol · Bjerksund
3 surfaces
SVI · SSVI · SABR — calibration + diagnostics
10 Greeks
First + Second Order
2,000+ tests
Reference + MPFR oracles
310 theorems
Machine-checked in Lean — no sorry
~79 ns
European price · M1 snapshot
~354 ns
BSM · 10 Greeks · M1 snapshot

Four pillars on one evaluation path

Pricing with full Greeks coverage, surface calibration, local-vol extraction, live chain analytics, and volatility & microstructure analytics — built so consumers get the same numerical answer whether they call a single contract, fit a surface, stream a venue's option chain, or measure the premium between implied and realized vol.

7M

Seven pricing models

European to American, lognormal to normal, deterministic to stochastic vol, with jumps. Each model named explicitly at the call site; intrinsic at expiry is model-independent.

  • BlackScholesMerton · Black-76 · Bachelier
  • DisplacedBlack — shifted-lognormal forwards
  • Heston — stochastic-vol via Fang-Oosterlee COS
  • JumpDiffusion — Merton (1976) lognormal jumps
  • Bjerksund-Stensland 2002 — American
  • Discrete cash dividends — escrowed-spot American with full-map Greeks
  • De-Americanization — European-equivalent price + IV with early-exercise-premium telemetry
  • Plus opt-in CRR reference + Dupire local-vol kernels
  • First- and second-order Greeks with typed validity checks
σ²

Surfaces, calibrated and diagnosed

SVI, SSVI, and SABR surface calibration with fit-quality diagnostics, pluggable losses, prior regularization, and static-arbitrage repair workflows. Jaeckel IV for per-contract inversion.

  • SVI / SSVI / SABR — public calibration entry points
  • Joint cross-tenor SSVI — Gatheral-Jacquier globals shared across the surface
  • Four-tier fit quality — Healthy / Acceptable / Degenerate / Failed
  • Pluggable loss + prior — Huber, Trimmed-MSE, Bayesian smoothing
  • Quality-filtered prep + bid-ask feasibility filter
  • Vega · bid-ask precision weighting axes for liquidity-aware SVI prep
  • Model-free put-call-parity forward recovery + consistency diagnostic
  • Cross-tenor parity forward curve — market-implied borrow / financing rate
  • Provenance-aware firewall — Live anomaly gates skip Modeled / Theoretical rows
  • Static-arbitrage diagnostics + repair workflows
  • Smile-panel readouts — 25Δ / 10Δ RR + fly + ATM IV with bracket diagnostics
  • σ-standardized log-moneyness wing anchors — stationary across vol and DTE
  • Non-parametric short-DTE fallback for degenerate parametric smiles
  • Dupire local-vol extraction with PDE / forward-PDE / MC
CH

Live chain analytics

Deterministic ChainState snapshots with incremental refresh, derived market metrics, exposure analytics, and typed ferro-wave scenario attribution across baseline and shocked flow.

  • ChainState snapshots + incremental updates
  • ATM term structure · implied move · RR · butterfly
  • DEX / GEX / VEX / vanna / charm exposures
  • Call / put OI concentration · strike + expiry walls
  • Spot ladders · flip levels
  • ferro-wave scenario attribution — spot · vol · rate · time · interaction
  • Smile-aware delta selection · risk reversal · butterfly
σφ

Volatility & microstructure analytics

Realized volatility from OHLC bars, the variance risk premium split into diffusive and jump legs, option-implied correlation, and per-quote and multi-leg execution cost — typed reads on the same fail-loud numerical path.

  • Realized vol — close-to-close · Parkinson · Garman-Klass · Yang-Zhang · bipower
  • Realized jump split — jump-robust diffusive vs jump variance
  • Directional semivariance — downside / upside legs summing exactly to RV
  • Variance risk premium — VrpRead in vol-point + variance units
  • VRP decomposition — diffusive vs jump/tail legs via a Merton fit
  • Merton jump calibration — quote-anchored Q-side fit from a physical-measure prior (Esscher tilt)
  • VRP z-score — normalize the premium against a rolling baseline
  • Implied correlation + dispersion — index vs weighted constituents
  • Effective spread — quoted / half / round-trip cost, typed outcomes
  • Multi-leg round-trip cost — quantity-aware spread aggregation

From inputs to portfolio risk

Four layers. Inputs typed at the boundary, pricing dispatched explicitly across seven models, surfaces calibrated with fit-quality contracts, and analytics composed over ordered containers and live chain state — never hidden mutable state.

1

Inputs

PricingInputs, IvSolveInputs, and ChainState typed at the boundary. Time in years, rates and dividend yields continuously compounded — no implicit conversions.

2

Price

Seven models dispatch through PricingModel — BSM, Black-76, Bachelier, DisplacedBlack, Heston, JumpDiffusion, Bjerksund-Stensland — plus opt-in CRR reference and Dupire local-vol kernels.

3

Calibrate

SVI / SSVI / SABR surface calibration — per-slice or a joint cross-tenor Gatheral-Jacquier SSVI fit — with pluggable loss, prior regularization, and fit-quality classification. Dupire extracts local-vol from any calibrated surface. Smile-aware delta selectors snap to listed strikes.

4

Aggregate

Position / Strategy / Book / Portfolio containers feed aggregate Greek and bucket reports. ChainExposureState derives DEX / GEX / VEX with ferro-wave scenario attribution.

Ten workflows on one evaluation path

Entry points across the four pillars. Each one is a 10–20 line snippet that takes the workflow from typed input shape to typed output. The deeper guides live in the docs.

01 · Pricing

Price a contract, read all ten Greeks

The single-contract path. One typed PricingInputs, one named model, one call returns the full first- and second-order Greek surface on a shared internal evaluation path — no per-Greek bumping at the call site, no inconsistency between sensitivities computed on different paths.

use ferro_risk::{ExerciseStyle, OptionType, PricingInputs,
             PricingModel, greeks_all};

let inputs = PricingInputs {
    option_type: OptionType::Call,
    exercise_style: ExerciseStyle::European,
    spot: 4_850.0,
    strike: 4_900.0,
    time_to_expiry: 0.25,
    rate: 0.045,
    dividend_yield: 0.0,
    volatility: 0.22,
};

let greeks = greeks_all(&inputs, PricingModel::Black76)?;
// greeks.delta, .gamma, .theta, .vega, .rho,
// .vanna, .volga, .charm, .veta, .color
Read the full guide →
Long ATM call · payoff + 10-Greek readout
02 · Pricing

Solve IV, forward, and Greeks in one call

When a workload needs solved IV, explicit forward, AND Greeks on the same contract, contract_analyticsfuses them on a shared internal evaluation path. Jaeckel's Let's-Be-Rational drives the IV solve to machine precision in a handful of iterations.

use ferro_risk::{ExerciseStyle, IvSolveInputs, OptionType,
             PricingModel, contract_analytics};

let inputs = IvSolveInputs {
    option_type: OptionType::Call,
    exercise_style: ExerciseStyle::European,
    spot: 100.0,
    strike: 100.0,
    time_to_expiry: 0.5,
    rate: 0.03,
    dividend_yield: 0.01,
};

let analytics = contract_analytics(
    &inputs,
    8.50,  // observed market price
    PricingModel::BlackScholesMerton,
)?;

// analytics.iv, .forward, .greeks (all 10)
Read the full guide →
Jaeckel solver · |residual| vs iteration
03 · Surfaces

Calibrate a vol surface with fit-quality contract

From normalized chain quotes to a typed SviSmile / SsviSlice / SabrSmile and a SurfaceCalibrationReport that grades the fit Healthy / Acceptable / Degenerate / Failed — on the residual, not just the optimizer. Model-free put-call-parity forward recovery, a joint Gatheral-Jacquier SSVI fit, and a non-parametric short-DTE fallback keep thin near-dated chains honest.

use ferro_risk::{calibrate_svi_surface, FitQuality,
             SviCalibrationPolicy, SurfaceCalibrationSearchPolicy};

let policy = SviCalibrationPolicy::default()
    .with_search(SurfaceCalibrationSearchPolicy::default());

let report = calibrate_svi_surface(&input, &policy)?;

match report.fit_quality() {
    FitQuality::Healthy => use_parametric(&report),
    FitQuality::Acceptable { reason } => // RR25 / ATM IV /
        readouts_only(&report),       // term-structure
    FitQuality::Degenerate { reason } => // shape-model breakdown
        surface_raw_interpolated_skew(/* … */)?,
    FitQuality::Failed { reason } => bail_or_retry(reason),
    _ => unreachable!(),
}
Read the full guide →
SVI fit · SPY 30D · 2026-05-07 · 11 OTM quotes
04 · Surfaces

Run the canonical smile-panel in one call

ATM IV at the smile's effective forward, 25Δ and 10Δ risk reversals, 25Δ and 10Δ butterfly flies — the canonical morning smile-panel readout — bundled into SurfaceSmileRiskMetrics for a single-call workflow that replaces 3-5 hand-rolled lookups. Each RR wing carries bracket diagnostics — bracket_delta_distance (snap to target) and bracket_spread (interpolation lever-arm) — so consumers can degrade loose-bracket wings instead of trusting the binary Interpolated flag.

use ferro_risk::{surface_smile_risk_metrics, PricingModel,
             SurfaceDeltaConvention,
             SurfaceSmileDeltaSelectionPolicy};

let metrics = surface_smile_risk_metrics(
    &smile,
    &market,
    SurfaceDeltaConvention::Forward,
    PricingModel::BlackScholesMerton,
    SurfaceSmileDeltaSelectionPolicy::default(),
    &listed_strikes,
)?;

// metrics.atm_iv()
// metrics.rr_25().risk_reversal()
// metrics.fly_25(), metrics.fly_10()

// bracket diagnostics quantify wing quality
// metrics.rr_25().put_wing().bracket_delta_distance()
// metrics.rr_25().put_wing().bracket_spread()
Read the full guide →
Smile-panel · SPY 30D · 2026-05-07
05 · Chain Analytics

Derive DEX, GEX, and flow walls from a live chain

Live ChainState in, ChainExposureReport out — per-strike and per-expiry DEX / GEX / VEX / vanna / charm, call/put OI concentration, strike walls, spot ladders, and flip-level detection. ChainExposureStatecaches incremental updates so streaming workloads don't recompute the whole chain on each refresh.

use ferro_risk::{ChainExposureInputs, ChainExposureMetric,
             PricingModel, derive_chain_exposures, strike_walls};

// chain_state, market, policy set up upstream
let inputs = ChainExposureInputs::new(
    &chain_state, &market,
    PricingModel::BlackScholesMerton, policy)?;
let report = derive_chain_exposures(&inputs)?;

// per-strike GEX, via the metric projection
for b in &report.strike_buckets {
    let gex = ChainExposureMetric::Gex
        .value(b.net_exposure);
}

// OI-side walls — one per metric
let walls = strike_walls(&report);
let call_wall = walls.iter().find(|w|
    w.metric == ChainExposureMetric::OpenInterestCall);
Read the full guide →
GEX by strike · SPY 30D · 2026-05-07 · call wall at 740
06 · Scenarios

Explain a portfolio scenario with attribution

A ScenarioDefinition composed of typed ScenarioShock variants — spot-relative, parallel vol, skew, rate, time, custom — runs over a Portfolio and returns exact PnL attribution per position, bucket, and ordered strategy group. The FerroWaveScenarioAdapter maps upstream regime / vol / jump signals into the same shock list.

use ferro_risk::{ScenarioDefinition, ScenarioShock,
             explain_portfolio_scenario_pnl};

let scenario = ScenarioDefinition::new(
    "spot_up_vol_up",
    vec![
        ScenarioShock::SpotRelative { relative: 0.05 },
        ScenarioShock::ParallelVol  { shift: 0.02 },
        ScenarioShock::Skew { slope: -0.01 },
    ],
)?;

let report = explain_portfolio_scenario_pnl(
    &portfolio,
    &scenario,
)?;

// report.pnl · report.books[..] · report.expiry_buckets
// report.position_rows() — per-position PnL, reconciles to total
Read the full guide →
PnL attribution · by position → total
07 · Volatility

Split the variance risk premium into diffusive and jump legs

variance_risk_premium takes implied and realized vol on a shared SurfaceTenor and returns a typed VrpRead in both vol-point and variance units. A JumpDiffusionFit — hand-supplied, or calibrated from quotes with a physical-measure anchor (09 below) — then drives vrp_decomposition, which splits the premium into its diffusive carry and its jump/tail compensation — so a raw implied-minus-realized number becomes two legs you can act on.

use ferro_risk::{variance_risk_premium, vrp_decomposition,
             JumpDiffusionFit, JumpDiffusionParameters,
             RvMethod, SurfaceTenor};

let tenor = SurfaceTenor::new(30.0 / 365.0)?;

let vrp = variance_risk_premium(
    0.22, 0.18,  // implied, realized vol
    tenor, RvMethod::YangZhang,
)?;

let params =
    JumpDiffusionParameters::new(0.9, -0.02, 0.08);
let fit = JumpDiffusionFit::new(0.18, params)?;
let split = vrp_decomposition(fit, vrp);

// split.diffusive_vrp(), .jump_vrp(),
// split.jump_variance_share()
Read the full guide →
VRP decomposition · premium = diffusive + jump
08 · Volatility

Read option-implied correlation from an index and its constituents

implied_correlationtakes the index's implied vol and a slice of typed BasketConstituent weights and IVs on a shared tenor, and returns a DispersionRead: the average pairwise correlation the market is pricing, plus the dispersion premium between the weighted-average constituent vol and the index vol — the read a dispersion book is built on. rho_implied is reported raw, so an out-of-range implied correlation is surfaced, not hidden.

use ferro_risk::{implied_correlation, BasketConstituent,
             SurfaceTenor};

let tenor = SurfaceTenor::new(30.0 / 365.0)?;

let constituents = vec![
    BasketConstituent::new(0.28, 0.35)?,
    BasketConstituent::new(0.24, 0.40)?,
    BasketConstituent::new(0.31, 0.25)?,
];

// index_iv on the same tenor
let read =
    implied_correlation(0.19, &constituents, tenor)?;

// read.rho_implied(), .dispersion_vol_pts(),
// read.weighted_avg_vol()
Read the full guide →
Implied correlation · index vs constituents
09 · Volatility

Calibrate the jump triple from quotes, anchored in the physical measure

calibrate_jump_diffusion fits the risk-neutral Merton triple against option quotes, identified by a JumpAssumptioncarrying the physical measure — the wire payload of ferro-wave's parametric wavelet jump detector maps into it field-for-field. The calibrator owns the P → Q Esscher tilt (the jump risk premium θ), jump_prior_cross_check holds the prior against the bipower realized jump split, and is_confident() folds tilt identifiability and the cross-check into one gate — a fit that cannot be identified says so instead of converging anyway.

use ferro_risk::{calibrate_jump_diffusion, jump_prior_cross_check,
             realized_jump_split, JumpAssumption, JumpEstimateUncertainty,
             JumpMeasure, JumpPriorCrossCheckPolicy};

// physical-measure anchor — e.g. ferro-wave's jump detector
let unc = JumpEstimateUncertainty::new(32, 0.68, 0.005, 0.004)?;
let prior = JumpAssumption::new(
    JumpMeasure::Physical, 3.66, -0.082, 0.025, unc)?;

let realized = realized_jump_split(&bars, 252.0)?;
let check = jump_prior_cross_check(&prior, &realized,
    JumpPriorCrossCheckPolicy::new(0.35)?)?;

let outcome = calibrate_jump_diffusion(
    &input, &policy, &prior, Some(check))?;

// outcome.is_confident() · outcome.tilted_assumption()
// outcome.per_expiry()[i].fit() → vrp_decomposition(..)
Read the full guide →
Jump calibration · P anchor → Q fit
10 · Pricing

De-Americanize an equity quote across its dividend schedule

Listed equity options are American and their underlyings pay discrete cash dividends — so the quotes a surface fitter wants are the wrong shape twice over. A DividendSchedule on IvSolveInputs prices the escrowed spot S − Σ Dᵢ·e^(−r·tᵢ) through the unchanged Bjerksund-Stensland kernel, and de_americanize_quote strips the early-exercise premium to a European-equivalent price and IV that SSVI can actually fit. The premium is reported, never thresholded — the bias is yours to gate on.

use ferro_risk::{de_americanize_quote, DividendEvent,
             DividendSchedule, ExerciseStyle, IvSolveInputs,
             OptionType};

// one $0.96 ex-div 38 days out, 110-day expiry
let schedule = DividendSchedule::new(vec![
    DividendEvent::new(38.0 / 365.0, 0.96)?,
])?;

let inputs = IvSolveInputs {
    option_type: OptionType::Put,
    exercise_style: ExerciseStyle::American,
    spot: 190.0,
    strike: 195.0,
    time_to_expiry: 110.0 / 365.0,
    rate: 0.043,
    dividend_yield: 0.0,
    dividends: Some(schedule),
};

let quote = de_americanize_quote(&inputs, 12.9220)?;

// quote.european_equivalent_iv()    → 0.26000
// quote.european_equivalent_price() → 12.6626
// quote.eep_fraction()              → 0.02008
Read the full guide →
De-Am · schedule-aware vs schedule-blind IV on the same quote

Performance as a first-class feature

Single-contract latency and 100K-contract throughput on a 10-core Apple M1 Pro, measured on 9 September 2026. Each figure comes from the dated fixtures and raw measurements below.

~79 ns
Single contract · median latency
European call price

Black-Scholes-Merton pricing for one contract.

~354 ns
Single contract · median latency
European call · all 10 Greeks

First- and second-order sensitivities from one resolved BSM request.

~443 ns
Single contract · median latency
European implied volatility

Jäckel inversion of a frozen, model-generated European quote.

~2.3 µs
Single contract · median latency
American call price

Bjerksund-Stensland 2002 pricing on the ordinary call fixture.

~19.7 µs
Single contract · median latency
American call · all 10 Greeks

The complete Greek set from one resolved BS2002 call request.

~18.3 µs
Single contract · median latency
American put · all 10 Greeks

The complete Greek set from one resolved BS2002 put request.

100K batch throughput

50,000 European calls + 50,000 European puts · Black-Scholes-Merton

100K full Greek sets

~10.0 ms
4 Rayon threads

10.0M contracts / sec

9.98–9.99 ms median range

~6.0 ms
10 Rayon threads

16.5M contracts / sec

5.88–6.20 ms median range

All ten Greeks from 100,000 pre-resolved requests.

100K fused analytics

~23.9 ms
4 Rayon threads

4.2M contracts / sec

23.93–23.96 ms median range

~12.7 ms
10 Rayon threads

7.9M contracts / sec

12.34–13.11 ms median range

Solved IV, explicit forward and all ten Greeks for each of 100,000 contracts.

FerroRisk 0.38.4 source build 301f5d0a4dad · Rust 1.97.1 · default + SIMD features · Cargo bench profile. Both batches alternate the two fixed BSM fixtures documented in the evidence. Input construction is outside timing; batch execution, result allocation and cleanup are included. Each configuration ran twice in 4 → 10 → 10 → 4 order, with 60 samples, one-second warmup and at least three seconds of measurement per workload per phase. Cards round the midpoint of the two phase medians; throughput uses 100,000 contracts per invocation. Measured on AC power under normal host activity, with host observations retained and no quiet-window admission screen.

Head to head vs QuantLib

Apple M1 Pro · identical numerical inputs · single-threaded scalar calls · QuantLib 1.41 from Python 3.14.7

PathFerroRisk 0.38.4 · RustQuantLib 1.41 · PythonComparison scope
European call · price~80 ns~1.78 µs~22.3× QL / FerroRisk API latency
European call · implied volatility~442 ns~9.62 µs~21.7× QL / FerroRisk API latency
European call · Greeks~358 ns~3.09 µs10 vs 5 Greeks
American put · price~2.39 µs~2.41 µsBS2002 vs BS1993
American put · Greeks~18.24 µs~3.73 µsBS2002 / 10 vs BS1993 / 5

Measured 2026-09-09, FerroRisk source 301f5d0a4dad, Rust 1.97.1, Cargo bench profile, default + SIMD features; QuantLib uses its released macOS ARM64 Python wheel. This compares native Rust calls with the QuantLib Python API, including Python and binding overhead. The European price and IV ratios divide QuantLib latency by FerroRisk latency for these API paths.

Options, curves and FerroRisk Greek requests are built before timing. QuantLib price and Greek calls force recalculation on every invocation; cached price retrieval is excluded. QuantLib's engine also computes Greeks during pricing. Its Greek row retrieves delta, gamma, vega, theta and rho; FerroRisk returns all ten Greeks. American rows use FerroRisk BS2002 versus QuantLib BS1993. Both expose American Greeks; the different models and Greek sets are shown without a speedup or accuracy ratio.

Comparison inputs, solvers and measurement method

European call: spot 120, strike 100, expiry 0.75 years, rate 0.03, dividend yield 0.01, volatility 0.32. American put: spot 100, strike 105, expiry 0.5 years, rate 0.03, dividend yield 0.01, volatility 0.25. Rates are continuously compounded decimal fractions; volatility is a decimal fraction. No discrete dividends. QuantLib uses an evaluation date of 9 September 2026 and Actual/360 with 270 and 180 days to reproduce those exact year fractions.

European IV uses the same frozen quote on both sides. FerroRisk uses its Jäckel solver and default stopping policy; QuantLib uses impliedVolatility with accuracy 1e-10, at most 100 evaluations and volatility bounds [1e-7, 4]. These solver policies differ. Both recover the fixture volatility within 1e-9. Theta is reported per 365-day calendar day; vega and rho are per unit volatility and rate.

One-second warmup, 60 samples per workload per phase. FerroRisk uses Criterion with at least three seconds of measurement; Python samples use calibrated loops targeting 50 ms each. Run order: FerroRisk → QuantLib → QuantLib → FerroRisk. Values are rounded midpoints of two phase medians; phase ranges and raw samples are linked below. AC power, normal host activity, no quiet-window admission screen. Output checks and an independent arithmetic repeatability check are retained. These scalar API measurements do not establish batch, service or native C++ performance.

Measured scalar workloads

Range of two phase medians · one contract per invocation · engine latency

OperationModelMedian range
European call · priceBlack-Scholes-Merton78.61–78.99 ns
European call · 10 GreeksBlack-Scholes-Merton353.20–355.29 ns
European call · IVBlack-Scholes-Merton / Jäckel442.13–443.33 ns
Forward call · priceBlack-76101.52–101.72 ns
Forward call · 10 GreeksBlack-76348.57–351.51 ns
American call · priceBjerksund-Stensland 20022.30–2.31 µs
American put · priceBjerksund-Stensland 20022.36–2.38 µs
American call · 10 GreeksBjerksund-Stensland 200219.62–19.73 µs
American put · 10 GreeksBjerksund-Stensland 200218.25–18.32 µs

Timing build: FerroRisk 0.38.2, M1 development source c8078b72e5c1 · Rust 1.97.1 · default + SIMD features · Cargo bench profile · macOS 26.6.2. Each phase uses 60 samples, a one-second warmup and at least three seconds of measurement per workload. Cards round the midpoint of the two phase medians. All scalar operations execute on one thread; Rayon is fixed at four threads for the surrounding harness. An untimed replay on the 0.38.4 source build reproduces the same nine fixture results; the methodology records both source identities.

Scalar Greek timings use pre-resolved requests and return delta, gamma, theta, vega, rho, vanna, volga, charm, veta and color. Request construction and transport are outside those timings. The published scalar cases succeed and allocate nothing in the retained scalar probes; other inputs remain subject to each model's validity checks. American Greeks combine dual derivatives with a time stencil for color; nonsmooth contacts can return typed unavailability.

Fixtures and measurement conditions

European call: spot 120, strike 100, expiry 0.75 years, rate 0.03, dividend yield 0.01, volatility 0.32. Black-76 call: forward 4900, strike 5000, expiry 0.5 years, rate 0.045, volatility 0.27. American call: spot = strike = 100, expiry 1 year, rate 0.05, dividend yield 0.03, volatility 0.30. American put: spot 100, strike 105, expiry 0.5 years, rate 0.03, dividend yield 0.01, volatility 0.25. Rates and volatility are decimal fractions; rates are continuously compounded. Exact input and quote bits are included in the evidence.

The Mac stayed on AC power with normal OS and application activity. Repeatability controls passed; this was not an isolated host run. No compiler, reference generator or fuzz worker was observed during timing. No thermal or performance warning was recorded; die temperature was not measured.

The scalar and 100K results retain separate source identities, workload definitions and measurement records. The QuantLib comparison has its own fresh measurements and API boundaries. Greek units and the reproduction procedure are documented with the raw results.

Built from first principles

FerroRisk is not a port of someone else's library. Every model starts with the source research, is implemented in-tree in pure Rust, is validated against the canonical reference and an independent oracle, and has its core pricing laws machine-checked in Lean before it ships.

01

Start from the research

Each model comes from its source paper, not a second-hand transcription — Bjerksund-Stensland (2002) for American, Merton (1976) jump-diffusion, Heston via Fang-Oosterlee COS, Gatheral-Jacquier (2014) SSVI, Hagan SABR, Dupire local-vol, and Jaeckel's Let's-Be-Rational IV solver.

02

Implement in-tree

The numerics live in the crate — closed-form wherever the math allows, with no hidden BLAS and no math DSL. The Cody normal CDF, the rational-branch IV solver, and the dual-number analytical Greeks are written and owned here.

03

Validate against canon and oracles

European IV round-trips match Jäckel's reference C++ to 4 ULPs — fixtures stored as exact bit patterns, never a paper's rounded tables. The fast American path is held against a 16,384-step CRR tree, second-order Greeks against a Richardson high-precision reference, and the SIMD transcendentals against MPFR grids.

04

Reproducible by construction

The same inputs always produce the same numbers. Parallel batch APIs use deterministic Rayon for byte-identical reports across thread counts, and every SIMD kernel is bit-tracked against the scalar twin it replaces.

05

Proven, not just tested

The pricing mathematics is machine-checked in Lean — 310theorems with no sorry, kernel-verified against a minimal axiom base: put-call parity, every first-order Greek and the higher-order surface, the Black-Scholes PDE, and implied-vol existence & uniqueness. The standard normal CDF is the genuine Gaussian, not an assumption — and these laws can't drift from the code.

06

Verified down to the floating point

Beyond the reals: the Cody normal-CDF and normalised-Black kernels carry rigorous rounding bounds (Gappa) and an approximation bound against the true erf (Sollya), and the proved identities are checked holding in the shipping f64 code to ~1e-15. The Let's-Be-Rational IV solver converges to machine precision in ≤2 iterations across the moneyness wings.

Mathematical correctness as a gate

Pricing libraries are easy to write and hard to verify. FerroRisk treats numerical correctness, calibrated performance, regression coverage, and formal proof obligations over stable pricing and risk laws as release gates — not aspirations.

Mathematical Correctness

Numerical equality round-trips, property tests, and known-answer suites against reference implementations — CRR for American, closed-form analytics for European.2,000+ tests, 600+ property assertions, and 16 fuzz targets cover the pricing, surface, and chain-analytics paths. Every release passes the gate.

FM

Formal Proof Gates

310 machine-checked Lean theorems over the pricing mathematics — kernel-verified with no sorry on the three standard axioms — plus Gappa/Sollya floating-point rounding and approximation bounds on the numeric kernels, and an f64 conformance harness that holds the proved identities to ~1e-15. The claim stays scoped to those laws, not every market model or trading outcome.

PT

Local Performance Review

Hot-path changes carry local before-and-after measurements with source revisions, fixed workloads and allocation probes. CI compiles the benchmarks; timing is reviewed on the recorded host. The dated results above retain their fixtures and raw samples so a performance claim can be checked against the work it measures.

Rs

Pure Rust. Lean by design.

Zero unsafe in the core — the SIMD intrinsics are encapsulated by pulp. Three runtime dependencies — rayon for parallel batch APIs, thiserror for typed errors, and pulp for portable runtime-dispatched SIMD (on by default; --no-default-features drops it for the always-present scalar path) — plus serde behind a feature flag. The numerics live in-tree: no hidden BLAS, no math DSL, no surprise transitive graph. High performance follows from the absence of surprises.

No surprises in the API contract

Pricing-library bugs hide in unit conventions. FerroRisk names them on the surface so a reader of the call site can reproduce the math without reading the source.

yr

Time in years

time_to_expiry is expressed in years. A 30-day expiry is 30.0 / 365.0. There is no implicit calendar conversion at any pricing entry point.

e

Continuously-compounded rates

Both rate and dividend_yield are continuously compounded. For Black-76, set dividend_yield = 0.0 — the model uses the risk-free rate as the effective carry term.

/d

Per-calendar-day decay

theta, charm, veta, and colorare reported per calendar day, not per year. This matches how risk desks read time-decay on a daily P&L.

∂σ

Vega and rho on raw scale

vega is reported as dV/dσ, not per one volatility point. rho is reported as dV/dr, not per basis point. Convert at the surface, not inside the engine.

One typed API, every pillar

A taste below — the guides walk each workflow end to end, and the complete API reference documents every public type, function, and trait.

Compute the full first- and second-order Greeks surface in one call, on a shared internal evaluation path

greeks_all takes a typed PricingInputs and a named PricingModel and returns the 10-Greek surface for the contract — no per-Greek bumping at the call site, no inconsistency between sensitivities computed on different internal paths.

For larger workloads, greeks_batch and contract_analytics_batch parallelize via Rayon with per-contract error isolation. Use contract_analytics when a workload needs solved IV, explicit forward, and Greeks for the same contract in one fused step.

use ferro_risk::{ExerciseStyle, OptionType, PricingInputs,
             PricingModel, greeks_all};

let inputs = PricingInputs {
    option_type: OptionType::Call,
    exercise_style: ExerciseStyle::European,
    spot: 4_850.0,
    strike: 4_900.0,
    time_to_expiry: 0.25,
    rate: 0.045,
    dividend_yield: 0.0,
    volatility: 0.22,
};

let greeks = greeks_all(&inputs, PricingModel::Black76).unwrap();
// greeks.delta, .gamma, .theta, .vega, .rho,
// .vanna, .volga, .charm, .veta, .color
10 Greeks per callShared eval pathRayon batchPer-call error isolation

Talk to us

Reach out for design-partner support, integration guidance on the surface-calibration and chain-analytics surfaces, or term-structure / calibration optimizer planning. The pricing core is formally verified — the math is proven, not just tested.

hello@morphiqlabs.com

Tell us about your use case

  • Asset class and model — equity, futures, American / European, stochastic-vol, jumps
  • Workload — single contracts, surface calibration, streaming chain analytics
  • Surface needs — SVI / SSVI / SABR, fit-quality tolerances, Dupire local-vol
  • Integration timeline and existing infrastructure