Welcome, guest | Sign In | My Account | Store | Cart

A few financial functions for quick analysis of an investment opportunity and a series of associated cashflows.

As a module, it currently provides straightforward and easy to understand implementations of the Net Present Value (NPV), Internal Rate of Return (IRR), and Payback Period functions.

As a script, it provides a simple command line interface which integrates the above functions into a concise analysis of the investment opportunity.

    usage: invest discount_rate [cashflow0, cashflow1, ..., cashflowN]
        where 
            discount_rate is the rate used to discount future cashflows 
                             to their present values
            cashflow0 is the investment (always a negative value)
            cashflow1 .. cashflowN values can be positive (net inflows) 
                                                 or
                                                 negative (net outflows)

Here is an example of actual usage and output:

$ ./invest 0.05 -10000 6000 6000 6000
----------------------------------------------------------------------
year      0        1      2      3     
cashflow  -10,000  6,000  6,000  6,000 

Discount Rate: 5.0%

Payback: 1.67 years
    IRR: 36.31%
    NPV: 6339.49

==> Approve Investment of 10,000
----------------------------------------------------------------------

Note: A check of the output of the Microsoft Excel NPV function against that of the function implemented here reveals a curious discrepancy/bug in the way Excel calculates its NPV. For further details see: http://www.tvmcalcs.com/blog/comments/the_npv_function_doesnt_calculate_net_present_value/

Furthermore, the method used to calculate the IRR is rough to say the least and fails at fewer than 3 entries. Please use the secant method along the lines of the following haskell code from (http://www.haskell.org/haskellwiki/Haskell_Quiz/Internal_Rate_of_Return/Solution_Dolio) for greater accuracy.

secant :: (Double -> Double) -> Double -> Double
secant f delta = fst $ until err update (0,1)
  where
    update (x,y) = (x - (x - y) * f x / (f x - f y), x)
    err (x,y) = abs (x - y) < delta

npv :: Double -> [Double] -> Double
npv i = sum . zipWith (\t c -> c / (1 + i)**t) [0..]

irr :: [Double] -> Double
irr cashflows = secant (`npv` cashflows) (0.1**4)
Python, 145 lines
  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
#!/usr/bin/env python
'''
A set of functions for quick financial analysis of an investment
opportunity and a series of projected cashflows.

For further details and pros/cons of each function please refer
to the respective wikipedia page:

    payback_period 
        http://en.wikipedia.org/wiki/Payback_period
    
    net present value 
        http://en.wikipedia.org/wiki/Net_present_value
        
    internal rate of return
        http://en.wikipedia.org/wiki/Internal_rate_of_return
'''

import sys, locale

def payback_of_investment(investment, cashflows):
    """The payback period refers to the length of time required 
       for an investment to have its initial cost recovered.
       
       >>> payback_of_investment(200.0, [60.0, 60.0, 70.0, 90.0])
       3.1111111111111112
    """
    total, years, cumulative = 0.0, 0, []
    if not cashflows or (sum(cashflows) < investment):
        raise Exception("insufficient cashflows")
    for cashflow in cashflows:
        total += cashflow
        if total < investment:
            years += 1
        cumulative.append(total)
    A = years
    B = investment - cumulative[years-1]
    C = cumulative[years] - cumulative[years-1]
    return A + (B/C)

def payback(cashflows):
    """The payback period refers to the length of time required
       for an investment to have its initial cost recovered.
       
       (This version accepts a list of cashflows)
       
       >>> payback([-200.0, 60.0, 60.0, 70.0, 90.0])
       3.1111111111111112
    """
    investment, cashflows = cashflows[0], cashflows[1:]
    if investment < 0 : investment = -investment
    return payback_of_investment(investment, cashflows)

def npv(rate, cashflows):
    """The total present value of a time series of cash flows.
    
        >>> npv(0.1, [-100.0, 60.0, 60.0, 60.0])
        49.211119459053322
    """
    total = 0.0
    for i, cashflow in enumerate(cashflows):
        total += cashflow / (1 + rate)**i
    return total

def irr(cashflows, iterations=100):
    """The IRR or Internal Rate of Return is the annualized effective 
       compounded return rate which can be earned on the invested 
       capital, i.e., the yield on the investment.
       
       >>> irr([-100.0, 60.0, 60.0, 60.0])
       0.36309653947517645

    """
    rate = 1.0
    investment = cashflows[0]
    for i in range(1, iterations+1):
        rate *= (1 - npv(rate, cashflows) / investment)
    return rate


# enable placing commas in thousands
locale.setlocale(locale.LC_ALL, "")
# convenience function to place commas in thousands
format = lambda x: locale.format('%d', x, True)

def investment_analysis(discount_rate, cashflows):
    """Provides summary investment analysis on a list of cashflows
       and a discount_rate.
       
       Assumes that the first element of the list (i.e. at period 0) 
       is the initial investment with a negative float value.
    """
    _npv = npv(discount_rate, cashflows)
    ts = [('year', 'cashflow')] + [(str(x), format(y)) for (x,y) in zip(
           range(len(cashflows)), cashflows)]
    print "-" * 70
    for y,c in ts:
        print y + (len(c) - len(y) + 1)*' ',
    print
    for y,c in ts:
        print c + ' ',
    print
    print
    print "Discount Rate: %.1f%%" % (discount_rate * 100)
    print
    print "Payback: %.2f years" % payback(cashflows)
    print "    IRR: %.2f%%" % (irr(cashflows) * 100)
    print "    NPV: %s" % format(_npv)
    print 
    print "==> %s investment of %s" % (
        ("Approve" if _npv > 0 else "Do Not Approve"), format(-cashflows[0]))
    print "-" * 70

def main(inputs):
    """commandline entry point
    """
    
    usage = '''Provides analysis of an investment and a series of cashflows.
    
    usage: invest discount_rate [cashflow0, cashflow1, ..., cashflowN]
        where 
            discount_rate is the rate used to discount future cashflows 
                             to their present values
            cashflow0 is the investment amount (always a negative value)
            cashflow1 .. cashflowN values can be positive (net inflows)
                                                 or
                                                 negative (net outflows)
    for example:
        invest 0.05 -10000 6000 6000 6000
    '''
    
    try:
        rate, cashflows = inputs[0], inputs[1:]
        investment_analysis(float(rate), [float(c) for c in cashflows])
    except IndexError:
        print usage
        sys.exit()

if __name__ == '__main__':
    debug = False
    if debug:
        import doctest
        doctest.testmod()
    else:
        main(sys.argv[1:])