use crate::errors::QlResult;
use crate::patterns::observable::Observable;
use crate::require;
use crate::settings::Settings;
use crate::time::date::Date;
use crate::types::Rate;
pub trait Index {
fn name(&self) -> String;
fn fixing_calendar(&self) -> crate::time::calendar::Calendar;
fn is_valid_fixing_date(&self, fixing_date: Date) -> bool;
fn fixing(&self, fixing_date: Date, forecast_todays_fixing: bool) -> QlResult<Rate>;
fn settings(&self) -> &Settings<Date>;
fn observable(&self) -> &Observable;
fn allows_native_fixings(&self) -> bool {
true
}
fn has_historical_fixing(&self, fixing_date: Date) -> bool {
self.settings()
.has_historical_fixing(&self.name(), fixing_date)
}
fn past_fixing(&self, fixing_date: Date) -> QlResult<Option<Rate>> {
require!(
self.is_valid_fixing_date(fixing_date),
"{fixing_date:?} is not a valid fixing date"
);
Ok(self.settings().fixing(&self.name(), fixing_date))
}
fn add_fixing(&self, fixing_date: Date, value: Rate) -> QlResult<()> {
self.add_fixings([(fixing_date, value)])
}
fn add_fixings(&self, fixings: impl IntoIterator<Item = (Date, Rate)>) -> QlResult<()> {
require!(
self.allows_native_fixings(),
"native fixings not allowed for index {}",
self.name()
);
let fixings: Vec<(Date, Rate)> = fixings.into_iter().collect();
for &(date, _) in &fixings {
require!(date != Date::null(), "cannot add fixing on a null date");
require!(
self.is_valid_fixing_date(date),
"{date:?} is not a valid fixing date"
);
}
self.settings().add_fixings(&self.name(), fixings)
}
fn clear_fixings(&self) {
self.settings().clear_fixing(&self.name());
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::shared::{Shared, SharedMut, shared, shared_mut};
use crate::time::calendar::Calendar;
use crate::time::calendars::weekendsonly::WeekendsOnly;
use crate::time::date::{Date, Month};
fn a_date() -> Date {
Date::new(15, Month::June, 2026)
}
struct TestIndex {
name: String,
calendar: Calendar,
settings: Shared<Settings<Date>>,
observable: Observable,
}
impl Index for TestIndex {
fn name(&self) -> String {
self.name.clone()
}
fn fixing_calendar(&self) -> Calendar {
self.calendar.clone()
}
fn is_valid_fixing_date(&self, fixing_date: Date) -> bool {
self.calendar.is_business_day(fixing_date)
}
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 an_index() -> TestIndex {
TestIndex {
name: "TestIndex".into(),
calendar: WeekendsOnly::new(),
settings: shared(Settings::<Date>::new()),
observable: Observable::new(),
}
}
#[test]
fn add_and_read_a_past_fixing() {
let index = an_index();
assert!(!index.has_historical_fixing(a_date()));
index.add_fixing(a_date(), 0.02).unwrap();
assert!(index.has_historical_fixing(a_date()));
assert_eq!(index.past_fixing(a_date()).unwrap(), Some(0.02));
}
#[test]
fn add_fixing_rejects_an_invalid_date() {
let index = an_index();
let saturday = Date::new(13, Month::June, 2026);
assert!(!index.is_valid_fixing_date(saturday));
assert!(index.add_fixing(saturday, 0.02).is_err());
}
#[test]
fn clear_fixings_removes_the_history() {
let index = an_index();
index.add_fixing(a_date(), 0.02).unwrap();
index.clear_fixings();
assert!(!index.has_historical_fixing(a_date()));
}
struct Flag {
up: bool,
}
impl crate::patterns::observable::Observer for Flag {
fn update(&mut self) {
self.up = true;
}
}
#[test]
fn observers_of_the_index_hear_its_own_notifications() {
let index = an_index();
let flag = shared_mut(Flag { up: false });
index.observable().register_observer(
&(flag.clone() as SharedMut<dyn crate::patterns::observable::Observer>),
);
index.observable().notify_observers();
assert!(flag.borrow().up);
}
}