use super::DiscretizedCapFloor;
use crate::discretizedasset::DiscretizedAsset;
use crate::errors::QlResult;
use crate::instrument::InstrumentResults;
use crate::instruments::CapFloorArguments;
use crate::math::timegrid::TimeGrid;
use crate::methods::lattices::lattice::Lattice;
use crate::models::model::CalibratedModelHolder;
use crate::models::shortrate::hullwhite::HullWhite;
use crate::patterns::observable::{AsObservable, Observable};
use crate::pricingengine::{Arguments, GenericEngine, PricingEngine, Results};
use crate::shared::{Shared, SharedMut, shared};
use crate::types::Size;
use crate::{fail, require};
pub struct TreeCapFloorEngine {
base: GenericEngine<CapFloorArguments, InstrumentResults>,
model: SharedMut<HullWhite>,
time_steps: Size,
time_grid: Option<TimeGrid>,
}
impl TreeCapFloorEngine {
pub fn new(model: SharedMut<HullWhite>, time_steps: Size) -> QlResult<Self> {
require!(time_steps > 0, "timeSteps must be positive");
Ok(Self::build(model, time_steps, None))
}
pub fn with_time_grid(model: SharedMut<HullWhite>, grid: TimeGrid) -> QlResult<Self> {
require!(
grid.size() >= 2 && grid.times().iter().all(|t| t.is_finite()),
"invalid cap/floor time grid"
);
require!(
grid.front().is_some_and(|time| time >= 0.0)
&& grid.back().is_some_and(|time| time > 0.0),
"invalid cap/floor time grid bounds"
);
require!(
grid.times().windows(2).all(|p| p[1] > p[0]),
"invalid cap/floor time grid"
);
Ok(Self::build(model, 0, Some(grid)))
}
fn build(model: SharedMut<HullWhite>, time_steps: Size, time_grid: Option<TimeGrid>) -> Self {
let base = GenericEngine::new(CapFloorArguments::default(), InstrumentResults::default());
base.register_with(model.borrow().calibrated_model().observable());
Self {
base,
model,
time_steps,
time_grid,
}
}
}
impl AsObservable for TreeCapFloorEngine {
fn observable(&self) -> &Observable {
self.base.observable()
}
}
impl PricingEngine for TreeCapFloorEngine {
fn arguments_mut(&mut self) -> &mut dyn Arguments {
self.base.arguments_mut()
}
fn results(&self) -> &dyn Results {
self.base.results()
}
fn reset(&mut self) {
self.base.reset();
}
fn calculate(&mut self) -> QlResult<()> {
let model = self.model.borrow();
let curve = model.term_structure().current_link()?;
let reference = curve.reference_date()?;
let dc = curve.require_day_counter()?;
let args = self.base.arguments();
let mut capfloor = DiscretizedCapFloor::new(args, reference, &dc)?;
let (Some(&start), Some(&end)) = (args.start_dates.first(), args.end_dates.last()) else {
fail!("cap/floor has no coupons");
};
let first = dc.year_fraction(reference, start);
let last = dc.year_fraction(reference, end);
require!(
first >= 0.0 && last > 0.0,
"cap/floor tree requires non-negative start and positive end times"
);
let times = capfloor.mandatory_times();
let grid = if let Some(grid) = &self.time_grid {
for &time in × {
grid.index(time)?;
}
grid.clone()
} else {
TimeGrid::with_mandatory_times(×, self.time_steps)?
};
let lattice: Shared<dyn Lattice> = shared(model.tree(grid)?);
drop(model);
capfloor.initialize(lattice, last)?;
capfloor.rollback(first)?;
let value = capfloor.present_value()?;
require!(value.is_finite(), "non-finite cap/floor lattice result");
self.base.results_mut().value = Some(value);
Ok(())
}
}
#[cfg(test)]
#[path = "treecapfloorengine_tests.rs"]
mod tests;