use std::fmt::Display;
use crate::traits::FloatExt;
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Compounding {
#[default]
Continuous,
Simple,
Periodic(u32),
}
impl Display for Compounding {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Continuous => write!(f, "Continuous"),
Self::Simple => write!(f, "Simple"),
Self::Periodic(n) => write!(f, "Periodic({n})"),
}
}
}
impl Compounding {
pub fn discount_factor<T: FloatExt>(&self, rate: T, tau: T) -> T {
match self {
Self::Continuous => (-rate * tau).exp(),
Self::Simple => T::one() / (T::one() + rate * tau),
Self::Periodic(n) => {
let n_t = T::from_f64_fast(*n as f64);
(T::one() + rate / n_t).powf(-n_t * tau)
}
}
}
pub fn zero_rate<T: FloatExt>(&self, df: T, tau: T) -> T {
if tau <= T::zero() {
return T::zero();
}
match self {
Self::Continuous => -df.ln() / tau,
Self::Simple => (T::one() / df - T::one()) / tau,
Self::Periodic(n) => {
let n_t = T::from_f64_fast(*n as f64);
n_t * (df.powf(-T::one() / (n_t * tau)) - T::one())
}
}
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InterpolationMethod {
#[default]
LinearOnZeroRates,
LogLinearOnDiscountFactors,
CubicSplineOnZeroRates,
MonotoneConvex,
}
impl Display for InterpolationMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::LinearOnZeroRates => write!(f, "Linear on zero rates"),
Self::LogLinearOnDiscountFactors => write!(f, "Log-linear on discount factors"),
Self::CubicSplineOnZeroRates => write!(f, "Cubic spline on zero rates"),
Self::MonotoneConvex => write!(f, "Monotone convex"),
}
}
}
#[derive(Debug, Clone)]
pub enum Instrument<T: FloatExt> {
Deposit { maturity: T, rate: T },
Fra { start: T, end: T, rate: T },
Future {
start: T,
end: T,
price: T,
sigma: T,
},
Swap {
maturity: T,
rate: T,
frequency: u32,
},
}
impl<T: FloatExt> Instrument<T> {
pub fn maturity(&self) -> T {
match self {
Self::Deposit { maturity, .. } => *maturity,
Self::Fra { end, .. } | Self::Future { end, .. } => *end,
Self::Swap { maturity, .. } => *maturity,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct CurvePoint<T: FloatExt> {
pub time: T,
pub discount_factor: T,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn continuous_compounding_round_trip() {
let cc = Compounding::Continuous;
let r: f64 = 0.05;
let tau: f64 = 2.0;
let df = cc.discount_factor(r, tau);
assert!((df - (-r * tau).exp()).abs() < 1e-12);
assert!((cc.zero_rate(df, tau) - r).abs() < 1e-12);
}
#[test]
fn simple_compounding_round_trip() {
let sc = Compounding::Simple;
let r: f64 = 0.05;
let tau: f64 = 0.5;
let df = sc.discount_factor(r, tau);
assert!((df - 1.0 / (1.0 + r * tau)).abs() < 1e-12);
assert!((sc.zero_rate(df, tau) - r).abs() < 1e-12);
}
#[test]
fn periodic_compounding_round_trip() {
let pc = Compounding::Periodic(2);
let r: f64 = 0.04;
let tau: f64 = 1.0;
let df = pc.discount_factor(r, tau);
assert!((pc.zero_rate(df, tau) - r).abs() < 1e-10);
}
#[test]
fn instrument_maturity() {
let dep: Instrument<f64> = Instrument::Deposit {
maturity: 0.5,
rate: 0.03,
};
assert_eq!(dep.maturity(), 0.5);
let fra: Instrument<f64> = Instrument::Fra {
start: 0.5,
end: 1.0,
rate: 0.04,
};
assert_eq!(fra.maturity(), 1.0);
}
}