use crate::errors::QlResult;
use crate::termstructures::credit::defaulttermstructure::DefaultProbabilityTermStructure;
use crate::types::{Rate, Real, Time};
pub trait HazardRateStructure: DefaultProbabilityTermStructure {
fn hazard_rate_curve_impl(&self, t: Time) -> QlResult<Rate>;
fn default_density_from_hazard_rate(&self, t: Time) -> QlResult<Real> {
let hazard_rate = self.hazard_rate_curve_impl(t)?;
Ok(hazard_rate * self.survival_probability_impl(t)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fail;
use crate::patterns::observable::{AsObservable, Observable};
use crate::termstructures::{TermStructure, TermStructureBase};
use crate::time::date::{Date, Month};
use crate::time::daycounters::actual360::Actual360;
use crate::types::Probability;
const INTENSITY: Real = 0.04;
struct DerivedHazardCurve {
base: TermStructureBase,
failing: bool,
}
impl DerivedHazardCurve {
fn new(failing: bool) -> DerivedHazardCurve {
DerivedHazardCurve {
base: TermStructureBase::with_reference_date(
Date::new(15, Month::June, 2026),
None,
Some(Actual360::new()),
),
failing,
}
}
}
impl AsObservable for DerivedHazardCurve {
fn observable(&self) -> &Observable {
self.base.observable()
}
}
impl TermStructure for DerivedHazardCurve {
fn base(&self) -> &TermStructureBase {
&self.base
}
fn max_date(&self) -> Date {
Date::max_date()
}
}
impl HazardRateStructure for DerivedHazardCurve {
fn hazard_rate_curve_impl(&self, _t: Time) -> QlResult<Rate> {
if self.failing {
fail!("no hazard rate available");
}
Ok(INTENSITY)
}
}
impl DefaultProbabilityTermStructure for DerivedHazardCurve {
fn survival_probability_impl(&self, t: Time) -> QlResult<Probability> {
Ok((-INTENSITY * t).exp())
}
fn default_density_impl(&self, t: Time) -> QlResult<Real> {
self.default_density_from_hazard_rate(t)
}
}
struct QuotedHazardCurve {
inner: DerivedHazardCurve,
}
impl AsObservable for QuotedHazardCurve {
fn observable(&self) -> &Observable {
self.inner.observable()
}
}
impl TermStructure for QuotedHazardCurve {
fn base(&self) -> &TermStructureBase {
self.inner.base()
}
fn max_date(&self) -> Date {
Date::max_date()
}
}
impl HazardRateStructure for QuotedHazardCurve {
fn hazard_rate_curve_impl(&self, t: Time) -> QlResult<Rate> {
self.inner.hazard_rate_curve_impl(t)
}
}
impl DefaultProbabilityTermStructure for QuotedHazardCurve {
fn survival_probability_impl(&self, t: Time) -> QlResult<Probability> {
self.inner.survival_probability_impl(t)
}
fn default_density_impl(&self, t: Time) -> QlResult<Real> {
self.default_density_from_hazard_rate(t)
}
fn hazard_rate_impl(&self, t: Time) -> QlResult<Rate> {
self.hazard_rate_curve_impl(t)
}
}
fn survival(t: Time) -> Probability {
(-INTENSITY * t).exp()
}
#[test]
fn default_density_is_the_hazard_rate_times_the_survival_probability() {
let curve = DerivedHazardCurve::new(false);
for t in [0.0_f64, 0.25, 1.0, 2.5] {
let expected = INTENSITY * survival(t);
assert!((curve.default_density(t, false).unwrap() - expected).abs() < 1.0e-15);
assert!(
(curve.default_density_from_hazard_rate(t).unwrap() - expected).abs() < 1.0e-15
);
}
}
#[test]
fn the_derived_hazard_rate_closes_back_on_the_quoted_one() {
let curve = DerivedHazardCurve::new(false);
for t in [0.25_f64, 1.0, 2.5] {
assert!((curve.hazard_rate(t, false).unwrap() - INTENSITY).abs() < 1.0e-15);
}
}
#[test]
fn overriding_the_base_hazard_rate_agrees_with_the_derived_one() {
let quoted = QuotedHazardCurve {
inner: DerivedHazardCurve::new(false),
};
let derived = DerivedHazardCurve::new(false);
for t in [0.25_f64, 1.0, 2.5] {
assert_eq!(quoted.hazard_rate(t, false).unwrap(), INTENSITY);
assert!(
(quoted.hazard_rate(t, false).unwrap() - derived.hazard_rate(t, false).unwrap())
.abs()
< 1.0e-15
);
assert!(
(quoted.default_density(t, false).unwrap()
- derived.default_density(t, false).unwrap())
.abs()
< 1.0e-15
);
}
}
#[test]
fn the_survival_probability_is_left_to_the_curve() {
let curve = DerivedHazardCurve::new(false);
assert!((curve.survival_probability(2.5, false).unwrap() - survival(2.5)).abs() < 1.0e-15);
assert!(
(curve.default_probability(2.5, false).unwrap() - (1.0 - survival(2.5))).abs()
< 1.0e-15
);
}
#[test]
fn hazard_rate_errors_propagate_through_the_default_density() {
let curve = DerivedHazardCurve::new(true);
let err = curve.default_density(1.0, false).unwrap_err();
assert!(err.message().contains("no hazard rate available"));
let err = curve.hazard_rate(1.0, false).unwrap_err();
assert!(err.message().contains("no hazard rate available"));
assert!((curve.survival_probability(1.0, false).unwrap() - survival(1.0)).abs() < 1.0e-15);
}
}