Shago commited on
Commit
79cb44a
·
verified ·
1 Parent(s): b9fad49

Upload engineconomics2.py

Browse files
Files changed (1) hide show
  1. tools/engineconomics2.py +1829 -0
tools/engineconomics2.py ADDED
@@ -0,0 +1,1829 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pandas import DataFrame
2
+ from numpy import exp, log
3
+ from plotly.express import bar
4
+ from plotly.express import area
5
+ from scipy.optimize import root
6
+
7
+ class factor(object):
8
+ '''
9
+ Engineering economics factors
10
+ '''
11
+
12
+ def pgivenfsp(self, i:float, n:int)->float:
13
+ '''
14
+ Single-payment present worth factor. Find Present Worth (P) given
15
+ a Future worth (F).
16
+
17
+ Input arguments:
18
+ i: Interest rate per (uniform) period.
19
+ n: Number of uniform interest periods.
20
+ '''
21
+ self.i = i
22
+ self.n = n
23
+ return 1 /((1 + self.i)**(self.n))
24
+
25
+ def fgivenpsp(self, i:float, n:int)->float:
26
+ '''
27
+ Single-payment compound amount factor. Find Future Worth (F) given
28
+ a Present Worth (P).
29
+
30
+ Input arguments:
31
+ i: Interest rate per (uniform) period.
32
+ n: Number of uniform interest periods.
33
+ '''
34
+ self.i = i
35
+ self.n = n
36
+ return (1 + self.i)**self.n
37
+
38
+ def pgivena(self, i:float, n:int)->float:
39
+ '''
40
+ Uniform series present worth. Find Present Worth (P) given
41
+ a Uniform Series (A) of cash flows.
42
+
43
+ Input arguments:
44
+ i: Interest rate per (uniform) period.
45
+ n: Number of uniform interest periods.
46
+ '''
47
+ self.i = i
48
+ self.n = n
49
+ return ((1 + self.i)**(self.n) - 1)/(self.i*(1+self.i)**self.n)
50
+
51
+ def agivenp (self, i:float, n:int)->float:
52
+ '''
53
+ Capital recovery. Find a Uniform Series (A) given a Present
54
+ Worth(P).
55
+
56
+ Input arguments:
57
+ i: Interest rate per (uniform) period.
58
+ n: Number of uniform interest periods.
59
+ '''
60
+ self.i = i
61
+ self.n = n
62
+ return (self.i*(1+self.i)**self.n)/((1 + self.i)**(self.n) - 1)
63
+
64
+ def fgivena (self, i, n):
65
+ '''
66
+ Uniform series compound amount. Find a Future Worth (F) given
67
+ a Uniform Serie (A).
68
+
69
+ Input arguments:
70
+ i: Interest rate per (uniform) period.
71
+ n: Number of uniform interest periods.
72
+ '''
73
+ self.i = i
74
+ self.n = n
75
+ return ((1+self.i)**self.n - 1)/self.i
76
+
77
+ def agivenf (self, i:float, n:int)->float:
78
+ '''
79
+ Sinking fund. Find a Uniform Serie (A) given a Future
80
+ Worth (F).
81
+
82
+ Input arguments:
83
+ i: Interest rate per (uniform) period.
84
+ n: Number of uniform interest periods.
85
+ '''
86
+ self.i = i
87
+ self.n = n
88
+ return self.i/((1+self.i)**self.n - 1)
89
+
90
+ def pgivenag (self, i:float, n:int)->float:
91
+ '''
92
+ Arithmetic Gradient series present worth. Find a Present
93
+ Worth (P) given an Arithmetic Gradient Serie (G).
94
+
95
+ Input arguments:
96
+ i: Interest rate per (uniform) period.
97
+ n: Number of uniform interest periods.
98
+ '''
99
+ self.i = i
100
+ self.n = n
101
+ return(((1+self.i)**self.n) - self.i*self.n - 1)/((self.i**2) * (((1+self.i)**self.n)))
102
+
103
+ def fgivenag(self, i:float, n:int)->float:
104
+ '''
105
+ Arithmetic gradient series future worth. Find a future Worth (F) given
106
+ an Arithmetic Gradient Serie (G).
107
+
108
+ Input arguments:
109
+ i: Interest rate per (uniform) period.
110
+ n: Number of uniform interest periods.
111
+ '''
112
+ self.i = i
113
+ self.n = n
114
+ return (1/self.i)*((((1+self.i)**n-1)/self.i)-n)
115
+
116
+
117
+ def agivenag(self, i:float, n:int)->float:
118
+ '''
119
+ Arithmetic gradient to equal payment series. Find a Unifom Serie (A) given
120
+ an Arithmetic Gradient Serie (G).
121
+
122
+ Input arguments:
123
+ i: Interest rate per (uniform) period.
124
+ n: Number of uniform interest periods
125
+ '''
126
+ self.i = i
127
+ self.n = n
128
+ return (1 / self.i) - (self.n /((1 + self.i)**self.n - 1))
129
+
130
+ def pgivenga1(self, i:float, n:int, g:float)->float:
131
+ '''
132
+ Geometric gradient series present worth. Find a Present
133
+ Worth (P) given an Geometric Gradient Serie (G).
134
+
135
+ Input arguments:
136
+ i: Interest rate per (uniform) period.
137
+ n: Number of uniform interest periods.
138
+ g: Geometric gradient or constant percentage or constant growth.
139
+ '''
140
+ self.i = i
141
+ self.n = n
142
+ self.g = g
143
+
144
+ if (self.g != self.i):
145
+ return (1 - (((1 + self.g)/(1 + self.i))**self.n))/(self.i - self.g)
146
+ else:
147
+ return self.n / (1+self.i)
148
+
149
+
150
+
151
+ class time_value(object):
152
+ """
153
+ Time Value Functions
154
+ """
155
+
156
+ def cfv(self, CF: float, F: str, i: float, n: float, g: float=None) -> float:
157
+ '''
158
+ input arguments:
159
+ CF: Assessed cash flow
160
+ F: Factor types =[
161
+ "P/F": Find P Present Worth given F Future worth, interest i and number of periods n.
162
+ "F/P": Find F Future worth given P Present Worth, interest i and number of periods n.,
163
+ "P/A": Find P Present Worth given A Equal payment series, interest i and number of periods n.
164
+ "A/P": Find A Equal payment series given P Present Worth, interest i and number of periods n.
165
+ "F/A": Find F Future worth given A Equal payment series, interest i and number of periods n.
166
+ "A/F": Find A Equal payment series given F Future worth, interest i and number of periods n.
167
+ "P/G": Find P Present Worth given G Arithmetic Gradient, interest i and number of periods n.
168
+ "P/g": Find P Present Worth given g Geometric Gradient, A1 First payment, interest i and number of periods n.
169
+ ]
170
+ i: Efective interest rate
171
+ n: Term
172
+ g: Geometric Gradient
173
+ '''
174
+ cf_asked = {
175
+ "P/F": "PV",
176
+ "F/P": "FV",
177
+ "P/A": "PV",
178
+ "A/P": "A",
179
+ "F/A": "FV",
180
+ "A/F": "A",
181
+ "P/G": "PV",
182
+ "F/G": "FV",
183
+ "A/G": "A",
184
+ "P/g": "PV"
185
+ }
186
+
187
+ cf_given = {
188
+ "P/F": "FV",
189
+ "F/P": "PV",
190
+ "P/A": "A",
191
+ "A/P": "PV",
192
+ "F/A": "A",
193
+ "A/F": "FV",
194
+ "P/G": "G",
195
+ "F/G": "G",
196
+ "A/G": "G",
197
+ "P/g": "A1"
198
+ }
199
+
200
+ self.CF = CF
201
+ self.F = F
202
+ self.i = i
203
+ self.n = n
204
+ self.g = g
205
+
206
+
207
+ self.values = {}
208
+ self.values[cf_given[self.F]] = self.CF
209
+ self.values['Factor'] = self.F
210
+ self.values['i'] = self.i
211
+ self.values['n'] = self.n
212
+ if self.g is not None:
213
+ self.values['g'] = self.g
214
+
215
+
216
+
217
+ factor_list = ["P/F", "F/P", "P/A", "A/P", "F/A", "A/F", "P/G", "F/G", "A/G","P/g"]
218
+
219
+
220
+ try:
221
+
222
+ if self.F in factor_list:
223
+ if self.F == "P/F":
224
+ value = self.CF * factor.pgivenfsp(self, self.i, self.n)
225
+ elif self.F == "F/P":
226
+ value = self.CF * factor.fgivenpsp(self,self.i, self.n)
227
+ elif self.F == "P/A":
228
+ value = self.CF * factor.pgivena(self, self.i, self.n)
229
+ elif self.F == "A/P":
230
+ value = self.CF * factor.agivenp(self, self.i, self.n)
231
+ elif self.F == "F/A":
232
+ value = self.CF * factor.fgivena(self, self.i, self.n)
233
+ elif self.F == "A/F":
234
+ value = self.CF * factor.agivenf(self, self.i, self.n)
235
+ elif self.F == "P/G":
236
+ value = self.CF * factor.pgivenag(self, self.i, self.n)
237
+ elif self.F == 'F/G':
238
+ value = self.CF * factor.fgivenag(self, self.i, self.n)
239
+ elif self.F == 'A/G':
240
+ value = self.CF * factor.agivenag(self, self.i, self.n)
241
+ elif self.F == "P/g":
242
+ if g is None:
243
+ print ('Input geometric gradient')
244
+ else:
245
+ value = self.CF * factor.pgivenga1(self, self.i, self.n, self.g)
246
+
247
+ self.values[cf_asked[self.F]] = value
248
+ return self.values
249
+
250
+ except:
251
+ raise Exception("Check arguments")
252
+
253
+
254
+
255
+ def pvp(self, a:float, i:float)->float:
256
+ '''
257
+ Perpetual present value. Find Present Worth (P) given
258
+ a Perpetual Uniform Serie (A).
259
+
260
+ Input arguments:
261
+ a: Perpetual Uniform Serie.
262
+ i: Interest rate per (uniform) period.
263
+ '''
264
+
265
+ self.a = a
266
+ self.i = i
267
+ return self.a/self.i
268
+
269
+
270
+ def _getcf(self, period, cf_tuples_list):
271
+ '''
272
+ This function is used to filter the tuples (p, cf) of a list
273
+ that share the same first element (p) of the tuple and
274
+ calculate the sum of the cash flows (cf) corresponding to
275
+ the filtered element (p).
276
+
277
+ Input arguments:
278
+ period: Period to filter
279
+ cf_tuples_list: List of tuples (p, cf)
280
+ '''
281
+
282
+ self.period = period
283
+ self.cf_tuples_list = cf_tuples_list
284
+
285
+ cf_tuples = list(filter(lambda x:self.period in x, self.cf_tuples_list))
286
+
287
+ if cf_tuples:
288
+ pcf = 0
289
+ for p, tcf in cf_tuples:
290
+ pcf += tcf
291
+ return (p, pcf)
292
+ else:
293
+ return (self.period, 0)
294
+
295
+ def npv(self, period_list:list, cf_list:list, i:float)->float:
296
+ '''
297
+ This function is used to estimate the net present value
298
+ from a list of cash flows, a list of the corresponding
299
+ periods to calculate the present value and an effective
300
+ interest rate.
301
+
302
+ Input arguments:
303
+ period_list: Period list
304
+ cf_list: Cash flow list
305
+ i: Effective interest rate
306
+ '''
307
+ self.period_list = period_list
308
+ self.cf_list = cf_list
309
+ self.i = i
310
+
311
+ p_len = len(self.period_list)
312
+ cf_len = len(self.cf_list)
313
+ assert p_len == cf_len, f"The length of the period list ({p_len}) must be equal to the length of the cash flow list ({cf_len})."
314
+ #assert i > 0, f"Interest rate {i} must be greater than 0"
315
+
316
+ n_max=max(self.period_list) + 1
317
+ CFL = list(zip(self.period_list, self.cf_list))
318
+
319
+ ncf = []
320
+
321
+ for p in range (n_max):
322
+ self.p = p
323
+ self.cf_tuples_list=CFL
324
+ cf_tuples = time_value._getcf(self, self.p, self.cf_tuples_list)
325
+ ncf.append(cf_tuples)
326
+
327
+ dcf = [x[1] / (1+i)**x[0] for x in ncf ]
328
+ npv_ = sum(dcf)
329
+
330
+ return npv_
331
+
332
+
333
+ def npviv(self, cf_list:list, iv:list)->float:
334
+ '''
335
+ This function estimates the net present value for several cash
336
+ flows with different effective rates for each period. In this
337
+ case there should be only one cash flow and one interest rate
338
+ for each period. For the calculation to be consistent, the spacing
339
+ between periods must be uniform (monthly, bimonthly, annually, etc.)
340
+ and the effective interest rates for each period must correspond
341
+ to the same periodicity.
342
+
343
+ Input arguments:
344
+ cf_list
345
+ iv
346
+ '''
347
+
348
+ len_cf_list = len(cf_list)
349
+ len_iv = len(iv)
350
+
351
+
352
+ assert len_cf_list == len_iv, f"The cash flow list {(len_cf_list)} and interest rates list{(len_iv)} has not the same number of elements"
353
+
354
+
355
+
356
+ self.period_list = list(range(len_iv))
357
+ self.cf_list = cf_list
358
+ self.iv = iv
359
+
360
+ n_max=max(self.period_list) + 1
361
+
362
+ CFL = list(zip(self.period_list, self.cf_list))
363
+
364
+
365
+ i = []
366
+
367
+ i_comp =1
368
+
369
+ for r in self.iv:
370
+ i_comp *= (1+r)
371
+
372
+ i.append(i_comp)
373
+
374
+
375
+ ncf = []
376
+
377
+ for p in range(n_max):
378
+ self.p = p
379
+ self.cf_tuples_list=CFL
380
+ _, cf = time_value._getcf(self, self.p,self.cf_tuples_list)
381
+ ncf.append((p, cf, i[p]))
382
+
383
+ dcf = [x[1] / x[2] for x in ncf ]
384
+ npv_ = sum(dcf)
385
+
386
+ self.period_list[0] = self.period_list[0] + npv_
387
+
388
+ return npv_
389
+
390
+
391
+
392
+ def vpn_terminal_value(self, cf_n: float, r:float, g:float, n:float)->float:
393
+ '''
394
+ This function returns two values. The first is the terminal value in period n.
395
+ The second returns the present value of this terminal value in period 0.
396
+
397
+ Input arguments:
398
+ cf_n : Cash flow at the end of period n
399
+ r: Discount cash flow rate at year n
400
+ g: Growth rate
401
+ n: Discount valuation period
402
+ '''
403
+
404
+ assert r != g, f"The interest rate {r} must be diferent from growth {g}"
405
+
406
+ self.cf_n = cf_n
407
+ self.r = r
408
+ self.g = g
409
+ self.n = n
410
+ self.tval = (self.cf_n * ( 1 + self.g)) / (self.r - self.g)
411
+ self.tvpv = self.tval * factor.pgivenfsp(self, self.r, self.n)
412
+
413
+ return self.tval, self.tvpv
414
+
415
+
416
+ def vpn_terminal_value_variable_rates(self, cf_n:float, rate_list:list, g:float)->float:
417
+ '''
418
+ This function returns two values. The first is the terminal value in period n.
419
+ The second returns the present value of this terminal value in period 0.
420
+
421
+ Input arguments:
422
+ cf_n : Cash flow at the end of period n
423
+ r: Discount cash flow rate at year n
424
+ g: Growth rate
425
+ '''
426
+
427
+ len_r = len(rate_list)
428
+ r = rate_list[len_r-1]
429
+
430
+ assert r != g, f"The interest rate {r} must be diferent from growth {g}"
431
+
432
+ self.r = r
433
+ self.cf_n = cf_n
434
+ self.r_list = rate_list
435
+ self.g = g
436
+ self.tval = (self.cf_n * ( 1 + self.g)) / (self.r - self.g)
437
+ cf_list = [0] * len_r
438
+ cf_list[len_r-1] = self.tval
439
+ self.cf_list = cf_list
440
+ self.tvpv = time_value.npviv(self, self.cf_list, self.r_list)
441
+
442
+ return self.tval, self.tvpv
443
+
444
+ class time_value_table(object):
445
+ """
446
+ Cash Flow Tables
447
+ """
448
+ def cfdataframe(self, cf_dic:dict):
449
+ '''
450
+ Passes the result of a dictionary of economic engineering formulas to a pandas dataframe.
451
+ '''
452
+
453
+ self.cf_dic= cf_dic
454
+
455
+ if self.cf_dic == None:
456
+ return None
457
+
458
+ if (self.cf_dic['Factor'] == 'P/F') or (self.cf_dic['Factor'] == 'F/P'):
459
+
460
+ pv = -round(self.cf_dic['PV'], 4)
461
+ fv = round(self.cf_dic['FV'], 4)
462
+
463
+ n = self.cf_dic['n']
464
+ x_data = range(n+1)
465
+ y_o_data = [pv] + [0.] * (n)
466
+ y_i_data = [0.] * (n) + [fv]
467
+
468
+
469
+ return DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Income", "Outcome"])
470
+
471
+
472
+ if (self.cf_dic['Factor'] == 'P/A') or (self.cf_dic['Factor'] == 'A/P'):
473
+
474
+ pv = -round(self.cf_dic['PV'], 4)
475
+ a = round(self.cf_dic['A'], 4)
476
+ n = cf_dic['n']
477
+ x_data = range(n+1)
478
+ y_i_data = [0.] + [a] * (n)
479
+ y_o_data = [0.] * (n+1)
480
+ y_o_data[0] = pv
481
+ return DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Income", "Outcome"])
482
+
483
+
484
+ if (self.cf_dic['Factor'] == 'F/A') or (self.cf_dic['Factor'] == 'A/F'):
485
+
486
+ fv = round(self.cf_dic['FV'], 4)
487
+ a = -round(self.cf_dic['A'], 4)
488
+ n = cf_dic['n']
489
+ x_data = range(n+1)
490
+ y_o_data = [0.] + [a] * (n)
491
+ y_i_data = [0.] * (n+1)
492
+ y_i_data[-1] = fv
493
+ return DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Income", "Outcome"])
494
+
495
+ if (self.cf_dic['Factor'] == 'P/G'):
496
+
497
+ pv = -round(self.cf_dic['PV'], 4)
498
+ ag = round(self.cf_dic['G'], 4)
499
+ n = cf_dic['n']
500
+ x_data = range(n+1)
501
+ y_ag_data = [0.] + [k * ag for k in range(n)]
502
+ y_o_data = [0.] * (n+1)
503
+ y_o_data[0] = pv
504
+ return DataFrame(list(zip(x_data, y_ag_data, y_o_data)), columns=["Period", "Gradient Income", "Outcome"])
505
+
506
+ if (self.cf_dic['Factor'] == 'A/G'):
507
+
508
+ a = -round(self.cf_dic['A'], 4)
509
+ ag = round(self.cf_dic['G'], 4)
510
+ n = cf_dic['n']
511
+ x_data = range(n+1)
512
+ y_ag_data = [0.] + [k * ag for k in range(n)]
513
+ y_o_data = [0.] + [a] * (n)
514
+ return DataFrame(list(zip(x_data, y_ag_data, y_o_data)), columns=["Period", "Gradient Income", "Outcome"])
515
+
516
+
517
+ if (self.cf_dic['Factor'] == 'F/G'):
518
+
519
+ fv = round(self.cf_dic['FV'], 4)
520
+ ag = -round(self.cf_dic['G'], 4)
521
+ n = cf_dic['n']
522
+ x_data = range(n+1)
523
+ y_ag_data = [0.] + [k * ag for k in range(n)]
524
+ y_o_data = [0.] * (n+1)
525
+ y_o_data[-1] = fv
526
+ return DataFrame(list(zip(x_data, y_ag_data, y_o_data)), columns=["Period", "Gradient Outcome", "Income"])
527
+
528
+
529
+
530
+ if (self.cf_dic['Factor'] == 'P/g'):
531
+
532
+ pv = -round(self.cf_dic['PV'], 4)
533
+ gg = round(self.cf_dic['g'], 4)
534
+ ba = round(self.cf_dic['A1'], 4)
535
+ n = cf_dic['n']
536
+ x_data = range(n+1)
537
+
538
+ y_i_data = [0] + [ba * (1 + gg)**k for k in range(n)]
539
+ y_o_data = [0.] * (n+1)
540
+ y_o_data[0] = pv
541
+ return DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Gradient Income", "Outcome"])
542
+
543
+ def npvtable(self, period_list:list, cf_list:list, i:float):
544
+ """
545
+ Creates a pandas dataframe for a cash flow of a given length
546
+
547
+ input arguments:
548
+ period_list: Term cash flow list
549
+ cf_list: Cash flow list to evalute
550
+ i: cash flow interest rate
551
+
552
+ """
553
+
554
+ self.period_list = period_list
555
+ self.cf_list = cf_list
556
+ self.i = i
557
+
558
+ p_len = len(self.period_list)
559
+ cf_len = len(self.cf_list)
560
+ assert p_len == cf_len, f"The length of the period list must be equal to the length of the cash flow list ({cf_len})."
561
+ assert i > 0, f"Interest rate {i} must be greater than 0"
562
+
563
+ n_max=max(self.period_list) + 1
564
+ CFL = list(zip(self.period_list, self.cf_list))
565
+
566
+ ncfl = []
567
+
568
+
569
+ for p in range(n_max):
570
+ cf_tuples = list(filter(lambda x:p in x, CFL))
571
+ if cf_tuples:
572
+ income = 0
573
+ outcome = 0
574
+ for n in range(len(cf_tuples)):
575
+ if cf_tuples[n][1] > 1:
576
+ income += cf_tuples[n][1]*1.0
577
+ outcome += 0.
578
+ else:
579
+ income += 0.
580
+ outcome += cf_tuples[n][1]*1.0
581
+ ncf = income + outcome
582
+ self.n = p
583
+ pv = ncf * factor.pgivenfsp(self, self.i,self.n)
584
+ ncfl.append((p, outcome, income, ncf, pv))
585
+ else:
586
+ ncfl.append((p, 0, 0, 0, 0))
587
+
588
+ return DataFrame(ncfl, columns=['Period', 'Outcome', 'Income', 'ncf', 'dcf'])
589
+
590
+ def npvsensitivitytable(self, period_list:list, cf_list:list, i_list:list):
591
+ '''
592
+ This function allows you to calculate different net present
593
+ values for the same cash flow from a list of different interest
594
+ rates.
595
+
596
+ Input arguments:
597
+ period_list: Period list
598
+ cf_list: Cash flow list
599
+ i_list: Effective interest rate list
600
+ '''
601
+
602
+ self.period_list = period_list
603
+ self.cf_list = cf_list
604
+ self.i_list = i_list
605
+
606
+
607
+ self.i_list.sort()
608
+
609
+ table = [(time_value.npv(self.period_list, self.cf_list, r), r) for r in self.i_list]
610
+
611
+ return DataFrame(table, columns=['npv', 'i'])
612
+
613
+ def npvivtable(self, period_list:list, cf_list:list, iv:list):
614
+ '''
615
+ Input arguments:
616
+ period_list: Cash flow term list
617
+ cf_list: Cash flow list
618
+ iv: Variable interest list
619
+ '''
620
+
621
+ len_period_list = len(period_list)
622
+ len_cf_list = len(cf_list)
623
+ len_iv = len(iv)
624
+
625
+ for p in period_list:
626
+ c = period_list.count(p)
627
+ if c > 1:
628
+ raise Exception(f"There should only be one cash flow per period. Period {p} has {c} elements")
629
+
630
+ assert len_period_list == len_cf_list and len_period_list == len_iv, f"The inpt list has not the same number of elements"
631
+
632
+
633
+
634
+ self.period_list = period_list
635
+ self.cf_list = cf_list
636
+ self.iv = iv
637
+
638
+ n_max=max(period_list) + 1
639
+ CFL = list(zip(self.period_list, self.cf_list))
640
+
641
+
642
+ i = []
643
+
644
+ i_comp =1
645
+
646
+ for r in self.iv:
647
+ i_comp *= (1+r)
648
+
649
+ i.append(i_comp)
650
+
651
+
652
+ ncf = []
653
+ income = 0
654
+ outcome = 0
655
+ for p in range (n_max):
656
+ self.p = p
657
+ self.cf_tuples_list=CFL
658
+ _, cf = time_value._getcf(self, self.p,self.cf_tuples_list)
659
+
660
+ if cf>0:
661
+ income = cf
662
+ outcome = 0
663
+ else:
664
+ income = 0
665
+ outcome = cf
666
+
667
+ if p ==0:
668
+ ie = (i[p]**(1))-1
669
+ else:
670
+ ie = (i[p]**(1/p))-1
671
+ dcf = cf/((1+ie)**p)
672
+ ncf.append((p, income, outcome, cf, i[p], ie, dcf))
673
+
674
+ df = DataFrame(ncf, columns=["Period", 'Outcome','Income',"ncf", "i_comp", "ie", "dcf"])
675
+ return df
676
+
677
+
678
+ def uniform_loan_amortization(self, loan_amount:float, rate:float, loan_term:int, periodicity:str='Y'):
679
+ """
680
+ loan_amount: Amount to lend
681
+ rate: fixed interest rates during the loan period
682
+ loan_term: Duration of loan
683
+ periodicity: Frequency of loan payments ({'M':'Month', 'B':'Bimonth', 'Q':'Quarter','S': 'Semiannual', 'Y':'Year')
684
+ per_conv = ['M', 'B', 'Q','S', 'Y']
685
+ per_names = {'M':'Month', 'B':'Bimonth', 'Q':'Quarter','S': 'Semiannual', 'Y':'Year'}
686
+ """
687
+ per_conv = ['M', 'B', 'Q','S', 'Y']
688
+ per_names = {'M':'Month', 'B':'Bimonth', 'Q':'Quarter','S': 'Semiannual', 'Y':'Year'}
689
+ assert periodicity in per_conv, f"Input 'Y' for Year, 'S' for Semiannual, 'Q' for Quarterly, 'B' for Bimonthly and 'M' for Monthly"
690
+ self.loan_amount= loan_amount
691
+ self.rate = rate
692
+ self.loan_term = loan_term
693
+ self.periodicity = periodicity
694
+
695
+ per = list(range(self.loan_term + 1))
696
+ beg_bal = []
697
+ pay_per = []
698
+ pri_per = []
699
+ int_per = []
700
+ tot_pay = []
701
+ tot_int = []
702
+ rem_bal = []
703
+
704
+ period_payment = round(self.loan_amount * factor.agivenp(self, i=rate, n=loan_term), 2)
705
+
706
+ pay_per.append(0)
707
+ beg_bal.append(loan_amount)
708
+ pri_per.append(0)
709
+ int_per.append(0)
710
+ tot_pay.append(0)
711
+ tot_int.append(0)
712
+ rem_bal.append(loan_amount)
713
+
714
+ for p in range(1, self.loan_term + 1):
715
+ beginning_balance = rem_bal[p-1]
716
+ period_interest = round(rem_bal[p-1] * self.rate, 2)
717
+ period_principal = period_payment - period_interest
718
+ total_principal = sum(pri_per) + period_principal
719
+ total_interest = sum(int_per) + period_interest
720
+ remaining_balance = beginning_balance - period_principal
721
+
722
+
723
+ beg_bal.append(beginning_balance)
724
+ pay_per.append(period_payment)
725
+ pri_per.append(period_principal)
726
+ int_per.append(period_interest)
727
+ tot_pay.append(total_principal)
728
+ tot_int.append(total_interest)
729
+ rem_bal.append(remaining_balance)
730
+
731
+
732
+ data = zip(per, beg_bal, pay_per, pri_per, int_per, tot_pay, tot_int, rem_bal)
733
+
734
+
735
+ period = per_names[self.periodicity]
736
+ columns = [period, 'Beginning balance', 'Payment', 'Principal', 'Interest', 'Total Payment', 'Total Interest', 'Remaining Balance' ]
737
+
738
+ amortization_table = DataFrame(data=data, columns=columns)
739
+
740
+ return amortization_table
741
+
742
+ def variable_payment_loan_amortization(self, loan_amount:float, rate:list, loan_term:int, periodicity:str='Y'):
743
+ """
744
+ loan_amount: Amount to lend
745
+ rate: List of variable or fixed interest rates during the loan period
746
+ uniform capital payment: loan_amount / loan_term
747
+ loan_term: Duration of loan
748
+ periodicity: Frequency of loan payments ({'M':'Month', 'B':'Bimonth', 'Q':'Quarter','S': 'Semiannual', 'Y':'Year')
749
+ variable interest payment: Net balance * rate in period p
750
+ """
751
+ assert isinstance(rate, list), "Argument rate must be a list"
752
+ assert len(rate)==loan_term, "Argument rate must has a len equal to loan_term"
753
+
754
+ per_conv = ['M', 'B', 'Q','S', 'Y']
755
+ per_names = {'M':'Month', 'B':'Bimonth', 'Q':'Quarter','S': 'Semiannual', 'Y':'Year'}
756
+ assert periodicity in per_conv, f"Input 'Y' for Year, 'S' for Semiannual, 'Q' for Quarterly, 'B' for Bimonthly and 'M' for Monthly"
757
+ self.loan_amount= loan_amount
758
+ # self.rate = rate
759
+ self.loan_term = loan_term
760
+ self.periodicity = periodicity
761
+
762
+ per = list(range(self.loan_term + 1))
763
+ beg_bal = []
764
+ pay_per = []
765
+ pri_per = []
766
+ int_per = []
767
+ tot_pay = []
768
+ tot_int = []
769
+ rem_bal = []
770
+
771
+ period_principal = round(self.loan_amount / self.loan_term, 2)
772
+
773
+ pay_per.append(0)
774
+ beg_bal.append(self.loan_amount)
775
+ pri_per.append(0)
776
+ int_per.append(0)
777
+ tot_pay.append(0)
778
+ tot_int.append(0)
779
+ rem_bal.append(self.loan_amount)
780
+
781
+ for p in range(1, self.loan_term + 1):
782
+ beginning_balance = rem_bal[p-1]
783
+ period_interest = round(rem_bal[p-1] * rate[p-1], 2)
784
+ period_payment = period_principal + period_interest
785
+ total_principal = sum(pri_per) + period_payment
786
+ total_interest = sum(int_per) + period_interest
787
+ remaining_balance = beginning_balance - period_principal
788
+
789
+
790
+ beg_bal.append(beginning_balance)
791
+ pay_per.append(period_payment)
792
+ pri_per.append(period_principal)
793
+ int_per.append(period_interest)
794
+ tot_pay.append(total_principal)
795
+ tot_int.append(total_interest)
796
+ rem_bal.append(remaining_balance)
797
+
798
+
799
+ data = zip(per, beg_bal, pay_per, pri_per, int_per, tot_pay, tot_int, rem_bal)
800
+
801
+
802
+ period = per_names[self.periodicity]
803
+ columns = [period, 'Beginning balance', 'Payment', 'Principal', 'Interest', 'Total Payment', 'Total Interest', 'Remaining Balance' ]
804
+
805
+ amortization_table = DataFrame(data=data, columns=columns)
806
+
807
+ return amortization_table
808
+
809
+
810
+ class time_value_plot(object):
811
+
812
+ def cf_plot_bar(self, cf_dic:dict):
813
+ """
814
+ Cash Flow plot bars type
815
+ """
816
+
817
+ self.cf_dic = cf_dic
818
+
819
+ if self.cf_dic == None:
820
+ self.cf_dic = {}
821
+ else:
822
+ self.cf_dic = cf_dic
823
+
824
+
825
+
826
+ if (self.cf_dic['Factor'] == 'P/F') or (self.cf_dic['Factor'] == 'F/P'):
827
+
828
+ pv = -round(self.cf_dic['PV'], 4)
829
+ fv = round(self.cf_dic['FV'], 4)
830
+
831
+ n = self.cf_dic['n']
832
+ x_data = range(n+1)
833
+ y_o_data = [pv] + [0] * (n)
834
+ y_i_data = [0.] * (n) + [fv]
835
+ df = DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Income", "Outcome"])
836
+ print(df)
837
+
838
+ if (self.cf_dic['Factor'] == 'P/F'):
839
+ title = f"{fv: .4f} * (P/F, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {-pv:.4f}"
840
+ else:
841
+ title = f"{-pv: .4f} * (F/P, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {fv:.4f}"
842
+
843
+ fig = bar(df,
844
+ x="Period",
845
+ y= ["Income", "Outcome"],
846
+ title= title ,
847
+ text_auto=True,
848
+ opacity=0.80,
849
+ facet_col_spacing= 0.0
850
+ )
851
+
852
+ y_max = round(fv,0) + 0.5
853
+ y_min = round(-pv, 0) + 0.5
854
+ pp = y_min / (y_min + y_max)
855
+ fig.update_layout( bargap=0.05,bargroupgap=0.75)
856
+ fig.update_traces(textangle=90, selector=dict(type='bar'))
857
+ fig.update_xaxes(position= pp,
858
+ anchor="free",
859
+ linecolor = "black",
860
+ tickfont=dict(size=12, color='black'),
861
+ ticks = "outside",
862
+ ticklabelposition = "outside left"
863
+ )
864
+ fig.update_layout(yaxis_range=[-y_min, y_max])
865
+
866
+ return(fig.show())
867
+
868
+
869
+
870
+ if (self.cf_dic['Factor'] == 'P/A') or (self.cf_dic['Factor'] == 'A/P'):
871
+
872
+ pv = -round(self.cf_dic['PV'], 4)
873
+ a = round(self.cf_dic['A'], 4)
874
+ n = cf_dic['n']
875
+ x_data = range(n+1)
876
+ y_i_data = [0.] + [a] * (n)
877
+ y_o_data = [0.] * (n+1)
878
+ y_o_data[0] = pv
879
+ df = DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Income", "Outcome"])
880
+ print(df)
881
+
882
+ if (self.cf_dic['Factor'] == 'P/A'):
883
+ title = f"{a: .4f} * (P/A, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {-pv:.4f}"
884
+ else:
885
+ title = f"{-pv: .4f} * (A/P, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {a:.2f}"
886
+
887
+
888
+ fig = bar(
889
+ df,
890
+ x="Period",
891
+ y=["Income", "Outcome"],
892
+ title= title ,
893
+ text_auto=True,
894
+ opacity=0.80,
895
+ facet_col_spacing= 0.0,
896
+ )
897
+
898
+ y_max = round(a,0) + 0.5
899
+ y_min = round(-pv, 0) + 0.5
900
+ pp = y_min / (y_min + y_max)
901
+
902
+
903
+ fig.update_layout(bargap=0.05,bargroupgap=0.75)
904
+ fig.update_traces(textangle=90, selector=dict(type='bar'))
905
+ fig.update_xaxes(position= pp,
906
+ anchor="free",
907
+ linecolor = "black",
908
+ tickfont=dict(size=12, color='black'),
909
+ ticks = "outside",
910
+ ticklabelposition = "outside left"
911
+ )
912
+ fig.update_yaxes(title="Cash Flow [$]")
913
+ fig.update_layout(yaxis_range=[-y_min, y_max])
914
+ fig.update_yaxes(title="Cash Flow [$]")
915
+
916
+ return(fig.show())
917
+
918
+ if (self.cf_dic['Factor'] == 'F/A') or (self.cf_dic['Factor'] == 'A/F'):
919
+
920
+ fv = round(self.cf_dic['FV'], 4)
921
+ a = -round(self.cf_dic['A'], 4)
922
+ n = cf_dic['n']
923
+ x_data = range(n+1)
924
+ y_o_data = [0.] + [a] * (n)
925
+ y_i_data = [0.] * (n+1)
926
+ y_i_data[-1] = fv
927
+ df = DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Income", "Outcome"])
928
+ print(df)
929
+
930
+ if (self.cf_dic['Factor'] == 'F/A'):
931
+ title = f"{-a: .4f} * (F/A, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {fv:.4f}"
932
+ else:
933
+ title = f"{fv: .4f} * (A/F, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {-a:.4f}"
934
+
935
+
936
+ fig = bar(df,
937
+ x="Period",
938
+ y=["Income", "Outcome"],
939
+ title= title ,
940
+ text_auto=True,
941
+ opacity=0.80,
942
+ facet_col_spacing= 0.0,
943
+ )
944
+
945
+ y_max = round(fv,0) + 10
946
+ y_min = round(-a, 0) + 10
947
+ pp = y_min / ( y_min + y_max)
948
+
949
+
950
+ fig.update_layout(bargap=0.05,bargroupgap=0.75)
951
+ fig.update_traces(textangle=90, selector=dict(type='bar'))
952
+ fig.update_xaxes(position= pp,
953
+ anchor="free",
954
+ linecolor = "black",
955
+ tickfont=dict(size=12, color='black'),
956
+ ticks = "outside",
957
+ ticklabelposition = "outside left"
958
+ )
959
+ fig.update_yaxes(title="Cash Flow [$]")
960
+ fig.update_layout(yaxis_range=[-y_min, y_max])
961
+ return(fig.show())
962
+
963
+
964
+ if (self.cf_dic['Factor'] == 'P/G'):
965
+
966
+ pv = -round(self.cf_dic['PV'], 4)
967
+ ag = round(self.cf_dic['G'], 4)
968
+ n = cf_dic['n']
969
+ x_data = range(n+1)
970
+ y_ag_data = [0.] + [k * ag for k in range(n)]
971
+ y_o_data = [0.] * (n+1)
972
+ y_o_data[0] = pv
973
+ df = DataFrame(list(zip(x_data, y_ag_data, y_o_data)), columns=["Period", "Gradient Income", "Outcome"])
974
+ print(df)
975
+
976
+
977
+ title = f"{ag:.2f} * (P/G, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {-pv:.4f}"
978
+ fig = bar(
979
+ df,
980
+ x="Period",
981
+ y=["Gradient Income", "Outcome"],
982
+ title= title ,
983
+ text_auto=True,
984
+ opacity=0.80,
985
+ facet_col_spacing= 0.0
986
+ )
987
+
988
+ y_max = round(ag * (n-1) * 1.5,0)
989
+ y_min = round(-pv, 0) + 0.5
990
+ pp = y_min / (y_min + y_max)
991
+
992
+ fig.update_layout( bargap=0.05,bargroupgap=0.75)
993
+ fig.update_traces(textangle=90, selector=dict(type='bar'))
994
+ fig.update_xaxes(position= pp,
995
+ anchor="free",
996
+ linecolor = "black",
997
+ tickfont=dict(size=12, color='black'),
998
+ ticks = "outside",
999
+ ticklabelposition = "outside left"
1000
+ )
1001
+ fig.update_yaxes(title="Cash Flow [$]")
1002
+ fig.update_layout(yaxis_range=[-y_min, y_max])
1003
+
1004
+ return(fig.show())
1005
+
1006
+
1007
+ if (self.cf_dic['Factor'] == 'A/G'):
1008
+
1009
+ a = -round(self.cf_dic['A'], 4)
1010
+ ag = round(self.cf_dic['G'], 4)
1011
+ n = cf_dic['n']
1012
+ x_data = range(n+1)
1013
+ y_ag_data = [0.] + [k * ag for k in range(n)]
1014
+ y_o_data = [0.] + [a] * (n)
1015
+ df = DataFrame(list(zip(x_data, y_ag_data, y_o_data)), columns=["Period", "Gradient Income", "Outcome"])
1016
+ print(df)
1017
+
1018
+
1019
+ title = f"{ag:.2f} * (A/G, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {-a:.4f}"
1020
+ fig = bar(
1021
+ df,
1022
+ x="Period",
1023
+ y=["Outcome", "Gradient Income"],
1024
+ title= title ,
1025
+ text_auto=True,
1026
+ opacity=0.80,
1027
+ facet_col_spacing= 0.0
1028
+ )
1029
+
1030
+ y_max = round(ag * (n-1) * 1.5,0)
1031
+ y_min = round(-a, 0) + 0.5
1032
+ pp = y_min / (y_min + y_max)
1033
+
1034
+
1035
+ fig.update_layout( bargap=0.05,bargroupgap=0.75)
1036
+ fig.update_traces(textangle=90, selector=dict(type='bar'))
1037
+ fig.update_xaxes(position= pp,
1038
+ anchor="free",
1039
+ linecolor = "black",
1040
+ tickfont=dict(size=12, color='black'),
1041
+ ticks = "outside",
1042
+ ticklabelposition = "outside left"
1043
+ )
1044
+ fig.update_yaxes(title="Cash Flow [$]")
1045
+ fig.update_layout(yaxis_range=[-y_min, y_max])
1046
+
1047
+ return(fig.show())
1048
+
1049
+ if (self.cf_dic['Factor'] == 'F/G'):
1050
+
1051
+ fv = round(self.cf_dic['FV'], 4)
1052
+ ag = -round(self.cf_dic['G'], 4)
1053
+ n = cf_dic['n']
1054
+ x_data = range(n+1)
1055
+ y_ag_data = [0.] + [k * ag for k in range(n)]
1056
+ y_o_data = [0.] * (n+1)
1057
+ y_o_data[-1] = fv
1058
+ df = DataFrame(list(zip(x_data, y_ag_data, y_o_data)), columns=["Period", "Gradient Outcome", "Income"])
1059
+ print(df)
1060
+
1061
+
1062
+ title = f"{-ag:.2f} * (F/G, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {fv:.4f}"
1063
+ fig = bar(
1064
+ df,
1065
+ x="Period",
1066
+ y=["Income", "Gradient Outcome"],
1067
+ title= title ,
1068
+ text_auto=True,
1069
+ opacity=0.80,
1070
+ facet_col_spacing= 0.0
1071
+ )
1072
+
1073
+ y_max = round(fv, 0) + 0.5
1074
+ y_min = round(-ag * (n-1) * 1.5,0)
1075
+ pp = y_min / (y_min + y_max)
1076
+
1077
+ fig.update_layout( bargap=0.05,bargroupgap=0.75)
1078
+ fig.update_traces(textangle=90, selector=dict(type='bar'))
1079
+ fig.update_xaxes(position= pp,
1080
+ anchor="free",
1081
+ linecolor = "black",
1082
+ tickfont=dict(size=12, color='black'),
1083
+ ticks = "outside",
1084
+ ticklabelposition = "outside left"
1085
+ )
1086
+ fig.update_yaxes(title="Cash Flow [$]")
1087
+ fig.update_layout(yaxis_range=[-y_min, y_max])
1088
+
1089
+ return(fig.show())
1090
+
1091
+ if (self.cf_dic['Factor'] == 'P/g'):
1092
+
1093
+ pv = -round(self.cf_dic['PV'], 4)
1094
+ gg = round(self.cf_dic['g'], 4)
1095
+ ba = round(self.cf_dic['A1'], 4)
1096
+ n = cf_dic['n']
1097
+ x_data = range(n+1)
1098
+
1099
+ y_i_data = [0] + [round(ba * (1 + gg)**k,4) for k in range(n)]
1100
+ y_o_data = [0.] * (n+1)
1101
+ y_o_data[0] = round(pv,4)
1102
+ df = DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Gradient Income", "Outcome"])
1103
+ print(df)
1104
+
1105
+
1106
+ title = f"{ba: .2f} * (P/g, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}, g: {gg*100:.2f}) = {-pv:.4f}"
1107
+
1108
+ fig = bar(
1109
+ df,
1110
+ x="Period",
1111
+ y=["Gradient Income", "Outcome"],
1112
+ title= title ,
1113
+ text_auto=True,
1114
+ opacity=0.80,
1115
+ facet_col_spacing= 0.0
1116
+ )
1117
+
1118
+ y_max = round(ba * (1+gg)**(n-1) * 1.2,0 )
1119
+ y_min = round(-pv, 0) + 0.5
1120
+ pp = y_min / (y_min + y_max)
1121
+
1122
+
1123
+ fig.update_layout( bargap=0.05,bargroupgap=0.75)
1124
+ fig.update_traces(textangle=90, selector=dict(type='bar'))
1125
+ fig.update_xaxes(position= pp,
1126
+ anchor="free",
1127
+ linecolor = "black",
1128
+ tickfont=dict(size=12, color='black'),
1129
+ ticks = "outside",
1130
+ ticklabelposition = "outside left"
1131
+ )
1132
+ fig.update_layout(yaxis_range=[-y_min, y_max])
1133
+ fig.update_yaxes(title="Cash Flow [$]")
1134
+
1135
+ return(fig.show())
1136
+
1137
+ def __arrowupdate(df_x, df_y):
1138
+
1139
+ counter = 0
1140
+ arrow_list = []
1141
+ text_list = []
1142
+
1143
+ for i in df_y.tolist():
1144
+ if i != 0:
1145
+ if i>0:
1146
+ arrowcolor = 'rgb(77,7,252)'
1147
+ xanchor = 'right'
1148
+ text = round(i,2)
1149
+ textangle = 0
1150
+ visible=True
1151
+ else:
1152
+ arrowcolor = 'rgb(252,7,77)'
1153
+ xanchor = 'left'
1154
+ text = round(i,2)
1155
+ textangle = 0
1156
+ visible=True
1157
+
1158
+ arrow_aux=dict(x=df_x.values[counter],
1159
+ y=df_y.values[counter],
1160
+ xref='x',
1161
+ ax=counter,
1162
+ ay=0,
1163
+ yref='y',
1164
+ axref='x',
1165
+ ayref='y',
1166
+ text='',
1167
+ showarrow=True,
1168
+ arrowhead=1,
1169
+ arrowsize=2,
1170
+ arrowwidth=2,
1171
+ arrowcolor=arrowcolor,
1172
+ xanchor = xanchor,
1173
+ yanchor='bottom'
1174
+ )
1175
+
1176
+ text_aux = dict(text = text,
1177
+ textangle = textangle,
1178
+ visible=visible,
1179
+ )
1180
+
1181
+ arrow_list.append(arrow_aux)
1182
+ text_list.append(text_aux)
1183
+
1184
+ counter += 1
1185
+ else:
1186
+ counter += 1
1187
+
1188
+ return arrow_list, text_list
1189
+
1190
+ def cf_plot_arrow(self, cf_dic):
1191
+
1192
+
1193
+ if self.cf_dic == None:
1194
+ self.cf_dic = {}
1195
+ else:
1196
+ self.cf_dic = cf_dic
1197
+
1198
+
1199
+
1200
+ if (self.cf_dic['Factor'] == 'P/F') or (self.cf_dic['Factor'] == 'F/P'):
1201
+
1202
+ pv = -round(self.cf_dic['PV'], 4)
1203
+ fv = round(self.cf_dic['FV'], 4)
1204
+
1205
+ n = self.cf_dic['n']
1206
+ x_data = range(n+1)
1207
+ y_o_data = [pv] + [0.] * (n+1)
1208
+ y_i_data = [0.] * (n) + [fv]
1209
+
1210
+ df= DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Income", "Outcome"])
1211
+ print(df)
1212
+
1213
+ if (self.cf_dic['Factor'] == 'P/F'):
1214
+ title = f"{fv: .4f} * (P/F, i: {self.cf_dic['i']*100:.2f}% n: {self.cf_dic['n']}) = {-pv:.4f}"
1215
+ else:
1216
+ title = f"{-pv: .4f} * (F/P, i: {self.cf_dic['i']*100:.2f}% n: {self.cf_dic['n']}) = {fv:.4f}"
1217
+
1218
+
1219
+ fig = bar(df,
1220
+ x="Period",
1221
+ y=["Income", "Outcome"],
1222
+ title= title ,
1223
+ text_auto=False,
1224
+ opacity=0.0,
1225
+ facet_col_spacing= 0.0,
1226
+ )
1227
+
1228
+ y_max = round(fv,0) + 5
1229
+ y_min = round(-pv, 0) + 5
1230
+ pp = y_min / (y_min + y_max)
1231
+
1232
+ fig.update_layout( bargap=0.05,bargroupgap=0.75)
1233
+ fig.update_traces(textangle=90, selector=dict(type='bar'))
1234
+ fig.update_xaxes(position= pp,
1235
+ anchor="free",
1236
+ linecolor = "black",
1237
+ tickfont=dict(size=12, color='black'),
1238
+ ticks = "outside",
1239
+ ticklabelposition = "outside left"
1240
+ )
1241
+
1242
+ fig.update_layout(yaxis_range=[-y_min, y_max])
1243
+
1244
+ arrow_list_1, text_list_1 = time_value_plot.__arrowupdate(df['Period'], df["Income"])
1245
+ arrow_list_2, text_list_2 = time_value_plot.__arrowupdate(df['Period'], df["Outcome"])
1246
+
1247
+ fig.update_layout(annotations=arrow_list_1 + arrow_list_2)
1248
+ fig.update_layout(annotations=text_list_1 + text_list_2)
1249
+ fig.update_traces(textposition='inside')
1250
+
1251
+ return(fig.show())
1252
+
1253
+
1254
+ if (self.cf_dic['Factor'] == 'P/A') or (self.cf_dic['Factor'] == 'A/P'):
1255
+
1256
+ pv = -round(self.cf_dic['PV'], 4)
1257
+ a = round(self.cf_dic['A'], 4)
1258
+ n = cf_dic['n']
1259
+ x_data = range(n+1)
1260
+ y_i_data = [0.] + [a] * (n)
1261
+ y_o_data = [0.] * (n+1)
1262
+ y_o_data[0] = pv
1263
+ df = DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Income", "Outcome"])
1264
+ print(df)
1265
+
1266
+ if (self.cf_dic['Factor'] == 'P/A'):
1267
+ title = f"{a: .4f} * (P/A, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {-pv:.4f}"
1268
+ else:
1269
+ title = f"{-pv: .4f} * (A/P, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {a:.2f}"
1270
+
1271
+
1272
+ fig = bar(
1273
+ df,
1274
+ x="Period",
1275
+ y=["Income", "Outcome"],
1276
+ title= title ,
1277
+ text_auto=False,
1278
+ opacity=0.0,
1279
+ facet_col_spacing= 0.0,
1280
+ )
1281
+
1282
+ y_max = round(a,0) + 0.5
1283
+ y_min = round(-pv, 0) + 0.5
1284
+ pp = y_min / (y_min + y_max)
1285
+
1286
+ fig.update_layout( bargap=0.05,bargroupgap=0.75)
1287
+ fig.update_traces(textangle=90, selector=dict(type='bar'))
1288
+ fig.update_xaxes(position= pp,
1289
+ anchor="free",
1290
+ linecolor = "black",
1291
+ tickfont=dict(size=12, color='black'),
1292
+ ticks = "outside",
1293
+ ticklabelposition = "outside left"
1294
+ )
1295
+
1296
+ fig.update_layout(yaxis_range=[-y_min, y_max])
1297
+
1298
+ arrow_list_1, text_list_1 = time_value_plot.__arrowupdate(df['Period'], df["Income"])
1299
+ arrow_list_2, text_list_2 = time_value_plot.__arrowupdate(df['Period'], df["Outcome"])
1300
+
1301
+ fig.update_layout(annotations=arrow_list_1 + arrow_list_2)
1302
+ fig.update_layout(annotations=text_list_1 + text_list_2)
1303
+ fig.update_traces(textposition='inside')
1304
+
1305
+ return(fig.show())
1306
+
1307
+ if (self.cf_dic['Factor'] == 'F/A') or (self.cf_dic['Factor'] == 'A/F'):
1308
+
1309
+ fv = round(self.cf_dic['FV'], 4)
1310
+ a = -round(self.cf_dic['A'], 4)
1311
+ n = cf_dic['n']
1312
+ x_data = range(n+1)
1313
+ y_o_data = [0.] + [a] * (n)
1314
+ y_i_data = [0.] * (n+1)
1315
+ y_i_data[-1] = fv
1316
+ df = DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Income", "Outcome"])
1317
+ print(df)
1318
+
1319
+ if (self.cf_dic['Factor'] == 'F/A'):
1320
+ title = f"{-a: .4f} * (F/A, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {fv:.4f}"
1321
+ else:
1322
+ title = f"{fv: .4f} * (A/F, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {-a:.4f}"
1323
+
1324
+
1325
+ fig = bar(df,
1326
+ x="Period",
1327
+ y=["Income", "Outcome"],
1328
+ title= title ,
1329
+ text_auto=False,
1330
+ opacity=0.0,
1331
+ facet_col_spacing= 0.0
1332
+ )
1333
+
1334
+ y_max = round(fv,0) + 0.5
1335
+ y_min = -round(a, 0) + 0.5
1336
+ pp = y_min / (y_min + y_max)
1337
+
1338
+
1339
+ fig.update_layout( bargap=0.05,bargroupgap=0.75)
1340
+ fig.update_traces(textangle=90, selector=dict(type='bar'))
1341
+ fig.update_xaxes(position= pp,
1342
+ anchor="free",
1343
+ linecolor = "black",
1344
+ tickfont=dict(size=12, color='black'),
1345
+ ticks = "outside",
1346
+ ticklabelposition = "outside left"
1347
+ )
1348
+ fig.update_layout(yaxis_range=[-y_min, y_max])
1349
+
1350
+ arrow_list_1, text_list_1 = time_value_plot.__arrowupdate(df['Period'], df["Income"])
1351
+ arrow_list_2, text_list_2 = time_value_plot.__arrowupdate(df['Period'], df["Outcome"])
1352
+
1353
+ fig.update_layout(annotations=arrow_list_1 + arrow_list_2)
1354
+ fig.update_layout(annotations=text_list_1 + text_list_2)
1355
+ fig.update_traces(textposition='inside')
1356
+
1357
+ return(fig.show())
1358
+
1359
+
1360
+ if (self.cf_dic['Factor'] == 'P/G'):
1361
+
1362
+ pv = -round(self.cf_dic['PV'], 4)
1363
+ ag = round(self.cf_dic['G'], 4)
1364
+ n = cf_dic['n']
1365
+ x_data = range(n+1)
1366
+ y_ag_data = [0.] + [k * ag for k in range(n)]
1367
+ y_o_data = [0.] * (n+1)
1368
+ y_o_data[0] = pv
1369
+ df = DataFrame(list(zip(x_data, y_ag_data, y_o_data)), columns=["Period", "Gradient Income", "Outcome"])
1370
+ print(df)
1371
+
1372
+
1373
+ title = f"{ag:.2f} * (P/G, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {-pv:.4f}"
1374
+
1375
+ fig = bar(
1376
+ df,
1377
+ x="Period",
1378
+ y=["Gradient Income", "Outcome"],
1379
+ title= title ,
1380
+ text_auto=False,
1381
+ opacity=0.0,
1382
+ facet_col_spacing= 0.0
1383
+ )
1384
+
1385
+ y_max = round(ag * (n-1) * 1.5,0)
1386
+ y_min = round(-pv, 0) + 0.5
1387
+ pp = y_min / (y_min + y_max)
1388
+
1389
+
1390
+ fig.update_layout( bargap=0.05,bargroupgap=0.75)
1391
+ fig.update_traces(textangle=90, selector=dict(type='bar'))
1392
+ fig.update_xaxes(position= pp,
1393
+ anchor="free",
1394
+ linecolor = "black",
1395
+ tickfont=dict(size=12, color='black'),
1396
+ ticks = "outside",
1397
+ ticklabelposition = "outside left"
1398
+ )
1399
+ fig.update_layout(yaxis_range=[-y_min, y_max])
1400
+
1401
+ arrow_list_1, text_list_1 = time_value_plot.__arrowupdate(df['Period'], df["Gradient Income"])
1402
+ arrow_list_2, text_list_2 = time_value_plot.__arrowupdate(df['Period'], df["Outcome"])
1403
+
1404
+ fig.update_layout(annotations=arrow_list_1 + arrow_list_2)
1405
+ fig.update_layout(annotations=text_list_1 + text_list_2)
1406
+ fig.update_traces(textposition='inside')
1407
+
1408
+ return(fig.show())
1409
+
1410
+
1411
+ if (self.cf_dic['Factor'] == 'A/G'):
1412
+
1413
+ a = -round(self.cf_dic['A'], 4)
1414
+ ag = round(self.cf_dic['G'], 4)
1415
+ n = cf_dic['n']
1416
+ x_data = range(n+1)
1417
+ y_ag_data = [0.] + [k * ag for k in range(n)]
1418
+ y_o_data = [0.] + [a] * (n)
1419
+ df = DataFrame(list(zip(x_data, y_ag_data, y_o_data)), columns=["Period", "Gradient Income", "Outcome"])
1420
+ print(df)
1421
+
1422
+
1423
+ title = f"{ag:.2f} * (A/G, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {-a:.4f}"
1424
+ fig = bar(
1425
+ df,
1426
+ x="Period",
1427
+ y=["Outcome", "Gradient Income"],
1428
+ title= title ,
1429
+ text_auto=False,
1430
+ opacity=0.0,
1431
+ facet_col_spacing= 0.0
1432
+ )
1433
+
1434
+ y_max = round(ag * (n-1) * 1.5,0)
1435
+ y_min = round(-a, 0) + 0.5
1436
+ pp = y_min / (y_min + y_max)
1437
+
1438
+
1439
+ fig.update_layout( bargap=0.05,bargroupgap=0.75)
1440
+ fig.update_traces(textangle=90, selector=dict(type='bar'))
1441
+ fig.update_xaxes(position= pp,
1442
+ anchor="free",
1443
+ linecolor = "black",
1444
+ tickfont=dict(size=12, color='black'),
1445
+ ticks = "outside",
1446
+ ticklabelposition = "outside left"
1447
+ )
1448
+ fig.update_yaxes(title="Cash Flow [$]")
1449
+ fig.update_layout(yaxis_range=[-y_min, y_max])
1450
+
1451
+ arrow_list_1, text_list_1 = time_value_plot.__arrowupdate(df['Period'], df["Gradient Income"])
1452
+ arrow_list_2, text_list_2 = time_value_plot.__arrowupdate(df['Period'], df["Outcome"])
1453
+
1454
+ fig.update_layout(annotations=arrow_list_1 + arrow_list_2)
1455
+ fig.update_layout(annotations=text_list_1 + text_list_2)
1456
+ fig.update_traces(textposition='inside')
1457
+
1458
+
1459
+ return(fig.show())
1460
+
1461
+
1462
+ if (self.cf_dic['Factor'] == 'F/G'):
1463
+
1464
+ fv = round(self.cf_dic['FV'], 4)
1465
+ ag = -round(self.cf_dic['G'], 4)
1466
+ n = cf_dic['n']
1467
+ x_data = range(n+1)
1468
+ y_ag_data = [0.] + [k * ag for k in range(n)]
1469
+ y_o_data = [0.] * (n+1)
1470
+ y_o_data[-1] = fv
1471
+ df = DataFrame(list(zip(x_data, y_ag_data, y_o_data)), columns=["Period", "Gradient Outcome", "Income"])
1472
+ print(df)
1473
+
1474
+
1475
+ title = f"{-ag:.2f} * (F/G, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {fv:.4f}"
1476
+ fig = bar(
1477
+ df,
1478
+ x="Period",
1479
+ y=["Income", "Gradient Outcome"],
1480
+ title= title ,
1481
+ text_auto=False,
1482
+ opacity=0.0,
1483
+ facet_col_spacing= 0.0
1484
+ )
1485
+
1486
+ y_max = round(fv, 0) + 0.5
1487
+ y_min = round(-ag * (n-1) * 1.5,0)
1488
+ pp = y_min / (y_min + y_max)
1489
+
1490
+ fig.update_layout( bargap=0.05,bargroupgap=0.75)
1491
+ fig.update_traces(textangle=90, selector=dict(type='bar'))
1492
+ fig.update_xaxes(position= pp,
1493
+ anchor="free",
1494
+ linecolor = "black",
1495
+ tickfont=dict(size=12, color='black'),
1496
+ ticks = "outside",
1497
+ ticklabelposition = "outside left"
1498
+ )
1499
+ fig.update_yaxes(title="Cash Flow [$]")
1500
+ fig.update_layout(yaxis_range=[-y_min, y_max])
1501
+
1502
+ arrow_list_1, text_list_1 = time_value_plot.__arrowupdate(df['Period'], df["Gradient Outcome"])
1503
+ arrow_list_2, text_list_2 = time_value_plot.__arrowupdate(df['Period'], df["Income"])
1504
+
1505
+ fig.update_layout(annotations=arrow_list_1 + arrow_list_2)
1506
+ fig.update_layout(annotations=text_list_1 + text_list_2)
1507
+ fig.update_traces(textposition='inside')
1508
+
1509
+ return(fig.show())
1510
+
1511
+ if (self.cf_dic['Factor'] == 'P/g'):
1512
+
1513
+ pv = -round(self.cf_dic['PV'], 4)
1514
+ gg = round(self.cf_dic['g'], 4)
1515
+ ba = round(self.cf_dic['A1'], 4)
1516
+ n = cf_dic['n']
1517
+ x_data = range(n+1)
1518
+
1519
+ y_i_data = [0] + [round(ba * (1 + gg)**k,4) for k in range(n)]
1520
+ y_o_data = [0.] * (n+1)
1521
+ y_o_data[0] = round(pv,4)
1522
+ df = DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Gradient Income", "Outcome"])
1523
+ print(df)
1524
+
1525
+
1526
+ title = f"{ba: .2f} * (P/g, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}, g: {gg*100:.2f}) = {-pv:.4f}"
1527
+
1528
+ fig = bar(
1529
+ df,
1530
+ x="Period",
1531
+ y=["Gradient Income", "Outcome"],
1532
+ title= title ,
1533
+ text_auto=False,
1534
+ opacity=0.0,
1535
+ facet_col_spacing= 0.0
1536
+ )
1537
+
1538
+ y_max = round(ba * (1+gg)**(n-1) * 1.2,0 )
1539
+ y_min = round(-pv, 0) + 0.5
1540
+ pp = y_min / (y_min + y_max)
1541
+
1542
+
1543
+ fig.update_layout( bargap=0.05,bargroupgap=0.75)
1544
+ fig.update_traces(textangle=90, selector=dict(type='bar'))
1545
+ fig.update_xaxes(position= pp,
1546
+ anchor="free",
1547
+ linecolor = "black",
1548
+ tickfont=dict(size=12, color='black'),
1549
+ ticks = "outside",
1550
+ ticklabelposition = "outside left"
1551
+ )
1552
+ fig.update_layout(yaxis_range=[-y_min, y_max])
1553
+ fig.update_yaxes(title="Cash Flow [$]")
1554
+
1555
+ arrow_list_1, text_list_1 = time_value_plot.__arrowupdate(df['Period'], df["Gradient Income"])
1556
+ arrow_list_2, text_list_2 = time_value_plot.__arrowupdate(df['Period'], df["Outcome"])
1557
+
1558
+ fig.update_layout(annotations=arrow_list_1 + arrow_list_2)
1559
+ fig.update_layout(annotations=text_list_1 + text_list_2)
1560
+ fig.update_traces(textposition='inside')
1561
+
1562
+ return(fig.show())
1563
+
1564
+ def npvplotbar(self, npvtable, i):
1565
+ '''
1566
+ Plot nominal cash flow stream from pandas data frame with npv
1567
+ '''
1568
+ # npvtable = npvtable[['Period', 'Income', 'Outcome']]
1569
+ self.npvtable = npvtable
1570
+ npv_ = self.npvtable['dcf'].sum()
1571
+
1572
+ if i==None:
1573
+ cf_list = list(self.npvtable['ncf'])
1574
+ period_list = list(self.npvtable['Period'])
1575
+ i = compound_interest.irr(self, npw=npv_,period_list=period_list,cf_list=cf_list)
1576
+ title = f"NPV: {npv_: .2f}, irr: {i: .4f}"
1577
+ else:
1578
+ title = f"NPV: {npv_: .2f}, i: {i: .4f}"
1579
+
1580
+
1581
+ fig = bar(
1582
+ npvtable,
1583
+ x="Period",
1584
+ y=["Income", "Outcome"],
1585
+ title= title ,
1586
+ text_auto=False,
1587
+ opacity=0.8,
1588
+ facet_col_spacing= 0.0
1589
+ )
1590
+
1591
+ y_max = round(self.npvtable['ncf'].max() + 10)
1592
+
1593
+ if self.npvtable['ncf'].min() < 0:
1594
+ y_min = round(self.npvtable['ncf'].min() + 10) * -1.
1595
+ else:
1596
+ y_min = y_max
1597
+
1598
+ pp = y_min / (y_min + y_max)
1599
+
1600
+
1601
+ fig.update_layout( bargap=0.05,bargroupgap=0.75)
1602
+ fig.update_traces(textangle=90, selector=dict(type='bar'))
1603
+ fig.update_xaxes(position= pp,
1604
+ anchor="free",
1605
+ linecolor = "black",
1606
+ tickfont=dict(size=12, color='black'),
1607
+ ticks = "outside",
1608
+ ticklabelposition = "outside left"
1609
+ )
1610
+ fig.update_layout(yaxis_range=[-y_min, y_max])
1611
+ fig.update_yaxes(title="Cash Flow [$]")
1612
+
1613
+ return(fig.show())
1614
+
1615
+ def npvplotarrow(self, npvtable, i):
1616
+ """
1617
+ Cash Flow plot arrows type
1618
+ """
1619
+ # npvtable = npvtable[['Period', 'Income', 'Outcome']]
1620
+ self.npvtable = npvtable
1621
+
1622
+ npv_ = self.npvtable['dcf'].sum()
1623
+
1624
+ if i==None:
1625
+ cf_list = list(self.npvtable['ncf'])
1626
+ period_list = list(self.npvtable['Period'])
1627
+ i = compound_interest.irr(self, npw=npv_,period_list=period_list,cf_list=cf_list)
1628
+ title = f"NPV: {npv_: .2f}, irr: {i: .4f}"
1629
+ else:
1630
+ title = f"NPV: {npv_: .2f}, i: {i: .4f}"
1631
+
1632
+ fig = bar(
1633
+ self.npvtable,
1634
+ x="Period",
1635
+ y=["Income", "Outcome"],
1636
+ title= title ,
1637
+ text_auto=False,
1638
+ opacity=0.0,
1639
+ facet_col_spacing= 0.0
1640
+ )
1641
+ y_max = round(npvtable['ncf'].max() + 10)
1642
+
1643
+ if self.npvtable['ncf'].min() < 0:
1644
+ y_min = round(self.npvtable['Outcome'].min() + 10) * -1.
1645
+ else:
1646
+ y_min = y_max
1647
+
1648
+ pp = y_min / (y_min + y_max)
1649
+
1650
+
1651
+ fig.update_layout( bargap=0.05,bargroupgap=0.75)
1652
+ fig.update_traces(textangle=90, selector=dict(type='bar'))
1653
+ fig.update_xaxes(position= pp,
1654
+ anchor="free",
1655
+ linecolor = "black",
1656
+ tickfont=dict(size=12, color='black'),
1657
+ ticks = "outside",
1658
+ ticklabelposition = "outside left"
1659
+ )
1660
+ fig.update_layout(yaxis_range=[-y_min, y_max])
1661
+ fig.update_yaxes(title="Nominal Cash Flow [$]")
1662
+
1663
+ arrow_list_1, text_list_1 = time_value_plot.__arrowupdate(self.npvtable['Period'], self.npvtable["Income"])
1664
+ arrow_list_2, text_list_2 = time_value_plot.__arrowupdate(self.npvtable['Period'], self.npvtable["Outcome"])
1665
+
1666
+ fig.update_layout(annotations=arrow_list_1 + arrow_list_2)
1667
+ fig.update_layout(annotations=text_list_1 + text_list_2)
1668
+ fig.update_traces(textposition='inside')
1669
+
1670
+ return(fig.show())
1671
+
1672
+ def amor_table_plot(self, loan_amount:float, rate:float, loan_term:int, periodicity:str):
1673
+ '''
1674
+ Plotting principal and interest payments over the repayment period
1675
+ '''
1676
+ self.loan_amount=loan_amount
1677
+ self.rate=rate
1678
+ self.loan_term=loan_term
1679
+ self.periodicity=periodicity
1680
+
1681
+ df = time_value_table.uniform_loan_amortization(self,
1682
+ self.loan_amount,
1683
+ self.rate,
1684
+ self.loan_term,
1685
+ self.periodicity)
1686
+ x = df.columns[0]
1687
+ y = ['Principal', 'Interest']
1688
+ payment = df.iloc[1,2]
1689
+ title1 = 'Evolution of Principal and Interest payments over the repayment period'
1690
+ title2 = f'Amount: {loan_amount: ,.2f}, Payment: {payment: ,.2f}, i: {rate: .2%}, Term: {loan_term}, Periodicity: {periodicity}'
1691
+ title = title1 +'<br>' + title2
1692
+ fig = area(df, x="Month", y=['Principal', 'Interest'], title=title)
1693
+ return(fig.show())
1694
+
1695
+
1696
+ def variable_amor_table_plot(self, loan_amount:float, rate:list, loan_term:int, periodicity:str):
1697
+
1698
+ '''
1699
+ Plotting principal and interest payments over the repayment period
1700
+ '''
1701
+ assert isinstance(rate, list), "Argument rate must be a list"
1702
+ assert len(rate)==loan_term, "Argument rate must has a len equal to loan_term"
1703
+ self.loan_amount=loan_amount
1704
+ self.rate=rate
1705
+ self.loan_term=loan_term
1706
+ self.periodicity=periodicity
1707
+
1708
+ df = time_value_table.variable_payment_loan_amortization(self,
1709
+ self.loan_amount,
1710
+ self.rate,
1711
+ self.loan_term,
1712
+ self.periodicity)
1713
+ x = df.columns[0]
1714
+ y = ['Principal', 'Interest']
1715
+ payment = df.iloc[1,2]
1716
+ title1 = 'Evolution of Principal and Interest payments over the repayment period'
1717
+ title2 = f'Amount: {loan_amount: ,.2f}, Payment: {payment: ,.2f}, i: variable, Term: {loan_term}, Periodicity: {periodicity}'
1718
+ title = title1 +'<br>' + title2
1719
+ fig = area(df, x="Month", y=['Principal', 'Interest'], title=title)
1720
+ return(fig.show())
1721
+
1722
+
1723
+ class compound_interest(object):
1724
+
1725
+ def spi(self, pv: float, fv: float, n: float)->float:
1726
+ '''
1727
+ spi: Single Payment Interest
1728
+
1729
+ Input arguments:
1730
+ pv: Present Value.
1731
+ fv: Future Value.
1732
+ '''
1733
+
1734
+ self.pv = pv
1735
+ self.fv = fv
1736
+ self.n = n
1737
+
1738
+ return ((self.fv/self.pv)**(1/self.n))-1
1739
+
1740
+ def ei(self, r: float, m: float)->float:
1741
+ '''
1742
+ ei: Effective Interest Per Time Period.
1743
+
1744
+ Input arguments:
1745
+ r: Interest Rate For Same Time Period.
1746
+ m: Number of Times Interest Is Compounded Per Stated Time Period.
1747
+ '''
1748
+ self.r = r
1749
+ self.m = m
1750
+ return (1 + self.r/self.m)**self.m - 1
1751
+
1752
+ def ipa(self, im:float)->float:
1753
+ '''
1754
+ ipa: Interest Paid In Advance Per Time Period.
1755
+
1756
+ Input arguments:
1757
+ ipm: Interest Paid at Maturity Per Time Period.
1758
+ '''
1759
+ self.im = im
1760
+ return self.im/(1 + self.im)
1761
+
1762
+
1763
+ def ipm(self, ia:float)->float:
1764
+ '''
1765
+ ipm: Interest Paid at Maturity Per Time Period.
1766
+
1767
+ Input arguments:
1768
+ ipa: Interest Paid In Advance Per Time Period.
1769
+ '''
1770
+ self.ia = ia
1771
+ return self.ia/(1-self.ia)
1772
+
1773
+ def di(self, i: float, m: float)->float:
1774
+ '''
1775
+ di: Discrete interest Rate or Compounding interest rate.
1776
+
1777
+ Input arguments:
1778
+ i: Effective Interest with Different Periodicity.
1779
+ m: Relationship of periods between rates. In example:
1780
+
1781
+ - 12: Month to Year
1782
+ - 6: Bimonthly to Year or Month to Half-year
1783
+ - 4: Quaterly to Year
1784
+ - 1/12: Year to Month
1785
+
1786
+ '''
1787
+ self.i = i
1788
+ self.m = m
1789
+ return ((1+self.i)**(1/self.m) - 1)
1790
+
1791
+ def cci(self, r)->float:
1792
+ '''
1793
+ cci: Continuous Interest Rate to Discrete Interest Rate.
1794
+
1795
+ Input arguments:
1796
+ r: Discrete Interest Rate For Same Time Period.
1797
+ '''
1798
+ self.r = r
1799
+ return exp(self.r) -1
1800
+
1801
+ def dci(self,i)->float:
1802
+ '''
1803
+ dci: Discrete Interest Rate to Continuous Interest Rate.
1804
+
1805
+ Input argument:
1806
+ i: Effective Interest Per Time Period.
1807
+ '''
1808
+ self.i = i
1809
+ return log(1 + self.i)
1810
+
1811
+ def irr(self,npw, period_list:list, cf_list:list):
1812
+ '''
1813
+ iir: Internal Rate of Return.
1814
+
1815
+ Input arguments:
1816
+ period_list: Period list.
1817
+ cf_list: Cash flow list.
1818
+ i: Effective interest rate.
1819
+ '''
1820
+ self.period_list = period_list
1821
+ self.cf_list = cf_list
1822
+ self.npw = npw
1823
+
1824
+
1825
+ f = lambda x: npw - time_value.npv(self,period_list, cf_list, x)
1826
+
1827
+ irr_ = root(f, [0], tol=0.00000001)['x'][0]
1828
+
1829
+ return irr_