summaryrefslogtreecommitdiff
path: root/numpy/lib/financial.py
blob: 885fffeeaee9573b88e58a21453505c615b43d51 (plain)
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
# Some simple financial calculations
from numpy import log, where
import numpy as np

__all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate', 'irr', 'npv']

_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)