use std::{
collections::HashMap,
sync::{Arc, RwLock, RwLockReadGuard},
};
use crate::{
currencies::enums::Currency,
time::{date::Date, enums::TimeUnit, period::Period},
utils::errors::{AtlasError, Result},
};
use super::{
interestrateindex::traits::InterestRateIndexTrait,
yieldtermstructure::traits::YieldTermStructureTrait,
};
#[derive(Clone)]
pub struct IndexStore {
reference_date: Date,
index_map: HashMap<usize, Arc<RwLock<dyn InterestRateIndexTrait>>>,
currency_curve: HashMap<Currency, usize>,
}
pub trait ReadIndex {
fn read_index(&self) -> Result<RwLockReadGuard<'_, dyn InterestRateIndexTrait>>;
}
impl ReadIndex for Arc<RwLock<dyn InterestRateIndexTrait>> {
fn read_index(&self) -> Result<RwLockReadGuard<'_, dyn InterestRateIndexTrait>> {
self.read()
.map_err(|_| AtlasError::InvalidValueErr("Could not read index".to_string()))
}
}
impl IndexStore {
#[must_use]
pub fn new(reference_date: Date) -> Self {
Self {
reference_date,
index_map: HashMap::new(),
currency_curve: HashMap::new(),
}
}
#[must_use]
pub const fn reference_date(&self) -> Date {
self.reference_date
}
pub fn add_currency_curve(&mut self, currency: Currency, fx_curve: usize) {
self.currency_curve.insert(currency, fx_curve);
}
pub fn get_currency_curve(&self, currency: Currency) -> Result<usize> {
self.currency_curve
.get(¤cy)
.copied()
.ok_or(AtlasError::NotFoundErr(format!(
"Currency curve for currency {currency:?}"
)))
}
pub fn link_term_structure(
&self,
id: usize,
term_structure: Arc<dyn YieldTermStructureTrait>,
) -> Result<()> {
self.index_map
.get(&id)
.ok_or(AtlasError::NotFoundErr(format!(
"Index with id {id} not found"
)))?
.write()
.map_err(|_| AtlasError::InvalidValueErr("Could not write index".to_string()))?
.link_to(term_structure);
Ok(())
}
pub fn add_index(
&mut self,
id: usize,
index: Arc<RwLock<dyn InterestRateIndexTrait>>,
) -> Result<()> {
if self.reference_date != index.read_index()?.reference_date() {
return Err(AtlasError::InvalidValueErr(format!(
"Index ({name:?}) reference date ({reference_date}) does not match index store reference date ({store_reference_date})",
name = index.read_index()?.name(),
reference_date = index.read_index()?.reference_date(),
store_reference_date = self.reference_date
)));
}
if self.index_map.contains_key(&id) {
return Err(AtlasError::InvalidValueErr(format!(
"Index with id {id} already exists"
)));
}
self.index_map.insert(id, index);
Ok(())
}
pub fn replace_index(
&mut self,
id: usize,
index: Arc<RwLock<dyn InterestRateIndexTrait>>,
) -> Result<()> {
if self.reference_date != index.read_index()?.reference_date() {
return Err(AtlasError::InvalidValueErr(format!(
"Index ({name:?}) reference date ({reference_date}) does not match index store reference date ({store_reference_date})",
name = index.read_index()?.name(),
reference_date = index.read_index()?.reference_date(),
store_reference_date = self.reference_date
)));
}
if !self.index_map.contains_key(&id) {
return Err(AtlasError::InvalidValueErr(format!(
"Index with id {id} does not exist"
)));
}
self.index_map.insert(id, index);
Ok(())
}
pub fn get_index(&self, id: usize) -> Result<Arc<RwLock<dyn InterestRateIndexTrait>>> {
self.index_map
.get(&id)
.cloned()
.ok_or(AtlasError::NotFoundErr(format!(
"Index with id {id} not found"
)))
}
pub fn get_index_by_name(&self, name: &str) -> Result<Arc<RwLock<dyn InterestRateIndexTrait>>> {
for (id, index) in &self.index_map {
if index.read_index()?.name()? == name {
return self.get_index(*id);
}
}
Err(AtlasError::NotFoundErr(format!(
"Index with name {name} not found"
)))
}
pub fn get_index_names(&self) -> Result<Vec<String>> {
let mut names = Vec::new();
for index in self.index_map.values() {
names.push(index.read_index()?.name()?);
}
Ok(names)
}
pub fn get_index_map(&self) -> Result<HashMap<String, usize>> {
let mut map = HashMap::new();
for (id, index) in &self.index_map {
map.insert(index.read_index()?.name()?, *id);
}
Ok(map)
}
#[must_use]
pub fn get_all_indices(&self) -> Vec<Arc<RwLock<dyn InterestRateIndexTrait>>> {
let mut indices = Vec::new();
for index in self.index_map.values() {
indices.push(Arc::clone(index));
}
indices
}
#[must_use]
pub fn next_available_id(&self) -> usize {
let keys = self.index_map.keys();
let mut max = 0;
for key in keys {
if *key > max {
max = *key;
}
}
max + 1
}
pub fn advance_to_period(&self, period: Period) -> Result<Self> {
let reference_date = self.reference_date + period;
let mut store = Self::new(reference_date);
for (id, index) in &self.index_map {
let new_index = index.read_index()?.advance_to_period(period)?;
store.add_index(*id, new_index)?;
}
for (currency, curve) in &self.currency_curve {
store.add_currency_curve(*currency, *curve);
}
Ok(store)
}
pub fn advance_to_date(&self, date: Date) -> Result<Self> {
let days = i32::try_from(date - self.reference_date)
.map_err(|_| AtlasError::InvalidValueErr("Day count should fit in i32".to_string()))?;
self.advance_to_period(Period::new(days, TimeUnit::Days))
}
pub fn swap_index_by_id(&mut self, from: usize, to: usize) -> Result<()> {
let index = self
.index_map
.remove(&from)
.ok_or_else(|| AtlasError::NotFoundErr(format!("Index with id {from} not found")))?;
self.index_map.insert(to, index);
Ok(())
}
pub fn currency_forescast_factor(
&self,
first_currency: Currency,
second_currency: Currency,
date: Date,
) -> Result<f64> {
let first_id = self.get_currency_curve(first_currency)?;
let second_id = self.get_currency_curve(second_currency)?;
let first_curve = self.get_index(first_id)?;
let second_curve = self.get_index(second_id)?;
let first_df = first_curve.read_index()?.discount_factor(date)?;
let second_df = second_curve.read_index()?.discount_factor(date)?;
Ok(second_df / first_df)
}
}