use std::collections::BTreeMap;
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::Weak;
use chrono::NaiveDate;
use super::observable::Observable;
use super::observable::ObservableBase;
use super::observable::Observer;
use crate::calendar::Calendar;
use crate::calendar::DayCountConvention;
use crate::calendar::HolidayCalendar;
use crate::cashflows::IborIndex;
use crate::cashflows::OvernightIndex;
use crate::cashflows::RateTenor;
use crate::fx::Currency;
use crate::fx::currency;
use crate::traits::FloatExt;
struct FixingInner<T: FloatExt> {
fixings: RwLock<BTreeMap<NaiveDate, T>>,
observable: ObservableBase,
}
pub struct FixingHistory<T: FloatExt> {
inner: Arc<FixingInner<T>>,
}
impl<T: FloatExt> Clone for FixingHistory<T> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
impl<T: FloatExt> Default for FixingHistory<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: FloatExt> std::fmt::Debug for FixingHistory<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let n = self
.inner
.fixings
.read()
.map(|m| m.len())
.unwrap_or_default();
f.debug_struct("FixingHistory")
.field("entries", &n)
.finish()
}
}
impl<T: FloatExt> FixingHistory<T> {
pub fn new() -> Self {
Self {
inner: Arc::new(FixingInner {
fixings: RwLock::new(BTreeMap::new()),
observable: ObservableBase::new(),
}),
}
}
pub fn add_fixing(&self, date: NaiveDate, value: T) -> bool {
let changed = {
let mut map = self.inner.fixings.write().expect("fixings poisoned");
match map.get(&date) {
Some(existing) if *existing == value => false,
_ => {
map.insert(date, value);
true
}
}
};
if changed {
self.inner.observable.notify_observers();
}
changed
}
pub fn fixing(&self, date: NaiveDate) -> Option<T> {
self
.inner
.fixings
.read()
.expect("fixings poisoned")
.get(&date)
.copied()
}
pub fn latest_before(&self, date: NaiveDate) -> Option<(NaiveDate, T)> {
self
.inner
.fixings
.read()
.expect("fixings poisoned")
.range(..=date)
.next_back()
.map(|(d, v)| (*d, *v))
}
pub fn len(&self) -> usize {
self
.inner
.fixings
.read()
.map(|m| m.len())
.unwrap_or_default()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn entries(&self) -> Vec<(NaiveDate, T)> {
self
.inner
.fixings
.read()
.expect("fixings poisoned")
.iter()
.map(|(d, v)| (*d, *v))
.collect()
}
}
impl<T: FloatExt> Observable for FixingHistory<T> {
fn register_observer(&self, observer: Weak<dyn Observer + Send + Sync>) {
self.inner.observable.register_observer(observer);
}
fn notify_observers(&self) {
self.inner.observable.notify_observers();
}
}
#[derive(Debug, Clone)]
pub struct NamedIborIndex<T: FloatExt> {
pub index: IborIndex<T>,
pub currency: Currency,
pub calendar: Calendar,
pub spot_lag: u32,
pub fixings: FixingHistory<T>,
}
impl<T: FloatExt> NamedIborIndex<T> {
pub fn new(index: IborIndex<T>, currency: Currency, calendar: Calendar, spot_lag: u32) -> Self {
Self {
index,
currency,
calendar,
spot_lag,
fixings: FixingHistory::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct NamedOvernightIndex<T: FloatExt> {
pub index: OvernightIndex<T>,
pub currency: Currency,
pub calendar: Calendar,
pub fixings: FixingHistory<T>,
}
impl<T: FloatExt> NamedOvernightIndex<T> {
pub fn new(index: OvernightIndex<T>, currency: Currency, calendar: Calendar) -> Self {
Self {
index,
currency,
calendar,
fixings: FixingHistory::new(),
}
}
}
pub mod ibor {
use super::*;
pub fn euribor<T: FloatExt>(tenor: RateTenor) -> NamedIborIndex<T> {
let name = format!("EURIBOR{}", tenor.curve_key());
let index = IborIndex::new(name, tenor, DayCountConvention::Actual360);
NamedIborIndex::new(
index,
currency::EUR,
Calendar::new(HolidayCalendar::Target),
2,
)
}
pub fn euribor_3m<T: FloatExt>() -> NamedIborIndex<T> {
euribor(RateTenor::ThreeMonths)
}
pub fn euribor_6m<T: FloatExt>() -> NamedIborIndex<T> {
euribor(RateTenor::SixMonths)
}
pub fn usd_libor<T: FloatExt>(tenor: RateTenor) -> NamedIborIndex<T> {
let name = format!("USD-LIBOR-{}", tenor.curve_key());
let index = IborIndex::new(name, tenor, DayCountConvention::Actual360);
NamedIborIndex::new(
index,
currency::USD,
Calendar::joint([
HolidayCalendar::UnitedStates,
HolidayCalendar::UnitedKingdom,
]),
2,
)
}
pub fn usd_libor_3m<T: FloatExt>() -> NamedIborIndex<T> {
usd_libor(RateTenor::ThreeMonths)
}
pub fn usd_libor_6m<T: FloatExt>() -> NamedIborIndex<T> {
usd_libor(RateTenor::SixMonths)
}
}
pub mod overnight {
use super::*;
pub fn sofr<T: FloatExt>() -> NamedOvernightIndex<T> {
let index = OvernightIndex::new("SOFR", DayCountConvention::Actual360);
NamedOvernightIndex::new(
index,
currency::USD,
Calendar::new(HolidayCalendar::UnitedStates),
)
}
pub fn fed_funds<T: FloatExt>() -> NamedOvernightIndex<T> {
let index = OvernightIndex::new("EFFR", DayCountConvention::Actual360);
NamedOvernightIndex::new(
index,
currency::USD,
Calendar::new(HolidayCalendar::UnitedStates),
)
}
pub fn estr<T: FloatExt>() -> NamedOvernightIndex<T> {
let index = OvernightIndex::new("ESTR", DayCountConvention::Actual360);
NamedOvernightIndex::new(index, currency::EUR, Calendar::new(HolidayCalendar::Target))
}
pub fn sonia<T: FloatExt>() -> NamedOvernightIndex<T> {
let index = OvernightIndex::new("SONIA", DayCountConvention::Actual365Fixed);
NamedOvernightIndex::new(
index,
currency::GBP,
Calendar::new(HolidayCalendar::UnitedKingdom),
)
}
pub fn tonar<T: FloatExt>() -> NamedOvernightIndex<T> {
let index = OvernightIndex::new("TONAR", DayCountConvention::Actual365Fixed);
NamedOvernightIndex::new(index, currency::JPY, Calendar::new(HolidayCalendar::Tokyo))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fixing_history_lookup_and_latest() {
let hist = FixingHistory::<f64>::new();
let d1 = NaiveDate::from_ymd_opt(2024, 1, 2).unwrap();
let d2 = NaiveDate::from_ymd_opt(2024, 1, 10).unwrap();
let d3 = NaiveDate::from_ymd_opt(2024, 1, 20).unwrap();
hist.add_fixing(d1, 0.053);
hist.add_fixing(d2, 0.052);
hist.add_fixing(d3, 0.051);
assert_eq!(hist.fixing(d2), Some(0.052));
let (d, v) = hist
.latest_before(NaiveDate::from_ymd_opt(2024, 1, 15).unwrap())
.unwrap();
assert_eq!(d, d2);
assert!((v - 0.052).abs() < 1e-12);
assert_eq!(hist.len(), 3);
}
#[test]
fn named_overnight_indices_have_expected_conventions() {
let sofr = overnight::sofr::<f64>();
assert_eq!(sofr.currency.code, "USD");
assert_eq!(sofr.index.day_count, DayCountConvention::Actual360);
let sonia = overnight::sonia::<f64>();
assert_eq!(sonia.currency.code, "GBP");
assert_eq!(sonia.index.day_count, DayCountConvention::Actual365Fixed);
let tonar = overnight::tonar::<f64>();
assert_eq!(tonar.currency.code, "JPY");
assert_eq!(tonar.index.day_count, DayCountConvention::Actual365Fixed);
}
#[test]
fn named_ibor_indices_have_expected_conventions() {
let euribor = ibor::euribor_3m::<f64>();
assert_eq!(euribor.currency.code, "EUR");
assert_eq!(euribor.spot_lag, 2);
assert_eq!(euribor.index.tenor, RateTenor::ThreeMonths);
}
}