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::*;