use std::cell::RefCell;
use crate::errors::QlResult;
use crate::handle::{Handle, RelinkableHandle};
use crate::indexes::inflationindex::{CpiInterpolationType, YoYInflationIndex};
use crate::instrument::Instrument;
use crate::instruments::{CapFloorType, MakeYoYInflationCapFloor, YoYInflationCapFloor};
use crate::patterns::observable::AsObservable;
use crate::pricingengine::PricingEngine;
use crate::pricingengines::inflation::YoYInflationCapFloorEngine;
use crate::quotes::Quote;
use crate::settings::Settings;
use crate::shared::{Shared, SharedMut};
use crate::termstructures::bootstraphelper::{BootstrapHelperBase, BootstrapHelperShared};
use crate::termstructures::volatility::YoYOptionletVolatilitySurface;
use crate::time::calendar::Calendar;
use crate::time::date::Date;
use crate::time::daycounter::DayCounter;
use crate::time::period::Period;
use crate::types::{Natural, Rate, Real, Size};
pub type YoYOptionletVolHelperBase = BootstrapHelperBase<dyn YoYOptionletVolatilitySurface>;
pub trait YoYOptionletVolatilityHelper: AsObservable {
fn base(&self) -> &YoYOptionletVolHelperBase;
fn implied_quote(&self) -> QlResult<Real>;
fn quote(&self) -> &Handle<dyn Quote> {
self.base().quote()
}
fn quote_error(&self) -> QlResult<Real> {
Ok(self.base().quote_value()? - self.implied_quote()?)
}
fn set_term_structure(&self, term_structure: &Shared<dyn YoYOptionletVolatilitySurface>) {
self.base().set_term_structure(term_structure);
}
fn earliest_date(&self) -> Date {
self.base().earliest_date()
}
fn maturity_date(&self) -> Date {
self.base().maturity_date()
}
fn latest_relevant_date(&self) -> Date {
self.base().latest_relevant_date()
}
fn pillar_date(&self) -> Date {
self.base().pillar_date()
}
fn latest_date(&self) -> Date {
self.base().latest_date()
}
}
impl BootstrapHelperShared for dyn YoYOptionletVolatilityHelper {
type TS = dyn YoYOptionletVolatilitySurface;
fn set_term_structure(&self, term_structure: &Shared<dyn YoYOptionletVolatilitySurface>) {
YoYOptionletVolatilityHelper::set_term_structure(self, term_structure);
}
fn quote_value(&self) -> QlResult<Real> {
self.base().quote_value()
}
fn quote_error(&self) -> QlResult<Real> {
YoYOptionletVolatilityHelper::quote_error(self)
}
fn pillar_date(&self) -> Date {
YoYOptionletVolatilityHelper::pillar_date(self)
}
fn latest_relevant_date(&self) -> Date {
YoYOptionletVolatilityHelper::latest_relevant_date(self)
}
fn maturity_date(&self) -> Date {
YoYOptionletVolatilityHelper::maturity_date(self)
}
}
pub struct YoYOptionletHelper {
base: YoYOptionletVolHelperBase,
vol_handle: RelinkableHandle<dyn YoYOptionletVolatilitySurface>,
capfloor: RefCell<YoYInflationCapFloor>,
}
impl YoYOptionletHelper {
#[allow(clippy::too_many_arguments)]
pub fn new(
price: Handle<dyn Quote>,
notional: Real,
cap_floor_type: CapFloorType,
lag: Period,
yoy_day_counter: DayCounter,
payment_calendar: Calendar,
fixing_days: Natural,
index: &Shared<YoYInflationIndex>,
interpolation: CpiInterpolationType,
strike: Rate,
n: Size,
pricer: SharedMut<YoYInflationCapFloorEngine>,
vol_handle: RelinkableHandle<dyn YoYOptionletVolatilitySurface>,
settings: Shared<Settings<Date>>,
) -> QlResult<Shared<YoYOptionletHelper>> {
let capfloor = MakeYoYInflationCapFloor::new(
cap_floor_type,
Shared::clone(index),
n,
payment_calendar,
lag,
interpolation,
settings,
)
.with_nominal(notional)
.with_fixing_days(fixing_days)
.with_payment_day_counter(yoy_day_counter)
.with_strike(strike)
.with_pricing_engine(pricer as SharedMut<dyn PricingEngine>)
.build()?;
let base = YoYOptionletVolHelperBase::new(price);
let leg = capfloor.yoy_leg();
base.set_earliest_date(leg.first().expect("the leg is non-empty").fixing_date());
base.set_latest_date(leg.last().expect("the leg is non-empty").fixing_date());
Ok(crate::shared::shared(YoYOptionletHelper {
base,
vol_handle,
capfloor: RefCell::new(capfloor),
}))
}
pub fn capfloor(&self) -> &RefCell<YoYInflationCapFloor> {
&self.capfloor
}
}
impl AsObservable for YoYOptionletHelper {
fn observable(&self) -> &crate::patterns::observable::Observable {
self.base.observable()
}
}
impl YoYOptionletVolatilityHelper for YoYOptionletHelper {
fn base(&self) -> &YoYOptionletVolHelperBase {
&self.base
}
fn implied_quote(&self) -> QlResult<Real> {
let mut capfloor = self.capfloor.borrow_mut();
capfloor.base().observer().borrow_mut().update();
capfloor.npv()
}
fn set_term_structure(&self, term_structure: &Shared<dyn YoYOptionletVolatilitySurface>) {
self.base.set_term_structure(term_structure);
self.vol_handle
.link_to_weak(Shared::downgrade(term_structure));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::currency::Currency;
use crate::indexes::inflation::YyGenericCpi;
use crate::quotes::SimpleQuote;
use crate::shared::{shared, shared_mut};
use crate::termstructures::volatility::ConstantYoYOptionletVolatility;
use crate::time::businessdayconvention::BusinessDayConvention;
use crate::time::calendars::target::Target;
use crate::time::date::Month::{June, March};
use crate::time::daycounters::actual365fixed::Actual365Fixed;
use crate::time::frequency::Frequency;
use crate::time::timeunit::TimeUnit;
fn lag() -> Period {
Period::new(3, TimeUnit::Months)
}
struct Fixture {
settings: Shared<Settings<Date>>,
vol_handle: RelinkableHandle<dyn YoYOptionletVolatilitySurface>,
helper: Shared<YoYOptionletHelper>,
}
fn a_helper() -> Fixture {
let settings = shared(Settings::<Date>::new());
settings.set_evaluation_date(Date::new(15, June, 2026));
let index = shared(YyGenericCpi::new(
Frequency::Monthly,
false,
lag(),
Currency::eur(),
Shared::clone(&settings),
));
let vol_handle = RelinkableHandle::<dyn YoYOptionletVolatilitySurface>::empty();
let pricer = shared_mut(YoYInflationCapFloorEngine::unit_displaced(
Shared::clone(&index),
vol_handle.handle(),
crate::handle::Handle::empty(),
));
let helper = YoYOptionletHelper::new(
Handle::new(shared(SimpleQuote::new(Some(25.0)))),
10_000.0,
CapFloorType::Cap,
lag(),
Actual365Fixed::new(),
Target::new(),
0,
&index,
CpiInterpolationType::Flat,
0.03,
3,
pricer,
vol_handle.clone(),
Shared::clone(&settings),
)
.expect("a well-formed helper");
Fixture {
settings,
vol_handle,
helper,
}
}
#[test]
fn the_dates_are_the_first_and_last_coupon_fixings() {
let fixture = a_helper();
let helper = &fixture.helper;
assert_eq!(helper.earliest_date(), Date::new(15, March, 2027));
assert_eq!(helper.latest_date(), Date::new(15, March, 2029));
assert_eq!(helper.pillar_date(), Date::new(15, March, 2029));
assert_eq!(helper.capfloor().borrow().yoy_leg().len(), 3);
let _ = fixture.settings;
}
#[test]
fn set_term_structure_repoints_the_retained_link_weakly() {
let fixture = a_helper();
assert!(fixture.vol_handle.handle().current_link().is_err());
let surface = shared(ConstantYoYOptionletVolatility::new(
0.01,
0,
Target::new(),
BusinessDayConvention::ModifiedFollowing,
Actual365Fixed::new(),
lag(),
Frequency::Monthly,
false,
-1.0,
3.0,
Shared::clone(&fixture.settings),
)) as Shared<dyn YoYOptionletVolatilitySurface>;
YoYOptionletVolatilityHelper::set_term_structure(fixture.helper.as_ref(), &surface);
assert!(
Shared::ptr_eq(
&fixture.vol_handle.handle().current_link().unwrap(),
&surface
),
"the link must point at the surface just set"
);
assert!(fixture.helper.base().term_structure().is_ok());
drop(surface);
assert!(
fixture.vol_handle.handle().current_link().is_err(),
"the link must not keep the surface alive"
);
}
}