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, 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 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)) 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, 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:]) Created by Alia Khouri on Wed, 11 Mar 2009 (MIT)