1#include <catch2/catch_test_macros.hpp>2#include "raid/data/calendar.hpp"3using namespace raid;45TEST_CASE("XNYS holidays and early closes", "[calendar]") {6 MarketCalendar cal;7 CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2024, 1, 1))); // New Year8 CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2024, 1, 15))); // MLK9 CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2024, 3, 29))); // Good Friday10 CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2024, 6, 19))); // Juneteenth11 CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2024, 7, 4)));12 CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2024, 11, 28))); // Thanksgiving13 CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2024, 12, 25)));14 CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2025, 1, 9))); // Carter funeral15 CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2012, 10, 29))); // Sandy16 CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2001, 9, 11)));17 CHECK(cal.is_trading_day(Date::from_ymd(2024, 11, 29)));18 CHECK(cal.is_early_close(Date::from_ymd(2024, 11, 29)));19 CHECK(cal.is_early_close(Date::from_ymd(2024, 12, 24)));20 CHECK(cal.is_early_close(Date::from_ymd(2024, 7, 3))); // July 4 2024 is a Thursday21 CHECK_FALSE(cal.is_early_close(Date::from_ymd(2026, 7, 3))); // July 3 2026 is a Friday, July 4 Saturday (observed Friday → holiday)22 CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2026, 7, 3)));23 CHECK(cal.is_trading_day(Date::from_ymd(2021, 12, 31))); // Jan 1 2022 Saturday → no Friday holiday24 CHECK_FALSE(cal.is_trading_day(Date::from_ymd(2021, 12, 24))); // Christmas 2021 Saturday → observed Friday25 CHECK(cal.expected_regular_bars(Date::from_ymd(2024, 11, 27), Interval::m1) == 391);26 CHECK(cal.expected_regular_bars(Date::from_ymd(2024, 11, 29), Interval::m1) == 211);27 CHECK(cal.expected_regular_bars(Date::from_ymd(2024, 11, 27), Interval::m5) == 79);28 CHECK(cal.sessions(Date::from_ymd(2024, 1, 1), Date::from_ymd(2024, 12, 31)).size() == 252);29}30TEST_CASE("New York DST conversions match FinData timestamps", "[calendar]") {31 // 2024-03-08 (EST): 04:00 ET = 09:00 UTC ; 2024-03-11 (EDT): 19:59 ET = 23:59 UTC32 CHECK(MarketCalendar::new_york_offset(timestamp_from_iso("2024-03-08T09:00:00Z")) == -18000);33 CHECK(MarketCalendar::new_york_offset(timestamp_from_iso("2024-03-11T23:59:00Z")) == -14400);34 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);35 auto s = MarketCalendar{}.session(Date::from_ymd(2024, 3, 11));36 CHECK(timestamp_iso(s.open_utc) == "2024-03-11T13:30:00.000000Z"); CHECK(timestamp_iso(s.close_utc) == "2024-03-11T20:00:00.000000Z");37 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);38 CHECK(timestamp_iso(s2.ext_close_utc) == "2024-11-29T22:00:00.000000Z");39 // 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 EST40 CHECK(MarketCalendar::new_york_offset(timestamp_from_iso("2024-03-10T06:59:00Z")) == -18000);41 CHECK(MarketCalendar::new_york_offset(timestamp_from_iso("2024-03-10T07:00:00Z")) == -14400);42 CHECK(MarketCalendar::new_york_offset(timestamp_from_iso("2024-11-03T05:59:00Z")) == -14400);43 CHECK(MarketCalendar::new_york_offset(timestamp_from_iso("2024-11-03T06:00:00Z")) == -18000);44 // pre-2007 rules: 2006 DST from April 2 to October 2945 CHECK(MarketCalendar::new_york_offset(timestamp_from_iso("2006-03-15T12:00:00Z")) == -18000);46 CHECK(MarketCalendar::new_york_offset(timestamp_from_iso("2006-04-03T12:00:00Z")) == -14400);47 CHECK(MarketCalendar::new_york_offset(timestamp_from_iso("2006-10-30T12:00:00Z")) == -18000);48 CHECK(MarketCalendar::session_of(timestamp_from_iso("2024-03-11T23:59:00Z")) == Date::from_ymd(2024, 3, 11));49}50