use chrono::NaiveDate;
use crate::calendar::DayCountConvention;
use crate::cashflows::CurveProvider;
use crate::traits::FloatExt;
#[derive(Debug, Clone)]
pub struct Deposit<T: FloatExt> {
pub notional: T,
pub rate: T,
pub value_date: NaiveDate,
pub maturity: NaiveDate,
pub day_count: DayCountConvention,
}
#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub struct DepositValuation<T: FloatExt> {
pub accrual_factor: T,
pub discount_factor: T,
pub par_rate: T,
pub npv: T,
}
impl<T: FloatExt> Deposit<T> {
pub fn new(
notional: T,
rate: T,
value_date: NaiveDate,
maturity: NaiveDate,
day_count: DayCountConvention,
) -> Self {
Self {
notional,
rate,
value_date,
maturity,
day_count,
}
}
pub fn valuation(
&self,
valuation_date: NaiveDate,
discount_day_count: DayCountConvention,
curves: &(impl CurveProvider<T> + ?Sized),
) -> DepositValuation<T> {
let alpha = self.day_count.year_fraction(self.value_date, self.maturity);
let t_value = discount_day_count.year_fraction(valuation_date, self.value_date);
let t_mat = discount_day_count.year_fraction(valuation_date, self.maturity);
let df_value = if self.value_date <= valuation_date {
T::one()
} else {
curves.discount_curve().discount_factor(t_value)
};
let df_mat = if self.maturity <= valuation_date {
T::zero()
} else {
curves.discount_curve().discount_factor(t_mat)
};
let par = if df_mat > T::zero() && alpha > T::zero() {
(df_value / df_mat - T::one()) / alpha
} else {
T::zero()
};
let npv = self.notional * df_mat * (T::one() + self.rate * alpha) - self.notional * df_value;
DepositValuation {
accrual_factor: alpha,
discount_factor: df_mat,
par_rate: par,
npv,
}
}
pub fn npv(
&self,
valuation_date: NaiveDate,
discount_day_count: DayCountConvention,
curves: &(impl CurveProvider<T> + ?Sized),
) -> T {
self
.valuation(valuation_date, discount_day_count, curves)
.npv
}
pub fn par_rate(
&self,
valuation_date: NaiveDate,
discount_day_count: DayCountConvention,
curves: &(impl CurveProvider<T> + ?Sized),
) -> T {
self
.valuation(valuation_date, discount_day_count, curves)
.par_rate
}
}
#[cfg(test)]
mod tests {
use ndarray::Array1;
use super::*;
use crate::curves::DiscountCurve;
use crate::curves::InterpolationMethod;
fn flat_curve(r: f64, tenor_years: f64) -> DiscountCurve<f64> {
let times = Array1::from(vec![0.25_f64, 0.5, 1.0, tenor_years]);
let rates = Array1::from(vec![r; 4]);
DiscountCurve::from_zero_rates(
×,
&rates,
InterpolationMethod::LogLinearOnDiscountFactors,
)
}
#[test]
fn deposit_at_par_rate_has_zero_npv() {
let curve = flat_curve(0.04, 2.0);
let val_date = NaiveDate::from_ymd_opt(2025, 1, 2).unwrap();
let deposit = Deposit::new(
1_000_000.0,
0.0,
val_date,
NaiveDate::from_ymd_opt(2025, 7, 2).unwrap(),
DayCountConvention::Actual360,
);
let par = deposit.par_rate(val_date, DayCountConvention::Actual365Fixed, &curve);
let at_par = Deposit::new(
1_000_000.0,
par,
deposit.value_date,
deposit.maturity,
deposit.day_count,
);
assert!(
at_par
.npv(val_date, DayCountConvention::Actual365Fixed, &curve)
.abs()
< 1e-6
);
}
}