use crate::discretizedasset::{
CouponAdjustment, DiscretizedAsset, DiscretizedAssetBase, DiscretizedOption,
};
use crate::errors::QlResult;
use crate::indexes::InterestRateIndex;
use crate::instrument::Instrument;
use crate::instruments::{FixedVsFloatingSwapArguments, SwaptionArguments, VanillaSwap};
use crate::settings::Settings;
use crate::shared::{Shared, SharedMut, shared_mut};
use crate::time::date::Date;
use crate::time::daycounter::DayCounter;
use crate::time::schedule::Schedule;
use crate::types::{Size, Time};
use crate::{fail, require};
use super::DiscretizedSwap;
pub struct DiscretizedSwaption {
option: DiscretizedOption,
last_payment: Time,
}
impl DiscretizedSwaption {
pub fn new(
args: &SwaptionArguments,
reference_date: Date,
day_counter: &DayCounter,
settings: &Settings<Date>,
) -> QlResult<Self> {
let Some(exercise) = args.exercise.as_ref() else {
fail!("exercise not set");
};
let prepared = prepare_swaption_with_snapped_dates(args)?;
let swap_args = &prepared.arguments;
let exercise_times: Vec<Time> = exercise
.dates()
.iter()
.map(|&date| day_counter.year_fraction(reference_date, date))
.collect();
let exercise_type = exercise.exercise_type();
let Some(&last_fixed_date) = swap_args.fixed_pay_dates.last() else {
fail!("swap has no fixed coupons");
};
let Some(&last_floating_date) = swap_args.floating_pay_dates.last() else {
fail!("swap has no floating coupons");
};
let last_fixed = day_counter.year_fraction(reference_date, last_fixed_date);
let last_floating = day_counter.year_fraction(reference_date, last_floating_date);
let last_payment = last_fixed.max(last_floating);
let swap = DiscretizedSwap::with_adjustments(
swap_args,
reference_date,
day_counter,
prepared.fixed_adjustments,
prepared.floating_adjustments,
settings,
)?;
let underlying: SharedMut<dyn DiscretizedAsset> = shared_mut(swap);
let option = DiscretizedOption::new(underlying, exercise_type, exercise_times);
Ok(DiscretizedSwaption {
option,
last_payment,
})
}
}
struct PreparedSwaption {
arguments: FixedVsFloatingSwapArguments,
fixed_adjustments: Vec<CouponAdjustment>,
floating_adjustments: Vec<CouponAdjustment>,
}
fn snap_dates(dates: &mut [Date], exercise_dates: &[Date]) -> QlResult<Vec<CouponAdjustment>> {
require!(
dates.len() >= 2,
"swap schedule requires at least two dates"
);
let mut adjustments = vec![CouponAdjustment::Pre; dates.len() - 1];
for &exercise_date in exercise_dates {
for (date, adjustment) in dates.iter_mut().zip(&mut adjustments) {
let distance = exercise_date - *date;
if distance != 0 && distance.abs() <= 7 {
*date = exercise_date;
if distance > 0 {
*adjustment = CouponAdjustment::Post;
}
}
}
}
require!(
dates.windows(2).all(|pair| pair[0] < pair[1]),
"snapped swap schedule must remain strictly increasing"
);
Ok(adjustments)
}
fn prepare_swaption_with_snapped_dates(args: &SwaptionArguments) -> QlResult<PreparedSwaption> {
let Some(source) = args.swap.as_ref() else {
fail!("source swap not set");
};
let source = source.borrow();
require!(
source.is_vanilla,
"date snapping requires a vanilla Ibor swap"
);
let Some(exercise) = args.exercise.as_ref() else {
fail!("exercise not set");
};
require!(!exercise.dates().is_empty(), "no exercise date given");
require!(
exercise.dates().iter().all(|d| *d != Date::null()),
"null exercise date"
);
let mut fixed_dates = source.fixed_schedule().dates().to_vec();
let mut floating_dates = source.floating_schedule().dates().to_vec();
let fixed_adjustments = snap_dates(&mut fixed_dates, exercise.dates())?;
let floating_adjustments = snap_dates(&mut floating_dates, exercise.dates())?;
let index = source.ibor_index();
let rebuilt = VanillaSwap::new(
source.swap_type(),
source.nominal()?,
Schedule::from_dates(fixed_dates),
source.fixed_rate(),
source.fixed_day_count().clone(),
Schedule::from_dates(floating_dates),
Shared::clone(index),
source.spread(),
source.floating_day_count().clone(),
Some(source.payment_convention()),
Shared::clone(index.base().settings()),
)?;
let mut arguments = FixedVsFloatingSwapArguments::default();
rebuilt.setup_arguments(&mut arguments)?;
Ok(PreparedSwaption {
arguments,
fixed_adjustments,
floating_adjustments,
})
}
impl DiscretizedAsset for DiscretizedSwaption {
fn base(&self) -> &DiscretizedAssetBase {
self.option.base()
}
fn base_mut(&mut self) -> &mut DiscretizedAssetBase {
self.option.base_mut()
}
fn as_asset_mut(&mut self) -> &mut dyn DiscretizedAsset {
self
}
fn reset(&mut self, size: Size) -> QlResult<()> {
let method = self.require_method()?;
let underlying = SharedMut::clone(self.option.underlying());
underlying
.borrow_mut()
.initialize(method, self.last_payment)?;
self.option.reset(size)
}
fn mandatory_times(&self) -> Vec<Time> {
self.option.mandatory_times()
}
fn pre_adjust_values_impl(&mut self) -> QlResult<()> {
self.option.pre_adjust_values_impl()
}
fn post_adjust_values_impl(&mut self) -> QlResult<()> {
self.option.post_adjust_values_impl()
}
}
#[cfg(test)]
#[path = "discretizedswaption_tests.rs"]
mod tests;