use crate::{
math::integrate,
time::{today, DayCountConvention},
};
use num::Complex;
use time::Date;
#[allow(clippy::too_many_arguments)]
#[must_use]
pub fn heston(
S0: f64, V0: f64, K: f64, r: f64, q: f64, rho: f64, sigma: f64, kappa: f64, theta: f64, evaluation_date: Option<Date>,
expiration_date: Date,
) -> (f64, f64) {
let tau = DayCountConvention::default()
.day_count_factor(evaluation_date.unwrap_or(today()), expiration_date);
let lambda = 0.0;
let i: Complex<f64> = Complex::i();
let u = |j: u8| -> f64 {
match j {
1 => 0.5,
2 => -0.5,
_ => panic!("`j` should be: 1 or 2."),
}
};
let b = |j: u8| -> f64 {
match j {
1 => kappa + lambda - rho * sigma,
2 => kappa + lambda,
_ => panic!("`j` should be: 1 or 2."),
}
};
let d = |j: u8, phi: f64| -> Complex<f64> {
((rho * sigma * i * phi - b(j)).powi(2)
- sigma.powi(2) * (2.0 * u(j) * i * phi - phi.powi(2)))
.sqrt()
};
let g = |j: u8, phi: f64| -> Complex<f64> {
assert!(j == 1 || j == 2);
(b(j) - rho * sigma * i * phi + d(j, phi)) / (b(j) - rho * sigma * i * phi - d(j, phi))
};
let C = |j: u8, phi: f64| -> Complex<f64> {
assert!(j == 1 || j == 2);
(r - q) * i * phi * tau
+ (kappa * theta / sigma.powi(2))
* ((b(j) - rho * sigma * i * phi + d(j, phi)) * tau
- 2.0 * ((1.0 - g(j, phi) * (d(j, phi) * tau).exp()) / (1.0 - g(j, phi))).ln())
};
let D = |j: u8, phi: f64| -> Complex<f64> {
assert!(j == 1 || j == 2);
((b(j) - rho * sigma * i * phi + d(j, phi)) * (1.0 - (d(j, phi) * tau).exp()))
/ (sigma.powi(2) * (1.0 - g(j, phi) * (d(j, phi) * tau).exp()))
};
let f = |j: u8, phi: f64| -> Complex<f64> {
assert!(j == 1 || j == 2);
(C(j, phi) + D(j, phi) * V0 + i * phi * S0.ln()).exp()
};
let Re1 = |phi: f64| -> f64 {
let j = 1;
(f(j, phi) * (-i * phi * K.ln()).exp() / (i * phi)).re
};
let Re2 = |phi: f64| -> f64 {
let j = 2;
(f(j, phi) * (-i * phi * K.ln()).exp() / (i * phi)).re
};
let P1 = 0.5 + std::f64::consts::FRAC_1_PI * integrate(Re1, 0.00001, 50.0);
let P2 = 0.5 + std::f64::consts::FRAC_1_PI * integrate(Re2, 0.00001, 50.0);
let call = S0 * (-q * tau).exp() * P1 - K * (-r * tau).exp() * P2;
let put = call + K * (-r * tau).exp() - S0 * (-q * tau).exp();
(call, put)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{assert_approx_equal, RUSTQUANT_EPSILON};
use time::Duration;
#[test]
fn test_heston_options() {
let expiry_date = today() + Duration::days(183);
let heston1 = heston(
100.0,
0.05,
100.0,
0.03,
0.02,
-0.8,
0.5,
5.0,
0.05,
None,
expiry_date,
);
assert_approx_equal!(heston1.0, 6.2528189954900455, RUSTQUANT_EPSILON);
assert_approx_equal!(heston1.1, 5.759029580879499, RUSTQUANT_EPSILON);
let heston2 = heston(
100.0,
0.05,
100.0,
0.03,
0.0,
-0.8,
0.5,
5.0,
0.05,
None,
expiry_date,
);
assert_approx_equal!(heston2.0, 6.867834818545035, RUSTQUANT_EPSILON);
assert_approx_equal!(heston2.1, 5.379028778851293, RUSTQUANT_EPSILON);
}
}