use scirs2_core::numeric::Float;
pub fn normal_cdf<F: Float>(x: F) -> F {
let a1 = F::from(0.254829592).expect("Failed to convert constant to float");
let a2 = F::from(-0.284496736).expect("Failed to convert constant to float");
let a3 = F::from(1.421413741).expect("Failed to convert constant to float");
let a4 = F::from(-1.453152027).expect("Failed to convert constant to float");
let a5 = F::from(1.061405429).expect("Failed to convert constant to float");
let p = F::from(0.3275911).expect("Failed to convert constant to float");
let sign = if x < F::zero() { -F::one() } else { F::one() };
let x_abs = x.abs();
let t = F::one() / (F::one() + p * x_abs);
let y = F::one()
- (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1)
* t
* (-x_abs * x_abs / F::from(2.0).expect("Failed to convert constant to float")).exp();
(F::one() + sign * y) / F::from(2.0).expect("Failed to convert constant to float")
}
pub fn normal_pdf<F: Float>(x: F) -> F {
let sqrt_2pi = F::from(2.506628274631).expect("Failed to convert constant to float"); (-x.powi(2) / F::from(2.0).expect("Failed to convert constant to float")).exp() / sqrt_2pi
}
pub fn normal_quantile<F: Float>(p: F) -> F {
if p <= F::zero() || p >= F::one() {
return F::nan();
}
let a0 = F::from(2.515517).expect("Failed to convert constant to float");
let a1 = F::from(0.802853).expect("Failed to convert constant to float");
let a2 = F::from(0.010328).expect("Failed to convert constant to float");
let b1 = F::from(1.432788).expect("Failed to convert constant to float");
let b2 = F::from(0.189269).expect("Failed to convert constant to float");
let b3 = F::from(0.001308).expect("Failed to convert constant to float");
let t = if p < F::from(0.5).expect("Failed to convert constant to float") {
(-F::from(2.0).expect("Failed to convert constant to float") * p.ln()).sqrt()
} else {
(-F::from(2.0).expect("Failed to convert constant to float") * (F::one() - p).ln()).sqrt()
};
let numerator = a0 + a1 * t + a2 * t.powi(2);
let denominator = F::one() + b1 * t + b2 * t.powi(2) + b3 * t.powi(3);
let z = t - numerator / denominator;
if p < F::from(0.5).expect("Failed to convert constant to float") {
-z
} else {
z
}
}
pub fn present_value<F: Float>(future_value: F, rate: F, time: F) -> F {
future_value * (-rate * time).exp()
}
pub fn future_value<F: Float>(present_value: F, rate: F, time: F) -> F {
present_value * (rate * time).exp()
}
pub fn calculate_d1<F: Float>(spot: F, strike: F, rate: F, volatility: F, time: F) -> F {
((spot / strike).ln()
+ (rate + volatility.powi(2) / F::from(2.0).expect("Failed to convert constant to float"))
* time)
/ (volatility * time.sqrt())
}
pub fn calculate_d2<F: Float>(d1: F, volatility: F, time: F) -> F {
d1 - volatility * time.sqrt()
}
pub fn bivariate_normal_cdf<F: Float>(x: F, y: F, rho: F) -> F {
if rho.abs() < F::from(0.001).expect("Failed to convert constant to float") {
return normal_cdf(x) * normal_cdf(y);
}
let cdf_x = normal_cdf(x);
let cdf_y = normal_cdf(y);
let correction = rho * normal_pdf(x) * normal_pdf(y)
/ F::from(4.0).expect("Failed to convert constant to float");
(cdf_x * cdf_y + correction).min(F::one()).max(F::zero())
}
pub fn continuous_to_simple_return<F: Float>(rate: F) -> F {
rate.exp() - F::one()
}
pub fn simple_to_continuous_return<F: Float>(simple_return: F) -> F {
(F::one() + simple_return).ln()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normal_cdf() {
let cdf_0 = normal_cdf(0.0);
assert!((cdf_0 - 0.5).abs() < 0.01);
let cdf_positive = normal_cdf(1.96);
assert!((cdf_positive - 0.975).abs() < 0.01);
let cdf_negative = normal_cdf(-1.96);
assert!((cdf_negative - 0.025).abs() < 0.01);
}
#[test]
fn test_normal_pdf() {
let pdf_0 = normal_pdf(0.0);
let expected = 1.0 / (2.0 * std::f64::consts::PI).sqrt();
assert!((pdf_0 - expected).abs() < 0.001);
let pdf_pos = normal_pdf(1.0);
let pdf_neg = normal_pdf(-1.0);
assert!((pdf_pos - pdf_neg).abs() < 1e-10);
}
#[test]
fn test_normal_quantile() {
let p = 0.975;
let quantile = normal_quantile(p);
let cdf_back = normal_cdf(quantile);
assert!((cdf_back - p).abs() < 0.01);
let q_median = normal_quantile(0.5);
assert!(q_median.abs() < 0.001);
}
#[test]
fn test_present_future_value() {
let pv = 100.0;
let rate = 0.05;
let time = 1.0;
let fv = future_value(pv, rate, time);
let pv_back = present_value(fv, rate, time);
assert!((pv_back - pv).abs() < 1e-10);
}
#[test]
fn test_d1_d2_calculation() {
let spot = 100.0;
let strike = 100.0;
let rate = 0.05;
let vol = 0.2;
let time = 1.0;
let d1 = calculate_d1(spot, strike, rate, vol, time);
let d2 = calculate_d2(d1, vol, time);
let expected_d2 = d1 - vol * time.sqrt();
assert!((d2 - expected_d2).abs() < 1e-10);
}
#[test]
fn test_bivariate_normal_independence() {
let x = 1.0;
let y = 0.5;
let rho = 0.0;
let biv_cdf = bivariate_normal_cdf(x, y, rho);
let product = normal_cdf(x) * normal_cdf(y);
assert!((biv_cdf - product).abs() < 0.01);
}
#[test]
fn test_return_conversions() {
let simple_return = 0.10; let continuous = simple_to_continuous_return(simple_return);
let back_to_simple = continuous_to_simple_return(continuous);
assert!((back_to_simple - simple_return).abs() < 1e-10);
}
#[test]
fn test_invalid_quantile() {
assert!(normal_quantile(-0.1).is_nan());
assert!(normal_quantile(1.1).is_nan());
assert!(normal_quantile(0.0).is_nan());
assert!(normal_quantile(1.0).is_nan());
}
}