Skip to content

Reimplement all exchange holiday calendars to match reference calendars, with CI cross-checks#21

Merged
pgc-dev merged 15 commits into
mainfrom
pgc/upd
Jul 7, 2026
Merged

Reimplement all exchange holiday calendars to match reference calendars, with CI cross-checks#21
pgc-dev merged 15 commits into
mainfrom
pgc/upd

Conversation

@pgc-dev

@pgc-dev pgc-dev commented Jul 7, 2026

Copy link
Copy Markdown
Member

This started from a single missing NYSE closure — January 9, 2025, the national day of mourning for President Carter — and grew into a full reimplementation of every exchange holiday calendar finance-dates exposes.

All 50 mapped exchange codes now reproduce their exact trading-day set over 2015–2030 when cross-checked against two independent reference libraries (pandas-market-calendars and exchange-calendars), verified weekmask-aware so Saudi's Fri/Sat and Tel Aviv's Sun–Thu weekends are handled correctly.

Everything computable is implemented from first-principles rules and astronomy (our own code); the references are used only as an oracle to validate against, never vendored into our source. Where a calendar is genuinely administrative (Chinese/Taiwan Golden Weeks, Korean substitutes, Islamic moon-sighting, Hindu festivals, Latin-American bridge days) the observed dates are curated from the official exchange calendars and tabulated alongside the computable anchors.

Motivation

The previous calendars were incomplete: missing ad-hoc closures (days of mourning, typhoons), an off-by-one Juneteenth first-observance, over-closing on national holidays that exchanges trade through, wrong weekend-substitution behavior, and lunar tables hard-coded only to ~2026. This PR makes the calendars correct and, crucially, keeps them correct via automated CI checks.

What changed

Holiday-rule engine (rust/src/holiday.rs, calendar.rs)

New reusable primitives so calendars are expressed as rules, not hand-tabulated where avoidable:

  • Weekend rolls: nearest-weekday, forward-Monday (UK/Commonwealth), Sunday-only (South Africa), preceding-Friday (year-end at SIX/B3/BVC).
  • ConsecutivePair — collision-bumped two-day holidays (Christmas+Boxing, NZ New Year, SA Christmas+Goodwill).
  • WeekdayOnOrBefore / WeekdayOnOrAfter — Canadian Victoria Day, Colombian Emiliani-law shifts.
  • until_year bounds and per-date exceptions — for calendar drift and one-off holiday moves.
  • Country holiday adjustments: Japanese (substitute + citizens' holidays), Hong Kong (Sunday substitution).

Astronomical lunisolar calendar (rust/src/lunar.rs, new)

A from-scratch implementation — no external calendar data:

  • New-moon instants (Meeus Astronomical Algorithms ch. 49) and apparent solar longitude (ch. 25).
  • Winter-solstice-anchored month numbering with correct leap-month placement.
  • Parameterized by meridian: CST (China/HK/Taiwan) and KST (Korea), which occasionally differ by a day.
  • Drives Lunar New Year, Qingming, Dragon Boat, Mid-Autumn, Buddha's Birthday, Seollal, Chuseok.

Plus a Japanese equinox computation (vernal/autumnal) for JPX.

Markets reimplemented (by region)

  • Americas: NYSE, NASDAQ, NYSE American, Cboe, CFE, TSX/TSX-V, Mexico, Brazil (B3), Colombia (Emiliani), Chile, Peru, Argentina.
  • Europe: Euronext (Paris/Amsterdam/Brussels/Lisbon — harmonized set), LSE (full bank-holiday substitution + royal one-offs), Xetra, SIX, Wiener Börse, Sweden, Denmark, Finland, Norway, Iceland, Italy, Spain, Poland, Hungary, Czechia, Ireland (ISE→Euronext migration).
  • APAC / MEA: Japan (equinox + substitute), Hong Kong, Korea, China (SSE), Taiwan, Singapore, Malaysia, Indonesia, Thailand, India (NSE/BSE), Australia, New Zealand, South Africa, Saudi (Tadawul), Tel Aviv, Istanbul.

Reference bugs found

Because we reimplemented rather than copied, the audit surfaced 3 dates where the references are wrong and our calendars are right (confirmed against the official exchange calendars, and the second reference agrees with us):

  • LSE 2022-05-30 — the spring bank holiday was moved to Jun 2 for the Platinum Jubilee; LSE traded.
  • HKEX 2021-04-06 & 2026-04-07 — Ching Ming on a Sunday whose Monday substitute is Easter Monday is observed the Tuesday; HKEX was closed.

Testing / CI

  • Rust: 66 unit tests pass (engine primitives, lunar leap-month years, per-market rules).
  • Python: 103 tests pass.
  • New blocking CI test (test_reference_calendars.py): validates every mapped exchange against a two-reference consensus over 2015–2030 — asserts our trading days only where the two references agree with each other, so known reference bugs need no allowlist. Deterministic and fully offline (reference data ships in the pip packages; added as a test-only [reference] extra pulled into develop).
  • New scheduled drift check (.github/workflows/calendar-drift.yaml + scripts/check_calendar_drift.py): monthly, non-blocking. Compares against the forward-projecting reference over 2031+ and opens/updates a calendar-drift tracking issue when a curated table needs extending or a computable calendar regresses. Confirms the computable markets (US, Euronext, DACH/Nordics, UK, Canada, Australia, Japan, HKEX) drift-free through 2040.

pgc-dev added 15 commits July 7, 2026 09:45
Add holiday-rule primitives for accurate exchange calendars: ForwardMonday
weekend substitution, computed Christmas+Boxing, until_year bounds, per-date
exceptions for moved holidays, and WeekdayOnOrBefore (Victoria Day).

Reimplement and verify against reference calendars:
- US (NYSE/NASDAQ/NYSE American/Cboe/CFE): ad-hoc mourning/9-11/Sandy closures;
  Juneteenth first observed 2022 (not 2021).
- Euronext (Paris/Amsterdam/Brussels/Lisbon): no substitute days; harmonized
  six-holiday set (drop national holidays the exchange trades through).
- UK (LSE): full bank-holiday substitution plus one-offs (Jubilee, Queen's
  funeral, Coronation, VE Day) and moved spring BH in 2022.
- Canada (TSX/TSXV): correct Victoria Day, Canada Day and Christmas substitution.

Signed-off-by: prettygoodcap <131185838+pgc-dev@users.noreply.github.com>
Add SundayToMonday roll and generalize Christmas+Boxing into a reusable
ConsecutivePair (collision-bumped) rule. Reimplement and verify:
- Australia (ASX): substituted New Year/Australia Day, no-substitute ANZAC,
  King's Birthday, Christmas+Boxing, Queen mourning day (2022-09-22).
- New Zealand (NZX): mondayised New Year pair, Waitangi, ANZAC; Matariki
  tabulated from official dates; Queen mourning day (2022-09-26).
- South Africa (JSE): Sunday-only substitution; independent Christmas/Goodwill
  (no cascade); election and 2016 festive one-offs.
- Ireland (Euronext Dublin): model the ISE to Euronext migration (June bank
  holiday and May bank holiday drift, Labour Day from 2019, Storm Emma 2018).

Signed-off-by: prettygoodcap <131185838+pgc-dev@users.noreply.github.com>
Add PrecedingFriday roll (SIX New Year's Eve) and fixed_between helper for
calendar drift. Reimplement Germany (Xetra), Switzerland (SIX), Austria
(Wiener Borse), Sweden, Denmark, Finland, Norway and Iceland as continental
no-substitute calendars, including festive-block closures (Dec 24-26/31),
Midsummer Eve and First Day of Summer weekday rules, and historical drift
(Xetra Whit Monday through 2021 and Unity Day 2016-2019; Wiener Borse trimming
Ascension/Epiphany/religious holidays; Danish Great Prayer Day through 2023).

Signed-off-by: prettygoodcap <131185838+pgc-dev@users.noreply.github.com>
Reimplement Italy (Euronext Milan) and Spain (BME) as trimmed no-substitute
calendars, and Poland (GPW), Hungary (BSE) and Czechia (PSE) with their national
holidays and festive-block closures. Tabulate Hungarian bridge days and one-off
closures (Madrid 2021 festive days, Warsaw 2018 independence centenary).

Signed-off-by: prettygoodcap <131185838+pgc-dev@users.noreply.github.com>
Add WeekdayOnOrAfter rule (Colombian Emiliani-law holidays move to the next
Monday) and fixed_prev_fri helper (year-end closure to the preceding Friday).
Reimplement Mexico (BMV), Brazil (B3) and Colombia (BVC), including B3's São
Paulo state/city holidays (observed through 2021, 2020 COVID exceptions) and
Black Awareness Day becoming national in 2024.

Signed-off-by: prettygoodcap <131185838+pgc-dev@users.noreply.github.com>
…olidays

Add computed Japanese equinox rule, a JapaneseEquinox holiday type, and a
Japanese holiday adjustment (substitute holidays for Sunday closures and
citizens' holidays between two holidays). Reimplement JPX with Happy-Monday
holidays and their historical fixed-date predecessors, the Emperor's Birthday
transition (Dec 23 through 2018, Feb 23 from 2020), Mountain Day (since 2016),
the 2019 imperial one-offs, the Tokyo 2020 Olympic holiday moves, and the
2020-10-01 system-outage closure.

Signed-off-by: prettygoodcap <131185838+pgc-dev@users.noreply.github.com>
Add a lunisolar calendar module computing new moons (Meeus ch. 49), apparent
solar longitude (ch. 25), winter-solstice-anchored month numbering with leap
months, and the Qingming solar term, all in China Standard Time. Add
ChineseLunar and Qingming holiday rules and a Hong Kong Sunday-substitute
adjustment. Reimplement HKEX with computed Lunar New Year, Buddha's Birthday,
Dragon Boat, day-after Mid-Autumn, Chung Yeung and Qingming, plus tabulated
typhoon/one-off closures. Ching Ming substitution surfaced two pmc errors
(2021-04-06, 2026-04-07).

Signed-off-by: prettygoodcap <131185838+pgc-dev@users.noreply.github.com>
Parameterize the lunisolar calendar by meridian (CST for China/HK/Taiwan, KST
for Korea) so Korea's occasionally-different lunar dates resolve correctly.
Reimplement KRX with computed Seollal, Chuseok and Buddha's Birthday, add Labour
Day and the exchange year-end closure, and tabulate the substitute holidays and
temporary/election closures (whose qualifying rules changed in 2014/2021/2023).

Signed-off-by: prettygoodcap <131185838+pgc-dev@users.noreply.github.com>
Reimplement TWSE and SSE using the computed lunisolar anchors (Lunar New Year,
Qingming, Dragon Boat, Mid-Autumn) plus their fixed national holidays, and
tabulate the annually State-Council/DGPA-decreed Golden Week span extensions,
bridge days and make-up arrangements (and Taiwan typhoon closures), curated from
the official exchange calendars for 2015-2030.

Signed-off-by: prettygoodcap <131185838+pgc-dev@users.noreply.github.com>
Switch these markets to no-substitute fixed holidays and curate the movable-
holiday observances and annually-decreed bridge days from the official exchange
calendars (additions), with exceptions for the days the recurring rules place on
the wrong date.

Signed-off-by: prettygoodcap <131185838+pgc-dev@users.noreply.github.com>
Curate the festival closures for Singapore, Malaysia, Indonesia, Thailand,
India (NSE/BSE), Saudi Arabia (Tadawul), Istanbul and Tel Aviv from the official
exchange calendars (2015-2030). These markets' Islamic (moon-sighting), Hindu
lunisolar and Hebrew observances plus bridge days are administratively set and
not algorithmically derivable, so they are tabulated alongside each venue's
fixed-date rules, with exceptions where a rule places a holiday on another day.

Signed-off-by: prettygoodcap <131185838+pgc-dev@users.noreply.github.com>
Update the NYSE Juneteenth test to reflect first observance in 2022 (open on
2021-06-18), and move the APAC lunch-break session test off 2026-05-25, which is
now correctly Hong Kong's Buddha's Birthday, to a plain trading day.

Signed-off-by: prettygoodcap <131185838+pgc-dev@users.noreply.github.com>
Add a deterministic, offline CI test that validates every mapped exchange
against two independent reference calendar packages (pandas-market-calendars and
exchange-calendars), whose holiday data ships in the package. It uses a
reference-consensus strategy: assert our trading days only where the two
references agree, so known reference bugs (where they disagree) need no
allowlist. Falls back to a single reference with a documented divergence list
when only one covers a market. Adds a [reference] extra (pulled into develop) so
CI installs the oracles; the test importorskips when they are absent.

Signed-off-by: prettygoodcap <131185838+pgc-dev@users.noreply.github.com>
Add a monthly (and manually-dispatchable) workflow that compares finance-dates
calendars against pandas-market-calendars over a future window (2031+, past the
2030 curation horizon), corroborated by exchange-calendars where projectable.
When drift is found it opens or updates a 'calendar-drift'-labelled tracking
issue with a Markdown report, flagging curated tables that need extending or any
computable-calendar regression. Non-blocking: separate from PR CI.

Signed-off-by: prettygoodcap <131185838+pgc-dev@users.noreply.github.com>
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Test Results

169 tests  +61   169 ✅ +61   40s ⏱️ +39s
  2 suites ± 0     0 💤 ± 0 
  2 files   ± 0     0 ❌ ± 0 

Results for commit 20c7240. ± Comparison against base commit 463fc18.

This pull request removes 3 and adds 64 tests. Note that renamed tests count towards both.
finance-dates ‑ calendar::tests::nyse_juneteenth_first_year_2021
finance-dates ‑ calendar::tests::xams_kingsday_2024
finance_dates.tests.test_all ‑ test_nyse_juneteenth_first_year_2021
finance-dates ‑ calendar::tests::jpx_equinox_substitute_and_citizens
finance-dates ‑ calendar::tests::lse_bank_holiday_substitution_and_oneoffs
finance-dates ‑ calendar::tests::nyse_carter_day_of_mourning_2025
finance-dates ‑ calendar::tests::nyse_juneteenth_first_year_2022
finance-dates ‑ calendar::tests::nyse_special_closures_9_11_and_sandy
finance-dates ‑ calendar::tests::tsx_victoria_day_and_substitution
finance-dates ‑ calendar::tests::xams_no_kingsday_closure
finance-dates ‑ holiday::tests::christmas_boxing_substitution
finance-dates ‑ holiday::tests::forward_monday_roll
finance-dates ‑ holiday::tests::sunday_only_pair_south_africa
…

@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.29412% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.44%. Comparing base (463fc18) to head (20c7240).

Files with missing lines Patch % Lines
finance_dates/tests/test_reference_calendars.py 84.37% 7 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #21      +/-   ##
==========================================
- Coverage   89.01%   88.44%   -0.57%     
==========================================
  Files           7        8       +1     
  Lines         437      502      +65     
  Branches       26       34       +8     
==========================================
+ Hits          389      444      +55     
- Misses         43       50       +7     
- Partials        5        8       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@pgc-dev
pgc-dev merged commit cdca0cd into main Jul 7, 2026
4 of 6 checks passed
@pgc-dev
pgc-dev deleted the pgc/upd branch July 7, 2026 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant