use super::{contains_leap_year, days_between, is_last_day_of_february, leap_year_count};
use crate::Calendar;
use std::fmt;
use time::{util::is_leap_year, Date, Duration, Month};
#[allow(non_camel_case_types)]
#[derive(Clone, Copy, Debug)]
pub enum DayCountConvention {
One_One,
Actual_360,
Actual_364,
Actual_366,
Actual_365_25,
Actual_365_Actual,
Actual_365_Fixed,
Actual_365_Leap,
Actual_Actual_AFB,
Actual_Actual_ICMA,
Actual_Actual_ISDA,
No_Leap_360,
No_Leap_365,
Thirty_360_ISDA,
Thirty_E_360,
Thirty_E_360_ISDA,
Thirty_E_365,
Thirty_E_Plus_360,
Thirty_U_360,
}
pub trait DayCounter {
fn calendar_day_count(&self, date1: Date, date2: Date) -> i64;
fn business_day_count(&self, date1: Date, date2: Date) -> i64;
fn day_count_factor(&self, date1: Date, date2: Date, convention: &DayCountConvention) -> f64;
fn calendar_day_counts(&self, dates: &[Date]) -> Vec<i64>;
fn business_day_counts(&self, dates: &[Date]) -> Vec<i64>;
fn day_count_factors(&self, dates: &[Date], convention: &DayCountConvention) -> Vec<f64>;
}
impl fmt::Display for DayCountConvention {
#[rustfmt::skip]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::One_One => write!(f, "1 / 1"),
Self::Actual_360 => write!(f, "Actual / 360"),
Self::Actual_364 => write!(f, "Actual / 364"),
Self::Actual_366 => write!(f, "Actual / 366"),
Self::Actual_365_25 => write!(f, "Actual / 365.25"),
Self::Actual_365_Actual => write!(f, "Actual / 365 Actual"),
Self::Actual_365_Fixed => write!(f, "Actual / 365F"),
Self::Actual_365_Leap => write!(f, "Actual / 365L"),
Self::Actual_Actual_AFB => write!(f, "Actual / Actual AFB"),
Self::Actual_Actual_ICMA => write!(f, "Actual / Actual ICMA"),
Self::Actual_Actual_ISDA => write!(f, "Actual / Actual ISDA"),
Self::No_Leap_360 => write!(f, "No Leap / 360"),
Self::No_Leap_365 => write!(f, "No Leap / 365"),
Self::Thirty_360_ISDA => write!(f, "30 / 360 ISDA"),
Self::Thirty_E_360 => write!(f, "30 E / 360"),
Self::Thirty_E_360_ISDA => write!(f, "30 E / 360 ISDA"),
Self::Thirty_E_365 => write!(f, "30 E / 365"),
Self::Thirty_E_Plus_360 => write!(f, "30 E+ / 360"),
Self::Thirty_U_360 => write!(f, "30 U / 360"),
}
}
}
impl<C> DayCounter for C
where
C: Calendar,
{
fn calendar_day_count(&self, date1: Date, date2: Date) -> i64 {
(date2 - date1).whole_days()
}
fn business_day_count(&self, date1: Date, date2: Date) -> i64 {
let mut count = 0;
let mut temp_date = date1;
while temp_date <= date2 {
if self.is_business_day(temp_date) {
count += 1;
}
temp_date += Duration::days(1);
}
count
}
fn day_count_factor(&self, date1: Date, date2: Date, convention: &DayCountConvention) -> f64 {
convention.day_count_factor(date1, date2)
}
fn calendar_day_counts(&self, dates: &[Date]) -> Vec<i64> {
dates
.windows(2)
.map(|window| self.calendar_day_count(window[0], window[1]))
.collect()
}
fn business_day_counts(&self, dates: &[Date]) -> Vec<i64> {
dates
.windows(2)
.map(|window| self.business_day_count(window[0], window[1]))
.collect()
}
fn day_count_factors(&self, dates: &[Date], convention: &DayCountConvention) -> Vec<f64> {
dates
.windows(2)
.map(|window| self.day_count_factor(window[0], window[1], convention))
.collect()
}
}
impl Default for DayCountConvention {
fn default() -> Self {
Self::Actual_Actual_ISDA
}
}
impl DayCountConvention {
#[rustfmt::skip]
pub fn day_count_factor(&self, start_date: Date, end_date: Date) -> f64 {
match self {
Self::One_One => Self::day_count_factor_one_one(start_date, end_date),
Self::Actual_360 => Self::day_count_factor_actual_360(start_date, end_date),
Self::Actual_364 => Self::day_count_factor_actual_364(start_date, end_date),
Self::Actual_366 => Self::day_count_factor_actual_366(start_date, end_date),
Self::Actual_365_25 => Self::day_count_factor_actual_365_25(start_date, end_date),
Self::Actual_365_Actual => Self::day_count_factor_actual_365_actual(start_date, end_date),
Self::Actual_365_Fixed => Self::day_count_factor_actual_365_fixed(start_date, end_date),
Self::Actual_365_Leap => Self::day_count_factor_actual_365_leap(start_date, end_date),
Self::Actual_Actual_AFB => Self::day_count_factor_actual_actual_afb(start_date, end_date),
Self::Actual_Actual_ICMA => Self::day_count_factor_actual_actual_icma(start_date, end_date),
Self::Actual_Actual_ISDA => Self::day_count_factor_actual_actual_isda(start_date, end_date),
Self::No_Leap_360 => Self::day_count_factor_nl_360(start_date, end_date),
Self::No_Leap_365 => Self::day_count_factor_nl_365(start_date, end_date),
Self::Thirty_360_ISDA => Self::day_count_factor_thirty_360_isda(start_date, end_date),
Self::Thirty_E_360 => Self::day_count_factor_thirty_e_360(start_date, end_date),
Self::Thirty_E_360_ISDA => Self::day_count_factor_thirty_e_360_isda(start_date, end_date),
Self::Thirty_E_365 => Self::day_count_factor_thirty_e_365(start_date, end_date),
Self::Thirty_E_Plus_360 => Self::day_count_factor_thirty_e_plus_360(start_date, end_date),
Self::Thirty_U_360 => Self::day_count_factor_thirty_u_360(start_date, end_date),
}
}
}
impl DayCountConvention {
pub(crate) fn day_count_factor_actual_actual_afb(start_date: Date, end_date: Date) -> f64 {
let (y1, _y2) = (start_date.year(), end_date.year());
let (_m1, m2) = (start_date.month(), end_date.month());
let (_d1, d2) = (start_date.day(), end_date.day());
let stub_date = if Date::from_calendar_date(y1, m2, d2).unwrap() < start_date {
Date::from_calendar_date(y1 + 1, m2, d2).unwrap()
} else {
Date::from_calendar_date(y1, m2, d2).unwrap()
};
let initial_stub_days = (stub_date - start_date).whole_days() as f64;
let final_stub_years = (end_date.year() - stub_date.year()) as f64;
let initial_stub_contains_leap = contains_leap_year(start_date, stub_date);
match initial_stub_contains_leap {
true => final_stub_years + initial_stub_days / 366.0,
false => (end_date - start_date).whole_days() as f64 / 365.0,
}
}
pub(crate) fn day_count_factor_actual_actual_icma(_start_date: Date, _end_date: Date) -> f64 {
todo!()
}
pub(crate) fn day_count_factor_actual_actual_isda(start_date: Date, end_date: Date) -> f64 {
if start_date == end_date {
return 0.0;
}
let (y1, y2) = (start_date.year(), end_date.year());
let (dib1, dib2) = (
if is_leap_year(y1) { 366.0 } else { 365.0 },
if is_leap_year(y2) { 366.0 } else { 365.0 },
);
let mut sum: f64 = (y2 - y1 - 1) as f64;
sum += days_between(
start_date,
Date::from_calendar_date(y1 + 1, Month::January, 1).unwrap(),
) as f64
/ dib1;
sum += days_between(
Date::from_calendar_date(y2, Month::January, 1).unwrap(),
end_date,
) as f64
/ dib2;
sum
}
pub(crate) fn day_count_factor_nl_360(start_date: Date, end_date: Date) -> f64 {
let day_count = (end_date - start_date).whole_days() as f64;
let leap_years = leap_year_count(start_date, end_date) as f64;
(day_count - leap_years) / 360.0
}
pub(crate) fn day_count_factor_nl_365(start_date: Date, end_date: Date) -> f64 {
let day_count = (end_date - start_date).whole_days() as f64;
let leap_years = leap_year_count(start_date, end_date) as f64;
(day_count - leap_years) / 365.0
}
pub(crate) fn day_count_factor_one_one(_start_date: Date, _end_date: Date) -> f64 {
1.0
}
pub(crate) fn day_count_factor_actual_360(start_date: Date, end_date: Date) -> f64 {
(end_date - start_date).whole_days() as f64 / 360.0
}
pub(crate) fn day_count_factor_actual_364(start_date: Date, end_date: Date) -> f64 {
(end_date - start_date).whole_days() as f64 / 364.0
}
pub(crate) fn day_count_factor_actual_365_25(start_date: Date, end_date: Date) -> f64 {
(end_date - start_date).whole_days() as f64 / 365.25
}
pub(crate) fn day_count_factor_actual_365_actual(start_date: Date, end_date: Date) -> f64 {
match contains_leap_year(start_date, end_date) {
true => (end_date - start_date).whole_days() as f64 / 366.0,
false => (end_date - start_date).whole_days() as f64 / 365.0,
}
}
pub(crate) fn day_count_factor_actual_365_fixed(start_date: Date, end_date: Date) -> f64 {
(end_date - start_date).whole_days() as f64 / 365.0
}
pub(crate) fn day_count_factor_actual_365_leap(start_date: Date, end_date: Date) -> f64 {
match contains_leap_year(start_date, end_date) {
true => (end_date - start_date).whole_days() as f64 / 366.0,
false => (end_date - start_date).whole_days() as f64 / 365.0,
}
}
pub(crate) fn day_count_factor_actual_366(start_date: Date, end_date: Date) -> f64 {
(end_date - start_date).whole_days() as f64 / 366.0
}
pub(crate) fn day_count_factor_thirty_360_isda(start_date: Date, end_date: Date) -> f64 {
let (y1, m1, mut d1) = Self::thirty_360_unpack_date(start_date);
let (y2, m2, mut d2) = Self::thirty_360_unpack_date(end_date);
if d1 == 31 {
d1 = 30;
}
if d1 == 30 && d2 == 31 {
d2 = 30;
}
Self::thirty_360_numerator(y1, y2, m1, m2, d1, d2) / 360.0
}
pub(crate) fn day_count_factor_thirty_e_360(start_date: Date, end_date: Date) -> f64 {
let (y1, m1, mut d1) = Self::thirty_360_unpack_date(start_date);
let (y2, m2, mut d2) = Self::thirty_360_unpack_date(end_date);
if d1 == 31 {
d1 = 30;
}
if d2 == 31 {
d2 = 30;
}
Self::thirty_360_numerator(y1, y2, m1, m2, d1, d2) / 360.0
}
pub(crate) fn day_count_factor_thirty_e_360_isda(start_date: Date, end_date: Date) -> f64 {
let (y1, m1, mut d1) = Self::thirty_360_unpack_date(start_date);
let (y2, m2, mut d2) = Self::thirty_360_unpack_date(end_date);
if d1 == 31 || is_last_day_of_february(start_date) {
d1 = 30;
}
if d2 == 31 || is_last_day_of_february(end_date) {
d2 = 30;
}
Self::thirty_360_numerator(y1, y2, m1, m2, d1, d2) / 360.0
}
pub(crate) fn day_count_factor_thirty_e_plus_360(start_date: Date, end_date: Date) -> f64 {
let (y1, m1, mut d1) = Self::thirty_360_unpack_date(start_date);
let (mut y2, mut m2, mut d2) = Self::thirty_360_unpack_date(end_date);
if d1 == 31 {
d1 = 30;
}
if d2 == 31 {
(y2, m2, d2) = Self::thirty_360_unpack_date(end_date.next_day().unwrap());
}
Self::thirty_360_numerator(y1, y2, m1, m2, d1, d2) / 360.0
}
pub(crate) fn day_count_factor_thirty_u_360(start_date: Date, end_date: Date) -> f64 {
let (y1, m1, mut d1) = Self::thirty_360_unpack_date(start_date);
let (y2, m2, mut d2) = Self::thirty_360_unpack_date(end_date);
if d1 == 31 || is_last_day_of_february(start_date) {
d1 = 30;
}
if d2 == 31 && d1 == 30 || is_last_day_of_february(end_date) {
d2 = 30;
}
Self::thirty_360_numerator(y1, y2, m1, m2, d1, d2) / 360.0
}
pub(crate) fn day_count_factor_thirty_e_365(start_date: Date, end_date: Date) -> f64 {
let (y1, m1, mut d1) = Self::thirty_360_unpack_date(start_date);
let (y2, m2, mut d2) = Self::thirty_360_unpack_date(end_date);
if d1 == 31 || is_last_day_of_february(start_date) {
d1 = 30;
}
if d2 == 31 || is_last_day_of_february(end_date) {
d2 = 30;
}
Self::thirty_360_numerator(y1, y2, m1, m2, d1, d2) / 365.0
}
pub(crate) fn thirty_360_numerator(
y1: i32,
y2: i32,
m1: i32,
m2: i32,
d1: i32,
d2: i32,
) -> f64 {
(360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1)) as f64
}
pub(crate) fn thirty_360_unpack_date(date: Date) -> (i32, i32, i32) {
(date.year(), date.month() as i32, date.day() as i32)
}
}
#[cfg(test)]
mod TESTS_thirty_360 {
use crate::DayCountConvention;
use time::macros::date;
use RustQuant_utils::assert_approx_equal;
use RustQuant_utils::RUSTQUANT_EPSILON;
#[test]
fn thirty_e_365() {
let start_date = date!(2011 - 06 - 17);
let end_date = date!(2012 - 12 - 30);
let dcf = DayCountConvention::day_count_factor_thirty_e_365(start_date, end_date);
assert_approx_equal!(dcf, 1.515_068_493, RUSTQUANT_EPSILON);
}
}
#[cfg(test)]
mod TESTS_actual_constant {
use crate::DayCountConvention;
use time::macros::date;
use RustQuant_utils::assert_approx_equal;
use RustQuant_utils::RUSTQUANT_EPSILON;
#[test]
fn actual_365_25() {
let test_dates: Vec<time::Date> = vec![
date!(2002 - 02 - 1),
date!(2002 - 02 - 4),
date!(2003 - 05 - 16),
date!(2003 - 12 - 17),
date!(2004 - 12 - 17),
date!(2005 - 12 - 19),
date!(2006 - 01 - 02),
date!(2006 - 03 - 13),
date!(2006 - 05 - 15),
date!(2006 - 03 - 17),
date!(2006 - 05 - 15),
date!(2006 - 07 - 26),
date!(2007 - 06 - 28),
date!(2009 - 09 - 16),
date!(2016 - 07 - 26),
];
let expected: Vec<f64> = vec![
0.0082135523613963,
1.27583846680356,
0.588637919233402,
1.00205338809035,
1.00479123887748,
0.0383299110198494,
0.191649555099247,
0.172484599589322,
-0.161533196440794,
0.161533196440794,
0.197125256673511,
0.922655715263518,
2.22039698836413,
6.85831622176591,
];
for i in 1..test_dates.len() {
let dcf = DayCountConvention::day_count_factor_actual_365_25(
test_dates[i - 1],
test_dates[i],
);
assert_approx_equal!(dcf, expected[i - 1], RUSTQUANT_EPSILON);
}
}
#[test]
fn actual_366() {
let test_dates: Vec<time::Date> = vec![
date!(2002 - 02 - 1),
date!(2002 - 02 - 4),
date!(2003 - 05 - 16),
date!(2003 - 12 - 17),
date!(2004 - 12 - 17),
date!(2005 - 12 - 19),
date!(2006 - 01 - 02),
date!(2006 - 03 - 13),
date!(2006 - 05 - 15),
date!(2006 - 03 - 17),
date!(2006 - 05 - 15),
date!(2006 - 07 - 26),
date!(2007 - 06 - 28),
date!(2009 - 09 - 16),
date!(2016 - 07 - 26),
];
let expected: Vec<f64> = vec![
0.00819672131147541,
1.27322404371585,
0.587431693989071,
1.0000000000000,
1.00273224043716,
0.0382513661202186,
0.191256830601093,
0.172131147540984,
-0.16120218579235,
0.16120218579235,
0.19672131147541,
0.920765027322404,
2.21584699453552,
6.84426229508197,
];
for i in 1..test_dates.len() {
let dcf =
DayCountConvention::day_count_factor_actual_366(test_dates[i - 1], test_dates[i]);
assert_approx_equal!(dcf, expected[i - 1], RUSTQUANT_EPSILON);
}
}
}
#[cfg(test)]
mod TESTS_actual_actual {
use crate::DayCountConvention;
use time::macros::date;
use RustQuant_utils::assert_approx_equal;
use RustQuant_utils::RUSTQUANT_EPSILON;
const DATE_1: time::Date = date!(2003 - 11 - 1);
const DATE_2: time::Date = date!(2004 - 5 - 1);
#[test]
fn actual_actual_isda() {
assert_approx_equal!(
DayCountConvention::day_count_factor_actual_actual_isda(DATE_1, DATE_2),
0.497724380567,
RUSTQUANT_EPSILON
);
}
#[test]
fn actual_actual_icma() {
assert_approx_equal!(
DayCountConvention::day_count_factor_actual_actual_isda(DATE_1, DATE_2),
0.497724380567,
RUSTQUANT_EPSILON
);
}
#[test]
fn actual_actual_afb() {
assert_approx_equal!(
DayCountConvention::day_count_factor_actual_actual_afb(DATE_1, DATE_2),
0.497267759563,
RUSTQUANT_EPSILON
);
}
}