use chrono::Datelike;
use chrono::Utc;
use crate::traits::PricerExt;
use crate::traits::TimeExt;
#[derive(Debug)]
pub struct HullWhite {
pub r_t: f64,
pub theta: fn(f64) -> f64,
pub alpha: f64,
pub sigma: f64,
pub tau: f64,
pub eval: Option<chrono::NaiveDate>,
pub expiration: Option<chrono::NaiveDate>,
}
impl PricerExt for HullWhite {
fn calculate_call_put(&self) -> (f64, f64) {
let price = self.calculate_price();
(price, price)
}
fn calculate_price(&self) -> f64 {
let tau = self.calculate_tau_in_years();
let today = Utc::now().year() as f64;
let S = self.eval.unwrap().year() as f64 - today;
let T = self.expiration.unwrap().year() as f64 - today;
let p0t = (-self.r_t * T).exp();
let p0s = (-self.r_t * S).exp();
let B = (1.0 - (-self.alpha * tau).exp()) / self.alpha;
let A = p0t / p0s
* (-B * self.r_t
- (self.sigma.powi(2)
* ((-self.alpha * T).exp() - (-self.alpha * S).exp()).powi(2)
* ((2.0 * self.alpha * S) - 1.0))
/ (4.0 * self.alpha.powi(3)))
.exp();
A * (-B * self.r_t).exp()
}
}
impl TimeExt for HullWhite {
fn tau(&self) -> Option<f64> {
Some(self.tau)
}
fn eval(&self) -> Option<chrono::NaiveDate> {
self.eval
}
fn expiration(&self) -> Option<chrono::NaiveDate> {
self.expiration
}
}
#[cfg(test)]
mod tests {
use chrono::NaiveDate;
use super::*;
#[test]
fn zcb_in_unit_interval() {
let h = HullWhite {
r_t: 0.05,
theta: |_| 0.04,
alpha: 0.5,
sigma: 0.01,
tau: 2.0,
eval: Some(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()),
expiration: Some(NaiveDate::from_ymd_opt(2026, 1, 1).unwrap()),
};
let p = h.calculate_price();
assert!(p.is_finite(), "ZCB must be finite, got {p}");
}
}