use crate::cashflows::CashFlows;
use crate::errors::QlResult;
use crate::instruments::{SwapArguments, SwapEngine, SwapResults};
use crate::patterns::observable::{AsObservable, Observable};
use crate::pricingengine::{Arguments, PricingEngine, Results};
use crate::settings::Settings;
use crate::shared::Shared;
use crate::termstructures::yieldtermstructure::YieldTermStructure;
use crate::time::date::Date;
use crate::types::DiscountFactor;
use crate::utilities::null::Null;
use crate::{fail, handle::Handle, require};
pub struct DiscountingSwapEngine {
base: SwapEngine,
discount_curve: Handle<dyn YieldTermStructure>,
include_settlement_date_flows: Option<bool>,
settlement_date: Option<Date>,
npv_date: Option<Date>,
settings: Shared<Settings<Date>>,
}
impl DiscountingSwapEngine {
pub fn new(
discount_curve: Handle<dyn YieldTermStructure>,
include_settlement_date_flows: Option<bool>,
settlement_date: Option<Date>,
npv_date: Option<Date>,
settings: Shared<Settings<Date>>,
) -> DiscountingSwapEngine {
let base = SwapEngine::new(SwapArguments::default(), SwapResults::default());
discount_curve.register_observer(&base.observer());
DiscountingSwapEngine {
base,
discount_curve,
include_settlement_date_flows,
settlement_date,
npv_date,
settings,
}
}
pub fn discount_curve(&self) -> &Handle<dyn YieldTermStructure> {
&self.discount_curve
}
}
impl AsObservable for DiscountingSwapEngine {
fn observable(&self) -> &Observable {
self.base.observable()
}
}
impl PricingEngine for DiscountingSwapEngine {
fn arguments_mut(&mut self) -> &mut dyn Arguments {
self.base.arguments_mut()
}
fn results(&self) -> &dyn Results {
self.base.results()
}
fn reset(&mut self) {
self.base.reset();
}
fn calculate(&mut self) -> QlResult<()> {
require!(
!self.discount_curve.is_empty(),
"discounting term structure handle is empty"
);
let curve = self.discount_curve.current_link()?;
let ref_date = curve.reference_date()?;
let settlement_date = match self.settlement_date {
None => ref_date,
Some(date) => {
require!(
date >= ref_date,
"settlement date ({date}) before discount curve reference date ({ref_date})"
);
date
}
};
let valuation_date = match self.npv_date {
None => ref_date,
Some(date) => {
require!(
date >= ref_date,
"npv date ({date}) before discount curve reference date ({ref_date})"
);
date
}
};
let npv_date_discount = curve.discount_date(valuation_date, false)?;
let include_ref_date_flows = self
.include_settlement_date_flows
.unwrap_or_else(|| self.settings.include_reference_date_events());
let arguments = self.base.arguments();
let n = arguments.legs.len();
let mut leg_npv = Vec::with_capacity(n);
let mut leg_bps = Vec::with_capacity(n);
let mut start_discounts = Vec::with_capacity(n);
let mut end_discounts = Vec::with_capacity(n);
let mut value = 0.0;
for (i, leg) in arguments.legs.iter().enumerate() {
let (npv, bps) = match CashFlows::npvbps(
leg,
&*curve,
&self.settings,
Some(include_ref_date_flows),
Some(settlement_date),
Some(valuation_date),
) {
Ok(pair) => pair,
Err(e) => fail!("leg #{}: {}", i + 1, e.message()),
};
let npv = npv * arguments.payer[i];
let bps = bps * arguments.payer[i];
if leg.is_empty() {
start_discounts.push(DiscountFactor::null());
end_discounts.push(DiscountFactor::null());
} else {
let d1 = CashFlows::start_date(leg)?;
start_discounts.push(if d1 >= ref_date {
curve.discount_date(d1, false)?
} else {
DiscountFactor::null()
});
let d2 = CashFlows::maturity_date(leg)?;
end_discounts.push(if d2 >= ref_date {
curve.discount_date(d2, false)?
} else {
DiscountFactor::null()
});
}
leg_npv.push(npv);
leg_bps.push(bps);
value += npv;
}
let results = self.base.results_mut();
results.instrument.value = Some(value);
results.instrument.error_estimate = None;
results.instrument.valuation_date = Some(valuation_date);
results.npv_date_discount = Some(npv_date_discount);
results.leg_npv = leg_npv;
results.leg_bps = leg_bps;
results.start_discounts = start_discounts;
results.end_discounts = end_discounts;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::handle::RelinkableHandle;
use crate::indexes::IborIndex;
use crate::indexes::ibor::Euribor;
use crate::instrument::Instrument;
use crate::instruments::{FixedVsFloatingSwap, SwapType, VanillaSwap};
use crate::interestrate::Compounding;
use crate::shared::{SharedMut, shared, shared_mut};
use crate::termstructures::yields::FlatForward;
use crate::time::businessdayconvention::BusinessDayConvention;
use crate::time::calendar::Calendar;
use crate::time::calendars::target::Target;
use crate::time::date::Month;
use crate::time::daycounters::actual360::Actual360;
use crate::time::daycounters::actual365fixed::Actual365Fixed;
use crate::time::daycounters::thirty360::{Convention, Thirty360};
use crate::time::frequency::Frequency;
use crate::time::schedule::MakeSchedule;
use crate::time::timeunit::TimeUnit;
use crate::types::{Integer, Rate, Real, Spread};
const NOMINAL: Real = 100.0;
struct Vars {
settings: Shared<Settings<Date>>,
calendar: Calendar,
settlement: Date,
curve: RelinkableHandle<dyn YieldTermStructure>,
index: Shared<IborIndex>,
nominal: Real,
}
fn flat(reference: Date, rate: Rate) -> Shared<dyn YieldTermStructure> {
shared(FlatForward::with_rate(
reference,
rate,
Actual365Fixed::new(),
Compounding::Continuous,
Frequency::Annual,
))
}
impl Vars {
fn new(today: Date, using_at_par: bool) -> Vars {
let settings = shared(Settings::new());
settings.set_evaluation_date(today);
settings.set_using_at_par_coupons(using_at_par);
let calendar = Target::new();
let settlement = calendar.advance(
today,
2,
TimeUnit::Days,
BusinessDayConvention::Following,
false,
);
let curve = RelinkableHandle::new(flat(settlement, 0.05));
let index = shared(Euribor::six_months(
curve.handle(),
Shared::clone(&settings),
));
Vars {
settings,
calendar,
settlement,
curve,
index,
nominal: NOMINAL,
}
}
fn make_swap(&self, length: Integer, fixed_rate: Rate, spread: Spread) -> VanillaSwap {
let maturity = self.calendar.advance(
self.settlement,
length,
TimeUnit::Years,
BusinessDayConvention::ModifiedFollowing,
false,
);
let fixed_schedule = MakeSchedule::new()
.from(self.settlement)
.to(maturity)
.with_frequency(Frequency::Annual)
.with_calendar(self.calendar.clone())
.with_convention(BusinessDayConvention::Unadjusted)
.with_termination_date_convention(BusinessDayConvention::Unadjusted)
.forwards()
.end_of_month(false)
.build();
let float_schedule = MakeSchedule::new()
.from(self.settlement)
.to(maturity)
.with_frequency(Frequency::Semiannual)
.with_calendar(self.calendar.clone())
.with_convention(BusinessDayConvention::ModifiedFollowing)
.with_termination_date_convention(BusinessDayConvention::ModifiedFollowing)
.forwards()
.end_of_month(false)
.build();
let mut swap = VanillaSwap::new(
SwapType::Payer,
self.nominal,
fixed_schedule,
fixed_rate,
Thirty360::with_convention(Convention::BondBasis),
float_schedule,
Shared::clone(&self.index),
spread,
Actual360::new(),
None,
Shared::clone(&self.settings),
)
.unwrap();
let engine = shared_mut(DiscountingSwapEngine::new(
self.curve.handle(),
None,
None,
None,
Shared::clone(&self.settings),
));
swap.base_mut()
.set_pricing_engine(engine as SharedMut<dyn PricingEngine>);
swap
}
}
#[test]
fn cached_value_reproduces_the_par_and_indexed_arms() {
for (using_at_par, expected) in [(true, -5.872863313209), (false, -5.872342992212)] {
let vars = Vars::new(Date::new(17, Month::June, 2002), using_at_par);
let mut swap = vars.make_swap(10, 0.06, 0.001);
let npv = swap.npv().unwrap();
assert!(
(npv - expected).abs() <= 1.0e-11,
"par={using_at_par}: npv {npv} vs cached {expected} (error {})",
(npv - expected).abs()
);
}
}
#[test]
fn a_swap_rebuilt_at_its_fair_rate_prices_to_zero() {
let vars = Vars::new(Date::new(17, Month::June, 2002), true);
let lengths: [Integer; 5] = [1, 2, 5, 10, 20];
let spreads: [Spread; 5] = [-0.001, -0.01, 0.0, 0.01, 0.001];
for length in lengths {
for spread in spreads {
let fair = vars
.make_swap(length, 0.0, spread)
.fixed_vs_floating_mut()
.fair_rate()
.unwrap();
let npv = vars.make_swap(length, fair, spread).npv().unwrap();
assert!(
npv.abs() <= 1.0e-10,
"length {length}y spread {spread}: npv {npv} not zero"
);
}
}
}
#[test]
fn a_swap_rebuilt_at_its_fair_spread_prices_to_zero() {
let vars = Vars::new(Date::new(17, Month::June, 2002), true);
let lengths: [Integer; 5] = [1, 2, 5, 10, 20];
let rates: [Rate; 4] = [0.04, 0.05, 0.06, 0.07];
for length in lengths {
for rate in rates {
let fair = vars
.make_swap(length, rate, 0.0)
.fixed_vs_floating_mut()
.fair_spread()
.unwrap();
let npv = vars.make_swap(length, rate, fair).npv().unwrap();
assert!(
npv.abs() <= 1.0e-10,
"length {length}y rate {rate}: npv {npv} not zero"
);
}
}
}
#[test]
fn a_zero_nominal_swap_has_no_fair_values() {
let mut vars = Vars::new(Date::new(17, Month::June, 2002), true);
vars.nominal = 0.0;
let mut swap = vars.make_swap(10, 0.0, 0.0);
let base = swap.fixed_vs_floating_mut();
assert!(!base.is_expired().unwrap());
assert_eq!(base.fixed_leg_bps().unwrap(), 0.0);
assert_eq!(base.floating_leg_bps().unwrap(), 0.0);
assert_eq!(
base.fair_rate().unwrap_err().message(),
"result not available"
);
assert_eq!(
base.fair_spread().unwrap_err().message(),
"result not available"
);
}
fn readings(swap: &mut VanillaSwap) -> [Real; 6] {
let base = swap.fixed_vs_floating_mut();
[
base.fair_rate().unwrap(),
base.fair_spread().unwrap(),
base.fixed_leg_bps().unwrap(),
base.fixed_leg_npv().unwrap(),
base.floating_leg_bps().unwrap(),
base.floating_leg_npv().unwrap(),
]
}
#[test]
fn a_leg_read_first_gives_the_natural_order_numbers() {
let vars = Vars::new(Date::new(17, Month::June, 2002), true);
let expected = readings(&mut vars.make_swap(10, 0.06, 0.001));
let firsts: [fn(&mut FixedVsFloatingSwap) -> QlResult<Real>; 4] = [
FixedVsFloatingSwap::fixed_leg_bps,
FixedVsFloatingSwap::fixed_leg_npv,
FixedVsFloatingSwap::floating_leg_bps,
FixedVsFloatingSwap::floating_leg_npv,
];
for first in firsts {
let mut swap = vars.make_swap(10, 0.06, 0.001);
first(swap.fixed_vs_floating_mut()).unwrap();
assert_eq!(readings(&mut swap), expected);
}
}
#[test]
fn a_leg_read_after_a_relink_refreshes_the_fair_values() {
let vars = Vars::new(Date::new(17, Month::June, 2002), true);
let mut swap = vars.make_swap(10, 0.06, 0.001);
let before = readings(&mut swap);
vars.curve.link_to(flat(vars.settlement, 0.07));
swap.fixed_vs_floating_mut().fixed_leg_bps().unwrap();
let after = readings(&mut swap);
assert_ne!(after[0], before[0]);
assert_ne!(after[1], before[1]);
assert_eq!(after, readings(&mut vars.make_swap(10, 0.06, 0.001)));
}
#[test]
fn an_empty_discount_curve_is_rejected() {
let settings = shared(Settings::<Date>::new());
settings.set_evaluation_date(Date::new(17, Month::June, 2002));
let mut engine = DiscountingSwapEngine::new(Handle::empty(), None, None, None, settings);
assert_eq!(
engine.calculate().unwrap_err().message(),
"discounting term structure handle is empty"
);
}
}