use crate::cashflow::{CashFlow, Leg};
use crate::cashflows::{IborLeg, SimpleCashFlow};
use crate::errors::QlResult;
use crate::indexes::{IborIndex, Index, InterestRateIndex};
use crate::instrument::{Instrument, InstrumentBase};
use crate::instruments::{Bond, Swap};
use crate::pricingengine::{Arguments, Results};
use crate::require;
use crate::shared::{Shared, shared};
use crate::time::businessdayconvention::BusinessDayConvention;
use crate::time::date::Date;
use crate::time::daycounter::DayCounter;
use crate::time::schedule::{MakeSchedule, Schedule};
use crate::time::timeunit::TimeUnit;
use crate::types::{Real, Spread};
pub struct AssetSwap {
swap: Swap,
bond: Shared<Bond>,
clean_price: Real,
spread: Spread,
upfront_date: Date,
notional: Real,
pay_bond_coupon: bool,
}
impl AssetSwap {
pub fn new(
pay_bond_coupon: bool,
bond: Shared<Bond>,
clean_price: Real,
index: Shared<IborIndex>,
spread: Spread,
floating_schedule: Option<Schedule>,
floating_day_counter: Option<DayCounter>,
) -> QlResult<Self> {
require!(clean_price.is_finite(), "clean price must be finite");
require!(spread.is_finite(), "spread must be finite");
require!(
index.tenor().length() > 0 && index.tenor().units() != TimeUnit::Days,
"day-tenor and overnight indexes are not supported"
);
let settings = bond.settings_handle();
require!(
Shared::ptr_eq(&settings, index.base().settings()),
"bond and index settings must match"
);
require!(
bond.redemptions().len() == 1,
"asset swap requires a bullet bond with one redemption"
);
let maturity = bond.maturity_date()?;
let schedule = match floating_schedule {
Some(schedule) => schedule,
None => {
let settlement = bond.settlement_date(None)?;
require!(settlement < maturity, "bond has matured");
MakeSchedule::new()
.from(settlement)
.to(maturity)
.with_tenor(index.tenor())
.with_calendar(index.fixing_calendar())
.with_convention(index.business_day_convention())
.with_termination_date_convention(index.business_day_convention())
.backwards()
.end_of_month(false)
.build()
}
};
require!(schedule.len() >= 2, "floating schedule needs two dates");
require!(
schedule.dates().iter().all(|date| *date != Date::null())
&& schedule.dates().windows(2).all(|w| w[0] < w[1]),
"floating schedule dates must be strictly increasing"
);
require!(
schedule.end_date()
== schedule
.calendar()
.adjust(maturity, schedule.business_day_convention()),
"floating schedule must end at full bond maturity"
);
let upfront_date = schedule.start_date();
let final_date = schedule
.calendar()
.adjust(schedule.end_date(), BusinessDayConvention::Following);
let notional = bond.notional(Some(upfront_date))?;
require!(
notional.is_finite() && notional > 0.0,
"bond notional must be positive and finite"
);
let dirty_price = clean_price + bond.accrued_amount(Some(upfront_date))?;
require!(dirty_price.is_finite(), "dirty price must be finite");
let flows = bond.cashflows();
require!(!flows.is_empty(), "no cashflows from bond");
let mut bond_leg = Leg::new();
let mut i = 0;
while i + 1 < flows.len() && flows[i].date() <= schedule.end_date() {
if !flows[i].has_occurred(&settings, Some(upfront_date), Some(false))? {
bond_leg.push(Shared::clone(&flows[i]));
}
i += 1;
}
if i + 1 < flows.len()
&& let Some(coupon) = flows[i].as_coupon()
{
bond_leg.push(shared(SimpleCashFlow::new(
coupon.accrued_amount(schedule.end_date())?,
final_date,
)?) as Shared<dyn CashFlow>);
}
bond_leg.push(shared(SimpleCashFlow::new(
flows[flows.len() - 1].amount()?,
final_date,
)?) as Shared<dyn CashFlow>);
let mut floating_leg = IborLeg::new(schedule.until(final_date), index.clone())
.with_notionals(vec![notional])
.with_payment_day_counter(
floating_day_counter.unwrap_or_else(|| index.day_counter().clone()),
)
.with_payment_adjustment(BusinessDayConvention::Following)
.with_spreads(vec![spread])
.build()?;
let upfront = (dirty_price - 100.0) / 100.0 * notional;
require!(upfront.is_finite(), "upfront amount must be finite");
floating_leg.insert(
0,
shared(SimpleCashFlow::new(upfront, upfront_date)?) as Shared<dyn CashFlow>,
);
floating_leg
.push(shared(SimpleCashFlow::new(notional, final_date)?) as Shared<dyn CashFlow>);
let swap = Swap::new(
vec![bond_leg, floating_leg],
vec![pay_bond_coupon, !pay_bond_coupon],
settings,
)?;
Ok(Self {
swap,
bond,
clean_price,
spread,
upfront_date,
notional,
pay_bond_coupon,
})
}
pub fn par_swap(&self) -> bool {
true
}
pub fn bond(&self) -> &Shared<Bond> {
&self.bond
}
pub fn swap(&self) -> &Swap {
&self.swap
}
pub fn swap_mut(&mut self) -> &mut Swap {
&mut self.swap
}
pub fn pay_bond_coupon(&self) -> bool {
self.pay_bond_coupon
}
pub fn clean_price(&self) -> Real {
self.clean_price
}
pub fn spread(&self) -> Spread {
self.spread
}
pub fn upfront_date(&self) -> Date {
self.upfront_date
}
pub fn bond_leg(&self) -> &Leg {
&self.swap.legs()[0]
}
pub fn floating_leg(&self) -> &Leg {
&self.swap.legs()[1]
}
pub fn bond_leg_npv(&mut self) -> QlResult<Real> {
self.swap.leg_npv(0)
}
pub fn floating_leg_npv(&mut self) -> QlResult<Real> {
self.swap.leg_npv(1)
}
pub fn floating_leg_bps(&mut self) -> QlResult<Real> {
self.swap.leg_bps(1)
}
pub fn fair_spread(&mut self) -> QlResult<Spread> {
let npv = self.swap.npv()?;
let bps = self.floating_leg_bps()?;
require!(bps.is_finite() && bps != 0.0, "fair spread not available");
let fair = self.spread - npv / bps * 1e-4;
require!(fair.is_finite(), "fair spread not available");
Ok(fair)
}
pub fn fair_clean_price(&mut self) -> QlResult<Real> {
let npv = self.swap.npv()?;
let discount = self.swap.start_discounts(1)?;
let npv_discount = self.swap.npv_date_discount()?;
require!(
discount.is_finite() && discount > 0.0,
"fair clean price not available for seasoned deal"
);
let sign = if self.pay_bond_coupon { 1.0 } else { -1.0 };
let fair =
self.clean_price - sign * npv * npv_discount / discount / (self.notional / 100.0);
require!(fair.is_finite(), "fair clean price not available");
Ok(fair)
}
}
impl Instrument for AssetSwap {
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)
}
}