Loan instalment (loans.instalment)¶
Returns the constant instalment of a loan from its principal, its rate, the number of instalments and the payment timing, with an optional balloon amount repaid with the last instalment and an optional rounding of the instalment. Use it to put new bank debt into a business plan or to check the instalment quoted by a lender.
Formula¶
With the periodic rate \(i\), the number of instalments \(n\), the principal \(P\) and the balloon amount \(B\) (paid at the end of period \(n\)), the instalment \(A\) makes the present value of the instalments and of the balloon amount equal to the principal:
The engine computes \(A\) as \((P - B\,(1 + i)^{-n})\) divided by the annuity factor of
tvm.annuity, so both methods share one implementation.
The periodic rate¶
The rate is given in exactly one of two forms:
- Form (a): an annual borrowing rate
ratewith itscompoundingand thepayment_frequencyof the instalments (\(p\) a year). The periodic rate is the equivalent rate with compounding equal to the payment frequency (asrates.convertcomputes it), divided by \(p\). When the compounding equals the payment frequency this is simplyrate/ \(p\): 6% compounded monthly with monthly instalments gives 0.5% a month. - Form (b): a
periodic_rate, the rate per payment period, astvm.annuitytakes it.
There is no default compounding: a borrowing rate means nothing without it.
Example¶
The ECMA-376 worked example (10,000 at 8% a year, monthly compounding and monthly instalments, 10 instalments), and a ten-year loan with a balloon of 50,000 rounded to the cent:
from decimal import Decimal
import fairmark as fm
monthly = {"rate": "0.08", "compounding": "monthly", "payment_frequency": "monthly"}
arrears = fm.loans.instalment(10000, 10, timing="ordinary", **monthly)
advance = fm.loans.instalment(10000, 10, timing="due", **monthly)
assert round(arrears.instalment, 2) == Decimal("1037.03")
assert round(advance.instalment, 2) == Decimal("1030.16")
balloon = fm.loans.instalment(
250000, 120, rate="0.054", compounding="monthly", payment_frequency="monthly",
timing="ordinary", balloon_amount=50000, decimal_places=2, rounding_mode="half_up",
)
assert balloon.instalment == Decimal("2385.63")
assert balloon.total_paid == Decimal("336275.60") # 120 x 2385.63 + 50000
print(balloon.conventions[-2]) # rounding: 2 decimal places, half_up, applied to the instalment
# The same loan with the periodic rate given directly (form b).
same = fm.loans.instalment(20000, 36, periodic_rate="0.005", timing="ordinary")
again = fm.loans.instalment(20000, 36, rate="0.06", compounding="monthly",
payment_frequency="monthly", timing="ordinary")
assert same.instalment == again.instalment
Inputs¶
| Field | Meaning | Default |
|---|---|---|
principal |
Amount borrowed, currency units, greater than 0 | required |
rate |
Annual borrowing rate, decimal fraction (form a) | one rate form required |
compounding |
Compounding of rate: annual, semi_annual, quarterly, monthly, continuous (form a) |
required with rate |
payment_frequency |
Instalments per year: annual, semi_annual, quarterly, monthly (form a) |
required with rate |
periodic_rate |
Rate per payment period, decimal fraction, greater than −1 (form b) | one rate form required |
number_of_instalments |
From 1 to 10000 | required |
timing |
ordinary (in arrears) or due (in advance) |
required |
balloon_amount |
Repaid at the end of the last period on top of the last instalment, \(0 \le B < P\) | 0 |
rounding |
decimal_places (0 to 6) and mode (half_up or half_even), both required |
none |
Outputs¶
| Field | Meaning |
|---|---|
instalment |
Constant instalment (rounded when rounding is given) |
periodic_rate |
Rate per payment period used |
total_paid |
\(n \times A + B\), with the instalment as returned |
total_interest |
Total paid less the principal |
Conventions (explicit, reported in explain.conventions)¶
| Convention | Options | Default |
|---|---|---|
| Rate form | (a) annual rate with compounding and payment frequency, or (b) periodic rate | exactly one, required |
| Periodic rate (form a) | equivalent rate with compounding equal to the payment frequency, divided by the instalments per year | fixed |
| Timing | ordinary: instalment \(k\) at the end of period \(k\); due: at the beginning |
none: must be chosen |
| Balloon amount | paid with the last instalment, at the end of period \(n\) for both timings | 0, reported as default |
| Rounding | none, or 0 to 6 decimal places with half_up (halves away from zero) or half_even (halves to the even digit) |
none |
| Rate zero | limit form \((P - B) / n\), shown as a separate step | applied when \(i = 0\) |
With rounding, the last instalment of a real schedule differs from the others by the
accumulated rounding: loans.amortisation_schedule computes
that final-period adjustment. total_paid here uses the rounded instalment for every period.
Errors¶
| Code | When |
|---|---|
FM-E001 |
Principal 0 or negative, number_of_instalments 0 or above 10000, both rate forms, neither, form (a) incomplete, decimal_places above 6, rounding without a mode, or a field missing or unknown |
FM-E003 |
The periodic rate is −100% or lower |
FM-E020 |
The balloon amount is negative or not lower than the principal |
FM-E008 |
Arithmetic overflow (inputs far outside a realistic range) |
Accuracy¶
The instalment uses integer powers, multiplication and division, and holds to the last of the engine's 28 significant digits. When the compounding differs from the payment frequency, the conversion uses the series functions of the decimal library (relative error near \(10^{-27}\)). The golden cases are checked to \(10^{-20}\) against an independent 50-digit reference.
Verified against¶
- Golden cases in
tests/golden/loans.instalment/: the ECMA-376 worked example of the loan instalment (in arrears and in advance) and an independent 50-digit reference (scripts/reference_loans.py). - The loan payment function of
numpy_financialon random loans, both timings, with the balloon amount as the future value.