FA-60901 / Bond day-count conventions / Open access
ICMA regular-period accrued with ex-coupon: ex-coupon trades report positive accrued interest · case 01
Buyers in the ex-coupon window are charged interest they will never receive.
ROOT CAUSE
The ex-coupon branch returns the rebate magnitude without the negative sign.
VERIFIED REPAIR
Return the negative of coupon times days to the next coupon over the period length.
Unsuccessful approach: Negating but measuring days from the previous coupon produces the wrong rebate size.
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 = (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 ex-coupon rebate sign 1', [[2031, 2, 28], [2031, 3, 28], [2031, 3, 22], 0.0625, 12, 7], -0.111607], ['regression ex-coupon rebate sign 2', [[2033, 7, 30], [2034, 7, 30], [2034, 7, 25], 0.035, 1, 10], -0.047945], ['partial repair probe 1', [[2008, 5, 27], [2008, 8, 27], [2008, 8, 20], 0.0125, 4, 7], -0.023777], ['partial repair probe 2', [[2059, 1, 29], [2059, 7, 29], [2059, 7, 24], 0.05, 2, 7], -0.069061], ['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, 7], 0.05, 2, 7], 2.39011], ['normal control 1', [[2008, 8, 31], [2008, 9, 30], [2008, 9, 30], 0.035, 12, 0], 0.0], ['normal control 2', [[2052, 10, 28], [2053, 4, 28], [2052, 10, 28], 0.0275, 2, 3], 0.0]], [['regression ex-coupon rebate sign 1', [[2050, 8, 31], [2050, 11, 30], [2050, 11, 25], 0.05, 4, 5], -0.068681], ['regression ex-coupon rebate sign 2', [[2036, 10, 18], [2037, 4, 18], [2037, 4, 15], 0.0625, 2, 10], -0.051511], ['partial repair probe 1', [[2029, 9, 30], [2029, 12, 30], [2029, 12, 26], 0.07, 4, 7], -0.076923], ['partial repair probe 2', [[2038, 9, 17], [2038, 10, 17], [2038, 10, 8], 0.04125, 12, 10], -0.103125], ['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, 7], 0.05, 2, 7], 2.39011], ['normal control 1', [[2024, 7, 28], [2025, 1, 28], [2024, 12, 17], 0.07, 2, 3], 2.701087], ['normal control 2', [[2015, 5, 1], [2015, 8, 1], [2015, 7, 17], 0.04125, 4, 0], 0.863111]], [['regression ex-coupon rebate sign 1', [[2029, 1, 29], [2029, 2, 28], [2029, 2, 26], 0.035, 12, 10], -0.019444], ['regression ex-coupon rebate sign 2', [[2047, 8, 29], [2047, 11, 29], [2047, 11, 27], 0.035, 4, 7], -0.019022], ['partial repair probe 1', [[2032, 8, 8], [2032, 9, 8], [2032, 9, 7], 0.0125, 12, 10], -0.00336], ['partial repair probe 2', [[2013, 4, 27], [2013, 10, 27], [2013, 10, 26], 0.04125, 2, 3], -0.01127], ['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', [[2028, 1, 31], [2028, 2, 29], [2028, 1, 31], 0.0125, 12, 7], 0.0], ['normal control 2', [[2044, 7, 23], [2044, 10, 23], [2044, 9, 12], 0.04125, 4, 0], 0.571671]], [['regression ex-coupon rebate sign 1', [[2042, 8, 31], [2043, 2, 28], [2043, 2, 25], 0.0125, 2, 10], -0.010359], ['regression ex-coupon rebate sign 2', [[2022, 7, 31], [2022, 10, 31], [2022, 10, 25], 0.04125, 4, 7], -0.067255], ['partial repair probe 1', [[2012, 4, 27], [2012, 7, 27], [2012, 7, 23], 0.0275, 4, 5], -0.03022], ['partial repair probe 2', [[2032, 10, 31], [2032, 11, 30], [2032, 11, 24], 0.05, 12, 10], -0.083333], ['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', [[2003, 4, 12], [2003, 10, 12], [2003, 10, 7], 0.0275, 2, 0], 1.337432], ['normal control 2', [[2056, 10, 21], [2056, 11, 21], [2056, 11, 12], 0.04125, 12, 7], 0.243952]], [['regression ex-coupon rebate sign 1', [[2060, 1, 31], [2060, 2, 29], [2060, 2, 22], 0.0625, 12, 10], -0.125718], ['regression ex-coupon rebate sign 2', [[2016, 11, 2], [2017, 2, 2], [2017, 1, 29], 0.05, 4, 5], -0.054348], ['partial repair probe 1', [[2052, 11, 21], [2052, 12, 21], [2052, 12, 14], 0.04125, 12, 10], -0.080208], ['partial repair probe 2', [[2020, 1, 31], [2020, 4, 30], [2020, 4, 29], 0.05, 4, 7], -0.013889], ['boundary control 1', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 7], 0.05, 2, 7], 2.39011], ['boundary control 2', [[2024, 1, 15], [2024, 7, 15], [2024, 1, 15], 0.05, 2, 7], 0.0], ['normal control 1', [[2036, 10, 10], [2037, 4, 10], [2037, 3, 30], 0.035, 2, 10], 1.644231], ['normal control 2', [[2044, 2, 25], [2044, 5, 25], [2044, 5, 16], 0.0125, 4, 0], 0.28125]]]
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 ex-coupon rebate sign 1 | 0.111607 | -0.111607 | Failed |
| regression ex-coupon rebate sign 2 | 0.047945 | -0.047945 | Failed |
| partial repair probe 1 | 0.023777 | -0.023777 | Failed |
| partial repair probe 2 | 0.069061 | -0.069061 | Failed |
| boundary control 1 | settlement outside period | settlement outside period | Passed |
| boundary control 2 | 2.39011 | 2.39011 | Passed |
| normal control 1 | 0.0 | 0.0 | Passed |
| normal control 2 | 0.0 | 0.0 | Passed |
SHA-256 / 517271e1ff2470c72fe0494a3178c634dfe9df34fe673277ed056685b27bb245
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 = (Q - P).days
coupon = 100 * rate / freq
if (Q - S).days <= exdays:
return round(-coupon * (S - P).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 ex-coupon rebate sign 1', [[2031, 2, 28], [2031, 3, 28], [2031, 3, 22], 0.0625, 12, 7], -0.111607], ['regression ex-coupon rebate sign 2', [[2033, 7, 30], [2034, 7, 30], [2034, 7, 25], 0.035, 1, 10], -0.047945], ['partial repair probe 1', [[2008, 5, 27], [2008, 8, 27], [2008, 8, 20], 0.0125, 4, 7], -0.023777], ['partial repair probe 2', [[2059, 1, 29], [2059, 7, 29], [2059, 7, 24], 0.05, 2, 7], -0.069061], ['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, 7], 0.05, 2, 7], 2.39011], ['normal control 1', [[2008, 8, 31], [2008, 9, 30], [2008, 9, 30], 0.035, 12, 0], 0.0], ['normal control 2', [[2052, 10, 28], [2053, 4, 28], [2052, 10, 28], 0.0275, 2, 3], 0.0]], [['regression ex-coupon rebate sign 1', [[2050, 8, 31], [2050, 11, 30], [2050, 11, 25], 0.05, 4, 5], -0.068681], ['regression ex-coupon rebate sign 2', [[2036, 10, 18], [2037, 4, 18], [2037, 4, 15], 0.0625, 2, 10], -0.051511], ['partial repair probe 1', [[2029, 9, 30], [2029, 12, 30], [2029, 12, 26], 0.07, 4, 7], -0.076923], ['partial repair probe 2', [[2038, 9, 17], [2038, 10, 17], [2038, 10, 8], 0.04125, 12, 10], -0.103125], ['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, 7], 0.05, 2, 7], 2.39011], ['normal control 1', [[2024, 7, 28], [2025, 1, 28], [2024, 12, 17], 0.07, 2, 3], 2.701087], ['normal control 2', [[2015, 5, 1], [2015, 8, 1], [2015, 7, 17], 0.04125, 4, 0], 0.863111]], [['regression ex-coupon rebate sign 1', [[2029, 1, 29], [2029, 2, 28], [2029, 2, 26], 0.035, 12, 10], -0.019444], ['regression ex-coupon rebate sign 2', [[2047, 8, 29], [2047, 11, 29], [2047, 11, 27], 0.035, 4, 7], -0.019022], ['partial repair probe 1', [[2032, 8, 8], [2032, 9, 8], [2032, 9, 7], 0.0125, 12, 10], -0.00336], ['partial repair probe 2', [[2013, 4, 27], [2013, 10, 27], [2013, 10, 26], 0.04125, 2, 3], -0.01127], ['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', [[2028, 1, 31], [2028, 2, 29], [2028, 1, 31], 0.0125, 12, 7], 0.0], ['normal control 2', [[2044, 7, 23], [2044, 10, 23], [2044, 9, 12], 0.04125, 4, 0], 0.571671]], [['regression ex-coupon rebate sign 1', [[2042, 8, 31], [2043, 2, 28], [2043, 2, 25], 0.0125, 2, 10], -0.010359], ['regression ex-coupon rebate sign 2', [[2022, 7, 31], [2022, 10, 31], [2022, 10, 25], 0.04125, 4, 7], -0.067255], ['partial repair probe 1', [[2012, 4, 27], [2012, 7, 27], [2012, 7, 23], 0.0275, 4, 5], -0.03022], ['partial repair probe 2', [[2032, 10, 31], [2032, 11, 30], [2032, 11, 24], 0.05, 12, 10], -0.083333], ['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', [[2003, 4, 12], [2003, 10, 12], [2003, 10, 7], 0.0275, 2, 0], 1.337432], ['normal control 2', [[2056, 10, 21], [2056, 11, 21], [2056, 11, 12], 0.04125, 12, 7], 0.243952]], [['regression ex-coupon rebate sign 1', [[2060, 1, 31], [2060, 2, 29], [2060, 2, 22], 0.0625, 12, 10], -0.125718], ['regression ex-coupon rebate sign 2', [[2016, 11, 2], [2017, 2, 2], [2017, 1, 29], 0.05, 4, 5], -0.054348], ['partial repair probe 1', [[2052, 11, 21], [2052, 12, 21], [2052, 12, 14], 0.04125, 12, 10], -0.080208], ['partial repair probe 2', [[2020, 1, 31], [2020, 4, 30], [2020, 4, 29], 0.05, 4, 7], -0.013889], ['boundary control 1', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 7], 0.05, 2, 7], 2.39011], ['boundary control 2', [[2024, 1, 15], [2024, 7, 15], [2024, 1, 15], 0.05, 2, 7], 0.0], ['normal control 1', [[2036, 10, 10], [2037, 4, 10], [2037, 3, 30], 0.035, 2, 10], 1.644231], ['normal control 2', [[2044, 2, 25], [2044, 5, 25], [2044, 5, 16], 0.0125, 4, 0], 0.28125]]]
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 ex-coupon rebate sign 1 | -0.409226 | -0.111607 | Failed |
| regression ex-coupon rebate sign 2 | -3.452055 | -0.047945 | Failed |
| partial repair probe 1 | -0.288723 | -0.023777 | Failed |
| partial repair probe 2 | -2.430939 | -0.069061 | Failed |
| boundary control 1 | settlement outside period | settlement outside period | Passed |
| boundary control 2 | 2.39011 | 2.39011 | Passed |
| normal control 1 | 0.0 | 0.0 | Passed |
| normal control 2 | 0.0 | 0.0 | Passed |
SHA-256 / 63fcc50823dbc0ccfb7305ec3e8a5d5f1690dd1ee926c4a1d3f633be0c1ad04f
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 ex-coupon rebate sign 1', [[2031, 2, 28], [2031, 3, 28], [2031, 3, 22], 0.0625, 12, 7], -0.111607], ['regression ex-coupon rebate sign 2', [[2033, 7, 30], [2034, 7, 30], [2034, 7, 25], 0.035, 1, 10], -0.047945], ['partial repair probe 1', [[2008, 5, 27], [2008, 8, 27], [2008, 8, 20], 0.0125, 4, 7], -0.023777], ['partial repair probe 2', [[2059, 1, 29], [2059, 7, 29], [2059, 7, 24], 0.05, 2, 7], -0.069061], ['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, 7], 0.05, 2, 7], 2.39011], ['normal control 1', [[2008, 8, 31], [2008, 9, 30], [2008, 9, 30], 0.035, 12, 0], 0.0], ['normal control 2', [[2052, 10, 28], [2053, 4, 28], [2052, 10, 28], 0.0275, 2, 3], 0.0]], [['regression ex-coupon rebate sign 1', [[2050, 8, 31], [2050, 11, 30], [2050, 11, 25], 0.05, 4, 5], -0.068681], ['regression ex-coupon rebate sign 2', [[2036, 10, 18], [2037, 4, 18], [2037, 4, 15], 0.0625, 2, 10], -0.051511], ['partial repair probe 1', [[2029, 9, 30], [2029, 12, 30], [2029, 12, 26], 0.07, 4, 7], -0.076923], ['partial repair probe 2', [[2038, 9, 17], [2038, 10, 17], [2038, 10, 8], 0.04125, 12, 10], -0.103125], ['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, 7], 0.05, 2, 7], 2.39011], ['normal control 1', [[2024, 7, 28], [2025, 1, 28], [2024, 12, 17], 0.07, 2, 3], 2.701087], ['normal control 2', [[2015, 5, 1], [2015, 8, 1], [2015, 7, 17], 0.04125, 4, 0], 0.863111]], [['regression ex-coupon rebate sign 1', [[2029, 1, 29], [2029, 2, 28], [2029, 2, 26], 0.035, 12, 10], -0.019444], ['regression ex-coupon rebate sign 2', [[2047, 8, 29], [2047, 11, 29], [2047, 11, 27], 0.035, 4, 7], -0.019022], ['partial repair probe 1', [[2032, 8, 8], [2032, 9, 8], [2032, 9, 7], 0.0125, 12, 10], -0.00336], ['partial repair probe 2', [[2013, 4, 27], [2013, 10, 27], [2013, 10, 26], 0.04125, 2, 3], -0.01127], ['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', [[2028, 1, 31], [2028, 2, 29], [2028, 1, 31], 0.0125, 12, 7], 0.0], ['normal control 2', [[2044, 7, 23], [2044, 10, 23], [2044, 9, 12], 0.04125, 4, 0], 0.571671]], [['regression ex-coupon rebate sign 1', [[2042, 8, 31], [2043, 2, 28], [2043, 2, 25], 0.0125, 2, 10], -0.010359], ['regression ex-coupon rebate sign 2', [[2022, 7, 31], [2022, 10, 31], [2022, 10, 25], 0.04125, 4, 7], -0.067255], ['partial repair probe 1', [[2012, 4, 27], [2012, 7, 27], [2012, 7, 23], 0.0275, 4, 5], -0.03022], ['partial repair probe 2', [[2032, 10, 31], [2032, 11, 30], [2032, 11, 24], 0.05, 12, 10], -0.083333], ['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', [[2003, 4, 12], [2003, 10, 12], [2003, 10, 7], 0.0275, 2, 0], 1.337432], ['normal control 2', [[2056, 10, 21], [2056, 11, 21], [2056, 11, 12], 0.04125, 12, 7], 0.243952]], [['regression ex-coupon rebate sign 1', [[2060, 1, 31], [2060, 2, 29], [2060, 2, 22], 0.0625, 12, 10], -0.125718], ['regression ex-coupon rebate sign 2', [[2016, 11, 2], [2017, 2, 2], [2017, 1, 29], 0.05, 4, 5], -0.054348], ['partial repair probe 1', [[2052, 11, 21], [2052, 12, 21], [2052, 12, 14], 0.04125, 12, 10], -0.080208], ['partial repair probe 2', [[2020, 1, 31], [2020, 4, 30], [2020, 4, 29], 0.05, 4, 7], -0.013889], ['boundary control 1', [[2024, 1, 15], [2024, 7, 15], [2024, 7, 7], 0.05, 2, 7], 2.39011], ['boundary control 2', [[2024, 1, 15], [2024, 7, 15], [2024, 1, 15], 0.05, 2, 7], 0.0], ['normal control 1', [[2036, 10, 10], [2037, 4, 10], [2037, 3, 30], 0.035, 2, 10], 1.644231], ['normal control 2', [[2044, 2, 25], [2044, 5, 25], [2044, 5, 16], 0.0125, 4, 0], 0.28125]]]
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 ex-coupon rebate sign 1 | -0.111607 | -0.111607 | Passed |
| regression ex-coupon rebate sign 2 | -0.047945 | -0.047945 | Passed |
| partial repair probe 1 | -0.023777 | -0.023777 | Passed |
| partial repair probe 2 | -0.069061 | -0.069061 | Passed |
| boundary control 1 | settlement outside period | settlement outside period | Passed |
| boundary control 2 | 2.39011 | 2.39011 | Passed |
| normal control 1 | 0.0 | 0.0 | Passed |
| normal control 2 | 0.0 | 0.0 | Passed |
SHA-256 / bf2ffdca38861348f924d58c0b087b54623599aa49c3dbab97104cc4675f98f1
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.974412+00:00.
Case digest / a0a7f7757152bd7fa15dd61beed4572b14dad1f7d2de1a395e023b7c9d95c596