Skip to content

Fix 30/360 US, 30/365 and Act/365 Canadian day counters diverging from QuantLib - #324

Merged
amaggiulli merged 1 commit into
amaggiulli:developfrom
gaoflow:fix-daycounter-quantlib-divergences
Aug 4, 2026
Merged

Fix 30/360 US, 30/365 and Act/365 Canadian day counters diverging from QuantLib#324
amaggiulli merged 1 commit into
amaggiulli:developfrom
gaoflow:fix-daycounter-quantlib-divergences

Conversation

@gaoflow

@gaoflow gaoflow commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Three day counters return different values from the QuantLib functions they port. Numbers below are from QuantLib 1.43 against QLNet develop.

case QLNet QuantLib 1.43
Thirty360(USA).dayCount(2007-02-28, 2007-03-31) 31 30
Thirty360(USA).dayCount(2008-02-29, 2008-08-31) 181 180
Thirty365().dayCount(2007-01-31, 2007-02-01) 0 1
Thirty365().dayCount(2007-01-01, 2007-01-31) 30 29
Actual365Fixed(Canadian), 2-year reference period DivideByZeroException domain error

UsImpl runs the last-of-February adjustments after the 31 -> 30 checks, so D1 is still 28/29 when dd2 == 31 && dd1 >= 30 is tested and D2 never collapses to 30. thirty360.cpp US_Impl::dayCount puts the February block first, under the comment // NOTE: the order of checks is important, and the class docstring here already describes that behaviour. Thirty365 is missing the two ISO 20022 31 -> 30 adjustments that thirty365.cpp applies, which is why a one-day interval can accrue zero. CA_Impl is missing QL_REQUIRE(frequency != 0, ...) from actual365fixed.cpp.

I compared every counter in Time/DayCounters against QuantLib 1.43 over 48,205 date pairs each (month ends, 31sts, Feb 28/29, leap and non-leap years). Only these three diverged: Bond Basis/ISMA, European/Eurobond, Italian, ISDA/German with and without a termination date, NASD, Act/360, Act/364, Act/366, Act/365.25, Act/365F Standard and NoLeap, Act/Act ISDA/AFB/ISMA, Simple, 1/1 and Business/252 all matched already, and all 27 match now.

Adds testThirty360_USA and testThirty360_NASD (NASD was already correct, just untested), extends testThirty365 with 31st endpoints, and adds the Canadian guard case. Expected values are QuantLib output. Suite: 533 passed, 0 failed.

Three day counters drifted from their QuantLib counterparts:

- Thirty360 (US) applied the last-of-February adjustments after the
  31 -> 30 checks instead of before, so a period starting on the last
  day of February and ending on a 31st gained one day.
- Thirty365 was missing the ISO 20022 31 -> 30 adjustments, which let a
  one-day interval accrue zero days.
- Actual365Fixed (Canadian) lacked the frequency != 0 guard, so a
  reference period longer than a year raised DivideByZeroException
  rather than the intended domain error.

Adds table-driven tests for the US and NASD conventions, extends the
30/365 test with 31st endpoints, and covers the Canadian guard.
Copilot AI review requested due to automatic review settings July 26, 2026 17:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aligns QLNet’s 30/360 (USA), 30/365, and Actual/365 (Fixed) Canadian day counters with QuantLib’s behavior, addressing previously observed divergences and adding targeted regression tests to lock in the corrected results.

Changes:

  • Fixes 30/360 (USA) end-of-February handling by applying the last-of-February adjustments before the 31 -> 30 checks (order-dependent).
  • Implements the ISO 20022 31 -> 30 endpoint adjustments in 30/365 day counting.
  • Adds a guard in Actual/365 (Fixed) Canadian to reject reference periods longer than one year (avoids zero-frequency division) and extends the test suite accordingly.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
tests/QLNet.Tests/T_DayCounters.cs Adds/extends regression tests for 30/365 31st endpoints, 30/360 (USA), 30/360 (NASD), and the Canadian long reference-period guard.
src/QLNet/Time/DayCounters/Thirty365.cs Adds ISO 20022 31 -> 30 adjustments to match QuantLib day-count behavior for 30/365.
src/QLNet/Time/DayCounters/Thirty360.cs Reorders and refines 30/360 (USA) adjustments so end-of-February logic is applied in the correct order.
src/QLNet/Time/DayCounters/Actual365Fixed.cs Adds a frequency != 0 requirement for the Canadian convention to prevent divide-by-zero for long reference periods.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@amaggiulli

Copy link
Copy Markdown
Owner

👍

@amaggiulli
amaggiulli merged commit 885ed4f into amaggiulli:develop Aug 4, 2026
1 check passed
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.

3 participants