Interest Accrual

Home | Contract | Products

Interest handling has two distinct phases: accrual (calculating how much interest has accumulated) and application (crediting or debiting that interest to the account). These can happen on different schedules and at different precisions.

Ledger movements

Deposits and withdrawals

A savings account is a bank liability -- the bank owes the customer. When a customer deposits cash:

Customer Liability -> Cash

The customer's account is credited (the bank's obligation grows, so a savings balance runs negative in the ledger) and cash is debited: the bank's view, gobank ADR-0008. The goldens are in this direction.

Interest

Interest on a savings account is a cost to the bank. When interest is applied:

-ve Expense:Interest -> Customer Liability

This is a negative movement from the expense account to the customer liability account -- the bank recognises the expense and the customer's balance increases.

For lending products the direction is reversed -- interest is income to the bank and increases the customer's obligation, a positive amount from Income:Interest to the account. An overdrawn current account is lending: drawn past zero it runs positive, like a loan.

Accrual precision

The ledger stores applied balances in minor units (pence for GBP, exponent 2). But during accrual -- while interest is being calculated daily but not yet applied -- the internal representation must use higher precision to avoid accumulated rounding errors.

Context Precision Example (GBP)
Applied balance (ledger) Minor units, exponent 2 100000 (= GBP 1,000.00)
Accrual register (normal) 5 decimal places 95890 (= 0.95890 in minor units)
Accrual register (high) Up to 10 decimal places For products needing sub-basis-point accuracy

When interest is applied at the end of a period, the accumulated accrual is rounded to minor units and posted as a ledger movement. Any fractional remainder carries forward into the next period.

Exact arithmetic: the 10,000 x 365 denominator

For rates specified to 0.01% (1 basis point) precision, there is an exact representation that avoids all rounding during accrual.

Express the daily interest calculation as an integer fraction:

daily_interest = balance_minor_units * rate_bps / (10000 * 365)

Where rate_bps is the annual rate in basis points (e.g. 350 for 3.50%). The denominator 10000 * 365 = 3,650,000 is fixed.

Example: GBP 10,000.00 at 3.50%:

daily_interest = 1000000 * 350 / 3650000
               = 350000000 / 3650000
               = 95.8904109589... minor units

The numerator (350,000,000) and denominator (3,650,000) are both integers. During accrual, accumulate the numerator and defer the division. At application time, divide, post the integer part to the ledger, and carry the remainder numerator forward.

This is the no-rounding option: interest specified to 0.01% annual gross is exactly representable, and no precision is lost during accrual. The only rounding occurs at application, where the fractional minor unit is carried forward rather than lost.

For actual/actual day count (see below), the denominator becomes 10000 * days_in_year (3,650,000 or 3,660,000).

Day count conventions

The day count convention determines how the annual rate is converted to a daily rate.

Convention Daily divisor When to use
Actual/365 (preferred) Always 365 UK money markets, most UK savings and lending products
Actual/Actual 365 or 366 depending on leap year Some bond markets, EU regulatory contexts

Actual/365 is the recommended default. It is simpler (fixed denominator) and standard for UK retail banking. In a leap year, 366 days of interest are charged against a 365 divisor, so the effective annual rate is slightly higher than the nominal rate.

Actual/Actual uses the actual number of days in the year as the divisor. In a leap year the daily rate is lower (divided by 366), so 366 days at the lower rate gives exactly the nominal annual rate. This is more "correct" but less common in UK retail products.

The day count convention is configured per product alongside the application period.

Accrual and application periods

Products choose independent schedules for accrual and application:

Period Accrual Application
Daily Calculate interest each day Post to ledger each day
Monthly Calculate interest each day, accumulate Post to ledger on month-end
Quarterly Calculate interest each day, accumulate Post to ledger on quarter-end
Annually Calculate interest each day, accumulate Post to ledger on year-end

Accrual is always daily -- the balance can change any day, so interest must be tracked daily regardless of when it is applied. The application period controls when accumulated interest is posted to the account as a ledger movement.

Recommendation

Daily accrual, monthly application is the recommended default for most products. It matches UK high-street savings account conventions and provides a good balance between accuracy and ledger volume.

Daily application (the current default) is appropriate for products where daily compounding is an explicit feature, or for simplicity during early development.

Application period When to use
Daily Simple products, early development, products advertising daily compounding
Monthly Recommended. Standard UK savings and lending convention
Quarterly Some NS&I products, specific fixed-term bonds
Annually Long-term fixed-rate bonds, some ISA products

How application period affects compounding

When interest is applied, it becomes part of the balance and itself earns interest. More frequent application means more compounding, which increases the effective annual return.

Example: 5.00% nominal rate on GBP 10,000 for one year (actual/365):

Application period Compounding events/year Year-end balance Effective annual rate
Daily 365 GBP 10,512.67 5.1267%
Monthly 12 GBP 10,511.62 5.1162%
Quarterly 4 GBP 10,509.45 5.0945%
Annually 1 GBP 10,500.00 5.0000%

The difference is small but meaningful for regulatory disclosure and product comparison.

AER (Annual Equivalent Rate)

AER is the annualised effective rate that accounts for compounding. It allows customers to compare products with different application periods on a like-for-like basis.

AER = (1 + r/n)^n - 1

Where r is the nominal annual rate and n is the number of application periods per year.

Application period n Formula
Daily 365 (1 + r/365)^365 - 1
Monthly 12 (1 + r/12)^12 - 1
Quarterly 4 (1 + r/4)^4 - 1
Annually 1 r (no compounding effect)

AER is a savings product concept. UK regulations (BCOBS) require savings products to display AER so customers can compare rates. The AER is derived from the nominal rate and the application period -- changing the application period changes the AER even if the nominal rate stays the same.

The product specification sets the nominal annual_rate. The AER is computed from this rate and the configured application period. This conversion will live in gobank-products so that product documentation and customer-facing rates are consistent.

APR (Annual Percentage Rate)

APR is the lending-side equivalent of AER. It represents the total annual cost of borrowing, including compounding and (eventually) fees.

For a simple interest-only calculation:

APR = (1 + r/n)^n - 1

This is the same formula as AER when only interest is considered. The distinction matters when fees are included -- APR folds arrangement fees, annual fees, and other charges into the effective rate.

APR applies to: Personal Loan, Mortgage, Overdraft.

Status: future feature. The current implementation does not include fee modelling, so APR and AER are numerically identical. APR will diverge once fee features are added.

Product configuration

Each product specifies its interest method via parameters:

Parameter Values Default
annual_rate Decimal (e.g. 0.035) Set per product
interest_application daily, monthly, annual monthly (quarterly not yet)
day_count actual_365, actual_actual actual_365
accrual_precision 5, 10, or exact 5

When accrual_precision is exact, the 10,000 x 365 integer fraction method is used with no rounding until application.

Product recommendations

Product Application Day count Rationale
Easy Access Monthly Actual/365 Standard UK savings convention
Fixed Term Monthly or Quarterly Actual/365 Matches typical bond terms
ISA Monthly Actual/365 Standard UK ISA convention
Personal Loan Monthly Actual/365 Aligns with repayment schedule
Mortgage Monthly Actual/365 Aligns with repayment schedule
Overdraft Daily Actual/365 Interest charged daily on fluctuating balances

Implementation notes

Implemented in gobank-products v0.1.10 (the calculation lives in the product layer, not go-luca):

  1. Accrual register -- ManagedAccount.AccruedNumerator holds accrued-but-unapplied interest as an exact integer fraction over AccrualDenominator (10,000 x 365). Daily accrual adds cached_balance_minor_units * rate_bps; no rounding occurs until application, and the sub-minor-unit remainder carries forward, so small balances never lose interest.
  2. Cached balances -- ManagedAccount.CachedBalance is maintained by Simulation.RecordMovement, so daily accrual issues no balance queries. Simulation.RefreshBalances re-primes the cache after a ledger import.
  3. The day rule, Product.NextDay -- a pure function from an account's position at the end of the previous day (go-luca Position: balance and accrued interest as an exact fraction) and the day's closing balance to the position at the end of this day and the ledger postings it calls for. Interest accrues on the closing balance; when the product's interest_application cycle ends on this day the whole minor units are applied and the remainder carries forward. Application is therefore product code inside the daily pass, not a separate month-end run. It is Apply of Accrue: Product.Accrue gives the day's position with the interest accrued and nothing applied, the same for any closing balance, so a caller projecting a day before it is over writes it provisionally and rewrites it as events move the balance; Product.Apply books the cycle-end application once the closing balance is final, and applying an applied position posts nothing. InterestAccrual runs it at EndOfDay (idempotent per date via LastAccrued, so accounts can be done individually, spread across the day) and posts what it returns.
  4. Ledger directions -- savings apply as Expense:Interest -> account; lending as Income:Interest -> account (increases the customer obligation).

Still open: a quarterly cycle, day_count conventions beyond actual/365, closed-form multi-day accrual between change events (a month at a time), AER/APR computation, and rate changes mid-period.