use crate::errors::QlResult;
use crate::option::OptionType;
use crate::pricingengines::blackformula::{bachelier_black_formula, black_formula};
use crate::termstructures::volatility::VolatilityType;
use crate::time::date::Date;
use crate::time::daycounter::DayCounter;
use crate::types::{Rate, Real, Time, Volatility};
use crate::{fail, require};
#[derive(Clone, Debug)]
pub struct SmileSectionBase {
reference_date: Option<Date>,
day_counter: DayCounter,
exercise_time: Time,
volatility_type: VolatilityType,
shift: Rate,
}
impl SmileSectionBase {
pub fn with_reference_date(
exercise_date: Date,
day_counter: DayCounter,
reference_date: Date,
volatility_type: VolatilityType,
shift: Rate,
) -> QlResult<SmileSectionBase> {
require!(
reference_date != Date::null(),
"a null reference date selects QuantLib's floating smile section, which tracks the \
evaluation date through the observer graph; that path is deferred to #586"
);
require!(
exercise_date >= reference_date,
"exercise date ({exercise_date}) must not precede the reference date \
({reference_date})"
);
let exercise_time = day_counter.year_fraction(reference_date, exercise_date);
Ok(SmileSectionBase {
reference_date: Some(reference_date),
day_counter,
exercise_time,
volatility_type,
shift,
})
}
pub fn with_exercise_time(
exercise_time: Time,
day_counter: DayCounter,
volatility_type: VolatilityType,
shift: Rate,
) -> QlResult<SmileSectionBase> {
if exercise_time < 0.0 {
fail!("exercise time ({exercise_time}) must be non-negative");
}
Ok(SmileSectionBase {
reference_date: None,
day_counter,
exercise_time,
volatility_type,
shift,
})
}
}
pub trait SmileSection {
fn base(&self) -> &SmileSectionBase;
fn volatility_impl(&self, strike: Rate) -> QlResult<Volatility>;
fn min_strike(&self) -> Rate;
fn max_strike(&self) -> Rate;
fn atm_level(&self) -> Option<Rate>;
fn volatility(&self, strike: Rate) -> QlResult<Volatility> {
self.volatility_impl(strike)
}
fn variance(&self, strike: Rate) -> QlResult<Real> {
let vol = self.volatility_impl(strike)?;
Ok(vol * vol * self.exercise_time())
}
fn exercise_time(&self) -> Time {
self.base().exercise_time
}
fn day_counter(&self) -> DayCounter {
self.base().day_counter.clone()
}
fn volatility_type(&self) -> VolatilityType {
self.base().volatility_type
}
fn shift(&self) -> Rate {
self.base().shift
}
fn reference_date(&self) -> QlResult<Date> {
match self.base().reference_date {
Some(date) => Ok(date),
None => fail!("reference date not available for this instance"),
}
}
fn option_price(
&self,
strike: Rate,
option_type: OptionType,
discount: Real,
) -> QlResult<Real> {
let Some(atm) = self.atm_level() else {
fail!("smile section must provide an atm level to compute an option price");
};
match self.volatility_type() {
VolatilityType::ShiftedLognormal => {
let shift = self.shift();
let std_dev = if (strike + shift).abs() < Real::EPSILON {
0.2
} else {
self.variance(strike)?.sqrt()
};
black_formula(option_type, strike, atm, std_dev, discount, shift)
}
VolatilityType::Normal => {
let std_dev = self.variance(strike)?.sqrt();
bachelier_black_formula(option_type, strike, atm, std_dev, discount)
}
}
}
}