use chrono::NaiveDate;
use super::caplet::caplet_price;
use super::types::CapFloorValuation;
use super::types::CollarValuation;
use super::types::InterestRateOptionKind;
use super::volatility::VolatilityModel;
use crate::calendar::DayCountConvention;
use crate::cashflows::Cashflow;
use crate::cashflows::CurveProvider;
use crate::cashflows::Leg;
use crate::cashflows::RateIndex;
use crate::traits::FloatExt;
#[derive(Debug, Clone)]
pub struct Cap<T: FloatExt, V: VolatilityModel<T>> {
pub strike: T,
pub leg: Leg<T>,
pub vol: V,
}
impl<T: FloatExt, V: VolatilityModel<T>> Cap<T, V> {
pub fn new(strike: T, leg: Leg<T>, vol: V) -> Self {
Self { strike, leg, vol }
}
pub fn valuation(
&self,
valuation_date: NaiveDate,
expiry_day_count: DayCountConvention,
discount_day_count: DayCountConvention,
curves: &(impl CurveProvider<T> + ?Sized),
) -> CapFloorValuation<T> {
price_cap_floor(
&self.leg,
self.strike,
&self.vol,
InterestRateOptionKind::Cap,
valuation_date,
expiry_day_count,
discount_day_count,
curves,
)
}
pub fn npv(
&self,
valuation_date: NaiveDate,
expiry_day_count: DayCountConvention,
discount_day_count: DayCountConvention,
curves: &(impl CurveProvider<T> + ?Sized),
) -> T {
self
.valuation(valuation_date, expiry_day_count, discount_day_count, curves)
.npv
}
}
#[derive(Debug, Clone)]
pub struct Floor<T: FloatExt, V: VolatilityModel<T>> {
pub strike: T,
pub leg: Leg<T>,
pub vol: V,
}
impl<T: FloatExt, V: VolatilityModel<T>> Floor<T, V> {
pub fn new(strike: T, leg: Leg<T>, vol: V) -> Self {
Self { strike, leg, vol }
}
pub fn valuation(
&self,
valuation_date: NaiveDate,
expiry_day_count: DayCountConvention,
discount_day_count: DayCountConvention,
curves: &(impl CurveProvider<T> + ?Sized),
) -> CapFloorValuation<T> {
price_cap_floor(
&self.leg,
self.strike,
&self.vol,
InterestRateOptionKind::Floor,
valuation_date,
expiry_day_count,
discount_day_count,
curves,
)
}
pub fn npv(
&self,
valuation_date: NaiveDate,
expiry_day_count: DayCountConvention,
discount_day_count: DayCountConvention,
curves: &(impl CurveProvider<T> + ?Sized),
) -> T {
self
.valuation(valuation_date, expiry_day_count, discount_day_count, curves)
.npv
}
}
#[derive(Debug, Clone)]
pub struct Collar<T: FloatExt, VC: VolatilityModel<T>, VF: VolatilityModel<T>> {
pub cap: Cap<T, VC>,
pub floor: Floor<T, VF>,
}
impl<T: FloatExt, VC: VolatilityModel<T>, VF: VolatilityModel<T>> Collar<T, VC, VF> {
pub fn new(cap: Cap<T, VC>, floor: Floor<T, VF>) -> Self {
Self { cap, floor }
}
pub fn valuation(
&self,
valuation_date: NaiveDate,
expiry_day_count: DayCountConvention,
discount_day_count: DayCountConvention,
curves: &(impl CurveProvider<T> + ?Sized),
) -> CollarValuation<T> {
let cap = self
.cap
.valuation(valuation_date, expiry_day_count, discount_day_count, curves);
let floor = self
.floor
.valuation(valuation_date, expiry_day_count, discount_day_count, curves);
let npv = cap.npv - floor.npv;
CollarValuation { npv, cap, floor }
}
pub fn npv(
&self,
valuation_date: NaiveDate,
expiry_day_count: DayCountConvention,
discount_day_count: DayCountConvention,
curves: &(impl CurveProvider<T> + ?Sized),
) -> T {
self
.valuation(valuation_date, expiry_day_count, discount_day_count, curves)
.npv
}
}
#[allow(clippy::too_many_arguments)]
fn price_cap_floor<T: FloatExt, V: VolatilityModel<T> + ?Sized>(
leg: &Leg<T>,
strike: T,
vol: &V,
kind: InterestRateOptionKind,
valuation_date: NaiveDate,
expiry_day_count: DayCountConvention,
discount_day_count: DayCountConvention,
curves: &(impl CurveProvider<T> + ?Sized),
) -> CapFloorValuation<T> {
let mut total_npv = T::zero();
let mut total_annuity = T::zero();
let mut caplet_prices = Vec::with_capacity(leg.cashflows().len());
let mut forward_rates = Vec::with_capacity(leg.cashflows().len());
let mut accrual_factors = Vec::with_capacity(leg.cashflows().len());
for cashflow in leg.cashflows() {
let Cashflow::Floating(coupon) = cashflow else {
continue;
};
if coupon.period.payment_date < valuation_date {
continue;
}
let tau = expiry_day_count.year_fraction(valuation_date, coupon.period.accrual_start);
let tau_payment = discount_day_count.year_fraction(valuation_date, coupon.period.payment_date);
let discount_factor = curves.discount_curve().discount_factor(tau_payment);
let forward = coupon.observed_rate.unwrap_or_else(|| {
coupon
.index
.forward_rate(curves, valuation_date, &coupon.period)
}) + coupon.spread;
let annuity = discount_factor * coupon.notional * coupon.period.accrual_factor;
total_annuity += annuity;
let caplet = if tau <= T::zero() || coupon.observed_rate.is_some() {
let payoff = match kind {
InterestRateOptionKind::Cap => (forward - strike).max(T::zero()),
InterestRateOptionKind::Floor => (strike - forward).max(T::zero()),
};
coupon.notional * coupon.period.accrual_factor * discount_factor * payoff
} else {
caplet_price(
forward,
strike,
tau,
coupon.notional,
coupon.period.accrual_factor,
discount_factor,
vol,
kind,
)
};
total_npv += caplet;
caplet_prices.push(caplet);
forward_rates.push(forward);
accrual_factors.push(coupon.period.accrual_factor);
}
CapFloorValuation {
npv: total_npv,
annuity: total_annuity,
caplet_prices,
forward_rates,
accrual_factors,
}
}