Skip to content

Commit

Permalink
Add public holidays calendars for Ukraine, UK, and US
Browse files Browse the repository at this point in the history
Added implementation of public holidays for Ukraine, the UK, and the US in the third-party calendar module. Including various markets and special closures days. Calendar functionality was expanded to indicate business days specific to these countries.  This will aid in accurate business day calculations for these markets. Test cases were also added to validate the accuracy of the holidays and business days implemented.
  • Loading branch information
nakashima-hikaru committed Jan 6, 2024
1 parent aee8b8a commit d4617f0
Show file tree
Hide file tree
Showing 52 changed files with 7,220 additions and 16 deletions.
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
resolver = "2"
members = [
"crates/*",
"third-parties/*"
]

[workspace.package]
Expand All @@ -21,6 +22,7 @@ qlab-time = { version = "0.1.0", path = "crates/qlab-time", default-features = f
qlab-termstructure = { version = "0.1.0", path = "crates/qlab-termstructure", default-features = false }
qlab-instrument = { version = "0.1.0", path = "crates/qlab-instrument", default-features = false }
qlab-math = { version = "0.1.0", path = "crates/qlab-math", default-features = false }
calendar = { version = "0.1.0", path = "third-parties/calendar", default-features = false }

[workspace.lints.rust]
unsafe_code = "forbid"
Expand Down
14 changes: 0 additions & 14 deletions crates/qlab-math/src/interpolation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,6 @@ pub(crate) struct Point<V: Float> {
y: V,
}

// pub struct InterpolationData<V: Float> {
// points: Vec<Point<V>>,
// }

// impl<V: Float> InterpolationData<V> {
// pub(crate) fn new(points: &[Point<V>]) -> Self {
// Self { points: points.to_vec() }
// }
//
// pub(crate) fn value(method: &impl InterpolationMethod<V>, x: V) -> QLabResult<V> {
// method.value(x)
// }
// }

#[allow(private_bounds)]
pub trait Method<V: Float> {
/// Fits the model to the given data points.
Expand Down
1 change: 1 addition & 0 deletions crates/qlab-time/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ description = "Time related code for the qlab"
qlab-error = { workspace = true }
chrono = "0.4.31"
num-traits = { workspace = true }
calendar = { workspace = true }

[lints]
workspace = true
8 changes: 8 additions & 0 deletions crates/qlab-time/src/business_day_convention.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DateRolling {
Unadjusted,
Following,
ModifiedFollowing,
Preceding,
ModifiedPreceding,
}
19 changes: 19 additions & 0 deletions crates/qlab-time/src/calendar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::date::Date;

pub trait Calendar {
fn is_business_day(&self, date: Date) -> bool;

fn is_holiday(&self, date: Date) -> bool {
!self.is_business_day(date)
}
}

impl<C: calendar::Calendar> Calendar for C {
fn is_business_day(&self, date: Date) -> bool {
self.is_business_day(date.0)
}

fn is_holiday(&self, date: Date) -> bool {
self.is_holiday(date.0)
}
}
62 changes: 61 additions & 1 deletion crates/qlab-time/src/date.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::business_day_convention::DateRolling;
use crate::calendar::Calendar;
use crate::period::Period;
use chrono::{Datelike, NaiveDate};
use std::fmt;
Expand All @@ -6,7 +8,7 @@ use std::ops::Sub;

/// Represents a date.
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone, Debug)]
pub struct Date(NaiveDate);
pub struct Date(pub(crate) NaiveDate);

impl Date {
/// Converts the given year, month, and day into a `NaiveDate` and
Expand Down Expand Up @@ -154,6 +156,23 @@ impl Date {
.checked_sub_days(chrono::Days::new(rhs_days.0))
.map(std::convert::Into::into)
}
/// Makes a new `NaiveDate` for the next calendar date.
///
/// Returns `None` when `self` is the last representable date.
#[inline]
#[must_use]
pub fn succ_opt(&self) -> Option<Date> {
self.0.succ_opt().map(Date)
}
/// Makes a new `NaiveDate` for the previous calendar date.
///
/// Returns `None` when `self` is the first representable date.
#[inline]
#[must_use]
pub fn pred_opt(&self) -> Option<Date> {
self.0.pred_opt().map(Date)
}

/// Returns the year stored in the corresponding `Date` object.
#[must_use]
#[inline]
Expand Down Expand Up @@ -235,6 +254,47 @@ impl Date {
fn checked_add(self, period: impl Period) -> Option<Date> {
period.checked_add(self)
}

pub fn checked_roll(
self,
period: impl Period,
calendar: &impl Calendar,
rolling: DateRolling,
) -> Option<Self> {
match rolling {
DateRolling::Unadjusted => self.checked_add(period),
DateRolling::Following => {
let mut ret = self.checked_roll(period, calendar, DateRolling::Unadjusted)?;
while !calendar.is_business_day(ret) {
ret = ret.succ_opt()?;
}
Some(ret)
}
DateRolling::ModifiedFollowing => {
let ret = self.checked_roll(period, calendar, DateRolling::Following)?;
if ret.month() == self.month() {
Some(ret)
} else {
self.checked_roll(period, calendar, DateRolling::Preceding)
}
}
DateRolling::Preceding => {
let mut ret = self.checked_roll(period, calendar, DateRolling::Unadjusted)?;
while !calendar.is_business_day(ret) {
ret = ret.pred_opt()?;
}
Some(ret)
}
DateRolling::ModifiedPreceding => {
let ret = self.checked_roll(period, calendar, DateRolling::Preceding)?;
if ret.month() == self.month() {
Some(ret)
} else {
self.checked_roll(period, calendar, DateRolling::Following)
}
}
}
}
}

#[cfg(test)]
Expand Down
3 changes: 3 additions & 0 deletions crates/qlab-time/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
mod business_day_convention;
// mod calendar;
mod calendar;
pub mod date;
pub mod day_count;
pub mod frequency;
Expand Down
2 changes: 1 addition & 1 deletion crates/qlab-time/src/period.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ use crate::date::Date;
pub mod days;
pub mod months;

pub(crate) trait Period: Copy {
pub trait Period: Copy {
fn checked_add(self, date: Date) -> Option<Date>;
}
48 changes: 48 additions & 0 deletions third-parties/calendar/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
MIT License

Copyright (c) 2024 hnakashima

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

The externally maintained library from which parts of the Software is derived
is:
- finquant, licensed as follows:
"""
MIT License

Copyright (c) 2023 Jeremy Wang

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
92 changes: 92 additions & 0 deletions third-parties/calendar/src/argentina.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Holidays in Argentina.

use crate::Calendar;
use chrono::{NaiveDate, Weekday};

#[derive(Default, Debug)]
pub struct Argentina;

impl Calendar for Argentina {
fn is_business_day(&self, date: NaiveDate) -> bool {
let (d, w, m, y, dd) = self.naive_date_to_dkmy(date);
let em = self.easter_monday(y);

if self.is_weekend(date)
// New Year's Day
|| (d == 1 && m == 1)
// Holy Thursday
|| (dd == em - 4)
// Good Weekday::Fri
|| (dd == em - 3)
// Labour Day
|| (d == 1 && m == 5)
// 5 Revolution
|| (d == 25 && m == 5)
// Death of General Manuel Belgrano
|| ((15..=21).contains(&d) && w == Weekday::Mon && m == 6)
// Independence Day
|| (d == 9 && m == 7)
// Death of General José de San Martín
|| ((15..=21).contains(&d) && w == Weekday::Mon && m == 8)
// Columbus Day
|| ((d == 10 || d == 11 || d == 12 || d == 15 || d == 16)
&& w == Weekday::Mon && m == 10)
// Immaculate Conception
|| (d == 8 && m == 12)
// Christmas Eve
|| (d == 24 && m == 12)
// New Year's Eve
|| ((d == 31 || (d == 30 && w == Weekday::Fri)) && m == 12)
{
return false;
}
true
}
}

#[cfg(test)]
mod tests {
use super::Argentina;
use crate::Calendar;
use chrono::{Duration, NaiveDate};

#[test]
fn test_mexico_holiday() {
// Test all results from 2023-01-01 to 2023-12-31
let expected_results_for_2023 = vec![
false, true, true, true, true, true, false, false, true, true, true, true, true, false,
false, true, true, true, true, true, false, false, true, true, true, true, true, false,
false, true, true, true, true, true, false, false, true, true, true, true, true, false,
false, true, true, true, true, true, false, false, true, true, true, true, true, false,
false, true, true, true, true, true, false, false, true, true, true, true, true, false,
false, true, true, true, true, true, false, false, true, true, true, true, true, false,
false, true, true, true, true, true, false, false, true, true, true, false, false,
false, false, true, true, true, true, true, false, false, true, true, true, true, true,
false, false, true, true, true, true, true, false, false, false, true, true, true,
true, false, false, true, true, true, true, true, false, false, true, true, true, true,
true, false, false, true, true, true, false, true, false, false, true, true, true,
true, true, false, false, true, true, true, true, true, false, false, true, true, true,
true, true, false, false, false, true, true, true, true, false, false, true, true,
true, true, true, false, false, true, true, true, true, true, false, false, true, true,
true, true, true, false, false, true, true, true, true, true, false, false, true, true,
true, true, true, false, false, true, true, true, true, true, false, false, true, true,
true, true, true, false, false, true, true, true, true, true, false, false, false,
true, true, true, true, false, false, true, true, true, true, true, false, false, true,
true, true, true, true, false, false, true, true, true, true, true, false, false, true,
true, true, true, true, false, false, true, true, true, true, true, false, false, true,
true, true, true, true, false, false, true, true, true, true, true, false, false,
false, true, true, true, true, false, false, true, true, true, true, true, false,
false, true, true, true, true, true, false, false, true, true, true, true, true, false,
false, true, true, true, true, true, false, false, true, true, true, true, true, false,
false, true, true, true, true, true, false, false, true, true, true, true, false,
false, false, true, true, true, true, true, false, false, true, true, true, true, true,
false, false, true, true, true, true, true, false, false,
];
let first_date = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap();
for n in 0i32..365 {
let target_date = first_date + Duration::days(i64::from(n));
let expected = expected_results_for_2023[n as usize];
assert_eq!(Argentina.is_business_day(target_date), expected);
}
}
}
Loading

0 comments on commit d4617f0

Please sign in to comment.