Skip to content

PrepaymentABS: SMM can exceed 1 before exhaustion, over-reporting prepayment vs outstanding balance #358

Open

Description

@yellowbean

Context — the PrepaymentABS feature from PR #322 (#323). The crash and negative-SMM failure modes at/after the implied balance-exhaustion month are fixed in src/Asset.hs (smm now returns 0 when 1 - r*(m-1) <= 0).

Residual issue — before exhaustion, SMM(m) = r / (1 - r*(m-1)) can exceed 1:

  • r = 6%, m = 17 → 1.5 (150% of the outstanding balance in one month)
  • r = 2%, m = 50 → 1.0
  • In general, the month just before exhaustion (m = floor(1/r) + 1) already yields SMM >= 1; any projection that runs to ~1/r months (e.g. a 30-year monthly schedule with r >= ~0.28%/month) enters this region.

Downstream impact — the rate is applied without clamping the amount:

  • src/AssetClass/Mortgage.hs:223 — newPrepay = mulBR (max 0 (begBal - newDefault)) ppyRate
  • same pattern at src/AssetClass/Mortgage.hs:53, :274; src/AssetClass/Loan.hs:51; src/AssetClass/Installment.hs:55

The balance is clamped (intBal = max 0 (begBal - newDefault - newPrepay)), but the recorded newPrepay row can exceed begBal - newDefault — i.e. reported prepayments exceed the outstanding balance, row identities (begin balance vs outflows) stop tying, and pool aggregations over-report prepayments.

Suggested fixes (decision needed)

  1. (recommended) Clamp the amount at the application sites: newPrepay = min (begBal - newDefault) (mulBR (max 0 (begBal - newDefault)) ppyRate) — preserves the modeled rate, keeps row identities consistent, and applies uniformly to every prepayment source.
  2. Clamp the rate to min 1 inside smm — simpler, but changes the ABS model semantics (the final months effectively prepay 100% of the remaining balance).
  3. Reject rate/horizon combinations that cross exhaustion with a descriptive Left — consistent with the other buildPrepayRates validations, but reduces usability.

Acceptance criteria

  • With PrepaymentABS 6%, month age 17: newPrepay <= begBal - newDefault, and the row satisfies begBal = endBal + principal + prepayment + default.
  • A unit test pinning the chosen semantics (Mortgage/Loan/Installment projection).

References — PR #322, review finding H1; issue #323 (feature); the exhaustion fix in src/Asset.hs.

Activity

  1. yellowbean commented on Oct 17, 2026

    @yellowbean
    CollaboratorAuthor

    The issue is , when default happens, the schedule prepayment shall be shrink as well as planned.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions