FA-60886 / Bond day-count conventions / Open access
ICMA regular-period accrued with ex-coupon: the accrual denominator is a nominal fraction of 365 days · case 01
Accrued interest drifts from the ICMA value for periods that are not exactly 365/freq days.
ROOT CAUSE
The period length is taken as 365 divided by frequency rather than the actual days between coupons.
VERIFIED REPAIR
Use the actual number of days from the previous to the next coupon date.
Unsuccessful approach: Switching to 366/freq in leap years still ignores the actual period length.
Case contract
Inputs prev, nxt, settle ([y,m,d]), annual rate, frequency and ex-coupon days. Settlement outside [prev, nxt] returns "settlement outside period"; settlement on nxt returns 0.0. If the settlement is within ex-coupon days of nxt (days to nxt <= exdays), accrued is negative: -coupon*days(settle,nxt)/days(prev,nxt). Otherwise accrued = coupon*days(prev,settle)/days(prev,nxt). coupon=100*rate/freq; round the final value to 6 decimals.
Why this case matters
Bond accrual and pricing systems depend on exact day-count arithmetic; a single-day error changes settlement cash.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import datetime
from fractions import Fraction
N = 1
observations = []
def solve(prev, nxt, settle, rate, freq, exdays):
P = datetime.date(*prev)
Q = datetime.date(*nxt)
S = datetime.date(*settle)
if not (P <= S <= Q):
return 'settlement outside period'
if S == Q:
return 0.0
period = 365 / freq
coupon = 100 * rate / freq
if (Q - S).days <= exdays:
return round(-coupon * (Q - S).days / period, 6)
days = (S - P).days
return round(coupon * days / period, 6)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression period denominator 1', [[2036, 2, 15], [2036, 8, 15], [2036, 8, 11], 0.0275, 2, 3], 1.34478], ['regression period denominator 2', [[2048, 7, 1], [2048, 8, 1], [2048, 7, 26], 0.05, 12, 0], 0.336022], ['partial repair probe 1', [[2012, 8, 30], [2013, 8, 30], [2013, 8, 23], 0.04125, 1, 7], -0.07911], ['partial repair probe 2', [[2004, 5, 31], [2005, 5, 31], [2004, 6, 3], 0.0275, 1, 3], 0.022603], ['boundary control 1', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 16], 0.05, 2, 7], 'settlement outside period'], ['boundary control 2', [[2024, 1, 15], [2024, 7, 15], [2024, 1, 15], 0.05, 2, 7], 0.0], ['normal control 1', [[2045, 1, 31], [2045, 2, 28], [2045, 2, 28], 0.04125, 12, 10], 0.0], ['normal control 2', [[2041, 2, 1], [2041, 8, 1], [2041, 2, 1], 0.05, 2, 0], 0.0]], [['regression period denominator 1', [[2035, 4, 29], [2036, 4, 29], [2035, 8, 11], 0.02, 1, 7], 0.568306], ['regression period denominator 2', [[2011, 3, 31], [2012, 3, 31], [2011, 6, 26], 0.035, 1, 7], 0.831967], ['partial repair probe 1', [[2060, 7, 14], [2061, 7, 14], [2061, 7, 8], 0.04125, 1, 0], 4.057192], ['partial repair probe 2', [[2044, 3, 7], [2045, 3, 7], [2045, 2, 25], 0.04125, 1, 0], 4.011986], ['boundary control 1', [[2024, 1, 15], [2024, 7, 15], [2024, 1, 15], 0.05, 2, 7], 0.0], ['boundary control 2', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 15], 0.05, 2, 7], 0.0], ['normal control 1', [[2037, 7, 30], [2038, 7, 30], [2037, 10, 8], 0.0125, 1, 5], 0.239726], ['normal control 2', [[2016, 3, 13], [2016, 4, 13], [2016, 3, 13], 0.035, 12, 0], 0.0]], [['regression period denominator 1', [[2054, 4, 30], [2054, 7, 30], [2054, 7, 20], 0.04125, 4, 0], 0.917926], ['regression period denominator 2', [[2006, 11, 7], [2006, 12, 7], [2006, 12, 5], 0.0275, 12, 3], -0.015278], ['partial repair probe 1', [[2048, 8, 20], [2049, 8, 20], [2048, 8, 29], 0.02, 1, 10], 0.049315], ['partial repair probe 2', [[2036, 12, 24], [2037, 12, 24], [2037, 4, 22], 0.07, 1, 7], 2.282192], ['boundary control 1', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 16], 0.05, 2, 7], 'settlement outside period'], ['boundary control 2', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 15], 0.05, 2, 7], 0.0], ['normal control 1', [[2030, 12, 17], [2031, 12, 17], [2031, 12, 9], 0.05, 1, 0], 4.890411], ['normal control 2', [[2054, 12, 24], [2055, 1, 24], [2054, 12, 24], 0.0125, 12, 0], 0.0]], [['regression period denominator 1', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 8], 0.05, 2, 7], -0.096154], ['regression period denominator 2', [[2047, 11, 28], [2047, 12, 28], [2047, 12, 12], 0.0125, 12, 5], 0.048611], ['partial repair probe 1', [[2028, 6, 30], [2029, 6, 30], [2029, 6, 26], 0.05, 1, 7], -0.054795], ['partial repair probe 2', [[2000, 7, 1], [2001, 7, 1], [2001, 1, 25], 0.0125, 1, 10], 0.712329], ['boundary control 1', [[2024, 1, 15], [2024, 7, 15], [2024, 1, 15], 0.05, 2, 7], 0.0], ['boundary control 2', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 16], 0.05, 2, 7], 'settlement outside period'], ['normal control 1', [[2050, 4, 30], [2051, 4, 30], [2050, 5, 10], 0.0125, 1, 7], 0.034247], ['normal control 2', [[2033, 5, 12], [2033, 6, 12], [2033, 5, 12], 0.05, 12, 7], 0.0]], [['regression period denominator 1', [[2025, 5, 28], [2025, 8, 28], [2025, 7, 18], 0.02, 4, 7], 0.277174], ['regression period denominator 2', [[2009, 12, 30], [2010, 6, 30], [2010, 6, 24], 0.04125, 2, 0], 1.994505], ['partial repair probe 1', [[2016, 10, 10], [2017, 10, 10], [2017, 10, 1], 0.0625, 1, 7], 6.09589], ['partial repair probe 2', [[2028, 4, 24], [2029, 4, 24], [2028, 7, 14], 0.04125, 1, 5], 0.915411], ['boundary control 1', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 16], 0.05, 2, 7], 'settlement outside period'], ['boundary control 2', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 15], 0.05, 2, 7], 0.0], ['normal control 1', [[2049, 11, 30], [2050, 11, 30], [2050, 11, 30], 0.0625, 1, 0], 0.0], ['normal control 2', [[2022, 8, 18], [2023, 8, 18], [2023, 8, 11], 0.04125, 1, 10], -0.07911]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression period denominator 1 | 1.341096 | 1.34478 | Failed |
| regression period denominator 2 | 0.342466 | 0.336022 | Failed |
| partial repair probe 1 | -0.07911 | -0.07911 | Passed |
| partial repair probe 2 | 0.022603 | 0.022603 | Passed |
| boundary control 1 | settlement outside period | settlement outside period | Passed |
| boundary control 2 | 0.0 | 0.0 | Passed |
| normal control 1 | 0.0 | 0.0 | Passed |
| normal control 2 | 0.0 | 0.0 | Passed |
SHA-256 / ebc28121b2521ff1062eeb3cb24c8d5ea183c7ea1077ab44b7ee5745e4453505
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import datetime
from fractions import Fraction
N = 1
observations = []
def solve(prev, nxt, settle, rate, freq, exdays):
P = datetime.date(*prev)
Q = datetime.date(*nxt)
S = datetime.date(*settle)
if not (P <= S <= Q):
return 'settlement outside period'
if S == Q:
return 0.0
period = (366 if P.year % 4 == 0 else 365) / freq
coupon = 100 * rate / freq
if (Q - S).days <= exdays:
return round(-coupon * (Q - S).days / period, 6)
days = (S - P).days
return round(coupon * days / period, 6)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression period denominator 1', [[2036, 2, 15], [2036, 8, 15], [2036, 8, 11], 0.0275, 2, 3], 1.34478], ['regression period denominator 2', [[2048, 7, 1], [2048, 8, 1], [2048, 7, 26], 0.05, 12, 0], 0.336022], ['partial repair probe 1', [[2012, 8, 30], [2013, 8, 30], [2013, 8, 23], 0.04125, 1, 7], -0.07911], ['partial repair probe 2', [[2004, 5, 31], [2005, 5, 31], [2004, 6, 3], 0.0275, 1, 3], 0.022603], ['boundary control 1', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 16], 0.05, 2, 7], 'settlement outside period'], ['boundary control 2', [[2024, 1, 15], [2024, 7, 15], [2024, 1, 15], 0.05, 2, 7], 0.0], ['normal control 1', [[2045, 1, 31], [2045, 2, 28], [2045, 2, 28], 0.04125, 12, 10], 0.0], ['normal control 2', [[2041, 2, 1], [2041, 8, 1], [2041, 2, 1], 0.05, 2, 0], 0.0]], [['regression period denominator 1', [[2035, 4, 29], [2036, 4, 29], [2035, 8, 11], 0.02, 1, 7], 0.568306], ['regression period denominator 2', [[2011, 3, 31], [2012, 3, 31], [2011, 6, 26], 0.035, 1, 7], 0.831967], ['partial repair probe 1', [[2060, 7, 14], [2061, 7, 14], [2061, 7, 8], 0.04125, 1, 0], 4.057192], ['partial repair probe 2', [[2044, 3, 7], [2045, 3, 7], [2045, 2, 25], 0.04125, 1, 0], 4.011986], ['boundary control 1', [[2024, 1, 15], [2024, 7, 15], [2024, 1, 15], 0.05, 2, 7], 0.0], ['boundary control 2', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 15], 0.05, 2, 7], 0.0], ['normal control 1', [[2037, 7, 30], [2038, 7, 30], [2037, 10, 8], 0.0125, 1, 5], 0.239726], ['normal control 2', [[2016, 3, 13], [2016, 4, 13], [2016, 3, 13], 0.035, 12, 0], 0.0]], [['regression period denominator 1', [[2054, 4, 30], [2054, 7, 30], [2054, 7, 20], 0.04125, 4, 0], 0.917926], ['regression period denominator 2', [[2006, 11, 7], [2006, 12, 7], [2006, 12, 5], 0.0275, 12, 3], -0.015278], ['partial repair probe 1', [[2048, 8, 20], [2049, 8, 20], [2048, 8, 29], 0.02, 1, 10], 0.049315], ['partial repair probe 2', [[2036, 12, 24], [2037, 12, 24], [2037, 4, 22], 0.07, 1, 7], 2.282192], ['boundary control 1', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 16], 0.05, 2, 7], 'settlement outside period'], ['boundary control 2', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 15], 0.05, 2, 7], 0.0], ['normal control 1', [[2030, 12, 17], [2031, 12, 17], [2031, 12, 9], 0.05, 1, 0], 4.890411], ['normal control 2', [[2054, 12, 24], [2055, 1, 24], [2054, 12, 24], 0.0125, 12, 0], 0.0]], [['regression period denominator 1', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 8], 0.05, 2, 7], -0.096154], ['regression period denominator 2', [[2047, 11, 28], [2047, 12, 28], [2047, 12, 12], 0.0125, 12, 5], 0.048611], ['partial repair probe 1', [[2028, 6, 30], [2029, 6, 30], [2029, 6, 26], 0.05, 1, 7], -0.054795], ['partial repair probe 2', [[2000, 7, 1], [2001, 7, 1], [2001, 1, 25], 0.0125, 1, 10], 0.712329], ['boundary control 1', [[2024, 1, 15], [2024, 7, 15], [2024, 1, 15], 0.05, 2, 7], 0.0], ['boundary control 2', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 16], 0.05, 2, 7], 'settlement outside period'], ['normal control 1', [[2050, 4, 30], [2051, 4, 30], [2050, 5, 10], 0.0125, 1, 7], 0.034247], ['normal control 2', [[2033, 5, 12], [2033, 6, 12], [2033, 5, 12], 0.05, 12, 7], 0.0]], [['regression period denominator 1', [[2025, 5, 28], [2025, 8, 28], [2025, 7, 18], 0.02, 4, 7], 0.277174], ['regression period denominator 2', [[2009, 12, 30], [2010, 6, 30], [2010, 6, 24], 0.04125, 2, 0], 1.994505], ['partial repair probe 1', [[2016, 10, 10], [2017, 10, 10], [2017, 10, 1], 0.0625, 1, 7], 6.09589], ['partial repair probe 2', [[2028, 4, 24], [2029, 4, 24], [2028, 7, 14], 0.04125, 1, 5], 0.915411], ['boundary control 1', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 16], 0.05, 2, 7], 'settlement outside period'], ['boundary control 2', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 15], 0.05, 2, 7], 0.0], ['normal control 1', [[2049, 11, 30], [2050, 11, 30], [2050, 11, 30], 0.0625, 1, 0], 0.0], ['normal control 2', [[2022, 8, 18], [2023, 8, 18], [2023, 8, 11], 0.04125, 1, 10], -0.07911]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression period denominator 1 | 1.337432 | 1.34478 | Failed |
| regression period denominator 2 | 0.34153 | 0.336022 | Failed |
| partial repair probe 1 | -0.078893 | -0.07911 | Failed |
| partial repair probe 2 | 0.022541 | 0.022603 | Failed |
| boundary control 1 | settlement outside period | settlement outside period | Passed |
| boundary control 2 | 0.0 | 0.0 | Passed |
| normal control 1 | 0.0 | 0.0 | Passed |
| normal control 2 | 0.0 | 0.0 | Passed |
SHA-256 / 561e8dea70906fc6ae80bfc991e5dbf0b1fcba7c809f43979cfe6850e49da2a9
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import datetime
from fractions import Fraction
N = 1
observations = []
def solve(prev, nxt, settle, rate, freq, exdays):
P = datetime.date(*prev)
Q = datetime.date(*nxt)
S = datetime.date(*settle)
if not (P <= S <= Q):
return 'settlement outside period'
if S == Q:
return 0.0
period = (Q - P).days
coupon = 100 * rate / freq
if (Q - S).days <= exdays:
return round(-coupon * (Q - S).days / period, 6)
days = (S - P).days
return round(coupon * days / period, 6)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression period denominator 1', [[2036, 2, 15], [2036, 8, 15], [2036, 8, 11], 0.0275, 2, 3], 1.34478], ['regression period denominator 2', [[2048, 7, 1], [2048, 8, 1], [2048, 7, 26], 0.05, 12, 0], 0.336022], ['partial repair probe 1', [[2012, 8, 30], [2013, 8, 30], [2013, 8, 23], 0.04125, 1, 7], -0.07911], ['partial repair probe 2', [[2004, 5, 31], [2005, 5, 31], [2004, 6, 3], 0.0275, 1, 3], 0.022603], ['boundary control 1', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 16], 0.05, 2, 7], 'settlement outside period'], ['boundary control 2', [[2024, 1, 15], [2024, 7, 15], [2024, 1, 15], 0.05, 2, 7], 0.0], ['normal control 1', [[2045, 1, 31], [2045, 2, 28], [2045, 2, 28], 0.04125, 12, 10], 0.0], ['normal control 2', [[2041, 2, 1], [2041, 8, 1], [2041, 2, 1], 0.05, 2, 0], 0.0]], [['regression period denominator 1', [[2035, 4, 29], [2036, 4, 29], [2035, 8, 11], 0.02, 1, 7], 0.568306], ['regression period denominator 2', [[2011, 3, 31], [2012, 3, 31], [2011, 6, 26], 0.035, 1, 7], 0.831967], ['partial repair probe 1', [[2060, 7, 14], [2061, 7, 14], [2061, 7, 8], 0.04125, 1, 0], 4.057192], ['partial repair probe 2', [[2044, 3, 7], [2045, 3, 7], [2045, 2, 25], 0.04125, 1, 0], 4.011986], ['boundary control 1', [[2024, 1, 15], [2024, 7, 15], [2024, 1, 15], 0.05, 2, 7], 0.0], ['boundary control 2', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 15], 0.05, 2, 7], 0.0], ['normal control 1', [[2037, 7, 30], [2038, 7, 30], [2037, 10, 8], 0.0125, 1, 5], 0.239726], ['normal control 2', [[2016, 3, 13], [2016, 4, 13], [2016, 3, 13], 0.035, 12, 0], 0.0]], [['regression period denominator 1', [[2054, 4, 30], [2054, 7, 30], [2054, 7, 20], 0.04125, 4, 0], 0.917926], ['regression period denominator 2', [[2006, 11, 7], [2006, 12, 7], [2006, 12, 5], 0.0275, 12, 3], -0.015278], ['partial repair probe 1', [[2048, 8, 20], [2049, 8, 20], [2048, 8, 29], 0.02, 1, 10], 0.049315], ['partial repair probe 2', [[2036, 12, 24], [2037, 12, 24], [2037, 4, 22], 0.07, 1, 7], 2.282192], ['boundary control 1', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 16], 0.05, 2, 7], 'settlement outside period'], ['boundary control 2', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 15], 0.05, 2, 7], 0.0], ['normal control 1', [[2030, 12, 17], [2031, 12, 17], [2031, 12, 9], 0.05, 1, 0], 4.890411], ['normal control 2', [[2054, 12, 24], [2055, 1, 24], [2054, 12, 24], 0.0125, 12, 0], 0.0]], [['regression period denominator 1', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 8], 0.05, 2, 7], -0.096154], ['regression period denominator 2', [[2047, 11, 28], [2047, 12, 28], [2047, 12, 12], 0.0125, 12, 5], 0.048611], ['partial repair probe 1', [[2028, 6, 30], [2029, 6, 30], [2029, 6, 26], 0.05, 1, 7], -0.054795], ['partial repair probe 2', [[2000, 7, 1], [2001, 7, 1], [2001, 1, 25], 0.0125, 1, 10], 0.712329], ['boundary control 1', [[2024, 1, 15], [2024, 7, 15], [2024, 1, 15], 0.05, 2, 7], 0.0], ['boundary control 2', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 16], 0.05, 2, 7], 'settlement outside period'], ['normal control 1', [[2050, 4, 30], [2051, 4, 30], [2050, 5, 10], 0.0125, 1, 7], 0.034247], ['normal control 2', [[2033, 5, 12], [2033, 6, 12], [2033, 5, 12], 0.05, 12, 7], 0.0]], [['regression period denominator 1', [[2025, 5, 28], [2025, 8, 28], [2025, 7, 18], 0.02, 4, 7], 0.277174], ['regression period denominator 2', [[2009, 12, 30], [2010, 6, 30], [2010, 6, 24], 0.04125, 2, 0], 1.994505], ['partial repair probe 1', [[2016, 10, 10], [2017, 10, 10], [2017, 10, 1], 0.0625, 1, 7], 6.09589], ['partial repair probe 2', [[2028, 4, 24], [2029, 4, 24], [2028, 7, 14], 0.04125, 1, 5], 0.915411], ['boundary control 1', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 16], 0.05, 2, 7], 'settlement outside period'], ['boundary control 2', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 15], 0.05, 2, 7], 0.0], ['normal control 1', [[2049, 11, 30], [2050, 11, 30], [2050, 11, 30], 0.0625, 1, 0], 0.0], ['normal control 2', [[2022, 8, 18], [2023, 8, 18], [2023, 8, 11], 0.04125, 1, 10], -0.07911]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression period denominator 1 | 1.34478 | 1.34478 | Passed |
| regression period denominator 2 | 0.336022 | 0.336022 | Passed |
| partial repair probe 1 | -0.07911 | -0.07911 | Passed |
| partial repair probe 2 | 0.022603 | 0.022603 | Passed |
| boundary control 1 | settlement outside period | settlement outside period | Passed |
| boundary control 2 | 0.0 | 0.0 | Passed |
| normal control 1 | 0.0 | 0.0 | Passed |
| normal control 2 | 0.0 | 0.0 | Passed |
SHA-256 / 666f86f835ba81b5b27cbfb8c0b97bb18b8db8ee770ec61abffa4a97c0657b7d
Verification & scope
A deterministic toy contract stated explicitly in the contract field; no claim of conformance to any published convention text. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:46:49.871198+00:00.
Case digest / 3c6a189d1fe94167b54940adc8fcfce90f21ee294c54fd05e729a07b7373836f