Skip to main content

implied_vol/
lets_be_rational.rs

1pub mod bachelier_impl;
2pub mod bs_option_price;
3mod constants;
4mod rational_cubic;
5pub mod special_function;
6
7use crate::fused_multiply_add::MulAdd;
8
9use crate::lets_be_rational::constants::{
10    FRAC_2_PI_SQRT_27, FRAC_ONE_SQRT_3, FRAC_SQRT_3_CUBIC_ROOT_2_PI, SQRT_2_OVER_PI, SQRT_2_PI,
11    SQRT_3, SQRT_DBL_MAX, SQRT_PI_OVER_2,
12};
13use crate::lets_be_rational::rational_cubic::{
14    convex_rational_cubic_control_parameter_to_fit_second_derivative_at_left_side,
15    convex_rational_cubic_control_parameter_to_fit_second_derivative_at_right_side,
16    rational_cubic_interpolation,
17};
18use crate::lets_be_rational::special_function::SpecialFn;
19use crate::lets_be_rational::special_function::normal_distribution::inv_norm_pdf;
20use std::f64::consts::{FRAC_1_SQRT_2, SQRT_2};
21use std::ops::{Div, Neg};
22
23#[inline(always)]
24fn householder3_factor(v: f64, h2: f64, h3: f64) -> f64 {
25    v.mul_add2(0.5 * h2, 1.0) / v.mul_add2(h3 / 6.0, h2).mul_add2(v, 1.0)
26}
27
28#[inline(always)]
29fn householder4_factor(v: f64, h2: f64, h3: f64, h4: f64) -> f64 {
30    v.mul_add2(h3 / 6.0, h2).mul_add2(v, 1.0)
31        / v.mul_add2(h4 / 24.0, h2.mul_add2(h2 / 4.0, h3 / 3.0))
32            .mul_add2(v, 1.5 * h2)
33            .mul_add2(v, 1.0)
34}
35
36#[inline(always)]
37fn b_u_over_b_max(s_c: f64) -> f64 {
38    if s_c >= 2.449_489_742_783_178 {
39        let y = s_c.recip();
40
41        let g = y
42            .mul_add2(-1.229_189_712_271_654_4, 6.589_280_957_677_407E2)
43            .mul_add2(y, 6.169_692_835_129_17E2)
44            .mul_add2(y, 2.983_680_162_805_663E2)
45            .mul_add2(y, 8.488_089_220_080_239E1)
46            .mul_add2(y, 1.455_319_886_249_397_7E1)
47            .mul_add2(y, 1.375_163_082_077_259_1)
48            .mul_add2(y, -4.605_394_817_212_609E-2)
49            / y.mul_add2(5.206_084_752_279_256E2, 8.881_238_333_960_678E2)
50                .mul_add2(y, 8.698_830_313_690_185E2)
51                .mul_add2(y, 5.079_647_179_123_228E2)
52                .mul_add2(y, 2.030_420_459_952_177_3E2)
53                .mul_add2(y, 5.436_378_146_588_073E1)
54                .mul_add2(y, 9.327_034_903_790_405)
55                .mul_add2(y, 1.0);
56        y.mul_add2(g, -1.253_314_137_315_500_3)
57            .mul_add2(0.113_984_531_941_499_06 * y, 0.894_954_297_278_031_3)
58    } else {
59        let g = s_c
60            .mul_add2(-3.386_756_817_001_176_5E-9, -8.733_991_026_156_887E-4)
61            .mul_add2(s_c, -8.143_812_878_548_491E-3)
62            .mul_add2(s_c, -3.512_133_741_041_69E-2)
63            .mul_add2(s_c, -8.976_383_086_137_545E-2)
64            .mul_add2(s_c, -1.416_368_116_424_721E-1)
65            .mul_add2(s_c, -1.344_864_378_589_371E-1)
66            .mul_add2(s_c, -6.063_099_880_334_851E-2)
67            / s_c
68                .mul_add2(1.421_206_743_529_177_8E-2, 1.324_801_623_892_073E-1)
69                .mul_add2(s_c, 5.959_161_649_351_221E-1)
70                .mul_add2(s_c, 1.652_734_794_196_848_7)
71                .mul_add2(s_c, 3.018_638_953_766_389_6)
72                .mul_add2(s_c, 3.650_335_036_015_884_6)
73                .mul_add2(s_c, 2.722_003_340_655_505_5)
74                .mul_add2(s_c, 1.0);
75
76        s_c.mul_add2(g, 0.061_461_680_580_514_74)
77            .mul_add2(s_c * s_c, 0.789_908_594_556_062_8)
78    }
79}
80
81#[inline(always)]
82fn b_l_over_b_max(s_c: f64) -> f64 {
83    if s_c < 0.709_929_573_971_953_9 {
84        let g = s_c
85            .mul_add2(4.542_510_209_361_606_4E-7, -6.403_639_934_147_98E-6)
86            .mul_add2(s_c, 5.971_692_845_958_919E-3)
87            .mul_add2(s_c, 3.976_063_144_567_705_5E-2)
88            .mul_add2(s_c, 9.807_891_178_635_89E-2)
89            .mul_add2(s_c, 8.074_107_237_288_286E-2)
90            / s_c
91                .mul_add2(6.125_459_704_983_172E-2, 4.613_270_710_865_565E-1)
92                .mul_add2(s_c, 1.365_880_147_571_179)
93                .mul_add2(s_c, 1.859_497_767_228_766_5)
94                .mul_add2(s_c, 1.0);
95        (s_c * s_c)
96            * s_c.mul_add2(
97                s_c.mul_add2(g, -0.096_727_192_813_394_37),
98                0.075_609_966_402_963_62,
99            )
100    } else if s_c < 2.626_785_107_312_739_5 {
101        s_c.mul_add2(6.971_140_063_983_471E-4, 6.584_925_270_230_231E-3)
102            .mul_add2(s_c, 2.953_705_895_096_301_8E-2)
103            .mul_add2(s_c, 6.917_130_174_466_835E-2)
104            .mul_add2(s_c, 7.561_014_227_254_904E-2)
105            .mul_add2(s_c, -2.708_128_856_468_558_7E-8)
106            .mul_add2(s_c, 1.979_573_792_759_858E-9)
107            / s_c
108                .mul_add2(6.636_197_582_786_12E-3, 7.171_486_244_882_935E-2)
109                .mul_add2(s_c, 3.783_162_225_306_046E-1)
110                .mul_add2(s_c, 1.157_148_318_717_978_3)
111                .mul_add2(s_c, 2.129_710_354_999_518)
112                .mul_add2(s_c, 2.194_144_852_558_658)
113                .mul_add2(s_c, 1.0)
114    } else if s_c < 7.348_469_228_349_534 {
115        s_c.mul_add2(1.701_257_940_724_605_5E-3, 1.002_291_337_825_409E-2)
116            .mul_add2(s_c, 3.922_517_740_768_760_6E-2)
117            .mul_add2(s_c, 7.403_965_818_682_282E-2)
118            .mul_add2(s_c, 7.411_485_544_834_501E-2)
119            .mul_add2(s_c, 5.311_803_397_279_465E-4)
120            .mul_add2(s_c, -9.332_511_535_483_788E-5)
121            / s_c
122                .mul_add2(1.619_540_589_593_093_7E-2, 1.174_400_591_971_610_1E-1)
123                .mul_add2(s_c, 5.323_125_844_350_184E-1)
124                .mul_add2(s_c, 1.391_232_364_627_114)
125                .mul_add2(s_c, 2.344_181_670_708_740_4)
126                .mul_add2(s_c, 2.221_723_813_222_813_4)
127                .mul_add2(s_c, 1.0)
128    } else {
129        s_c.mul_add2(1.693_020_807_842_147_5E-3, 5.183_252_617_163_152E-3)
130            .mul_add2(s_c, 2.934_240_565_862_844_5E-2)
131            .mul_add2(s_c, 3.921_610_857_820_463_6E-2)
132            .mul_add2(s_c, 7.168_217_831_093_633E-2)
133            .mul_add2(s_c, -1.511_669_248_501_119_6E-3)
134            .mul_add2(s_c, 1.450_007_229_724_060_4E-3)
135            / s_c
136                .mul_add2(1.611_699_254_678_867_7E-2, 7.126_137_099_644_303E-2)
137                .mul_add2(s_c, 3.754_374_213_737_579E-1)
138                .mul_add2(s_c, 8.487_830_756_737_222E-1)
139                .mul_add2(s_c, 1.682_315_917_528_153_2)
140                .mul_add2(s_c, 1.617_631_350_230_541_5)
141                .mul_add2(s_c, 1.0)
142    }
143}
144
145#[inline(always)]
146fn compute_f_lower_map_and_first_two_derivatives<SpFn: SpecialFn>(
147    x: f64,
148    s: f64,
149) -> (f64, f64, f64) {
150    let ax = x.abs();
151    let z = FRAC_ONE_SQRT_3 * ax / s;
152    let y = z * z;
153    let s2 = s * s;
154    let phi_m = 0.5 * SpFn::erfc(FRAC_1_SQRT_2 * z);
155
156    let phi2 = phi_m * phi_m;
157    (
158        FRAC_2_PI_SQRT_27 * ax * (phi2 * phi_m),
159        std::f64::consts::TAU * y * phi2 * s2.mul_add2(0.125, y).exp(),
160        std::f64::consts::FRAC_PI_6 * y / (s2 * s)
161            * phi_m
162            * (8.0 * SQRT_3 * s).mul_add2(
163                ax,
164                (3.0 * s2).mul_add2(s2 - 8.0, -(8.0 * x * x)) * phi_m * inv_norm_pdf(z),
165            )
166            * 2.0f64.mul_add2(y, 0.25 * s2).exp(),
167    )
168}
169
170#[inline(always)]
171fn inverse_f_lower_map<SpFn: SpecialFn>(x: f64, f: f64) -> f64 {
172    (x * FRAC_ONE_SQRT_3
173        / SpFn::inverse_norm_cdf(FRAC_SQRT_3_CUBIC_ROOT_2_PI * f.cbrt() / x.abs().cbrt()))
174    .abs()
175}
176
177#[inline(always)]
178fn compute_f_upper_map_and_first_two_derivatives<SpFn: SpecialFn>(
179    x: f64,
180    s: f64,
181) -> (f64, f64, f64) {
182    let w = (x / s).powi(2);
183    (
184        0.5 * SpFn::erfc((0.5 * FRAC_1_SQRT_2) * s),
185        -0.5 * (0.5 * w).exp(),
186        SQRT_PI_OVER_2 * ((0.125 * s).mul_add2(s, w).exp()) * w / s,
187    )
188}
189
190#[inline(always)]
191fn inverse_f_upper_map<SpFn: SpecialFn>(f: f64) -> f64 {
192    -2.0 * SpFn::inverse_norm_cdf(f)
193}
194
195#[inline(always)]
196fn implied_normalised_volatility_atm<SpFn: SpecialFn>(beta: f64) -> f64 {
197    2.0 * SQRT_2 * SpFn::erfinv(beta)
198}
199
200#[inline(always)]
201fn lets_be_rational<SpFn: SpecialFn>(beta: f64, theta_x: f64) -> Option<f64> {
202    debug_assert!(theta_x < 0.0);
203    debug_assert!(beta > 0.0);
204    let b_max = (0.5 * theta_x).exp();
205    if beta >= b_max {
206        // time value exceeds the supremum of the model
207        None
208    } else {
209        Some(lets_be_rational_unchecked::<SpFn>(beta, theta_x, b_max))
210    }
211}
212
213#[inline(always)]
214fn lets_be_rational_unchecked<SpFn: SpecialFn>(beta: f64, theta_x: f64, b_max: f64) -> f64 {
215    let mut s;
216    let sqrt_ax = theta_x.neg().sqrt();
217    let s_c = SQRT_2 * sqrt_ax;
218    let ome = SpFn::one_minus_erfcx(sqrt_ax);
219    let b_c = 0.5 * b_max * ome;
220    if beta < b_c {
221        debug_assert!(theta_x < 0.0);
222        let s_l = SQRT_PI_OVER_2.mul_add2(-ome, s_c);
223        debug_assert!(s_l > 0.0);
224        let b_l = b_l_over_b_max(s_c) * b_max;
225        // no return
226        if beta < b_l {
227            let (f_lower_map_l, d_f_lower_map_l_d_beta, d2_f_lower_map_l_d_beta2) =
228                compute_f_lower_map_and_first_two_derivatives::<SpFn>(theta_x, s_l);
229            let r2 = convex_rational_cubic_control_parameter_to_fit_second_derivative_at_right_side::<
230                true,
231            >(
232                b_l,
233                f_lower_map_l,
234                (1.0, d_f_lower_map_l_d_beta),
235                d2_f_lower_map_l_d_beta2,
236            );
237            let mut f = rational_cubic_interpolation(
238                beta,
239                b_l,
240                (0.0, f_lower_map_l),
241                (1.0, d_f_lower_map_l_d_beta),
242                r2,
243            );
244            match f.partial_cmp(&0.0) {
245                Some(std::cmp::Ordering::Greater) | None => {
246                    let t = beta / b_l;
247                    f = f_lower_map_l.mul_add2(t, b_l * (1.0 - t)) * t;
248                }
249                _ => {}
250            }
251            let mut s = inverse_f_lower_map::<SpFn>(theta_x, f);
252            debug_assert!(s > 0.0);
253            let ln_beta = beta.ln();
254
255            let mut ds = 1.0_f64;
256            let mut final_trial = false;
257            while ds.abs() > f64::EPSILON * s {
258                debug_assert!(s > 0.0);
259                let (bx, ln_vega) =
260                    bs_option_price::scaled_normalised_black_and_ln_vega::<SpFn>(theta_x, s);
261                let ln_b = bx.ln() + ln_vega;
262                let bpob = bx.recip();
263                let h = theta_x / s;
264                let x2_over_s3 = h * h / s;
265                let b_h2 = s.mul_add2(-0.25, x2_over_s3);
266                let v = (ln_beta - ln_b) * ln_b / ln_beta * bx;
267                let lambda = ln_b.recip();
268                let ot_lambda = lambda.mul_add2(2.0, 1.0);
269                let h2 = ot_lambda.mul_add2(-bpob, b_h2);
270                let c = 3.0 * (x2_over_s3 / s);
271                let b_h3 = b_h2.mul_add2(b_h2, -c) - 0.25;
272                let sq_bpob = bpob * bpob;
273                let bppob = b_h2 * bpob;
274                let mu_plus_2 = (1.0 + lambda).mul_add2(6.0 * lambda, 2.0);
275                let h3 = (bppob * 3.0).mul_add2(-ot_lambda, sq_bpob.mul_add2(mu_plus_2, b_h3));
276                ds = v * if theta_x < -190.0 {
277                    householder4_factor(
278                        v,
279                        h2,
280                        h3,
281                        b_h2.mul_add2(b_h3 - 0.5, -((b_h2 - 2.0 / s) * 2.0 * c))
282                            - (b_h3 * bpob * 4.0).mul_add2(
283                                -ot_lambda,
284                                bpob.mul_add2(
285                                    sq_bpob.mul_add2(
286                                        lambda
287                                            .mul_add2(24.0, 36.0)
288                                            .mul_add2(lambda, 22.0)
289                                            .mul_add2(lambda, 6.0),
290                                        -(6.0 * bppob * mu_plus_2),
291                                    ),
292                                    -(bppob * 3.0 * ot_lambda),
293                                ),
294                            ),
295                    )
296                } else {
297                    householder3_factor(v, h2, h3)
298                };
299                s += ds;
300                debug_assert!(s > 0.0);
301                if final_trial {
302                    return s;
303                }
304                final_trial = true;
305            }
306            return s;
307        }
308        let inv_v = (
309            SQRT_2_PI / b_max,
310            bs_option_price::inv_normalised_vega(theta_x, s_l),
311        );
312        let h = b_c - b_l;
313        let r_im = convex_rational_cubic_control_parameter_to_fit_second_derivative_at_right_side::<
314            false,
315        >(h, s_c - s_l, inv_v, 0.0);
316        s = rational_cubic_interpolation(beta - b_l, h, (s_l, s_c), inv_v, r_im);
317        debug_assert!(s > 0.0);
318    } else {
319        let s_u = SQRT_PI_OVER_2.mul_add2(2.0 - ome, s_c);
320        debug_assert!(s_u > 0.0);
321        let b_u = b_u_over_b_max(s_c) * b_max;
322        if beta <= b_u {
323            let inv_v = (
324                SQRT_2_PI / b_max,
325                bs_option_price::inv_normalised_vega(theta_x, s_u),
326            );
327            let h = b_u - b_c;
328            let r_u_m =
329                convex_rational_cubic_control_parameter_to_fit_second_derivative_at_left_side::<
330                    false,
331                >(h, s_u - s_c, inv_v, 0.0);
332            s = rational_cubic_interpolation(beta - b_c, h, (s_c, s_u), inv_v, r_u_m);
333            debug_assert!(s > 0.0);
334        } else {
335            let (f_upper_map_h, d_f_upper_map_h_d_beta, d2_f_upper_map_h_d_beta2) =
336                compute_f_upper_map_and_first_two_derivatives::<SpFn>(theta_x, s_u);
337            let mut f = if (-SQRT_DBL_MAX..SQRT_DBL_MAX).contains(&d2_f_upper_map_h_d_beta2) {
338                let h = b_max - b_u;
339                let r_uu =
340                    convex_rational_cubic_control_parameter_to_fit_second_derivative_at_left_side::<
341                        true,
342                    >(
343                        h,
344                        -f_upper_map_h,
345                        (d_f_upper_map_h_d_beta, -0.5),
346                        d2_f_upper_map_h_d_beta2,
347                    );
348                rational_cubic_interpolation(
349                    beta - b_u,
350                    h,
351                    (f_upper_map_h, 0.0),
352                    (d_f_upper_map_h_d_beta, -0.5),
353                    r_uu,
354                )
355            } else {
356                f64::MIN
357            };
358            if f <= 0.0 {
359                let h = b_max - b_u;
360                let t = (beta - b_u) / h;
361                f = f_upper_map_h.mul_add2(1.0 - t, 0.5 * h * t) * (1.0 - t);
362            }
363            s = inverse_f_upper_map::<SpFn>(f);
364            if beta > 0.5 * b_max {
365                let beta_bar = b_max - beta;
366                let mut ds = f64::MIN;
367                let mut final_trial = false;
368                while ds.abs() > f64::EPSILON * s {
369                    let h = theta_x / s;
370                    let t = s / 2.0;
371                    let gp = SQRT_2_OVER_PI
372                        / (SpFn::erfcx((t + h) * FRAC_1_SQRT_2)
373                            + SpFn::erfcx((t - h) * FRAC_1_SQRT_2));
374                    let b_bar = bs_option_price::normalised_vega(theta_x, s) / gp;
375                    let g = (beta_bar / b_bar).ln();
376                    let x2_over_s3 = h * h / s;
377                    let b_h2 = s.mul_add2(-0.25, x2_over_s3);
378                    let c = 3.0 * (x2_over_s3 / s);
379                    let b_h3 = b_h2.mul_add2(b_h2, -c - 0.25);
380                    let v = -g / gp;
381                    let h2 = b_h2 + gp;
382                    let h3 = gp.mul_add2(2.0f64.mul_add2(gp, 3.0 * b_h2), b_h3);
383                    ds = v * if theta_x < -580.0 {
384                        householder4_factor(
385                            v,
386                            h2,
387                            h3,
388                            gp.mul_add2(
389                                4.0f64.mul_add2(
390                                    b_h3,
391                                    (6.0 * gp).mul_add2(b_h2.mul_add2(2.0, gp), 3.0 * b_h2 * b_h2),
392                                ),
393                                b_h2.mul_add2(b_h3 - 0.5, -((b_h2 - 2.0 / s) * 2.0 * c)),
394                            ),
395                        )
396                    } else {
397                        householder3_factor(v, h2, h3)
398                    };
399                    s += ds;
400                    if final_trial {
401                        break;
402                    }
403                    final_trial = true;
404                }
405                return s;
406            }
407        }
408    }
409    let mut ds = f64::MIN;
410    for _ in 0..2 {
411        if ds.abs() <= f64::EPSILON * s {
412            break;
413        }
414        debug_assert!(s > 0.0);
415        debug_assert!(theta_x < 0.0_f64);
416        let b = bs_option_price::normalised_black::<SpFn>(theta_x, s);
417        let bp = bs_option_price::normalised_vega(theta_x, s);
418        let nu = (beta - b) / bp;
419        let h = theta_x / s;
420        let h2 = s.mul_add2(-0.25, h * h / s);
421        let h3 = h2.mul_add2(h2, -(3.0 * (h / s).powi(2))) - 0.25_f64;
422        ds = nu * householder3_factor(nu, h2, h3);
423        s += ds;
424        // the upstream uses the following code, but it is not performant on my benchmark
425        // assert!(s > 0.0);
426        // let b = normalised_black(x, s);
427        // let inv_bp = inv_normalised_vega(x, s);
428        // let v = (beta - b) * inv_bp;
429        // let h = x / s;
430        // let x2_over_s3 = (h * h) / s;
431        // let h2 = x2_over_s3 - s * 0.25;
432        // let h3 = h2 * h2 - 3.0 * (x2_over_s3 / s) - 0.25;
433        // ds = v * householder3_factor(v, h2, h3);
434        // s += ds;
435    }
436    s
437}
438
439#[inline(always)]
440pub fn implied_black_volatility_input_unchecked<SpFn: SpecialFn, const IS_CALL: bool>(
441    price: f64,
442    f: f64,
443    k: f64,
444    t: f64,
445) -> Option<f64> {
446    if price >= if IS_CALL { f } else { k } {
447        return (price == if IS_CALL { f } else { k }).then_some(f64::INFINITY);
448    }
449    let intrinsic_value = if IS_CALL { f - k } else { k - f };
450    let normalized_time_value = if intrinsic_value > 0.0_f64 {
451        price - intrinsic_value
452    } else {
453        price
454    } / (f.sqrt() * k.sqrt());
455    if normalized_time_value <= 0.0_f64 {
456        return (normalized_time_value == 0.0).then_some(0.0);
457    }
458    Some(if f == k {
459        implied_normalised_volatility_atm::<SpFn>(normalized_time_value) / t.sqrt()
460    } else {
461        lets_be_rational::<SpFn>(normalized_time_value, (f / k).ln().abs().neg())?.div(t.sqrt())
462    })
463}
464
465#[cfg(test)]
466mod tests {
467    use super::*;
468    use crate::lets_be_rational::bs_option_price::{
469        black_input_unchecked, scaled_normalised_black_and_ln_vega,
470    };
471    use crate::lets_be_rational::special_function::DefaultSpecialFn;
472    use rand::Rng;
473
474    pub(crate) const FOURTH_ROOT_DBL_EPSILON: f64 = f64::from_bits(0x3f20000000000000);
475
476    fn normalised_intrinsic(theta_x: f64) -> f64 {
477        // if theta_x <= 0.0 {
478        //     return 0.0;
479        // }
480        let x2 = theta_x * theta_x;
481        if x2 < 98.0 * FOURTH_ROOT_DBL_EPSILON {
482            return x2
483                .mul_add2(1.0 / 92897280.0, 1.0 / 322560.0)
484                .mul_add2(x2, 1.0 / 1920.0)
485                .mul_add2(x2, 1.0 / 120.0)
486                .mul_add2(x2, 1.0 / 24.0)
487                .mul_add2(x2, 1.0)
488                * theta_x;
489        }
490        (0.5 * theta_x).exp() - (-0.5 * theta_x).exp()
491    }
492    fn scaled_normalised_black(theta_x: f64, s: f64) -> f64 {
493        debug_assert!(s > 0.0 && theta_x != 0.0);
494        (if theta_x > 0.0 {
495            normalised_intrinsic(theta_x)
496                * SQRT_2_PI
497                * (0.5 * ((theta_x / s).powi(2) + 0.25 * s * s)).exp()
498        } else {
499            0.0
500        }) + scaled_normalised_black_and_ln_vega::<DefaultSpecialFn>(-theta_x.abs(), s).0
501    }
502
503    #[allow(unused)]
504    fn black_accuracy_factor(x: f64, s: f64, theta: f64 /* θ=±1 */) -> f64 {
505        // When x = 0, we have bx(x,s) = b(x,s) / (∂(b(x,s)/∂s)  =  s·(1+s²/12+s⁴/240+O(s⁶)) for small s.
506        if x == 0.0 {
507            return if s.abs() < f64::EPSILON {
508                1.0
509            } else {
510                s / (DefaultSpecialFn::erf((0.5 * FRAC_1_SQRT_2) * s)
511                    * SQRT_2_PI
512                    * (0.125 * s * s).exp())
513            };
514        }
515        let theta_x = if theta < 0.0 { -x } else { x };
516        if s <= 0.0 {
517            return if theta_x > 0.0 { 0.0 } else { f64::MAX };
518        }
519        s / scaled_normalised_black(theta_x, s)
520    }
521
522    #[test]
523    fn reconstruction_call_atm() {
524        for i in 1..10000 {
525            let price = 0.01 * i as f64;
526            let f = 100.0;
527            let k = f;
528            let t = 1.0;
529            const Q: bool = true;
530            let sigma =
531                implied_black_volatility_input_unchecked::<DefaultSpecialFn, Q>(price, f, k, t)
532                    .unwrap();
533            let reprice = black_input_unchecked::<DefaultSpecialFn, Q>(f, k, sigma, t);
534            debug_assert!(
535                (price - reprice).abs() / price < 4.0 * f64::EPSILON,
536                "{f},{k},{t},{sigma},{price},{reprice},{}",
537                (price - reprice).abs() / price / f64::EPSILON
538            );
539        }
540    }
541
542    #[test]
543    fn reconstruction_call_atm2() {
544        for i in 1..=10000 {
545            let f = 100.0;
546            let k = f;
547            let t = 1.0;
548            const Q: bool = true;
549            let sigma = 0.001 * i as f64;
550            let price = black_input_unchecked::<DefaultSpecialFn, Q>(f, k, sigma, t);
551            let sigma2 =
552                implied_black_volatility_input_unchecked::<DefaultSpecialFn, Q>(price, f, k, t)
553                    .unwrap();
554            debug_assert!(
555                (sigma - sigma2).abs() / sigma
556                    <= 1.0
557                        + black_accuracy_factor((f / k).ln(), sigma * t.sqrt(), 1.0).recip()
558                            * f64::EPSILON,
559                "f: {f}, k: {k}, t: {t}, sigma: {sigma}, sigma2; {sigma2}, price: {price}, {}, {}",
560                (sigma - sigma2).abs() / sigma / f64::EPSILON,
561                1.0 + black_accuracy_factor((f / k).ln(), sigma * t.sqrt(), 1.0).recip()
562            );
563        }
564    }
565
566    #[test]
567    fn reconstruction_put_atm() {
568        for i in 1..100 {
569            let price = 0.01 * i as f64;
570            let f = 100.0;
571            let k = f;
572            let t = 1.0;
573            const Q: bool = false;
574            let sigma =
575                implied_black_volatility_input_unchecked::<DefaultSpecialFn, Q>(price, f, k, t)
576                    .unwrap();
577            let reprice = black_input_unchecked::<DefaultSpecialFn, Q>(f, k, sigma, t);
578            assert!((price - reprice).abs() < f64::EPSILON * 100.0);
579        }
580    }
581
582    #[test]
583    fn reconstruction_random_call_intrinsic() {
584        let n = 100_000;
585        let seed: [u8; 32] = [13; 32];
586        let mut rng: rand::rngs::StdRng = rand::SeedableRng::from_seed(seed);
587        for _ in 0..n {
588            let (r, r2, r3): (f64, f64, f64) = rng.random();
589            let price = 1e5 * r2;
590            let f = r + 1e5 * r2;
591            let k = f - price;
592            let t = 1e5 * r3;
593            const Q: bool = true;
594            let sigma =
595                implied_black_volatility_input_unchecked::<DefaultSpecialFn, Q>(price, f, k, t)
596                    .unwrap();
597            let reprice = black_input_unchecked::<DefaultSpecialFn, Q>(f, k, sigma, t);
598            assert!((price - reprice).abs() <= f64::EPSILON);
599        }
600    }
601
602    #[test]
603    fn reconstruction_random_call_itm() {
604        let n = 100_000;
605        let seed: [u8; 32] = [13; 32];
606        let mut rng: rand::rngs::StdRng = rand::SeedableRng::from_seed(seed);
607        for _ in 0..n {
608            let (r, r2, r3): (f64, f64, f64) = rng.random();
609            let price = 1.0 * (1.0 - r) + 1.0 * r * r2;
610            let f = 1.0;
611            let k = 1.0 * r;
612            let t = 1e5 * r3;
613            const Q: bool = true;
614            let sigma =
615                implied_black_volatility_input_unchecked::<DefaultSpecialFn, Q>(price, f, k, t)
616                    .unwrap();
617            let reprice = black_input_unchecked::<DefaultSpecialFn, Q>(f, k, sigma, t);
618            assert!(
619                (price - reprice).abs() <= 1.5 * f64::EPSILON,
620                "{f},{k},{t},{sigma},{price},{reprice},{}",
621                (price - reprice).abs() / f64::EPSILON
622            );
623        }
624    }
625
626    #[test]
627    fn reconstruction_random_call_otm() {
628        let n = 100_000;
629        let seed: [u8; 32] = [13; 32];
630        let mut rng: rand::rngs::StdRng = rand::SeedableRng::from_seed(seed);
631        for _ in 0..n {
632            let (r, r2, r3): (f64, f64, f64) = rng.random();
633            let price = 1.0 * r * r2;
634            let f = 1.0 * r;
635            let k = 1.0;
636            let t = 1e5 * r3;
637            const Q: bool = true;
638            let sigma =
639                implied_black_volatility_input_unchecked::<DefaultSpecialFn, Q>(price, f, k, t)
640                    .unwrap();
641            let reprice = black_input_unchecked::<DefaultSpecialFn, Q>(f, k, sigma, t);
642            assert!((price - reprice).abs() <= 1.5 * f64::EPSILON);
643        }
644    }
645
646    #[test]
647    fn reconstruction_random_put_itm() {
648        let n = 100_000;
649        let seed: [u8; 32] = [13; 32];
650        let mut rng: rand::rngs::StdRng = rand::SeedableRng::from_seed(seed);
651        for _ in 0..n {
652            let (r, r2, r3): (f64, f64, f64) = rng.random();
653            let price = 1.0 * r * r2;
654            let f = 1.0;
655            let k = 1.0 * r;
656            let t = 1e5 * r3;
657            const Q: bool = false;
658            let sigma =
659                implied_black_volatility_input_unchecked::<DefaultSpecialFn, Q>(price, f, k, t)
660                    .unwrap();
661            let reprice = black_input_unchecked::<DefaultSpecialFn, Q>(f, k, sigma, t);
662            // if (price - reprice).abs() > 1.5 * f64::EPSILON{
663            //     println!("{:?}", (price, f, k, t, q, sigma));
664            //     println!("{:?}", (price - reprice).abs() / f64::EPSILON);
665            // }
666            assert!((price - reprice).abs() <= 1.75 * f64::EPSILON);
667        }
668    }
669
670    #[test]
671    fn reconstruction_random_put_otm() {
672        let n = 100_000;
673        let seed: [u8; 32] = [13; 32];
674        let mut rng: rand::rngs::StdRng = rand::SeedableRng::from_seed(seed);
675        for _ in 0..n {
676            let (r, r2, r3): (f64, f64, f64) = rng.random();
677            let price = 1.0 * (1.0 - r) + 1.0 * r * r2;
678            let f = 1.0 * r;
679            let k = 1.0;
680            let t = 1e5 * r3;
681            const Q: bool = false;
682            let sigma =
683                implied_black_volatility_input_unchecked::<DefaultSpecialFn, Q>(price, f, k, t)
684                    .unwrap();
685            let reprice = black_input_unchecked::<DefaultSpecialFn, Q>(f, k, sigma, t);
686            assert!((price - reprice).abs() <= 1.5 * f64::EPSILON);
687        }
688    }
689
690    #[test]
691    fn panic_case() {
692        {
693            let price = 73.425;
694            let f = 12173.425;
695            let k = 12100.0;
696            let t = 0.0077076327759348934;
697            const Q: bool = true;
698            let sigma =
699                implied_black_volatility_input_unchecked::<DefaultSpecialFn, Q>(price, f, k, t)
700                    .unwrap();
701            let reprice = black_input_unchecked::<DefaultSpecialFn, Q>(f, k, sigma, t);
702            assert_eq!(price, reprice);
703        }
704        {
705            let price = 73.425;
706            let f = 12173.425;
707            let k = 12100.0;
708            let t = 0.007705811088032645;
709            const Q: bool = true;
710            let sigma =
711                implied_black_volatility_input_unchecked::<DefaultSpecialFn, Q>(price, f, k, t)
712                    .unwrap();
713            let reprice = black_input_unchecked::<DefaultSpecialFn, Q>(f, k, sigma, t);
714            assert_eq!(price, reprice);
715        }
716        {
717            let price = 73.425;
718            let f = 12173.425;
719            let k = 12100.0;
720            let t = 0.007705808219781035;
721            const Q: bool = true;
722            let sigma =
723                implied_black_volatility_input_unchecked::<DefaultSpecialFn, Q>(price, f, k, t)
724                    .unwrap();
725            let reprice = black_input_unchecked::<DefaultSpecialFn, Q>(f, k, sigma, t);
726            assert_eq!(price, reprice);
727        }
728        {
729            let price = 73.425;
730            let f = 12173.425;
731            let k = 12100.0;
732            let t = 0.007705804818688366;
733            const Q: bool = true;
734            let sigma =
735                implied_black_volatility_input_unchecked::<DefaultSpecialFn, Q>(price, f, k, t)
736                    .unwrap();
737            let reprice = black_input_unchecked::<DefaultSpecialFn, Q>(f, k, sigma, t);
738            assert_eq!(price, reprice);
739        }
740        {
741            let price = 33.55;
742            let f = 11633.55;
743            let k = 12100.0;
744            let t = 0.007705800716005495;
745            const Q: bool = true;
746            let sigma =
747                implied_black_volatility_input_unchecked::<DefaultSpecialFn, Q>(price, f, k, t)
748                    .unwrap();
749            let reprice = black_input_unchecked::<DefaultSpecialFn, Q>(f, k, sigma, t);
750            assert!(
751                ((price - reprice) / price).abs() <= 2.0 * f64::EPSILON,
752                "{}",
753                ((price - reprice) / price).abs() / f64::EPSILON
754            );
755        }
756        {
757            let price = 33.55;
758            let f = 11633.55;
759            let t = 0.0016085064438058978;
760            let k = 11600.0;
761            const Q: bool = true;
762            let sigma =
763                implied_black_volatility_input_unchecked::<DefaultSpecialFn, Q>(price, f, k, t)
764                    .unwrap();
765            let reprice = black_input_unchecked::<DefaultSpecialFn, Q>(f, k, sigma, t);
766            assert_eq!(price, reprice, "f: {f}, k: {k}, t: {t}, sigma: {sigma}");
767        }
768        {
769            let price = 33.55;
770            let f = 11633.55;
771            let t = 0.0016085064438058978;
772            let k = 11600.0;
773            const Q: bool = true;
774            let sigma =
775                implied_black_volatility_input_unchecked::<DefaultSpecialFn, Q>(price, f, k, t)
776                    .unwrap();
777            let reprice = black_input_unchecked::<DefaultSpecialFn, Q>(f, k, sigma, t);
778            assert_eq!(price, reprice, "f: {f}, k: {k}, t: {t}, sigma: {sigma}");
779        }
780    }
781
782    #[test]
783    fn time_inf() {
784        let price = 20.0;
785        let f = 100.0;
786        let k = 100.0;
787        let t = f64::INFINITY;
788        const Q: bool = true;
789        let sigma = implied_black_volatility_input_unchecked::<DefaultSpecialFn, Q>(price, f, k, t)
790            .unwrap();
791        assert_eq!(sigma, 0.0);
792    }
793}