black-76 0.1.0

Black-76 closed-form pricing, Greeks, and implied volatility solver for futures and forward options.
Documentation

black-76

Crates.io Documentation License: MIT OR Apache-2.0

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 SolverResult whose iv is NaN (and converged = false) on every non-converged path, plus a SolverStatus enum 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 and N(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

[dependencies]
black-76 = "0.1"
use black_76::{call_price, solve_iv, SolverConfig};

// Price an ATM call: F=100, K=100, T=1 year, sigma=20%, r=0
let c = call_price(100.0, 100.0, 1.0, 0.20, 0.0);
assert!((c - 7.9656).abs() < 1e-3);

// Recover the IV from a market price
let cfg = SolverConfig::default();
let result = solve_iv(7.9656, 100.0, 100.0, 1.0, 0.0, true, &cfg);
assert!(result.converged);
assert!((result.iv - 0.20).abs() < 1e-4);

Typo-resistant inputs

Positional f64 arguments are easy to mis-order. BlackInputs and IvQuery name every field (and are const-constructible):

use black_76::{BlackInputs, IvQuery, SolverConfig};

let inputs = BlackInputs::new(100.0, 100.0, 1.0, 0.20, 0.0); // f, k, t, sigma, r
let c = inputs.call_price();
let g = inputs.greeks(true); // delta, gamma, vega, theta, rho

let iv = IvQuery::new(c, 100.0, 100.0, 1.0, 0.0, true)
    .solve(&SolverConfig::default());
assert!(iv.converged);

Convergence checking is mandatory

use black_76::{solve_iv, SolverConfig};

let cfg = SolverConfig::default();
let result = solve_iv(/* implausibly high market price */ 1_000.0,
                      100.0, 100.0, 1.0, 0.0, true, &cfg);
if result.converged {
    println!("IV = {}", result.iv);
} else {
    eprintln!("no IV ({:?}); result.iv is NaN", result.status);
}

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)
black-76 = { version = "0.1", features = ["vol-surface", "digital", "serde"] }

Examples

Six runnable examples under examples/:

cargo run --example atm_price
cargo run --example solve_iv
cargo run --example put_call_parity
cargo run --example greeks
cargo run --example vol_smile     --features vol-surface
cargo run --example digital_prob  --features digital

Benchmarks

Two Criterion benches under benches/:

cargo bench --bench pricing
cargo bench --bench iv_solver

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/dsigma divided by 100.
  • Theta is reported per calendar day with year = 365.25 days.
  • Time is in years.
  • Rate is continuous compounding.
  • All numerics are f64. The crate exposes no Decimal-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

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.