Python implementation of the Bond Market Association (BMA) standard formulas for mortgage cashflow modeling: scheduled amortization, prepayment/default assumptions, and loan-to-pool aggregation.
This library is designed to be transparent and teachable (for both Python and finance novices) while still being strict about core modeling contracts.
- B.1 scheduled mortgage math: balance/payment/amortization factors for fixed and floating coupons.
- B.2-B.4 prepayment and default math: SMM/CPR/PSA/ABS, CDR/MDR/SDA, and historical speed recovery.
- C.3 cashflow runners: scheduled and actual loan-level projections.
- Engine layer:
Loan,TapeSchema, portfolio runners, aggregation/waterfall, and Parquet persistence.
pip install bma-standard-formulasRequirements: Python 3.12+, NumPy, SciPy, pandas, pyarrow.
git clone https://github.com/crmerrill/bma-standard-formulas.git
cd bma-standard-formulas
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev,app]"If you plan to run the frontend, also install Node.js 20+ (includes npm).
import numpy as np
from bma_standard_formulas.formulas import (
run_bma_scheduled_cashflow,
run_bma_actual_cashflow,
generate_smm_curve_from_psa,
cdr_to_mdr_vector,
)
scheduled = run_bma_scheduled_cashflow(
original_balance=1_000_000,
current_balance=1_000_000,
coupon_vector=8.0, # annual percent
original_term=360,
remaining_term=360,
)
smm = generate_smm_curve_from_psa(100, 360) # decimal SMM, len=361
mdr = cdr_to_mdr_vector(np.full(361, 1.0)) # 1% annual CDR -> decimal MDR
sev = np.full(361, 0.35) # 35% severity
actual = run_bma_actual_cashflow(
scheduled_cf=scheduled,
smm_curve=smm, # period-indexed, index 0 is snapshot
mdr_curve=mdr,
severity_curve=sev,
coupon_vector=8.0, # scalar or vector in annual percent
)import numpy as np
from bma_standard_formulas.engine import (
read_loan_tape,
run_actual_portfolio,
)
loans = read_loan_tape("tape.csv", asof_date=np.datetime64("2024-01-01"))
max_term = max(l.original_term for l in loans)
smm = np.full(max_term + 1, 0.005) # age-indexed SMM
mdr = np.full(max_term + 1, 0.001) # age-indexed MDR
sev = np.full(max_term + 1, 0.35) # age-indexed severity
portfolio = run_actual_portfolio(loans, smm, mdr, sev, flush=True)
pool_df = portfolio.pool.to_dataframe()This repo also includes an optional app scaffold at src/bma_cfengine_app/
(FastAPI backend + React/Vite UI).
Install with app extras:
pip install -e ".[app]"Run the full app stack:
python scripts/run_app.pyUseful variants:
# do not auto-open browser
python scripts/run_app.py --no-browser
# custom ports
python scripts/run_app.py --api-port 9000 --ui-port 5200
# serve built UI from FastAPI (no Vite dev server)
python scripts/run_app.py --prodDefault endpoints:
- UI:
http://localhost:5175 - API:
http://127.0.0.1:8000 - API docs:
http://127.0.0.1:8000/api/docs
If you want to run backend and frontend separately instead of scripts/run_app.py:
# Terminal 1: API
uvicorn bma_cfengine_app.api.main:app --host 127.0.0.1 --port 8000 --reload# Terminal 2: UI
cd src/bma_cfengine_app/ui
npm install
npm run dev -- --port 5175Then open:
- UI:
http://localhost:5175 - API docs:
http://127.0.0.1:8000/api/docs
Build frontend assets:
cd src/bma_cfengine_app/ui
npm run buildServe built frontend from FastAPI:
python scripts/run_app.py --prodCanonical BMA example fixtures live in:
src/bma_standard_formulas/formulas/examples.py
Use it directly from Python:
from bma_standard_formulas.formulas import examples
print(examples.INCLUDED_BMA_REFERENCE_IDS)This repo includes notebooks for worked examples:
notebooks/BMA_Examples_Walkthrough.ipynbnotebooks/ReadMeExamples.ipynb
Open with Jupyter:
jupyter labor
jupyter notebookthen navigate to the notebooks/ folder.
| Quantity | Unit in API | Example |
|---|---|---|
Coupon / rate_margin / WAC |
percent | 8.0 = 8% |
| CPR / CDR / PSA / ABS / SDA | percent | 100.0 PSA |
| SMM / MDR / severity | decimal fraction | 0.005 = 0.5% |
servicing_fee on Loan / scheduled runner |
percent | 0.25 = 25 bps |
svc_rate_performing/default/foreclosure in actual runner |
decimal fraction | 0.0025 = 25 bps |
- Age-indexed: index 0 = origination age (
Loanwrapper inputs). - Period-indexed: index 0 = as-of snapshot (
run_bma_actual_cashflowinputs).
The engine wrappers convert age-indexed curves into period-indexed windows automatically.
run_bma_scheduled_cashflowandrun_bma_actual_cashflowacceptcoupon_vectoras scalar or vector.- Scalar and constant short vectors are expanded to
remaining_term. - Non-constant short vectors are rejected with
ValueError. run_bma_actual_cashflowalso acceptsremaining_term + 1period-indexed coupon vectors and drops slot 0.
run_bma_scheduled_cashflow validates servicing_fee but does not currently alter scheduled principal/interest math with it. This is intentional: scheduled cashflow here is contractual amortization; servicing fee can be used downstream in custom trust/reporting logic.
scheduled_payments: B.1 factors and vectors.payment_models: B.2-B.4/C conversions, curves, historical recovery.cashflows: C.3 runners and leaf dataclasses (BMAScheduledCashflow,BMAActualCashflow,CashFlowPair).examples: reference scenarios and fixtures.
loan:Loan, wrapper runners, portfolio runner entry points.portfolio:PortfolioCashflow, lazy aggregation, waterfall, rewind/history.tape: strict tape parsing (TapeSchema,read_loan_tape).rate_index: floating-rate index vectors.cashflow_persistence: schema-aware Parquet read/write (write_cashflow,read_cashflows, etc.).
api: FastAPI routes and request/response models.orchestrator: run setup, grouping, mapping, assumptions, rates, and execution wiring.storage: local workspace and run artifact management.ui: React/Vite frontend for tape intake, run setup, and results views.
- Use
PortfolioCashflow(..., persistent_history=True, history_path=...)with a context manager or callclose(). PortfolioCashflow.load_rewind_components(path)loads persisted constituents asdict[cf_id -> cashflow]using the schema-aware reader.- Rewind history is bounded by
max_history_events(default5000); dropped-front count is tracked viahistory_dropped_events.
docs/architecture/overview.md- architecture and API contracts.docs/architecture/cashflow_aggregation_design.md- deep design notes forPortfolioCashflow.docs/BMA_FORMULAS.md- mathematical reference (BMA notation-focused).docs/notation_reference.md- notation and indexing glossary.
GPL-2.0-only. See LICENSE.