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 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 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 }
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 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 ) -> f64 {
505 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 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}