``` 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 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166``` ```from datetime import date from math import exp, log # Program by Jim Shapiro, Ph.D. # 2021-07-29 # Boulder, CO 80301-5013 # # If you copy this file, please include this header. If you modify the code, please # note the modifications. Iterations, Epsilon = 20, 1.0e-5 # Requires amounts and times already converted from dates class Transactions: def __init__(self): self.amounts, self.times = [], [] def add(self, amount, time): self.amounts.append(amount) self.times.append(time) # Requires amounts and dates. Dates then get converted to times, i.e. days class Date_Transactions: def __init__(self): self.amounts, self.times, self.the_dates = [], [], [] def add_amount_and_date(self, amount, a_date): self.amounts.append(amount) self.the_dates.append(a_date) # dates_2_times requires a second parameter, the separator in the dates. def dates_2_times(self, sep): ds = [] for a_date in self.the_dates: y, m, d =[int(x) for x in a_date.split(sep)] ds.append(date(y, m, d)) for a_date in ds: # Note that self.times[0] is always 0. We calculate it # anyway for no good reason. self.times.append((a_date - ds[0]).days / 365.25) # print('{0:.2f}'.format(self.times[-1])) # Uses a Date_Transactions class which needs conversion to times # irrcc requires a second parameter, the separator in the dates. def irrcc(date_transactions, sep): global Iterations, Epsilon have_pos, have_neg = False, False # Dates haved been entered. Convert to times, i.e., days after # first date. date_transactions.dates_2_times(sep) for amount in date_transactions.amounts: if amount > 0.0: have_pos = True if have_neg: break elif amount < 0.0: have_neg = True if have_pos: break if have_neg and have_pos: u, converged = 0.0, False for i in range(Iterations): pos, d_pos, neg, d_neg = 0.0, 0.0, 0.0, 0.0 # dd_pos, dd_neg = 0.0, 0.0 # print(i) for j in range(len(date_transactions.amounts)): an_amount, a_time = date_transactions.amounts[j], date_transactions.times[j] tmp = an_amount * exp(u * a_time) if an_amount > 0.0: pos += tmp d_pos += tmp * a_time # dd_pos += tmp * a_time * a_time else: neg -= tmp; d_neg -= tmp * a_time # dd_neg -= tmp * a_time * a_time # Haley's 2nd order Newton's method f = log(neg / pos) fp = (d_neg / neg) - (d_pos / pos) # tmp = (neg * dd_neg - d_neg * d_neg) / neg / neg # fpp = tmp - ((pos * dd_pos - d_pos * d_pos) / pos /pos) # h_inv = -fp / f + fpp / (2 * fp) # delta = -1 / h_inv # First order Newton's method # delta = log(neg / pos) / (d_neg / neg - d_pos / pos) delta = f / fp u -= delta if abs(delta) < Epsilon: converged = True break if converged: result = -u else: result = 'No convergence' else: result = 'Bad Data!' return result # Uses a Transactions class which already has times def jns_irr(transactions): global Iterations, Epsilon have_pos, have_neg = False, False for amount in transactions.amounts: if amount > 0.0: have_pos = True if have_neg: break elif amount < 0.0: have_neg = True if have_pos: break if have_neg and have_pos: u, converged = 0.0, False for i in range(Iterations): pos, d_pos, neg, d_neg = 0.0, 0.0, 0.0, 0.0 # print(i) for j in range(len(transactions.amounts)): an_amount, a_time = transactions.amounts[j], transactions.times[j] tmp = an_amount * exp(u * a_time) if an_amount > 0.0: pos += tmp d_pos += tmp * a_time else: neg -= tmp; d_neg -= tmp * a_time delta = log(neg / pos) / (d_neg / neg - d_pos / pos) u -= delta if abs(delta) < Epsilon: converged = True break if converged: result = -u else: result = 'No convergence' else: result = 'Bad Data!' return result if __name__ == "__main__": amounts = [-1000.00, 500.00, -2000.00, -2000.00, 1500.00, 4000.00] dates = ['2016-03-16', '2017-09-26', '2018-01-15', '2020-04-05', '2019-05-1', '2021-01-01'] dts = Date_Transactions() for i in range(len(amounts)): dts.add_amount_and_date(amounts[i], dates[i]) # irrcc requires a second parameter, the separator in the dates. irr = irrcc(dts, '-') print('The internal rate of return with continuous compounding is {0:.2f}%.'.format(100 * irr)) ```