Changelog¶
[Unreleased]¶
[0.7.1] - 2026-05-06¶
Added¶
Korean Tistory fixture (46 total). First Korean fixture: ₩300,000,000 / 4% / 30yr with 3 published row-level cells, validated with
currency_unit=1andROUND_HALF_UP.loan_summary()convenience function. Returns aLoanSummarywithperiodic_payment,total_interest,total_paid,total_fees,balloon_balance,total_cost, andnum_payments. For balloon loans,total_costgives the total cash needed to extinguish the debt (scheduled payments plus balloon balance).CLI
summarysubcommand.mortgagemath summary --principal ... --rate ... --term-months ...prints the loan summary without generating a full schedule.Compact
__repr__forLoanParamsandInstallment. Interactive use now shows readable summaries instead of the verbose dataclass default.
[0.7.0] - 2026-05-05¶
International and flexible repayment structures. Fixture count grows from 36 to 45 with coverage across four new countries.
Added¶
Currency unit precision. New
LoanParams.currency_unitfield (defaultDecimal("0.01")) controls the quantization unit for all monetary amounts. Set toDecimal("1")for zero-decimal currencies like JPY or KRW.ROUND_DOWNrounding mode.PaymentRounding.ROUND_DOWNtruncates monetary values to the configured currency unit, matching yen/won-style floor conventions.Interest-only (IO) periods. New
LoanParams.interest_only_monthsfield. The borrower pays only accrued interest for the specified period; after the IO period the loan recasts and fully amortizes over the remaining term.Zero-interest loan support.
annual_rate=0is now accepted (previously raisedValueError). Payment isprincipal / nrounded to the currency unit.Flat per-period fees. New
LoanParams.fee_per_periodfield andInstallment.feeattribute model fee-loaded schedules such as French assurance emprunteur. The fee rides on top of the P+I schedule without affecting balance accounting.Canadian accelerated bi-weekly. New convenience constructor
canada_accelerated_biweekly(...)derives the accelerated bi-weekly payment (monthly / 2) used by major Canadian banks.Convenience constructors.
fixed_rate_mortgage(),us_30_year_fixed(),us_15_year_fixed(),canada_fixed_j2(),us_actual_360_commercial(), andfixed_payment_mortgage()return ordinary validatedLoanParamsobjects with fixture-backed defaults.9 new fixtures (45 total). MoneyVox France (first French fixture, with assurance emprunteur fee loading); JHF Flat 35 and LoanKeisan Japan (first Japanese fixtures, full 360-row
ROUND_DOWNschedule); CFPB Interest-Only sample; RBC Canadian Accelerated Bi-Weekly; TI BA II Plus guidebook; Solution Bank and BCC Brescia Italy (first Italian fixtures); Zero-Interest Promo (synthetic).Pandas vignette. New
docs/vignettes/pandas.qmddemonstrating DataFrame conversion and matplotlib plotting.Optional
examplesdependency extra.matplotlib,pandas, andnumpyfor the visualization examples.
Fixed¶
Validation vignette fixture smoke test. Added pytest coverage for the Python chunks in
validation.qmdso missing display-map entries for new enum values fail during normal tests.Corrected the Canadian
j_2quickstart payment. The 25-year monthly-payment example now showsDecimal("1744.81"), matching the semi-annual-compounding calculation.
[0.6.1] - 2026-05-03¶
Stabilization release: removes review blockers found after v0.6.0 and makes the public documentation, Read the Docs build, and release artifacts trustworthy. No new mortgage features.
Fixed¶
Schedule math no longer depends on the caller’s ambient Decimal context.
amortization_schedule(...)now runs under an explicit 50-digitlocalcontext()(matching whatperiodic_payment()already does). Without this guard, a caller who lowereddecimal.getcontext().preccould silently shift the Fannie Mae §1103 SARM published $20,885,505.83 balloon to $20,885,505.23 — a 60-cent error on a $25 M schedule. Cent-accurate guarantees now hold under any reasonable Decimal context.payment_overrideno longer overpays into negative balances. The carry-precision schedule path gains the early-payoff guard the round-each path already has. An over-large override (e.g.,$500/moon a$1,000 / 12moloan) now truncates the schedule at the actual payoff period withEarlyPayoffWarninginstead of producing rows with negative balance, negative interest, or negative final payment.payment_overridevalidation now happens at construction time.LoanParams.__post_init__rejects:non-cent overrides (e.g.
Decimal("99.999")),overrides combined with a balloon (
amortization_period_months > term_months). Generalamortization_period_monthsvalidation (positivity and>= term_months) was also moved fromperiodic_paymentinto__post_init__, so override loans (which return the pinned payment without invokingperiodic_payment’s guards) are checked too.
.readthedocs.yamlinstall path normalized topath: .(a stray local-drift typo had been seen in some checkouts).Sphinx docs build cleanly under
-Wstrict mode. Duplicated autodoc indexing inapi.mdremoved; MyST:start-after:marker inchangelog.mdsimplified; one cross-reference link in CHANGELOG.md changed to an absolute GitHub URL so it resolves both inside and outside the docs tree.
Changed¶
CLAUDE.mdadded at the project root in the post-v0.6.0 period documenting project-specific rules (strict complexity-threshold; synthetic-fixture-only features are not enough; no-partial-fixtures), branch-naming convention (vX.Y.Z-<feature>for releases vs<feature>-wipfor unversioned preliminary work), the activefee-per-period-wipWIP branch with its trigger-source watchlist, and the open research priority of finding more real published worked amortization examples.README tightened from 527 to 164 lines (~70% reduction), matching the norm for focused Python packages (requests ~110, pydantic ~160, httpx ~200). Three overlapping “why” sections consolidated into one. Five Quick Start examples reduced to one (CFPB H-25(B), with CLI form before Python form). Detailed rounding / balance-tracking / day-count / API-reference reference sections moved to the canonical Read the Docs site to stop drifting out of sync. Lint badge commented out (kept in source); Python pyversions badge dropped (redundant with the PyPI badge).
README “What’s validated” section now leads with substantive nouns (“37 fully published amortization tables from government regulatory documents, GSE servicing guides, and academic textbooks, exercised by a test suite of more than 300 tests…”) rather than internal-test-suite vocabulary (“paired fixtures (TOML loan parameters + CSV schedule) under tests/schedules/”).
Doc references to the three v0.6.0-folded vignettes (
arm-regz-h14,payment-caps-proeducate,canadian-j2) updated inREADME.md,docs/vignettes/README.md, anddocs/sphinx/vignettes.mdto point at the consolidatedexamples.qmdvignette.Public fixture-count claims and validation language unified. Top-level README,
docs/sphinx/index.md, anddocs/sphinx/vignettes.mdhad drifted across releases (37, 27, 36 respectively). They now agree on the actual count (36 paired TOML/CSV fixtures). The README’s “every published cell matches” tagline was softened to “every committed fixture cell reproduces its source value to the cent” — a more defensible claim that acknowledges the small number of historical sources (notably two rows of the Geltner CRE example) where the source itself contains an internal arithmetic typo and the divergent rows are documented rather than forced into the corpus.Stale “future work” entries removed. Canadian semi-annual compounding shipped in v0.3.0 and is now documented as shipped (was still listed as missing). The History vignette §9 no longer claims “Given-payment, find-term contracts are not modeled” — v0.6.0’s
payment_overridecovers them.
Documentation¶
11 new regression tests (
tests/test_v061_regressions.py) pin the Decimal-context-independence guarantee, the carry-precision early-payoff guard, and the v0.6.1 validation moves.docs/v0.6.1-plan.mdrecords the stabilization-release plan.
[0.6.0] - 2026-05-02¶
Added¶
LoanParams.payment_override— pin the periodic payment to a chosen value instead of deriving it from the closed-form annuity formula. The schedule’s final row absorbs the residual balance, with the published payment computed from the full-precisionbalance + interestrounded once. Reproduces the historical “given-payment, find-term” convention used by pre-1968 American building-and-loan associations and the earliest U.S. federal direct-reduction schedules.FHLBB Federal Home Loan Bank Review (March 1935) Direct-Reduction Plan A fixture — $3,000 / 6% / 138 monthly payments of $30 + 139th of $29.27. The earliest U.S. federal-authority publication of a worked direct-reduction amortization schedule, validated cell-for-cell against the source. (Fixture #36.)
mortgagemath schedule --payment-override AMOUNTCLI flag exposing the new field.
Changed¶
Vignette set consolidated from six to four. The three single-fixture vignettes (
arm-regz-h14,payment-caps-proeducate,canadian-j2) are folded into a new comprehensiveexamples.qmdorganized by country convention then by loan type. The four-vignette set is now: At a glance, Validation, Examples, History. Every example block in the new vignette has a uniform structure: scenario,LoanParams(...)literal, equivalent CLI invocation, live Python chunk producing the schedule and published-source anchors, citation + fixture filename.
Documentation¶
docs/v0.6-plan.mdrecords the design decisions for this release, including the deferredfee_per_periodfield whose ship trigger is conditional on retrieving one row-level Crédit Foncier or modern French source.docs/sphinx/installation.mdself-check sample updated to reflect v0.6.0.
[0.5.2] - 2026-05-02¶
Added¶
8 new validated fixtures drawn from public-domain and open-licensed sources, expanding the suite from 27 to 35:
Skinner §42 Example 1 ($1,000 / 6% / 15-year annual payment; public-domain 1913) — first annual-cadence fixture sourced from a pre-WWI actuarial textbook.
Skinner §42 Example 3 piano ($500 / 6% effective annual / 5-year monthly payment) — first fixture isolating
Compounding.ANNUALon a monthly-cadence loan, validating the actuarial convention of treating the quoted rate as effective annual and back-deriving the equivalent nominal-monthly rate.Arcones SOA FM Manual §4.1 Example 4 ($20,000 / 8% / 12-year annual schedule) — full 12-row published schedule from an SOA Exam FM / CAS Exam 2 study manual; first multi-row annual-cadence fixture in the corpus.
Broverman MIC §2.1 Example 2.7(a) and 2.7(b) ($12,000 at 12% / 36 months and 15% / 48 months) — closed-form payment anchors at two distinct rates from a perennial SOA Exam FM reference text.
eCampus Ontario Mathematics of Finance §4.3 Example 4.3.1 (Pearline) ($10,000 / 10% / 4-year annual full schedule).
eCampus §4.3 Exercise 2 (Erika) ($32,600 / 4.83% / 9-year monthly with year-aggregate anchors).
eCampus §4.3 Exercise 3 (Johnetta) ($20,200 / 3.53% / 8-year monthly with mid-schedule probe at payment 60).
Self-contained bibliographic schema for fixture TOMLs. Every fixture’s
[source]block now carriesshort_label,bib_key,bib_title, andcitationfields with full prose citations (authors, title, edition, publisher, year, ISBN, URL where available). Fixtures sharing a source share abib_keyand the bibliography emits one entry per key.
Changed¶
Validation vignette is now data-driven. The
validation.qmdbibliography section is generated directly from the fixture[source]blocks; the previously hand-curatedSOURCE_LABELSdict and prose bibliography are gone. Adding a new fixture automatically updates both the validation table and the bibliography on next render.tests/schedules/README.mddocuments the new bibliographic fields and listsreference_workas an additional acceptedkindvalue (used by the Wikipedia fixture).
[0.5.1] - 2026-04-30¶
Added¶
Read the Docs site at https://mortgagemath.readthedocs.io/. Small Sphinx project under
docs/sphinx/(Furo theme + MyST parser + autodoc) covering installation, quickstart, full auto-generated API reference, vignette directory, and changelog. Sphinx pulls the version from package metadata, so the site always reports the same version aspip showand the in-Python__version__.GitHub Pages site at https://murraystokely.github.io/mortgagemath/ hosting the rendered Quarto vignettes. Built and deployed by a new
vignettes.ymlGitHub Actions workflow that triggers on push tomain(whendocs/vignettes/**changes) and onv*tags (which also publish the rendered PDFs as workflow artifacts).Pre-rendered PDF vignettes committed to
docs/vignettes/rendered/so anyone browsing the GitHub repo can click a PDF and read it without cloning or running Quarto.README “Documentation” section prominently linking the Read the Docs site, the GitHub Pages vignette site, and the five individual PDFs.
PyPI Project URLs expanded:
Documentationnow points to Read the Docs (was the README anchor); newVignettesURL points to GitHub Pages; newChangelogURL.
Changed¶
pyproject.tomlgains a[project.optional-dependencies] docsgroup withsphinx,furo,myst-parser, andsphinx-copybutton. Read the Docs usespip install .[docs]per.readthedocs.yaml.
Notes¶
No library code changes in this release. Test suite, coverage, CLI, and the v0.5.0 ARM payment-cap feature are unchanged.
[0.5.0] - 2026-04-30¶
Added¶
Payment caps with negative amortization for ARMs.
RateChangegains an optionalpayment_cap_factorfield (e.g.Decimal("1.075")for a 7.5% annual cap). When set on a recasting rate change, the new payment is bounded bymin(closed_form_recast, prior_payment * cap_factor). If the cap binds and the per-period interest exceeds the capped payment, the unpaid interest is capitalized into the balance — the correspondingInstallment.principalis negative and the balance grows. The per-row invariantprincipal + interest == paymentcontinues to hold.ProEducate ARM payment-cap fixture. $65,000 / Year 1 at 10% / Year 2 at 12% / 30yr / 7.5% annual payment cap. Validates 7 cents-level anchors: year-1 P&I $570.42, balance after pmt 12 $64,638.72, year-2 capped P&I $613.20, month-13 interest $646.39 with -$33.19 principal (neg-am), cumulative neg-am $420.90 over year 2, balance after pmt 24 $65,059.62, year-2 uncapped recast for reference $667.30. See
tests/schedules/proeducate_arm_pmt_cap_65k_10pct_to_12pct_360mo.{toml,csv}.CLI cap-suffix syntax.
--rate-changeacceptsEFFECTIVE_PMT:NEW_RATE:cap=FACTOR, repeatable suffixes parsed in any order alongside the existing:no_recastflag.6 new structural / invariant tests for cap mechanics in both balance-tracking modes (cap binds vs doesn’t bind, neg-am invariant, regression check that cap=None is byte-identical to v0.4 recast).
Quarto vignette documentation set in
docs/vignettes/. Five vignettes branded with the MortgageMath logo, rendered to both HTML (Quarto’s website format) and PDF (typst engine, deterministic builds, no LaTeX dependency).At a glance — single-page overview with install/use, worked example, and the full Required + Optional parameter list.
Validation against published sources — 27-fixture table with the six parameter columns (Day / Bal / Rnd / Cmp / Freq / ARM) showing the exact
LoanParamssettings required to match each source, plus a curated bibliography keyed by source. The table is generated dynamically fromtests/schedules/*.tomlso it stays current as fixtures are added.Reg Z Sample H-14: an ARM walkthrough — cap derivation table, encoded
LoanParams, and live anchor verification of all 11 published anchors against the regulatory worked example.Payment caps and negative amortization — ProEducate worked example with a row-by-row trace of the rate-change boundary (months 10–25) showing where the cap binds and where the schedule enters negative amortization.
Canadian semi-annual mortgages — Interest Act §6 convention with worked Olivier (Chans, monthly) and eCampus (§4.4.1, quarterly) reproductions.
Validation¶
Validation rejects
payment_cap_factor <= 0and the combinationpayment_cap_factor+recast=False(cap is only meaningful when recasting).Empty
rate_scheduleand rate changes withoutpayment_cap_factorare byte-identical to v0.4.0 (regression- tested; every existing fixture passes unchanged).
Notes¶
Goldstein 12e §10.4 Example 14 (the textbook canonical 5/1 ARM with payment cap and neg-am at year 7) was the algorithmic motivator for the API but is not committed as a fixture. Goldstein computes year-6 balances via the textbook closed-form outstanding-balance formula at the rounded year-1 payment, which diverges 13–26¢ from the library’s row-by-row schedule (same algorithmic mismatch documented in v0.4.0 for Goldstein Ex 13). The 1¢ propagation breaks the year-7 cap calculation. ProEducate is the cents-level published-source fixture.
See
docs/v0.5-plan.mdfor the v0.5 design and a deferred-work list (Canadian biweekly fixtures, UK / AU fixtures, Quarto vignette docs, Reg Z Appendix J APR validator, GPM).
[0.4.0] - 2026-04-30¶
Added¶
Adjustable-rate mortgages (Tier 1). New
RateChangedataclass andLoanParams.rate_schedulefield (tuple[RateChange, ...]) allow rate changes at specified payment numbers. EachRateChangedeclares aneffective_payment_number(1-indexed), anew_annual_rate, and an optionalrecastflag (default True — recompute the level payment over remaining payments at the new rate). Emptyrate_scheduleis the v0.3.0 fast path with byte-identical output; every existing fixture still passes.Validation.
RateChange.__post_init__rejectseffective_payment_number < 2and non-positive rates.LoanParams.__post_init__rejects non-strictly-increasing schedules, entries past the loan’s total payments, ARM + ACTUAL_360 (day-counted accrual semantics undefined under non-monthly cadences), and ARM + balloon (amortization_period_months != term_months) — both deferred until a fixture motivates them.First-class CLI. Three subcommands:
mortgagemath selfcheck(the existing post-install validation),mortgagemath payment(print the periodic P&I), andmortgagemath schedule(full schedule with--format table|csv|json). Console-script entry pointmortgagemath = "mortgagemath.__main__:main"registered inpyproject.toml;python -m mortgagemathcontinues to work. No-args invocation defaults to selfcheck (preserves v0.2.x behavior). ARMs supported in the CLI via repeatable--rate-change EFFECTIVE_PMT:NEW_RATE[:no_recast]. Stdlib only — no new runtime dependencies.24 new structural / invariant tests for ARMs covering both balance-tracking modes, recast and no-recast, multi-rate schedules, and the empty-schedule fast path.
17 new CLI tests covering all three subcommands, all three output formats, ARMs via flag, the no-args default routing to selfcheck, and standard argparse error handling.
Validated against¶
Reg Z, 12 CFR Part 1026, Appendix H, Sample H-14 — $10,000 / 30yr 1/1 ARM at 1-year CMT + 3pp margin, 2pp annual cap, 5pp symmetric lifetime cap. Initial rate 17.41% (1982 origination). The fixture validates 11 schedule rows spanning the regulation’s full 15-year published trajectory, including the periodic cap binding at years 2 and 4 (1983 / 1985), the lifetime cap binding from year 5 onward (1986–1996), and the year-15 terminal balance $8,700.37. Every published value reproduces exactly under
BalanceTracking.ROUND_EACH+ROUND_HALF_UP. Seetests/schedules/regz_apph_h14_arm_10k_1741_360mo.{toml,csv}.
Notes¶
Cap-application math. The Reg Z H-14 fixture encodes post-cap rates explicitly in its
[[loan.rate_schedule]]entries; the cap-derivation table (year-by-year application of periodic + lifetime caps to the index + margin) is documented in the fixture’s TOMLnotesfield. A library-sideIndexedRateScheduleAPI that derives post-cap rates from raw index history was considered (“Tier 2” indocs/v0.4-plan.md) but deferred — only one published source motivates it today, falling below the project’s complexity-threshold rule. When a second source surfaces, that helper becomes justified.Goldstein §10.4 Example 13 5/1 ARM not committed. Goldstein publishes balances computed via the textbook closed-form outstanding-balance formula
pmt × (1 - (1+r)^-(n-k))/rusing the rounded payment $1,160.80, which differs from the library’s row-by-row schedule by 13–26¢ at month 60 ($185,405.12 published vs $185,405.25 carry-precision / $185,405.38 round-each). This is an algorithmic difference, not a library bug — Goldstein computes balances at conceptual checkpoints, while the library produces actual amortization rows. Per the no-partial-fixtures rule, the Goldstein fixture is not committed.tests/test_selfcheck.pywas retired in favor of consolidated coverage in the newtests/test_cli.py.
[0.3.0] - 2026-04-29¶
Added¶
Non-monthly compounding. New
Compoundingenum withMONTHLY(default; unchanged US convention),SEMI_ANNUAL(the Canadian Interest Act §6 convention — quotedj_2rate is per year compounded semi-annually), andANNUAL. Adds thecompoundingfield toLoanParams.Non-monthly payment cadence. New
PaymentFrequencyenum coveringMONTHLY,SEMI_MONTHLY,BIWEEKLY,WEEKLY,QUARTERLY, andANNUAL. Adds thepayment_frequencyfield toLoanParamsand apayments_per_yearproperty toPaymentFrequency.term_months × payments_per_yearmust be divisible by 12; validated at construction.periodic_payment(loan)— replacesmonthly_payment(loan)as the canonical name now that non-monthly cadences are supported.monthly_paymentis preserved as a permanent alias (monthly_payment is periodic_paymentevaluates True); existing imports keep working with no deprecation.4 new test fixtures from Canadian textbook sources, all reproducing every published value to the cent under
Compounding.SEMI_ANNUAL:Olivier Business Math §13.4 — Chans first term: $350,100 / j_2 = 4.9% / 3-yr term, 20-yr amort, monthly → $2,281.73 + balloon $316,593.49.
Olivier — Chans renewal: $316,593.49 / j_2 = 5.85% / 17yr → $2,440.73.
eCampus Ontario Mathematics of Finance §4.4.1 — first term: $297,500 / j_2 = 3.8% / 3-yr term, 20-yr amort, quarterly → $5,317.62 + balloon $265,830.61.
eCampus §4.4.1 — renewal: $265,830.61 / j_2 = 2.5% / 17yr, quarterly → $4,807.70.
Fixture TOML schema additions:
[loan.compounding],[loan.payment_frequency], and[expected.periodic_payment](alias for[expected.monthly_payment]). All optional and backward compatible.docs/v0.3-design.mddocuments the v0.3 (international fixed- rate) and v0.4+ (ARM, Australian, UK reversion) design decisions and rejected sources.
Changed¶
Schedule generation generalizes from “monthly rate × term in months” to “periodic rate × total payments.” Default-construction (monthly
monthly) is still a fast-path with byte-identical numerical output to v0.2.x — every existing fixture passes unchanged.
Notes¶
DayCount.ACTUAL_360requires bothCompounding.MONTHLYandPaymentFrequency.MONTHLY; day-counted accrual is not well-defined for non-monthly cadences and the §1103 / §1104 / §1106 worked examples we validate against are all monthly + monthly. Validated atLoanParamsconstruction.Sources investigated but not committed as fixtures (per the no-partial-fixtures rule): FCAC Government of Canada calculator (publishes a “total interest” computed as
unrounded_payment × n − P, not from the schedule); AIB(NI) Self-Build representative example (publishes “total payable” computed asrounded_payment × n + fees, again not from the schedule). Republic of Ireland and Australian sources also investigated; details indocs/v0.3-design.md.
[0.2.2] - 2026-04-29¶
Added¶
python -m mortgagemathruns a built-in post-install self-check that recomputes a small set of well-known reference values (CFPB H-25(B), Goldstein §10.3 Example 1 carry-precision schedule, and Fannie Mae §1103 monthly P&I + balloon at term) and reports pass/fail. Exits 0 on success, 1 on any mismatch — useful for verifying that a freshly-installed wheel matches the same numbers the test suite validates, without needing to clone the repo.
Fixed¶
mortgagemath.__version__is now sourced fromimportlib.metadata.version("mortgagemath")instead of a hard-coded string. The previous literal had drifted (still reported"0.1.1"in the 0.2.0 and 0.2.1 wheels even thoughpip showcorrectly reported the published version). The in-Python__version__now always matches the installed package metadata.
[0.2.1] - 2026-04-29¶
Changed¶
PyPI Trove classifier promoted from
Development Status :: 4 - BetatoDevelopment Status :: 5 - Production/Stable. The library has been validated against every published source it ships fixtures for (CFPB H-25(B), Fannie Mae §1103, Geltner CRE textbook, Goldstein Finite Mathematics, OpenStax, Las Positas, MS State Extension, and synthetic boundary cases), exposes a stable public API, and is on-channel via PyPI and conda-forge.
[0.2.0] - 2026-04-29¶
Add support for Excel-style carry-precision balance tracking and adds more textbook examples.
Fixed¶
amortization_schedule()no longer walks the balance past zero for very small principals. When 30/360 round-each-balance accounting amortizes a loan beforeterm_monthsdue to monthly payment rounding overpayment, the schedule is now truncated at the actual payoff month with the final row trued up to land balance at exactly$0.00. Reference example:$20 / 4.4% / 30yrpreviously produced 360 rows with the balance walking from$0.02(month 300) through−$7.18(month 359) before a−$7.21“payment” trued it up; it now produces 302 rows ending cleanly at month 301.
Added¶
BalanceTrackingenum andLoanParams.balance_trackingfield, with valuesROUND_EACH(the default — US residential lender convention, unchanged from prior releases) andCARRY_PRECISION(Excel-default carry-precision: full-precision balance carried internally between rows, per-row figures rounded to cents only for display). Affects 30/360 schedules; Actual/360 schedules already used carry-precision unconditionally.Test fixture for the Geltner et al. Commercial Real Estate Analysis Chapter 20 Exhibit 20-6 CPM example ($1M / 12% / 30yr → monthly P&I $10,286.13). The library reproduces 7 of the 9 published rows exactly under
BalanceTracking.CARRY_PRECISION; the two remaining rows contain editorial arithmetic typos in the textbook itself (verified:principal + interest != paymentin the published values). Seedocs/accuracy.mdandtests/test_schedule.py::TestGeltnerCPM.EarlyPayoffWarning(aUserWarningsubclass) is emitted when the schedule truncates due to rounding overpayment. Filterable via the standardwarningsmodule. Exported from the top-level package.
[0.1.0] - 2026-04-28¶
First public release.
Added¶
monthly_payment(LoanParams)— closed-form annuity payment, Decimal end-to-end, configurable rounding mode.amortization_schedule(LoanParams)— full month-by-month schedule guaranteeingprincipal + interest == paymentper row and final balance of exactlyDecimal("0.00")for fully amortizing loans.LoanParamsdataclass withprincipal,annual_rate,term_months, plus optionalday_count,payment_rounding,interest_rounding,start_date, andamortization_period_months.DayCountenum:THIRTY_360(default — US residential round-each- balance accounting) andACTUAL_360(commercial day-counted accrual with full-precision internal balance).PaymentRoundingenum:ROUND_UP(ceiling — most US residential lenders),ROUND_HALF_UP, andROUND_HALF_EVEN(banker’s rounding).Native balloon-loan support: when
amortization_period_months > term_months, the closed-form payment uses the longer basis, the schedule producesterm_monthsrows, and the final row’sbalanceis the balloon owed at term.Installmentdataclass exposingnumber,payment,interest,principal,total_interest,balance.Test fixtures validated against the CFPB H-25(B) sample Closing Disclosure, the Fannie Mae Multifamily Selling and Servicing Guide §1103 Tier 2 SARM example (monthly P&I + $20,885,505.83 balloon at term 120, both exact), OpenStax Contemporary Mathematics worked examples, Las Positas Math for Liberal Arts examples, the Mississippi State Extension P3920 publication, and synthetic half-cent boundary cases. See
docs/accuracy.mdfor the full source table.100% test coverage on
src/mortgagemath/. 144 tests, including parametric structural invariants and per-fixture validation.CI workflows: tests on Python 3.11/3.12/3.13/3.14 (Ubuntu), ruff lint + format check, mypy
--strict, dynamic coverage badge.GitHub issue templates for cent-mismatch submissions and general bug/feature reports.
py.typedmarker for downstream type-checkers.
Documentation¶
README.mdwith installation, quick start, rounding/day-count conventions, accuracy summary, and PyPI badges.docs/accuracy.mdlisting every source the library reproduces exactly.docs/future-work.mdlisting investigated sources that did not match exactly (and why).tests/schedules/README.mddocumenting the fixture schema.CONTRIBUTING.mdpointing reporters at the issue templates and contributors at the fixture submission flow.