#On the name of ALLAH #Author : Fouad Teniou #Date : 07/03/09 #version :2.6.1 """ New in python 2.6 namedtuple function is used in my program IRR-Versus-NPV with a PresentValue as a typename, and discount rates r_1, r_2 and number of periods until payments n as fieldnames (PresentValue('r_1','r_2','n')) IRR-Versus-NPV program provide an NPV and IRR (linear interpolation) calculations by entering the outflows/inflows values for a project with 2 different rates r_1 and r_2, and only an NPV if r_2 is equal to zero The program also returns any value or a series of values from the Present Value Annuity Tables """ import itertools import operator import collections import math as m class MinusSignError(ArithmeticError): """ user attempt an operation on negative number""" pass class PresentValue(collections.namedtuple('PresentValue', 'r_1,r_2,n')): """PresentValue a subclass of namedtuple Python class with two rates values (r_1,r_2) and a period number n """ #set __slots__ to an empty tuple keep memory requirements low __slots__ = () @property def DF(self): """Compute the discount factor of two values""" if self.r_1<0 or self.r_2 < 0 or self.n<0: raise MinusSignError,\ "\n<Rates values and period number should be positive " try: discount_factor_1 = "%2.3f" % m.pow((1+self.r_1),-self.n) discount_factor_2 = "%2.3f" % m.pow((1+self.r_2),-self.n) return (discount_factor_1,discount_factor_2) #DF raises Negative number error except MinusSignError,exception: print exception @property def AF(self): """Compute the annuity factor of two values""" if self.r_1<0 or self.r_2 < 0 or self.n<0: raise MinusSignError,\ "\n<Rates values and period number should be positive" try: annuity_factor_1 = "%2.3f" %((1-((m.pow((1+self.r_1),-self.n))))/self.r_1) annuity_factor_2 = "%2.3f" %((1-((m.pow((1+self.r_2),-self.n))))/self.r_2) return (annuity_factor_1,annuity_factor_2) #AF raises Zero division number error except ZeroDivisionError: print "\n<Please choose a rate value greater than zero" #AF raises Negative number error except MinusSignError,exception: print exception def npvTable(self,*args): """Compute the NPV and IRR values of a project with two different rates""" try: #You need at least one rate to compute an NPV assert self.r_1 !=0,"The first rate should not be equal to zero " res_1 = [] res_2 = [] item_1 =[] item_2 =[] count_1 = -1 for arg in args: count_1 +=1 #outflows/inflows starting at year 0 pv_set = PresentValue(r_1=self.r_1,r_2=self.r_2,n = count_1) res_1.append(pv_set.DF[0]) # Trigger the Discount factor at rate r_1 and append the res_1 res_2.append(pv_set.DF[1]) # Trigger the Discount factor at rate r_2 and append the res_2 print "\n years \tCash flows \t DF at %s\t PV" %(str(int(self.r_1*100))+'%') count_2 = -1 for (x_1,y) in (itertools.izip(res_1,args)): #for loop return PV set item_1 item_1.append((float(x_1)*y)) count_2 +=1 print "\n %s\t%s\t\t %s\t\t%s" %(count_2,y,x_1,int(float(x_1)*y)) npv_1 =(reduce(operator.add,item_1)) #Compute the npv_1 total print '\n\t\t\t\t NPV = %s\n' % int(npv_1) if self.r_2 == 0: #user attempt only one NPV calculation print "<No NPV calculation %s=%s" % pv_set._asdict().items()[1] else: print "\n years \tCash flows \t DF at %s\t PV" %(str(int(self.r_2*100))+'%') count_3 = -1 for (x_2,y) in (itertools.izip(res_2,args)): #for loop return PV set item_2 item_2.append((float(x_2)*y)) count_3 +=1 print "\n %s\t%s\t\t %s\t\t%s" %(count_3,y,x_2,int(float(x_2)*y)) npv_2 = (reduce(operator.add,item_2)) #Compute the npv_2 total print '\n\t\t\t\t NPV = %s\n ' % int(npv_2) #The IRR computation will depend of the rates and npvs (higher and lower values) if self.r_1 > self.r_2: print "<The IRR = %2.1f" % ((self.r_2+(npv_2/(npv_2-npv_1))*(self.r_1-self.r_2))*100)+' %\n' else: print "<The IRR = %2.1f" % ((self.r_1+(npv_1/(npv_1-npv_2))*(self.r_2-self.r_1))*100)+' %\n' print "<The project will be recommended on finanacial grounds, while " print "ignoring risk, if the IRR is greater than the target rate of " print "return, otherwise the project will be rejected.\n" #npvTable raises Negative number error except MinusSignError,exception: print exception if __name__ == "__main__": p = PresentValue('r_1','r_2','n') p = p._replace(r_1=0.27,r_2 = 0.13)# n is equal to the numbers of outflows/inflows p.npvTable(-70000,48000,24000,22000,13000,33000,15000,17000) s = p._replace(r_1=0.07,r_2 =0.05) #You can set r_2 to zero to get only an NPV but not r_1 since you need at least one rate for the NPV print " years\t\tDF at%2.0f%s\tAF at%2.0f%s\t DF at%2.0f%s\t AF at%2.0f%s \n" %\ (s.r_1*100,'%',s.r_1*100,'%',s.r_2*100,'%',s.r_2*100,'%') for i in range(1,11): # you can do it individualy by setting r_1,r_2 and n (eg: PresentValue(r_1 = 0.17,r_2 = 0.07,n=7) s = p._replace(r_1=0.07,r_2 =0.05,n=i) print " %s\t\t%s\t\t %s\t\t %s\t\t %s" % \ (i,s.DF[0],s.AF[0],s.DF[1],s.AF[1]) # Another method to display the npv,for the same outflows/inflows values # using different sets of rates and (n = to the number of outflows/inflows ) # It will save you time to write the same inflows/outflows every time you # alter the rates # below is just an example and you can extend your set of values as you wish. a = [0.07,0.02,'n'] b = [0.04,0.05,'n'] c = [0.04,0.07,'n'] for item in a,b,c: p = p._make(item) p.npvTable(-70000,48000,24000,22000,13000,37000) #Another way to retrieve Annuity factors and Discount factors #individually or in sets of (r_1,r_2) g = [0.04,0.07,17] p = p._make(g) print p.DF print p.AF print p.DF[0] print p.DF[1] print p.AF[0] print p.AF[1] ######################################################################################### # c:\Python26>python "C:Fouad Teniou\Documents\IRRvNPV.py" # years Cash flows DF at 27% PV # 0 -70000 1.000 -70000 # 1 28000 0.787 22036 # 2 24000 0.620 14880 # 3 22500 0.488 10980 # 4 13000 0.384 4992 # 5 33000 0.303 9999 # 6 15000 0.238 3570 # 7 17000 0.188 3196 # NPV = -347 # years Cash flows DF at 13% PV # 0 -70000 1.000 -70000 # 1 28000 0.885 24780 # 2 24000 0.783 18792 # 3 22500 0.693 15592 # 4 13000 0.613 7969 # 5 33000 0.543 17919 # 6 15000 0.480 7200 # 7 17000 0.425 7225 # NPV = 29477 #<The IRR = 26.8 % #<The project will be recommended on finanacial grounds, while #ignoring risk, if the IRR is greater than the target rate of #return, otherwise the project will be rejected. # years DF at 7% AF at 7% DF at 5% AF at 5% # 1 0.935 0.935 0.952 0.952 # 2 0.873 1.808 0.907 1.859 # 3 0.816 2.624 0.864 2.723 # 4 0.763 3.387 0.823 3.546 # 5 0.713 4.100 0.784 4.329 # 6 0.666 4.767 0.746 5.076 # 7 0.623 5.389 0.711 5.786 # 8 0.582 5.971 0.677 6.463 # 9 0.544 6.515 0.645 7.108 # 10 0.508 7.024 0.614 7.722 #c:\Python26> ###########################################################################################