1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
|
# Some simple financial calculations
# patterned after spreadsheet computations.
from numpy import log, where
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}
eqstr = """
Parameters
----------
rate :
Rate of interest (per period)
nper :
Number of compounding periods
pmt :
Payment
pv :
Present value
fv :
Future value
when :
When payments are due ('begin' (1) or 'end' (0))
nper / (1 + rate*when) \ / nper \
fv + pv*(1+rate) + pmt*|-------------------|*| (1+rate) - 1 | = 0
\ rate / \ /
fv + pv + pmt * nper = 0 (when rate == 0)
"""
def fv(rate, nper, pmt, pv, when='end'):
"""future value computed by solving the equation
%s
""" % eqstr
when = _when_to_num[when]
temp = (1+rate)**nper
fact = where(rate==0.0, nper, (1+rate*when)*(temp-1)/rate)
return -(pv*temp + pmt*fact)
def pmt(rate, nper, pv, fv=0, when='end'):
"""Payment computed by solving the equation
%s
""" % eqstr
when = _when_to_num[when]
temp = (1+rate)**nper
fact = where(rate==0.0, nper, (1+rate*when)*(temp-1)/rate)
return -(fv + pv*temp) / fact
def nper(rate, pmt, pv, fv=0, when='end'):
"""Number of periods found by solving the equation
%s
""" % eqstr
when = _when_to_num[when]
try:
z = pmt*(1.0+rate*when)/rate
except ZeroDivisionError:
z = 0.0
A = -(fv + pv)/(pmt+0.0)
B = (log(fv-z) - log(pv-z))/log(1.0+rate)
return where(rate==0.0, A, B) + 0.0
def ipmt(rate, per, nper, pv, fv=0.0, when='end'):
raise NotImplementedError
def ppmt(rate, per, nper, pv, fv=0.0, when='end'):
raise NotImplementedError
def pv(rate, nper, pmt, fv=0.0, when='end'):
"""Number of periods found by solving the equation
%s
""" % eqstr
when = _when_to_num[when]
temp = (1+rate)**nper
fact = where(rate == 0.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)
# 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):
"""Number of periods found by solving the equation
%s
""" % eqstr
when = _when_to_num[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):
"""Internal Rate of Return
This is the rate of return that gives a net present value of 0.0
npv(irr(values), values) == 0.0
"""
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):
"""Net Present Value
sum ( values_k / (1+rate)**k, k = 1..n)
"""
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:
Cash flows (must contain at least one positive and one negative value)
or nan is returned.
finance_rate :
Interest rate paid on the cash flows
reinvest_rate :
Interest rate received on the cash flows upon reinvestment
"""
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
|