#[cfg(test)]
use crate::ad::dual::DualFwd;
use crate::{
ad::scalar::Scalar,
core::trade::Side,
currencies::currency::Currency,
indices::marketindex::MarketIndex,
instruments::{
cashflows::makeleg::{MakeLeg, RateType},
rates::basisswap::BasisSwap,
},
time::{
calendar::Calendar,
date::Date,
enums::{BusinessDayConvention, DateGenerationRule, Frequency},
},
utils::errors::{QSError, Result},
};
use std::marker::PhantomData;
#[derive(Default)]
pub struct MakeBasisSwap<T: Scalar> {
start_date: Option<Date>,
maturity_date: Option<Date>,
notional: Option<f64>,
pay_spread: Option<f64>,
receive_spread: Option<f64>,
identifier: Option<String>,
pay_market_index: Option<MarketIndex>,
receive_market_index: Option<MarketIndex>,
currency: Option<Currency>,
side: Option<Side>,
pay_leg_frequency: Option<Frequency>,
receive_leg_frequency: Option<Frequency>,
calendar: Option<Calendar>,
business_day_convention: Option<BusinessDayConvention>,
date_generation_rule: Option<DateGenerationRule>,
end_of_month: Option<bool>,
_marker: PhantomData<T>,
}
impl<T> MakeBasisSwap<T>
where
T: Scalar,
{
#[must_use]
pub const fn with_start_date(mut self, date: Date) -> Self {
self.start_date = Some(date);
self
}
#[must_use]
pub const fn with_maturity_date(mut self, date: Date) -> Self {
self.maturity_date = Some(date);
self
}
#[must_use]
pub const fn with_notional(mut self, notional: f64) -> Self {
self.notional = Some(notional);
self
}
#[must_use]
pub const fn with_pay_spread(mut self, spread: f64) -> Self {
self.pay_spread = Some(spread);
self
}
#[must_use]
pub const fn with_receive_spread(mut self, spread: f64) -> Self {
self.receive_spread = Some(spread);
self
}
#[must_use]
pub fn with_identifier(mut self, identifier: String) -> Self {
self.identifier = Some(identifier);
self
}
#[must_use]
pub fn with_pay_market_index(mut self, idx: MarketIndex) -> Self {
self.pay_market_index = Some(idx);
self
}
#[must_use]
pub fn with_receive_market_index(mut self, idx: MarketIndex) -> Self {
self.receive_market_index = Some(idx);
self
}
#[must_use]
pub const fn with_currency(mut self, currency: Currency) -> Self {
self.currency = Some(currency);
self
}
#[must_use]
pub const fn with_side(mut self, side: Side) -> Self {
self.side = Some(side);
self
}
#[must_use]
pub const fn with_pay_leg_frequency(mut self, freq: Frequency) -> Self {
self.pay_leg_frequency = Some(freq);
self
}
#[must_use]
pub const fn with_receive_leg_frequency(mut self, freq: Frequency) -> Self {
self.receive_leg_frequency = Some(freq);
self
}
#[must_use]
pub fn with_calendar(mut self, calendar: Calendar) -> Self {
self.calendar = Some(calendar);
self
}
#[must_use]
pub const fn with_business_day_convention(mut self, convention: BusinessDayConvention) -> Self {
self.business_day_convention = Some(convention);
self
}
#[must_use]
pub const fn with_date_generation_rule(mut self, rule: DateGenerationRule) -> Self {
self.date_generation_rule = Some(rule);
self
}
#[must_use]
pub const fn with_end_of_month(mut self, eom: bool) -> Self {
self.end_of_month = Some(eom);
self
}
pub fn build(self) -> Result<BasisSwap<T>> {
let notional = self
.notional
.ok_or_else(|| QSError::ValueNotSetErr("Notional".into()))?;
let start_date = self
.start_date
.ok_or_else(|| QSError::ValueNotSetErr("Start date".into()))?;
let maturity_date = self
.maturity_date
.ok_or_else(|| QSError::ValueNotSetErr("Maturity date".into()))?;
let currency = self
.currency
.ok_or_else(|| QSError::ValueNotSetErr("Currency".into()))?;
let pay_market_index = self
.pay_market_index
.ok_or_else(|| QSError::ValueNotSetErr("Pay market index".into()))?;
let receive_market_index = self
.receive_market_index
.ok_or_else(|| QSError::ValueNotSetErr("Receive market index".into()))?;
let identifier = self
.identifier
.ok_or_else(|| QSError::ValueNotSetErr("Identifier".into()))?;
let pay_spread = self.pay_spread.unwrap_or(0.0);
let receive_spread = self.receive_spread.unwrap_or(0.0);
let pay_freq = self.pay_leg_frequency.unwrap_or(Frequency::Quarterly);
let receive_freq = self.receive_leg_frequency.unwrap_or(Frequency::Quarterly);
let pay_leg = MakeLeg::<T>::default()
.with_leg_id(0)
.with_notional(notional)
.with_side(Side::PayShort)
.with_currency(currency)
.with_forward_index(pay_market_index.clone())
.with_start_date(start_date)
.with_end_date(maturity_date)
.with_rate_type(RateType::Floating)
.with_spread(pay_spread)
.with_payment_frequency(pay_freq)
.bullet()
.with_calendar(self.calendar.clone())
.with_business_day_convention(self.business_day_convention)
.with_date_generation_rule(self.date_generation_rule)
.with_end_of_month(self.end_of_month)
.build()?;
let receive_leg = MakeLeg::<T>::default()
.with_leg_id(1)
.with_notional(notional)
.with_side(Side::LongReceive)
.with_currency(currency)
.with_forward_index(receive_market_index.clone())
.with_start_date(start_date)
.with_end_date(maturity_date)
.with_rate_type(RateType::Floating)
.with_spread(receive_spread)
.with_payment_frequency(receive_freq)
.bullet()
.with_calendar(self.calendar)
.with_business_day_convention(self.business_day_convention)
.with_date_generation_rule(self.date_generation_rule)
.with_end_of_month(self.end_of_month)
.build()?;
Ok(BasisSwap::new(
identifier,
pay_leg,
receive_leg,
pay_market_index,
receive_market_index,
currency,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::instrument::Instrument;
fn base_builder() -> MakeBasisSwap<DualFwd> {
MakeBasisSwap::<DualFwd>::default()
.with_identifier("basis_swap_test".to_string())
.with_start_date(Date::new(2024, 1, 1))
.with_maturity_date(Date::new(2025, 1, 1))
.with_notional(1_000_000.0)
.with_pay_market_index(MarketIndex::SOFR)
.with_receive_market_index(MarketIndex::TermSOFR3m)
.with_currency(Currency::USD)
}
#[test]
fn test_build_basis_swap_success() {
let result = base_builder().build();
assert!(result.is_ok(), "expected basis swap build to succeed");
let swap = result.unwrap();
assert_eq!(swap.identifier(), "basis_swap_test");
assert_eq!(swap.currency(), Currency::USD);
assert_eq!(swap.pay_forward_index(), MarketIndex::SOFR);
assert_eq!(swap.receive_forward_index(), MarketIndex::TermSOFR3m);
assert!(!swap.pay_leg().cashflows().is_empty());
assert!(!swap.receive_leg().cashflows().is_empty());
}
#[test]
fn test_build_basis_swap_missing_receive_index_fails() {
let result = MakeBasisSwap::<DualFwd>::default()
.with_identifier("basis_swap_missing_receive".to_string())
.with_start_date(Date::new(2024, 1, 1))
.with_maturity_date(Date::new(2025, 1, 1))
.with_notional(1_000_000.0)
.with_pay_market_index(MarketIndex::SOFR)
.with_currency(Currency::USD)
.build();
assert!(
result.is_err(),
"expected missing receive market index to fail"
);
}
}