use std::collections::HashMap;
use crate::{
currencies::currency::Currency,
indices::marketindex::MarketIndex,
quotes::quote::BuiltInstrument,
utils::errors::{QSError, Result},
};
pub struct BootstrapDiscountPolicy {
csa_index: MarketIndex,
csa_currency: Currency,
collateral_by_currency: HashMap<Currency, MarketIndex>,
}
impl BootstrapDiscountPolicy {
#[must_use]
pub fn new(csa_index: MarketIndex, csa_currency: Currency) -> Self {
Self {
csa_index,
csa_currency,
collateral_by_currency: HashMap::new(),
}
}
#[must_use]
pub fn with_collateral_curve(
mut self,
cashflow_currency: Currency,
discount_index: MarketIndex,
) -> Self {
self.collateral_by_currency
.insert(cashflow_currency, discount_index);
self
}
#[must_use]
pub const fn csa_index(&self) -> &MarketIndex {
&self.csa_index
}
#[must_use]
pub const fn csa_currency(&self) -> Currency {
self.csa_currency
}
#[must_use]
pub fn discount_index_for_currency(&self, cashflow_currency: Currency) -> MarketIndex {
if cashflow_currency == self.csa_currency {
return self.csa_index.clone();
}
self.collateral_by_currency
.get(&cashflow_currency)
.cloned()
.unwrap_or_else(|| self.csa_index.clone())
}
pub fn discount_index(
&self,
built: &BuiltInstrument,
target_index: &MarketIndex,
) -> Result<MarketIndex> {
match built {
BuiltInstrument::FixedRateDeposit(_) | BuiltInstrument::RateFutures(_) => {
Ok(target_index.clone())
}
BuiltInstrument::Swap(_)
| BuiltInstrument::BasisSwap(_)
| BuiltInstrument::CrossCurrencySwap(_)
| BuiltInstrument::FxForward(_) => Ok(self.csa_index.clone()),
_ => Err(QSError::InvalidValueErr(
"Unsupported instrument for bootstrap discounting".into(),
)),
}
}
#[must_use]
pub fn dependencies(
&self,
built: &BuiltInstrument,
target_index: &MarketIndex,
) -> Vec<MarketIndex> {
let mut deps = Vec::new();
match built {
BuiltInstrument::Swap(s) => {
Self::push_if_different(&mut deps, &self.csa_index, target_index);
let proj = s.market_index();
Self::push_if_different(&mut deps, &proj, target_index);
}
BuiltInstrument::BasisSwap(bs) => {
Self::push_if_different(&mut deps, &self.csa_index, target_index);
let pay = bs.pay_market_index();
let rec = bs.receive_market_index();
Self::push_if_different(&mut deps, &pay, target_index);
Self::push_if_different(&mut deps, &rec, target_index);
}
BuiltInstrument::CrossCurrencySwap(xccy) => {
let dom_disc = self.discount_index_for_currency(xccy.domestic_currency());
let for_disc = self.discount_index_for_currency(xccy.foreign_currency());
Self::push_if_different(&mut deps, &dom_disc, target_index);
Self::push_if_different(&mut deps, &for_disc, target_index);
let dom_proj = xccy.domestic_market_index();
let for_proj = xccy.foreign_market_index();
Self::push_if_different(&mut deps, &dom_proj, target_index);
Self::push_if_different(&mut deps, &for_proj, target_index);
}
BuiltInstrument::RateFutures(f) => {
let proj = f.market_index();
Self::push_if_different(&mut deps, &proj, target_index);
}
BuiltInstrument::FxForward(fx) => {
let base = self.discount_index_for_currency(fx.base_currency());
let quote = self.discount_index_for_currency(fx.quote_currency());
Self::push_if_different(&mut deps, &base, target_index);
Self::push_if_different(&mut deps, "e, target_index);
}
_ => {}
}
let mut seen = std::collections::HashSet::new();
deps.retain(|d| seen.insert(d.clone()));
deps
}
#[must_use]
pub fn all_discount_indices(&self) -> Vec<MarketIndex> {
let mut indices = vec![self.csa_index.clone()];
for idx in self.collateral_by_currency.values() {
if !indices.contains(idx) {
indices.push(idx.clone());
}
}
indices
}
fn push_if_different(
out: &mut Vec<MarketIndex>,
candidate: &MarketIndex,
exclude: &MarketIndex,
) {
if candidate != exclude && !out.contains(candidate) {
out.push(candidate.clone());
}
}
}