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)
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:])
|