use crate::errors::QlResult;
use crate::math::interpolations::Interpolation;
use crate::termstructures::bootstraptraits::BootstrapTraits;
use crate::termstructures::credit::interpolatedhazardratecurve::{
hazard_rate_from_nodes, survival_probability_from_nodes,
};
use crate::termstructures::credit::interpolatedsurvivalprobabilitycurve::{
density_from_nodes, survival_from_nodes,
};
use crate::types::{Real, Size, Time};
const AVG_HAZARD_RATE: Real = 0.01;
const MAX_HAZARD_RATE: Real = 1.0;
pub struct HazardRate;
impl BootstrapTraits for HazardRate {
fn initial_value() -> Real {
AVG_HAZARD_RATE
}
fn guess(i: Size, _times: &[Time], data: &[Real], valid_data: bool) -> Real {
if valid_data {
return data[i];
}
if i == 1 {
return AVG_HAZARD_RATE;
}
data[i - 1]
}
fn min_value_after(_i: Size, _times: &[Time], data: &[Real], valid_data: bool) -> Real {
if valid_data {
let r = data.iter().copied().fold(Real::INFINITY, Real::min);
return r / 2.0;
}
Real::EPSILON
}
fn max_value_after(_i: Size, _times: &[Time], data: &[Real], valid_data: bool) -> Real {
if valid_data {
let r = data.iter().copied().fold(Real::NEG_INFINITY, Real::max);
return r * 2.0;
}
MAX_HAZARD_RATE
}
fn update_guess(data: &mut [Real], value: Real, i: Size) {
data[i] = value;
if i == 1 {
data[0] = value;
}
}
fn max_iterations() -> Size {
30
}
}
pub trait CreditBootstrapTraits: BootstrapTraits {
fn survival<I: Interpolation>(interpolation: &I, t: Time) -> QlResult<Real>;
fn density<I: Interpolation>(interpolation: &I, t: Time) -> QlResult<Real>;
fn hazard<I: Interpolation>(interpolation: &I, t: Time) -> QlResult<Real> {
let survival = Self::survival(interpolation, t)?;
if survival == 0.0 {
return Ok(0.0);
}
Ok(Self::density(interpolation, t)? / survival)
}
}
impl CreditBootstrapTraits for HazardRate {
fn survival<I: Interpolation>(interpolation: &I, t: Time) -> QlResult<Real> {
survival_probability_from_nodes(interpolation, t)
}
fn density<I: Interpolation>(interpolation: &I, t: Time) -> QlResult<Real> {
Ok(Self::hazard(interpolation, t)? * Self::survival(interpolation, t)?)
}
fn hazard<I: Interpolation>(interpolation: &I, t: Time) -> QlResult<Real> {
hazard_rate_from_nodes(interpolation, t)
}
}
pub struct SurvivalProbability;
impl BootstrapTraits for SurvivalProbability {
fn initial_value() -> Real {
1.0
}
fn guess(i: Size, times: &[Time], data: &[Real], valid_data: bool) -> Real {
if valid_data {
return data[i];
}
if i == 1 {
return 1.0 / (1.0 + AVG_HAZARD_RATE * 0.25);
}
let hazard = (data[i - 2] / data[i - 1]).ln() / (times[i - 1] - times[i - 2]);
data[i - 1] * (-hazard * (times[i] - times[i - 1])).exp()
}
fn min_value_after(i: Size, times: &[Time], data: &[Real], valid_data: bool) -> Real {
if valid_data {
return data[data.len() - 1] / 2.0;
}
data[i - 1] * (-MAX_HAZARD_RATE * (times[i] - times[i - 1])).exp()
}
fn max_value_after(i: Size, _times: &[Time], data: &[Real], _valid_data: bool) -> Real {
data[i - 1]
}
fn update_guess(data: &mut [Real], value: Real, i: Size) {
data[i] = value;
}
fn max_iterations() -> Size {
50
}
}
impl CreditBootstrapTraits for SurvivalProbability {
fn survival<I: Interpolation>(interpolation: &I, t: Time) -> QlResult<Real> {
survival_from_nodes(interpolation, t)
}
fn density<I: Interpolation>(interpolation: &I, t: Time) -> QlResult<Real> {
density_from_nodes(interpolation, t)
}
}
pub struct DefaultDensity;
impl BootstrapTraits for DefaultDensity {
fn initial_value() -> Real {
HazardRate::initial_value()
}
fn guess(i: Size, times: &[Time], data: &[Real], valid_data: bool) -> Real {
HazardRate::guess(i, times, data, valid_data)
}
fn min_value_after(i: Size, times: &[Time], data: &[Real], valid_data: bool) -> Real {
HazardRate::min_value_after(i, times, data, valid_data)
}
fn max_value_after(i: Size, times: &[Time], data: &[Real], valid_data: bool) -> Real {
HazardRate::max_value_after(i, times, data, valid_data)
}
fn update_guess(data: &mut [Real], value: Real, i: Size) {
HazardRate::update_guess(data, value, i);
}
fn max_iterations() -> Size {
30
}
}
impl CreditBootstrapTraits for DefaultDensity {
fn survival<I: Interpolation>(interpolation: &I, t: Time) -> QlResult<Real> {
super::interpolateddefaultdensitycurve::survival_from_density_nodes(interpolation, t)
}
fn density<I: Interpolation>(interpolation: &I, t: Time) -> QlResult<Real> {
super::interpolateddefaultdensitycurve::default_density_from_nodes(interpolation, t)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::termstructures::bootstraptraits::ZeroYield;
#[test]
fn survival_traits_preserve_the_anchor_and_monotone_brackets() {
let times = [0.0, 1.0, 3.0];
let mut data = [1.0, 0.9, 0.8];
assert_eq!(SurvivalProbability::initial_value(), 1.0);
assert_eq!(SurvivalProbability::max_iterations(), 50);
assert_eq!(
SurvivalProbability::guess(1, ×, &data, false),
1.0 / 1.0025
);
assert_eq!(SurvivalProbability::guess(2, ×, &data, true), 0.8);
assert!((SurvivalProbability::guess(2, ×, &data, false) - 0.729).abs() < 1e-14);
assert!(
(SurvivalProbability::min_value_after(2, ×, &data, false)
- 0.9 * (-2.0_f64).exp())
.abs()
< 1e-14
);
assert_eq!(
SurvivalProbability::min_value_after(2, ×, &data, true),
0.4
);
for valid in [false, true] {
assert_eq!(
SurvivalProbability::max_value_after(2, ×, &data, valid),
0.9
);
}
SurvivalProbability::update_guess(&mut data, 0.95, 1);
assert_eq!(data, [1.0, 0.95, 0.8]);
}
#[test]
fn initial_value_is_the_average_hazard_rate_not_the_average_yield() {
assert_eq!(HazardRate::initial_value(), 0.01);
assert_ne!(HazardRate::initial_value(), ZeroYield::initial_value());
}
#[test]
fn max_iterations_is_thirty_not_the_yield_hundred() {
assert_eq!(HazardRate::max_iterations(), 30);
assert_ne!(HazardRate::max_iterations(), ZeroYield::max_iterations());
}
#[test]
fn first_pillar_guess_is_the_average_hazard_rate() {
let times = [0.0, 1.0];
let data = [0.01, 0.01];
assert_eq!(HazardRate::guess(1, ×, &data, false), 0.01);
}
#[test]
fn later_pillar_guess_extends_the_previous_node_flat() {
let times = [0.0, 1.0, 2.0];
let data = [0.02, 0.02, 0.01];
assert_eq!(HazardRate::guess(2, ×, &data, false), 0.02);
}
#[test]
fn valid_data_guess_reuses_the_stored_node() {
let times = [0.0, 1.0, 2.0];
let data = [0.02, 0.02, 0.035];
assert_eq!(HazardRate::guess(2, ×, &data, true), 0.035);
}
#[test]
fn fresh_curve_bracket_is_strictly_positive_unlike_the_yield_bracket() {
let times = [0.0, 1.0, 2.0];
let data = [0.01, 0.02, 0.01];
let min = HazardRate::min_value_after(2, ×, &data, false);
let max = HazardRate::max_value_after(2, ×, &data, false);
assert_eq!(min, Real::EPSILON);
assert!(min > 0.0);
assert_eq!(max, 1.0);
assert!(min < max);
assert_ne!(min, ZeroYield::min_value_after(2, ×, &data, false));
}
#[test]
fn valid_data_bracket_halves_the_minimum_and_doubles_the_maximum() {
let times = [0.0, 1.0, 2.0];
let data = [0.02, 0.02, 0.05];
assert!((HazardRate::min_value_after(2, ×, &data, true) - 0.01).abs() < 1e-15);
assert!((HazardRate::max_value_after(2, ×, &data, true) - 0.10).abs() < 1e-15);
}
#[test]
fn valid_data_bracket_has_no_sign_branch() {
let times = [0.0, 1.0, 2.0];
let data = [-0.02, -0.02, -0.01];
let min = HazardRate::min_value_after(2, ×, &data, true);
let max = HazardRate::max_value_after(2, ×, &data, true);
assert!((min - (-0.01)).abs() < 1e-15);
assert!((max - (-0.02)).abs() < 1e-15);
assert_ne!(min, ZeroYield::min_value_after(2, ×, &data, true));
assert_ne!(max, ZeroYield::max_value_after(2, ×, &data, true));
}
#[test]
fn update_guess_writes_the_node_and_mirrors_the_first_pillar() {
let mut data = [0.01, 0.01, 0.01];
HazardRate::update_guess(&mut data, 0.03, 1);
assert_eq!(data[1], 0.03);
assert_eq!(data[0], 0.03);
HazardRate::update_guess(&mut data, 0.04, 2);
assert_eq!(data[2], 0.04);
assert_eq!(data[0], 0.03);
}
}