use std::collections::HashSet;
use serde::{Deserialize, Serialize};
use crate::{
core::collateral::Discountable,
indices::marketindex::MarketIndex,
math::interpolation::interpolator::Interpolator,
quotes::quote::{CalibrationInstrumentType, Level, Quote},
rates::bootstrapping::{
bootstrapdiscountpolicy::BootstrapDiscountPolicy,
calibrationinstrument::CalibrationInstrument,
},
time::{date::Date, daycounter::DayCounter},
utils::errors::{QSError, Result},
};
pub trait QuoteSelector {
fn select(&self, identifier: &str) -> Option<Quote>;
fn reference_date(&self) -> Date;
}
#[derive(Serialize, Deserialize, Clone)]
pub struct CurveConfiguration {
market_index: MarketIndex,
#[serde(default = "default_day_counter")]
day_counter: DayCounter,
#[serde(default = "default_interpolator")]
interpolator: Interpolator,
#[serde(default = "default_enable_extrapolation")]
enable_extrapolation: bool,
#[serde(default)]
quotes: Vec<String>,
#[serde(skip)]
reference_date: Option<Date>,
#[serde(skip)]
calibration_instruments: Option<Vec<CalibrationInstrument>>,
}
const fn default_day_counter() -> DayCounter {
DayCounter::Actual360
}
const fn default_interpolator() -> Interpolator {
Interpolator::LogLinear
}
const fn default_enable_extrapolation() -> bool {
true
}
impl CurveConfiguration {
#[must_use]
pub const fn new(
market_index: MarketIndex,
day_counter: DayCounter,
interpolator: Interpolator,
enable_extrapolation: bool,
quotes: Vec<String>,
) -> Self {
Self {
market_index,
day_counter,
interpolator,
enable_extrapolation,
quotes,
reference_date: None,
calibration_instruments: None,
}
}
#[must_use]
pub const fn market_index(&self) -> &MarketIndex {
&self.market_index
}
pub fn resolve(
&mut self,
selector: &impl QuoteSelector,
level: Level,
fx_spot: Option<f64>,
) -> Result<()> {
let mut instruments = Vec::new();
self.reference_date = Some(selector.reference_date());
for id in &self.quotes {
let quote = selector
.select(id)
.ok_or_else(|| QSError::NotFoundErr(format!("Quote {id} not found in quotes.")))?;
let quote_value = quote.levels().value(level)?;
let built = quote.build_instrument(selector.reference_date(), level, fx_spot)?;
let pillar_date = built.pillar_date()?;
instruments.push(CalibrationInstrument::new(
quote,
level,
built,
quote_value,
pillar_date,
));
}
instruments.sort_by_key(CalibrationInstrument::pillar_date);
self.calibration_instruments = Some(instruments);
Ok(())
}
pub fn instruments(&self) -> Result<&[CalibrationInstrument]> {
self.calibration_instruments
.as_deref()
.ok_or_else(|| QSError::InvalidValueErr("Curve configuration not resolved".into()))
}
#[must_use]
#[allow(clippy::expect_used)]
pub const fn reference_date(&self) -> Date {
self.reference_date
.expect("Curve configuration must be resolved before accessing reference_date")
}
#[must_use]
pub const fn day_counter(&self) -> DayCounter {
self.day_counter
}
#[must_use]
pub const fn interpolator(&self) -> Interpolator {
self.interpolator
}
#[must_use]
pub const fn enable_extrapolation(&self) -> bool {
self.enable_extrapolation
}
#[must_use]
pub fn pillar_dates(&self) -> Vec<Date> {
self.calibration_instruments
.as_ref()
.map(|instruments| {
instruments
.iter()
.map(CalibrationInstrument::pillar_date)
.collect()
})
.unwrap_or_default()
}
#[must_use]
pub fn pillar_labels(&self) -> Vec<String> {
self.calibration_instruments
.as_ref()
.map(|instruments| {
instruments
.iter()
.map(CalibrationInstrument::pillar_label)
.collect()
})
.unwrap_or_default()
}
#[must_use]
pub fn quote_values(&self) -> Vec<f64> {
self.calibration_instruments
.as_ref()
.map(|instruments| {
instruments
.iter()
.map(CalibrationInstrument::quote_value)
.collect()
})
.unwrap_or_default()
}
pub fn local_dependencies(&self) -> Result<HashSet<MarketIndex>> {
let mut set = HashSet::new();
set.insert(self.market_index.clone());
let instruments = self.instruments()?;
for instrument in instruments {
match instrument.built() {
CalibrationInstrumentType::FixedRateDeposit(deposit) => {
if let Some(discount_index) = deposit.discount_index() {
set.insert(discount_index);
}
}
CalibrationInstrumentType::Swap(swap) => {
set.insert(swap.forward_index());
}
CalibrationInstrumentType::FixFloatCrossCurrencySwap(xccy) => {
set.insert(xccy.forward_index());
}
CalibrationInstrumentType::FloatFloatCrossCurrencySwap(xccy) => {
set.insert(xccy.domestic_forward_index());
set.insert(xccy.foreign_forward_index());
}
CalibrationInstrumentType::BasisSwap(basis) => {
set.insert(basis.pay_forward_index());
set.insert(basis.receive_forward_index());
}
CalibrationInstrumentType::RateFutures(rf) => {
set.insert(rf.market_index());
}
_ => {}
}
}
Ok(set)
}
pub fn dependencies(&self, policy: &BootstrapDiscountPolicy) -> Result<HashSet<MarketIndex>> {
let mut deps = self.local_dependencies()?;
let instruments = self.instruments()?;
for instrument in instruments {
match instrument.built() {
CalibrationInstrumentType::Swap(swap) => {
if let Ok(idx) = policy.discount_index(swap.fixed_leg()) {
deps.insert(idx);
}
}
CalibrationInstrumentType::BasisSwap(basis) => {
if let Ok(idx) = policy.discount_index(basis.pay_leg()) {
deps.insert(idx);
}
}
CalibrationInstrumentType::FixFloatCrossCurrencySwap(xccy) => {
if let Ok(idx) = policy.discount_index(xccy.domestic_leg()) {
deps.insert(idx);
}
if let Ok(idx) = policy.discount_index(xccy.foreign_leg()) {
deps.insert(idx);
}
}
CalibrationInstrumentType::FloatFloatCrossCurrencySwap(xccy) => {
if let Ok(idx) = policy.discount_index(xccy.domestic_leg()) {
deps.insert(idx);
}
if let Ok(idx) = policy.discount_index(xccy.foreign_leg()) {
deps.insert(idx);
}
}
CalibrationInstrumentType::FxForward(fwd) => {
if let Ok(idx) = policy.discount_index_for_currency(fwd.base_currency()) {
deps.insert(idx);
}
if let Ok(idx) = policy.discount_index_for_currency(fwd.quote_currency()) {
deps.insert(idx);
}
}
_ => {}
}
}
Ok(deps)
}
}