Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions benchmarks/benchmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,27 @@ def time_for_loop(self, n_cashflows, cashflow_lengths, rates_lengths):

def time_broadcast(self, n_cashflows, cashflow_lengths, rates_lengths):
npf.npv(self.rates, self.cashflows)


class MortgageScenarios:
"""Compare per-loan calls with existing pmt broadcasting."""

param_names = ["loans", "scenarios"]
params = [(100, 1000, 10000), (1, 5)]

def setup(self, loans, scenarios):
self.principal = np.linspace(75000, 450000, loans)
self.periods = np.resize([120, 180, 240, 300, 360], loans)
annual_rates = np.linspace(0.03, 0.065, loans)
shifts = np.linspace(-0.01, 0.01, scenarios) if scenarios > 1 else [0]
self.rates = (annual_rates[:, None] + np.asarray(shifts)[None, :]) / 12

def time_individual_calls(self, loans, scenarios):
return np.array([
[npf.pmt(self.rates[i, j], self.periods[i], self.principal[i])
for j in range(scenarios)]
for i in range(loans)
])

def time_broadcast(self, loans, scenarios):
return npf.pmt(self.rates, self.periods[:, None], self.principal[:, None])
1 change: 1 addition & 0 deletions doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ alias is `npf`. For example,
:maxdepth: 4

api
portfolio
dev/index


Expand Down
28 changes: 28 additions & 0 deletions doc/source/portfolio.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Loan portfolios
===============

Multiple loans and rate scenarios
---------------------------------

``pmt`` already broadcasts its inputs. Put loans on rows and rate scenarios on
columns. Rates below are nominal annual rates, compounded monthly; fees are
excluded. Payments are returned as negative cash flows for positive loans.

.. code-block:: python

import numpy as np
import numpy_financial as npf

principal = np.array([250000, 15000, 20000])
months = np.array([300, 48, 48])
annual_rate = np.array([0.05, 0.08, 0.07])
rate_shifts = np.array([-0.01, 0, 0.01])
monthly_rates = (annual_rate[:, None] + rate_shifts[None, :]) / 12
payments = npf.pmt(monthly_rates, months[:, None], principal[:, None])
# payments.shape == (3, 3): three loans, three scenarios

For one rate per loan, pass one-dimensional arrays of the same length. Do not
add a scenario axis unless the cross-product of loans and scenarios is wanted.
For very large portfolios, process slices of loans to limit temporary memory.
Keep full precision internally and round only where a contractual schedule
requires it.