Chris@87: """Some simple financial calculations Chris@87: Chris@87: patterned after spreadsheet computations. Chris@87: Chris@87: There is some complexity in each function Chris@87: so that the functions behave like ufuncs with Chris@87: broadcasting and being able to be called with scalars Chris@87: or arrays (or other sequences). Chris@87: Chris@87: """ Chris@87: from __future__ import division, absolute_import, print_function Chris@87: Chris@87: import numpy as np Chris@87: Chris@87: __all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate', Chris@87: 'irr', 'npv', 'mirr'] Chris@87: Chris@87: _when_to_num = {'end':0, 'begin':1, Chris@87: 'e':0, 'b':1, Chris@87: 0:0, 1:1, Chris@87: 'beginning':1, Chris@87: 'start':1, Chris@87: 'finish':0} Chris@87: Chris@87: def _convert_when(when): Chris@87: #Test to see if when has already been converted to ndarray Chris@87: #This will happen if one function calls another, for example ppmt Chris@87: if isinstance(when, np.ndarray): Chris@87: return when Chris@87: try: Chris@87: return _when_to_num[when] Chris@87: except (KeyError, TypeError): Chris@87: return [_when_to_num[x] for x in when] Chris@87: Chris@87: Chris@87: def fv(rate, nper, pmt, pv, when='end'): Chris@87: """ Chris@87: Compute the future value. Chris@87: Chris@87: Given: Chris@87: * a present value, `pv` Chris@87: * an interest `rate` compounded once per period, of which Chris@87: there are Chris@87: * `nper` total Chris@87: * a (fixed) payment, `pmt`, paid either Chris@87: * at the beginning (`when` = {'begin', 1}) or the end Chris@87: (`when` = {'end', 0}) of each period Chris@87: Chris@87: Return: Chris@87: the value at the end of the `nper` periods Chris@87: Chris@87: Parameters Chris@87: ---------- Chris@87: rate : scalar or array_like of shape(M, ) Chris@87: Rate of interest as decimal (not per cent) per period Chris@87: nper : scalar or array_like of shape(M, ) Chris@87: Number of compounding periods Chris@87: pmt : scalar or array_like of shape(M, ) Chris@87: Payment Chris@87: pv : scalar or array_like of shape(M, ) Chris@87: Present value Chris@87: when : {{'begin', 1}, {'end', 0}}, {string, int}, optional Chris@87: When payments are due ('begin' (1) or 'end' (0)). Chris@87: Defaults to {'end', 0}. Chris@87: Chris@87: Returns Chris@87: ------- Chris@87: out : ndarray Chris@87: Future values. If all input is scalar, returns a scalar float. If Chris@87: any input is array_like, returns future values for each input element. Chris@87: If multiple inputs are array_like, they all must have the same shape. Chris@87: Chris@87: Notes Chris@87: ----- Chris@87: The future value is computed by solving the equation:: Chris@87: Chris@87: fv + Chris@87: pv*(1+rate)**nper + Chris@87: pmt*(1 + rate*when)/rate*((1 + rate)**nper - 1) == 0 Chris@87: Chris@87: or, when ``rate == 0``:: Chris@87: Chris@87: fv + pv + pmt * nper == 0 Chris@87: Chris@87: References Chris@87: ---------- Chris@87: .. [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May). Chris@87: Open Document Format for Office Applications (OpenDocument)v1.2, Chris@87: Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version, Chris@87: Pre-Draft 12. Organization for the Advancement of Structured Information Chris@87: Standards (OASIS). Billerica, MA, USA. [ODT Document]. Chris@87: Available: Chris@87: http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula Chris@87: OpenDocument-formula-20090508.odt Chris@87: Chris@87: Examples Chris@87: -------- Chris@87: What is the future value after 10 years of saving $100 now, with Chris@87: an additional monthly savings of $100. Assume the interest rate is Chris@87: 5% (annually) compounded monthly? Chris@87: Chris@87: >>> np.fv(0.05/12, 10*12, -100, -100) Chris@87: 15692.928894335748 Chris@87: Chris@87: By convention, the negative sign represents cash flow out (i.e. money not Chris@87: available today). Thus, saving $100 a month at 5% annual interest leads Chris@87: to $15,692.93 available to spend in 10 years. Chris@87: Chris@87: If any input is array_like, returns an array of equal shape. Let's Chris@87: compare different interest rates from the example above. Chris@87: Chris@87: >>> a = np.array((0.05, 0.06, 0.07))/12 Chris@87: >>> np.fv(a, 10*12, -100, -100) Chris@87: array([ 15692.92889434, 16569.87435405, 17509.44688102]) Chris@87: Chris@87: """ Chris@87: when = _convert_when(when) Chris@87: (rate, nper, pmt, pv, when) = map(np.asarray, [rate, nper, pmt, pv, when]) Chris@87: temp = (1+rate)**nper Chris@87: miter = np.broadcast(rate, nper, pmt, pv, when) Chris@87: zer = np.zeros(miter.shape) Chris@87: fact = np.where(rate == zer, nper + zer, Chris@87: (1 + rate*when)*(temp - 1)/rate + zer) Chris@87: return -(pv*temp + pmt*fact) Chris@87: Chris@87: def pmt(rate, nper, pv, fv=0, when='end'): Chris@87: """ Chris@87: Compute the payment against loan principal plus interest. Chris@87: Chris@87: Given: Chris@87: * a present value, `pv` (e.g., an amount borrowed) Chris@87: * a future value, `fv` (e.g., 0) Chris@87: * an interest `rate` compounded once per period, of which Chris@87: there are Chris@87: * `nper` total Chris@87: * and (optional) specification of whether payment is made Chris@87: at the beginning (`when` = {'begin', 1}) or the end Chris@87: (`when` = {'end', 0}) of each period Chris@87: Chris@87: Return: Chris@87: the (fixed) periodic payment. Chris@87: Chris@87: Parameters Chris@87: ---------- Chris@87: rate : array_like Chris@87: Rate of interest (per period) Chris@87: nper : array_like Chris@87: Number of compounding periods Chris@87: pv : array_like Chris@87: Present value Chris@87: fv : array_like (optional) Chris@87: Future value (default = 0) Chris@87: when : {{'begin', 1}, {'end', 0}}, {string, int} Chris@87: When payments are due ('begin' (1) or 'end' (0)) Chris@87: Chris@87: Returns Chris@87: ------- Chris@87: out : ndarray Chris@87: Payment against loan plus interest. If all input is scalar, returns a Chris@87: scalar float. If any input is array_like, returns payment for each Chris@87: input element. If multiple inputs are array_like, they all must have Chris@87: the same shape. Chris@87: Chris@87: Notes Chris@87: ----- Chris@87: The payment is computed by solving the equation:: Chris@87: Chris@87: fv + Chris@87: pv*(1 + rate)**nper + Chris@87: pmt*(1 + rate*when)/rate*((1 + rate)**nper - 1) == 0 Chris@87: Chris@87: or, when ``rate == 0``:: Chris@87: Chris@87: fv + pv + pmt * nper == 0 Chris@87: Chris@87: for ``pmt``. Chris@87: Chris@87: Note that computing a monthly mortgage payment is only Chris@87: one use for this function. For example, pmt returns the Chris@87: periodic deposit one must make to achieve a specified Chris@87: future balance given an initial deposit, a fixed, Chris@87: periodically compounded interest rate, and the total Chris@87: number of periods. Chris@87: Chris@87: References Chris@87: ---------- Chris@87: .. [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May). Chris@87: Open Document Format for Office Applications (OpenDocument)v1.2, Chris@87: Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version, Chris@87: Pre-Draft 12. Organization for the Advancement of Structured Information Chris@87: Standards (OASIS). Billerica, MA, USA. [ODT Document]. Chris@87: Available: Chris@87: http://www.oasis-open.org/committees/documents.php Chris@87: ?wg_abbrev=office-formulaOpenDocument-formula-20090508.odt Chris@87: Chris@87: Examples Chris@87: -------- Chris@87: What is the monthly payment needed to pay off a $200,000 loan in 15 Chris@87: years at an annual interest rate of 7.5%? Chris@87: Chris@87: >>> np.pmt(0.075/12, 12*15, 200000) Chris@87: -1854.0247200054619 Chris@87: Chris@87: In order to pay-off (i.e., have a future-value of 0) the $200,000 obtained Chris@87: today, a monthly payment of $1,854.02 would be required. Note that this Chris@87: example illustrates usage of `fv` having a default value of 0. Chris@87: Chris@87: """ Chris@87: when = _convert_when(when) Chris@87: (rate, nper, pv, fv, when) = map(np.asarray, [rate, nper, pv, fv, when]) Chris@87: temp = (1+rate)**nper Chris@87: miter = np.broadcast(rate, nper, pv, fv, when) Chris@87: zer = np.zeros(miter.shape) Chris@87: fact = np.where(rate == zer, nper + zer, Chris@87: (1 + rate*when)*(temp - 1)/rate + zer) Chris@87: return -(fv + pv*temp) / fact Chris@87: Chris@87: def nper(rate, pmt, pv, fv=0, when='end'): Chris@87: """ Chris@87: Compute the number of periodic payments. Chris@87: Chris@87: Parameters Chris@87: ---------- Chris@87: rate : array_like Chris@87: Rate of interest (per period) Chris@87: pmt : array_like Chris@87: Payment Chris@87: pv : array_like Chris@87: Present value Chris@87: fv : array_like, optional Chris@87: Future value Chris@87: when : {{'begin', 1}, {'end', 0}}, {string, int}, optional Chris@87: When payments are due ('begin' (1) or 'end' (0)) Chris@87: Chris@87: Notes Chris@87: ----- Chris@87: The number of periods ``nper`` is computed by solving the equation:: Chris@87: Chris@87: fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate*((1+rate)**nper-1) = 0 Chris@87: Chris@87: but if ``rate = 0`` then:: Chris@87: Chris@87: fv + pv + pmt*nper = 0 Chris@87: Chris@87: Examples Chris@87: -------- Chris@87: If you only had $150/month to pay towards the loan, how long would it take Chris@87: to pay-off a loan of $8,000 at 7% annual interest? Chris@87: Chris@87: >>> print round(np.nper(0.07/12, -150, 8000), 5) Chris@87: 64.07335 Chris@87: Chris@87: So, over 64 months would be required to pay off the loan. Chris@87: Chris@87: The same analysis could be done with several different interest rates Chris@87: and/or payments and/or total amounts to produce an entire table. Chris@87: Chris@87: >>> np.nper(*(np.ogrid[0.07/12: 0.08/12: 0.01/12, Chris@87: ... -150 : -99 : 50 , Chris@87: ... 8000 : 9001 : 1000])) Chris@87: array([[[ 64.07334877, 74.06368256], Chris@87: [ 108.07548412, 127.99022654]], Chris@87: [[ 66.12443902, 76.87897353], Chris@87: [ 114.70165583, 137.90124779]]]) Chris@87: Chris@87: """ Chris@87: when = _convert_when(when) Chris@87: (rate, pmt, pv, fv, when) = map(np.asarray, [rate, pmt, pv, fv, when]) Chris@87: Chris@87: use_zero_rate = False Chris@87: with np.errstate(divide="raise"): Chris@87: try: Chris@87: z = pmt*(1.0+rate*when)/rate Chris@87: except FloatingPointError: Chris@87: use_zero_rate = True Chris@87: Chris@87: if use_zero_rate: Chris@87: return (-fv + pv) / (pmt + 0.0) Chris@87: else: Chris@87: A = -(fv + pv)/(pmt+0.0) Chris@87: B = np.log((-fv+z) / (pv+z))/np.log(1.0+rate) Chris@87: miter = np.broadcast(rate, pmt, pv, fv, when) Chris@87: zer = np.zeros(miter.shape) Chris@87: return np.where(rate == zer, A + zer, B + zer) + 0.0 Chris@87: Chris@87: def ipmt(rate, per, nper, pv, fv=0.0, when='end'): Chris@87: """ Chris@87: Compute the interest portion of a payment. Chris@87: Chris@87: Parameters Chris@87: ---------- Chris@87: rate : scalar or array_like of shape(M, ) Chris@87: Rate of interest as decimal (not per cent) per period Chris@87: per : scalar or array_like of shape(M, ) Chris@87: Interest paid against the loan changes during the life or the loan. Chris@87: The `per` is the payment period to calculate the interest amount. Chris@87: nper : scalar or array_like of shape(M, ) Chris@87: Number of compounding periods Chris@87: pv : scalar or array_like of shape(M, ) Chris@87: Present value Chris@87: fv : scalar or array_like of shape(M, ), optional Chris@87: Future value Chris@87: when : {{'begin', 1}, {'end', 0}}, {string, int}, optional Chris@87: When payments are due ('begin' (1) or 'end' (0)). Chris@87: Defaults to {'end', 0}. Chris@87: Chris@87: Returns Chris@87: ------- Chris@87: out : ndarray Chris@87: Interest portion of payment. If all input is scalar, returns a scalar Chris@87: float. If any input is array_like, returns interest payment for each Chris@87: input element. If multiple inputs are array_like, they all must have Chris@87: the same shape. Chris@87: Chris@87: See Also Chris@87: -------- Chris@87: ppmt, pmt, pv Chris@87: Chris@87: Notes Chris@87: ----- Chris@87: The total payment is made up of payment against principal plus interest. Chris@87: Chris@87: ``pmt = ppmt + ipmt`` Chris@87: Chris@87: Examples Chris@87: -------- Chris@87: What is the amortization schedule for a 1 year loan of $2500 at Chris@87: 8.24% interest per year compounded monthly? Chris@87: Chris@87: >>> principal = 2500.00 Chris@87: Chris@87: The 'per' variable represents the periods of the loan. Remember that Chris@87: financial equations start the period count at 1! Chris@87: Chris@87: >>> per = np.arange(1*12) + 1 Chris@87: >>> ipmt = np.ipmt(0.0824/12, per, 1*12, principal) Chris@87: >>> ppmt = np.ppmt(0.0824/12, per, 1*12, principal) Chris@87: Chris@87: Each element of the sum of the 'ipmt' and 'ppmt' arrays should equal Chris@87: 'pmt'. Chris@87: Chris@87: >>> pmt = np.pmt(0.0824/12, 1*12, principal) Chris@87: >>> np.allclose(ipmt + ppmt, pmt) Chris@87: True Chris@87: Chris@87: >>> fmt = '{0:2d} {1:8.2f} {2:8.2f} {3:8.2f}' Chris@87: >>> for payment in per: Chris@87: ... index = payment - 1 Chris@87: ... principal = principal + ppmt[index] Chris@87: ... print fmt.format(payment, ppmt[index], ipmt[index], principal) Chris@87: 1 -200.58 -17.17 2299.42 Chris@87: 2 -201.96 -15.79 2097.46 Chris@87: 3 -203.35 -14.40 1894.11 Chris@87: 4 -204.74 -13.01 1689.37 Chris@87: 5 -206.15 -11.60 1483.22 Chris@87: 6 -207.56 -10.18 1275.66 Chris@87: 7 -208.99 -8.76 1066.67 Chris@87: 8 -210.42 -7.32 856.25 Chris@87: 9 -211.87 -5.88 644.38 Chris@87: 10 -213.32 -4.42 431.05 Chris@87: 11 -214.79 -2.96 216.26 Chris@87: 12 -216.26 -1.49 -0.00 Chris@87: Chris@87: >>> interestpd = np.sum(ipmt) Chris@87: >>> np.round(interestpd, 2) Chris@87: -112.98 Chris@87: Chris@87: """ Chris@87: when = _convert_when(when) Chris@87: rate, per, nper, pv, fv, when = np.broadcast_arrays(rate, per, nper, Chris@87: pv, fv, when) Chris@87: total_pmt = pmt(rate, nper, pv, fv, when) Chris@87: ipmt = _rbl(rate, per, total_pmt, pv, when)*rate Chris@87: try: Chris@87: ipmt = np.where(when == 1, ipmt/(1 + rate), ipmt) Chris@87: ipmt = np.where(np.logical_and(when == 1, per == 1), 0.0, ipmt) Chris@87: except IndexError: Chris@87: pass Chris@87: return ipmt Chris@87: Chris@87: def _rbl(rate, per, pmt, pv, when): Chris@87: """ Chris@87: This function is here to simply have a different name for the 'fv' Chris@87: function to not interfere with the 'fv' keyword argument within the 'ipmt' Chris@87: function. It is the 'remaining balance on loan' which might be useful as Chris@87: it's own function, but is easily calculated with the 'fv' function. Chris@87: """ Chris@87: return fv(rate, (per - 1), pmt, pv, when) Chris@87: Chris@87: def ppmt(rate, per, nper, pv, fv=0.0, when='end'): Chris@87: """ Chris@87: Compute the payment against loan principal. Chris@87: Chris@87: Parameters Chris@87: ---------- Chris@87: rate : array_like Chris@87: Rate of interest (per period) Chris@87: per : array_like, int Chris@87: Amount paid against the loan changes. The `per` is the period of Chris@87: interest. Chris@87: nper : array_like Chris@87: Number of compounding periods Chris@87: pv : array_like Chris@87: Present value Chris@87: fv : array_like, optional Chris@87: Future value Chris@87: when : {{'begin', 1}, {'end', 0}}, {string, int} Chris@87: When payments are due ('begin' (1) or 'end' (0)) Chris@87: Chris@87: See Also Chris@87: -------- Chris@87: pmt, pv, ipmt Chris@87: Chris@87: """ Chris@87: total = pmt(rate, nper, pv, fv, when) Chris@87: return total - ipmt(rate, per, nper, pv, fv, when) Chris@87: Chris@87: def pv(rate, nper, pmt, fv=0.0, when='end'): Chris@87: """ Chris@87: Compute the present value. Chris@87: Chris@87: Given: Chris@87: * a future value, `fv` Chris@87: * an interest `rate` compounded once per period, of which Chris@87: there are Chris@87: * `nper` total Chris@87: * a (fixed) payment, `pmt`, paid either Chris@87: * at the beginning (`when` = {'begin', 1}) or the end Chris@87: (`when` = {'end', 0}) of each period Chris@87: Chris@87: Return: Chris@87: the value now Chris@87: Chris@87: Parameters Chris@87: ---------- Chris@87: rate : array_like Chris@87: Rate of interest (per period) Chris@87: nper : array_like Chris@87: Number of compounding periods Chris@87: pmt : array_like Chris@87: Payment Chris@87: fv : array_like, optional Chris@87: Future value Chris@87: when : {{'begin', 1}, {'end', 0}}, {string, int}, optional Chris@87: When payments are due ('begin' (1) or 'end' (0)) Chris@87: Chris@87: Returns Chris@87: ------- Chris@87: out : ndarray, float Chris@87: Present value of a series of payments or investments. Chris@87: Chris@87: Notes Chris@87: ----- Chris@87: The present value is computed by solving the equation:: Chris@87: Chris@87: fv + Chris@87: pv*(1 + rate)**nper + Chris@87: pmt*(1 + rate*when)/rate*((1 + rate)**nper - 1) = 0 Chris@87: Chris@87: or, when ``rate = 0``:: Chris@87: Chris@87: fv + pv + pmt * nper = 0 Chris@87: Chris@87: for `pv`, which is then returned. Chris@87: Chris@87: References Chris@87: ---------- Chris@87: .. [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May). Chris@87: Open Document Format for Office Applications (OpenDocument)v1.2, Chris@87: Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version, Chris@87: Pre-Draft 12. Organization for the Advancement of Structured Information Chris@87: Standards (OASIS). Billerica, MA, USA. [ODT Document]. Chris@87: Available: Chris@87: http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula Chris@87: OpenDocument-formula-20090508.odt Chris@87: Chris@87: Examples Chris@87: -------- Chris@87: What is the present value (e.g., the initial investment) Chris@87: of an investment that needs to total $15692.93 Chris@87: after 10 years of saving $100 every month? Assume the Chris@87: interest rate is 5% (annually) compounded monthly. Chris@87: Chris@87: >>> np.pv(0.05/12, 10*12, -100, 15692.93) Chris@87: -100.00067131625819 Chris@87: Chris@87: By convention, the negative sign represents cash flow out Chris@87: (i.e., money not available today). Thus, to end up with Chris@87: $15,692.93 in 10 years saving $100 a month at 5% annual Chris@87: interest, one's initial deposit should also be $100. Chris@87: Chris@87: If any input is array_like, ``pv`` returns an array of equal shape. Chris@87: Let's compare different interest rates in the example above: Chris@87: Chris@87: >>> a = np.array((0.05, 0.04, 0.03))/12 Chris@87: >>> np.pv(a, 10*12, -100, 15692.93) Chris@87: array([ -100.00067132, -649.26771385, -1273.78633713]) Chris@87: Chris@87: So, to end up with the same $15692.93 under the same $100 per month Chris@87: "savings plan," for annual interest rates of 4% and 3%, one would Chris@87: need initial investments of $649.27 and $1273.79, respectively. Chris@87: Chris@87: """ Chris@87: when = _convert_when(when) Chris@87: (rate, nper, pmt, fv, when) = map(np.asarray, [rate, nper, pmt, fv, when]) Chris@87: temp = (1+rate)**nper Chris@87: miter = np.broadcast(rate, nper, pmt, fv, when) Chris@87: zer = np.zeros(miter.shape) Chris@87: fact = np.where(rate == zer, nper+zer, (1+rate*when)*(temp-1)/rate+zer) Chris@87: return -(fv + pmt*fact)/temp Chris@87: Chris@87: # Computed with Sage Chris@87: # (y + (r + 1)^n*x + p*((r + 1)^n - 1)*(r*w + 1)/r)/(n*(r + 1)^(n - 1)*x - Chris@87: # p*((r + 1)^n - 1)*(r*w + 1)/r^2 + n*p*(r + 1)^(n - 1)*(r*w + 1)/r + Chris@87: # p*((r + 1)^n - 1)*w/r) Chris@87: Chris@87: def _g_div_gp(r, n, p, x, y, w): Chris@87: t1 = (r+1)**n Chris@87: t2 = (r+1)**(n-1) Chris@87: return ((y + t1*x + p*(t1 - 1)*(r*w + 1)/r) / Chris@87: (n*t2*x - p*(t1 - 1)*(r*w + 1)/(r**2) + n*p*t2*(r*w + 1)/r + Chris@87: p*(t1 - 1)*w/r)) Chris@87: Chris@87: # Use Newton's iteration until the change is less than 1e-6 Chris@87: # for all values or a maximum of 100 iterations is reached. Chris@87: # Newton's rule is Chris@87: # r_{n+1} = r_{n} - g(r_n)/g'(r_n) Chris@87: # where Chris@87: # g(r) is the formula Chris@87: # g'(r) is the derivative with respect to r. Chris@87: def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100): Chris@87: """ Chris@87: Compute the rate of interest per period. Chris@87: Chris@87: Parameters Chris@87: ---------- Chris@87: nper : array_like Chris@87: Number of compounding periods Chris@87: pmt : array_like Chris@87: Payment Chris@87: pv : array_like Chris@87: Present value Chris@87: fv : array_like Chris@87: Future value Chris@87: when : {{'begin', 1}, {'end', 0}}, {string, int}, optional Chris@87: When payments are due ('begin' (1) or 'end' (0)) Chris@87: guess : float, optional Chris@87: Starting guess for solving the rate of interest Chris@87: tol : float, optional Chris@87: Required tolerance for the solution Chris@87: maxiter : int, optional Chris@87: Maximum iterations in finding the solution Chris@87: Chris@87: Notes Chris@87: ----- Chris@87: The rate of interest is computed by iteratively solving the Chris@87: (non-linear) equation:: Chris@87: Chris@87: fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate * ((1+rate)**nper - 1) = 0 Chris@87: Chris@87: for ``rate``. Chris@87: Chris@87: References Chris@87: ---------- Chris@87: Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May). Open Document Chris@87: Format for Office Applications (OpenDocument)v1.2, Part 2: Recalculated Chris@87: Formula (OpenFormula) Format - Annotated Version, Pre-Draft 12. Chris@87: Organization for the Advancement of Structured Information Standards Chris@87: (OASIS). Billerica, MA, USA. [ODT Document]. Available: Chris@87: http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula Chris@87: OpenDocument-formula-20090508.odt Chris@87: Chris@87: """ Chris@87: when = _convert_when(when) Chris@87: (nper, pmt, pv, fv, when) = map(np.asarray, [nper, pmt, pv, fv, when]) Chris@87: rn = guess Chris@87: iter = 0 Chris@87: close = False Chris@87: while (iter < maxiter) and not close: Chris@87: rnp1 = rn - _g_div_gp(rn, nper, pmt, pv, fv, when) Chris@87: diff = abs(rnp1-rn) Chris@87: close = np.all(diff < tol) Chris@87: iter += 1 Chris@87: rn = rnp1 Chris@87: if not close: Chris@87: # Return nan's in array of the same shape as rn Chris@87: return np.nan + rn Chris@87: else: Chris@87: return rn Chris@87: Chris@87: def irr(values): Chris@87: """ Chris@87: Return the Internal Rate of Return (IRR). Chris@87: Chris@87: This is the "average" periodically compounded rate of return Chris@87: that gives a net present value of 0.0; for a more complete explanation, Chris@87: see Notes below. Chris@87: Chris@87: Parameters Chris@87: ---------- Chris@87: values : array_like, shape(N,) Chris@87: Input cash flows per time period. By convention, net "deposits" Chris@87: are negative and net "withdrawals" are positive. Thus, for Chris@87: example, at least the first element of `values`, which represents Chris@87: the initial investment, will typically be negative. Chris@87: Chris@87: Returns Chris@87: ------- Chris@87: out : float Chris@87: Internal Rate of Return for periodic input values. Chris@87: Chris@87: Notes Chris@87: ----- Chris@87: The IRR is perhaps best understood through an example (illustrated Chris@87: using np.irr in the Examples section below). Suppose one invests 100 Chris@87: units and then makes the following withdrawals at regular (fixed) Chris@87: intervals: 39, 59, 55, 20. Assuming the ending value is 0, one's 100 Chris@87: unit investment yields 173 units; however, due to the combination of Chris@87: compounding and the periodic withdrawals, the "average" rate of return Chris@87: is neither simply 0.73/4 nor (1.73)^0.25-1. Rather, it is the solution Chris@87: (for :math:`r`) of the equation: Chris@87: Chris@87: .. math:: -100 + \\frac{39}{1+r} + \\frac{59}{(1+r)^2} Chris@87: + \\frac{55}{(1+r)^3} + \\frac{20}{(1+r)^4} = 0 Chris@87: Chris@87: In general, for `values` :math:`= [v_0, v_1, ... v_M]`, Chris@87: irr is the solution of the equation: [G]_ Chris@87: Chris@87: .. math:: \\sum_{t=0}^M{\\frac{v_t}{(1+irr)^{t}}} = 0 Chris@87: Chris@87: References Chris@87: ---------- Chris@87: .. [G] L. J. Gitman, "Principles of Managerial Finance, Brief," 3rd ed., Chris@87: Addison-Wesley, 2003, pg. 348. Chris@87: Chris@87: Examples Chris@87: -------- Chris@87: >>> round(irr([-100, 39, 59, 55, 20]), 5) Chris@87: 0.28095 Chris@87: >>> round(irr([-100, 0, 0, 74]), 5) Chris@87: -0.0955 Chris@87: >>> round(irr([-100, 100, 0, -7]), 5) Chris@87: -0.0833 Chris@87: >>> round(irr([-100, 100, 0, 7]), 5) Chris@87: 0.06206 Chris@87: >>> round(irr([-5, 10.5, 1, -8, 1]), 5) Chris@87: 0.0886 Chris@87: Chris@87: (Compare with the Example given for numpy.lib.financial.npv) Chris@87: Chris@87: """ Chris@87: res = np.roots(values[::-1]) Chris@87: mask = (res.imag == 0) & (res.real > 0) Chris@87: if res.size == 0: Chris@87: return np.nan Chris@87: res = res[mask].real Chris@87: # NPV(rate) = 0 can have more than one solution so we return Chris@87: # only the solution closest to zero. Chris@87: rate = 1.0/res - 1 Chris@87: rate = rate.item(np.argmin(np.abs(rate))) Chris@87: return rate Chris@87: Chris@87: def npv(rate, values): Chris@87: """ Chris@87: Returns the NPV (Net Present Value) of a cash flow series. Chris@87: Chris@87: Parameters Chris@87: ---------- Chris@87: rate : scalar Chris@87: The discount rate. Chris@87: values : array_like, shape(M, ) Chris@87: The values of the time series of cash flows. The (fixed) time Chris@87: interval between cash flow "events" must be the same as that for Chris@87: which `rate` is given (i.e., if `rate` is per year, then precisely Chris@87: a year is understood to elapse between each cash flow event). By Chris@87: convention, investments or "deposits" are negative, income or Chris@87: "withdrawals" are positive; `values` must begin with the initial Chris@87: investment, thus `values[0]` will typically be negative. Chris@87: Chris@87: Returns Chris@87: ------- Chris@87: out : float Chris@87: The NPV of the input cash flow series `values` at the discount Chris@87: `rate`. Chris@87: Chris@87: Notes Chris@87: ----- Chris@87: Returns the result of: [G]_ Chris@87: Chris@87: .. math :: \\sum_{t=0}^{M-1}{\\frac{values_t}{(1+rate)^{t}}} Chris@87: Chris@87: References Chris@87: ---------- Chris@87: .. [G] L. J. Gitman, "Principles of Managerial Finance, Brief," 3rd ed., Chris@87: Addison-Wesley, 2003, pg. 346. Chris@87: Chris@87: Examples Chris@87: -------- Chris@87: >>> np.npv(0.281,[-100, 39, 59, 55, 20]) Chris@87: -0.0084785916384548798 Chris@87: Chris@87: (Compare with the Example given for numpy.lib.financial.irr) Chris@87: Chris@87: """ Chris@87: values = np.asarray(values) Chris@87: return (values / (1+rate)**np.arange(0, len(values))).sum(axis=0) Chris@87: Chris@87: def mirr(values, finance_rate, reinvest_rate): Chris@87: """ Chris@87: Modified internal rate of return. Chris@87: Chris@87: Parameters Chris@87: ---------- Chris@87: values : array_like Chris@87: Cash flows (must contain at least one positive and one negative Chris@87: value) or nan is returned. The first value is considered a sunk Chris@87: cost at time zero. Chris@87: finance_rate : scalar Chris@87: Interest rate paid on the cash flows Chris@87: reinvest_rate : scalar Chris@87: Interest rate received on the cash flows upon reinvestment Chris@87: Chris@87: Returns Chris@87: ------- Chris@87: out : float Chris@87: Modified internal rate of return Chris@87: Chris@87: """ Chris@87: values = np.asarray(values, dtype=np.double) Chris@87: n = values.size Chris@87: pos = values > 0 Chris@87: neg = values < 0 Chris@87: if not (pos.any() and neg.any()): Chris@87: return np.nan Chris@87: numer = np.abs(npv(reinvest_rate, values*pos)) Chris@87: denom = np.abs(npv(finance_rate, values*neg)) Chris@87: return (numer/denom)**(1.0/(n - 1))*(1 + reinvest_rate) - 1