FA-61051 / Bond day-count conventions / Open access
Irregular first coupon accrued by quasi-coupon periods: a long first period is accrued as one regular period · case 01
Accrued interest on long first coupons exceeds the per-period coupon once more than one quasi period has passed.
ROOT CAUSE
The accrual divides days since issue by a single regular period length instead of summing quasi periods.
VERIFIED REPAIR
Split the first period into quasi-coupon periods and accrue each against its own length.
Unsuccessful approach: Dividing by the whole irregular period length understates accrual for long first coupons.
Case contract
Inputs issue, first coupon and settlement [y,m,d], annual rate and months per period. Settlement must lie in [issue, first] else return "settlement outside first period". Quasi-coupon dates are generated back from the first coupon in steps of months (day clamped to month length) until one is on or before issue. For each quasi period [start, end), accrued days are those in [max(start, issue), min(end, settle)) and are divided by that quasi period length. Accrued = 100*rate/freq*sum, rounded 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(issue, first, settle, rate, months):
def mlen(y, m):
if m == 2:
return 29 if (y % 4 == 0 and y % 100 != 0) or y % 400 == 0 else 28
return 30 if m in (4, 6, 9, 11) else 31
I = datetime.date(*issue)
Fc = datetime.date(*first)
S = datetime.date(*settle)
if not (I <= S <= Fc):
return 'settlement outside first period'
freq = 12 // months
def back(k):
t = Fc.year * 12 + Fc.month - 1 - k * months
y, m = t // 12, t % 12 + 1
return datetime.date(y, m, min(Fc.day, mlen(y, m)))
q = [Fc]
k = 1
while q[-1] > I:
q.append(back(k))
k += 1
frac = Fraction(0)
frac = Fraction((S - I).days, (Fc - q[1]).days)
return round(float(100 * Fraction(str(rate)) / freq * frac), 6)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression long period denominator 1', [[2035, 3, 1], [2036, 10, 31], [2035, 6, 11], 0.08, 12], 2.235616], ['regression long period denominator 2', [[2014, 1, 18], [2014, 6, 30], [2014, 6, 25], 0.0675, 3], 2.927038], ['partial repair probe 1', [[2026, 7, 10], [2027, 7, 24], [2027, 5, 13], 0.045, 12], 3.784932], ['partial repair probe 2', [[2007, 5, 31], [2007, 9, 30], [2007, 9, 10], 0.045, 6], 1.247283], ['boundary control 1', [[2024, 3, 15], [2024, 9, 15], [2024, 6, 15], 0.05, 6], 1.25], ['boundary control 2', [[2024, 1, 10], [2024, 9, 15], [2024, 9, 16], 0.05, 6], 'settlement outside first period'], ['normal control 1', [[2030, 12, 1], [2030, 12, 25], [2030, 11, 28], 0.045, 1], 'settlement outside first period'], ['normal control 2', [[2011, 8, 6], [2013, 1, 29], [2011, 8, 3], 0.045, 6], 'settlement outside first period']], [['regression long period denominator 1', [[2026, 6, 29], [2027, 12, 28], [2027, 3, 29], 0.05, 6], 3.736339], ['regression long period denominator 2', [[2020, 9, 30], [2021, 10, 30], [2021, 7, 11], 0.0675, 6], 5.256148], ['partial repair probe 1', [[2034, 5, 27], [2035, 1, 31], [2034, 9, 6], 0.02, 12], 0.558904], ['partial repair probe 2', [[2005, 3, 11], [2005, 8, 16], [2005, 4, 24], 0.05, 6], 0.607735], ['boundary control 1', [[2024, 3, 15], [2024, 9, 15], [2024, 6, 15], 0.05, 6], 1.25], ['boundary control 2', [[2024, 1, 10], [2024, 9, 15], [2024, 9, 16], 0.05, 6], 'settlement outside first period'], ['normal control 1', [[2021, 4, 20], [2021, 4, 30], [2021, 4, 20], 0.0675, 1], 0.0], ['normal control 2', [[2011, 6, 28], [2011, 7, 5], [2011, 6, 25], 0.045, 1], 'settlement outside first period']], [['regression long period denominator 1', [[2006, 2, 8], [2006, 3, 23], [2006, 3, 1], 0.045, 1], 0.261809], ['regression long period denominator 2', [[2004, 10, 16], [2005, 9, 5], [2004, 11, 30], 0.08, 6], 0.994475], ['partial repair probe 1', [[2020, 11, 13], [2020, 12, 31], [2020, 12, 31], 0.03, 3], 0.391304], ['partial repair probe 2', [[2042, 7, 29], [2043, 1, 31], [2042, 12, 9], 0.0675, 12], 2.459589], ['boundary control 1', [[2024, 1, 10], [2024, 9, 15], [2024, 9, 16], 0.05, 6], 'settlement outside first period'], ['boundary control 2', [[2024, 3, 15], [2024, 9, 15], [2024, 6, 15], 0.05, 6], 1.25], ['normal control 1', [[2041, 6, 16], [2041, 8, 30], [2041, 6, 13], 0.08, 12], 'settlement outside first period'], ['normal control 2', [[2005, 1, 10], [2005, 1, 31], [2005, 1, 10], 0.045, 3], 0.0]], [['regression long period denominator 1', [[2036, 7, 10], [2037, 2, 28], [2037, 2, 16], 0.0675, 6], 4.063545], ['regression long period denominator 2', [[2006, 9, 2], [2007, 3, 12], [2006, 12, 9], 0.045, 6], 1.216205], ['partial repair probe 1', [[2029, 5, 22], [2029, 7, 31], [2029, 7, 11], 0.0675, 6], 0.93232], ['partial repair probe 2', [[2033, 9, 26], [2034, 2, 28], [2033, 10, 19], 0.0675, 3], 0.421875], ['boundary control 1', [[2024, 1, 10], [2024, 9, 15], [2024, 9, 16], 0.05, 6], 'settlement outside first period'], ['boundary control 2', [[2024, 3, 15], [2024, 9, 15], [2024, 6, 15], 0.05, 6], 1.25], ['normal control 1', [[2004, 9, 16], [2004, 9, 30], [2004, 9, 16], 0.045, 3], 0.0], ['normal control 2', [[2035, 5, 9], [2035, 6, 30], [2035, 5, 6], 0.03, 6], 'settlement outside first period']], [['regression long period denominator 1', [[2019, 3, 16], [2019, 4, 30], [2019, 4, 4], 0.03, 1], 0.156989], ['regression long period denominator 2', [[2039, 4, 20], [2039, 6, 30], [2039, 5, 24], 0.02, 1], 0.187097], ['partial repair probe 1', [[2031, 8, 23], [2031, 12, 28], [2031, 9, 3], 0.045, 6], 0.135246], ['partial repair probe 2', [[2017, 9, 1], [2018, 2, 28], [2018, 2, 6], 0.0675, 12], 2.921918], ['boundary control 1', [[2024, 3, 15], [2024, 9, 15], [2024, 6, 15], 0.05, 6], 1.25], ['boundary control 2', [[2024, 1, 10], [2024, 9, 15], [2024, 9, 16], 0.05, 6], 'settlement outside first period'], ['normal control 1', [[2034, 7, 30], [2034, 11, 30], [2034, 7, 27], 0.045, 12], 'settlement outside first period'], ['normal control 2', [[2041, 1, 14], [2041, 1, 24], [2041, 1, 14], 0.03, 1], 0.0]]]
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 long period denominator 1 | 2.229508 | 2.235616 | Failed |
| regression long period denominator 2 | 2.898098 | 2.927038 | Failed |
| partial repair probe 1 | 3.784932 | 3.784932 | Passed |
| partial repair probe 2 | 1.247283 | 1.247283 | Passed |
| boundary control 1 | 1.25 | 1.25 | Passed |
| boundary control 2 | settlement outside first period | settlement outside first period | Passed |
| normal control 1 | settlement outside first period | settlement outside first period | Passed |
| normal control 2 | settlement outside first period | settlement outside first period | Passed |
SHA-256 / d569fb326d6e2a2a5b00d508cdd00337c3a401db9b6f41a6514dfd333027c007
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(issue, first, settle, rate, months):
def mlen(y, m):
if m == 2:
return 29 if (y % 4 == 0 and y % 100 != 0) or y % 400 == 0 else 28
return 30 if m in (4, 6, 9, 11) else 31
I = datetime.date(*issue)
Fc = datetime.date(*first)
S = datetime.date(*settle)
if not (I <= S <= Fc):
return 'settlement outside first period'
freq = 12 // months
def back(k):
t = Fc.year * 12 + Fc.month - 1 - k * months
y, m = t // 12, t % 12 + 1
return datetime.date(y, m, min(Fc.day, mlen(y, m)))
q = [Fc]
k = 1
while q[-1] > I:
q.append(back(k))
k += 1
frac = Fraction(0)
frac = Fraction((S - I).days, (Fc - I).days)
return round(float(100 * Fraction(str(rate)) / freq * frac), 6)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression long period denominator 1', [[2035, 3, 1], [2036, 10, 31], [2035, 6, 11], 0.08, 12], 2.235616], ['regression long period denominator 2', [[2014, 1, 18], [2014, 6, 30], [2014, 6, 25], 0.0675, 3], 2.927038], ['partial repair probe 1', [[2026, 7, 10], [2027, 7, 24], [2027, 5, 13], 0.045, 12], 3.784932], ['partial repair probe 2', [[2007, 5, 31], [2007, 9, 30], [2007, 9, 10], 0.045, 6], 1.247283], ['boundary control 1', [[2024, 3, 15], [2024, 9, 15], [2024, 6, 15], 0.05, 6], 1.25], ['boundary control 2', [[2024, 1, 10], [2024, 9, 15], [2024, 9, 16], 0.05, 6], 'settlement outside first period'], ['normal control 1', [[2030, 12, 1], [2030, 12, 25], [2030, 11, 28], 0.045, 1], 'settlement outside first period'], ['normal control 2', [[2011, 8, 6], [2013, 1, 29], [2011, 8, 3], 0.045, 6], 'settlement outside first period']], [['regression long period denominator 1', [[2026, 6, 29], [2027, 12, 28], [2027, 3, 29], 0.05, 6], 3.736339], ['regression long period denominator 2', [[2020, 9, 30], [2021, 10, 30], [2021, 7, 11], 0.0675, 6], 5.256148], ['partial repair probe 1', [[2034, 5, 27], [2035, 1, 31], [2034, 9, 6], 0.02, 12], 0.558904], ['partial repair probe 2', [[2005, 3, 11], [2005, 8, 16], [2005, 4, 24], 0.05, 6], 0.607735], ['boundary control 1', [[2024, 3, 15], [2024, 9, 15], [2024, 6, 15], 0.05, 6], 1.25], ['boundary control 2', [[2024, 1, 10], [2024, 9, 15], [2024, 9, 16], 0.05, 6], 'settlement outside first period'], ['normal control 1', [[2021, 4, 20], [2021, 4, 30], [2021, 4, 20], 0.0675, 1], 0.0], ['normal control 2', [[2011, 6, 28], [2011, 7, 5], [2011, 6, 25], 0.045, 1], 'settlement outside first period']], [['regression long period denominator 1', [[2006, 2, 8], [2006, 3, 23], [2006, 3, 1], 0.045, 1], 0.261809], ['regression long period denominator 2', [[2004, 10, 16], [2005, 9, 5], [2004, 11, 30], 0.08, 6], 0.994475], ['partial repair probe 1', [[2020, 11, 13], [2020, 12, 31], [2020, 12, 31], 0.03, 3], 0.391304], ['partial repair probe 2', [[2042, 7, 29], [2043, 1, 31], [2042, 12, 9], 0.0675, 12], 2.459589], ['boundary control 1', [[2024, 1, 10], [2024, 9, 15], [2024, 9, 16], 0.05, 6], 'settlement outside first period'], ['boundary control 2', [[2024, 3, 15], [2024, 9, 15], [2024, 6, 15], 0.05, 6], 1.25], ['normal control 1', [[2041, 6, 16], [2041, 8, 30], [2041, 6, 13], 0.08, 12], 'settlement outside first period'], ['normal control 2', [[2005, 1, 10], [2005, 1, 31], [2005, 1, 10], 0.045, 3], 0.0]], [['regression long period denominator 1', [[2036, 7, 10], [2037, 2, 28], [2037, 2, 16], 0.0675, 6], 4.063545], ['regression long period denominator 2', [[2006, 9, 2], [2007, 3, 12], [2006, 12, 9], 0.045, 6], 1.216205], ['partial repair probe 1', [[2029, 5, 22], [2029, 7, 31], [2029, 7, 11], 0.0675, 6], 0.93232], ['partial repair probe 2', [[2033, 9, 26], [2034, 2, 28], [2033, 10, 19], 0.0675, 3], 0.421875], ['boundary control 1', [[2024, 1, 10], [2024, 9, 15], [2024, 9, 16], 0.05, 6], 'settlement outside first period'], ['boundary control 2', [[2024, 3, 15], [2024, 9, 15], [2024, 6, 15], 0.05, 6], 1.25], ['normal control 1', [[2004, 9, 16], [2004, 9, 30], [2004, 9, 16], 0.045, 3], 0.0], ['normal control 2', [[2035, 5, 9], [2035, 6, 30], [2035, 5, 6], 0.03, 6], 'settlement outside first period']], [['regression long period denominator 1', [[2019, 3, 16], [2019, 4, 30], [2019, 4, 4], 0.03, 1], 0.156989], ['regression long period denominator 2', [[2039, 4, 20], [2039, 6, 30], [2039, 5, 24], 0.02, 1], 0.187097], ['partial repair probe 1', [[2031, 8, 23], [2031, 12, 28], [2031, 9, 3], 0.045, 6], 0.135246], ['partial repair probe 2', [[2017, 9, 1], [2018, 2, 28], [2018, 2, 6], 0.0675, 12], 2.921918], ['boundary control 1', [[2024, 3, 15], [2024, 9, 15], [2024, 6, 15], 0.05, 6], 1.25], ['boundary control 2', [[2024, 1, 10], [2024, 9, 15], [2024, 9, 16], 0.05, 6], 'settlement outside first period'], ['normal control 1', [[2034, 7, 30], [2034, 11, 30], [2034, 7, 27], 0.045, 12], 'settlement outside first period'], ['normal control 2', [[2041, 1, 14], [2041, 1, 24], [2041, 1, 14], 0.03, 1], 0.0]]]
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 long period denominator 1 | 1.337705 | 2.235616 | Failed |
| regression long period denominator 2 | 1.635736 | 2.927038 | Failed |
| partial repair probe 1 | 3.645119 | 3.784932 | Failed |
| partial repair probe 2 | 1.881148 | 1.247283 | Failed |
| boundary control 1 | 1.25 | 1.25 | Passed |
| boundary control 2 | settlement outside first period | settlement outside first period | Passed |
| normal control 1 | settlement outside first period | settlement outside first period | Passed |
| normal control 2 | settlement outside first period | settlement outside first period | Passed |
SHA-256 / eeba6a2c07af8074600e40cc94e54a91543edd1d9749be2778b2498d8be58655
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(issue, first, settle, rate, months):
def mlen(y, m):
if m == 2:
return 29 if (y % 4 == 0 and y % 100 != 0) or y % 400 == 0 else 28
return 30 if m in (4, 6, 9, 11) else 31
I = datetime.date(*issue)
Fc = datetime.date(*first)
S = datetime.date(*settle)
if not (I <= S <= Fc):
return 'settlement outside first period'
freq = 12 // months
def back(k):
t = Fc.year * 12 + Fc.month - 1 - k * months
y, m = t // 12, t % 12 + 1
return datetime.date(y, m, min(Fc.day, mlen(y, m)))
q = [Fc]
k = 1
while q[-1] > I:
q.append(back(k))
k += 1
frac = Fraction(0)
for j in range(len(q) - 1):
end, start = q[j], q[j + 1]
lo = max(start, I)
hi = min(end, S)
if hi > lo:
frac += Fraction((hi - lo).days, (end - start).days)
return round(float(100 * Fraction(str(rate)) / freq * frac), 6)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression long period denominator 1', [[2035, 3, 1], [2036, 10, 31], [2035, 6, 11], 0.08, 12], 2.235616], ['regression long period denominator 2', [[2014, 1, 18], [2014, 6, 30], [2014, 6, 25], 0.0675, 3], 2.927038], ['partial repair probe 1', [[2026, 7, 10], [2027, 7, 24], [2027, 5, 13], 0.045, 12], 3.784932], ['partial repair probe 2', [[2007, 5, 31], [2007, 9, 30], [2007, 9, 10], 0.045, 6], 1.247283], ['boundary control 1', [[2024, 3, 15], [2024, 9, 15], [2024, 6, 15], 0.05, 6], 1.25], ['boundary control 2', [[2024, 1, 10], [2024, 9, 15], [2024, 9, 16], 0.05, 6], 'settlement outside first period'], ['normal control 1', [[2030, 12, 1], [2030, 12, 25], [2030, 11, 28], 0.045, 1], 'settlement outside first period'], ['normal control 2', [[2011, 8, 6], [2013, 1, 29], [2011, 8, 3], 0.045, 6], 'settlement outside first period']], [['regression long period denominator 1', [[2026, 6, 29], [2027, 12, 28], [2027, 3, 29], 0.05, 6], 3.736339], ['regression long period denominator 2', [[2020, 9, 30], [2021, 10, 30], [2021, 7, 11], 0.0675, 6], 5.256148], ['partial repair probe 1', [[2034, 5, 27], [2035, 1, 31], [2034, 9, 6], 0.02, 12], 0.558904], ['partial repair probe 2', [[2005, 3, 11], [2005, 8, 16], [2005, 4, 24], 0.05, 6], 0.607735], ['boundary control 1', [[2024, 3, 15], [2024, 9, 15], [2024, 6, 15], 0.05, 6], 1.25], ['boundary control 2', [[2024, 1, 10], [2024, 9, 15], [2024, 9, 16], 0.05, 6], 'settlement outside first period'], ['normal control 1', [[2021, 4, 20], [2021, 4, 30], [2021, 4, 20], 0.0675, 1], 0.0], ['normal control 2', [[2011, 6, 28], [2011, 7, 5], [2011, 6, 25], 0.045, 1], 'settlement outside first period']], [['regression long period denominator 1', [[2006, 2, 8], [2006, 3, 23], [2006, 3, 1], 0.045, 1], 0.261809], ['regression long period denominator 2', [[2004, 10, 16], [2005, 9, 5], [2004, 11, 30], 0.08, 6], 0.994475], ['partial repair probe 1', [[2020, 11, 13], [2020, 12, 31], [2020, 12, 31], 0.03, 3], 0.391304], ['partial repair probe 2', [[2042, 7, 29], [2043, 1, 31], [2042, 12, 9], 0.0675, 12], 2.459589], ['boundary control 1', [[2024, 1, 10], [2024, 9, 15], [2024, 9, 16], 0.05, 6], 'settlement outside first period'], ['boundary control 2', [[2024, 3, 15], [2024, 9, 15], [2024, 6, 15], 0.05, 6], 1.25], ['normal control 1', [[2041, 6, 16], [2041, 8, 30], [2041, 6, 13], 0.08, 12], 'settlement outside first period'], ['normal control 2', [[2005, 1, 10], [2005, 1, 31], [2005, 1, 10], 0.045, 3], 0.0]], [['regression long period denominator 1', [[2036, 7, 10], [2037, 2, 28], [2037, 2, 16], 0.0675, 6], 4.063545], ['regression long period denominator 2', [[2006, 9, 2], [2007, 3, 12], [2006, 12, 9], 0.045, 6], 1.216205], ['partial repair probe 1', [[2029, 5, 22], [2029, 7, 31], [2029, 7, 11], 0.0675, 6], 0.93232], ['partial repair probe 2', [[2033, 9, 26], [2034, 2, 28], [2033, 10, 19], 0.0675, 3], 0.421875], ['boundary control 1', [[2024, 1, 10], [2024, 9, 15], [2024, 9, 16], 0.05, 6], 'settlement outside first period'], ['boundary control 2', [[2024, 3, 15], [2024, 9, 15], [2024, 6, 15], 0.05, 6], 1.25], ['normal control 1', [[2004, 9, 16], [2004, 9, 30], [2004, 9, 16], 0.045, 3], 0.0], ['normal control 2', [[2035, 5, 9], [2035, 6, 30], [2035, 5, 6], 0.03, 6], 'settlement outside first period']], [['regression long period denominator 1', [[2019, 3, 16], [2019, 4, 30], [2019, 4, 4], 0.03, 1], 0.156989], ['regression long period denominator 2', [[2039, 4, 20], [2039, 6, 30], [2039, 5, 24], 0.02, 1], 0.187097], ['partial repair probe 1', [[2031, 8, 23], [2031, 12, 28], [2031, 9, 3], 0.045, 6], 0.135246], ['partial repair probe 2', [[2017, 9, 1], [2018, 2, 28], [2018, 2, 6], 0.0675, 12], 2.921918], ['boundary control 1', [[2024, 3, 15], [2024, 9, 15], [2024, 6, 15], 0.05, 6], 1.25], ['boundary control 2', [[2024, 1, 10], [2024, 9, 15], [2024, 9, 16], 0.05, 6], 'settlement outside first period'], ['normal control 1', [[2034, 7, 30], [2034, 11, 30], [2034, 7, 27], 0.045, 12], 'settlement outside first period'], ['normal control 2', [[2041, 1, 14], [2041, 1, 24], [2041, 1, 14], 0.03, 1], 0.0]]]
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 long period denominator 1 | 2.235616 | 2.235616 | Passed |
| regression long period denominator 2 | 2.927038 | 2.927038 | Passed |
| partial repair probe 1 | 3.784932 | 3.784932 | Passed |
| partial repair probe 2 | 1.247283 | 1.247283 | Passed |
| boundary control 1 | 1.25 | 1.25 | Passed |
| boundary control 2 | settlement outside first period | settlement outside first period | Passed |
| normal control 1 | settlement outside first period | settlement outside first period | Passed |
| normal control 2 | settlement outside first period | settlement outside first period | Passed |
SHA-256 / 777840def408aeac371d64a22b79f3046d142a5aaaaf3cc179a438a068380f7b
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:51.416721+00:00.
Case digest / 5fbff34ed90ae8d4f4f49a18b4131abb197d0f40d46884082fdf47c369bd483d