use crate::currency::Currency;
use crate::errors::QlResult;
use crate::handle::Handle;
use crate::indexes::iborindex::IborIndex;
use crate::indexes::interestrateindex::InterestRateIndex;
use crate::settings::Settings;
use crate::shared::Shared;
use crate::termstructures::yieldtermstructure::YieldTermStructure;
use crate::time::businessdayconvention::BusinessDayConvention;
use crate::time::calendars::target::Target;
use crate::time::date::Date;
use crate::time::daycounters::actual360::Actual360;
use crate::time::period::Period;
use crate::time::timeunit::TimeUnit;
use crate::{fail, require};
pub struct Euribor;
impl Euribor {
#[allow(clippy::new_ret_no_self)]
pub fn new(
tenor: Period,
forwarding: Handle<dyn YieldTermStructure>,
settings: Shared<Settings<Date>>,
) -> QlResult<IborIndex> {
let index = IborIndex::new(
"Euribor".into(),
tenor,
2,
Currency::eur(),
Target::new(),
euribor_convention(tenor)?,
euribor_eom(tenor)?,
Actual360::new(),
forwarding,
settings,
);
require!(
index.tenor().units() != TimeUnit::Days,
"for daily tenors ({}) dedicated DailyTenor constructor must be used",
index.tenor()
);
Ok(index)
}
pub fn one_week(
forwarding: Handle<dyn YieldTermStructure>,
settings: Shared<Settings<Date>>,
) -> IborIndex {
Self::named(Period::new(1, TimeUnit::Weeks), forwarding, settings)
}
pub fn one_month(
forwarding: Handle<dyn YieldTermStructure>,
settings: Shared<Settings<Date>>,
) -> IborIndex {
Self::named(Period::new(1, TimeUnit::Months), forwarding, settings)
}
pub fn three_months(
forwarding: Handle<dyn YieldTermStructure>,
settings: Shared<Settings<Date>>,
) -> IborIndex {
Self::named(Period::new(3, TimeUnit::Months), forwarding, settings)
}
pub fn six_months(
forwarding: Handle<dyn YieldTermStructure>,
settings: Shared<Settings<Date>>,
) -> IborIndex {
Self::named(Period::new(6, TimeUnit::Months), forwarding, settings)
}
pub fn one_year(
forwarding: Handle<dyn YieldTermStructure>,
settings: Shared<Settings<Date>>,
) -> IborIndex {
Self::named(Period::new(1, TimeUnit::Years), forwarding, settings)
}
fn named(
tenor: Period,
forwarding: Handle<dyn YieldTermStructure>,
settings: Shared<Settings<Date>>,
) -> IborIndex {
Self::new(tenor, forwarding, settings)
.expect("a week, month, or year Euribor tenor is always valid")
}
}
fn euribor_convention(tenor: Period) -> QlResult<BusinessDayConvention> {
match tenor.units() {
TimeUnit::Days | TimeUnit::Weeks => Ok(BusinessDayConvention::Following),
TimeUnit::Months | TimeUnit::Years => Ok(BusinessDayConvention::ModifiedFollowing),
_ => fail!("invalid time units"),
}
}
fn euribor_eom(tenor: Period) -> QlResult<bool> {
match tenor.units() {
TimeUnit::Days | TimeUnit::Weeks => Ok(false),
TimeUnit::Months | TimeUnit::Years => Ok(true),
_ => fail!("invalid time units"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::indexes::index::Index;
use crate::interestrate::Compounding;
use crate::shared::shared;
use crate::termstructures::yields::FlatForward;
use crate::time::date::Month;
use crate::time::frequency::Frequency;
fn settings_on(today: Date) -> Shared<Settings<Date>> {
let settings = shared(Settings::<Date>::new());
settings.set_evaluation_date(today);
settings
}
fn flat_curve(reference: Date, rate: f64) -> Handle<dyn YieldTermStructure> {
Handle::new(shared(FlatForward::with_rate(
reference,
rate,
Actual360::new(),
Compounding::Continuous,
Frequency::Annual,
)) as Shared<dyn YieldTermStructure>)
}
#[test]
fn euribor6m_matches_the_quantlib_construction_table() {
let settings = shared(Settings::<Date>::new());
let index = Euribor::six_months(Handle::empty(), settings);
assert_eq!(index.name(), "Euribor6M Actual/360");
assert_eq!(index.fixing_days(), 2);
assert_eq!(*index.currency(), Currency::eur());
assert_eq!(index.fixing_calendar().name(), "TARGET");
assert_eq!(index.day_counter().name(), "Actual/360");
assert_eq!(
index.business_day_convention(),
BusinessDayConvention::ModifiedFollowing
);
assert!(index.end_of_month());
}
#[test]
fn one_week_rolls_following_off_month_end() {
let settings = shared(Settings::<Date>::new());
let index = Euribor::one_week(Handle::empty(), settings);
assert_eq!(
index.business_day_convention(),
BusinessDayConvention::Following
);
assert!(!index.end_of_month());
}
#[test]
fn a_daily_tenor_is_rejected_with_the_quantlib_message() {
let settings = shared(Settings::<Date>::new());
let Err(err) = Euribor::new(Period::new(1, TimeUnit::Days), Handle::empty(), settings)
else {
panic!("a daily tenor must be rejected");
};
assert!(
err.to_string()
.contains("for daily tenors (1D) dedicated DailyTenor constructor must be used")
);
}
#[test]
fn euribor6m_forecasts_like_an_equivalent_hand_built_index() {
let today = Date::new(15, Month::June, 2026);
let settings = settings_on(today);
let rate = 0.03;
let euribor = Euribor::six_months(flat_curve(today, rate), settings.clone());
let hand_built = IborIndex::new(
"Euribor".into(),
Period::new(6, TimeUnit::Months),
2,
Currency::eur(),
Target::new(),
BusinessDayConvention::ModifiedFollowing,
true,
Actual360::new(),
flat_curve(today, rate),
settings,
);
let fixing_date = Date::new(15, Month::July, 2026);
let a = euribor.forecast_fixing(fixing_date).unwrap();
let b = hand_built.forecast_fixing(fixing_date).unwrap();
assert_eq!(a, b);
}
}