use stochastic_rs_distributions::special::norm_cdf;
use super::types::InterestRateOptionKind;
use super::types::VolatilityQuoteKind;
use super::volatility::VolatilityModel;
use crate::traits::FloatExt;
pub fn black_forward_caplet<T: FloatExt>(forward: T, strike: T, tau: T, sigma: T) -> T {
let f = forward.to_f64().unwrap_or(0.0);
let k = strike.to_f64().unwrap_or(0.0);
let t = tau.to_f64().unwrap_or(0.0);
let v = sigma.to_f64().unwrap_or(0.0);
if f <= 0.0 || k <= 0.0 || t <= 0.0 || v <= 0.0 {
return T::from_f64_fast((f - k).max(0.0));
}
let sqrt_t = t.sqrt();
let d1 = ((f / k).ln() + 0.5 * v * v * t) / (v * sqrt_t);
let d2 = d1 - v * sqrt_t;
T::from_f64_fast(f * norm_cdf(d1) - k * norm_cdf(d2))
}
pub fn black_forward_floorlet<T: FloatExt>(forward: T, strike: T, tau: T, sigma: T) -> T {
let f = forward.to_f64().unwrap_or(0.0);
let k = strike.to_f64().unwrap_or(0.0);
let t = tau.to_f64().unwrap_or(0.0);
let v = sigma.to_f64().unwrap_or(0.0);
if f <= 0.0 || k <= 0.0 || t <= 0.0 || v <= 0.0 {
return T::from_f64_fast((k - f).max(0.0));
}
let sqrt_t = t.sqrt();
let d1 = ((f / k).ln() + 0.5 * v * v * t) / (v * sqrt_t);
let d2 = d1 - v * sqrt_t;
T::from_f64_fast(k * norm_cdf(-d2) - f * norm_cdf(-d1))
}
pub fn bachelier_forward_caplet<T: FloatExt>(forward: T, strike: T, tau: T, sigma: T) -> T {
let f = forward.to_f64().unwrap_or(0.0);
let k = strike.to_f64().unwrap_or(0.0);
let t = tau.to_f64().unwrap_or(0.0);
let v = sigma.to_f64().unwrap_or(0.0);
if t <= 0.0 || v <= 0.0 {
return T::from_f64_fast((f - k).max(0.0));
}
let sqrt_vt = v * t.sqrt();
let d = (f - k) / sqrt_vt;
let pdf = (-0.5 * d * d).exp() / (2.0 * std::f64::consts::PI).sqrt();
T::from_f64_fast((f - k) * norm_cdf(d) + sqrt_vt * pdf)
}
pub fn bachelier_forward_floorlet<T: FloatExt>(forward: T, strike: T, tau: T, sigma: T) -> T {
let f = forward.to_f64().unwrap_or(0.0);
let k = strike.to_f64().unwrap_or(0.0);
let t = tau.to_f64().unwrap_or(0.0);
let v = sigma.to_f64().unwrap_or(0.0);
if t <= 0.0 || v <= 0.0 {
return T::from_f64_fast((k - f).max(0.0));
}
let sqrt_vt = v * t.sqrt();
let d = (f - k) / sqrt_vt;
let pdf = (-0.5 * d * d).exp() / (2.0 * std::f64::consts::PI).sqrt();
T::from_f64_fast((k - f) * norm_cdf(-d) + sqrt_vt * pdf)
}
#[allow(clippy::too_many_arguments)]
pub fn caplet_price<T: FloatExt, V: VolatilityModel<T> + ?Sized>(
forward: T,
strike: T,
tau: T,
notional: T,
accrual_factor: T,
discount_factor: T,
vol: &V,
kind: InterestRateOptionKind,
) -> T {
let sigma = vol.implied_volatility(forward, strike, tau);
let forward_value = match (vol.quote_kind(), kind) {
(VolatilityQuoteKind::Lognormal, InterestRateOptionKind::Cap) => {
black_forward_caplet(forward, strike, tau, sigma)
}
(VolatilityQuoteKind::Lognormal, InterestRateOptionKind::Floor) => {
black_forward_floorlet(forward, strike, tau, sigma)
}
(VolatilityQuoteKind::Normal, InterestRateOptionKind::Cap) => {
bachelier_forward_caplet(forward, strike, tau, sigma)
}
(VolatilityQuoteKind::Normal, InterestRateOptionKind::Floor) => {
bachelier_forward_floorlet(forward, strike, tau, sigma)
}
};
notional * accrual_factor * discount_factor * forward_value
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn black_put_call_parity() {
let f = 0.04_f64;
let k = 0.035_f64;
let tau = 1.5_f64;
let sigma = 0.25_f64;
let call = black_forward_caplet(f, k, tau, sigma);
let put = black_forward_floorlet(f, k, tau, sigma);
let parity = call - put;
assert!((parity - (f - k)).abs() < 1e-12);
}
#[test]
fn bachelier_put_call_parity() {
let f = 0.04_f64;
let k = 0.03_f64;
let tau = 2.0_f64;
let sigma = 0.0125_f64;
let call = bachelier_forward_caplet(f, k, tau, sigma);
let put = bachelier_forward_floorlet(f, k, tau, sigma);
assert!(((call - put) - (f - k)).abs() < 1e-12);
}
#[test]
fn bachelier_atm_closed_form() {
let f = 0.03_f64;
let tau = 1.0_f64;
let sigma = 0.01_f64;
let call = bachelier_forward_caplet(f, f, tau, sigma);
let expected = sigma * tau.sqrt() / (2.0 * std::f64::consts::PI).sqrt();
assert!((call - expected).abs() < 1e-12);
}
}