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}