use std::rc::Rc;
use crate::errors::QlResult;
use crate::math::optimization::constraint::{NoConstraint, PositiveConstraint};
use crate::models::model::{CalibratedModel, CalibratedModelHolder};
use crate::models::parameter::ConstantParameter;
use crate::models::shortrate::onefactormodel::OneFactorAffineModel;
use crate::types::{Rate, Real, Time};
pub struct Vasicek {
model: CalibratedModel,
r0: Rate,
}
impl Vasicek {
pub fn new(r0: Rate, a: Real, b: Real, sigma: Real, lambda: Real) -> QlResult<Vasicek> {
let mut model = CalibratedModel::new(4);
model.arguments_mut()[0] = ConstantParameter::new(a, Rc::new(PositiveConstraint))?;
model.arguments_mut()[1] = ConstantParameter::new(b, Rc::new(NoConstraint))?;
model.arguments_mut()[2] = ConstantParameter::new(sigma, Rc::new(PositiveConstraint))?;
model.arguments_mut()[3] = ConstantParameter::new(lambda, Rc::new(NoConstraint))?;
Ok(Vasicek { model, r0 })
}
pub fn a(&self) -> Real {
self.model.arguments()[0].value(0.0)
}
pub fn b(&self) -> Real {
self.model.arguments()[1].value(0.0)
}
pub fn sigma(&self) -> Real {
self.model.arguments()[2].value(0.0)
}
pub fn lambda(&self) -> Real {
self.model.arguments()[3].value(0.0)
}
pub fn r0(&self) -> Rate {
self.r0
}
pub(crate) fn set_r0(&mut self, r0: Rate) {
self.r0 = r0;
}
}
impl CalibratedModelHolder for Vasicek {
fn calibrated_model(&self) -> &CalibratedModel {
&self.model
}
fn calibrated_model_mut(&mut self) -> &mut CalibratedModel {
&mut self.model
}
}
impl Default for Vasicek {
fn default() -> Vasicek {
Vasicek::new(0.05, 0.1, 0.05, 0.01, 0.0)
.expect("QuantLib's default Vasicek parameters satisfy the positivity constraints")
}
}
impl OneFactorAffineModel for Vasicek {
fn a(&self, t: Time, maturity: Time) -> Real {
let a = self.a();
let sigma = self.sigma();
if a < Real::EPSILON.sqrt() {
let sigma2 = sigma * sigma;
let tau = maturity - t;
(-0.5 * self.lambda() * sigma * tau * tau + sigma2 * tau * tau * tau / 6.0).exp()
} else {
let sigma2 = sigma * sigma;
let bt = OneFactorAffineModel::b(self, t, maturity);
((self.b() + self.lambda() * sigma / a - 0.5 * sigma2 / (a * a))
* (bt - (maturity - t))
- 0.25 * sigma2 * bt * bt / a)
.exp()
}
}
fn b(&self, t: Time, maturity: Time) -> Real {
let a = self.a();
if a < Real::EPSILON.sqrt() {
maturity - t
} else {
(1.0 - (-a * (maturity - t)).exp()) / a
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn small_mean_reversion_discount_factor_matches_closed_form() {
let r0 = 0.05;
let sigma = 0.01;
let maturity = 1.0;
let model = Vasicek::new(r0, 1e-12, 0.05, sigma, 0.0).unwrap();
let expected =
(-r0 * maturity + sigma * sigma * maturity * maturity * maturity / 6.0).exp();
let calculated = model.discount_bond(0.0, maturity, r0);
assert!((expected - calculated).abs() < 1e-12);
}
#[test]
fn default_matches_the_cpp_value_defaulted_constructor() {
let model = Vasicek::default();
assert_eq!(model.a(), 0.1);
assert_eq!(model.b(), 0.05);
assert_eq!(model.sigma(), 0.01);
assert_eq!(model.lambda(), 0.0);
assert_eq!(model.r0(), 0.05);
}
#[test]
fn large_mean_reversion_discount_factor_matches_closed_form() {
let model = Vasicek::default();
let calculated = model.discount_bond(0.0, 1.0, 0.05);
assert!((calculated - 0.951_244_142_965_253_6).abs() < 1e-15);
}
#[test]
fn new_rejects_non_positive_mean_reversion() {
assert!(Vasicek::new(0.05, -0.1, 0.05, 0.01, 0.0).is_err());
}
#[test]
fn new_rejects_non_positive_volatility() {
assert!(Vasicek::new(0.05, 0.1, 0.05, -0.01, 0.0).is_err());
}
}