use std::collections::{HashMap, HashSet, VecDeque};
use crate::{
ad::adreal::{ADReal, IsReal},
currencies::{currency::Currency, exchangeratestore::ExchangeRateStore},
indices::marketindex::MarketIndex,
instruments::cashflows::leg::Leg,
math::interpolation::interpolator::{Interpolate, Interpolator},
rates::{
bootstrapping::{
bootstrapdiscountpolicy::BootstrapDiscountPolicy,
calibrationinstrument::CalibrationInstrument, curveconfiguration::CurveConfiguration,
},
interestrate::{InterestRate, RateDefinition},
},
time::{date::Date, daycounter::DayCounter},
utils::errors::{QSError, Result},
};
#[must_use]
pub fn get_pillar_times(
reference_date: Date,
day_counter: DayCounter,
instruments: &[CalibrationInstrument],
) -> Vec<f64> {
let mut times = vec![0.0_f64];
for instr in instruments {
times.push(day_counter.year_fraction(reference_date, instr.pillar_date()));
}
times
}
pub fn dependency_order<S: ::std::hash::BuildHasher>(
curve_configs: &HashMap<MarketIndex, CurveConfiguration, S>,
policy: &BootstrapDiscountPolicy,
) -> Result<Vec<MarketIndex>> {
let mut dep_map: HashMap<MarketIndex, HashSet<MarketIndex>> = HashMap::new();
for (idx, spec) in curve_configs {
let mut deps = spec.dependencies(policy)?;
deps.remove(idx);
deps.retain(|dep| curve_configs.contains_key(dep));
dep_map.insert(idx.clone(), deps);
}
let mut indegree: HashMap<MarketIndex, usize> = curve_configs
.keys()
.map(|k| (k.clone(), dep_map.get(k).map_or(0, HashSet::len)))
.collect();
let mut reverse: HashMap<MarketIndex, Vec<MarketIndex>> = HashMap::new();
for (idx, deps) in &dep_map {
for dep in deps {
reverse.entry(dep.clone()).or_default().push(idx.clone());
}
}
let mut queue: VecDeque<MarketIndex> = indegree
.iter()
.filter(|(_, degree)| **degree == 0)
.map(|(idx, _)| idx.clone())
.collect();
let mut order = Vec::new();
while let Some(node) = queue.pop_front() {
order.push(node.clone());
if let Some(children) = reverse.get(&node) {
for child in children {
if let Some(value) = indegree.get_mut(child) {
*value = value.saturating_sub(1);
if *value == 0 {
queue.push_back(child.clone());
}
}
}
}
}
if order.len() < curve_configs.len() {
return Err(QSError::InvalidValueErr(
"Circular dependency detected among curve specifications".into(),
));
}
Ok(order)
}
#[derive(Clone)]
pub struct CrossCurveDep {
pub cross_df_sens: Vec<Vec<f64>>,
pub parent_ift_sens: Vec<Vec<f64>>,
pub parent_quote_values: Vec<f64>,
pub parent_pillar_labels: Vec<String>,
}
#[derive(Clone)]
pub struct SolvedCurve {
market_index: MarketIndex,
reference_date: Date,
times: Vec<f64>,
discount_factors: Vec<f64>,
day_counter: DayCounter,
interpolator: Interpolator,
pillar_values: Option<Vec<ADReal>>,
pillar_labels: Option<Vec<String>>,
output_discount_factors: Option<Vec<ADReal>>,
ift_sensitivities: Option<Vec<Vec<f64>>>,
}
impl SolvedCurve {
#[must_use]
pub const fn new(
market_index: MarketIndex,
reference_date: Date,
times: Vec<f64>,
discount_factors: Vec<f64>,
day_counter: DayCounter,
interpolator: Interpolator,
) -> Self {
Self {
market_index,
reference_date,
times,
discount_factors,
day_counter,
interpolator,
pillar_values: None,
pillar_labels: None,
output_discount_factors: None,
ift_sensitivities: None,
}
}
#[must_use]
pub fn market_index(&self) -> MarketIndex {
self.market_index.clone()
}
#[must_use]
pub fn with_pillar_values(mut self, pillar_values: Vec<ADReal>) -> Self {
self.pillar_values = Some(pillar_values);
self
}
#[must_use]
pub fn with_pillar_labels(mut self, labels: Vec<String>) -> Self {
self.pillar_labels = Some(labels);
self
}
#[must_use]
pub fn pillar_labels(&self) -> Option<&[String]> {
self.pillar_labels.as_deref()
}
#[must_use]
pub fn with_output_discount_factors(mut self, output_discount_factors: Vec<ADReal>) -> Self {
self.output_discount_factors = Some(output_discount_factors);
self
}
#[must_use]
pub fn with_ift_sensitivities(mut self, sensitivities: Vec<Vec<f64>>) -> Self {
self.ift_sensitivities = Some(sensitivities);
self
}
#[must_use]
pub const fn ift_sensitivities(&self) -> Option<&Vec<Vec<f64>>> {
self.ift_sensitivities.as_ref()
}
pub fn pillar_values(&self) -> Result<&[ADReal]> {
self.pillar_values
.as_deref()
.ok_or_else(|| QSError::InvalidValueErr("Pillar values not set".into()))
}
#[must_use]
pub fn discount_factors(&self) -> &[f64] {
&self.discount_factors
}
#[must_use]
pub const fn discount_factors_mut(&mut self) -> &mut Vec<f64> {
&mut self.discount_factors
}
pub fn discount_factor(&self, date: Date) -> Result<f64> {
let year_fraction = self.day_counter.year_fraction(self.reference_date, date);
self.interpolator
.interpolate(year_fraction, &self.times, &self.discount_factors, true)
}
pub fn output_discount_factors(&self) -> Result<&[ADReal]> {
self.output_discount_factors
.as_deref()
.ok_or_else(|| QSError::InvalidValueErr("Output discount factors not set".into()))
}
pub fn forward_rate(
&self,
start_date: Date,
end_date: Date,
rate_definition: RateDefinition,
) -> Result<f64> {
let discount_factor_to_start = self.discount_factor(start_date)?;
let discount_factor_to_end = self.discount_factor(end_date)?;
let comp_factor = discount_factor_to_start / discount_factor_to_end;
let tenor = self.day_counter.year_fraction(start_date, end_date);
Ok(InterestRate::<f64>::implied_rate(
comp_factor,
self.day_counter,
rate_definition.compounding(),
rate_definition.frequency(),
tenor,
)?
.rate())
}
}
pub struct BootstrapCurveSet<'a> {
curves: HashMap<MarketIndex, &'a SolvedCurve>,
discount_policy: &'a BootstrapDiscountPolicy,
exchange_rate_store: &'a ExchangeRateStore,
}
impl<'a> BootstrapCurveSet<'a> {
#[must_use]
pub fn new(
trial: &'a SolvedCurve,
other_curves: &'a HashMap<MarketIndex, SolvedCurve>,
discount_policy: &'a BootstrapDiscountPolicy,
exchange_rate_store: &'a ExchangeRateStore,
) -> Self {
let mut curves: HashMap<MarketIndex, &SolvedCurve> =
other_curves.iter().map(|(k, v)| (k.clone(), v)).collect();
curves.insert(trial.market_index(), trial);
Self {
curves,
discount_policy,
exchange_rate_store,
}
}
#[must_use]
pub fn get(&self, index: &MarketIndex) -> Option<&SolvedCurve> {
self.curves.get(index).copied()
}
#[must_use]
pub const fn discount_policy(&self) -> &BootstrapDiscountPolicy {
self.discount_policy
}
pub fn discount_curve_for_leg(&self, leg: &Leg<f64>) -> Result<&SolvedCurve> {
let index = self.discount_policy.discount_index(leg)?;
self.curves
.get(&index)
.copied()
.ok_or_else(|| QSError::NotFoundErr(format!("Missing discount curve {index}")))
}
pub fn forward_curve_for_leg(&self, leg: &Leg<f64>) -> Result<Option<&SolvedCurve>> {
match leg.forward_index() {
Some(idx) => {
let curve =
self.curves.get(idx).copied().ok_or_else(|| {
QSError::NotFoundErr(format!("Missing forward curve {idx}"))
})?;
Ok(Some(curve))
}
None => Ok(None),
}
}
pub fn fx_spot(&self, base: Currency, quote: Currency) -> Result<f64> {
Ok(self
.exchange_rate_store
.get_exchange_rate(base, quote)?
.value())
}
}