#include #include "raid/data/calendar.hpp" using namespace raid; TEST_CASE("XNYS holidays and early closes", "[calendar]") { MarketCalendar cal; CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2024, 1, 1))); // New Year CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2024, 1, 15))); // MLK CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2024, 3, 29))); // Good Friday CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2024, 6, 19))); // Juneteenth CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2024, 7, 4))); CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2024, 11, 28))); // Thanksgiving CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2024, 12, 25))); CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2025, 1, 9))); // Carter funeral CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2012, 10, 29))); // Sandy CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2001, 9, 11))); CHECK(cal.is_trading_day(Date::from_ymd(2024, 11, 29))); CHECK(cal.is_early_close(Date::from_ymd(2024, 11, 29))); CHECK(cal.is_early_close(Date::from_ymd(2024, 12, 24))); CHECK(cal.is_early_close(Date::from_ymd(2024, 7, 3))); // July 4 2024 is a Thursday CHECK_FALSE(cal.is_early_close(Date::from_ymd(2026, 7, 3))); // July 3 2026 is a Friday, July 4 Saturday (observed Friday → holiday) CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2026, 7, 3))); CHECK(cal.is_trading_day(Date::from_ymd(2021, 12, 31))); // Jan 1 2022 Saturday → no Friday holiday CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2021, 12, 24))); // Christmas 2021 Saturday → observed Friday CHECK(cal.expected_regular_bars(Date::from_ymd(2024, 11, 27), Interval::m1) == 391); CHECK(cal.expected_regular_bars(Date::from_ymd(2024, 11, 29), Interval::m1) == 211); CHECK(cal.expected_regular_bars(Date::from_ymd(2024, 11, 27), Interval::m5) == 79); CHECK(cal.sessions(Date::from_ymd(2024, 1, 1), Date::from_ymd(2024, 12, 31)).size() == 252); } TEST_CASE("New York DST conversions match FinData timestamps", "[calendar]") { // 2024-03-08 (EST): 04:00 ET = 09:00 UTC ; 2024-03-11 (EDT): 19:59 ET = 23:59 UTC CHECK(MarketCalendar::new_york_offset(timestamp_from_iso("2024-03-08T09:00:00Z")) == -18000); CHECK(MarketCalendar::new_york_offset(timestamp_from_iso("2024-03-11T23:59:00Z")) == -14400); auto l = MarketCalendar::to_new_york(timestamp_from_iso("2024-03-11T23:59:00Z")); CHECK(l.date == Date::from_ymd(2024, 3, 11)); CHECK(l.minute_of_day == 19 * 60 + 59); auto s = MarketCalendar{}.session(Date::from_ymd(2024, 3, 11)); CHECK(timestamp_iso(s.open_utc) == "2024-03-11T13:30:00.000000Z"); CHECK(timestamp_iso(s.close_utc) == "2024-03-11T20:00:00.000000Z"); auto s2 = MarketCalendar{}.session(Date::from_ymd(2024, 11, 29)); CHECK(timestamp_iso(s2.close_utc) == "2024-11-29T18:00:00.000000Z"); CHECK(s2.early_close); CHECK(timestamp_iso(s2.ext_close_utc) == "2024-11-29T22:00:00.000000Z"); // DST transition instants: 2024-03-10 06:59 UTC is EST, 07:00 UTC is EDT; 2024-11-03 05:59 UTC EDT, 06:00 UTC EST CHECK(MarketCalendar::new_york_offset(timestamp_from_iso("2024-03-10T06:59:00Z")) == -18000); CHECK(MarketCalendar::new_york_offset(timestamp_from_iso("2024-03-10T07:00:00Z")) == -14400); CHECK(MarketCalendar::new_york_offset(timestamp_from_iso("2024-11-03T05:59:00Z")) == -14400); CHECK(MarketCalendar::new_york_offset(timestamp_from_iso("2024-11-03T06:00:00Z")) == -18000); // pre-2007 rules: 2006 DST from April 2 to October 29 CHECK(MarketCalendar::new_york_offset(timestamp_from_iso("2006-03-15T12:00:00Z")) == -18000); CHECK(MarketCalendar::new_york_offset(timestamp_from_iso("2006-04-03T12:00:00Z")) == -14400); CHECK(MarketCalendar::new_york_offset(timestamp_from_iso("2006-10-30T12:00:00Z")) == -18000); CHECK(MarketCalendar::session_of(timestamp_from_iso("2024-03-11T23:59:00Z")) == Date::from_ymd(2024, 3, 11)); }