Chris@87
|
1 """Some simple financial calculations
|
Chris@87
|
2
|
Chris@87
|
3 patterned after spreadsheet computations.
|
Chris@87
|
4
|
Chris@87
|
5 There is some complexity in each function
|
Chris@87
|
6 so that the functions behave like ufuncs with
|
Chris@87
|
7 broadcasting and being able to be called with scalars
|
Chris@87
|
8 or arrays (or other sequences).
|
Chris@87
|
9
|
Chris@87
|
10 """
|
Chris@87
|
11 from __future__ import division, absolute_import, print_function
|
Chris@87
|
12
|
Chris@87
|
13 import numpy as np
|
Chris@87
|
14
|
Chris@87
|
15 __all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate',
|
Chris@87
|
16 'irr', 'npv', 'mirr']
|
Chris@87
|
17
|
Chris@87
|
18 _when_to_num = {'end':0, 'begin':1,
|
Chris@87
|
19 'e':0, 'b':1,
|
Chris@87
|
20 0:0, 1:1,
|
Chris@87
|
21 'beginning':1,
|
Chris@87
|
22 'start':1,
|
Chris@87
|
23 'finish':0}
|
Chris@87
|
24
|
Chris@87
|
25 def _convert_when(when):
|
Chris@87
|
26 #Test to see if when has already been converted to ndarray
|
Chris@87
|
27 #This will happen if one function calls another, for example ppmt
|
Chris@87
|
28 if isinstance(when, np.ndarray):
|
Chris@87
|
29 return when
|
Chris@87
|
30 try:
|
Chris@87
|
31 return _when_to_num[when]
|
Chris@87
|
32 except (KeyError, TypeError):
|
Chris@87
|
33 return [_when_to_num[x] for x in when]
|
Chris@87
|
34
|
Chris@87
|
35
|
Chris@87
|
36 def fv(rate, nper, pmt, pv, when='end'):
|
Chris@87
|
37 """
|
Chris@87
|
38 Compute the future value.
|
Chris@87
|
39
|
Chris@87
|
40 Given:
|
Chris@87
|
41 * a present value, `pv`
|
Chris@87
|
42 * an interest `rate` compounded once per period, of which
|
Chris@87
|
43 there are
|
Chris@87
|
44 * `nper` total
|
Chris@87
|
45 * a (fixed) payment, `pmt`, paid either
|
Chris@87
|
46 * at the beginning (`when` = {'begin', 1}) or the end
|
Chris@87
|
47 (`when` = {'end', 0}) of each period
|
Chris@87
|
48
|
Chris@87
|
49 Return:
|
Chris@87
|
50 the value at the end of the `nper` periods
|
Chris@87
|
51
|
Chris@87
|
52 Parameters
|
Chris@87
|
53 ----------
|
Chris@87
|
54 rate : scalar or array_like of shape(M, )
|
Chris@87
|
55 Rate of interest as decimal (not per cent) per period
|
Chris@87
|
56 nper : scalar or array_like of shape(M, )
|
Chris@87
|
57 Number of compounding periods
|
Chris@87
|
58 pmt : scalar or array_like of shape(M, )
|
Chris@87
|
59 Payment
|
Chris@87
|
60 pv : scalar or array_like of shape(M, )
|
Chris@87
|
61 Present value
|
Chris@87
|
62 when : {{'begin', 1}, {'end', 0}}, {string, int}, optional
|
Chris@87
|
63 When payments are due ('begin' (1) or 'end' (0)).
|
Chris@87
|
64 Defaults to {'end', 0}.
|
Chris@87
|
65
|
Chris@87
|
66 Returns
|
Chris@87
|
67 -------
|
Chris@87
|
68 out : ndarray
|
Chris@87
|
69 Future values. If all input is scalar, returns a scalar float. If
|
Chris@87
|
70 any input is array_like, returns future values for each input element.
|
Chris@87
|
71 If multiple inputs are array_like, they all must have the same shape.
|
Chris@87
|
72
|
Chris@87
|
73 Notes
|
Chris@87
|
74 -----
|
Chris@87
|
75 The future value is computed by solving the equation::
|
Chris@87
|
76
|
Chris@87
|
77 fv +
|
Chris@87
|
78 pv*(1+rate)**nper +
|
Chris@87
|
79 pmt*(1 + rate*when)/rate*((1 + rate)**nper - 1) == 0
|
Chris@87
|
80
|
Chris@87
|
81 or, when ``rate == 0``::
|
Chris@87
|
82
|
Chris@87
|
83 fv + pv + pmt * nper == 0
|
Chris@87
|
84
|
Chris@87
|
85 References
|
Chris@87
|
86 ----------
|
Chris@87
|
87 .. [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May).
|
Chris@87
|
88 Open Document Format for Office Applications (OpenDocument)v1.2,
|
Chris@87
|
89 Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version,
|
Chris@87
|
90 Pre-Draft 12. Organization for the Advancement of Structured Information
|
Chris@87
|
91 Standards (OASIS). Billerica, MA, USA. [ODT Document].
|
Chris@87
|
92 Available:
|
Chris@87
|
93 http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula
|
Chris@87
|
94 OpenDocument-formula-20090508.odt
|
Chris@87
|
95
|
Chris@87
|
96 Examples
|
Chris@87
|
97 --------
|
Chris@87
|
98 What is the future value after 10 years of saving $100 now, with
|
Chris@87
|
99 an additional monthly savings of $100. Assume the interest rate is
|
Chris@87
|
100 5% (annually) compounded monthly?
|
Chris@87
|
101
|
Chris@87
|
102 >>> np.fv(0.05/12, 10*12, -100, -100)
|
Chris@87
|
103 15692.928894335748
|
Chris@87
|
104
|
Chris@87
|
105 By convention, the negative sign represents cash flow out (i.e. money not
|
Chris@87
|
106 available today). Thus, saving $100 a month at 5% annual interest leads
|
Chris@87
|
107 to $15,692.93 available to spend in 10 years.
|
Chris@87
|
108
|
Chris@87
|
109 If any input is array_like, returns an array of equal shape. Let's
|
Chris@87
|
110 compare different interest rates from the example above.
|
Chris@87
|
111
|
Chris@87
|
112 >>> a = np.array((0.05, 0.06, 0.07))/12
|
Chris@87
|
113 >>> np.fv(a, 10*12, -100, -100)
|
Chris@87
|
114 array([ 15692.92889434, 16569.87435405, 17509.44688102])
|
Chris@87
|
115
|
Chris@87
|
116 """
|
Chris@87
|
117 when = _convert_when(when)
|
Chris@87
|
118 (rate, nper, pmt, pv, when) = map(np.asarray, [rate, nper, pmt, pv, when])
|
Chris@87
|
119 temp = (1+rate)**nper
|
Chris@87
|
120 miter = np.broadcast(rate, nper, pmt, pv, when)
|
Chris@87
|
121 zer = np.zeros(miter.shape)
|
Chris@87
|
122 fact = np.where(rate == zer, nper + zer,
|
Chris@87
|
123 (1 + rate*when)*(temp - 1)/rate + zer)
|
Chris@87
|
124 return -(pv*temp + pmt*fact)
|
Chris@87
|
125
|
Chris@87
|
126 def pmt(rate, nper, pv, fv=0, when='end'):
|
Chris@87
|
127 """
|
Chris@87
|
128 Compute the payment against loan principal plus interest.
|
Chris@87
|
129
|
Chris@87
|
130 Given:
|
Chris@87
|
131 * a present value, `pv` (e.g., an amount borrowed)
|
Chris@87
|
132 * a future value, `fv` (e.g., 0)
|
Chris@87
|
133 * an interest `rate` compounded once per period, of which
|
Chris@87
|
134 there are
|
Chris@87
|
135 * `nper` total
|
Chris@87
|
136 * and (optional) specification of whether payment is made
|
Chris@87
|
137 at the beginning (`when` = {'begin', 1}) or the end
|
Chris@87
|
138 (`when` = {'end', 0}) of each period
|
Chris@87
|
139
|
Chris@87
|
140 Return:
|
Chris@87
|
141 the (fixed) periodic payment.
|
Chris@87
|
142
|
Chris@87
|
143 Parameters
|
Chris@87
|
144 ----------
|
Chris@87
|
145 rate : array_like
|
Chris@87
|
146 Rate of interest (per period)
|
Chris@87
|
147 nper : array_like
|
Chris@87
|
148 Number of compounding periods
|
Chris@87
|
149 pv : array_like
|
Chris@87
|
150 Present value
|
Chris@87
|
151 fv : array_like (optional)
|
Chris@87
|
152 Future value (default = 0)
|
Chris@87
|
153 when : {{'begin', 1}, {'end', 0}}, {string, int}
|
Chris@87
|
154 When payments are due ('begin' (1) or 'end' (0))
|
Chris@87
|
155
|
Chris@87
|
156 Returns
|
Chris@87
|
157 -------
|
Chris@87
|
158 out : ndarray
|
Chris@87
|
159 Payment against loan plus interest. If all input is scalar, returns a
|
Chris@87
|
160 scalar float. If any input is array_like, returns payment for each
|
Chris@87
|
161 input element. If multiple inputs are array_like, they all must have
|
Chris@87
|
162 the same shape.
|
Chris@87
|
163
|
Chris@87
|
164 Notes
|
Chris@87
|
165 -----
|
Chris@87
|
166 The payment is computed by solving the equation::
|
Chris@87
|
167
|
Chris@87
|
168 fv +
|
Chris@87
|
169 pv*(1 + rate)**nper +
|
Chris@87
|
170 pmt*(1 + rate*when)/rate*((1 + rate)**nper - 1) == 0
|
Chris@87
|
171
|
Chris@87
|
172 or, when ``rate == 0``::
|
Chris@87
|
173
|
Chris@87
|
174 fv + pv + pmt * nper == 0
|
Chris@87
|
175
|
Chris@87
|
176 for ``pmt``.
|
Chris@87
|
177
|
Chris@87
|
178 Note that computing a monthly mortgage payment is only
|
Chris@87
|
179 one use for this function. For example, pmt returns the
|
Chris@87
|
180 periodic deposit one must make to achieve a specified
|
Chris@87
|
181 future balance given an initial deposit, a fixed,
|
Chris@87
|
182 periodically compounded interest rate, and the total
|
Chris@87
|
183 number of periods.
|
Chris@87
|
184
|
Chris@87
|
185 References
|
Chris@87
|
186 ----------
|
Chris@87
|
187 .. [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May).
|
Chris@87
|
188 Open Document Format for Office Applications (OpenDocument)v1.2,
|
Chris@87
|
189 Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version,
|
Chris@87
|
190 Pre-Draft 12. Organization for the Advancement of Structured Information
|
Chris@87
|
191 Standards (OASIS). Billerica, MA, USA. [ODT Document].
|
Chris@87
|
192 Available:
|
Chris@87
|
193 http://www.oasis-open.org/committees/documents.php
|
Chris@87
|
194 ?wg_abbrev=office-formulaOpenDocument-formula-20090508.odt
|
Chris@87
|
195
|
Chris@87
|
196 Examples
|
Chris@87
|
197 --------
|
Chris@87
|
198 What is the monthly payment needed to pay off a $200,000 loan in 15
|
Chris@87
|
199 years at an annual interest rate of 7.5%?
|
Chris@87
|
200
|
Chris@87
|
201 >>> np.pmt(0.075/12, 12*15, 200000)
|
Chris@87
|
202 -1854.0247200054619
|
Chris@87
|
203
|
Chris@87
|
204 In order to pay-off (i.e., have a future-value of 0) the $200,000 obtained
|
Chris@87
|
205 today, a monthly payment of $1,854.02 would be required. Note that this
|
Chris@87
|
206 example illustrates usage of `fv` having a default value of 0.
|
Chris@87
|
207
|
Chris@87
|
208 """
|
Chris@87
|
209 when = _convert_when(when)
|
Chris@87
|
210 (rate, nper, pv, fv, when) = map(np.asarray, [rate, nper, pv, fv, when])
|
Chris@87
|
211 temp = (1+rate)**nper
|
Chris@87
|
212 miter = np.broadcast(rate, nper, pv, fv, when)
|
Chris@87
|
213 zer = np.zeros(miter.shape)
|
Chris@87
|
214 fact = np.where(rate == zer, nper + zer,
|
Chris@87
|
215 (1 + rate*when)*(temp - 1)/rate + zer)
|
Chris@87
|
216 return -(fv + pv*temp) / fact
|
Chris@87
|
217
|
Chris@87
|
218 def nper(rate, pmt, pv, fv=0, when='end'):
|
Chris@87
|
219 """
|
Chris@87
|
220 Compute the number of periodic payments.
|
Chris@87
|
221
|
Chris@87
|
222 Parameters
|
Chris@87
|
223 ----------
|
Chris@87
|
224 rate : array_like
|
Chris@87
|
225 Rate of interest (per period)
|
Chris@87
|
226 pmt : array_like
|
Chris@87
|
227 Payment
|
Chris@87
|
228 pv : array_like
|
Chris@87
|
229 Present value
|
Chris@87
|
230 fv : array_like, optional
|
Chris@87
|
231 Future value
|
Chris@87
|
232 when : {{'begin', 1}, {'end', 0}}, {string, int}, optional
|
Chris@87
|
233 When payments are due ('begin' (1) or 'end' (0))
|
Chris@87
|
234
|
Chris@87
|
235 Notes
|
Chris@87
|
236 -----
|
Chris@87
|
237 The number of periods ``nper`` is computed by solving the equation::
|
Chris@87
|
238
|
Chris@87
|
239 fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate*((1+rate)**nper-1) = 0
|
Chris@87
|
240
|
Chris@87
|
241 but if ``rate = 0`` then::
|
Chris@87
|
242
|
Chris@87
|
243 fv + pv + pmt*nper = 0
|
Chris@87
|
244
|
Chris@87
|
245 Examples
|
Chris@87
|
246 --------
|
Chris@87
|
247 If you only had $150/month to pay towards the loan, how long would it take
|
Chris@87
|
248 to pay-off a loan of $8,000 at 7% annual interest?
|
Chris@87
|
249
|
Chris@87
|
250 >>> print round(np.nper(0.07/12, -150, 8000), 5)
|
Chris@87
|
251 64.07335
|
Chris@87
|
252
|
Chris@87
|
253 So, over 64 months would be required to pay off the loan.
|
Chris@87
|
254
|
Chris@87
|
255 The same analysis could be done with several different interest rates
|
Chris@87
|
256 and/or payments and/or total amounts to produce an entire table.
|
Chris@87
|
257
|
Chris@87
|
258 >>> np.nper(*(np.ogrid[0.07/12: 0.08/12: 0.01/12,
|
Chris@87
|
259 ... -150 : -99 : 50 ,
|
Chris@87
|
260 ... 8000 : 9001 : 1000]))
|
Chris@87
|
261 array([[[ 64.07334877, 74.06368256],
|
Chris@87
|
262 [ 108.07548412, 127.99022654]],
|
Chris@87
|
263 [[ 66.12443902, 76.87897353],
|
Chris@87
|
264 [ 114.70165583, 137.90124779]]])
|
Chris@87
|
265
|
Chris@87
|
266 """
|
Chris@87
|
267 when = _convert_when(when)
|
Chris@87
|
268 (rate, pmt, pv, fv, when) = map(np.asarray, [rate, pmt, pv, fv, when])
|
Chris@87
|
269
|
Chris@87
|
270 use_zero_rate = False
|
Chris@87
|
271 with np.errstate(divide="raise"):
|
Chris@87
|
272 try:
|
Chris@87
|
273 z = pmt*(1.0+rate*when)/rate
|
Chris@87
|
274 except FloatingPointError:
|
Chris@87
|
275 use_zero_rate = True
|
Chris@87
|
276
|
Chris@87
|
277 if use_zero_rate:
|
Chris@87
|
278 return (-fv + pv) / (pmt + 0.0)
|
Chris@87
|
279 else:
|
Chris@87
|
280 A = -(fv + pv)/(pmt+0.0)
|
Chris@87
|
281 B = np.log((-fv+z) / (pv+z))/np.log(1.0+rate)
|
Chris@87
|
282 miter = np.broadcast(rate, pmt, pv, fv, when)
|
Chris@87
|
283 zer = np.zeros(miter.shape)
|
Chris@87
|
284 return np.where(rate == zer, A + zer, B + zer) + 0.0
|
Chris@87
|
285
|
Chris@87
|
286 def ipmt(rate, per, nper, pv, fv=0.0, when='end'):
|
Chris@87
|
287 """
|
Chris@87
|
288 Compute the interest portion of a payment.
|
Chris@87
|
289
|
Chris@87
|
290 Parameters
|
Chris@87
|
291 ----------
|
Chris@87
|
292 rate : scalar or array_like of shape(M, )
|
Chris@87
|
293 Rate of interest as decimal (not per cent) per period
|
Chris@87
|
294 per : scalar or array_like of shape(M, )
|
Chris@87
|
295 Interest paid against the loan changes during the life or the loan.
|
Chris@87
|
296 The `per` is the payment period to calculate the interest amount.
|
Chris@87
|
297 nper : scalar or array_like of shape(M, )
|
Chris@87
|
298 Number of compounding periods
|
Chris@87
|
299 pv : scalar or array_like of shape(M, )
|
Chris@87
|
300 Present value
|
Chris@87
|
301 fv : scalar or array_like of shape(M, ), optional
|
Chris@87
|
302 Future value
|
Chris@87
|
303 when : {{'begin', 1}, {'end', 0}}, {string, int}, optional
|
Chris@87
|
304 When payments are due ('begin' (1) or 'end' (0)).
|
Chris@87
|
305 Defaults to {'end', 0}.
|
Chris@87
|
306
|
Chris@87
|
307 Returns
|
Chris@87
|
308 -------
|
Chris@87
|
309 out : ndarray
|
Chris@87
|
310 Interest portion of payment. If all input is scalar, returns a scalar
|
Chris@87
|
311 float. If any input is array_like, returns interest payment for each
|
Chris@87
|
312 input element. If multiple inputs are array_like, they all must have
|
Chris@87
|
313 the same shape.
|
Chris@87
|
314
|
Chris@87
|
315 See Also
|
Chris@87
|
316 --------
|
Chris@87
|
317 ppmt, pmt, pv
|
Chris@87
|
318
|
Chris@87
|
319 Notes
|
Chris@87
|
320 -----
|
Chris@87
|
321 The total payment is made up of payment against principal plus interest.
|
Chris@87
|
322
|
Chris@87
|
323 ``pmt = ppmt + ipmt``
|
Chris@87
|
324
|
Chris@87
|
325 Examples
|
Chris@87
|
326 --------
|
Chris@87
|
327 What is the amortization schedule for a 1 year loan of $2500 at
|
Chris@87
|
328 8.24% interest per year compounded monthly?
|
Chris@87
|
329
|
Chris@87
|
330 >>> principal = 2500.00
|
Chris@87
|
331
|
Chris@87
|
332 The 'per' variable represents the periods of the loan. Remember that
|
Chris@87
|
333 financial equations start the period count at 1!
|
Chris@87
|
334
|
Chris@87
|
335 >>> per = np.arange(1*12) + 1
|
Chris@87
|
336 >>> ipmt = np.ipmt(0.0824/12, per, 1*12, principal)
|
Chris@87
|
337 >>> ppmt = np.ppmt(0.0824/12, per, 1*12, principal)
|
Chris@87
|
338
|
Chris@87
|
339 Each element of the sum of the 'ipmt' and 'ppmt' arrays should equal
|
Chris@87
|
340 'pmt'.
|
Chris@87
|
341
|
Chris@87
|
342 >>> pmt = np.pmt(0.0824/12, 1*12, principal)
|
Chris@87
|
343 >>> np.allclose(ipmt + ppmt, pmt)
|
Chris@87
|
344 True
|
Chris@87
|
345
|
Chris@87
|
346 >>> fmt = '{0:2d} {1:8.2f} {2:8.2f} {3:8.2f}'
|
Chris@87
|
347 >>> for payment in per:
|
Chris@87
|
348 ... index = payment - 1
|
Chris@87
|
349 ... principal = principal + ppmt[index]
|
Chris@87
|
350 ... print fmt.format(payment, ppmt[index], ipmt[index], principal)
|
Chris@87
|
351 1 -200.58 -17.17 2299.42
|
Chris@87
|
352 2 -201.96 -15.79 2097.46
|
Chris@87
|
353 3 -203.35 -14.40 1894.11
|
Chris@87
|
354 4 -204.74 -13.01 1689.37
|
Chris@87
|
355 5 -206.15 -11.60 1483.22
|
Chris@87
|
356 6 -207.56 -10.18 1275.66
|
Chris@87
|
357 7 -208.99 -8.76 1066.67
|
Chris@87
|
358 8 -210.42 -7.32 856.25
|
Chris@87
|
359 9 -211.87 -5.88 644.38
|
Chris@87
|
360 10 -213.32 -4.42 431.05
|
Chris@87
|
361 11 -214.79 -2.96 216.26
|
Chris@87
|
362 12 -216.26 -1.49 -0.00
|
Chris@87
|
363
|
Chris@87
|
364 >>> interestpd = np.sum(ipmt)
|
Chris@87
|
365 >>> np.round(interestpd, 2)
|
Chris@87
|
366 -112.98
|
Chris@87
|
367
|
Chris@87
|
368 """
|
Chris@87
|
369 when = _convert_when(when)
|
Chris@87
|
370 rate, per, nper, pv, fv, when = np.broadcast_arrays(rate, per, nper,
|
Chris@87
|
371 pv, fv, when)
|
Chris@87
|
372 total_pmt = pmt(rate, nper, pv, fv, when)
|
Chris@87
|
373 ipmt = _rbl(rate, per, total_pmt, pv, when)*rate
|
Chris@87
|
374 try:
|
Chris@87
|
375 ipmt = np.where(when == 1, ipmt/(1 + rate), ipmt)
|
Chris@87
|
376 ipmt = np.where(np.logical_and(when == 1, per == 1), 0.0, ipmt)
|
Chris@87
|
377 except IndexError:
|
Chris@87
|
378 pass
|
Chris@87
|
379 return ipmt
|
Chris@87
|
380
|
Chris@87
|
381 def _rbl(rate, per, pmt, pv, when):
|
Chris@87
|
382 """
|
Chris@87
|
383 This function is here to simply have a different name for the 'fv'
|
Chris@87
|
384 function to not interfere with the 'fv' keyword argument within the 'ipmt'
|
Chris@87
|
385 function. It is the 'remaining balance on loan' which might be useful as
|
Chris@87
|
386 it's own function, but is easily calculated with the 'fv' function.
|
Chris@87
|
387 """
|
Chris@87
|
388 return fv(rate, (per - 1), pmt, pv, when)
|
Chris@87
|
389
|
Chris@87
|
390 def ppmt(rate, per, nper, pv, fv=0.0, when='end'):
|
Chris@87
|
391 """
|
Chris@87
|
392 Compute the payment against loan principal.
|
Chris@87
|
393
|
Chris@87
|
394 Parameters
|
Chris@87
|
395 ----------
|
Chris@87
|
396 rate : array_like
|
Chris@87
|
397 Rate of interest (per period)
|
Chris@87
|
398 per : array_like, int
|
Chris@87
|
399 Amount paid against the loan changes. The `per` is the period of
|
Chris@87
|
400 interest.
|
Chris@87
|
401 nper : array_like
|
Chris@87
|
402 Number of compounding periods
|
Chris@87
|
403 pv : array_like
|
Chris@87
|
404 Present value
|
Chris@87
|
405 fv : array_like, optional
|
Chris@87
|
406 Future value
|
Chris@87
|
407 when : {{'begin', 1}, {'end', 0}}, {string, int}
|
Chris@87
|
408 When payments are due ('begin' (1) or 'end' (0))
|
Chris@87
|
409
|
Chris@87
|
410 See Also
|
Chris@87
|
411 --------
|
Chris@87
|
412 pmt, pv, ipmt
|
Chris@87
|
413
|
Chris@87
|
414 """
|
Chris@87
|
415 total = pmt(rate, nper, pv, fv, when)
|
Chris@87
|
416 return total - ipmt(rate, per, nper, pv, fv, when)
|
Chris@87
|
417
|
Chris@87
|
418 def pv(rate, nper, pmt, fv=0.0, when='end'):
|
Chris@87
|
419 """
|
Chris@87
|
420 Compute the present value.
|
Chris@87
|
421
|
Chris@87
|
422 Given:
|
Chris@87
|
423 * a future value, `fv`
|
Chris@87
|
424 * an interest `rate` compounded once per period, of which
|
Chris@87
|
425 there are
|
Chris@87
|
426 * `nper` total
|
Chris@87
|
427 * a (fixed) payment, `pmt`, paid either
|
Chris@87
|
428 * at the beginning (`when` = {'begin', 1}) or the end
|
Chris@87
|
429 (`when` = {'end', 0}) of each period
|
Chris@87
|
430
|
Chris@87
|
431 Return:
|
Chris@87
|
432 the value now
|
Chris@87
|
433
|
Chris@87
|
434 Parameters
|
Chris@87
|
435 ----------
|
Chris@87
|
436 rate : array_like
|
Chris@87
|
437 Rate of interest (per period)
|
Chris@87
|
438 nper : array_like
|
Chris@87
|
439 Number of compounding periods
|
Chris@87
|
440 pmt : array_like
|
Chris@87
|
441 Payment
|
Chris@87
|
442 fv : array_like, optional
|
Chris@87
|
443 Future value
|
Chris@87
|
444 when : {{'begin', 1}, {'end', 0}}, {string, int}, optional
|
Chris@87
|
445 When payments are due ('begin' (1) or 'end' (0))
|
Chris@87
|
446
|
Chris@87
|
447 Returns
|
Chris@87
|
448 -------
|
Chris@87
|
449 out : ndarray, float
|
Chris@87
|
450 Present value of a series of payments or investments.
|
Chris@87
|
451
|
Chris@87
|
452 Notes
|
Chris@87
|
453 -----
|
Chris@87
|
454 The present value is computed by solving the equation::
|
Chris@87
|
455
|
Chris@87
|
456 fv +
|
Chris@87
|
457 pv*(1 + rate)**nper +
|
Chris@87
|
458 pmt*(1 + rate*when)/rate*((1 + rate)**nper - 1) = 0
|
Chris@87
|
459
|
Chris@87
|
460 or, when ``rate = 0``::
|
Chris@87
|
461
|
Chris@87
|
462 fv + pv + pmt * nper = 0
|
Chris@87
|
463
|
Chris@87
|
464 for `pv`, which is then returned.
|
Chris@87
|
465
|
Chris@87
|
466 References
|
Chris@87
|
467 ----------
|
Chris@87
|
468 .. [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May).
|
Chris@87
|
469 Open Document Format for Office Applications (OpenDocument)v1.2,
|
Chris@87
|
470 Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version,
|
Chris@87
|
471 Pre-Draft 12. Organization for the Advancement of Structured Information
|
Chris@87
|
472 Standards (OASIS). Billerica, MA, USA. [ODT Document].
|
Chris@87
|
473 Available:
|
Chris@87
|
474 http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula
|
Chris@87
|
475 OpenDocument-formula-20090508.odt
|
Chris@87
|
476
|
Chris@87
|
477 Examples
|
Chris@87
|
478 --------
|
Chris@87
|
479 What is the present value (e.g., the initial investment)
|
Chris@87
|
480 of an investment that needs to total $15692.93
|
Chris@87
|
481 after 10 years of saving $100 every month? Assume the
|
Chris@87
|
482 interest rate is 5% (annually) compounded monthly.
|
Chris@87
|
483
|
Chris@87
|
484 >>> np.pv(0.05/12, 10*12, -100, 15692.93)
|
Chris@87
|
485 -100.00067131625819
|
Chris@87
|
486
|
Chris@87
|
487 By convention, the negative sign represents cash flow out
|
Chris@87
|
488 (i.e., money not available today). Thus, to end up with
|
Chris@87
|
489 $15,692.93 in 10 years saving $100 a month at 5% annual
|
Chris@87
|
490 interest, one's initial deposit should also be $100.
|
Chris@87
|
491
|
Chris@87
|
492 If any input is array_like, ``pv`` returns an array of equal shape.
|
Chris@87
|
493 Let's compare different interest rates in the example above:
|
Chris@87
|
494
|
Chris@87
|
495 >>> a = np.array((0.05, 0.04, 0.03))/12
|
Chris@87
|
496 >>> np.pv(a, 10*12, -100, 15692.93)
|
Chris@87
|
497 array([ -100.00067132, -649.26771385, -1273.78633713])
|
Chris@87
|
498
|
Chris@87
|
499 So, to end up with the same $15692.93 under the same $100 per month
|
Chris@87
|
500 "savings plan," for annual interest rates of 4% and 3%, one would
|
Chris@87
|
501 need initial investments of $649.27 and $1273.79, respectively.
|
Chris@87
|
502
|
Chris@87
|
503 """
|
Chris@87
|
504 when = _convert_when(when)
|
Chris@87
|
505 (rate, nper, pmt, fv, when) = map(np.asarray, [rate, nper, pmt, fv, when])
|
Chris@87
|
506 temp = (1+rate)**nper
|
Chris@87
|
507 miter = np.broadcast(rate, nper, pmt, fv, when)
|
Chris@87
|
508 zer = np.zeros(miter.shape)
|
Chris@87
|
509 fact = np.where(rate == zer, nper+zer, (1+rate*when)*(temp-1)/rate+zer)
|
Chris@87
|
510 return -(fv + pmt*fact)/temp
|
Chris@87
|
511
|
Chris@87
|
512 # Computed with Sage
|
Chris@87
|
513 # (y + (r + 1)^n*x + p*((r + 1)^n - 1)*(r*w + 1)/r)/(n*(r + 1)^(n - 1)*x -
|
Chris@87
|
514 # p*((r + 1)^n - 1)*(r*w + 1)/r^2 + n*p*(r + 1)^(n - 1)*(r*w + 1)/r +
|
Chris@87
|
515 # p*((r + 1)^n - 1)*w/r)
|
Chris@87
|
516
|
Chris@87
|
517 def _g_div_gp(r, n, p, x, y, w):
|
Chris@87
|
518 t1 = (r+1)**n
|
Chris@87
|
519 t2 = (r+1)**(n-1)
|
Chris@87
|
520 return ((y + t1*x + p*(t1 - 1)*(r*w + 1)/r) /
|
Chris@87
|
521 (n*t2*x - p*(t1 - 1)*(r*w + 1)/(r**2) + n*p*t2*(r*w + 1)/r +
|
Chris@87
|
522 p*(t1 - 1)*w/r))
|
Chris@87
|
523
|
Chris@87
|
524 # Use Newton's iteration until the change is less than 1e-6
|
Chris@87
|
525 # for all values or a maximum of 100 iterations is reached.
|
Chris@87
|
526 # Newton's rule is
|
Chris@87
|
527 # r_{n+1} = r_{n} - g(r_n)/g'(r_n)
|
Chris@87
|
528 # where
|
Chris@87
|
529 # g(r) is the formula
|
Chris@87
|
530 # g'(r) is the derivative with respect to r.
|
Chris@87
|
531 def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100):
|
Chris@87
|
532 """
|
Chris@87
|
533 Compute the rate of interest per period.
|
Chris@87
|
534
|
Chris@87
|
535 Parameters
|
Chris@87
|
536 ----------
|
Chris@87
|
537 nper : array_like
|
Chris@87
|
538 Number of compounding periods
|
Chris@87
|
539 pmt : array_like
|
Chris@87
|
540 Payment
|
Chris@87
|
541 pv : array_like
|
Chris@87
|
542 Present value
|
Chris@87
|
543 fv : array_like
|
Chris@87
|
544 Future value
|
Chris@87
|
545 when : {{'begin', 1}, {'end', 0}}, {string, int}, optional
|
Chris@87
|
546 When payments are due ('begin' (1) or 'end' (0))
|
Chris@87
|
547 guess : float, optional
|
Chris@87
|
548 Starting guess for solving the rate of interest
|
Chris@87
|
549 tol : float, optional
|
Chris@87
|
550 Required tolerance for the solution
|
Chris@87
|
551 maxiter : int, optional
|
Chris@87
|
552 Maximum iterations in finding the solution
|
Chris@87
|
553
|
Chris@87
|
554 Notes
|
Chris@87
|
555 -----
|
Chris@87
|
556 The rate of interest is computed by iteratively solving the
|
Chris@87
|
557 (non-linear) equation::
|
Chris@87
|
558
|
Chris@87
|
559 fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate * ((1+rate)**nper - 1) = 0
|
Chris@87
|
560
|
Chris@87
|
561 for ``rate``.
|
Chris@87
|
562
|
Chris@87
|
563 References
|
Chris@87
|
564 ----------
|
Chris@87
|
565 Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May). Open Document
|
Chris@87
|
566 Format for Office Applications (OpenDocument)v1.2, Part 2: Recalculated
|
Chris@87
|
567 Formula (OpenFormula) Format - Annotated Version, Pre-Draft 12.
|
Chris@87
|
568 Organization for the Advancement of Structured Information Standards
|
Chris@87
|
569 (OASIS). Billerica, MA, USA. [ODT Document]. Available:
|
Chris@87
|
570 http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula
|
Chris@87
|
571 OpenDocument-formula-20090508.odt
|
Chris@87
|
572
|
Chris@87
|
573 """
|
Chris@87
|
574 when = _convert_when(when)
|
Chris@87
|
575 (nper, pmt, pv, fv, when) = map(np.asarray, [nper, pmt, pv, fv, when])
|
Chris@87
|
576 rn = guess
|
Chris@87
|
577 iter = 0
|
Chris@87
|
578 close = False
|
Chris@87
|
579 while (iter < maxiter) and not close:
|
Chris@87
|
580 rnp1 = rn - _g_div_gp(rn, nper, pmt, pv, fv, when)
|
Chris@87
|
581 diff = abs(rnp1-rn)
|
Chris@87
|
582 close = np.all(diff < tol)
|
Chris@87
|
583 iter += 1
|
Chris@87
|
584 rn = rnp1
|
Chris@87
|
585 if not close:
|
Chris@87
|
586 # Return nan's in array of the same shape as rn
|
Chris@87
|
587 return np.nan + rn
|
Chris@87
|
588 else:
|
Chris@87
|
589 return rn
|
Chris@87
|
590
|
Chris@87
|
591 def irr(values):
|
Chris@87
|
592 """
|
Chris@87
|
593 Return the Internal Rate of Return (IRR).
|
Chris@87
|
594
|
Chris@87
|
595 This is the "average" periodically compounded rate of return
|
Chris@87
|
596 that gives a net present value of 0.0; for a more complete explanation,
|
Chris@87
|
597 see Notes below.
|
Chris@87
|
598
|
Chris@87
|
599 Parameters
|
Chris@87
|
600 ----------
|
Chris@87
|
601 values : array_like, shape(N,)
|
Chris@87
|
602 Input cash flows per time period. By convention, net "deposits"
|
Chris@87
|
603 are negative and net "withdrawals" are positive. Thus, for
|
Chris@87
|
604 example, at least the first element of `values`, which represents
|
Chris@87
|
605 the initial investment, will typically be negative.
|
Chris@87
|
606
|
Chris@87
|
607 Returns
|
Chris@87
|
608 -------
|
Chris@87
|
609 out : float
|
Chris@87
|
610 Internal Rate of Return for periodic input values.
|
Chris@87
|
611
|
Chris@87
|
612 Notes
|
Chris@87
|
613 -----
|
Chris@87
|
614 The IRR is perhaps best understood through an example (illustrated
|
Chris@87
|
615 using np.irr in the Examples section below). Suppose one invests 100
|
Chris@87
|
616 units and then makes the following withdrawals at regular (fixed)
|
Chris@87
|
617 intervals: 39, 59, 55, 20. Assuming the ending value is 0, one's 100
|
Chris@87
|
618 unit investment yields 173 units; however, due to the combination of
|
Chris@87
|
619 compounding and the periodic withdrawals, the "average" rate of return
|
Chris@87
|
620 is neither simply 0.73/4 nor (1.73)^0.25-1. Rather, it is the solution
|
Chris@87
|
621 (for :math:`r`) of the equation:
|
Chris@87
|
622
|
Chris@87
|
623 .. math:: -100 + \\frac{39}{1+r} + \\frac{59}{(1+r)^2}
|
Chris@87
|
624 + \\frac{55}{(1+r)^3} + \\frac{20}{(1+r)^4} = 0
|
Chris@87
|
625
|
Chris@87
|
626 In general, for `values` :math:`= [v_0, v_1, ... v_M]`,
|
Chris@87
|
627 irr is the solution of the equation: [G]_
|
Chris@87
|
628
|
Chris@87
|
629 .. math:: \\sum_{t=0}^M{\\frac{v_t}{(1+irr)^{t}}} = 0
|
Chris@87
|
630
|
Chris@87
|
631 References
|
Chris@87
|
632 ----------
|
Chris@87
|
633 .. [G] L. J. Gitman, "Principles of Managerial Finance, Brief," 3rd ed.,
|
Chris@87
|
634 Addison-Wesley, 2003, pg. 348.
|
Chris@87
|
635
|
Chris@87
|
636 Examples
|
Chris@87
|
637 --------
|
Chris@87
|
638 >>> round(irr([-100, 39, 59, 55, 20]), 5)
|
Chris@87
|
639 0.28095
|
Chris@87
|
640 >>> round(irr([-100, 0, 0, 74]), 5)
|
Chris@87
|
641 -0.0955
|
Chris@87
|
642 >>> round(irr([-100, 100, 0, -7]), 5)
|
Chris@87
|
643 -0.0833
|
Chris@87
|
644 >>> round(irr([-100, 100, 0, 7]), 5)
|
Chris@87
|
645 0.06206
|
Chris@87
|
646 >>> round(irr([-5, 10.5, 1, -8, 1]), 5)
|
Chris@87
|
647 0.0886
|
Chris@87
|
648
|
Chris@87
|
649 (Compare with the Example given for numpy.lib.financial.npv)
|
Chris@87
|
650
|
Chris@87
|
651 """
|
Chris@87
|
652 res = np.roots(values[::-1])
|
Chris@87
|
653 mask = (res.imag == 0) & (res.real > 0)
|
Chris@87
|
654 if res.size == 0:
|
Chris@87
|
655 return np.nan
|
Chris@87
|
656 res = res[mask].real
|
Chris@87
|
657 # NPV(rate) = 0 can have more than one solution so we return
|
Chris@87
|
658 # only the solution closest to zero.
|
Chris@87
|
659 rate = 1.0/res - 1
|
Chris@87
|
660 rate = rate.item(np.argmin(np.abs(rate)))
|
Chris@87
|
661 return rate
|
Chris@87
|
662
|
Chris@87
|
663 def npv(rate, values):
|
Chris@87
|
664 """
|
Chris@87
|
665 Returns the NPV (Net Present Value) of a cash flow series.
|
Chris@87
|
666
|
Chris@87
|
667 Parameters
|
Chris@87
|
668 ----------
|
Chris@87
|
669 rate : scalar
|
Chris@87
|
670 The discount rate.
|
Chris@87
|
671 values : array_like, shape(M, )
|
Chris@87
|
672 The values of the time series of cash flows. The (fixed) time
|
Chris@87
|
673 interval between cash flow "events" must be the same as that for
|
Chris@87
|
674 which `rate` is given (i.e., if `rate` is per year, then precisely
|
Chris@87
|
675 a year is understood to elapse between each cash flow event). By
|
Chris@87
|
676 convention, investments or "deposits" are negative, income or
|
Chris@87
|
677 "withdrawals" are positive; `values` must begin with the initial
|
Chris@87
|
678 investment, thus `values[0]` will typically be negative.
|
Chris@87
|
679
|
Chris@87
|
680 Returns
|
Chris@87
|
681 -------
|
Chris@87
|
682 out : float
|
Chris@87
|
683 The NPV of the input cash flow series `values` at the discount
|
Chris@87
|
684 `rate`.
|
Chris@87
|
685
|
Chris@87
|
686 Notes
|
Chris@87
|
687 -----
|
Chris@87
|
688 Returns the result of: [G]_
|
Chris@87
|
689
|
Chris@87
|
690 .. math :: \\sum_{t=0}^{M-1}{\\frac{values_t}{(1+rate)^{t}}}
|
Chris@87
|
691
|
Chris@87
|
692 References
|
Chris@87
|
693 ----------
|
Chris@87
|
694 .. [G] L. J. Gitman, "Principles of Managerial Finance, Brief," 3rd ed.,
|
Chris@87
|
695 Addison-Wesley, 2003, pg. 346.
|
Chris@87
|
696
|
Chris@87
|
697 Examples
|
Chris@87
|
698 --------
|
Chris@87
|
699 >>> np.npv(0.281,[-100, 39, 59, 55, 20])
|
Chris@87
|
700 -0.0084785916384548798
|
Chris@87
|
701
|
Chris@87
|
702 (Compare with the Example given for numpy.lib.financial.irr)
|
Chris@87
|
703
|
Chris@87
|
704 """
|
Chris@87
|
705 values = np.asarray(values)
|
Chris@87
|
706 return (values / (1+rate)**np.arange(0, len(values))).sum(axis=0)
|
Chris@87
|
707
|
Chris@87
|
708 def mirr(values, finance_rate, reinvest_rate):
|
Chris@87
|
709 """
|
Chris@87
|
710 Modified internal rate of return.
|
Chris@87
|
711
|
Chris@87
|
712 Parameters
|
Chris@87
|
713 ----------
|
Chris@87
|
714 values : array_like
|
Chris@87
|
715 Cash flows (must contain at least one positive and one negative
|
Chris@87
|
716 value) or nan is returned. The first value is considered a sunk
|
Chris@87
|
717 cost at time zero.
|
Chris@87
|
718 finance_rate : scalar
|
Chris@87
|
719 Interest rate paid on the cash flows
|
Chris@87
|
720 reinvest_rate : scalar
|
Chris@87
|
721 Interest rate received on the cash flows upon reinvestment
|
Chris@87
|
722
|
Chris@87
|
723 Returns
|
Chris@87
|
724 -------
|
Chris@87
|
725 out : float
|
Chris@87
|
726 Modified internal rate of return
|
Chris@87
|
727
|
Chris@87
|
728 """
|
Chris@87
|
729 values = np.asarray(values, dtype=np.double)
|
Chris@87
|
730 n = values.size
|
Chris@87
|
731 pos = values > 0
|
Chris@87
|
732 neg = values < 0
|
Chris@87
|
733 if not (pos.any() and neg.any()):
|
Chris@87
|
734 return np.nan
|
Chris@87
|
735 numer = np.abs(npv(reinvest_rate, values*pos))
|
Chris@87
|
736 denom = np.abs(npv(finance_rate, values*neg))
|
Chris@87
|
737 return (numer/denom)**(1.0/(n - 1))*(1 + reinvest_rate) - 1
|