[
{
"name": "\u00a310,000 at 3.65% for 30 days on ACT/365 is exactly \u00a330",
"args": [
[
{
"date": "2026-01-01",
"balance": {
"minor": 1000000,
"currency": "GBP"
}
}
],
"2026-01-01",
"2026-01-31",
365,
"act-365f",
"half-up"
],
"expect": {
"interest": {
"minor": 3000,
"currency": "GBP"
},
"exact": {
"numerator": 3000,
"denominator": 1
},
"days": 30
}
},
{
"name": "a deposit mid-month: the new balance earns from its date",
"args": [
[
{
"date": "2026-03-01",
"balance": {
"minor": 100000,
"currency": "GBP"
}
},
{
"date": "2026-03-16",
"balance": {
"minor": 600000,
"currency": "GBP"
}
}
],
"2026-03-01",
"2026-04-01",
450,
"act-365f",
"half-up"
],
"expect": {
"interest": {
"minor": 1368,
"currency": "GBP"
},
"exact": {
"numerator": 99900,
"denominator": 73
},
"days": 31
}
},
{
"name": "a balance before the period starts is carried in",
"args": [
[
{
"date": "2025-12-10",
"balance": {
"minor": 250000,
"currency": "GBP"
}
},
{
"date": "2026-01-20",
"balance": {
"minor": 0,
"currency": "GBP"
}
}
],
"2026-01-01",
"2026-02-01",
500,
"act-365f",
"half-up"
],
"expect": {
"interest": {
"minor": 651,
"currency": "GBP"
},
"exact": {
"numerator": 47500,
"denominator": 73
},
"days": 31
}
},
{
"name": "changes after the period end are ignored",
"args": [
[
{
"date": "2026-01-01",
"balance": {
"minor": 100000,
"currency": "GBP"
}
},
{
"date": "2026-02-10",
"balance": {
"minor": 900000,
"currency": "GBP"
}
}
],
"2026-01-01",
"2026-02-01",
500,
"act-365f",
"half-up"
],
"expect": {
"interest": {
"minor": 425,
"currency": "GBP"
},
"exact": {
"numerator": 31000,
"denominator": 73
},
"days": 31
}
},
{
"name": "ACT/360 charges more than ACT/365 for the same days",
"args": [
[
{
"date": "2026-01-01",
"balance": {
"minor": 1000000,
"currency": "GBP"
}
}
],
"2026-01-01",
"2026-01-31",
365,
"act-360",
"half-up"
],
"expect": {
"interest": {
"minor": 3042,
"currency": "GBP"
},
"exact": {
"numerator": 9125,
"denominator": 3
},
"days": 30
}
},
{
"name": "30/360: February counts as 30 days",
"args": [
[
{
"date": "2026-02-01",
"balance": {
"minor": 1200000,
"currency": "GBP"
}
}
],
"2026-02-01",
"2026-03-01",
600,
"30-360",
"half-up"
],
"expect": {
"interest": {
"minor": 6000,
"currency": "GBP"
},
"exact": {
"numerator": 6000,
"denominator": 1
},
"days": 28
}
},
{
"name": "ACT/ACT ISDA across a leap-year boundary",
"args": [
[
{
"date": "2027-12-01",
"balance": {
"minor": 5000000,
"currency": "GBP"
}
}
],
"2027-12-01",
"2028-02-01",
400,
"act-act-isda",
"half-up"
],
"expect": {
"interest": {
"minor": 33926,
"currency": "GBP"
},
"exact": {
"numerator": 453220000,
"denominator": 13359
},
"days": 62
}
},
{
"name": "an overdrawn balance accrues negative interest",
"args": [
[
{
"date": "2026-05-01",
"balance": {
"minor": -50000,
"currency": "GBP"
}
}
],
"2026-05-01",
"2026-06-01",
1990,
"act-365f",
"half-up"
],
"expect": {
"interest": {
"minor": -845,
"currency": "GBP"
},
"exact": {
"numerator": -61690,
"denominator": 73
},
"days": 31
}
},
{
"name": "the same debt with \"up\" rounds away from zero",
"args": [
[
{
"date": "2026-05-01",
"balance": {
"minor": -50000,
"currency": "GBP"
}
}
],
"2026-05-01",
"2026-06-01",
1990,
"act-365f",
"up"
],
"expect": {
"interest": {
"minor": -846,
"currency": "GBP"
},
"exact": {
"numerator": -61690,
"denominator": 73
},
"days": 31
}
},
{
"name": "rounding once: many small changes, one rounding",
"args": [
[
{
"date": "2026-01-01",
"balance": {
"minor": 333,
"currency": "GBP"
}
},
{
"date": "2026-01-02",
"balance": {
"minor": 334,
"currency": "GBP"
}
},
{
"date": "2026-01-03",
"balance": {
"minor": 333,
"currency": "GBP"
}
},
{
"date": "2026-01-04",
"balance": {
"minor": 0,
"currency": "GBP"
}
}
],
"2026-01-01",
"2026-01-05",
2500,
"act-365f",
"half-up"
],
"expect": {
"interest": {
"minor": 1,
"currency": "GBP"
},
"exact": {
"numerator": 50,
"denominator": 73
},
"days": 4
}
},
{
"name": "a zero-length period accrues nothing",
"args": [
[
{
"date": "2026-01-01",
"balance": {
"minor": 100000,
"currency": "GBP"
}
}
],
"2026-01-10",
"2026-01-10",
500,
"act-365f",
"half-up"
],
"expect": {
"interest": {
"minor": 0,
"currency": "GBP"
},
"exact": {
"numerator": 0,
"denominator": 1
},
"days": 0
}
},
{
"name": "a negative rate charges a positive balance",
"args": [
[
{
"date": "2026-01-01",
"balance": {
"minor": 10000000,
"currency": "GBP"
}
}
],
"2026-01-01",
"2026-04-01",
-50,
"act-365f",
"half-even"
],
"expect": {
"interest": {
"minor": -12329,
"currency": "GBP"
},
"exact": {
"numerator": -900000,
"denominator": 73
},
"days": 90
}
},
{
"name": "a whole leap year on ACT/365F is 366/365 of a year's interest",
"args": [
[
{
"date": "2028-01-01",
"balance": {
"minor": 100000,
"currency": "GBP"
}
}
],
"2028-01-01",
"2029-01-01",
1000,
"act-365f",
"half-up"
],
"expect": {
"interest": {
"minor": 10027,
"currency": "GBP"
},
"exact": {
"numerator": 732000,
"denominator": 73
},
"days": 366
}
},
{
"name": "an empty balance list is refused",
"args": [
[],
"2026-01-01",
"2026-02-01",
500,
"act-365f",
"half-up"
],
"expectError": "balances must not be empty"
},
{
"name": "a first balance after the start is refused",
"args": [
[
{
"date": "2026-01-05",
"balance": {
"minor": 100,
"currency": "GBP"
}
}
],
"2026-01-01",
"2026-02-01",
500,
"act-365f",
"half-up"
],
"expectError": "after fromIso"
},
{
"name": "balances out of order are refused",
"args": [
[
{
"date": "2026-01-05",
"balance": {
"minor": 100,
"currency": "GBP"
}
},
{
"date": "2026-01-01",
"balance": {
"minor": 200,
"currency": "GBP"
}
}
],
"2026-01-01",
"2026-02-01",
500,
"act-365f",
"half-up"
],
"expectError": "strictly ascending date order"
},
{
"name": "an end before the start is refused",
"args": [
[
{
"date": "2026-01-01",
"balance": {
"minor": 100,
"currency": "GBP"
}
}
],
"2026-02-01",
"2026-01-01",
500,
"act-365f",
"half-up"
],
"expectError": "must not be after toIso"
},
{
"name": "mixed currencies are refused",
"args": [
[
{
"date": "2026-01-01",
"balance": {
"minor": 100,
"currency": "GBP"
}
},
{
"date": "2026-01-10",
"balance": {
"minor": 100,
"currency": "EUR"
}
}
],
"2026-01-01",
"2026-02-01",
500,
"act-365f",
"half-up"
],
"expectError": "currency mismatch"
},
{
"name": "an impossible date is refused",
"args": [
[
{
"date": "2026-02-30",
"balance": {
"minor": 100,
"currency": "GBP"
}
}
],
"2026-01-01",
"2026-02-01",
500,
"act-365f",
"half-up"
],
"expectError": "is not a real calendar date"
},
{
"name": "an unknown convention is refused",
"args": [
[
{
"date": "2026-01-01",
"balance": {
"minor": 100,
"currency": "GBP"
}
}
],
"2026-01-01",
"2026-02-01",
500,
"act-365",
"half-up"
],
"expectError": "unknown day count convention"
},
{
"name": "a rate out of range is refused",
"args": [
[
{
"date": "2026-01-01",
"balance": {
"minor": 100,
"currency": "GBP"
}
}
],
"2026-01-01",
"2026-02-01",
100001,
"act-365f",
"half-up"
],
"expectError": "annualRateBasisPoints must be between -100000 and 100000"
}
]