use crate::cashflow::{CashFlow, cash_flow_has_occurred};
use crate::cashflows::Coupon;
use crate::errors::QlResult;
use crate::event::Event;
use crate::indexes::index::Index;
use crate::patterns::observable::{AsObservable, Observable, Observer, ResetThenNotify};
use crate::settings::Settings;
use crate::shared::{Shared, SharedMut};
use crate::time::date::Date;
use crate::types::Real;
pub struct IndexedCashFlow<I> {
notional: Real,
index: Shared<I>,
base_date: Date,
fixing_date: Date,
payment_date: Date,
growth_only: bool,
observable: Shared<Observable>,
forwarder: SharedMut<ResetThenNotify>,
}
impl<I: Index> IndexedCashFlow<I> {
pub fn new(
notional: Real,
index: Shared<I>,
base_date: Date,
fixing_date: Date,
payment_date: Date,
growth_only: bool,
) -> Self {
let (observable, forwarder) = ResetThenNotify::forwarder();
let flow = IndexedCashFlow {
notional,
index,
base_date,
fixing_date,
payment_date,
growth_only,
observable,
forwarder,
};
flow.register_with(flow.index.observable());
flow
}
fn register_with(&self, observable: &Observable) {
observable.register_observer(&(self.forwarder.clone() as SharedMut<dyn Observer>));
}
pub fn notional(&self) -> Real {
self.notional
}
pub fn index(&self) -> &Shared<I> {
&self.index
}
pub fn base_date(&self) -> Date {
self.base_date
}
pub fn fixing_date(&self) -> Date {
self.fixing_date
}
pub fn growth_only(&self) -> bool {
self.growth_only
}
pub fn base_fixing(&self) -> QlResult<Real> {
self.index.fixing(self.base_date, false)
}
pub fn index_fixing(&self) -> QlResult<Real> {
self.index.fixing(self.fixing_date, false)
}
pub(super) fn amount_from(&self, i0: Real, i1: Real) -> Real {
if self.growth_only {
self.notional * (i1 / i0 - 1.0)
} else {
self.notional * (i1 / i0)
}
}
}
impl<I> AsObservable for IndexedCashFlow<I> {
fn observable(&self) -> &Observable {
&self.observable
}
}
impl<I: Index> Event for IndexedCashFlow<I> {
fn date(&self) -> Date {
self.payment_date
}
fn has_occurred(
&self,
settings: &Settings<Date>,
ref_date: Option<Date>,
include_ref_date: Option<bool>,
) -> QlResult<bool> {
cash_flow_has_occurred(self.payment_date, settings, ref_date, include_ref_date)
}
}
impl<I: Index> CashFlow for IndexedCashFlow<I> {
fn amount(&self) -> QlResult<Real> {
Ok(self.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::patterns::observable::Observer;
use crate::shared::{shared, shared_mut};
use crate::time::calendar::Calendar;
use crate::time::calendars::nullcalendar::NullCalendar;
use crate::time::date::Month::{December, January};
use crate::types::Rate;
struct TestIndex {
settings: Shared<Settings<Date>>,
observable: Observable,
}
impl Index for TestIndex {
fn name(&self) -> String {
"TestIndex".into()
}
fn fixing_calendar(&self) -> Calendar {
NullCalendar::new()
}
fn is_valid_fixing_date(&self, _fixing_date: Date) -> bool {
true
}
fn fixing(&self, fixing_date: Date, _forecast_todays_fixing: bool) -> QlResult<Rate> {
match self.past_fixing(fixing_date)? {
Some(rate) => Ok(rate),
None => crate::fail!("no fixing for {fixing_date}"),
}
}
fn settings(&self) -> &Settings<Date> {
&self.settings
}
fn observable(&self) -> &Observable {
&self.observable
}
}
fn base_date() -> Date {
Date::new(1, January, 2021)
}
fn fixing_date() -> Date {
Date::new(1, January, 2022)
}
fn payment_date() -> Date {
Date::new(15, January, 2022)
}
fn an_index() -> Shared<TestIndex> {
let index = shared(TestIndex {
settings: shared(Settings::<Date>::new()),
observable: Observable::new(),
});
index.add_fixing(base_date(), 100.0).unwrap();
index.add_fixing(fixing_date(), 110.0).unwrap();
index
}
fn a_flow(growth_only: bool) -> IndexedCashFlow<TestIndex> {
IndexedCashFlow::new(
1000.0,
an_index(),
base_date(),
fixing_date(),
payment_date(),
growth_only,
)
}
#[test]
fn an_indexed_cash_flow_pays_the_index_ratio() {
let flow = a_flow(false);
assert_eq!(flow.notional(), 1000.0);
assert!(!flow.growth_only());
assert_eq!(flow.base_date(), base_date());
assert_eq!(flow.fixing_date(), fixing_date());
assert_eq!(flow.date(), payment_date());
assert_eq!(flow.ex_coupon_date(), None);
assert!(flow.as_coupon().is_none());
assert!((flow.base_fixing().unwrap() - 100.0).abs() < 1e-12);
assert!((flow.index_fixing().unwrap() - 110.0).abs() < 1e-12);
assert!((flow.amount().unwrap() - 1100.0).abs() < 1e-10);
}
#[test]
fn a_growth_only_indexed_cash_flow_pays_the_ratio_less_one() {
let flow = a_flow(true);
assert!(flow.growth_only());
assert!((flow.amount().unwrap() - 100.0).abs() < 1e-10);
}
#[test]
fn an_indexed_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.index()).notify_observers();
assert!(flag.borrow().up);
}
#[test]
fn a_missing_fixing_surfaces_as_an_error() {
let flow = IndexedCashFlow::new(
1000.0,
an_index(),
base_date(),
Date::new(1, December, 2021),
payment_date(),
true,
);
assert!(flow.amount().is_err());
}
}