use crate::cashflow::{CashFlow, cash_flow_has_occurred};
use crate::cashflows::Coupon;
use crate::errors::QlResult;
use crate::event::Event;
use crate::patterns::observable::{AsObservable, Observable};
use crate::settings::Settings;
use crate::time::date::Date;
use crate::types::Real;
use crate::utilities::null::Null;
use crate::{fail, require};
pub struct SimpleCashFlow {
amount: Real,
date: Date,
observable: Observable,
}
impl SimpleCashFlow {
pub fn new(amount: Real, date: Date) -> QlResult<Self> {
require!(date != Date::null(), "null date SimpleCashFlow");
if amount.is_null() {
fail!("null amount SimpleCashFlow");
}
Ok(SimpleCashFlow {
amount,
date,
observable: Observable::new(),
})
}
}
impl AsObservable for SimpleCashFlow {
fn observable(&self) -> &Observable {
&self.observable
}
}
impl Event for SimpleCashFlow {
fn date(&self) -> Date {
self.date
}
fn has_occurred(
&self,
settings: &Settings<Date>,
ref_date: Option<Date>,
include_ref_date: Option<bool>,
) -> QlResult<bool> {
cash_flow_has_occurred(self.date, settings, ref_date, include_ref_date)
}
}
impl CashFlow for SimpleCashFlow {
fn amount(&self) -> QlResult<Real> {
Ok(self.amount)
}
fn ex_coupon_date(&self) -> Option<Date> {
None
}
fn as_coupon(&self) -> Option<&dyn Coupon> {
None
}
}
macro_rules! simple_cash_flow_alias {
($(#[$meta:meta])* $name:ident) => {
$(#[$meta])*
pub struct $name(SimpleCashFlow);
impl $name {
pub fn new(amount: Real, date: Date) -> QlResult<Self> {
Ok($name(SimpleCashFlow::new(amount, date)?))
}
}
impl AsObservable for $name {
fn observable(&self) -> &Observable {
self.0.observable()
}
}
impl Event for $name {
fn date(&self) -> Date {
self.0.date()
}
fn has_occurred(
&self,
settings: &Settings<Date>,
ref_date: Option<Date>,
include_ref_date: Option<bool>,
) -> QlResult<bool> {
self.0.has_occurred(settings, ref_date, include_ref_date)
}
}
impl CashFlow for $name {
fn amount(&self) -> QlResult<Real> {
self.0.amount()
}
fn ex_coupon_date(&self) -> Option<Date> {
self.0.ex_coupon_date()
}
fn as_coupon(&self) -> Option<&dyn Coupon> {
self.0.as_coupon()
}
}
};
}
simple_cash_flow_alias! {
Redemption
}
simple_cash_flow_alias! {
AmortizingPayment
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cashflow::Leg;
use crate::shared::{Shared, shared};
use crate::time::date::Month;
fn today() -> Date {
Date::new(7, Month::July, 2026)
}
fn leg(settings: &Settings<Date>) -> Leg {
settings.set_evaluation_date(today());
(0..3)
.map(|i| shared(SimpleCashFlow::new(1.0, today() + i).unwrap()) as Shared<dyn CashFlow>)
.collect()
}
fn included(leg: &Leg, settings: &Settings<Date>, n: usize, days: i32) -> bool {
!leg[n]
.has_occurred(settings, Some(today() + days), None)
.unwrap()
}
#[test]
fn a_simple_cash_flow_pays_its_amount_on_its_date() {
let flow = SimpleCashFlow::new(105.0, today()).unwrap();
assert_eq!(flow.date(), today());
assert_eq!(flow.amount().unwrap(), 105.0);
assert_eq!(flow.ex_coupon_date(), None);
}
#[test]
fn a_simple_cash_flow_rejects_quantlib_null_inputs() {
assert_eq!(
SimpleCashFlow::new(105.0, Date::null())
.err()
.unwrap()
.message(),
"null date SimpleCashFlow"
);
assert_eq!(
SimpleCashFlow::new(Real::null(), today())
.err()
.unwrap()
.message(),
"null amount SimpleCashFlow"
);
}
#[test]
fn reference_date_payments_are_excluded() {
for todays_flows in [None, Some(false)] {
let settings = Settings::new();
let leg = leg(&settings);
settings.set_include_reference_date_events(false);
settings.set_include_todays_cash_flows(todays_flows);
assert!(!included(&leg, &settings, 0, 0));
assert!(!included(&leg, &settings, 0, 1));
assert!(included(&leg, &settings, 1, 0));
assert!(!included(&leg, &settings, 1, 1));
assert!(!included(&leg, &settings, 1, 2));
assert!(included(&leg, &settings, 2, 1));
assert!(!included(&leg, &settings, 2, 2));
assert!(!included(&leg, &settings, 2, 3));
}
}
#[test]
fn reference_date_payments_are_included() {
for todays_flows in [None, Some(true)] {
let settings = Settings::new();
let leg = leg(&settings);
settings.set_include_reference_date_events(true);
settings.set_include_todays_cash_flows(todays_flows);
assert!(included(&leg, &settings, 0, 0));
assert!(!included(&leg, &settings, 0, 1));
assert!(included(&leg, &settings, 1, 0));
assert!(included(&leg, &settings, 1, 1));
assert!(!included(&leg, &settings, 1, 2));
assert!(included(&leg, &settings, 2, 1));
assert!(included(&leg, &settings, 2, 2));
assert!(!included(&leg, &settings, 2, 3));
}
}
#[test]
fn todays_cash_flows_override_the_reference_date_rule() {
let settings = Settings::new();
let leg = leg(&settings);
settings.set_include_reference_date_events(true);
settings.set_include_todays_cash_flows(Some(false));
assert!(!included(&leg, &settings, 0, 0));
assert!(!included(&leg, &settings, 0, 1));
assert!(included(&leg, &settings, 1, 0));
assert!(included(&leg, &settings, 1, 1));
assert!(!included(&leg, &settings, 1, 2));
assert!(included(&leg, &settings, 2, 1));
assert!(included(&leg, &settings, 2, 2));
assert!(!included(&leg, &settings, 2, 3));
}
#[test]
fn a_redemption_and_an_amortizing_payment_behave_as_simple_cash_flows() {
let settings = Settings::new();
settings.set_evaluation_date(today());
settings.set_include_reference_date_events(true);
settings.set_include_todays_cash_flows(Some(false));
let redemption = Redemption::new(100.0, today()).unwrap();
let amortizing = AmortizingPayment::new(2.5, today()).unwrap();
assert_eq!(redemption.date(), today());
assert_eq!(redemption.amount().unwrap(), 100.0);
assert!(redemption.has_occurred(&settings, None, None).unwrap());
assert_eq!(amortizing.date(), today());
assert_eq!(amortizing.amount().unwrap(), 2.5);
assert!(amortizing.has_occurred(&settings, None, None).unwrap());
}
}