API reference¶
The explicit listings below cover every public function, dataclass,
enum, and warning. conf.py enables
autodoc_default_options = {"members": True}, so each
autoclass directive auto-includes its members; we omit a
top-level automodule directive to avoid duplicate index
entries.
Functions¶
Convenience constructors¶
These helpers choose common, fixture-backed defaults and return
ordinary validated LoanParams objects. Use LoanParams directly
when you need every parameter explicitly.
- mortgagemath.fixed_rate_mortgage(principal, annual_rate, term_years, *, payment_rounding=PaymentRounding.ROUND_UP, interest_rounding=PaymentRounding.ROUND_HALF_UP, balance_tracking=BalanceTracking.ROUND_EACH)[source]¶
Return
LoanParamsfor a fully amortizing fixed-rate mortgage.This is the generic easy-mode constructor for the common 30/360, monthly-payment case. Use
LoanParamsdirectly when you need non-monthly payments, ARMs, Actual/360, balloon terms, or other less common conventions.- Parameters:
term_years (int)
payment_rounding (PaymentRounding)
interest_rounding (PaymentRounding)
balance_tracking (BalanceTracking)
- Return type:
- mortgagemath.us_30_year_fixed(principal, annual_rate, *, payment_rounding=PaymentRounding.ROUND_UP, interest_rounding=PaymentRounding.ROUND_HALF_UP, balance_tracking=BalanceTracking.ROUND_EACH)[source]¶
Return
LoanParamsfor a standard US 30-year fixed-rate mortgage.- Parameters:
payment_rounding (PaymentRounding)
interest_rounding (PaymentRounding)
balance_tracking (BalanceTracking)
- Return type:
- mortgagemath.us_15_year_fixed(principal, annual_rate, *, payment_rounding=PaymentRounding.ROUND_UP, interest_rounding=PaymentRounding.ROUND_HALF_UP, balance_tracking=BalanceTracking.ROUND_EACH)[source]¶
Return
LoanParamsfor a standard US 15-year fixed-rate mortgage.- Parameters:
payment_rounding (PaymentRounding)
interest_rounding (PaymentRounding)
balance_tracking (BalanceTracking)
- Return type:
- mortgagemath.canada_fixed_j2(principal, annual_rate, *, amortization_years=25, term_years=None, payment_frequency=PaymentFrequency.MONTHLY, payment_rounding=PaymentRounding.ROUND_HALF_UP, interest_rounding=PaymentRounding.ROUND_HALF_UP, balance_tracking=BalanceTracking.ROUND_EACH)[source]¶
Return
LoanParamsfor a Canadian fixed-ratej_2mortgage.Canadian residential rates are commonly quoted as
j_2: nominal annual rates compounded semi-annually under Interest Act convention. Ifterm_yearsis provided and is shorter than the amortization, the returned loan models the fixed term as a balloon period on the longer amortization basis.- Parameters:
amortization_years (int)
term_years (int | None)
payment_frequency (PaymentFrequency)
payment_rounding (PaymentRounding)
interest_rounding (PaymentRounding)
balance_tracking (BalanceTracking)
- Return type:
- mortgagemath.canada_accelerated_biweekly(principal, annual_rate, *, amortization_years=25, payment_rounding=PaymentRounding.ROUND_HALF_UP, interest_rounding=PaymentRounding.ROUND_HALF_UP, balance_tracking=BalanceTracking.ROUND_EACH)[source]¶
Return
LoanParamsfor a Canadian Accelerated Bi-Weekly mortgage.Calculated by taking the standard monthly
j_2payment, dividing it by two, and applying that amount every two weeks (26 times per year). This results in approximately one extra monthly payment per year, paying the loan off significantly faster than a 25-year monthly schedule.- Parameters:
amortization_years (int)
payment_rounding (PaymentRounding)
interest_rounding (PaymentRounding)
balance_tracking (BalanceTracking)
- Return type:
- mortgagemath.us_actual_360_commercial(principal, annual_rate, *, term_years, amortization_years, start_date, payment_rounding=PaymentRounding.ROUND_HALF_UP, interest_rounding=PaymentRounding.ROUND_HALF_UP)[source]¶
Return
LoanParamsfor a US Actual/360 commercial mortgage.This preset matches the Fannie Mae Multifamily style: monthly payments, monthly compounding, Actual/360 interest accrual, and a possible balloon when
term_yearsis shorter thanamortization_years.start_dateis required because Actual/360 schedules compute interest from the calendar days in each accrual period. Accepts either adatetime.dateor an ISO-format string ("YYYY-MM-DD").- Parameters:
- Return type:
- mortgagemath.fixed_payment_mortgage(principal, annual_rate, payment, *, term_months, payment_rounding=PaymentRounding.ROUND_HALF_UP, interest_rounding=PaymentRounding.ROUND_HALF_UP, balance_tracking=BalanceTracking.CARRY_PRECISION)[source]¶
Return
LoanParamsfor a fixed-rate loan with a chosen payment.The returned loan uses
payment_override: the schedule applies the chosen periodic payment until the final row trues up the remaining balance. The default carry-precision balance tracking is the historical given-payment convention validated against the FHLBB 1935 Direct-Reduction Plan A fixture.term_monthsis explicit rather than a year count because historical fixed-payment examples can end after non-whole-year terms such as 139 months.- Parameters:
term_months (int)
payment_rounding (PaymentRounding)
interest_rounding (PaymentRounding)
balance_tracking (BalanceTracking)
- Return type:
Calculations¶
- mortgagemath.periodic_payment(loan)[source]¶
Calculate the periodic principal-and-interest payment for a loan.
Uses the standard annuity formula with Decimal arithmetic throughout:
r = periodic interest rate (per payment period) n = number of payments over the amortization period payment = P * r * (1+r)^n / ((1+r)^n - 1)
The result is rounded according to
loan.payment_rounding.The same closed-form formula applies to both 30/360 and Actual/360 loans: the day-count convention determines how the schedule accrues interest each period, not how the level periodic payment is computed. Validated against the Fannie Mae Multifamily Selling and Servicing Guide §1103 worked example ($25M / 5.5% / 30yr → DSC 6.8134680% → monthly P&I $141,947.25).
For non-monthly compounding (Canadian Interest Act §6 semi-annual quoting; or annual compounding) the periodic rate is derived from the quoted annual rate; see
_periodic_rate().- Parameters:
loan (LoanParams) – Loan parameters.
- Returns:
Periodic P&I payment rounded to the loan’s currency unit.
- Raises:
ValueError – If principal, term_months, or annual_rate is not positive, or the day_count is unsupported.
- Return type:
- mortgagemath.monthly_payment(loan)¶
Calculate the periodic principal-and-interest payment for a loan.
Uses the standard annuity formula with Decimal arithmetic throughout:
r = periodic interest rate (per payment period) n = number of payments over the amortization period payment = P * r * (1+r)^n / ((1+r)^n - 1)
The result is rounded according to
loan.payment_rounding.The same closed-form formula applies to both 30/360 and Actual/360 loans: the day-count convention determines how the schedule accrues interest each period, not how the level periodic payment is computed. Validated against the Fannie Mae Multifamily Selling and Servicing Guide §1103 worked example ($25M / 5.5% / 30yr → DSC 6.8134680% → monthly P&I $141,947.25).
For non-monthly compounding (Canadian Interest Act §6 semi-annual quoting; or annual compounding) the periodic rate is derived from the quoted annual rate; see
_periodic_rate().- Parameters:
loan (LoanParams) – Loan parameters.
- Returns:
Periodic P&I payment rounded to the loan’s currency unit.
- Raises:
ValueError – If principal, term_months, or annual_rate is not positive, or the day_count is unsupported.
- Return type:
- mortgagemath.amortization_schedule(loan)[source]¶
Generate a full amortization schedule.
For 30/360 loans the schedule uses bank-style round-each-balance accounting:
The periodic payment is computed once and rounded.
Each period’s interest is computed on the (rounded) remaining balance and rounded to the cent.
Principal is the difference:
payment - interest.The final payment is adjusted so the balance lands exactly at zero.
For Actual/360 loans the schedule uses Fannie Mae §1103 conventions:
loan.start_dateis required; period 1 covers the calendar month containing the start date.The unrounded closed-form payment is carried internally; each month’s interest is computed in full Decimal precision as
balance * annual_rate * days_in_month / 360.Display values (
payment,interest,principal) are rounded to the cent; the running balance internally is unrounded.The final payment is adjusted to land balance at exactly zero.
Both modes guarantee
principal + interest == paymentfor every installment and a final balance of exactly$0.00.For very small principals the cent-rounded periodic payment can overpay the closed-form value by enough that the loan amortizes before the requested term. In that case the schedule is truncated at the actual payoff period and an
EarlyPayoffWarningis emitted;len(schedule)will be smaller thantotal_payments + 1.- Parameters:
loan (LoanParams) – Loan parameters.
- Returns:
A list of
Installmentobjects from period 0 (initial state showing the starting balance) through the final payment.- Raises:
ValueError – If principal or term_months is not positive, the day_count is unsupported, or ACTUAL_360 is requested without a start_date.
- Warns:
EarlyPayoffWarning – When 30/360 round-each-balance accounting amortizes the loan before its scheduled end due to periodic payment overpayment from rounding.
- Return type:
- mortgagemath.loan_summary(loan)[source]¶
Compute a high-level summary of a loan’s cost and structure.
This is a convenience wrapper that calls
periodic_payment()andamortization_schedule()internally and extracts the values most users need first.- Parameters:
loan (LoanParams) – Loan parameters.
- Returns:
A
LoanSummarywith the periodic payment, total interest, total paid, total fees, balloon balance, total cost, and number of payments.- Return type:
Example:
>>> from mortgagemath import us_30_year_fixed, loan_summary >>> s = loan_summary(us_30_year_fixed("300000", "6.5")) >>> s.periodic_payment Decimal('1896.21') >>> s.total_interest Decimal('382628.90') >>> s.total_cost Decimal('682628.90')
Dataclasses¶
- class mortgagemath.LoanSummary(periodic_payment, total_paid, total_interest, total_fees, total_principal, balloon_balance, total_cost, num_payments)[source]¶
Bases:
objectHigh-level summary of a loan’s cost and structure.
Returned by
loan_summary(). All monetary values areDecimalat the loan’s currency-unit precision.- Parameters:
- total_paid: Decimal¶
Sum of all scheduled payments over the life of the loan (includes fees). For balloon loans this does NOT include the balloon balance — see
total_cost.
- total_principal: Decimal¶
Principal repaid through scheduled payments. For fully amortizing loans this equals the original principal. For balloon loans this is less than the original principal by the balloon amount.
- balloon_balance: Decimal¶
Remaining balance at the end of the term. Zero for fully amortizing loans; the balloon amount for balloon loans.
- class mortgagemath.LoanParams(principal, annual_rate, term_months, day_count=DayCount.THIRTY_360, payment_rounding=PaymentRounding.ROUND_UP, interest_rounding=PaymentRounding.ROUND_HALF_UP, start_date=None, amortization_period_months=None, balance_tracking=BalanceTracking.ROUND_EACH, compounding=Compounding.MONTHLY, payment_frequency=PaymentFrequency.MONTHLY, rate_schedule=(), payment_override=None, currency_unit=Decimal('0.01'), interest_only_months=0, fee_per_period=Decimal('0'))[source]¶
Bases:
objectParameters defining a fixed-rate mortgage.
- Parameters:
principal (Decimal) – Original loan amount (e.g. Decimal(“131250.00”)).
annual_rate (Decimal) – Annual interest rate as a percentage (e.g. Decimal(“4.25”) for 4.25%).
term_months (int) – Loan term in months — the number of payments the schedule will produce (e.g. 360 for a 30-year fully amortizing residential mortgage; 120 for a 10-year SARM).
day_count (DayCount) – Day-count convention. Defaults to 30/360.
payment_rounding (PaymentRounding) – How to round the monthly payment to the nearest cent. Defaults to ROUND_UP.
interest_rounding (PaymentRounding) – How to round each month’s interest charge. Defaults to ROUND_HALF_UP.
start_date (date | None) – Date of the first interest-accrual period (the issue date). Required for ACTUAL_360 schedules; ignored for 30/360. Period 1 covers the calendar month containing this date and the first payment is due on the same day of the next month. Per Fannie Mae §1103, an issue date of December 1, 2018 produces a first payment on January 1, 2019 with period 1 spanning December 2018 (31 days).
amortization_period_months (int | None) – Period over which the closed-form payment is computed.
None(the default) means it equalsterm_months— i.e., the loan amortizes fully over its term. Set this larger thanterm_monthsfor balloon loans: the level monthly payment is computed as if the loan amortized overamortization_period_months, but the schedule terminates afterterm_monthspayments, with the unpaid principal at term equal toInstallment.balanceon the final row (this is the borrower’s balloon payment). Per the Fannie Mae §1103 Tier 2 SARM example, a 10-year ($120 months) SARM on a 30-year (360 months) amortization basis is expressed asterm_months=120, amortization_period_months=360; the published balloon at term 120 is $20,885,505.83.balance_tracking (BalanceTracking) – How the running balance propagates between rows.
ROUND_EACH(default) is the round-each-month convention used by US residential lenders and validated against CFPB sample disclosures.CARRY_PRECISIONis the Excel-default convention used by graduate-level CRE finance textbooks (Geltner, LibreTexts, eCampus). Ignored for ACTUAL_360 day count, which always uses carry-precision.compounding (Compounding) – How the annual rate compounds to a periodic rate.
MONTHLY(default, US convention) divides the annual rate bypayments_per_year.SEMI_ANNUALis the Canadian convention required by Interest Act §6.ANNUALtreats the quoted rate as the effective annual rate.payment_frequency (PaymentFrequency) – How often payments are made. Defaults to
MONTHLY(12/yr). Other supported cadences areSEMI_MONTHLY(24/yr),BIWEEKLY(26/yr),WEEKLY(52/yr),QUARTERLY(4/yr), andANNUAL(1/yr).term_months * payments_per_yearmust be divisible by 12.payment_override (Decimal | None) – When set, pin the periodic payment to this value (in the loan’s currency) instead of deriving it from the closed-form annuity formula. The schedule’s final row absorbs whatever residual balance remains after
term_months - 1full payments — the final payment isbalance_before_final + final_period_interestrounded once to cents. This unlocks the historical “given-payment, find-term” convention used by the FHLBB Federal Home Loan Bank Review of March 1935 (Direct-Reduction Plan A: $3,000 / 6% / $30 monthly / 138 full payments + 139th of $29.27). Defaults toNone(use the closed-form value). Currently incompatible with non-emptyrate_schedule.interest_only_months (int) – Number of months at the start of the loan where only interest is paid. After this period, the loan recasts and amortizes over the remaining term. Defaults to 0.
currency_unit (Decimal) – The smallest monetary unit for quantization. Defaults to
Decimal("0.01")(cents for USD, EUR, GBP, etc.). Set toDecimal("1")for zero-decimal currencies like JPY or KRW where the base unit is the smallest denomination. Must be a positive power of 10, at most 1.fee_per_period (Decimal) – Flat amount added to each installment’s
paymenton top of the closed-form interest+principal value. Models the structure of the 1852 Crédit Foncier de France annuité (which embedded frais d’administration + fonds de réserve + impôt as a constant loading on top of the actuarial interest+amortissement) and the modern French tableau d’amortissement convention of pricing assurance emprunteur astaux_d_assurance * original_principalpaid as a flat amount per period. Closed-form payment derivation, balance accounting, and rate-schedule recasts are all unaffected: the fee rides on top. Defaults toDecimal("0")(no loading; behavior identical to v0.6.0).rate_schedule (tuple[RateChange, ...])
- payment_rounding: PaymentRounding¶
- interest_rounding: PaymentRounding¶
- balance_tracking: BalanceTracking¶
- compounding: Compounding¶
- payment_frequency: PaymentFrequency¶
- rate_schedule: tuple[RateChange, ...]¶
- class mortgagemath.RateChange(effective_payment_number, new_annual_rate, recast=True, payment_cap_factor=None)[source]¶
Bases:
objectA scheduled rate change for an Adjustable-Rate Mortgage (ARM).
Used in
LoanParams.rate_schedule(a tuple ofRateChange). Goldstein 12e §10.4 Example 13 5/1 ARM is the canonical fixture:RateChange(effective_payment_number=61, new_annual_rate=Decimal("7.2"))moves the rate from the initial 5.7% to 7.2% starting at payment 61 and recasts the level payment over the remaining 300 payments.- Parameters:
effective_payment_number (int) – 1-indexed payment ordinal at which the new rate first takes effect. Interest accrual for that payment uses the new rate. Must be
>= 2(a rate change at payment 1 is the same as constructing the loan with that initial rate).new_annual_rate (Decimal) – The new annual rate in percent (e.g.
Decimal("7.2")). Must be positive.recast (bool) – When True (the default), recompute the level payment for the remaining payments using the new periodic rate applied to the current balance. When False, keep the prior level payment — the rate change still affects every subsequent period’s interest accrual, and any residual is absorbed by the final-row trueup.
payment_cap_factor (Decimal | None) – Optional bound on the recast payment as a multiple of the prior period’s payment (e.g.
Decimal("1.075")for a 7.5% annual cap). Only meaningful whenrecast=True. When set, the new payment ismin(closed_form_recast, prior_payment * cap_factor). If the cap binds and the new periodic interest exceeds the capped payment, the unpaid interest is capitalized into the balance (negative amortization) — the correspondingInstallment.principalwill be negative and the balance will grow. Validated against the ProEducate ARM payment- cap example ($65,000 / 10% Year 1 → 12% Year 2 / 7.5% cap; year-2 P&I $613.20, cumulative neg-am $420.90).
- class mortgagemath.Installment(number, payment, interest, principal, total_interest, balance, fee=Decimal('0.00'))[source]¶
Bases:
objectA single payment in an amortization schedule.
Invariant:
principal + interest + fee == paymentfor every installment. WhenLoanParams.fee_per_periodis the defaultDecimal("0"),feeis alsoDecimal("0.00")and the invariant collapses to the historicalprincipal + interest == paymentform.The
feefield models the structure of fee-loaded annuités (Crédit Foncier de France 1852+; modern French tableau d’amortissement with assurance emprunteur loaded into the total échéance). Consumers that need the actuarially-pure interest+principal value can recover it aspayment - fee; consumers that need the gross figure the borrower wrote on the check usepaymentdirectly.- Parameters:
Enums¶
- class mortgagemath.DayCount(*values)[source]¶
Bases:
EnumDay-count convention for interest calculation.
US residential mortgages typically use 30/360. US commercial loans often use Actual/360.
- THIRTY_360 = '30/360'¶
- ACTUAL_360 = 'actual/360'¶
- class mortgagemath.PaymentRounding(*values)[source]¶
Bases:
EnumRounding convention for monetary amounts.
ROUND_UP (ceiling to nearest unit) is used by most US lenders for the monthly payment amount. ROUND_DOWN (truncate toward zero) matches published yen/won calculator schedules that floor monetary values to the currency unit. ROUND_HALF_UP (standard rounding) is used for monthly interest calculations. ROUND_HALF_EVEN (banker’s rounding) is included so fixtures from lenders or worked examples that use it can be modeled.
- ROUND_UP = 'ROUND_UP'¶
- ROUND_DOWN = 'ROUND_DOWN'¶
- ROUND_HALF_UP = 'ROUND_HALF_UP'¶
- ROUND_HALF_EVEN = 'ROUND_HALF_EVEN'¶
- class mortgagemath.BalanceTracking(*values)[source]¶
Bases:
EnumHow the running balance is tracked between schedule rows.
ROUND_EACH (the default) is how most US residential lender statements are computed: each month’s balance is rounded to cents, and next month’s interest is computed from that rounded balance. Validated against CFPB sample disclosures and most published bank statements.
CARRY_PRECISION carries the unrounded balance internally between rows and rounds only for display. This is the Excel-default behavior and the convention used by graduate-level CRE finance textbooks (Geltner et al., LibreTexts Olivier, eCampus Ontario). For long-horizon loans the two algorithms diverge by single-digit cents at each row, with the displayed total interest sometimes drifting by a few dollars over a 30-year term.
The ACTUAL_360 day-count always uses CARRY_PRECISION internally regardless of this field, because day-counted interest accrual is only meaningful with full balance precision.
- ROUND_EACH = 'round_each'¶
- CARRY_PRECISION = 'carry_precision'¶
- class mortgagemath.Compounding(*values)[source]¶
Bases:
EnumHow the annual rate compounds to a periodic rate.
MONTHLY(the default) is the US residential / commercial convention: the periodic rate isannual_rate / payments_per_yeartreated as a simple division. For monthly payments under monthly compounding this is exactly the v0.2.x behavior.SEMI_ANNUALis the Canadian convention (Interest Act §6): the quoted ratej_2is per year compounded semi-annually, and the periodic rate per payment is(1 + j_2 / 200) ** (2 / payments_per_year) - 1. For a 5% Canadian mortgage with monthly payments this is(1.025) ** (1/6) - 1 ≈ 0.41239...%, not5/12.ANNUALtreats the quoted rate as the effective annual rate; the periodic rate is(1 + annual / 100) ** (1 / payments_per_year) - 1. Included for completeness.- MONTHLY = 'monthly'¶
- SEMI_ANNUAL = 'semi_annual'¶
- ANNUAL = 'annual'¶
- class mortgagemath.PaymentFrequency(*values)[source]¶
Bases:
EnumHow often payments are made.
The
payments_per_yearmapping is the standard banking convention used by the worked-example sources the library validates against (e.g. the Canadian Olivier and eCampus Ontario texts use exactly 52 weekly / 26 biweekly / 24 semi-monthly periods per year).- MONTHLY = 'monthly'¶
- SEMI_MONTHLY = 'semi_monthly'¶
- BIWEEKLY = 'biweekly'¶
- WEEKLY = 'weekly'¶
- QUARTERLY = 'quarterly'¶
- ANNUAL = 'annual'¶
Warnings¶
- class mortgagemath.EarlyPayoffWarning[source]¶
Bases:
UserWarningSchedule terminated before
term_monthsdue to rounding overpayment.For very small principals, the cent-rounded monthly payment can overpay the closed-form value by enough that the schedule fully amortizes before the requested term. When this happens,
amortization_scheduletruncates the schedule at the actual payoff month (with the final row trued up to land balance at exactly zero) and emits this warning.Filter via the standard
warningsmachinery:import warnings from mortgagemath import EarlyPayoffWarning warnings.filterwarnings("ignore", category=EarlyPayoffWarning) # or, to promote it to an exception: warnings.simplefilter("error", EarlyPayoffWarning)