use crate::cashflows::IborLeg;
use crate::errors::QlResult;
use crate::indexes::{IborIndex, InterestRateIndex};
use crate::instrument::{Instrument, InstrumentBase};
use crate::instruments::Swap;
use crate::pricingengine::{Arguments, Results};
use crate::settings::Settings;
use crate::shared::Shared;
use crate::time::{
businessdayconvention::BusinessDayConvention, date::Date, daycounter::DayCounter,
schedule::Schedule, timeunit::TimeUnit,
};
use crate::types::{Real, Spread};
pub struct FloatFloatSwap {
swap: Swap,
spreads: [Spread; 2],
settings: Shared<Settings<Date>>,
settings_flags: (bool, Option<bool>, bool, bool),
}
impl FloatFloatSwap {
#[allow(clippy::too_many_arguments)]
pub fn new(
nominal: Real,
schedule1: Schedule,
index1: Shared<IborIndex>,
spread1: Spread,
day_count1: DayCounter,
schedule2: Schedule,
index2: Shared<IborIndex>,
spread2: Spread,
day_count2: DayCounter,
payment_convention: Option<BusinessDayConvention>,
settings: Shared<Settings<Date>>,
) -> QlResult<Self> {
crate::require!(
nominal.is_finite() && nominal > 0.0,
"nominal must be finite and positive"
);
crate::require!(
spread1.is_finite() && spread2.is_finite(),
"spreads must be finite"
);
crate::require!(
index1.currency() == index2.currency(),
"indexes must have the same currency"
);
crate::require!(
!index1.currency().code().trim().is_empty()
&& !index1.currency().name().trim().is_empty(),
"currency must not be blank"
);
for index in [&index1, &index2] {
crate::require!(
Shared::ptr_eq(index.base().settings(), &settings),
"indexes must share swap settings"
);
crate::require!(
index.tenor().units() != TimeUnit::Days,
"day-tenor and overnight indexes are not supported"
);
}
for schedule in [&schedule1, &schedule2] {
crate::require!(
schedule.len() >= 2,
"schedule must contain at least two dates"
);
crate::require!(
schedule
.dates()
.iter()
.all(|date| *date >= Date::min_date() && *date <= Date::max_date()),
"schedule dates must be valid and non-null"
);
crate::require!(
schedule.dates().windows(2).all(|pair| pair[0] < pair[1]),
"schedule dates must increase"
);
}
let build = |schedule: Schedule, index, spread, day_count| {
let convention =
payment_convention.unwrap_or_else(|| schedule.business_day_convention());
IborLeg::new(schedule, index)
.with_notional(nominal)
.with_spread(spread)
.with_payment_day_counter(day_count)
.with_payment_adjustment(convention)
.build()
};
let first = build(schedule1, index1, spread1, day_count1)?;
let second = build(schedule2, index2, spread2, day_count2)?;
let swap = Swap::new(vec![first, second], vec![true, false], settings.clone())?;
Ok(Self {
swap,
spreads: [spread1, spread2],
settings_flags: Self::flags(&settings),
settings,
})
}
fn flags(settings: &Settings<Date>) -> (bool, Option<bool>, bool, bool) {
(
settings.include_reference_date_events(),
settings.include_todays_cash_flows(),
settings.enforces_todays_historic_fixings(),
settings.using_at_par_coupons(),
)
}
pub fn swap(&self) -> &Swap {
&self.swap
}
fn leg_measure(&mut self, leg: usize, bps: bool) -> QlResult<Real> {
self.calculate()?;
let value = if bps {
self.swap.leg_bps(leg)?
} else {
self.swap.leg_npv(leg)?
};
crate::require!(value.is_finite(), "leg result must be finite");
Ok(value)
}
pub fn pay_leg_npv(&mut self) -> QlResult<Real> {
self.leg_measure(0, false)
}
pub fn receive_leg_npv(&mut self) -> QlResult<Real> {
self.leg_measure(1, false)
}
pub fn pay_leg_bps(&mut self) -> QlResult<Real> {
self.leg_measure(0, true)
}
pub fn receive_leg_bps(&mut self) -> QlResult<Real> {
self.leg_measure(1, true)
}
fn fair_spread(&mut self, leg: usize) -> QlResult<Spread> {
let npv = self.npv()?;
let annuity = self.swap.leg_bps(leg)? / 1e-4;
crate::require!(
annuity.is_finite() && annuity != 0.0,
"null or non-finite spread sensitivity"
);
let fair = self.spreads[leg] - npv / annuity;
crate::require!(fair.is_finite(), "fair spread must be finite");
Ok(fair)
}
pub fn fair_pay_spread(&mut self) -> QlResult<Spread> {
self.fair_spread(0)
}
pub fn fair_receive_spread(&mut self) -> QlResult<Spread> {
self.fair_spread(1)
}
}
impl Instrument for FloatFloatSwap {
fn base(&self) -> &InstrumentBase {
self.swap.base()
}
fn base_mut(&mut self) -> &mut InstrumentBase {
self.swap.base_mut()
}
fn is_expired(&self) -> QlResult<bool> {
self.swap.is_expired()
}
fn setup_expired(&mut self) {
self.swap.setup_expired();
}
fn setup_arguments(&self, arguments: &mut dyn Arguments) -> QlResult<()> {
self.swap.setup_arguments(arguments)
}
fn fetch_results(&mut self, results: &dyn Results) -> QlResult<()> {
self.swap.fetch_results(results)
}
fn calculate(&mut self) -> QlResult<()> {
let date = self.settings.evaluation_date().ok_or_else(|| {
crate::errors::QlError::new("evaluation date is unset", file!(), line!())
})?;
crate::require!(
date >= Date::min_date() && date <= Date::max_date(),
"evaluation date must be valid and non-null"
);
let flags = Self::flags(&self.settings);
if flags != self.settings_flags {
self.swap.base().invalidate_silently();
self.settings_flags = flags;
}
self.swap.calculate()?;
crate::require!(self.swap.npv()?.is_finite(), "NPV must be finite");
Ok(())
}
}