use std::cell::Cell;
use crate::errors::QlResult;
use crate::indexes::inflationindex::inflation_period;
use crate::require;
use crate::settings::Settings;
use crate::shared::Shared;
use crate::termstructures::TermStructureBase;
use crate::time::businessdayconvention::BusinessDayConvention;
use crate::time::calendar::Calendar;
use crate::time::date::Date;
use crate::time::daycounter::DayCounter;
use crate::time::frequency::Frequency;
use crate::time::period::Period;
use crate::types::{Natural, Rate, Time, Volatility};
pub struct YoYOptionletVolatilitySurfaceBase {
term: TermStructureBase,
business_day_convention: BusinessDayConvention,
observation_lag: Period,
frequency: Frequency,
index_is_interpolated: bool,
base_level: Cell<Option<Volatility>>,
}
impl YoYOptionletVolatilitySurfaceBase {
#[allow(clippy::too_many_arguments)]
pub fn new(
settlement_days: Natural,
calendar: Calendar,
business_day_convention: BusinessDayConvention,
day_counter: DayCounter,
observation_lag: Period,
frequency: Frequency,
index_is_interpolated: bool,
settings: Shared<Settings<Date>>,
) -> YoYOptionletVolatilitySurfaceBase {
YoYOptionletVolatilitySurfaceBase {
term: TermStructureBase::moving(settlement_days, calendar, Some(day_counter), settings),
business_day_convention,
observation_lag,
frequency,
index_is_interpolated,
base_level: Cell::new(None),
}
}
pub fn term_structure_base(&self) -> &TermStructureBase {
&self.term
}
pub fn business_day_convention(&self) -> BusinessDayConvention {
self.business_day_convention
}
pub fn observation_lag(&self) -> Period {
self.observation_lag
}
pub fn frequency(&self) -> Frequency {
self.frequency
}
pub fn index_is_interpolated(&self) -> bool {
self.index_is_interpolated
}
pub fn base_level(&self) -> QlResult<Volatility> {
match self.base_level.get() {
Some(level) => Ok(level),
None => crate::fail!("base volatility, for base_date(), not set"),
}
}
pub fn set_base_level(&self, level: Volatility) {
self.base_level.set(Some(level));
}
pub fn observed(&self, date: Date) -> QlResult<Date> {
if self.index_is_interpolated {
Ok(date)
} else {
Ok(inflation_period(date, self.frequency)?.0)
}
}
pub fn base_date(&self) -> QlResult<Date> {
self.observed(self.term.reference_date()? - self.observation_lag)
}
pub fn time_from_base(&self, date: Date, obs_lag: Period) -> QlResult<Time> {
let observed = self.observed(date - obs_lag)?;
let Some(day_counter) = self.term.day_counter() else {
crate::fail!("day counter not provided for this volatility surface");
};
Ok(day_counter.year_fraction(self.base_date()?, observed))
}
pub fn check_range(
&self,
date: Date,
strike: Rate,
min_strike: Rate,
max_strike: Rate,
max_date: Date,
) -> QlResult<()> {
let base_date = self.base_date()?;
require!(
date >= base_date,
"date ({date}) is before base date ({base_date})"
);
require!(
self.term.allows_extrapolation() || date <= max_date,
"date ({date}) is past max curve date ({max_date})"
);
require!(
self.term.allows_extrapolation() || (strike >= min_strike && strike <= max_strike),
"strike ({strike}) is outside the curve domain [{min_strike},{max_strike}] at date = \
{date}"
);
Ok(())
}
}