use std::rc::Rc;
use crate::errors::QlResult;
use crate::math::array::Array;
use crate::math::optimization::constraint::{Constraint, 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 VolatilityConstraint {
k: Real,
theta: Real,
}
impl VolatilityConstraint {
pub fn new(k: Real, theta: Real) -> VolatilityConstraint {
VolatilityConstraint { k, theta }
}
}
impl Constraint for VolatilityConstraint {
fn test(&self, params: &Array) -> bool {
let sigma = params[0];
sigma > 0.0 && sigma * sigma < 2.0 * self.k * self.theta
}
}
pub struct CoxIngersollRoss {
model: CalibratedModel,
}
impl CoxIngersollRoss {
pub fn new(
r0: Rate,
theta: Real,
k: Real,
sigma: Real,
with_feller_constraint: bool,
) -> QlResult<CoxIngersollRoss> {
let mut model = CalibratedModel::new(4);
model.arguments_mut()[0] = ConstantParameter::new(theta, Rc::new(PositiveConstraint))?;
model.arguments_mut()[1] = ConstantParameter::new(k, Rc::new(PositiveConstraint))?;
let sigma_constraint: Rc<dyn Constraint> = if with_feller_constraint {
Rc::new(VolatilityConstraint::new(k, theta))
} else {
Rc::new(PositiveConstraint)
};
model.arguments_mut()[2] = ConstantParameter::new(sigma, sigma_constraint)?;
model.arguments_mut()[3] = ConstantParameter::new(r0, Rc::new(PositiveConstraint))?;
Ok(CoxIngersollRoss { model })
}
pub fn theta(&self) -> Real {
self.model.arguments()[0].value(0.0)
}
pub fn k(&self) -> Real {
self.model.arguments()[1].value(0.0)
}
pub fn sigma(&self) -> Real {
self.model.arguments()[2].value(0.0)
}
pub fn x0(&self) -> Real {
self.model.arguments()[3].value(0.0)
}
}
impl Default for CoxIngersollRoss {
fn default() -> CoxIngersollRoss {
CoxIngersollRoss::new(0.05, 0.1, 0.1, 0.1, true)
.expect("QuantLib's default CIR parameters satisfy the Feller constraint")
}
}
impl CalibratedModelHolder for CoxIngersollRoss {
fn calibrated_model(&self) -> &CalibratedModel {
&self.model
}
fn calibrated_model_mut(&mut self) -> &mut CalibratedModel {
&mut self.model
}
}
impl OneFactorAffineModel for CoxIngersollRoss {
fn a(&self, t: Time, maturity: Time) -> Real {
let k = self.k();
let theta = self.theta();
let sigma = self.sigma();
let sigma2 = sigma * sigma;
let h = (k * k + 2.0 * sigma2).sqrt();
let numerator = 2.0 * h * (0.5 * (k + h) * (maturity - t)).exp();
let denominator = 2.0 * h + (k + h) * (((maturity - t) * h).exp() - 1.0);
let value = (numerator / denominator).ln() * 2.0 * k * theta / sigma2;
value.exp()
}
fn b(&self, t: Time, maturity: Time) -> Real {
let k = self.k();
let sigma = self.sigma();
let h = (k * k + 2.0 * sigma * sigma).sqrt();
let temp = ((maturity - t) * h).exp() - 1.0;
let numerator = 2.0 * temp;
let denominator = 2.0 * h + (k + h) * temp;
numerator / denominator
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_matches_the_cpp_value_defaulted_constructor() {
let model = CoxIngersollRoss::default();
assert_eq!(model.theta(), 0.1);
assert_eq!(model.k(), 0.1);
assert_eq!(model.sigma(), 0.1);
assert_eq!(model.x0(), 0.05);
}
#[test]
fn discount_bond_matches_the_closed_form_at_default_params() {
let model = CoxIngersollRoss::default();
let calculated = model.discount_bond(0.0, 1.0, 0.05);
assert!((calculated - 0.949_006_558_472_911).abs() < 1e-14);
}
#[test]
fn discount_bond_matches_the_closed_form_with_non_trivial_a_and_b() {
let model = CoxIngersollRoss::new(0.05, 0.1, 0.5, 0.08, true).unwrap();
let calculated = model.discount_bond(0.0, 2.0, 0.05);
assert!((calculated - 0.872_385_885_028_455_4).abs() < 1e-14);
}
#[test]
fn discount_bond_is_one_over_a_zero_length_period() {
let model = CoxIngersollRoss::default();
assert!((model.discount_bond(0.0, 0.0, 0.05) - 1.0).abs() < 1e-15);
}
#[test]
fn volatility_constraint_enforces_the_feller_condition() {
let c = VolatilityConstraint::new(1.0, 0.1);
assert!(c.test(&Array::from([0.1])));
assert!(!c.test(&Array::from([0.5])));
assert!(!c.test(&Array::from([-0.1])));
}
#[test]
fn new_rejects_a_volatility_violating_the_feller_condition() {
assert!(CoxIngersollRoss::new(0.1, 0.1, 1.0, 0.5, true).is_err());
}
#[test]
fn without_the_feller_constraint_a_large_volatility_is_accepted() {
assert!(CoxIngersollRoss::new(0.1, 0.1, 1.0, 0.5, false).is_ok());
}
#[test]
fn new_rejects_non_positive_theta_k_or_r0() {
assert!(CoxIngersollRoss::new(0.1, -0.1, 1.0, 0.1, true).is_err());
assert!(CoxIngersollRoss::new(0.1, 0.1, -1.0, 0.1, true).is_err());
assert!(CoxIngersollRoss::new(-0.1, 0.1, 1.0, 0.1, true).is_err());
}
}