[
{
"name": "one year, no flows: the simple return, annualised to itself",
"args": [
[
{
"date": "2025-01-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2026-01-01",
"value": {
"minor": 110000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expect": {
"basisPoints": 1000,
"subPeriodBasisPoints": [
1000
],
"days": 365,
"annualisedBasisPoints": 1000
}
},
{
"name": "a mid-year deposit is taken out: 10% then 5% links to 15.5%, not 18%",
"args": [
[
{
"date": "2025-01-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2025-07-01",
"value": {
"minor": 110000,
"currency": "GBP"
},
"flow": {
"minor": 50000,
"currency": "GBP"
}
},
{
"date": "2026-01-01",
"value": {
"minor": 168000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expect": {
"basisPoints": 1550,
"subPeriodBasisPoints": [
1000,
500
],
"days": 365,
"annualisedBasisPoints": 1550
}
},
{
"name": "a withdrawal after a loss: -10% then +20% is +8%, not annualised under a year",
"args": [
[
{
"date": "2025-01-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2025-04-01",
"value": {
"minor": 90000,
"currency": "GBP"
},
"flow": {
"minor": -40000,
"currency": "GBP"
}
},
{
"date": "2025-10-01",
"value": {
"minor": 60000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expect": {
"basisPoints": 800,
"subPeriodBasisPoints": [
-1000,
2000
],
"days": 273,
"annualisedBasisPoints": null
}
},
{
"name": "an account opened by its first flow from a value of zero",
"args": [
[
{
"date": "2025-01-01",
"value": {
"minor": 0,
"currency": "GBP"
},
"flow": {
"minor": 100000,
"currency": "GBP"
}
},
{
"date": "2025-03-01",
"value": {
"minor": 105000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expect": {
"basisPoints": 500,
"subPeriodBasisPoints": [
500
],
"days": 59,
"annualisedBasisPoints": null
}
},
{
"name": "the last point's flow does not change the return",
"args": [
[
{
"date": "2025-01-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2026-01-01",
"value": {
"minor": 110000,
"currency": "GBP"
},
"flow": {
"minor": -110000,
"currency": "GBP"
}
}
]
],
"expect": {
"basisPoints": 1000,
"subPeriodBasisPoints": [
1000
],
"days": 365,
"annualisedBasisPoints": 1000
}
},
{
"name": "three years to 1.331 annualise over 1096 days, a shade under 10%",
"args": [
[
{
"date": "2020-01-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2023-01-01",
"value": {
"minor": 133100,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expect": {
"basisPoints": 3310,
"subPeriodBasisPoints": [
3310
],
"days": 1096,
"annualisedBasisPoints": 999
}
},
{
"name": "three sub-periods with deposits and a withdrawal",
"args": [
[
{
"date": "2022-01-01",
"value": {
"minor": 250000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2022-03-15",
"value": {
"minor": 262500,
"currency": "GBP"
},
"flow": {
"minor": 25000,
"currency": "GBP"
}
},
{
"date": "2022-08-31",
"value": {
"minor": 280000,
"currency": "GBP"
},
"flow": {
"minor": -30000,
"currency": "GBP"
}
},
{
"date": "2023-02-28",
"value": {
"minor": 262000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expect": {
"basisPoints": 717,
"subPeriodBasisPoints": [
500,
-261,
480
],
"days": 423,
"annualisedBasisPoints": 616
}
},
{
"name": "a total loss is -100%, annualised too",
"args": [
[
{
"date": "2020-01-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2021-06-01",
"value": {
"minor": 0,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expect": {
"basisPoints": -10000,
"subPeriodBasisPoints": [
-10000
],
"days": 517,
"annualisedBasisPoints": -10000
}
},
{
"name": "364 days is not annualised",
"args": [
[
{
"date": "2025-01-02",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2026-01-01",
"value": {
"minor": 110000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expect": {
"basisPoints": 1000,
"subPeriodBasisPoints": [
1000
],
"days": 364,
"annualisedBasisPoints": null
}
},
{
"name": "-0.5 bp rounds away from zero to -1",
"args": [
[
{
"date": "2025-01-01",
"value": {
"minor": 1000000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2025-02-01",
"value": {
"minor": 999950,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expect": {
"basisPoints": -1,
"subPeriodBasisPoints": [
-1
],
"days": 31,
"annualisedBasisPoints": null
}
},
{
"name": "+0.5 bp rounds away from zero to 1",
"args": [
[
{
"date": "2025-01-01",
"value": {
"minor": 1000000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2025-02-01",
"value": {
"minor": 1000050,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expect": {
"basisPoints": 1,
"subPeriodBasisPoints": [
1
],
"days": 31,
"annualisedBasisPoints": null
}
},
{
"name": "a sub-period with no change is 0",
"args": [
[
{
"date": "2025-01-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 5000,
"currency": "GBP"
}
},
{
"date": "2025-06-01",
"value": {
"minor": 105000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2025-09-01",
"value": {
"minor": 99750,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expect": {
"basisPoints": -500,
"subPeriodBasisPoints": [
0,
-500
],
"days": 243,
"annualisedBasisPoints": null
}
},
{
"name": "repeating fractions are linked exactly before rounding",
"args": [
[
{
"date": "2024-01-01",
"value": {
"minor": 300,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2024-06-01",
"value": {
"minor": 400,
"currency": "GBP"
},
"flow": {
"minor": -100,
"currency": "GBP"
}
},
{
"date": "2025-06-01",
"value": {
"minor": 400,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expect": {
"basisPoints": 7778,
"subPeriodBasisPoints": [
3333,
3333
],
"days": 517,
"annualisedBasisPoints": 5011
}
},
{
"name": "one point is not a period",
"args": [
[
{
"date": "2025-01-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expectError": "at least two valuation points are needed"
},
{
"name": "dates out of order",
"args": [
[
{
"date": "2025-06-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2025-01-01",
"value": {
"minor": 110000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expectError": "dates must be strictly increasing"
},
{
"name": "two points on one date",
"args": [
[
{
"date": "2025-01-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2025-01-01",
"value": {
"minor": 110000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expectError": "dates must be strictly increasing"
},
{
"name": "a negative valuation",
"args": [
[
{
"date": "2025-01-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2025-06-01",
"value": {
"minor": -1,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expectError": "valuations must not be negative"
},
{
"name": "everything withdrawn before the end leaves nothing to measure",
"args": [
[
{
"date": "2025-01-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2025-06-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": -100000,
"currency": "GBP"
}
},
{
"date": "2025-09-01",
"value": {
"minor": 0,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expectError": "nothing is invested at the start of the sub-period from 2025-06-01"
},
{
"name": "a flow in another currency",
"args": [
[
{
"date": "2025-01-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2025-06-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 500,
"currency": "EUR"
}
},
{
"date": "2025-09-01",
"value": {
"minor": 0,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expectError": "currency mismatch"
},
{
"name": "fractional minor units",
"args": [
[
{
"date": "2025-01-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0.5,
"currency": "GBP"
}
},
{
"date": "2025-06-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expectError": "amounts must be whole minor units"
},
{
"name": "an impossible date",
"args": [
[
{
"date": "2025-01-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2025-02-29",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expectError": "is not a real calendar date"
},
{
"name": "more than 36500 days",
"args": [
[
{
"date": "1900-01-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
},
{
"date": "2001-01-01",
"value": {
"minor": 100000,
"currency": "GBP"
},
"flow": {
"minor": 0,
"currency": "GBP"
}
}
]
],
"expectError": "points must fall within 36500 days"
}
]