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.
Pillars
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.
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
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.
ChainStatesnapshots + 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 —
VrpReadin 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
Architecture
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.
Inputs
PricingInputs, IvSolveInputs, and ChainState typed at the boundary. Time in years, rates and dividend yields continuously compounded — no implicit conversions.
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.
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.
Aggregate
Position / Strategy / Book / Portfolio containers feed aggregate Greek and bucket reports. ChainExposureState derives DEX / GEX / VEX with ferro-wave scenario attribution.
Use Cases
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.
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
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)
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!(), }
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()
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);
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
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 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()
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(..)
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
Benchmarks · 9 September 2026
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.
Black-Scholes-Merton pricing for one contract.
First- and second-order sensitivities from one resolved BSM request.
Jäckel inversion of a frozen, model-generated European quote.
Bjerksund-Stensland 2002 pricing on the ordinary call fixture.
The complete Greek set from one resolved BS2002 call request.
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.0M contracts / sec
9.98–9.99 ms median range
16.5M contracts / sec
5.88–6.20 ms median range
All ten Greeks from 100,000 pre-resolved requests.
100K fused analytics
4.2M contracts / sec
23.93–23.96 ms median range
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
| Path | FerroRisk 0.38.4 · Rust | QuantLib 1.41 · Python | Comparison 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 µs | 10 vs 5 Greeks |
| American put · price | ~2.39 µs | ~2.41 µs | BS2002 vs BS1993 |
| American put · Greeks | ~18.24 µs | ~3.73 µs | BS2002 / 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
| Operation | Model | Median range |
|---|---|---|
| European call · price | Black-Scholes-Merton | 78.61–78.99 ns |
| European call · 10 Greeks | Black-Scholes-Merton | 353.20–355.29 ns |
| European call · IV | Black-Scholes-Merton / Jäckel | 442.13–443.33 ns |
| Forward call · price | Black-76 | 101.52–101.72 ns |
| Forward call · 10 Greeks | Black-76 | 348.57–351.51 ns |
| American call · price | Bjerksund-Stensland 2002 | 2.30–2.31 µs |
| American put · price | Bjerksund-Stensland 2002 | 2.36–2.38 µs |
| American call · 10 Greeks | Bjerksund-Stensland 2002 | 19.62–19.73 µs |
| American put · 10 Greeks | Bjerksund-Stensland 2002 | 18.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.
Foundations
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.
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.
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.
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.
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.
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.
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.
Code Quality
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.
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.
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.
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.
Conventions
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.
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.
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.
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.
API Surface
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
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.comTell 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