use crate::errors::BootstrapError;
use crate::interpolation::{Interpolator, LogLinear};
use crate::types::{Date, Daycount};
pub mod basis_swap;
pub mod bond;
pub mod deposit;
pub mod fra;
pub mod future;
pub mod ois_swap;
pub mod schedule;
pub mod swap_fixed_float;
pub use basis_swap::{BasisLeg, BasisSwap};
pub use bond::Bond;
pub use deposit::Deposit;
pub use fra::Fra;
pub use future::Future;
pub use ois_swap::OisSwap;
pub use schedule::SwapSchedule;
pub use swap_fixed_float::SwapFixedFloat;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Instrument {
Bond(Bond),
Deposit(Deposit),
Fra(Fra),
Future(Future),
SwapFixedFloat(SwapFixedFloat),
OisSwap(OisSwap),
BasisSwap(BasisSwap),
}
impl Instrument {
#[must_use]
pub fn pillar(&self) -> Date {
match self {
Self::Bond(b) => InstrumentLike::pillar(b),
Self::Deposit(d) => InstrumentLike::pillar(d),
Self::Fra(f) => InstrumentLike::pillar(f),
Self::Future(f) => InstrumentLike::pillar(f),
Self::SwapFixedFloat(s) => InstrumentLike::pillar(s),
Self::OisSwap(s) => InstrumentLike::pillar(s),
Self::BasisSwap(b) => InstrumentLike::pillar(b),
}
}
}
#[allow(dead_code)]
pub(crate) trait InstrumentLike {
fn pillar(&self) -> Date;
fn residual(
&self,
reference_date: Date,
curve: &CurveSnapshot<'_>,
) -> Result<f64, BootstrapError>;
}
#[allow(dead_code)]
pub(crate) struct CurveSnapshot<'a> {
pub(crate) reference_date: Date,
pub(crate) daycount: Daycount,
pub(crate) times: &'a [f64],
pub(crate) discounts: &'a [f64],
}
impl CurveSnapshot<'_> {
pub(crate) fn discount_at(&self, t: f64) -> Option<f64> {
if self.times.is_empty() || self.times.len() != self.discounts.len() {
return None;
}
if self.times.len() == 1 {
return Some(self.discounts[0]);
}
let knots: Vec<(f64, f64)> = self
.times
.iter()
.zip(self.discounts.iter())
.map(|(&t, &d)| (t, d))
.collect();
let interp = LogLinear::build(&knots).ok()?;
Some(interp.eval(t))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn d(y: i32, m: u32, day: u32) -> Date {
Date::from_ymd(y, m, day).unwrap()
}
#[test]
fn instrument_pillar_dispatches_to_variant() {
let dep = Deposit::new(d(2024, 1, 2), d(2024, 4, 2), 0.05, Daycount::Act360).unwrap();
let inst = Instrument::Deposit(dep);
assert_eq!(inst.pillar(), d(2024, 4, 2));
}
#[test]
fn instrument_clone_eq() {
let dep = Deposit::new(d(2024, 1, 2), d(2024, 4, 2), 0.05, Daycount::Act360).unwrap();
let a = Instrument::Deposit(dep);
let b = a.clone();
assert_eq!(a, b);
}
#[test]
fn instrument_debug_includes_variant() {
let dep = Deposit::new(d(2024, 1, 2), d(2024, 4, 2), 0.05, Daycount::Act360).unwrap();
let s = format!("{:?}", Instrument::Deposit(dep));
assert!(s.contains("Deposit"));
}
#[test]
fn curve_snapshot_discount_at_returns_none_on_empty() {
let snap = CurveSnapshot {
reference_date: d(2024, 1, 2),
daycount: Daycount::Act360,
times: &[],
discounts: &[],
};
assert!(snap.discount_at(1.0).is_none());
}
#[test]
fn curve_snapshot_discount_at_returns_single_when_one_knot() {
let snap = CurveSnapshot {
reference_date: d(2024, 1, 2),
daycount: Daycount::Act360,
times: &[0.0],
discounts: &[1.0],
};
let v = snap.discount_at(5.0).unwrap();
assert!((v - 1.0).abs() < 1e-15);
}
#[test]
fn curve_snapshot_discount_at_log_linear_through_knots() {
let times = [0.0_f64, 1.0, 2.0];
let disc = [1.0_f64, 0.95, 0.90];
let snap = CurveSnapshot {
reference_date: d(2024, 1, 2),
daycount: Daycount::Act360,
times: ×,
discounts: &disc,
};
for (&t, &d) in times.iter().zip(disc.iter()) {
assert!((snap.discount_at(t).unwrap() - d).abs() < 1e-15);
}
let mid = snap.discount_at(0.5).unwrap();
assert!((mid - 0.95_f64.sqrt()).abs() < 1e-15);
}
#[test]
fn curve_snapshot_discount_at_returns_none_on_mismatched_lengths() {
let snap = CurveSnapshot {
reference_date: d(2024, 1, 2),
daycount: Daycount::Act360,
times: &[0.0_f64, 1.0],
discounts: &[1.0_f64],
};
assert!(snap.discount_at(0.5).is_none());
}
#[test]
fn curve_snapshot_discount_at_flat_extrapolation() {
let times = [0.0_f64, 1.0];
let disc = [1.0_f64, 0.95];
let snap = CurveSnapshot {
reference_date: d(2024, 1, 2),
daycount: Daycount::Act360,
times: ×,
discounts: &disc,
};
assert!((snap.discount_at(-1.0).unwrap() - 1.0).abs() < 1e-15);
assert!((snap.discount_at(2.0).unwrap() - 0.95).abs() < 1e-15);
}
}