Skip to main content

implied_vol/builder/
price_black_scholes.rs

1use crate::{SpecialFn, lets_be_rational};
2use bon::Builder;
3
4/// Builder-backed container for computing undiscounted European option prices
5/// under the Black–Scholes model.
6///
7/// Construct instances with `PriceBlackScholes::builder()`. The builder performs
8/// basic domain validation when you call `.build()`. Use `.build_unchecked()` to
9/// skip validation when you know inputs are already valid.
10///
11/// Fields:
12/// - `forward`: forward price of the underlying (F). Must be finite.
13/// - `strike`: strike price (K). Must be finite.
14/// - `volatility`: volatility (σ). Must be finite and `σ >= 0`.
15/// - `expiry`: time to expiry (T). Must be finite and `T >= 0`.
16/// - `is_call`: `true` to price a call option, `false` to price a put option.
17///
18/// The `calculate::<SpFn>()` method performs the numerical evaluation and uses a
19/// `SpecialFn` implementation for any special-function approximations required
20/// by the numerical routines.
21#[derive(Builder)]
22#[builder(const, derive(Clone, Debug),
23finish_fn(name = build_unchecked,
24doc{
25/// Build without performing any validation.
26///
27/// This constructor constructs the `PriceBlackScholes` directly from
28/// the builder's fields and does **not** check for NaNs, infinities, or
29/// sign constraints. Use only when you are certain the inputs are valid
30/// or when you want to avoid the cost of runtime validation.
31})
32)]
33pub struct PriceBlackScholes {
34    forward: f64,
35    strike: f64,
36    volatility: f64,
37    expiry: f64,
38    is_call: bool,
39}
40
41impl<S: price_black_scholes_builder::IsComplete> PriceBlackScholesBuilder<S> {
42    /// Validate builder inputs and construct `PriceBlackScholes`.
43    ///
44    /// Validation performed:
45    /// - `forward` must be positive and finite.
46    /// - `strike` must be positive and finite.
47    /// - `volatility` must be non-negative (`σ >= 0`) but can be positive infinite.
48    /// - `expiry` must be non-negative (`T >= 0`) but can be positive infinite.
49    ///
50    /// Returns `Some(PriceBlackScholes)` when all checks pass; otherwise returns
51    /// `None`.
52    pub const fn build(self) -> Option<PriceBlackScholes> {
53        let price_black_scholes = self.build_unchecked();
54        if !price_black_scholes.forward.is_finite() || !(price_black_scholes.forward > 0.0) {
55            return None;
56        }
57        if !price_black_scholes.strike.is_finite() || !(price_black_scholes.strike > 0.0) {
58            return None;
59        }
60        if !(price_black_scholes.volatility >= 0.0) {
61            return None;
62        }
63        if !(price_black_scholes.expiry >= 0.0) {
64            return None;
65        }
66        Some(price_black_scholes)
67    }
68}
69
70impl PriceBlackScholes {
71    /// Compute the undiscounted Black–Scholes option price for the stored inputs.
72    ///
73    /// # Type parameter
74    /// - `SpFn: SpecialFn` — implementation used for internal special-function
75    ///   evaluations. Use the crate's `DefaultSpecialFn` for the default behavior or provide a custom
76    ///   implementation to change numerical characteristics.
77    ///
78    /// # Returns
79    /// The computed undiscounted European option price.
80    #[must_use]
81    #[inline(always)]
82    pub fn calculate<SpFn: SpecialFn>(&self) -> f64 {
83        if self.is_call {
84            lets_be_rational::bs_option_price::black_input_unchecked::<SpFn, true>(
85                self.forward,
86                self.strike,
87                self.volatility,
88                self.expiry,
89            )
90        } else {
91            lets_be_rational::bs_option_price::black_input_unchecked::<SpFn, false>(
92                self.forward,
93                self.strike,
94                self.volatility,
95                self.expiry,
96            )
97        }
98    }
99}