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 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
|
# 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}
eqstr = """
nper / (1 + rate*when) \ / nper \
fv + pv*(1+rate) + pmt*|-------------------|*| (1+rate) - 1 | = 0
\ rate / \ /
fv + pv + pmt * nper = 0 (when rate == 0)
where (all can be scalars or sequences)
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))
"""
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'):
"""future value computed by solving the equation
"""
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)
fv.__doc__ += eqstr + """
Example
--------
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?
>>> 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.
"""
def pmt(rate, nper, pv, fv=0, when='end'):
"""Payment computed by solving the equation
"""
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
pmt.__doc__ += eqstr + """
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%?
>>> 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.
"""
def nper(rate, pmt, pv, fv=0, when='end'):
"""Number of periods found by solving the equation
"""
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
nper.__doc__ += eqstr + """
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?
>>> 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.
>>> nper(*(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]],
[[ 33.07413144, 39.26244268],
[ 74.06368256, 90.22989997]]])
"""
def ipmt(rate, per, nper, pv, fv=0.0, when='end'):
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'):
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'):
"""Number of periods found by solving the equation
"""
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
pv.__doc__ += eqstr
# 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
"""
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
rate.__doc__ += eqstr
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
|