Quickstart¶
A 30-year fixed-rate residential mortgage¶
from mortgagemath import us_30_year_fixed, periodic_payment, amortization_schedule
loan = us_30_year_fixed("300000", "6.5")
pmt = periodic_payment(loan) # Decimal("1896.21")
sched = amortization_schedule(loan)
print(sched[1].interest) # Decimal("1625.00")
print(sched[1].principal) # Decimal("271.21")
print(sched[-1].balance) # Decimal("0.00") — exact closure
Returns a cent-accurate periodic payment and a lender-style
amortization schedule that lands exactly at $0.00 on the final
payment. The schedule is a plain list[Installment] —
sched[0] is the initial balance (no payment), sched[1]
through sched[360] are the monthly payments.
# Useful schedule queries
print(sched[-1].total_interest) # Decimal("382628.90") — lifetime interest
print(len(sched) - 1) # 360 — number of payments
The convenience constructors return ordinary LoanParams objects.
Use LoanParams directly when you need an uncommon configuration,
or pass optional rounding overrides when a published source requires
them.
Quick loan summary¶
For the headline numbers without iterating the schedule:
from mortgagemath import us_30_year_fixed, loan_summary
s = loan_summary(us_30_year_fixed("300000", "6.5"))
print(s.periodic_payment) # Decimal("1896.21")
print(s.total_interest) # Decimal("382628.90")
print(s.total_paid) # Decimal("682628.90")
print(s.num_payments) # 360
print(s.balloon_balance) # Decimal("0.00") — fully amortizing
print(s.total_cost) # Decimal("682628.90") — same as total_paid
For balloon loans, total_paid is the sum of scheduled payments
only; total_cost adds the balloon balance — the total cash
required to extinguish the debt at maturity:
from datetime import date
from mortgagemath import us_actual_360_commercial, loan_summary
# 10-year term on a 30-year amortization basis — balloon at term.
loan = us_actual_360_commercial(
"25000000", "5.5",
term_years=10,
amortization_years=30,
start_date=date(2018, 12, 1),
)
s = loan_summary(loan)
print(s.balloon_balance) # Decimal("20885505.83")
print(s.total_cost) # total_paid + balloon
Pandas integration¶
import pandas as pd
from mortgagemath import us_30_year_fixed, amortization_schedule
loan = us_30_year_fixed("300000", "6.5")
df = pd.DataFrame(amortization_schedule(loan))
print(df[["number", "payment", "interest", "principal", "balance"]].head())
Canadian semi-annual mortgages¶
The Canadian Interest Act §6 quotes rates as semi-annually
compounded. The canada_fixed_j2 helper chooses that convention:
from mortgagemath import canada_fixed_j2, periodic_payment
# Canadian 5-year-term mortgage on a 25-year amortization basis,
# monthly payments at j_2 = 5%.
loan = canada_fixed_j2("300000", "5", amortization_years=25, term_years=5)
print(periodic_payment(loan)) # Decimal("1744.81")
See Vignettes for a full Canadian-mortgage walkthrough.
Adjustable-rate mortgages¶
ARMs are modelled via a tuple of RateChange entries:
from decimal import Decimal
from mortgagemath import LoanParams, RateChange, PaymentRounding, amortization_schedule
# 5/1 ARM: $200,000 at 5.7%, single rate change at month 61 to 7.2%.
loan = LoanParams(
principal=Decimal("200000"),
annual_rate=Decimal("5.7"),
term_months=360,
payment_rounding=PaymentRounding.ROUND_HALF_UP,
interest_rounding=PaymentRounding.ROUND_HALF_UP,
rate_schedule=(
RateChange(effective_payment_number=61, new_annual_rate=Decimal("7.2")),
),
)
sched = amortization_schedule(loan)
print(sched[60].payment) # Decimal("1160.80") — initial
print(sched[61].payment) # Decimal("1334.16") — recast
RateChange also supports an optional payment_cap_factor for
payment-capped ARMs with negative amortization. See the
Vignettes page for the full Reg Z H-14 and ProEducate
walkthroughs.
Commercial Actual/360 with a balloon¶
from datetime import date
from mortgagemath import (
amortization_schedule,
periodic_payment,
us_actual_360_commercial,
)
# Fannie Mae §1103 Tier 2 SARM: $25M at 5.5%, 10-year term on a
# 30-year amortization basis, Actual/360 day count.
loan = us_actual_360_commercial(
"25000000",
"5.5",
term_years=10,
amortization_years=30,
start_date=date(2018, 12, 1),
)
sched = amortization_schedule(loan)
print(periodic_payment(loan)) # Decimal("141947.25")
print(sched[120].balance) # Decimal("20885505.83") — balloon at term
Pinning the payment (FHLBB 1935 given-payment convention)¶
Pre-1968 American building-and-loan practice often chose a round
periodic payment (typically 1% of original principal per month)
and accepted whatever final-payment trueup the math produced.
The payment_override field reproduces this:
from mortgagemath import LoanParams, BalanceTracking, PaymentRounding
# FHLBB Federal Home Loan Bank Review (March 1935) Plan A:
# $3,000 / 6% / payment chosen as 1% = $30 / month.
loan = LoanParams(
principal=Decimal("3000.00"),
annual_rate=Decimal("6"),
term_months=139,
payment_rounding=PaymentRounding.ROUND_HALF_UP,
interest_rounding=PaymentRounding.ROUND_HALF_UP,
balance_tracking=BalanceTracking.CARRY_PRECISION,
payment_override=Decimal("30.00"),
)
sched = amortization_schedule(loan)
print(sched[138].payment) # Decimal("30.00")
print(sched[139].payment) # Decimal("29.27") — final-row trueup
Fee-loaded French schedule¶
A fee_per_period field on LoanParams adds a flat amount
per period to each Installment.payment. It models the
modern French tableau d’amortissement convention of pricing
assurance emprunteur as taux × original_principal paid as
a flat amount per month.
loan = LoanParams(
principal=Decimal("10000"),
annual_rate=Decimal("5"),
term_months=12,
payment_rounding=PaymentRounding.ROUND_HALF_UP,
interest_rounding=PaymentRounding.ROUND_HALF_UP,
fee_per_period=Decimal("2.92"), # MoneyVox 0.35% assurance / 12
)
print(periodic_payment(loan)) # Decimal("856.07") — pure P+I
sched = amortization_schedule(loan)
print(sched[1].payment) # Decimal("858.99") — gross mensualité
print(sched[1].fee) # Decimal("2.92")
The closed-form periodic_payment(loan) continues to return
the actuarially-pure interest+principal value; the fee rides on
top in Installment.payment, with Installment.fee exposing
the loading separately.
Command line¶
# Just the periodic payment
mortgagemath payment --principal 131250 --rate 4.25 --term-months 360
# Full schedule as table (default), CSV, or JSON
mortgagemath schedule --principal 131250 --rate 4.25 --term-months 360 \
--format csv > schedule.csv
# ARM with rate change at month 61
mortgagemath schedule --principal 200000 --rate 5.7 --term-months 360 \
--payment-rounding ROUND_HALF_UP --interest-rounding ROUND_HALF_UP \
--rate-change 61:7.2 --format json
# FHLBB 1935 Plan A — pinned payment with final-row trueup
mortgagemath schedule --principal 3000 --rate 6 --term-months 139 \
--payment-rounding ROUND_HALF_UP --interest-rounding ROUND_HALF_UP \
--balance-tracking carry_precision --payment-override 30 --format table
# Fee-loaded French assurance schedule
mortgagemath schedule --principal 10000 --rate 5 --term-months 12 \
--payment-rounding ROUND_HALF_UP --interest-rounding ROUND_HALF_UP \
--fee-per-period 2.92 --format table
mortgagemath --help and mortgagemath <subcommand> --help document
the full flag surface.