surface_lib/models/
mod.rs

1pub mod bs;
2pub mod linear_iv;
3pub mod svi;
4
5/// Common traits used by all surface models
6pub mod traits {
7    use anyhow::Result;
8
9    /// Surface model trait for implied volatility calculations
10    pub trait SurfaceModel {
11        type Parameters;
12
13        fn parameters(&self) -> &Self::Parameters;
14        fn validate_params(&self) -> Result<()>;
15        fn total_variance(&self, k: f64, t: f64) -> Result<f64>;
16        fn check_calendar_arbitrage(&self, k: f64, t1: f64, t2: f64) -> Result<()>;
17        fn check_butterfly_arbitrage_at_k(&self, k: f64, t: f64) -> Result<()>;
18    }
19}
20
21/// Utility functions for option pricing and calculations
22pub mod utils {
23    use crate::models::traits::SurfaceModel;
24    use anyhow::{anyhow, Result};
25
26    /// Calculate log-moneyness: ln(K/S)
27    pub fn log_moneyness(strike: f64, spot: f64) -> f64 {
28        (strike / spot).ln()
29    }
30
31    /// Option pricing result
32    pub struct OptionPricingResult {
33        pub price: f64,
34        pub model_iv: f64,
35    }
36
37    /// Price an option using a surface model
38    pub fn price_option<T: SurfaceModel>(
39        option_type: &str,
40        strike: f64,
41        spot: f64,
42        r: f64,
43        q: f64,
44        t: f64,
45        model: &T,
46    ) -> Result<OptionPricingResult> {
47        let k = log_moneyness(strike, spot);
48        let total_var = model.total_variance(k, t)?;
49
50        if total_var <= 0.0 {
51            return Err(anyhow!("Non-positive total variance: {}", total_var));
52        }
53
54        let model_iv = (total_var / t).sqrt();
55        let price = black_scholes_price(option_type, spot, strike, r, q, t, model_iv)?;
56
57        Ok(OptionPricingResult { price, model_iv })
58    }
59
60    /// Black-Scholes option pricing
61    fn black_scholes_price(
62        option_type: &str,
63        s: f64,
64        k: f64,
65        r: f64,
66        q: f64,
67        t: f64,
68        sigma: f64,
69    ) -> Result<f64> {
70        if sigma <= 0.0 || t <= 0.0 {
71            return Err(anyhow!("Invalid parameters: sigma={}, t={}", sigma, t));
72        }
73
74        let d1 = ((s / k).ln() + (r - q + 0.5 * sigma * sigma) * t) / (sigma * t.sqrt());
75        let d2 = d1 - sigma * t.sqrt();
76
77        let price = match option_type.to_lowercase().as_str() {
78            "call" => s * (-q * t).exp() * normal_cdf(d1) - k * (-r * t).exp() * normal_cdf(d2),
79            "put" => k * (-r * t).exp() * normal_cdf(-d2) - s * (-q * t).exp() * normal_cdf(-d1),
80            _ => return Err(anyhow!("Invalid option type: {}", option_type)),
81        };
82
83        Ok(price)
84    }
85
86    /// Standard normal cumulative distribution function approximation
87    fn normal_cdf(x: f64) -> f64 {
88        0.5 * (1.0 + erf(x / 2.0_f64.sqrt()))
89    }
90
91    /// Error function approximation
92    fn erf(x: f64) -> f64 {
93        let a1 = 0.254829592;
94        let a2 = -0.284496736;
95        let a3 = 1.421413741;
96        let a4 = -1.453152027;
97        let a5 = 1.061405429;
98        let p = 0.3275911;
99
100        let sign = if x < 0.0 { -1.0 } else { 1.0 };
101        let x = x.abs();
102
103        let t = 1.0 / (1.0 + p * x);
104        let y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * (-x * x).exp();
105
106        sign * y
107    }
108}