summaryrefslogtreecommitdiff
path: root/numpy/lib/financial.py
diff options
context:
space:
mode:
Diffstat (limited to 'numpy/lib/financial.py')
-rw-r--r--numpy/lib/financial.py482
1 files changed, 482 insertions, 0 deletions
diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py
new file mode 100644
index 000000000..f53a77631
--- /dev/null
+++ b/numpy/lib/financial.py
@@ -0,0 +1,482 @@
+# Some simple financial calculations
+# patterned after spreadsheet computations.
+
+# There is some complexity in each function
+# so that the functions behave like ufuncs with
+# broadcasting and being able to be called with scalars
+# or arrays (or other sequences).
+import numpy as np
+
+__all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate',
+ 'irr', 'npv', 'mirr']
+
+_when_to_num = {'end':0, 'begin':1,
+ 'e':0, 'b':1,
+ 0:0, 1:1,
+ 'beginning':1,
+ 'start':1,
+ 'finish':0}
+
+def _convert_when(when):
+ try:
+ return _when_to_num[when]
+ except KeyError:
+ return [_when_to_num[x] for x in when]
+
+
+def fv(rate, nper, pmt, pv, when='end'):
+ """
+ Compute the future value.
+
+ Parameters
+ ----------
+ rate : scalar or array_like of shape(M, )
+ Rate of interest as decimal (not per cent) per period
+ nper : scalar or array_like of shape(M, )
+ Number of compounding periods
+ pmt : scalar or array_like of shape(M, )
+ Payment
+ pv : scalar or array_like of shape(M, )
+ Present value
+ when : {{'begin', 1}, {'end', 0}}, {string, int}, optional
+ When payments are due ('begin' (1) or 'end' (0)).
+ Defaults to {'end', 0}.
+
+ Returns
+ -------
+ out : ndarray
+ Future values. If all input is scalar, returns a scalar float. If
+ any input is array_like, returns future values for each input element.
+ If multiple inputs are array_like, they all must have the same shape.
+
+ Notes
+ -----
+ The future value is computed by solving the equation::
+
+ fv +
+ pv*(1+rate)**nper +
+ pmt*(1 + rate*when)/rate*((1 + rate)**nper - 1) == 0
+
+ or, when ``rate == 0``::
+
+ fv + pv + pmt * nper == 0
+
+ Examples
+ --------
+ What is the future value after 10 years of saving $100 now, with
+ an additional monthly savings of $100. Assume the interest rate is
+ 5% (annually) compounded monthly?
+
+ >>> np.fv(0.05/12, 10*12, -100, -100)
+ 15692.928894335748
+
+ By convention, the negative sign represents cash flow out (i.e. money not
+ available today). Thus, saving $100 a month at 5% annual interest leads
+ to $15,692.93 available to spend in 10 years.
+
+ If any input is array_like, returns an array of equal shape. Let's
+ compare different interest rates from the example above.
+
+ >>> a = np.array((0.05, 0.06, 0.07))/12
+ >>> np.fv(a, 10*12, -100, -100)
+ array([ 15692.92889434, 16569.87435405, 17509.44688102])
+
+ """
+ when = _convert_when(when)
+ rate, nper, pmt, pv, when = map(np.asarray, [rate, nper, pmt, pv, when])
+ temp = (1+rate)**nper
+ miter = np.broadcast(rate, nper, pmt, pv, when)
+ zer = np.zeros(miter.shape)
+ fact = np.where(rate==zer, nper+zer, (1+rate*when)*(temp-1)/rate+zer)
+ return -(pv*temp + pmt*fact)
+
+def pmt(rate, nper, pv, fv=0, when='end'):
+ """
+ Compute the payment against loan principal plus interest.
+
+ Parameters
+ ----------
+ rate : array_like
+ Rate of interest (per period)
+ nper : array_like
+ Number of compounding periods
+ pv : array_like
+ Present value
+ fv : array_like
+ Future value
+ when : {{'begin', 1}, {'end', 0}}, {string, int}
+ When payments are due ('begin' (1) or 'end' (0))
+
+ Returns
+ -------
+ out : ndarray
+ Payment against loan plus interest. If all input is scalar, returns a
+ scalar float. If any input is array_like, returns payment for each
+ input element. If multiple inputs are array_like, they all must have
+ the same shape.
+
+ Notes
+ -----
+ The payment ``pmt`` is computed by solving the equation::
+
+ fv +
+ pv*(1 + rate)**nper +
+ pmt*(1 + rate*when)/rate*((1 + rate)**nper - 1) == 0
+
+ or, when ``rate == 0``::
+
+ fv + pv + pmt * nper == 0
+
+ Examples
+ --------
+ What would the monthly payment need to be to pay off a $200,000 loan in 15
+ years at an annual interest rate of 7.5%?
+
+ >>> np.pmt(0.075/12, 12*15, 200000)
+ -1854.0247200054619
+
+ In order to pay-off (i.e. have a future-value of 0) the $200,000 obtained
+ today, a monthly payment of $1,854.02 would be required.
+
+ """
+ when = _convert_when(when)
+ rate, nper, pv, fv, when = map(np.asarray, [rate, nper, pv, fv, when])
+ temp = (1+rate)**nper
+ miter = np.broadcast(rate, nper, pv, fv, when)
+ zer = np.zeros(miter.shape)
+ fact = np.where(rate==zer, nper+zer, (1+rate*when)*(temp-1)/rate+zer)
+ return -(fv + pv*temp) / fact
+
+def nper(rate, pmt, pv, fv=0, when='end'):
+ """
+ Compute the number of periods.
+
+ Parameters
+ ----------
+ rate : array_like
+ Rate of interest (per period)
+ pmt : array_like
+ Payment
+ pv : array_like
+ Present value
+ fv : array_like, optional
+ Future value
+ when : {{'begin', 1}, {'end', 0}}, {string, int}, optional
+ When payments are due ('begin' (1) or 'end' (0))
+
+ Notes
+ -----
+ The number of periods ``nper`` is computed by solving the equation::
+
+ fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate * ((1+rate)**nper - 1) == 0
+
+ or, when ``rate == 0``::
+
+ fv + pv + pmt * nper == 0
+
+ Examples
+ --------
+ If you only had $150 to spend as payment, how long would it take to pay-off
+ a loan of $8,000 at 7% annual interest?
+
+ >>> np.nper(0.07/12, -150, 8000)
+ 64.073348770661852
+
+ So, over 64 months would be required to pay off the loan.
+
+ The same analysis could be done with several different interest rates
+ and/or payments and/or total amounts to produce an entire table.
+
+ >>> np.nper(*(np.ogrid[0.06/12:0.071/12:0.01/12, -200:-99:100, 6000:7001:1000]))
+ array([[[ 32.58497782, 38.57048452],
+ [ 71.51317802, 86.37179563]],
+ <BLANKLINE>
+ [[ 33.07413144, 39.26244268],
+ [ 74.06368256, 90.22989997]]])
+
+ """
+ when = _convert_when(when)
+ rate, pmt, pv, fv, when = map(np.asarray, [rate, pmt, pv, fv, when])
+ try:
+ z = pmt*(1.0+rate*when)/rate
+ except ZeroDivisionError:
+ z = 0.0
+ A = -(fv + pv)/(pmt+0.0)
+ B = np.log((-fv+z) / (pv+z))/np.log(1.0+rate)
+ miter = np.broadcast(rate, pmt, pv, fv, when)
+ zer = np.zeros(miter.shape)
+ return np.where(rate==zer, A+zer, B+zer) + 0.0
+
+def ipmt(rate, per, nper, pv, fv=0.0, when='end'):
+ """
+ Not implemented. Compute the payment portion for loan interest.
+
+ Parameters
+ ----------
+ rate : scalar or array_like of shape(M, )
+ Rate of interest as decimal (not per cent) per period
+ per : scalar or array_like of shape(M, )
+ Interest paid against the loan changes during the life or the loan.
+ The `per` is the payment period to calculate the interest amount.
+ nper : scalar or array_like of shape(M, )
+ Number of compounding periods
+ pv : scalar or array_like of shape(M, )
+ Present value
+ fv : scalar or array_like of shape(M, ), optional
+ Future value
+ when : {{'begin', 1}, {'end', 0}}, {string, int}, optional
+ When payments are due ('begin' (1) or 'end' (0)).
+ Defaults to {'end', 0}.
+
+ Returns
+ -------
+ out : ndarray
+ Interest portion of payment. If all input is scalar, returns a scalar
+ float. If any input is array_like, returns interest payment for each
+ input element. If multiple inputs are array_like, they all must have
+ the same shape.
+
+ See Also
+ --------
+ ppmt, pmt, pv
+
+ Notes
+ -----
+ The total payment is made up of payment against principal plus interest.
+
+ ``pmt = ppmt + ipmt``
+
+ """
+ total = pmt(rate, nper, pv, fv, when)
+ # Now, compute the nth step in the amortization
+ raise NotImplementedError
+
+def ppmt(rate, per, nper, pv, fv=0.0, when='end'):
+ """
+ Not implemented. Compute the payment against loan principal.
+
+ Parameters
+ ----------
+ rate : array_like
+ Rate of interest (per period)
+ per : array_like, int
+ Amount paid against the loan changes. The `per` is the period of
+ interest.
+ nper : array_like
+ Number of compounding periods
+ pv : array_like
+ Present value
+ fv : array_like, optional
+ Future value
+ when : {{'begin', 1}, {'end', 0}}, {string, int}
+ When payments are due ('begin' (1) or 'end' (0))
+
+ See Also
+ --------
+ pmt, pv, ipmt
+
+ """
+ total = pmt(rate, nper, pv, fv, when)
+ return total - ipmt(rate, per, nper, pv, fv, when)
+
+def pv(rate, nper, pmt, fv=0.0, when='end'):
+ """
+ Compute the present value.
+
+ Parameters
+ ----------
+ rate : array_like
+ Rate of interest (per period)
+ nper : array_like
+ Number of compounding periods
+ pmt : array_like
+ Payment
+ fv : array_like, optional
+ Future value
+ when : {{'begin', 1}, {'end', 0}}, {string, int}, optional
+ When payments are due ('begin' (1) or 'end' (0))
+
+ Returns
+ -------
+ out : ndarray, float
+ Present value of a series of payments or investments.
+
+ Notes
+ -----
+ The present value ``pv`` is computed by solving the equation::
+
+ fv +
+ pv*(1 + rate)**nper +
+ pmt*(1 + rate*when)/rate*((1 + rate)**nper - 1) = 0
+
+ or, when ``rate = 0``::
+
+ fv + pv + pmt * nper = 0
+
+ """
+ when = _convert_when(when)
+ rate, nper, pmt, fv, when = map(np.asarray, [rate, nper, pmt, fv, when])
+ temp = (1+rate)**nper
+ miter = np.broadcast(rate, nper, pmt, fv, when)
+ zer = np.zeros(miter.shape)
+ fact = np.where(rate == zer, nper+zer, (1+rate*when)*(temp-1)/rate+zer)
+ return -(fv + pmt*fact)/temp
+
+# Computed with Sage
+# (y + (r + 1)^n*x + p*((r + 1)^n - 1)*(r*w + 1)/r)/(n*(r + 1)^(n - 1)*x - p*((r + 1)^n - 1)*(r*w + 1)/r^2 + n*p*(r + 1)^(n - 1)*(r*w + 1)/r + p*((r + 1)^n - 1)*w/r)
+
+def _g_div_gp(r, n, p, x, y, w):
+ t1 = (r+1)**n
+ t2 = (r+1)**(n-1)
+ return (y + t1*x + p*(t1 - 1)*(r*w + 1)/r)/(n*t2*x - p*(t1 - 1)*(r*w + 1)/(r**2) + n*p*t2*(r*w + 1)/r + p*(t1 - 1)*w/r)
+
+# Use Newton's iteration until the change is less than 1e-6
+# for all values or a maximum of 100 iterations is reached.
+# Newton's rule is
+# r_{n+1} = r_{n} - g(r_n)/g'(r_n)
+# where
+# g(r) is the formula
+# g'(r) is the derivative with respect to r.
+def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100):
+ """
+ Compute the rate of interest per period.
+
+ Parameters
+ ----------
+ nper : array_like
+ Number of compounding periods
+ pmt : array_like
+ Payment
+ pv : array_like
+ Present value
+ fv : array_like
+ Future value
+ when : {{'begin', 1}, {'end', 0}}, {string, int}, optional
+ When payments are due ('begin' (1) or 'end' (0))
+ guess : float, optional
+ Starting guess for solving the rate of interest
+ tol : float, optional
+ Required tolerance for the solution
+ maxiter : int, optional
+ Maximum iterations in finding the solution
+
+ Notes
+ -----
+ The rate of interest ``rate`` is computed by solving the equation::
+
+ fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate * ((1+rate)**nper - 1) = 0
+
+ or, if ``rate = 0``::
+
+ fv + pv + pmt * nper = 0
+
+ """
+ when = _convert_when(when)
+ nper, pmt, pv, fv, when = map(np.asarray, [nper, pmt, pv, fv, when])
+ rn = guess
+ iter = 0
+ close = False
+ while (iter < maxiter) and not close:
+ rnp1 = rn - _g_div_gp(rn, nper, pmt, pv, fv, when)
+ diff = abs(rnp1-rn)
+ close = np.all(diff<tol)
+ iter += 1
+ rn = rnp1
+ if not close:
+ # Return nan's in array of the same shape as rn
+ return np.nan + rn
+ else:
+ return rn
+
+def irr(values):
+ """
+ Return the Internal Rate of Return (IRR).
+
+ This is the rate of return that gives a net present value of 0.0.
+
+ Parameters
+ ----------
+ values : array_like, shape(N,)
+ Input cash flows per time period. At least the first value would be
+ negative to represent the investment in the project.
+
+ Returns
+ -------
+ out : float
+ Internal Rate of Return for periodic input values.
+
+ Examples
+ --------
+ >>> np.irr([-100, 39, 59, 55, 20])
+ 0.2809484211599611
+
+ """
+ res = np.roots(values[::-1])
+ # Find the root(s) between 0 and 1
+ mask = (res.imag == 0) & (res.real > 0) & (res.real <= 1)
+ res = res[mask].real
+ if res.size == 0:
+ return np.nan
+ rate = 1.0/res - 1
+ if rate.size == 1:
+ rate = rate.item()
+ return rate
+
+def npv(rate, values):
+ """
+ Returns the NPV (Net Present Value) of a cash flow series.
+
+ Parameters
+ ----------
+ rate : scalar
+ The discount rate.
+ values : array_like, shape(M, )
+ The values of the time series of cash flows. Must be the same
+ increment as the `rate`.
+
+ Returns
+ -------
+ out : float
+ The NPV of the input cash flow series `values` at the discount `rate`.
+
+ Notes
+ -----
+ Returns the result of:
+
+ .. math :: \\sum_{t=1}^M{\\frac{values_t}{(1+rate)^{t}}}
+
+ """
+ values = np.asarray(values)
+ return (values / (1+rate)**np.arange(1,len(values)+1)).sum(axis=0)
+
+def mirr(values, finance_rate, reinvest_rate):
+ """
+ Modified internal rate of return.
+
+ Parameters
+ ----------
+ values : array_like
+ Cash flows (must contain at least one positive and one negative value)
+ or nan is returned.
+ finance_rate : scalar
+ Interest rate paid on the cash flows
+ reinvest_rate : scalar
+ Interest rate received on the cash flows upon reinvestment
+
+ Returns
+ -------
+ out : float
+ Modified internal rate of return
+
+ """
+
+ values = np.asarray(values)
+ pos = values > 0
+ neg = values < 0
+ if not (pos.size > 0 and neg.size > 0):
+ return np.nan
+
+ n = pos.size + neg.size
+ numer = -npv(reinvest_rate, values[pos])*((1+reinvest_rate)**n)
+ denom = npv(finance_rate, values[neg])*(1+finance_rate)
+ return (numer / denom)**(1.0/(n-1)) - 1