use std::f64::consts::FRAC_1_SQRT_2;
use statrs::consts::SQRT_2PI;
use crate::{
OptionType,
lets_be_rational::{
DENORMALISATION_CUTOFF, ONE_OVER_SQRT_TWO_PI,
cody::optimized::{erfc, erfcx},
intrinsic::normalised_intrinsic,
normal_distribution::standard_normal_cdf,
},
};
const H_LARGE: f64 = -10.0;
const ASYMPTOTIC_EXPANSION_ACCURACY_THRESHOLD: f64 = -10.0;
const SIXTEENTH_ROOT_DBL_EPSILON: f64 = 0.10566243270259357;
const CODYS_THRESHOLD: f64 = 0.46875;
const SMALL_T_EXPANSION_OF_NORMALISED_BLACK_THRESHOLD: f64 = 2.0 * SIXTEENTH_ROOT_DBL_EPSILON;
#[allow(dead_code)]
fn normalised_black_call_using_norm_cdf(x: f64, s: f64) -> f64 {
let h = x / s;
let t = 0.5 * s;
let b_max = (0.5 * x).exp();
let b = standard_normal_cdf(h + t) * b_max - standard_normal_cdf(h - t) / b_max;
b.max(0.0)
}
fn normalised_black_call_with_optimal_use_of_codys_functions(x: f64, s: f64) -> f64 {
let h = x / s;
let t = 0.5 * s;
let q1 = -FRAC_1_SQRT_2 * (h + t);
let q2 = -FRAC_1_SQRT_2 * (h - t);
let two_b;
if q1 < CODYS_THRESHOLD {
if q2 < CODYS_THRESHOLD {
two_b = (0.5 * x).exp() * erfc(q1) - (-0.5 * x).exp() * erfc(q2);
} else {
two_b = (0.5 * x).exp() * erfc(q1) - (-0.5 * (h * h + t * t)).exp() * erfcx(q2);
}
} else if q2 < CODYS_THRESHOLD {
two_b = (-0.5 * (h * h + t * t)).exp() * erfcx(q1) - (-0.5 * x).exp() * erfc(q2);
} else {
two_b = (-0.5 * (h * h + t * t)).exp() * (erfcx(q1) - erfcx(q2));
}
(0.5 * two_b).max(0.0)
}
pub fn small_t_expansion_of_normalised_black_call(h: f64, t: f64) -> Option<f64> {
if t >= SMALL_T_EXPANSION_OF_NORMALISED_BLACK_THRESHOLD {
return None;
}
let t2 = t * t;
let h2 = h * h;
let a = 1.0 + h * (0.5 * SQRT_2PI) * erfcx(-FRAC_1_SQRT_2 * h);
let y_diff = {
let term1 = a.mul_add(h2 + 3.0, -1.0) / 6.0;
let term2 = h2.mul_add(a.mul_add(h2 + 10.0, -1.0), a.mul_add(15.0, -7.0)) / 120.0;
let term3 = h2.mul_add(
h2.mul_add(a.mul_add(h2 + 21.0, -1.0), a.mul_add(105.0, -18.0)),
a.mul_add(105.0, -57.0),
) / 5040.0;
let term4 = h2.mul_add(
h2.mul_add(
h2.mul_add(a.mul_add(h2 + 36.0, -1.0), a.mul_add(378.0, -33.0)),
a.mul_add(1260.0, -285.0),
),
a.mul_add(945.0, -561.0),
) / 362880.0;
let term5 = h2.mul_add(
h2.mul_add(
h2.mul_add(
h2.mul_add(a.mul_add(h2 + 55.0, -1.0), a.mul_add(990.0, -52.0)),
a.mul_add(6930.0, -840.0),
),
a.mul_add(17325.0, -4680.0),
),
a.mul_add(10395.0, -6555.0),
) / 39916800.0;
let term6 = h2.mul_add(
h2.mul_add(
h2.mul_add(
h2.mul_add(
h2.mul_add(a.mul_add(h2 + 78.0, -1.0), a.mul_add(2145.0, -75.0)),
a.mul_add(25740.0, -1926.0),
),
a.mul_add(135135.0, -20370.0),
),
a.mul_add(270270.0, -82845.0),
),
a.mul_add(135135.0, -89055.0),
) / 6227020800.0;
let t2_squared = t2 * t2;
let t2_cubed = t2_squared * t2;
let t2_quartic = t2_squared * t2_squared;
let t2_quintic = t2_cubed * t2_squared;
let t2_sextic = t2_cubed * t2_cubed;
2.0 * t
* (a.mul_add(
1.0,
term1.mul_add(
t2,
term2.mul_add(
t2_squared,
term3.mul_add(
t2_cubed,
term4.mul_add(t2_quartic, term5.mul_add(t2_quintic, term6 * t2_sextic)),
),
),
),
))
};
let exp_term = (-0.5 * (h2 + t2)).exp();
let black_value = ONE_OVER_SQRT_TWO_PI * exp_term * y_diff;
Some(black_value.max(0.0))
}
#[allow(dead_code)]
pub fn normalised_black_call_using_erfcx(h: f64, t: f64) -> f64 {
let b = 0.5
* (-0.5 * (h * h + t * t)).exp()
* (erfcx(-FRAC_1_SQRT_2 * (h + t)) - erfcx(-FRAC_1_SQRT_2 * (h - t)));
b.max(0.0)
}
pub(crate) fn normalised_black_call(x: f64, s: f64) -> f64 {
if x > 0.0 {
return normalised_intrinsic(x, OptionType::Call) + normalised_black_call(-x, s);
}
if s <= x.abs() * DENORMALISATION_CUTOFF {
return normalised_intrinsic(x, OptionType::Call); }
if x < s * ASYMPTOTIC_EXPANSION_ACCURACY_THRESHOLD
&& 0.5 * s * s + x
< s * (SMALL_T_EXPANSION_OF_NORMALISED_BLACK_THRESHOLD
+ ASYMPTOTIC_EXPANSION_ACCURACY_THRESHOLD)
{
return asymptotic_expansion_of_normalised_black_call(x / s, 0.5 * s)
.expect("Asymptotic expansion failed");
}
if 0.5 * s < SMALL_T_EXPANSION_OF_NORMALISED_BLACK_THRESHOLD {
return small_t_expansion_of_normalised_black_call(x / s, 0.5 * s)
.expect("Small t expansion failed");
}
normalised_black_call_with_optimal_use_of_codys_functions(x, s)
}
pub(crate) fn normalised_black(x: f64, s: f64, option_type: OptionType) -> f64 {
normalised_black_call(option_type * x, s)
}
pub fn asymptotic_expansion_of_normalised_black_call(h: f64, t: f64) -> Result<f64, &'static str> {
if h > H_LARGE
|| t >= (h.abs() - H_LARGE.abs() + SMALL_T_EXPANSION_OF_NORMALISED_BLACK_THRESHOLD)
{
return Err("This asymptotic expansion is only valid for large negative h and small t");
}
let h_plus_t = h + t;
let h_minus_t = h - t;
let h_plus_t_sq = h_plus_t * h_plus_t;
let h_minus_t_sq = h_minus_t * h_minus_t;
let mut y_h_plus_t = 1.0 / h_plus_t - 1.0 / (h_plus_t_sq * h_plus_t)
+ 3.0 / (h_plus_t_sq * h_plus_t_sq * h_plus_t);
let mut y_h_minus_t = 1.0 / h_minus_t - 1.0 / (h_minus_t_sq * h_minus_t)
+ 3.0 / (h_minus_t_sq * h_minus_t_sq * h_minus_t);
let mut factorial = 15.0; let mut sign = -1.0;
let mut term_plus = 1.0 / (factorial * h_plus_t_sq * h_plus_t_sq * h_plus_t);
let mut term_minus = 1.0 / (factorial * h_minus_t_sq * h_minus_t_sq * h_minus_t);
for i in 3..=17 {
y_h_plus_t += sign * term_plus;
y_h_minus_t += sign * term_minus;
sign = -sign;
let i_f64 = i as f64;
let factor = (2.0 * i_f64 + 2.0) * (2.0 * i_f64 + 3.0);
factorial *= factor;
term_plus *= h_plus_t_sq / factor;
term_minus *= h_minus_t_sq / factor;
}
let diff_y = y_h_plus_t - y_h_minus_t;
let exp_term = f64::mul_add(-0.5, h * h, -0.5 * t * t).exp();
Ok((exp_term * diff_y / SQRT_2PI).abs())
}