use super::zero_coupon_bond::ZeroCouponBond;
use crate::data::{Curve, DiscountCurve};
use crate::instruments::fx::currency::Currency;
use crate::instruments::Instrument;
use crate::time::{Calendar, DateRollingConvention, Frequency, Schedule};
use std::collections::BTreeMap;
use time::{Date, Duration};
#[allow(clippy::module_name_repetitions)]
pub struct CouponBond<C: Calendar> {
pub face_value: f64,
pub schedule: Schedule,
pub calendar: C,
pub evaluation_date: Date,
pub expiration_date: Date,
pub currency: Option<Currency>,
pub coupon_rate: f64,
pub coupon_frequency: Frequency,
pub settlement_convention: DateRollingConvention,
pub discount_curve: DiscountCurve<Date, C>,
pub coupons: BTreeMap<Date, f64>,
}
pub struct CouponBond2 {
pub coupons: BTreeMap<Date, ZeroCouponBond>,
}
impl<C> CouponBond<C>
where
C: Calendar,
{
pub fn construct_coupons(&mut self) {
let mut coupons: BTreeMap<Date, f64> = BTreeMap::new();
let years = (self.expiration_date - self.evaluation_date).whole_days() / 365;
let n_coupons = years * self.coupon_frequency as i64;
let mut coupon_dates: Vec<Date> = Vec::with_capacity(n_coupons as usize);
for i in 1..=n_coupons {
let coupon_date =
self.evaluation_date + Duration::days(365 * i) / self.coupon_frequency as i32;
coupon_dates.push(coupon_date);
}
let mut coupon_amounts: Vec<f64> = Vec::with_capacity(n_coupons as usize);
for _ in 1..n_coupons {
let coupon_amount =
self.face_value * self.coupon_rate / self.coupon_frequency as isize as f64;
coupon_amounts.push(coupon_amount);
}
for (date, amount) in coupon_dates.iter().zip(coupon_amounts.iter()) {
coupons.insert(*date, *amount);
}
coupons.insert(
self.expiration_date,
self.face_value * (1.0 + self.coupon_rate / self.coupon_frequency as isize as f64),
);
self.coupons = coupons;
}
}
impl<C> Instrument for CouponBond<C>
where
C: Calendar,
{
fn price(&self) -> f64 {
self.coupons
.values()
.zip(self.discount_curve.curve.nodes.values().into_iter())
.map(|(coupon, df)| coupon * df)
.sum::<f64>()
}
fn error(&self) -> Option<f64> {
None
}
fn valuation_date(&self) -> Date {
self.evaluation_date
}
fn instrument_type(&self) -> &'static str {
"Coupon Bond"
}
}
impl CouponBond2 {
#[must_use]
pub fn validate_dates(&self) -> bool {
let mut evaluation_date: Option<Date> = None;
for bond in self.coupons.values() {
if evaluation_date.is_none() {
evaluation_date = Some(bond.evaluation_date);
} else if evaluation_date != Some(bond.evaluation_date) {
return false;
}
}
true
}
}
#[cfg(test)]
mod tests_bond {
use super::*;
use crate::{data::Curve, iso::USD, time::today};
#[allow(clippy::similar_names)]
fn create_test_discount_curve(t0: Date) -> YieldCurve {
let rate_vec = vec![0.0544, 0.0556, 0.0546, 0.0514, 0.0481, 0.0481, 0.0494];
let date_vec = vec![
t0 + Duration::days(90),
t0 + Duration::days(180),
t0 + Duration::days(365),
t0 + Duration::days(2 * 365),
t0 + Duration::days(5 * 365),
t0 + Duration::days(10 * 365),
t0 + Duration::days(30 * 365),
];
DiscountCurve::from_dates_and_rates(&date_vec, &rate_vec)
}
#[test]
fn test_coupon_construction() {
let today = today();
let mut bond = CouponBond {
evaluation_date: today,
expiration_date: today + Duration::days(365 * 2),
currency: Some(USD),
coupon_rate: 0.15,
coupon_frequency: Frequency::SemiAnnually,
settlement_convention: DateRollingConvention::Actual,
discount_curve: create_test_discount_curve(today),
face_value: 1000.0,
coupons: BTreeMap::new(),
};
bond.construct_coupons();
println!("Price: {}", bond.price());
}
}