implied_vol/
lib.rs

1//! Safe, ergonomic builders for Black–Scholes / Bachelier pricing and implied volatility.
2//!
3//! This crate exposes builders for computing:
4//! - the implied **Black** volatility,
5//! - the implied **Normal** (Bachelier) volatility,
6//! - undiscounted European option prices under the **Black–Scholes** model,
7//! - undiscounted European option prices under the **Bachelier** (Normal) model.
8//!
9//! Additionally, the crate provides a `SpecialFn` trait for implementing special functions such as `erf`
10//! and `normal_cdf` used by the underlying algorithms.
11//!
12//! # Getting started
13//!
14//! Add the crate to your `Cargo.toml`:
15//!
16//! ```toml
17//! [dependencies]
18//! implied-vol = "2.0.0"
19//! ```
20//!
21//! To enable aggressive fused-multiply-add optimizations (when available), enable the
22//! `fma` feature in `Cargo.toml`:
23//!
24//! ```toml
25//! [dependencies]
26//! implied-vol = { version = "2.0.0", features = ["fma"] }
27//! ```
28//!
29//! # Models and notation
30//!
31//! For clarity, we restrict the documentation examples to *undiscounted* European call options.
32//! The same APIs apply to put options and to the Bachelier model (normal model).
33//!
34//! Let `BS(F, K, T, σ)` denote the undiscounted European call price under the Black–Scholes model,
35//! where:
36//! - `F`: forward price (underlying), `0 < F < ∞`,
37//! - `K`: strike price, `0 < K < ∞`,
38//! - `T`: time to expiry, `0 ≤ T < ∞`,
39//! - `σ`: volatility, `0 ≤ σ < ∞`.
40//!
41//! Given `F`, `K`, `T` and an observed option price `P`, the **implied Black volatility**
42//! is the value `σ` such that `BS(F, K, T, σ) = P`. In other words, it is the inverse
43//! of `BS` with respect to `σ`.
44//!
45//! # Usage and error handling
46//!
47//! The crate provides builder types that validate inputs at construction time and then
48//!  exposes a `calculate()` method for performing the computation. Validation behavior is:
49//!
50//! - `build()` performs input validation and returns `Option<...>`; it yields `None` when
51//!   the provided parameters are outside the mathematical domain of the target function.
52//! - `build_unchecked()` constructs the object without validation (for callers who prefer
53//!   to do their own checks or avoid the runtime cost).
54//!
55//! All heavy numerical work is done in `calculate()`; for implied-volatility builders
56//! `calculate()` returns `Option<f64>` (or `None` when the given option price is not in the
57//! function's image), and for price builders `calculate()` returns `f64`.
58//!
59//! ## Special functions
60//!
61//! Some algorithms require special mathematical functions.
62//! Those are abstracted behind the `SpecialFn` trait. The crate provides
63//! a default implementation named `DefaultSpecialFn` (based on the original author's code).
64//! If you need to swap in a different implementation (for testing or higher-precision math),
65//! implement the `SpecialFn` trait and call `calculate::<YourSpecialFn>()`.
66//!
67//! ## `PriceBlackScholes` (example)
68//!
69//! `PriceBlackScholes::builder()` validates inputs (via `build()`), returning `None` when
70//! the inputs are not in the domain of `BS`. Use `build_unchecked()` to skip validation.
71//!
72//! ```rust
73//! use implied_vol::{DefaultSpecialFn, PriceBlackScholes};
74//!
75//! // Valid inputs -> builder.build() returns Some(...)
76//! let builder = PriceBlackScholes::builder()
77//!     .forward(100.0)
78//!     .strike(100.0)
79//!     .volatility(0.2)
80//!     .expiry(1.0)
81//!     .is_call(true);
82//!
83//! let price_builder = builder.build();
84//! assert!(price_builder.is_some());
85//! let price = price_builder.unwrap().calculate::<DefaultSpecialFn>();
86//! assert!(price.is_finite());
87//!
88//! // Skip validation:
89//! let price_builder = PriceBlackScholes::builder()
90//!     .forward(100.0)
91//!     .strike(100.0)
92//!     .volatility(0.2)
93//!     .expiry(1.0)
94//!     .is_call(true)
95//!     .build_unchecked();
96//! let price = price_builder.calculate::<DefaultSpecialFn>();
97//! assert!(price.is_finite());
98//!
99//! // Invalid inputs -> build() returns None
100//! let invalid = PriceBlackScholes::builder()
101//!     .forward(f64::INFINITY) // invalid forward
102//!     .strike(100.0)
103//!     .volatility(0.2)
104//!     .expiry(1.0)
105//!     .is_call(true)
106//!     .build();
107//! assert!(invalid.is_none());
108//! ```
109//!
110//! ## `ImpliedBlackVolatility` (example)
111//!
112//! `ImpliedBlackVolatility::builder()` validates that `BS(F, K, T, ·)` is well-defined for
113//! the supplied `F`, `K`, `T` and that the provided `option_price` is a finite, non-negative number.
114//! - `build()` returns `None` if the inputs fall outside the model domain.
115//! - After a successful `build()`, calling `calculate()` returns `Some(σ)` when the given
116//!   `option_price` lies in the image of `BS(F, K, T, ·)`. If the price is outside that image,
117//!   `calculate()` returns `None`.
118//!
119//! ```rust
120//! use implied_vol::{DefaultSpecialFn, ImpliedBlackVolatility};
121//!
122//! // Valid inputs -> build() returns Some(...), calculate() may return Some(σ).
123//! let builder = ImpliedBlackVolatility::builder()
124//!     .option_price(10.0)
125//!     .forward(100.0)
126//!     .strike(100.0)
127//!     .expiry(1.0)
128//!     .is_call(true);
129//!
130//! let iv_builder = builder.build();
131//! assert!(iv_builder.is_some());
132//! let sigma_opt = iv_builder.unwrap().calculate::<DefaultSpecialFn>();
133//! assert!(sigma_opt.is_some()); // implied vol found
134//!
135//! // Skip validation:
136//! let sigma = ImpliedBlackVolatility::builder()
137//!     .option_price(10.0)
138//!     .forward(100.0)
139//!     .strike(100.0)
140//!     .expiry(1.0)
141//!     .is_call(true)
142//!     .build_unchecked()
143//!     .calculate::<DefaultSpecialFn>();
144//! assert!(sigma.is_some());
145//!
146//! // If model parameters are invalid -> build() returns None
147//! let invalid_builder = ImpliedBlackVolatility::builder()
148//!     .option_price(10.0)
149//!     .forward(f64::INFINITY) // invalid forward
150//!     .strike(100.0)
151//!     .expiry(1.0)
152//!     .is_call(true)
153//!     .build();
154//! assert!(invalid_builder.is_none());
155//!
156//! // If the option price is outside the attainable range, calculate() returns None.
157//! let out_of_range = ImpliedBlackVolatility::builder()
158//!     .option_price(110.0) // too large for F=100,K=100
159//!     .forward(100.0)
160//!     .strike(100.0)
161//!     .expiry(1.0)
162//!     .is_call(true)
163//!     .build()
164//!     .unwrap()
165//!     .calculate::<DefaultSpecialFn>();
166//! assert!(out_of_range.is_none());
167//! ```
168mod builder;
169#[cfg(feature = "cxx_bench")]
170pub mod cxx;
171mod fused_multiply_add;
172mod lets_be_rational;
173
174pub use crate::lets_be_rational::special_function::DefaultSpecialFn;
175pub use crate::lets_be_rational::special_function::SpecialFn;
176pub use builder::*;