use std::cell::RefCell;
use crate::errors::{QlError, QlResult};
use crate::math::array::Array;
use crate::math::interpolations::Interpolator;
use crate::math::optimization::constraint::NoConstraint;
use crate::math::optimization::costfunction::CostFunction;
use crate::math::optimization::endcriteria::EndCriteria;
use crate::math::optimization::levenbergmarquardt::LevenbergMarquardt;
use crate::math::optimization::method::OptimizationMethod;
use crate::math::optimization::problem::Problem;
use crate::require;
use crate::shared::Shared;
use crate::termstructures::bootstraphelper::BootstrapHelperShared;
use crate::termstructures::bootstraptraits::{BootstrapTraits, YieldBootstrapTraits};
use crate::termstructures::iterativebootstrap::{Bootstrap, PiecewiseCurve};
use crate::types::{Real, Size};
#[derive(Clone, Debug, Default)]
pub struct GlobalBootstrap {
accuracy: Option<Real>,
end_criteria: Option<EndCriteria>,
instrument_weights: Vec<Real>,
}
impl GlobalBootstrap {
pub fn new(
accuracy: Option<Real>,
end_criteria: Option<EndCriteria>,
instrument_weights: Vec<Real>,
) -> GlobalBootstrap {
GlobalBootstrap {
accuracy,
end_criteria,
instrument_weights,
}
}
}
struct GlobalCost<'a, C: PiecewiseCurve>
where
C::Traits: YieldBootstrapTraits,
{
curve: &'a C,
alive: &'a [Shared<C::Helper>],
alive_weights: &'a [Real],
interior: Size,
error: RefCell<Option<QlError>>,
}
impl<C: PiecewiseCurve> GlobalCost<'_, C>
where
C::Traits: YieldBootstrapTraits,
{
fn try_values(&self, x: &Array) -> QlResult<Array> {
{
let mut cd = self.curve.curve_data().borrow_mut();
for i in 0..self.interior {
let value = C::Traits::transform_direct(x[i]);
C::Traits::update_guess(cd.data_mut(), value, i + 1);
}
cd.rebuild(self.curve.interpolator(), self.interior)?;
}
let mut residuals = Array::with_size(self.alive.len());
for (i, helper) in self.alive.iter().enumerate() {
residuals[i] = helper.quote_error()? * self.alive_weights[i];
}
Ok(residuals)
}
}
impl<C: PiecewiseCurve> CostFunction for GlobalCost<'_, C>
where
C::Traits: YieldBootstrapTraits,
{
fn values(&self, x: &Array) -> Array {
match self.try_values(x) {
Ok(values) => values,
Err(err) => {
let mut slot = self.error.borrow_mut();
if slot.is_none() {
*slot = Some(err);
}
std::iter::repeat_n(Real::NAN, self.alive.len()).collect()
}
}
}
}
impl<C: PiecewiseCurve> Bootstrap<C> for GlobalBootstrap
where
C::Traits: YieldBootstrapTraits,
{
fn calculate(&self, curve: &C) -> QlResult<()> {
let instruments = curve.instruments();
let n = instruments.len();
require!(
self.instrument_weights.is_empty() || self.instrument_weights.len() == n,
"GlobalBootstrap: number of instrument weights ({}) must match number of instruments ({n})",
self.instrument_weights.len()
);
let mut weights = self.instrument_weights.clone();
weights.resize(n, 1.0);
let first_date = curve.initial_date()?;
let mut alive: Vec<Shared<C::Helper>> = Vec::new();
let mut alive_weights: Vec<Real> = Vec::new();
for (helper, weight) in instruments.iter().zip(&weights) {
if helper.pillar_date() > first_date {
alive.push(Shared::clone(helper));
alive_weights.push(*weight);
}
}
let mut dates = Vec::with_capacity(alive.len() + 1);
dates.push(first_date);
dates.extend(alive.iter().map(|helper| helper.pillar_date()));
dates.sort_unstable();
dates.dedup();
let required = curve.interpolator().required_points();
require!(
dates.len() >= required,
"GlobalBootstrap: not enough curve points ({}) for interpolation requiring at least {required}",
dates.len()
);
let mut times = Vec::with_capacity(dates.len());
for date in &dates {
times.push(curve.time_from_reference(*date)?);
}
let mut max_date = *dates.last().expect("the grid holds the first date");
for helper in &alive {
max_date = max_date.max(helper.latest_relevant_date());
}
let nodes = dates.len();
let interior = nodes - 1;
let initial_value = curve.initial_value()?;
let valid_data = {
let mut cd = curve.curve_data().borrow_mut();
let reuse = cd.is_valid() && cd.data().len() == nodes;
cd.set_pillars(dates, times);
if !reuse {
cd.reset_data(initial_value, nodes);
}
cd.set_max_date(max_date);
reuse
};
let term_structure = curve.term_structure_shared()?;
for helper in &alive {
if helper.quote_value().is_err() {
crate::fail!(
"instrument (maturity: {}, pillar: {}) has an invalid quote",
helper.maturity_date(),
helper.pillar_date()
);
}
helper.set_term_structure(&term_structure);
}
let mut guess = Array::with_size(interior);
{
let mut cd = curve.curve_data().borrow_mut();
for i in 0..interior {
let g = C::Traits::guess(i + 1, cd.times(), cd.data(), valid_data);
C::Traits::update_guess(cd.data_mut(), g, i + 1);
guess[i] = C::Traits::transform_inverse(cd.data()[i + 1]);
}
cd.rebuild(curve.interpolator(), interior)?;
}
let accuracy = self.accuracy.unwrap_or_else(|| curve.accuracy());
let mut optimizer = LevenbergMarquardt::new(accuracy, accuracy, accuracy, false);
let end_criteria = match self.end_criteria {
Some(criteria) => criteria,
None => EndCriteria::new(1000, Some(10), accuracy, accuracy, Some(accuracy))?,
};
let cost = GlobalCost::<C> {
curve,
alive: &alive,
alive_weights: &alive_weights,
interior,
error: RefCell::new(None),
};
let no_constraint = NoConstraint;
let (end_type, solution) = {
let mut problem = Problem::new(&cost, &no_constraint, guess);
let outcome = optimizer.minimize(&mut problem, &end_criteria);
(outcome, problem.current_value().clone())
};
if let Some(inner) = cost.error.into_inner() {
return Err(inner);
}
let end_type = end_type?;
require!(
end_type.succeeded(),
"global bootstrap failed to minimize to required accuracy: {end_type}"
);
{
let mut cd = curve.curve_data().borrow_mut();
for i in 0..interior {
let value = C::Traits::transform_direct(solution[i]);
C::Traits::update_guess(cd.data_mut(), value, i + 1);
}
cd.rebuild(curve.interpolator(), interior)?;
cd.set_valid(true);
}
Ok(())
}
}