use crate::{
ad::{dual::DualFwd, scalar::Scalar},
indices::marketindex::MarketIndex,
instruments::cashflows::{coupons::NonLinearCoupon, payoffops::PayoffOps},
time::date::Date,
utils::errors::{QSError, Result},
};
#[derive(Clone)]
pub struct OptionEmbeddedCoupon<T: Scalar> {
notional: f64,
fixing: Option<T>,
spread: T,
index: MarketIndex,
accrual_start_date: Date,
accrual_end_date: Date,
payment_date: Date,
payoff: PayoffOps,
}
impl<T: Scalar> OptionEmbeddedCoupon<T> {
#[must_use]
pub const fn new(
notional: f64,
index: MarketIndex,
spread: T,
accrual_start_date: Date,
accrual_end_date: Date,
payment_date: Date,
payoff: PayoffOps,
) -> Self {
Self {
notional,
fixing: None,
spread,
index,
accrual_start_date,
accrual_end_date,
payment_date,
payoff,
}
}
#[must_use]
pub const fn with_fixing(mut self, fixing: T) -> Self {
self.fixing = Some(fixing);
self
}
pub const fn spread(&self) -> T {
self.spread
}
pub const fn fixing(&self) -> Option<T> {
self.fixing
}
pub const fn market_index(&self) -> &MarketIndex {
&self.index
}
pub const fn payoff_ops(&self) -> &PayoffOps {
&self.payoff
}
}
impl OptionEmbeddedCoupon<f64> {
pub fn amount(&self) -> Result<f64> {
self.accrued_amount(self.accrual_start_date, self.accrual_end_date)
}
}
impl OptionEmbeddedCoupon<DualFwd> {
pub fn amount(&self) -> Result<DualFwd> {
self.accrued_amount(self.accrual_start_date, self.accrual_end_date)
}
}
impl NonLinearCoupon<f64> for OptionEmbeddedCoupon<f64> {
fn accrual_end_date(&self) -> Date {
self.accrual_end_date
}
fn accrual_start_date(&self) -> Date {
self.accrual_start_date
}
fn accrued_amount(&self, start_date: Date, end_date: Date) -> Result<f64> {
let fixing = self
.fixing
.ok_or_else(|| QSError::NotFoundErr("Fixing not set".into()))?;
let year_fraction = self
.index
.rate_index_details()?
.rate_definition()
.day_counter()
.year_fraction(start_date, end_date);
let resuling_rate = self.payoff.eval(fixing)?;
let coupon_rate = self.spread + resuling_rate;
Ok(coupon_rate * (year_fraction * self.notional))
}
fn notional(&self) -> f64 {
self.notional
}
fn payoff_description(&self) -> PayoffOps {
self.payoff.clone()
}
fn payment_date(&self) -> Date {
self.payment_date
}
}
impl NonLinearCoupon<DualFwd> for OptionEmbeddedCoupon<DualFwd> {
fn accrual_end_date(&self) -> Date {
self.accrual_end_date
}
fn accrual_start_date(&self) -> Date {
self.accrual_start_date
}
fn accrued_amount(&self, start_date: Date, end_date: Date) -> Result<DualFwd> {
let fixing = self
.fixing
.ok_or_else(|| QSError::NotFoundErr("Fixing not set".into()))?;
let year_fraction = self
.index
.rate_index_details()?
.rate_definition()
.day_counter()
.year_fraction(start_date, end_date);
let resuling_rate = self.payoff.eval(fixing)?;
let coupon_rate: DualFwd = (self.spread + resuling_rate).into();
Ok((coupon_rate * DualFwd::new(year_fraction * self.notional)).into())
}
fn notional(&self) -> f64 {
self.notional
}
fn payoff_description(&self) -> PayoffOps {
self.payoff.clone()
}
fn payment_date(&self) -> Date {
self.payment_date
}
}
impl From<OptionEmbeddedCoupon<f64>> for OptionEmbeddedCoupon<DualFwd> {
fn from(value: OptionEmbeddedCoupon<f64>) -> Self {
Self {
notional: value.notional,
fixing: value.fixing.map(DualFwd::new),
spread: DualFwd::new(value.spread.value()),
index: value.index,
accrual_start_date: value.accrual_start_date,
accrual_end_date: value.accrual_end_date,
payment_date: value.payment_date,
payoff: value.payoff,
}
}
}
impl From<OptionEmbeddedCoupon<DualFwd>> for OptionEmbeddedCoupon<f64> {
fn from(value: OptionEmbeddedCoupon<DualFwd>) -> Self {
Self {
notional: value.notional,
fixing: value.fixing.map(|fixing| fixing.value()),
spread: value.spread.value(),
index: value.index,
accrual_start_date: value.accrual_start_date,
accrual_end_date: value.accrual_end_date,
payment_date: value.payment_date,
payoff: value.payoff,
}
}
}