use crate::termstructures::bootstraptraits::BootstrapTraits;
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
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::termstructures::bootstraptraits::ZeroYield;
#[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);
}
}