use crate::discretizedasset::{CouponAdjustment, DiscretizedAsset, DiscretizedAssetBase};
use crate::errors::QlResult;
use crate::instruments::{CallabilityType, CallableBondArguments};
use crate::interestrate::Compounding;
use crate::math::array::Array;
use crate::pricingengine::Arguments;
use crate::termstructures::yieldtermstructure::YieldTermStructure;
use crate::time::date::Date;
use crate::time::frequency::Frequency;
use crate::types::{Real, Size, Time};
use crate::{fail, require};
const ONE_WEEK: Time = 1.0 / 52.0;
pub struct DiscretizedCallableFixedRateBond {
base: DiscretizedAssetBase,
redemption: Real,
redemption_time: Time,
coupon_times: Vec<Time>,
coupon_amounts: Vec<Real>,
coupon_adjustments: Vec<CouponAdjustment>,
callability_times: Vec<Time>,
callability_types: Vec<CallabilityType>,
adjusted_callability_prices: Vec<Real>,
}
impl DiscretizedCallableFixedRateBond {
pub fn new(
args: &CallableBondArguments,
curve: &dyn YieldTermStructure,
) -> QlResult<DiscretizedCallableFixedRateBond> {
Self::with_spread(args, curve, 0.0)
}
pub fn with_spread(
args: &CallableBondArguments,
curve: &dyn YieldTermStructure,
spread: Real,
) -> QlResult<DiscretizedCallableFixedRateBond> {
args.validate()?;
let reference_date = curve.reference_date()?;
let day_counter = curve.require_day_counter()?;
let Some(redemption_date) = args.redemption_date else {
fail!("null redemption date");
};
let redemption_time = day_counter.year_fraction(reference_date, redemption_date);
let coupon_times: Vec<Time> = args
.coupon_dates
.iter()
.map(|&d| day_counter.year_fraction(reference_date, d))
.collect();
let mut coupon_adjustments = vec![CouponAdjustment::Post; args.coupon_dates.len()];
let mut adjusted = args.callability_prices.clone();
let mut callability_times = Vec::with_capacity(args.callability_dates.len());
for (i, &call_date) in args.callability_dates.iter().enumerate() {
let mut call_time = day_counter.year_fraction(reference_date, call_date);
for (j, &coupon_time) in coupon_times.iter().enumerate() {
let coupon_date = args.coupon_dates[j];
if call_time <= coupon_time
&& coupon_time <= call_time + ONE_WEEK
&& call_date < coupon_date
{
call_time = coupon_time;
coupon_adjustments[j] = CouponAdjustment::Pre;
let discount = |date: Date| -> QlResult<Real> {
let rate = curve.zero_rate_date(
date,
day_counter.clone(),
Compounding::Continuous,
Frequency::NoFrequency,
false,
)?;
Ok((-(rate.rate() + spread) * curve.time_from_reference(date)?).exp())
};
let df_call = discount(call_date)?;
let df_coupon = discount(coupon_date)?;
require!(
df_call.is_finite()
&& df_call > 0.0
&& df_coupon.is_finite()
&& df_coupon > 0.0,
"invalid exercise adjustment discount factor"
);
adjusted[i] *= df_call / df_coupon;
break;
}
}
adjusted[i] *= args.face_amount / 100.0;
require!(adjusted[i].is_finite(), "exercise amount overflow");
callability_times.push(call_time);
}
for (i, call_date) in args.callability_dates.iter().enumerate() {
for (j, coupon_date) in args.coupon_dates.iter().enumerate() {
if coupon_adjustments[j] == CouponAdjustment::Pre && call_date == coupon_date {
adjusted[i] += args.coupon_amounts[j];
require!(adjusted[i].is_finite(), "exercise amount overflow");
}
}
}
require!(
redemption_time.is_finite()
&& coupon_times.iter().all(|t| t.is_finite())
&& callability_times.iter().all(|t| t.is_finite()),
"nonfinite callable bond time"
);
Ok(DiscretizedCallableFixedRateBond {
base: DiscretizedAssetBase::default(),
redemption: args.redemption,
redemption_time,
coupon_times,
coupon_amounts: args.coupon_amounts.clone(),
coupon_adjustments,
callability_times,
callability_types: args.callability_types.clone(),
adjusted_callability_prices: adjusted,
})
}
fn add_coupon(&mut self, i: usize) -> QlResult<()> {
let amount = self.coupon_amounts[i];
let values = self.values_mut();
for j in 0..values.size() {
values[j] += amount;
require!(
values[j].is_finite(),
"nonfinite callable bond node after coupon"
);
}
Ok(())
}
fn apply_callability(&mut self, i: usize) -> QlResult<()> {
let price = self.adjusted_callability_prices[i];
let call_type = self.callability_types[i];
let values = self.values_mut();
require!(
values.iter().all(|value| value.is_finite()),
"nonfinite callable bond continuation value"
);
match call_type {
CallabilityType::Call => {
for j in 0..values.size() {
values[j] = values[j].min(price);
}
}
CallabilityType::Put => {
for j in 0..values.size() {
values[j] = values[j].max(price);
}
}
}
Ok(())
}
}
impl DiscretizedAsset for DiscretizedCallableFixedRateBond {
fn base(&self) -> &DiscretizedAssetBase {
&self.base
}
fn base_mut(&mut self) -> &mut DiscretizedAssetBase {
&mut self.base
}
fn as_asset_mut(&mut self) -> &mut dyn DiscretizedAsset {
self
}
fn reset(&mut self, size: Size) -> QlResult<()> {
*self.values_mut() = Array::filled(size, self.redemption);
self.adjust_values()
}
fn mandatory_times(&self) -> Vec<Time> {
let mut times = Vec::new();
if self.redemption_time >= 0.0 {
times.push(self.redemption_time);
}
for &t in &self.coupon_times {
if t >= 0.0 {
times.push(t);
}
}
for &t in &self.callability_times {
if t >= 0.0 {
times.push(t);
}
}
times
}
fn pre_adjust_values_impl(&mut self) -> QlResult<()> {
for i in 0..self.coupon_times.len() {
if self.coupon_adjustments[i] == CouponAdjustment::Pre
&& self.coupon_times[i] >= 0.0
&& self.is_on_time(self.coupon_times[i])
{
self.add_coupon(i)?;
}
}
Ok(())
}
fn post_adjust_values_impl(&mut self) -> QlResult<()> {
for i in 0..self.callability_times.len() {
if self.callability_times[i] >= 0.0 && self.is_on_time(self.callability_times[i]) {
self.apply_callability(i)?;
}
}
for i in 0..self.coupon_times.len() {
if self.coupon_adjustments[i] == CouponAdjustment::Post
&& self.coupon_times[i] >= 0.0
&& self.is_on_time(self.coupon_times[i])
{
self.add_coupon(i)?;
}
}
Ok(())
}
}