use super::indexedcashflow::IndexedCashFlow;
use crate::cashflow::CashFlow;
use crate::cashflows::Coupon;
use crate::errors::QlResult;
use crate::event::Event;
use crate::indexes::inflationindex::{Cpi, CpiInterpolationType, ZeroInflationIndex};
use crate::patterns::observable::{AsObservable, Observable};
use crate::settings::Settings;
use crate::shared::Shared;
use crate::time::date::Date;
use crate::time::period::Period;
use crate::types::Real;
pub struct ZeroInflationCashFlow {
base: IndexedCashFlow<ZeroInflationIndex>,
interpolation: CpiInterpolationType,
start_date: Date,
end_date: Date,
observation_lag: Period,
}
impl ZeroInflationCashFlow {
#[allow(clippy::too_many_arguments)]
pub fn new(
notional: Real,
index: Shared<ZeroInflationIndex>,
observation_interpolation: CpiInterpolationType,
start_date: Date,
end_date: Date,
observation_lag: Period,
payment_date: Date,
growth_only: bool,
) -> Self {
ZeroInflationCashFlow {
base: IndexedCashFlow::new(
notional,
index,
start_date - observation_lag,
end_date - observation_lag,
payment_date,
growth_only,
),
interpolation: observation_interpolation,
start_date,
end_date,
observation_lag,
}
}
pub fn zero_inflation_index(&self) -> &Shared<ZeroInflationIndex> {
self.base.index()
}
pub fn observation_interpolation(&self) -> CpiInterpolationType {
self.interpolation
}
pub fn start_date(&self) -> Date {
self.start_date
}
pub fn end_date(&self) -> Date {
self.end_date
}
pub fn observation_lag(&self) -> Period {
self.observation_lag
}
pub fn notional(&self) -> Real {
self.base.notional()
}
pub fn growth_only(&self) -> bool {
self.base.growth_only()
}
pub fn base_date(&self) -> Date {
self.base.base_date()
}
pub fn fixing_date(&self) -> Date {
self.base.fixing_date()
}
pub fn base_fixing(&self) -> QlResult<Real> {
Cpi::lagged_fixing(
self.base.index(),
self.start_date,
self.observation_lag,
self.interpolation,
)
}
pub fn index_fixing(&self) -> QlResult<Real> {
Cpi::lagged_fixing(
self.base.index(),
self.end_date,
self.observation_lag,
self.interpolation,
)
}
}
impl AsObservable for ZeroInflationCashFlow {
fn observable(&self) -> &Observable {
self.base.observable()
}
}
impl Event for ZeroInflationCashFlow {
fn date(&self) -> Date {
self.base.date()
}
fn has_occurred(
&self,
settings: &Settings<Date>,
ref_date: Option<Date>,
include_ref_date: Option<bool>,
) -> QlResult<bool> {
self.base.has_occurred(settings, ref_date, include_ref_date)
}
}
impl CashFlow for ZeroInflationCashFlow {
fn amount(&self) -> QlResult<Real> {
Ok(self
.base
.amount_from(self.base_fixing()?, self.index_fixing()?))
}
fn ex_coupon_date(&self) -> Option<Date> {
None
}
fn as_coupon(&self) -> Option<&dyn Coupon> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::indexes::Index;
use crate::indexes::inflation::UkRpi;
use crate::patterns::observable::Observer;
use crate::shared::{SharedMut, shared, shared_mut};
use crate::time::date::Month::{December, February, January, March, May, November};
use crate::time::timeunit::TimeUnit;
const NOTIONAL: Real = 1_000_000.0;
fn lag() -> Period {
Period::new(3, TimeUnit::Months)
}
fn start_date() -> Date {
Date::new(10, February, 2021)
}
fn end_date() -> Date {
Date::new(12, May, 2021)
}
fn payment_date() -> Date {
Date::new(26, May, 2021)
}
fn a_ukrpi_with_2021_fixings() -> Shared<ZeroInflationIndex> {
let settings = shared(Settings::<Date>::new());
settings.set_evaluation_date(Date::new(10, February, 2022));
let index = shared(UkRpi::new(settings));
for (date, value) in [
(Date::new(1, November, 2020), 293.5),
(Date::new(1, December, 2020), 295.4),
(Date::new(1, January, 2021), 294.6),
(Date::new(1, February, 2021), 296.0),
(Date::new(1, March, 2021), 296.9),
] {
index.add_fixing(date, value).expect("a published figure");
}
index
}
fn a_flow_with(
interpolation: CpiInterpolationType,
growth_only: bool,
) -> ZeroInflationCashFlow {
ZeroInflationCashFlow::new(
NOTIONAL,
a_ukrpi_with_2021_fixings(),
interpolation,
start_date(),
end_date(),
lag(),
payment_date(),
growth_only,
)
}
fn a_flow(growth_only: bool) -> ZeroInflationCashFlow {
a_flow_with(CpiInterpolationType::Flat, growth_only)
}
#[test]
fn a_zero_inflation_cash_flow_pays_the_lagged_inflation_growth() {
let flow = a_flow(true);
assert!((flow.base_fixing().unwrap() - 293.5).abs() < 1e-8);
assert!((flow.index_fixing().unwrap() - 296.0).abs() < 1e-8);
assert!((flow.amount().unwrap() - 8517.887563884053).abs() < 1e-8);
}
#[test]
fn a_linear_flow_pays_the_interpolated_growth_not_the_raw_one() {
let flow = a_flow_with(CpiInterpolationType::Linear, true);
let i0 = 293.5 * (19.0 / 28.0) + 295.4 * (9.0 / 28.0);
let i1 = 296.0 * (20.0 / 31.0) + 296.9 * (11.0 / 31.0);
assert!((flow.base_fixing().unwrap() - i0).abs() < 1e-8);
assert!((flow.index_fixing().unwrap() - i1).abs() < 1e-8);
assert!((flow.amount().unwrap() - 7509.554891120818).abs() < 1e-8);
}
#[test]
fn a_zero_inflation_cash_flow_can_pay_the_full_ratio() {
let flow = a_flow(false);
assert!(!flow.growth_only());
assert!((flow.amount().unwrap() - 1_008_517.887563884).abs() < 1e-8);
}
#[test]
fn the_observation_dates_are_lagged_but_not_snapped() {
let flow = a_flow(true);
assert_eq!(flow.start_date(), start_date());
assert_eq!(flow.end_date(), end_date());
assert_eq!(flow.observation_lag(), lag());
assert_eq!(flow.notional(), NOTIONAL);
assert_eq!(flow.observation_interpolation(), CpiInterpolationType::Flat);
assert_eq!(flow.base_date(), Date::new(10, November, 2020));
assert_eq!(flow.fixing_date(), Date::new(12, February, 2021));
assert_eq!(flow.date(), payment_date());
assert_eq!(flow.ex_coupon_date(), None);
assert!(flow.as_coupon().is_none());
}
#[test]
fn a_zero_inflation_cash_flow_forwards_its_index_notifications() {
#[derive(Default)]
struct Flag {
up: bool,
}
impl Observer for Flag {
fn update(&mut self) {
self.up = true;
}
}
let flow = a_flow(true);
let flag = shared_mut(Flag::default());
flow.observable()
.register_observer(&(flag.clone() as SharedMut<dyn Observer>));
Index::observable(&**flow.zero_inflation_index()).notify_observers();
assert!(flag.borrow().up);
}
}