1use crate::error::{IntegrateError, IntegrateResult as Result};
28use std::f64::consts::{PI, SQRT_2};
29
30#[derive(Debug, Clone)]
32pub struct SABRParameters {
33 pub alpha: f64,
35 pub beta: f64,
37 pub rho: f64,
39 pub nu: f64,
41}
42
43impl SABRParameters {
44 pub fn new(alpha: f64, beta: f64, rho: f64, nu: f64) -> Result<Self> {
46 if alpha <= 0.0 {
47 return Err(IntegrateError::ValueError(
48 "Alpha must be positive".to_string(),
49 ));
50 }
51 if !(0.0..=1.0).contains(&beta) {
52 return Err(IntegrateError::ValueError(
53 "Beta must be between 0 and 1".to_string(),
54 ));
55 }
56 if !(-1.0..=1.0).contains(&rho) {
57 return Err(IntegrateError::ValueError(
58 "Rho must be between -1 and 1".to_string(),
59 ));
60 }
61 if nu < 0.0 {
62 return Err(IntegrateError::ValueError(
63 "Nu must be non-negative".to_string(),
64 ));
65 }
66
67 Ok(Self {
68 alpha,
69 beta,
70 rho,
71 nu,
72 })
73 }
74
75 pub fn implied_volatility(&self, forward: f64, strike: f64, time: f64) -> Result<f64> {
77 if forward <= 0.0 || strike <= 0.0 {
78 return Err(IntegrateError::ValueError(
79 "Forward and strike must be positive".to_string(),
80 ));
81 }
82 if time <= 0.0 {
83 return Err(IntegrateError::ValueError(
84 "Time must be positive".to_string(),
85 ));
86 }
87
88 if (forward - strike).abs() / forward < 1e-8 {
90 let f_mid_beta = forward.powf(1.0 - self.beta);
91 let term1 = self.alpha / f_mid_beta;
92
93 let term2 = 1.0
94 + ((1.0 - self.beta).powi(2) / 24.0 * self.alpha.powi(2)
95 / forward.powf(2.0 - 2.0 * self.beta)
96 + 0.25 * self.rho * self.beta * self.nu * self.alpha / f_mid_beta
97 + (2.0 - 3.0 * self.rho.powi(2)) / 24.0 * self.nu.powi(2))
98 * time;
99
100 return Ok(term1 * term2);
101 }
102
103 let log_fk = (forward / strike).ln();
105 let f_k_mid_beta = (forward * strike).powf((1.0 - self.beta) / 2.0);
106
107 let z = (self.nu / self.alpha) * f_k_mid_beta * log_fk;
108
109 let x = if z.abs() < 1e-6 {
111 1.0 - 0.5 * self.rho * z
113 } else {
114 let sqrt_term = (1.0 - 2.0 * self.rho * z + z.powi(2)).sqrt();
115 ((sqrt_term + z - self.rho) / (1.0 - self.rho)).ln() / z
116 };
117
118 let numerator = self.alpha;
119 let denominator = f_k_mid_beta
120 * (1.0
121 + (1.0 - self.beta).powi(2) / 24.0 * log_fk.powi(2)
122 + (1.0 - self.beta).powi(4) / 1920.0 * log_fk.powi(4));
123
124 let correction = 1.0
125 + ((1.0 - self.beta).powi(2) / 24.0 * self.alpha.powi(2)
126 / forward.powf(2.0 - 2.0 * self.beta)
127 + 0.25 * self.rho * self.beta * self.nu * self.alpha / f_k_mid_beta
128 + (2.0 - 3.0 * self.rho.powi(2)) / 24.0 * self.nu.powi(2))
129 * time;
130
131 Ok((numerator / denominator) * x * correction)
132 }
133}
134
135#[deprecated(
137 since = "0.1.0-beta.4",
138 note = "Use SABRParameters::implied_volatility instead"
139)]
140pub fn interpolate_smile() -> Result<()> {
141 Err(IntegrateError::ValueError(
142 "Use SABRParameters for smile interpolation".to_string(),
143 ))
144}
145
146#[deprecated(
147 since = "0.1.0-beta.4",
148 note = "Arbitrage checking not yet implemented"
149)]
150pub fn vol_surface_arbitrage_free() -> Result<()> {
151 Err(IntegrateError::ValueError(
152 "Arbitrage-free surface checking not yet implemented".to_string(),
153 ))
154}
155
156pub fn norm_cdf(x: f64) -> f64 {
160 if x.is_nan() {
161 return f64::NAN;
162 }
163 if x.is_infinite() {
164 return if x > 0.0 { 1.0 } else { 0.0 };
165 }
166
167 let abs_x = x.abs();
170
171 if abs_x > 10.0 {
172 return if x > 0.0 { 1.0 } else { 0.0 };
174 }
175
176 let t = 1.0 / (1.0 + 0.2316419 * abs_x);
177 let poly = t
178 * (0.319381530
179 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));
180
181 let pdf = (-0.5 * x * x).exp() / (2.0 * PI).sqrt();
182 let cdf = 1.0 - pdf * poly;
183
184 if x >= 0.0 {
185 cdf
186 } else {
187 1.0 - cdf
188 }
189}
190
191#[inline]
193pub fn norm_pdf(x: f64) -> f64 {
194 (2.0 * PI).sqrt().recip() * (-0.5 * x * x).exp()
195}
196
197#[inline]
199pub fn safe_log(x: f64) -> f64 {
200 if x > 0.0 {
201 x.ln()
202 } else if x == 0.0 {
203 f64::NEG_INFINITY
204 } else {
205 f64::NAN
206 }
207}
208
209#[inline]
211pub fn safe_sqrt(x: f64) -> f64 {
212 if x >= 0.0 {
213 x.sqrt()
214 } else {
215 f64::NAN
216 }
217}
218
219pub fn d1(spot: f64, strike: f64, time: f64, rate: f64, volatility: f64) -> f64 {
221 if time <= 0.0 || volatility <= 0.0 {
222 return f64::NAN;
223 }
224 let vol_sqrt_t = volatility * time.sqrt();
225 (safe_log(spot / strike) + (rate + 0.5 * volatility * volatility) * time) / vol_sqrt_t
226}
227
228pub fn d2(spot: f64, strike: f64, time: f64, rate: f64, volatility: f64) -> f64 {
230 if time <= 0.0 || volatility <= 0.0 {
231 return f64::NAN;
232 }
233 let vol_sqrt_t = volatility * time.sqrt();
234 d1(spot, strike, time, rate, volatility) - vol_sqrt_t
235}
236
237pub fn black_scholes_call(spot: f64, strike: f64, time: f64, rate: f64, volatility: f64) -> f64 {
239 if time <= 0.0 {
240 return (spot - strike).max(0.0);
241 }
242 if volatility <= 0.0 {
243 return ((spot - strike * (-rate * time).exp()).max(0.0)).max(0.0);
244 }
245
246 let d1_val = d1(spot, strike, time, rate, volatility);
247 let d2_val = d2(spot, strike, time, rate, volatility);
248
249 spot * norm_cdf(d1_val) - strike * (-rate * time).exp() * norm_cdf(d2_val)
250}
251
252pub fn black_scholes_put(spot: f64, strike: f64, time: f64, rate: f64, volatility: f64) -> f64 {
254 if time <= 0.0 {
255 return (strike - spot).max(0.0);
256 }
257 if volatility <= 0.0 {
258 return ((strike * (-rate * time).exp() - spot).max(0.0)).max(0.0);
259 }
260
261 let d1_val = d1(spot, strike, time, rate, volatility);
262 let d2_val = d2(spot, strike, time, rate, volatility);
263
264 strike * (-rate * time).exp() * norm_cdf(-d2_val) - spot * norm_cdf(-d1_val)
265}
266
267pub fn vega(spot: f64, strike: f64, time: f64, rate: f64, volatility: f64) -> f64 {
269 if time <= 0.0 || volatility <= 0.0 {
270 return 0.0;
271 }
272
273 let d1_val = d1(spot, strike, time, rate, volatility);
274 spot * norm_pdf(d1_val) * time.sqrt()
275}
276
277pub fn implied_volatility_newton(
279 market_price: f64,
280 spot: f64,
281 strike: f64,
282 time: f64,
283 rate: f64,
284 is_call: bool,
285) -> Result<f64> {
286 if market_price <= 0.0 {
287 return Err(IntegrateError::ValueError(
288 "Market price must be positive".to_string(),
289 ));
290 }
291
292 let intrinsic = if is_call {
294 (spot - strike * (-rate * time).exp()).max(0.0)
295 } else {
296 (strike * (-rate * time).exp() - spot).max(0.0)
297 };
298
299 if market_price <= intrinsic {
300 return Err(IntegrateError::ValueError(
301 "Market price below intrinsic value".to_string(),
302 ));
303 }
304
305 let time_value = market_price - intrinsic;
306 let mut vol = (2.0 * PI / time).sqrt() * (time_value / spot);
307 vol = vol.max(0.01).min(5.0); const MAX_ITERATIONS: usize = 100;
310 const TOLERANCE: f64 = 1e-8;
311
312 for _ in 0..MAX_ITERATIONS {
313 let price = if is_call {
314 black_scholes_call(spot, strike, time, rate, vol)
315 } else {
316 black_scholes_put(spot, strike, time, rate, vol)
317 };
318
319 let diff = price - market_price;
320
321 if diff.abs() < TOLERANCE {
322 return Ok(vol);
323 }
324
325 let vega_val = vega(spot, strike, time, rate, vol);
326
327 if vega_val < 1e-10 {
328 return Err(IntegrateError::ValueError(
329 "Vega too small for convergence".to_string(),
330 ));
331 }
332
333 let vol_new = vol - diff / vega_val;
334
335 let vol_new = vol_new.max(0.001).min(10.0);
337
338 if (vol_new - vol).abs() < TOLERANCE {
339 return Ok(vol_new);
340 }
341
342 vol = vol_new;
343 }
344
345 Err(IntegrateError::ValueError(
346 "Implied volatility did not converge".to_string(),
347 ))
348}
349
350pub fn bachelier_call(spot: f64, strike: f64, time: f64, rate: f64, volatility: f64) -> f64 {
352 if time <= 0.0 {
353 return (spot - strike).max(0.0);
354 }
355 if volatility <= 0.0 {
356 return ((spot - strike * (-rate * time).exp()).max(0.0)).max(0.0);
357 }
358
359 let forward = spot * (rate * time).exp();
360 let std_dev = volatility * time.sqrt();
361 let d = (forward - strike) / std_dev;
362
363 (forward - strike) * norm_cdf(d) + std_dev * norm_pdf(d)
364}
365
366pub fn bachelier_put(spot: f64, strike: f64, time: f64, rate: f64, volatility: f64) -> f64 {
368 if time <= 0.0 {
369 return (strike - spot).max(0.0);
370 }
371 if volatility <= 0.0 {
372 return ((strike * (-rate * time).exp() - spot).max(0.0)).max(0.0);
373 }
374
375 let forward = spot * (rate * time).exp();
376 let std_dev = volatility * time.sqrt();
377 let d = (forward - strike) / std_dev;
378
379 (strike - forward) * norm_cdf(-d) + std_dev * norm_pdf(d)
380}
381
382pub fn implied_volatility_brent(
384 market_price: f64,
385 spot: f64,
386 strike: f64,
387 time: f64,
388 rate: f64,
389 is_call: bool,
390) -> Result<f64> {
391 if market_price <= 0.0 {
392 return Err(IntegrateError::ValueError(
393 "Market price must be positive".to_string(),
394 ));
395 }
396
397 let mut vol_low = 0.001;
398 let mut vol_high = 5.0;
399
400 const MAX_ITERATIONS: usize = 100;
401 const TOLERANCE: f64 = 1e-8;
402
403 let price_low = if is_call {
405 black_scholes_call(spot, strike, time, rate, vol_low)
406 } else {
407 black_scholes_put(spot, strike, time, rate, vol_low)
408 };
409
410 let price_high = if is_call {
411 black_scholes_call(spot, strike, time, rate, vol_high)
412 } else {
413 black_scholes_put(spot, strike, time, rate, vol_high)
414 };
415
416 if market_price < price_low || market_price > price_high {
417 return Err(IntegrateError::ValueError(
418 "Market price outside pricing bounds".to_string(),
419 ));
420 }
421
422 for _ in 0..MAX_ITERATIONS {
424 let vol_mid = (vol_low + vol_high) / 2.0;
425
426 let price_mid = if is_call {
427 black_scholes_call(spot, strike, time, rate, vol_mid)
428 } else {
429 black_scholes_put(spot, strike, time, rate, vol_mid)
430 };
431
432 if (price_mid - market_price).abs() < TOLERANCE {
433 return Ok(vol_mid);
434 }
435
436 if price_mid < market_price {
437 vol_low = vol_mid;
438 } else {
439 vol_high = vol_mid;
440 }
441
442 if vol_high - vol_low < TOLERANCE {
443 return Ok(vol_mid);
444 }
445 }
446
447 Err(IntegrateError::ValueError(
448 "Implied volatility did not converge".to_string(),
449 ))
450}
451
452pub fn delta_call(spot: f64, strike: f64, time: f64, rate: f64, volatility: f64) -> f64 {
454 if time <= 0.0 {
455 return if spot > strike { 1.0 } else { 0.0 };
456 }
457 if volatility <= 0.0 {
458 return if spot > strike { 1.0 } else { 0.0 };
459 }
460
461 let d1_val = d1(spot, strike, time, rate, volatility);
462 norm_cdf(d1_val)
463}
464
465pub fn delta_put(spot: f64, strike: f64, time: f64, rate: f64, volatility: f64) -> f64 {
467 delta_call(spot, strike, time, rate, volatility) - 1.0
468}
469
470pub fn gamma(spot: f64, strike: f64, time: f64, rate: f64, volatility: f64) -> f64 {
472 if time <= 0.0 || volatility <= 0.0 || spot <= 0.0 {
473 return 0.0;
474 }
475
476 let d1_val = d1(spot, strike, time, rate, volatility);
477 norm_pdf(d1_val) / (spot * volatility * time.sqrt())
478}
479
480pub fn theta_call(spot: f64, strike: f64, time: f64, rate: f64, volatility: f64) -> f64 {
482 if time <= 0.0 {
483 return 0.0;
484 }
485 if volatility <= 0.0 {
486 return 0.0;
487 }
488
489 let d1_val = d1(spot, strike, time, rate, volatility);
490 let d2_val = d2(spot, strike, time, rate, volatility);
491
492 let term1 = -(spot * norm_pdf(d1_val) * volatility) / (2.0 * time.sqrt());
493 let term2 = rate * strike * (-rate * time).exp() * norm_cdf(d2_val);
494
495 term1 - term2
496}
497
498pub fn theta_put(spot: f64, strike: f64, time: f64, rate: f64, volatility: f64) -> f64 {
500 if time <= 0.0 {
501 return 0.0;
502 }
503 if volatility <= 0.0 {
504 return 0.0;
505 }
506
507 let d1_val = d1(spot, strike, time, rate, volatility);
508 let d2_val = d2(spot, strike, time, rate, volatility);
509
510 let term1 = -(spot * norm_pdf(d1_val) * volatility) / (2.0 * time.sqrt());
511 let term2 = rate * strike * (-rate * time).exp() * norm_cdf(-d2_val);
512
513 term1 + term2
514}
515
516pub fn rho_call(spot: f64, strike: f64, time: f64, rate: f64, volatility: f64) -> f64 {
518 if time <= 0.0 {
519 return 0.0;
520 }
521 if volatility <= 0.0 {
522 return if spot > strike {
523 strike * time * (-rate * time).exp()
524 } else {
525 0.0
526 };
527 }
528
529 let d2_val = d2(spot, strike, time, rate, volatility);
530 strike * time * (-rate * time).exp() * norm_cdf(d2_val)
531}
532
533pub fn rho_put(spot: f64, strike: f64, time: f64, rate: f64, volatility: f64) -> f64 {
535 if time <= 0.0 {
536 return 0.0;
537 }
538 if volatility <= 0.0 {
539 return if spot < strike {
540 -strike * time * (-rate * time).exp()
541 } else {
542 0.0
543 };
544 }
545
546 let d2_val = d2(spot, strike, time, rate, volatility);
547 -strike * time * (-rate * time).exp() * norm_cdf(-d2_val)
548}
549
550pub struct Greeks {
552 pub delta: f64,
553 pub gamma: f64,
554 pub vega: f64,
555 pub theta: f64,
556 pub rho: f64,
557}
558
559impl Greeks {
560 pub fn call(spot: f64, strike: f64, time: f64, rate: f64, volatility: f64) -> Self {
562 Self {
563 delta: delta_call(spot, strike, time, rate, volatility),
564 gamma: gamma(spot, strike, time, rate, volatility),
565 vega: vega(spot, strike, time, rate, volatility),
566 theta: theta_call(spot, strike, time, rate, volatility),
567 rho: rho_call(spot, strike, time, rate, volatility),
568 }
569 }
570
571 pub fn put(spot: f64, strike: f64, time: f64, rate: f64, volatility: f64) -> Self {
573 Self {
574 delta: delta_put(spot, strike, time, rate, volatility),
575 gamma: gamma(spot, strike, time, rate, volatility),
576 vega: vega(spot, strike, time, rate, volatility),
577 theta: theta_put(spot, strike, time, rate, volatility),
578 rho: rho_put(spot, strike, time, rate, volatility),
579 }
580 }
581}
582
583#[cfg(test)]
584mod tests {
585 use super::*;
586
587 #[test]
588 fn test_norm_cdf() {
589 assert!((norm_cdf(0.0) - 0.5).abs() < 1e-7);
590 assert!(norm_cdf(-10.0) < 1e-10);
591 assert!(norm_cdf(10.0) > 0.9999999);
592 assert!((norm_cdf(1.0) - 0.8413447).abs() < 1e-5);
593 }
594
595 #[test]
596 fn test_norm_pdf() {
597 let pdf_0 = norm_pdf(0.0);
598 assert!((pdf_0 - 0.3989423).abs() < 1e-6);
599
600 let pdf_1 = norm_pdf(1.0);
601 assert!((pdf_1 - 0.2419707).abs() < 1e-6);
602 }
603
604 #[test]
605 fn test_black_scholes_call() {
606 let price = black_scholes_call(100.0, 100.0, 1.0, 0.05, 0.2);
607 assert!(price > 9.0 && price < 12.0); }
609
610 #[test]
611 fn test_black_scholes_put() {
612 let price = black_scholes_put(100.0, 100.0, 1.0, 0.05, 0.2);
613 assert!(price > 5.0 && price < 8.0); }
615
616 #[test]
617 fn test_put_call_parity() {
618 let s = 100.0;
619 let k = 100.0;
620 let t = 1.0;
621 let r = 0.05;
622 let v = 0.2;
623
624 let call = black_scholes_call(s, k, t, r, v);
625 let put = black_scholes_put(s, k, t, r, v);
626
627 let lhs = call - put;
629 let rhs = s - k * (-r * t).exp();
630
631 assert!((lhs - rhs).abs() < 1e-10);
632 }
633
634 #[test]
635 fn test_vega() {
636 let vega_val = vega(100.0, 100.0, 1.0, 0.05, 0.2);
637 assert!(vega_val > 0.0);
638 assert!(vega_val < 50.0); }
640
641 #[test]
642 fn test_implied_volatility_newton() {
643 let spot = 100.0;
644 let strike = 100.0;
645 let time = 1.0;
646 let rate = 0.05;
647 let true_vol = 0.25;
648
649 let market_price = black_scholes_call(spot, strike, time, rate, true_vol);
650 let implied_vol =
651 implied_volatility_newton(market_price, spot, strike, time, rate, true).unwrap();
652
653 assert!((implied_vol - true_vol).abs() < 1e-6);
654 }
655
656 #[test]
657 fn test_implied_volatility_brent() {
658 let spot = 100.0;
659 let strike = 100.0;
660 let time = 1.0;
661 let rate = 0.05;
662 let true_vol = 0.25;
663
664 let market_price = black_scholes_call(spot, strike, time, rate, true_vol);
665 let implied_vol =
666 implied_volatility_brent(market_price, spot, strike, time, rate, true).unwrap();
667
668 assert!((implied_vol - true_vol).abs() < 1e-6);
669 }
670
671 #[test]
672 fn test_delta_call() {
673 let delta = delta_call(100.0, 100.0, 1.0, 0.05, 0.2);
674 assert!(delta > 0.50 && delta < 0.65);
676 }
677
678 #[test]
679 fn test_delta_put() {
680 let delta = delta_put(100.0, 100.0, 1.0, 0.05, 0.2);
681 assert!(delta > -0.50 && delta < -0.35);
683 }
684
685 #[test]
686 fn test_gamma() {
687 let gamma_val = gamma(100.0, 100.0, 1.0, 0.05, 0.2);
688 assert!(gamma_val > 0.0);
689 assert!(gamma_val < 0.1); }
691
692 #[test]
693 fn test_bachelier_call() {
694 let price = bachelier_call(100.0, 100.0, 1.0, 0.05, 20.0);
695 assert!(price > 0.0);
696 }
697
698 #[test]
699 fn test_bachelier_put() {
700 let price = bachelier_put(100.0, 100.0, 1.0, 0.05, 20.0);
701 assert!(price > 0.0);
702 }
703
704 #[test]
705 fn test_zero_time() {
706 assert_eq!(black_scholes_call(110.0, 100.0, 0.0, 0.05, 0.2), 10.0);
707 assert_eq!(black_scholes_put(90.0, 100.0, 0.0, 0.05, 0.2), 10.0);
708 }
709
710 #[test]
711 fn test_safe_functions() {
712 assert!(safe_log(-1.0).is_nan());
713 assert_eq!(safe_log(0.0), f64::NEG_INFINITY);
714 assert!(safe_sqrt(-1.0).is_nan());
715 assert_eq!(safe_sqrt(4.0), 2.0);
716 }
717
718 #[test]
719 fn test_theta_call() {
720 let theta = theta_call(100.0, 100.0, 1.0, 0.05, 0.2);
721 assert!(theta < 0.0);
723 assert!(theta.abs() < 20.0);
725 }
726
727 #[test]
728 fn test_theta_put() {
729 let theta = theta_put(100.0, 100.0, 1.0, 0.05, 0.2);
730 assert!(theta.abs() < 20.0);
733 }
734
735 #[test]
736 fn test_rho_call() {
737 let rho = rho_call(100.0, 100.0, 1.0, 0.05, 0.2);
738 assert!(rho > 0.0);
740 assert!(rho < 100.0);
742 }
743
744 #[test]
745 fn test_rho_put() {
746 let rho = rho_put(100.0, 100.0, 1.0, 0.05, 0.2);
747 assert!(rho < 0.0);
749 assert!(rho.abs() < 100.0);
751 }
752
753 #[test]
754 fn test_greeks_struct_call() {
755 let greeks = Greeks::call(100.0, 100.0, 1.0, 0.05, 0.2);
756
757 assert!(greeks.delta > 0.5 && greeks.delta < 0.65);
759 assert!(greeks.gamma > 0.0);
760 assert!(greeks.vega > 0.0);
761 assert!(greeks.theta < 0.0);
762 assert!(greeks.rho > 0.0);
763 }
764
765 #[test]
766 fn test_greeks_struct_put() {
767 let greeks = Greeks::put(100.0, 100.0, 1.0, 0.05, 0.2);
768
769 assert!(greeks.delta < 0.0 && greeks.delta > -0.5);
771 assert!(greeks.gamma > 0.0); assert!(greeks.vega > 0.0); assert!(greeks.rho < 0.0);
774 }
775
776 #[test]
777 fn test_theta_zero_time() {
778 assert_eq!(theta_call(100.0, 100.0, 0.0, 0.05, 0.2), 0.0);
779 assert_eq!(theta_put(100.0, 100.0, 0.0, 0.05, 0.2), 0.0);
780 }
781
782 #[test]
783 fn test_rho_zero_time() {
784 assert_eq!(rho_call(100.0, 100.0, 0.0, 0.05, 0.2), 0.0);
785 assert_eq!(rho_put(100.0, 100.0, 0.0, 0.05, 0.2), 0.0);
786 }
787
788 #[test]
789 fn test_sabr_parameters_creation() {
790 let sabr = SABRParameters::new(0.2, 0.5, -0.3, 0.4).unwrap();
791 assert_eq!(sabr.alpha, 0.2);
792 assert_eq!(sabr.beta, 0.5);
793 assert_eq!(sabr.rho, -0.3);
794 assert_eq!(sabr.nu, 0.4);
795 }
796
797 #[test]
798 fn test_sabr_parameters_validation() {
799 assert!(SABRParameters::new(-0.1, 0.5, 0.0, 0.4).is_err()); assert!(SABRParameters::new(0.2, 1.5, 0.0, 0.4).is_err()); assert!(SABRParameters::new(0.2, 0.5, 1.5, 0.4).is_err()); assert!(SABRParameters::new(0.2, 0.5, 0.0, -0.1).is_err()); }
804
805 #[test]
806 fn test_sabr_atm_volatility() {
807 let sabr = SABRParameters::new(0.2, 0.5, -0.3, 0.4).unwrap();
808 let forward = 100.0;
809 let strike = 100.0;
810 let time = 1.0;
811
812 let vol = sabr.implied_volatility(forward, strike, time).unwrap();
813 assert!(vol > 0.0);
814 assert!(vol < 1.0); }
816
817 #[test]
818 fn test_sabr_smile_shape() {
819 let sabr = SABRParameters::new(0.2, 0.5, -0.3, 0.4).unwrap();
820 let forward = 100.0;
821 let time = 1.0;
822
823 let vol_otm = sabr.implied_volatility(forward, 110.0, time).unwrap();
824 let vol_atm = sabr.implied_volatility(forward, 100.0, time).unwrap();
825 let vol_itm = sabr.implied_volatility(forward, 90.0, time).unwrap();
826
827 assert!(vol_otm > 0.0 && vol_otm < 1.0);
829 assert!(vol_atm > 0.0 && vol_atm < 1.0);
830 assert!(vol_itm > 0.0 && vol_itm < 1.0);
831
832 let smile_measure = (vol_itm - vol_atm).abs() + (vol_otm - vol_atm).abs();
834 assert!(smile_measure > 0.001); }
836
837 #[test]
838 fn test_sabr_beta_zero() {
839 let sabr = SABRParameters::new(20.0, 0.0, 0.0, 0.0).unwrap();
841 let vol = sabr.implied_volatility(100.0, 100.0, 1.0).unwrap();
842 assert!(vol > 0.0 && vol < 100.0);
844 }
845
846 #[test]
847 fn test_sabr_beta_one() {
848 let sabr = SABRParameters::new(0.2, 1.0, 0.0, 0.0).unwrap();
850 let vol = sabr.implied_volatility(100.0, 100.0, 1.0).unwrap();
851 assert!((vol - 0.2).abs() < 0.01); }
853
854 #[test]
855 fn test_sabr_symmetry() {
856 let sabr = SABRParameters::new(0.2, 0.5, 0.0, 0.2).unwrap();
857 let forward = 100.0;
858 let time = 1.0;
859
860 let vol_up = sabr.implied_volatility(forward, 105.0, time).unwrap();
862 let vol_down = sabr.implied_volatility(forward, 95.0, time).unwrap();
863
864 assert!((vol_up - vol_down).abs() / vol_up < 0.1);
866 }
867}