Skip to content

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PoolFlow

I wanted to understand mortgage-backed securities from the ground up, so I built the machinery myself: a small engine that takes a pool of fixed-rate mortgages and projects every dollar of cash flow, month by month, until the last loan pays off. Along the way it handles the things that make agency pools interesting: borrowers prepaying, borrowers going delinquent, and the servicing and guarantee-fee strips that get carved out of interest before investors see it.

The rule I set for myself: every formula gets checked against something independent of my own code. Amortization against the closed-form annuity formula. Prepayment against hand-computed fixtures from published conventions. Cohort delinquency against analytic matrix powers. Weighted-average life against a second implementation written a different way. If two roads do not arrive at the same number, something is wrong.

The four modules

Loan-level amortization (poolflow/amortization.py). The closed-form level payment, computed with expm1/log1p so it stays stable at rates arbitrarily close to zero, plus an integer-cent schedule generator that bills the way a servicer does: rounded payment, rounded monthly interest accrual, and a final payment adjusted so the loan retires exactly. The cent schedule conserves principal exactly, by construction and by test.

Prepayment conventions (poolflow/prepayment.py). SMM and CPR conversions and the standard PSA benchmark: CPR ramping 0.2% per month of loan age to 6% at month 30, then flat, with a linear multiplier. Formulas follow the open literature (Fabozzi's mortgage pass-through chapter is the reference I worked from); fixtures/psa_fixtures.json holds 19 hand-computed fixture values the tests must reproduce.

Pool projection (poolflow/pool.py). A seeded 5,000-loan synthetic pool projected to full runoff, vectorized in numpy: scheduled principal, prepayments at the PSA SMM, gross interest, servicing and guarantee-fee strips, and net investor interest. Every loan carries a cash-conservation invariant: total principal paid must equal the starting balance to within half a cent. Weighted-average life comes out of the same flows.

Delinquency roll rates (poolflow/rollrates.py). A five-state monthly Markov chain (current, 30, 60, 90+ days, default) with a seeded, realistically shaped transition matrix. Cohort evolution runs three ways: iterative propagation, analytic matrix powers, and Monte Carlo over individual loans. Lifetime absorption uses the fundamental-matrix formula N = (I - Q)^-1 from standard Markov-chain theory.

Validation results

Full details and reproduce commands live in RESULTS.md; committed raw output in results/. Highlights from the seeded 5,000-loan pool (about $1.37B balance, Apple Silicon, single thread):

  • 0 cash-conservation violations across all 5,000 loans; worst per-loan error was under a billionth of a dollar
  • WAL at 100 PSA: 6.39 years, matching the independent oracle to 2.7e-15 years
  • iterative cohort evolution matches matrix powers to 1.8e-15; Monte Carlo with 200k loans lands within 5.6e-4
  • roughly 239 million loan-months per second of projection throughput

Setup

python3 -m venv .venv && .venv/bin/pip install -U pip numpy pytest pytest-cov
.venv/bin/python -m pytest --cov=poolflow
.venv/bin/python scripts/validate.py
.venv/bin/python scripts/bench.py

What's missing

  • All loan data is synthetic (seeded RNG). No real loan tapes, so the pool's rate/term/balance distributions are plausible but invented.
  • Prepayment is the deterministic PSA convention only. There is no interest-rate model, no refinancing incentive, no burnout, no OAS.
  • The roll-rate chain has default as its only absorbing state, so the lifetime default rate is 1.0 by construction; the meaningful outputs are the finite-horizon default shares and the speed of convergence, both of which are validated. A payoff state would fix this but is out of scope here.
  • Delinquency and the cash-flow engine are validated side by side but not coupled: delinquent loans do not stop paying in the pool projection.
  • The cent-level schedule shows real final-payment drift at extreme rates: at 18% over 360 months the final payment absorbs about $50 of compounded sub-cent rounding. At realistic rates it stays within a few dollars, and principal conservation is exact everywhere.
  • No tranching, no CMO structuring, no servicer advance modeling.

About

PoolFlow: mortgage-pool cash-flow engine with SMM/CPR/PSA prepayment conventions and 5-state delinquency roll rates; 0 cash-conservation violations across $1.37B principal, WAL matching an independent oracle to 2.7e-15 years. 167 tests, 100% coverage.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages