123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830 |
- """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).
- Functions support the :class:`decimal.Decimal` type unless
- otherwise stated.
- """
- from __future__ import division, absolute_import, print_function
- from decimal import Decimal
- import functools
- import numpy as np
- from numpy.core import overrides
- array_function_dispatch = functools.partial(
- overrides.array_function_dispatch, module='numpy')
- __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):
- #Test to see if when has already been converted to ndarray
- #This will happen if one function calls another, for example ppmt
- if isinstance(when, np.ndarray):
- return when
- try:
- return _when_to_num[when]
- except (KeyError, TypeError):
- return [_when_to_num[x] for x in when]
- def _fv_dispatcher(rate, nper, pmt, pv, when=None):
- return (rate, nper, pmt, pv)
- @array_function_dispatch(_fv_dispatcher)
- def fv(rate, nper, pmt, pv, when='end'):
- """
- Compute the future value.
- Given:
- * a present value, `pv`
- * an interest `rate` compounded once per period, of which
- there are
- * `nper` total
- * a (fixed) payment, `pmt`, paid either
- * at the beginning (`when` = {'begin', 1}) or the end
- (`when` = {'end', 0}) of each period
- Return:
- the value at the end of the `nper` periods
- 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
- References
- ----------
- .. [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May).
- Open Document Format for Office Applications (OpenDocument)v1.2,
- Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version,
- Pre-Draft 12. Organization for the Advancement of Structured Information
- Standards (OASIS). Billerica, MA, USA. [ODT Document].
- Available:
- http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula
- OpenDocument-formula-20090508.odt
- 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
- fact = np.where(rate == 0, nper,
- (1 + rate*when)*(temp - 1)/rate)
- return -(pv*temp + pmt*fact)
- def _pmt_dispatcher(rate, nper, pv, fv=None, when=None):
- return (rate, nper, pv, fv)
- @array_function_dispatch(_pmt_dispatcher)
- def pmt(rate, nper, pv, fv=0, when='end'):
- """
- Compute the payment against loan principal plus interest.
- Given:
- * a present value, `pv` (e.g., an amount borrowed)
- * a future value, `fv` (e.g., 0)
- * an interest `rate` compounded once per period, of which
- there are
- * `nper` total
- * and (optional) specification of whether payment is made
- at the beginning (`when` = {'begin', 1}) or the end
- (`when` = {'end', 0}) of each period
- Return:
- the (fixed) periodic payment.
- Parameters
- ----------
- rate : array_like
- Rate of interest (per period)
- nper : array_like
- Number of compounding periods
- pv : array_like
- Present value
- fv : array_like, optional
- Future value (default = 0)
- 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 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
- for ``pmt``.
- Note that computing a monthly mortgage payment is only
- one use for this function. For example, pmt returns the
- periodic deposit one must make to achieve a specified
- future balance given an initial deposit, a fixed,
- periodically compounded interest rate, and the total
- number of periods.
- References
- ----------
- .. [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May).
- Open Document Format for Office Applications (OpenDocument)v1.2,
- Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version,
- Pre-Draft 12. Organization for the Advancement of Structured Information
- Standards (OASIS). Billerica, MA, USA. [ODT Document].
- Available:
- http://www.oasis-open.org/committees/documents.php
- ?wg_abbrev=office-formulaOpenDocument-formula-20090508.odt
- Examples
- --------
- What is the monthly payment needed 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. Note that this
- example illustrates usage of `fv` having a default value of 0.
- """
- when = _convert_when(when)
- (rate, nper, pv, fv, when) = map(np.array, [rate, nper, pv, fv, when])
- temp = (1 + rate)**nper
- mask = (rate == 0)
- masked_rate = np.where(mask, 1, rate)
- fact = np.where(mask != 0, nper,
- (1 + masked_rate*when)*(temp - 1)/masked_rate)
- return -(fv + pv*temp) / fact
- def _nper_dispatcher(rate, pmt, pv, fv=None, when=None):
- return (rate, pmt, pv, fv)
- @array_function_dispatch(_nper_dispatcher)
- def nper(rate, pmt, pv, fv=0, when='end'):
- """
- Compute the number of periodic payments.
- :class:`decimal.Decimal` type is not supported.
- 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
- but if ``rate = 0`` then::
- fv + pv + pmt*nper = 0
- Examples
- --------
- If you only had $150/month to pay towards the loan, how long would it take
- to pay-off a loan of $8,000 at 7% annual interest?
- >>> print(round(np.nper(0.07/12, -150, 8000), 5))
- 64.07335
- 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.07/12: 0.08/12: 0.01/12,
- ... -150 : -99 : 50 ,
- ... 8000 : 9001 : 1000]))
- array([[[ 64.07334877, 74.06368256],
- [ 108.07548412, 127.99022654]],
- [[ 66.12443902, 76.87897353],
- [ 114.70165583, 137.90124779]]])
- """
- when = _convert_when(when)
- (rate, pmt, pv, fv, when) = map(np.asarray, [rate, pmt, pv, fv, when])
- use_zero_rate = False
- with np.errstate(divide="raise"):
- try:
- z = pmt*(1+rate*when)/rate
- except FloatingPointError:
- use_zero_rate = True
- if use_zero_rate:
- return (-fv + pv) / pmt
- else:
- A = -(fv + pv)/(pmt+0)
- B = np.log((-fv+z) / (pv+z))/np.log(1+rate)
- return np.where(rate == 0, A, B)
- def _ipmt_dispatcher(rate, per, nper, pv, fv=None, when=None):
- return (rate, per, nper, pv, fv)
- @array_function_dispatch(_ipmt_dispatcher)
- def ipmt(rate, per, nper, pv, fv=0, when='end'):
- """
- Compute the interest portion of a payment.
- 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``
- Examples
- --------
- What is the amortization schedule for a 1 year loan of $2500 at
- 8.24% interest per year compounded monthly?
- >>> principal = 2500.00
- The 'per' variable represents the periods of the loan. Remember that
- financial equations start the period count at 1!
- >>> per = np.arange(1*12) + 1
- >>> ipmt = np.ipmt(0.0824/12, per, 1*12, principal)
- >>> ppmt = np.ppmt(0.0824/12, per, 1*12, principal)
- Each element of the sum of the 'ipmt' and 'ppmt' arrays should equal
- 'pmt'.
- >>> pmt = np.pmt(0.0824/12, 1*12, principal)
- >>> np.allclose(ipmt + ppmt, pmt)
- True
- >>> fmt = '{0:2d} {1:8.2f} {2:8.2f} {3:8.2f}'
- >>> for payment in per:
- ... index = payment - 1
- ... principal = principal + ppmt[index]
- ... print(fmt.format(payment, ppmt[index], ipmt[index], principal))
- 1 -200.58 -17.17 2299.42
- 2 -201.96 -15.79 2097.46
- 3 -203.35 -14.40 1894.11
- 4 -204.74 -13.01 1689.37
- 5 -206.15 -11.60 1483.22
- 6 -207.56 -10.18 1275.66
- 7 -208.99 -8.76 1066.67
- 8 -210.42 -7.32 856.25
- 9 -211.87 -5.88 644.38
- 10 -213.32 -4.42 431.05
- 11 -214.79 -2.96 216.26
- 12 -216.26 -1.49 -0.00
- >>> interestpd = np.sum(ipmt)
- >>> np.round(interestpd, 2)
- -112.98
- """
- when = _convert_when(when)
- rate, per, nper, pv, fv, when = np.broadcast_arrays(rate, per, nper,
- pv, fv, when)
- total_pmt = pmt(rate, nper, pv, fv, when)
- ipmt = _rbl(rate, per, total_pmt, pv, when)*rate
- try:
- ipmt = np.where(when == 1, ipmt/(1 + rate), ipmt)
- ipmt = np.where(np.logical_and(when == 1, per == 1), 0, ipmt)
- except IndexError:
- pass
- return ipmt
- def _rbl(rate, per, pmt, pv, when):
- """
- This function is here to simply have a different name for the 'fv'
- function to not interfere with the 'fv' keyword argument within the 'ipmt'
- function. It is the 'remaining balance on loan' which might be useful as
- it's own function, but is easily calculated with the 'fv' function.
- """
- return fv(rate, (per - 1), pmt, pv, when)
- def _ppmt_dispatcher(rate, per, nper, pv, fv=None, when=None):
- return (rate, per, nper, pv, fv)
- @array_function_dispatch(_ppmt_dispatcher)
- def ppmt(rate, per, nper, pv, fv=0, when='end'):
- """
- 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_dispatcher(rate, nper, pmt, fv=None, when=None):
- return (rate, nper, nper, pv, fv)
- @array_function_dispatch(_pv_dispatcher)
- def pv(rate, nper, pmt, fv=0, when='end'):
- """
- Compute the present value.
- Given:
- * a future value, `fv`
- * an interest `rate` compounded once per period, of which
- there are
- * `nper` total
- * a (fixed) payment, `pmt`, paid either
- * at the beginning (`when` = {'begin', 1}) or the end
- (`when` = {'end', 0}) of each period
- Return:
- the value now
- 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 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
- for `pv`, which is then returned.
- References
- ----------
- .. [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May).
- Open Document Format for Office Applications (OpenDocument)v1.2,
- Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version,
- Pre-Draft 12. Organization for the Advancement of Structured Information
- Standards (OASIS). Billerica, MA, USA. [ODT Document].
- Available:
- http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula
- OpenDocument-formula-20090508.odt
- Examples
- --------
- What is the present value (e.g., the initial investment)
- of an investment that needs to total $15692.93
- after 10 years of saving $100 every month? Assume the
- interest rate is 5% (annually) compounded monthly.
- >>> np.pv(0.05/12, 10*12, -100, 15692.93)
- -100.00067131625819
- By convention, the negative sign represents cash flow out
- (i.e., money not available today). Thus, to end up with
- $15,692.93 in 10 years saving $100 a month at 5% annual
- interest, one's initial deposit should also be $100.
- If any input is array_like, ``pv`` returns an array of equal shape.
- Let's compare different interest rates in the example above:
- >>> a = np.array((0.05, 0.04, 0.03))/12
- >>> np.pv(a, 10*12, -100, 15692.93)
- array([ -100.00067132, -649.26771385, -1273.78633713])
- So, to end up with the same $15692.93 under the same $100 per month
- "savings plan," for annual interest rates of 4% and 3%, one would
- need initial investments of $649.27 and $1273.79, respectively.
- """
- when = _convert_when(when)
- (rate, nper, pmt, fv, when) = map(np.asarray, [rate, nper, pmt, fv, when])
- temp = (1+rate)**nper
- fact = np.where(rate == 0, nper, (1+rate*when)*(temp-1)/rate)
- 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))
- def _rate_dispatcher(nper, pmt, pv, fv, when=None, guess=None, tol=None,
- maxiter=None):
- return (nper, pmt, pv, fv)
- # 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.
- @array_function_dispatch(_rate_dispatcher)
- def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, 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 : Number, optional
- Starting guess for solving the rate of interest, default 0.1
- tol : Number, optional
- Required tolerance for the solution, default 1e-6
- maxiter : int, optional
- Maximum iterations in finding the solution
- Notes
- -----
- The rate of interest is computed by iteratively solving the
- (non-linear) equation::
- fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate * ((1+rate)**nper - 1) = 0
- for ``rate``.
- References
- ----------
- Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May). Open Document
- Format for Office Applications (OpenDocument)v1.2, Part 2: Recalculated
- Formula (OpenFormula) Format - Annotated Version, Pre-Draft 12.
- Organization for the Advancement of Structured Information Standards
- (OASIS). Billerica, MA, USA. [ODT Document]. Available:
- http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula
- OpenDocument-formula-20090508.odt
- """
- when = _convert_when(when)
- default_type = Decimal if isinstance(pmt, Decimal) else float
- # Handle casting defaults to Decimal if/when pmt is a Decimal and
- # guess and/or tol are not given default values
- if guess is None:
- guess = default_type('0.1')
- if tol is None:
- tol = default_type('1e-6')
- (nper, pmt, pv, fv, when) = map(np.asarray, [nper, pmt, pv, fv, when])
- rn = guess
- iterator = 0
- close = False
- while (iterator < maxiter) and not close:
- rnp1 = rn - _g_div_gp(rn, nper, pmt, pv, fv, when)
- diff = abs(rnp1-rn)
- close = np.all(diff < tol)
- iterator += 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_dispatcher(values):
- return (values,)
- @array_function_dispatch(_irr_dispatcher)
- def irr(values):
- """
- Return the Internal Rate of Return (IRR).
- This is the "average" periodically compounded rate of return
- that gives a net present value of 0.0; for a more complete explanation,
- see Notes below.
- :class:`decimal.Decimal` type is not supported.
- Parameters
- ----------
- values : array_like, shape(N,)
- Input cash flows per time period. By convention, net "deposits"
- are negative and net "withdrawals" are positive. Thus, for
- example, at least the first element of `values`, which represents
- the initial investment, will typically be negative.
- Returns
- -------
- out : float
- Internal Rate of Return for periodic input values.
- Notes
- -----
- The IRR is perhaps best understood through an example (illustrated
- using np.irr in the Examples section below). Suppose one invests 100
- units and then makes the following withdrawals at regular (fixed)
- intervals: 39, 59, 55, 20. Assuming the ending value is 0, one's 100
- unit investment yields 173 units; however, due to the combination of
- compounding and the periodic withdrawals, the "average" rate of return
- is neither simply 0.73/4 nor (1.73)^0.25-1. Rather, it is the solution
- (for :math:`r`) of the equation:
- .. math:: -100 + \\frac{39}{1+r} + \\frac{59}{(1+r)^2}
- + \\frac{55}{(1+r)^3} + \\frac{20}{(1+r)^4} = 0
- In general, for `values` :math:`= [v_0, v_1, ... v_M]`,
- irr is the solution of the equation: [G]_
- .. math:: \\sum_{t=0}^M{\\frac{v_t}{(1+irr)^{t}}} = 0
- References
- ----------
- .. [G] L. J. Gitman, "Principles of Managerial Finance, Brief," 3rd ed.,
- Addison-Wesley, 2003, pg. 348.
- Examples
- --------
- >>> round(irr([-100, 39, 59, 55, 20]), 5)
- 0.28095
- >>> round(irr([-100, 0, 0, 74]), 5)
- -0.0955
- >>> round(irr([-100, 100, 0, -7]), 5)
- -0.0833
- >>> round(irr([-100, 100, 0, 7]), 5)
- 0.06206
- >>> round(irr([-5, 10.5, 1, -8, 1]), 5)
- 0.0886
- (Compare with the Example given for numpy.lib.financial.npv)
- """
- # `np.roots` call is why this function does not support Decimal type.
- #
- # Ultimately Decimal support needs to be added to np.roots, which has
- # greater implications on the entire linear algebra module and how it does
- # eigenvalue computations.
- res = np.roots(values[::-1])
- mask = (res.imag == 0) & (res.real > 0)
- if not mask.any():
- return np.nan
- res = res[mask].real
- # NPV(rate) = 0 can have more than one solution so we return
- # only the solution closest to zero.
- rate = 1/res - 1
- rate = rate.item(np.argmin(np.abs(rate)))
- return rate
- def _npv_dispatcher(rate, values):
- return (values,)
- @array_function_dispatch(_npv_dispatcher)
- 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. The (fixed) time
- interval between cash flow "events" must be the same as that for
- which `rate` is given (i.e., if `rate` is per year, then precisely
- a year is understood to elapse between each cash flow event). By
- convention, investments or "deposits" are negative, income or
- "withdrawals" are positive; `values` must begin with the initial
- investment, thus `values[0]` will typically be negative.
- Returns
- -------
- out : float
- The NPV of the input cash flow series `values` at the discount
- `rate`.
- Notes
- -----
- Returns the result of: [G]_
- .. math :: \\sum_{t=0}^{M-1}{\\frac{values_t}{(1+rate)^{t}}}
- References
- ----------
- .. [G] L. J. Gitman, "Principles of Managerial Finance, Brief," 3rd ed.,
- Addison-Wesley, 2003, pg. 346.
- Examples
- --------
- >>> np.npv(0.281,[-100, 39, 59, 55, 20])
- -0.0084785916384548798
- (Compare with the Example given for numpy.lib.financial.irr)
- """
- values = np.asarray(values)
- return (values / (1+rate)**np.arange(0, len(values))).sum(axis=0)
- def _mirr_dispatcher(values, finance_rate, reinvest_rate):
- return (values,)
- @array_function_dispatch(_mirr_dispatcher)
- 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. The first value is considered a sunk
- cost at time zero.
- 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)
- n = values.size
- # Without this explicit cast the 1/(n - 1) computation below
- # becomes a float, which causes TypeError when using Decimal
- # values.
- if isinstance(finance_rate, Decimal):
- n = Decimal(n)
- pos = values > 0
- neg = values < 0
- if not (pos.any() and neg.any()):
- return np.nan
- numer = np.abs(npv(reinvest_rate, values*pos))
- denom = np.abs(npv(finance_rate, values*neg))
- return (numer/denom)**(1/(n - 1))*(1 + reinvest_rate) - 1
|