use crate::instruments::Instrument;
use crate::math::integrate;
use crate::time::{today, DayCountConvention};
use time::Date;
pub struct HullWhite {
a: f64,
theta_t: fn(f64) -> f64,
sigma: f64,
r_t: f64,
pub evaluation_date: Option<Date>,
pub expiration_date: Date,
}
impl HullWhite {
fn B(&self) -> f64 {
assert!(self.a > 0.0);
(1.0 / self.a) * (1.0 - (-self.a).exp())
}
fn A(&self) -> f64 {
assert!(self.a > 0.0);
let today = today();
let t = (self.evaluation_date.unwrap_or(today).year() - today.year()) as f64;
let T = (self.expiration_date.year() - today.year()) as f64;
let first = -1.0 * integrate(|u| (self.theta_t)(u) * self.B(), t, T);
let second = ((self.sigma).powi(2) / (2.0 * (self.a).powi(2))) * (self.B() - self.tau());
let third = ((self.sigma).powi(2) / (4.0 * self.a)) * (self.B()).powi(2);
(first - second - third).exp()
}
fn tau(&self) -> f64 {
DayCountConvention::default().day_count_factor(
self.evaluation_date.unwrap_or(today()),
self.expiration_date,
)
}
}
impl Instrument for HullWhite {
fn price(&self) -> f64 {
assert!(self.a > 0.0);
assert!(self.expiration_date >= self.evaluation_date.unwrap_or(today()));
self.A() * (-1.0 * self.B() * self.r_t).exp()
}
fn error(&self) -> Option<f64> {
None
}
fn valuation_date(&self) -> time::Date {
self.evaluation_date.unwrap_or(today())
}
fn instrument_type(&self) -> &'static str {
"Zero Coupon Bond"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hw_zero_coupon_bond() {
let hw_bond = HullWhite {
a: 2.0,
theta_t: |_x| 0.5,
sigma: 0.3,
r_t: 0.05,
evaluation_date: None,
expiration_date: today() + time::Duration::days(365 * 10),
};
let _price = hw_bond.price();
}
}