use chrono::NaiveDate;
use super::shared::floating_leg_spread_annuity;
use super::types::CrossCurrencyBasisSwapValuation;
use super::types::CrossCurrencySwapDirection;
use crate::calendar::DayCountConvention;
use crate::calendar::Schedule;
use crate::cashflows::CashflowPricer;
use crate::cashflows::CurveProvider;
use crate::cashflows::FloatingIndex;
use crate::cashflows::Leg;
use crate::cashflows::NotionalSchedule;
use crate::fx::Currency;
use crate::traits::FloatExt;
#[derive(Debug, Clone)]
pub struct CrossCurrencyBasisSwap<T: FloatExt> {
pub direction: CrossCurrencySwapDirection,
pub domestic_currency: Currency,
pub foreign_currency: Currency,
pub fx_spot_domestic_per_foreign: T,
pub domestic_spread: T,
pub foreign_spread: T,
pub domestic_leg: Leg<T>,
pub foreign_leg: Leg<T>,
}
impl<T: FloatExt> CrossCurrencyBasisSwap<T> {
pub fn new(
direction: CrossCurrencySwapDirection,
domestic_currency: Currency,
foreign_currency: Currency,
fx_spot_domestic_per_foreign: T,
domestic_schedule: &Schedule,
foreign_schedule: &Schedule,
domestic_notional: T,
domestic_index: FloatingIndex<T>,
domestic_spread: T,
domestic_day_count: DayCountConvention,
foreign_notional: T,
foreign_index: FloatingIndex<T>,
foreign_spread: T,
foreign_day_count: DayCountConvention,
exchange_final_notionals: bool,
) -> Self {
assert!(
domestic_schedule.adjusted_dates.len() >= 2,
"domestic schedule must contain at least two dates"
);
assert!(
foreign_schedule.adjusted_dates.len() >= 2,
"foreign schedule must contain at least two dates"
);
let domestic_maturity = *domestic_schedule.adjusted_dates.last().unwrap();
let foreign_maturity = *foreign_schedule.adjusted_dates.last().unwrap();
let mut domestic_leg = Leg::floating_rate(
domestic_schedule,
NotionalSchedule::bullet(
domestic_schedule.adjusted_dates.len() - 1,
domestic_notional,
),
domestic_index,
domestic_spread,
domestic_day_count,
);
let mut foreign_leg = Leg::floating_rate(
foreign_schedule,
NotionalSchedule::bullet(foreign_schedule.adjusted_dates.len() - 1, foreign_notional),
foreign_index,
foreign_spread,
foreign_day_count,
);
if exchange_final_notionals {
domestic_leg = domestic_leg.with_redemption(domestic_maturity, domestic_notional);
foreign_leg = foreign_leg.with_redemption(foreign_maturity, foreign_notional);
}
Self {
direction,
domestic_currency,
foreign_currency,
fx_spot_domestic_per_foreign,
domestic_spread,
foreign_spread,
domestic_leg,
foreign_leg,
}
}
pub fn valuation(
&self,
valuation_date: NaiveDate,
domestic_discount_day_count: DayCountConvention,
domestic_curves: &(impl CurveProvider<T> + ?Sized),
foreign_discount_day_count: DayCountConvention,
foreign_curves: &(impl CurveProvider<T> + ?Sized),
) -> CrossCurrencyBasisSwapValuation<T> {
let domestic_pricer = CashflowPricer::new(valuation_date, domestic_discount_day_count);
let foreign_pricer = CashflowPricer::new(valuation_date, foreign_discount_day_count);
let domestic_leg_npv = domestic_pricer.leg_npv(&self.domestic_leg, domestic_curves);
let foreign_leg_npv_foreign = foreign_pricer.leg_npv(&self.foreign_leg, foreign_curves);
let foreign_leg_npv_domestic = self.fx_spot_domestic_per_foreign * foreign_leg_npv_foreign;
let domestic_leg_annuity = floating_leg_spread_annuity(
&self.domestic_leg,
valuation_date,
domestic_discount_day_count,
domestic_curves,
);
let foreign_leg_annuity_foreign = floating_leg_spread_annuity(
&self.foreign_leg,
valuation_date,
foreign_discount_day_count,
foreign_curves,
);
let foreign_leg_annuity_domestic =
self.fx_spot_domestic_per_foreign * foreign_leg_annuity_foreign;
let net_npv = match self.direction {
CrossCurrencySwapDirection::PayDomesticReceiveForeign => {
foreign_leg_npv_domestic - domestic_leg_npv
}
CrossCurrencySwapDirection::ReceiveDomesticPayForeign => {
domestic_leg_npv - foreign_leg_npv_domestic
}
};
let fair_domestic_spread = if domestic_leg_annuity > T::zero() {
match self.direction {
CrossCurrencySwapDirection::PayDomesticReceiveForeign => {
self.domestic_spread + net_npv / domestic_leg_annuity
}
CrossCurrencySwapDirection::ReceiveDomesticPayForeign => {
self.domestic_spread - net_npv / domestic_leg_annuity
}
}
} else {
self.domestic_spread
};
let fair_foreign_spread = if foreign_leg_annuity_domestic > T::zero() {
match self.direction {
CrossCurrencySwapDirection::PayDomesticReceiveForeign => {
self.foreign_spread - net_npv / foreign_leg_annuity_domestic
}
CrossCurrencySwapDirection::ReceiveDomesticPayForeign => {
self.foreign_spread + net_npv / foreign_leg_annuity_domestic
}
}
} else {
self.foreign_spread
};
CrossCurrencyBasisSwapValuation {
domestic_leg_npv,
foreign_leg_npv_foreign,
foreign_leg_npv_domestic,
net_npv,
domestic_leg_bpv: domestic_leg_annuity * T::from_f64_fast(1e-4),
foreign_leg_bpv_domestic: foreign_leg_annuity_domestic * T::from_f64_fast(1e-4),
fair_domestic_spread,
fair_foreign_spread,
}
}
pub fn npv(
&self,
valuation_date: NaiveDate,
domestic_discount_day_count: DayCountConvention,
domestic_curves: &(impl CurveProvider<T> + ?Sized),
foreign_discount_day_count: DayCountConvention,
foreign_curves: &(impl CurveProvider<T> + ?Sized),
) -> T {
self
.valuation(
valuation_date,
domestic_discount_day_count,
domestic_curves,
foreign_discount_day_count,
foreign_curves,
)
.net_npv
}
}