diff --git a/crates/RustQuant_math/src/distributions/gamma.rs b/crates/RustQuant_math/src/distributions/gamma.rs index dabfa417..cad82623 100644 --- a/crates/RustQuant_math/src/distributions/gamma.rs +++ b/crates/RustQuant_math/src/distributions/gamma.rs @@ -40,6 +40,17 @@ pub struct Gamma { impl Gamma { /// New instance of a Gamma distribution. /// + /// # Examples + /// ``` + /// # use RustQuant::utils::assert_approx_equal; + /// # use RustQuant::math::distributions::*; + /// + /// let gamma = Gamma::new(2.0, 3.0); + /// + /// assert_approx_equal!(gamma.mean(), 2.0 / 3.0, 1e-12); + /// assert_approx_equal!(gamma.variance(), 2.0 / 9.0, 1e-12); + /// ``` + /// /// # Panics /// /// Panics if alpha and beta are not positive. @@ -52,6 +63,19 @@ impl Gamma { } impl Distribution for Gamma { + /// Characteristic function of the Gamma distribution. + /// + /// # Examples + /// ``` + /// # use RustQuant::utils::assert_approx_equal; + /// # use RustQuant::math::distributions::*; + /// + /// let gamma = Gamma::new(1.0, 1.0); + /// let cf = gamma.cf(1.0); + /// + /// assert_approx_equal!(cf.re, 0.5, 1e-10); + /// assert_approx_equal!(cf.im, 0.5, 1e-10); + /// ``` fn cf(&self, t: f64) -> Complex { let i: Complex = Complex::i(); let alpha = self.alpha; @@ -60,6 +84,17 @@ impl Distribution for Gamma { (1.0 - i * t / beta).powf(-alpha) } + /// Probability density function of the Gamma distribution. + /// + /// # Examples + /// ``` + /// # use RustQuant::utils::assert_approx_equal; + /// # use RustQuant::math::distributions::*; + /// + /// // Gamma(1,1) is equivalent to Exp(1). + /// let gamma = Gamma::new(1.0, 1.0); + /// assert_approx_equal!(gamma.pdf(1.0), 0.367_879_441_171_442_5, 1e-12); + /// ``` fn pdf(&self, x: f64) -> f64 { assert!(x > 0.0); @@ -73,6 +108,16 @@ impl Distribution for Gamma { self.pdf(x) } + /// Cumulative distribution function of the Gamma distribution. + /// + /// # Examples + /// ``` + /// # use RustQuant::utils::assert_approx_equal; + /// # use RustQuant::math::distributions::*; + /// + /// let gamma = Gamma::new(1.0, 1.0); + /// assert_approx_equal!(gamma.cdf(1.0), 0.632_120_558_828_558_1, 1e-12); + /// ``` fn cdf(&self, x: f64) -> f64 { assert!(x > 0.0); @@ -86,6 +131,16 @@ impl Distribution for Gamma { unimplemented!() } + /// Mean of the Gamma distribution. + /// + /// # Examples + /// ``` + /// # use RustQuant::utils::assert_approx_equal; + /// # use RustQuant::math::distributions::*; + /// + /// let gamma = Gamma::new(2.0, 4.0); + /// assert_approx_equal!(gamma.mean(), 0.5, 1e-12); + /// ``` fn mean(&self) -> f64 { self.alpha / self.beta } @@ -102,6 +157,16 @@ impl Distribution for Gamma { } } + /// Variance of the Gamma distribution. + /// + /// # Examples + /// ``` + /// # use RustQuant::utils::assert_approx_equal; + /// # use RustQuant::math::distributions::*; + /// + /// let gamma = Gamma::new(2.0, 4.0); + /// assert_approx_equal!(gamma.variance(), 0.125, 1e-12); + /// ``` fn variance(&self) -> f64 { self.alpha / self.beta.powi(2) } diff --git a/crates/RustQuant_time/src/calendar.rs b/crates/RustQuant_time/src/calendar.rs index 3cbc435d..c5b528ce 100644 --- a/crates/RustQuant_time/src/calendar.rs +++ b/crates/RustQuant_time/src/calendar.rs @@ -73,6 +73,8 @@ pub enum Market { Israel, /// Italy national calendar. Italy, + /// Japan national calendar. + Japan, /// Mexico national calendar. Mexico, /// Netherlands national calendar. @@ -428,6 +430,7 @@ impl Calendar { Market::Indonesia => is_holiday_impl_indonesia(date), Market::Israel => is_holiday_impl_israel(date), Market::Italy => is_holiday_impl_italy(date), + Market::Japan => is_holiday_impl_japan(date), Market::Mexico => is_holiday_impl_mexico(date), Market::Netherlands => is_holiday_impl_netherlands(date), Market::NewZealand => is_holiday_impl_new_zealand(date), diff --git a/crates/RustQuant_time/src/countries/japan.rs b/crates/RustQuant_time/src/countries/japan.rs new file mode 100644 index 00000000..06c66fad --- /dev/null +++ b/crates/RustQuant_time/src/countries/japan.rs @@ -0,0 +1,107 @@ +use crate::utilities::unpack_date; +use time::{Date, Month, Weekday}; + +pub(crate) fn is_holiday_impl_japan(date: Date) -> bool { + let (y, m, d, wd, _, _) = unpack_date(date, false); + + // Fixed-date holidays (modern scope) + let fixed = + (m == Month::January && d == 1) // New Year's Day + || (m == Month::February && d == 11) // National Foundation Day + || (m == Month::February && d == 23 && y >= 2020) // Emperor's Birthday (Reiwa) + || (m == Month::April && d == 29) // Showa Day + || (m == Month::May && d == 3) // Constitution Memorial Day + || (m == Month::May && d == 4) // Greenery Day + || (m == Month::May && d == 5) // Children's Day + || (m == Month::August && d == 11 && y >= 2016) // Mountain Day + || (m == Month::November && d == 3) // Culture Day + || (m == Month::November && d == 23); // Labor Thanksgiving Day + + // Happy Monday system holidays + let happy_monday = + // Coming of Age Day: 2nd Monday of January + (m == Month::January && wd == Weekday::Monday && d >= 8 && d <= 14) + // Marine Day: 3rd Monday of July + || (m == Month::July && wd == Weekday::Monday && d >= 15 && d <= 21) + // Respect for the Aged Day: 3rd Monday of September + || (m == Month::September && wd == Weekday::Monday && d >= 15 && d <= 21) + // Sports Day: 2nd Monday of October + || (m == Month::October && wd == Weekday::Monday && d >= 8 && d <= 14); + + // Equinox holidays (standard approximation) + let vernal = + m == Month::March && d as i32 == vernal_equinox_day(y as i32); + let autumnal = + m == Month::September && d as i32 == autumnal_equinox_day(y as i32); + + // Substitute holidays: if holiday falls on Sunday, following Monday is holiday. + let substitute_monday = wd == Weekday::Monday && is_recognized_holiday(date.previous_day().unwrap()); + + fixed || happy_monday || vernal || autumnal || substitute_monday +} + +fn is_recognized_holiday(date: Date) -> bool { + let (y, m, d, wd, _, _) = unpack_date(date, false); + + let fixed = + (m == Month::January && d == 1) + || (m == Month::February && d == 11) + || (m == Month::February && d == 23 && y >= 2020) + || (m == Month::April && d == 29) + || (m == Month::May && d == 3) + || (m == Month::May && d == 4) + || (m == Month::May && d == 5) + || (m == Month::August && d == 11 && y >= 2016) + || (m == Month::November && d == 3) + || (m == Month::November && d == 23); + + let happy_monday = + (m == Month::January && wd == Weekday::Monday && d >= 8 && d <= 14) + || (m == Month::July && wd == Weekday::Monday && d >= 15 && d <= 21) + || (m == Month::September && wd == Weekday::Monday && d >= 15 && d <= 21) + || (m == Month::October && wd == Weekday::Monday && d >= 8 && d <= 14); + + let vernal = m == Month::March && d as i32 == vernal_equinox_day(y as i32); + let autumnal = m == Month::September && d as i32 == autumnal_equinox_day(y as i32); + + fixed || happy_monday || vernal || autumnal +} + +fn vernal_equinox_day(year: i32) -> i32 { + (20.8431 + 0.242194 * (year - 1980) as f64 - ((year - 1980) / 4) as f64).floor() as i32 +} + +fn autumnal_equinox_day(year: i32) -> i32 { + (23.2488 + 0.242194 * (year - 1980) as f64 - ((year - 1980) / 4) as f64).floor() as i32 +} + +#[cfg(test)] +mod tests { + use crate::{Calendar, Market}; + use time::macros::date; + + const CALENDAR: Calendar = Calendar::new(Market::Japan); + + #[test] + fn test_japan_holidays_2025() { + assert!(CALENDAR.is_holiday(date!(2025 - 01 - 01))); // New Year + assert!(CALENDAR.is_holiday(date!(2025 - 01 - 13))); // Coming of Age Day + assert!(CALENDAR.is_holiday(date!(2025 - 02 - 11))); // National Foundation Day + assert!(CALENDAR.is_holiday(date!(2025 - 03 - 20))); // Vernal Equinox + assert!(CALENDAR.is_holiday(date!(2025 - 04 - 29))); // Showa Day + assert!(CALENDAR.is_holiday(date!(2025 - 05 - 05))); // Children's Day + assert!(CALENDAR.is_holiday(date!(2025 - 07 - 21))); // Marine Day + assert!(CALENDAR.is_holiday(date!(2025 - 09 - 15))); // Respect for the Aged Day + assert!(CALENDAR.is_holiday(date!(2025 - 09 - 23))); // Autumnal Equinox + assert!(CALENDAR.is_holiday(date!(2025 - 10 - 13))); // Sports Day + assert!(CALENDAR.is_holiday(date!(2025 - 11 - 03))); // Culture Day + assert!(CALENDAR.is_holiday(date!(2025 - 11 - 24))); // Labor Thanksgiving substitute + } + + #[test] + fn test_regular_business_days_2025() { + assert!(!CALENDAR.is_holiday(date!(2025 - 01 - 02))); + assert!(!CALENDAR.is_holiday(date!(2025 - 06 - 10))); + assert!(!CALENDAR.is_holiday(date!(2025 - 12 - 01))); + } +} diff --git a/crates/RustQuant_time/src/countries/mod.rs b/crates/RustQuant_time/src/countries/mod.rs index b5ed2fc1..52c78208 100644 --- a/crates/RustQuant_time/src/countries/mod.rs +++ b/crates/RustQuant_time/src/countries/mod.rs @@ -89,6 +89,10 @@ pub(crate) use israel::*; pub mod italy; pub(crate) use italy::*; +/// Japan holidays and calendars. +pub mod japan; +pub(crate) use japan::*; + /// Mexico holidays and calendars pub mod mexico; pub(crate) use mexico::*;