Python: irr redux

An irr function is included in a past entry for interest entry. Here we separate it and make it more robust by allowing it to return multiple irr values (if there is more than one) on a specified interval [a, b]. Our revised irr function still uses Newton’s method, but is combined with an interval generator where a solution may be found.

?Download irr.py
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
"""
file       irr.py
author Ernesto P. Adorio
           UPDEPP (UP Clarkfield)
revisisions 0.0.2 apr. 2, 2009
"""
 
 
def irr(C,  ilow = 0,  ihigh = 4,  npts = 1000,  ztol = 1.0e-5,  verbose = True):
    """
    C              - cash flow
    ilow, ihigh - interval  to find the interest rate.
    npts - number of pts to try.
    ztol  - tolerance  for Newton's method
    verbose - True if  print progress to solution.
 
    Returns an array of the interests which results in an NPV of zero.
    Each element in the array returns a tuple of
        estimated rate of return, NPV, derivative, final increment, number of iterations.
 
    The returned array may have more than one element.   
    """
    def f(guess,  C):
         """
         Evaluates the NPV function for cashflow  array C
         using interest rate of guess.
         """
         f = 0.0 
         n = len(C)
         factor = 1.0
         for i in range(n):
             d = C[i]/factor
             f+=d  
             factor *=  (1 + guess)
         return f
 
    def getintervals(C,  ilow,  ihigh,  npts = 1000):
        """
        Returns intervals where there is a change of sign in the irr.
        """
        di = (ihigh - ilow)/float(npts)
        intervals = []
        interest = ilow
        feval = f(interest,  C)
        sign = (feval < 0.0)
        if verbose:
            print "initial interest, function value:",  interest, feval
            for j in range(1,  npts):
             interest += di                  
             fnew  = f(interest,  C)
             if  (fnew< 0.0)  != sign: #different sign detected.
                 intervals.append((interest - di,  interest))
                 sign = not sign
        return intervals     
 
    def fg(guess, C):
         """
         Evaluates and compute the derivative of the NPV function
         using interest rate of guess.
         """
         f,g = 0.0, 0.0
         n = len(C)
         factor = 1.0
         for i in range(n):
             d = C[i]/factor
             f+=d  
             factor *=  (1 + guess)
             g+= -i* d/(1 + guess)
         return f, g
 
    def newton(guess, C, maxiter=1000, ztol = 1.0e-5,  verbose = True):
         for i in range(maxiter):
               f, g = fg(guess, C)
               dguess = f/g 
               guess = guess - dguess
               if verbose:
                   print "(i,guess, f,g)", i,  guess,  f,  g
               if abs(dguess) < ztol:
                   f, g = fg(guess,  C)
                   dguess = f/g 
                   guess = guess - dguess
                   return guess, f,  g, dguess,   i+1
         return guess,  f,  g,  dguess,  maxiter
    intervals = getintervals(C,  ilow,  ihigh,  npts)
 
    solutions = []
    for interval in intervals:
        if verbose:
            print "Testing interval:",  interval
        guess = interval[0] +0.5 * (interval[1] - interval[0])
        sol = newton(guess,  C,  verbose = verbose)
        print "a solution:(irr, f, g, delta irr,maxiter )",  sol
        solutions.append(sol)
    return solutions    
 
if __name__ =="__main__":    
        C = [-2000,  1000,  -2500,  0,  5000,  3000]
        print "irr = ", irr(C,  0,  4,  1000)

The getintervals function is what makes the module more reliable in finding one (or more solutions). The intervals ensure that the function whose zero or root is to be found has opposite signs in the interval ends. Here is the result of running the above program with verbose=True:

$ python irr.py
irr = initial interest, function value: 0 4500.0
Testing interval: (0.27600000000000019, 0.28000000000000019)
(i,guess, f,g) 0 0.278812461885 6.11465040097 -7526.07662803
(i,guess, f,g) 1 0.278813976123 0.0113539822869 -7498.14691018
a solution:(irr, f, g, delta irr,maxiter ) (0.27881397612830144,
3.9325300349446479e-08, -7498.0949668773683,
-5.2447055583004659e-12, 2)
[(0.27881397612830144, 3.9325300349446479e-08,
-7498.0949668773683, -5.2447055583004659e-12, 2)]
$

Next , in the same spirit as safesec, we combine both half-interval or bisection method with the Newton's method in order that is more reliable and still fast as ever to find a solution.

  • Share/Bookmark

Leave a Reply

Digital explorations is Digg proof thanks to caching by WP Super Cache