black-76
Black-76 closed-form pricing, Greeks, and implied-volatility solver for
European options on forwards and futures. Synchronous f64 math, no
allocations on the hot path, no logging dependency.
The Black-76 model (Fischer Black, The Pricing of Commodity Contracts, Journal of Financial Economics, 1976) prices options whose underlying is a forward contract rather than a spot asset. Typical applications: Eurodollar futures options, FX forward options, commodity-futures options, and crypto perpetual-futures options.
Features
- Closed-form call / put prices, vega, and intrinsic value.
- First-order Greeks: delta, gamma, vega (per 1%), theta (per day), rho (per 1%).
- Implied-volatility solver: Newton-Raphson with a Brent's-method fallback when vega is too small (deep OTM / near-expiry). Convergence is decided in volatility space, so it holds at any forward scale.
- Explicit convergence contract: a
SolverResultwhoseivisNaN(andconverged = false) on every non-converged path, plus aSolverStatusenum giving the exact reason (near-expiry, below intrinsic, non-positive price, no root in range, not identifiable, max iterations). No silently clamped boundary values pretending to be solutions. - Optional per-expiry vol smile (
vol-surface) with linear interpolation and flat extrapolation. - Optional risk-neutral probability extraction (
digital) via call-spread replication andN(d2)with skew adjustment.
Single runtime dependency: statrs (for the standard normal CDF / PDF).
No chrono, no tracing, no async runtime, no serde unless you opt in.
Quick start
[]
= "0.1"
use ;
// Price an ATM call: F=100, K=100, T=1 year, sigma=20%, r=0
let c = call_price;
assert!;
// Recover the IV from a market price
let cfg = default;
let result = solve_iv;
assert!;
assert!;
Typo-resistant inputs
Positional f64 arguments are easy to mis-order. BlackInputs and
IvQuery name every field (and are const-constructible):
use ;
let inputs = new; // f, k, t, sigma, r
let c = inputs.call_price;
let g = inputs.greeks; // delta, gamma, vega, theta, rho
let iv = new
.solve;
assert!;
Convergence checking is mandatory
use ;
let cfg = default;
let result = solve_iv;
if result.converged else
Skipping the converged check is a bug: iv is f64::NAN on every
non-converged path, and SolverStatus tells you which one.
Feature flags
| Flag | Default? | What it enables |
|---|---|---|
| (none) | yes | core pricing, Greeks, IV solver |
serde |
no | Serialize / Deserialize derives on public types |
vol-surface |
no | per-expiry VolSmile with interpolation |
digital |
no | risk-neutral probability extraction (requires vol-surface) |
= { = "0.1", = ["vol-surface", "digital", "serde"] }
Examples
Six runnable examples under examples/:
Benchmarks
Two Criterion benches under benches/:
Indicative single-thread timings (Criterion median, --release, AMD Ryzen 7
PRO 8840U). Treat them as ballpark rather than a guarantee:
| Operation | Median |
|---|---|
d1_d2 |
~20 ns |
call_price (ATM) |
~68 ns |
put_price (ATM) |
~63 ns |
vega (ATM) |
~41 ns |
solve_iv (Newton path) |
~320 ns |
solve_iv (Brent fallback, deep OTM) |
~3300 ns |
Conventions
- Vega is reported per 1% absolute change in IV (trader convention),
i.e. the raw
dC/dsigmadivided by 100. - Theta is reported per calendar day with
year = 365.25days. - Time is in years.
- Rate is continuous compounding.
- All numerics are
f64. The crate exposes noDecimal-typed APIs.
Comparison with alternatives
black-76 |
py_vollib |
QuantLib (bindings) | RustQuant / black_scholes |
|
|---|---|---|---|---|
| Language | pure Rust | Python + C ext | C++ via FFI | Rust |
| Runtime deps | statrs only |
NumPy/SciPy | QuantLib | varies |
| Async / runtime | none | n/a | n/a | none |
| Model focus | Black-76 (forwards/futures) | Black / BS / -76 | everything | mostly Black-Scholes |
| IV solver | Newton + Brent, typed SolverStatus |
Newton / lets_be_rational |
various | varies |
| Non-convergence | iv = NaN + status enum |
exceptions | exceptions | varies by crate |
| Greeks | delta, gamma, vega, theta, rho | full | full | crate-dependent |
| Smile / digitals | optional (vol-surface, digital) |
n/a | full surfaces | n/a |
forbid(unsafe_code) |
yes | n/a | no (FFI) | varies |
| MSRV / semver | 1.85, #[non_exhaustive] API |
n/a | n/a | varies |
The table is positioning, not a precise feature audit; check each project's current release for specifics.
black-76 is deliberately narrow: a synchronous, dependency-light
forward-options pricer with a typed convergence contract, not a full
derivatives library. Reach for QuantLib when you need exotic products or full
curve/surface machinery.
Background
This began as the pricing core of a crypto-options arbitrage system, where I
needed Black-76 prices, Greeks, and an implied-vol solver that were
allocation-free on the hot path and explicit about non-convergence: near
expiry, deep out-of-the-money, or at exchange-scale forwards (BTC ~100k) where
an absolute price tolerance stops being meaningful. I pulled the pure-math
modules out of that project and generalized them into this standalone f64
crate.
MSRV
Rust 1.85 (Edition 2024).
References
- Hull, Options, Futures, and Other Derivatives, 10th ed., §17.
- Haug, The Complete Guide to Option Pricing Formulas, 2nd ed., §1.1.
- Brent, Algorithms for Minimization without Derivatives, 1973, Ch. 4.
- Brenner & Subrahmanyam, A Simple Formula to Compute the Implied Standard Deviation, Financial Analysts Journal, 1988.
License
Dual-licensed under either of
- Apache License, Version 2.0 (
LICENSE-APACHEor http://www.apache.org/licenses/LICENSE-2.0) - MIT License (
LICENSE-MITor http://opensource.org/licenses/MIT)
at your option.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.