use crate::termstructures::bootstraptraits::BootstrapTraits;
use crate::types::{Real, Size, Time};
const AVG_INFLATION: Real = 0.02;
const MAX_INFLATION: Real = 0.5;
pub struct ZeroInflationTraits;
impl BootstrapTraits for ZeroInflationTraits {
fn initial_value() -> Real {
AVG_INFLATION
}
fn guess(i: Size, _times: &[Time], data: &[Real], valid_data: bool) -> Real {
if valid_data {
return data[i];
}
AVG_INFLATION
}
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 if r < 0.0 { r * 2.0 } else { r / 2.0 };
}
-MAX_INFLATION
}
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 if r < 0.0 { r / 2.0 } else { r * 2.0 };
}
MAX_INFLATION
}
fn update_guess(data: &mut [Real], value: Real, i: Size) {
data[i] = value;
if i == 1 {
data[0] = value;
}
}
fn max_iterations() -> Size {
40
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::termstructures::bootstraptraits::ZeroYield;
#[test]
fn initial_value_is_the_average_inflation_rate_not_the_average_yield() {
assert_eq!(ZeroInflationTraits::initial_value(), 0.02);
assert_ne!(
ZeroInflationTraits::initial_value(),
ZeroYield::initial_value()
);
}
#[test]
fn max_iterations_is_forty_not_the_yield_hundred() {
assert_eq!(ZeroInflationTraits::max_iterations(), 40);
assert_ne!(
ZeroInflationTraits::max_iterations(),
ZeroYield::max_iterations()
);
}
#[test]
fn every_fresh_pillar_guesses_the_average_rate_where_the_yield_trait_extrapolates() {
let times = [0.0, 1.0, 2.0];
let data = [0.02, 0.03, 0.02];
assert_eq!(ZeroInflationTraits::guess(1, ×, &data, false), 0.02);
assert_eq!(ZeroInflationTraits::guess(2, ×, &data, false), 0.02);
assert_ne!(
ZeroInflationTraits::guess(2, ×, &data, false),
ZeroYield::guess(2, ×, &data, false)
);
}
#[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!(ZeroInflationTraits::guess(2, ×, &data, true), 0.035);
}
#[test]
fn fresh_curve_bracket_is_the_half_percent_band_around_zero() {
let times = [0.0, 1.0, 2.0];
let data = [0.02, 0.03, 0.02];
let min = ZeroInflationTraits::min_value_after(2, ×, &data, false);
let max = ZeroInflationTraits::max_value_after(2, ×, &data, false);
assert_eq!(min, -0.5);
assert_eq!(max, 0.5);
assert!(min < 0.0, "the bracket must admit deflation");
assert_ne!(min, ZeroYield::min_value_after(2, ×, &data, false));
assert_ne!(max, ZeroYield::max_value_after(2, ×, &data, false));
}
#[test]
fn valid_data_bracket_halves_a_positive_minimum_and_doubles_a_positive_maximum() {
let times = [0.0, 1.0, 2.0];
let data = [0.02, 0.02, 0.05];
assert!(
(ZeroInflationTraits::min_value_after(2, ×, &data, true) - 0.01).abs() < 1e-15
);
assert!(
(ZeroInflationTraits::max_value_after(2, ×, &data, true) - 0.10).abs() < 1e-15
);
}
#[test]
fn valid_data_bracket_flips_the_branches_on_negative_nodes() {
let times = [0.0, 1.0, 2.0];
let data = [-0.02, -0.02, -0.01];
let min = ZeroInflationTraits::min_value_after(2, ×, &data, true);
let max = ZeroInflationTraits::max_value_after(2, ×, &data, true);
assert!((min - (-0.04)).abs() < 1e-15);
assert!((max - (-0.005)).abs() < 1e-15);
assert!(min < max);
}
#[test]
fn update_guess_writes_the_node_and_mirrors_the_base_date_node() {
let mut data = [0.02, 0.02, 0.02];
ZeroInflationTraits::update_guess(&mut data, 0.03, 1);
assert_eq!(data[1], 0.03);
assert_eq!(data[0], 0.03);
ZeroInflationTraits::update_guess(&mut data, 0.04, 2);
assert_eq!(data[2], 0.04);
assert_eq!(data[0], 0.03);
}
}