use crate::errors::QlResult;
use crate::indexes::IborIndex;
use crate::instrument::Instrument;
use crate::instruments::capfloor::{CapFloor, CapFloorType};
use crate::instruments::makevanillaswap::MakeVanillaSwap;
use crate::pricingengine::PricingEngine;
use crate::settings::Settings;
use crate::shared::{Shared, SharedMut};
use crate::time::date::Date;
use crate::time::daycounters::actual365fixed::Actual365Fixed;
use crate::time::period::Period;
use crate::time::timeunit::TimeUnit;
use crate::types::Rate;
pub struct MakeCapFloor {
cap_floor_type: CapFloorType,
strike: Rate,
first_caplet_excluded: bool,
make_vanilla_swap: MakeVanillaSwap,
settings: Shared<Settings<Date>>,
engine: Option<SharedMut<dyn PricingEngine>>,
}
impl MakeCapFloor {
pub fn new(
cap_floor_type: CapFloorType,
cap_floor_tenor: Period,
ibor_index: Shared<IborIndex>,
strike: Rate,
forward_start: Period,
settings: Shared<Settings<Date>>,
) -> MakeCapFloor {
let first_caplet_excluded = forward_start == Period::new(0, TimeUnit::Days);
let make_vanilla_swap = MakeVanillaSwap::new(
cap_floor_tenor,
Shared::clone(&ibor_index),
Some(0.0),
forward_start,
Shared::clone(&settings),
)
.with_fixed_leg_tenor(Period::new(1, TimeUnit::Years))
.with_fixed_leg_day_count(Actual365Fixed::new());
MakeCapFloor {
cap_floor_type,
strike,
first_caplet_excluded,
make_vanilla_swap,
settings,
engine: None,
}
}
pub fn with_pricing_engine(mut self, engine: SharedMut<dyn PricingEngine>) -> MakeCapFloor {
self.engine = Some(engine);
self
}
pub fn build(self) -> QlResult<CapFloor> {
let mut coupons = self.make_vanilla_swap.floating_leg()?;
if self.first_caplet_excluded && !coupons.is_empty() {
coupons.remove(0);
}
let strikes = vec![self.strike];
let mut cap_floor = match self.cap_floor_type {
CapFloorType::Cap => CapFloor::cap(coupons, strikes, Shared::clone(&self.settings))?,
CapFloorType::Floor => {
CapFloor::floor(coupons, strikes, Shared::clone(&self.settings))?
}
CapFloorType::Collar => {
crate::fail!("MakeCapFloor builds only caps and floors, not collars")
}
};
if let Some(engine) = self.engine {
cap_floor.base_mut().set_pricing_engine(engine);
}
Ok(cap_floor)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cashflows::Coupon;
use crate::event::Event;
use crate::handle::Handle;
use crate::indexes::ibor::Euribor;
use crate::shared::shared;
use crate::termstructures::yieldtermstructure::YieldTermStructure;
use crate::time::date::Month;
fn settings_on(today: Date) -> Shared<Settings<Date>> {
let settings = shared(Settings::<Date>::new());
settings.set_evaluation_date(today);
settings
}
fn euribor6m(settings: Shared<Settings<Date>>) -> Shared<IborIndex> {
shared(Euribor::six_months(
Handle::<dyn YieldTermStructure>::empty(),
settings,
))
}
#[test]
fn a_spot_cap_drops_the_first_caplet() {
let settings = settings_on(Date::new(15, Month::January, 2026));
let index = euribor6m(settings.clone());
let tenor = Period::new(3, TimeUnit::Years);
let reference = MakeVanillaSwap::new(
tenor,
Shared::clone(&index),
Some(0.0),
Period::new(0, TimeUnit::Days),
settings.clone(),
)
.floating_leg()
.unwrap();
let cap = MakeCapFloor::new(
CapFloorType::Cap,
tenor,
index,
0.03,
Period::new(0, TimeUnit::Days),
settings,
)
.build()
.unwrap();
assert_eq!(cap.coupons().len(), reference.len() - 1);
assert_eq!(cap.coupons()[0].date(), reference[1].date());
assert_eq!(
cap.coupons()[0].accrual_start_date(),
reference[1].accrual_start_date()
);
let last = cap.last_floating_rate_coupon().unwrap();
let reference_last = reference.last().unwrap();
assert_eq!(last.date(), reference_last.date());
assert_eq!(last.fixing_date(), reference_last.fixing_date());
assert_eq!(last.accrual_period(), reference_last.accrual_period());
}
#[test]
fn a_forward_starting_cap_keeps_the_first_caplet() {
let settings = settings_on(Date::new(15, Month::January, 2026));
let index = euribor6m(settings.clone());
let tenor = Period::new(3, TimeUnit::Years);
let forward_start = Period::new(6, TimeUnit::Months);
let reference = MakeVanillaSwap::new(
tenor,
Shared::clone(&index),
Some(0.0),
forward_start,
settings.clone(),
)
.floating_leg()
.unwrap();
let cap = MakeCapFloor::new(
CapFloorType::Cap,
tenor,
index,
0.03,
forward_start,
settings,
)
.build()
.unwrap();
assert_eq!(cap.coupons().len(), reference.len());
assert_eq!(cap.coupons()[0].date(), reference[0].date());
}
#[test]
fn a_floor_carries_the_strike_as_a_floor_rate() {
let settings = settings_on(Date::new(15, Month::January, 2026));
let index = euribor6m(settings.clone());
let floor = MakeCapFloor::new(
CapFloorType::Floor,
Period::new(2, TimeUnit::Years),
index,
0.01,
Period::new(0, TimeUnit::Days),
settings,
)
.build()
.unwrap();
assert_eq!(floor.cap_floor_type(), CapFloorType::Floor);
assert!(floor.cap_rates().is_empty());
assert_eq!(floor.floor_rates()[0], 0.01);
}
}