from pandas import DataFrame
from numpy import exp, log
from plotly.express import bar
from plotly.express import area
from scipy.optimize import root
class factor(object):
'''
Engineering economics factors
'''
def pgivenfsp(self, i:float, n:int)->float:
'''
Single-payment present worth factor. Find Present Worth (P) given
a Future worth (F).
Input arguments:
i: Interest rate per (uniform) period.
n: Number of uniform interest periods.
'''
self.i = i
self.n = n
return 1 /((1 + self.i)**(self.n))
def fgivenpsp(self, i:float, n:int)->float:
'''
Single-payment compound amount factor. Find Future Worth (F) given
a Present Worth (P).
Input arguments:
i: Interest rate per (uniform) period.
n: Number of uniform interest periods.
'''
self.i = i
self.n = n
return (1 + self.i)**self.n
def pgivena(self, i:float, n:int)->float:
'''
Uniform series present worth. Find Present Worth (P) given
a Uniform Series (A) of cash flows.
Input arguments:
i: Interest rate per (uniform) period.
n: Number of uniform interest periods.
'''
self.i = i
self.n = n
return ((1 + self.i)**(self.n) - 1)/(self.i*(1+self.i)**self.n)
def agivenp (self, i:float, n:int)->float:
'''
Capital recovery. Find a Uniform Series (A) given a Present
Worth(P).
Input arguments:
i: Interest rate per (uniform) period.
n: Number of uniform interest periods.
'''
self.i = i
self.n = n
return (self.i*(1+self.i)**self.n)/((1 + self.i)**(self.n) - 1)
def fgivena (self, i, n):
'''
Uniform series compound amount. Find a Future Worth (F) given
a Uniform Serie (A).
Input arguments:
i: Interest rate per (uniform) period.
n: Number of uniform interest periods.
'''
self.i = i
self.n = n
return ((1+self.i)**self.n - 1)/self.i
def agivenf (self, i:float, n:int)->float:
'''
Sinking fund. Find a Uniform Serie (A) given a Future
Worth (F).
Input arguments:
i: Interest rate per (uniform) period.
n: Number of uniform interest periods.
'''
self.i = i
self.n = n
return self.i/((1+self.i)**self.n - 1)
def pgivenag (self, i:float, n:int)->float:
'''
Arithmetic Gradient series present worth. Find a Present
Worth (P) given an Arithmetic Gradient Serie (G).
Input arguments:
i: Interest rate per (uniform) period.
n: Number of uniform interest periods.
'''
self.i = i
self.n = n
return(((1+self.i)**self.n) - self.i*self.n - 1)/((self.i**2) * (((1+self.i)**self.n)))
def fgivenag(self, i:float, n:int)->float:
'''
Arithmetic gradient series future worth. Find a future Worth (F) given
an Arithmetic Gradient Serie (G).
Input arguments:
i: Interest rate per (uniform) period.
n: Number of uniform interest periods.
'''
self.i = i
self.n = n
return (1/self.i)*((((1+self.i)**n-1)/self.i)-n)
def agivenag(self, i:float, n:int)->float:
'''
Arithmetic gradient to equal payment series. Find a Unifom Serie (A) given
an Arithmetic Gradient Serie (G).
Input arguments:
i: Interest rate per (uniform) period.
n: Number of uniform interest periods
'''
self.i = i
self.n = n
return (1 / self.i) - (self.n /((1 + self.i)**self.n - 1))
def pgivenga1(self, i:float, n:int, g:float)->float:
'''
Geometric gradient series present worth. Find a Present
Worth (P) given an Geometric Gradient Serie (G).
Input arguments:
i: Interest rate per (uniform) period.
n: Number of uniform interest periods.
g: Geometric gradient or constant percentage or constant growth.
'''
self.i = i
self.n = n
self.g = g
if (self.g != self.i):
return (1 - (((1 + self.g)/(1 + self.i))**self.n))/(self.i - self.g)
else:
return self.n / (1+self.i)
class time_value(object):
"""
Time Value Functions
"""
def cfv(self, CF: float, F: str, i: float, n: float, g: float=None) -> float:
'''
input arguments:
CF: Assessed cash flow
F: Factor types =[
"P/F": Find P Present Worth given F Future worth, interest i and number of periods n.
"F/P": Find F Future worth given P Present Worth, interest i and number of periods n.,
"P/A": Find P Present Worth given A Equal payment series, interest i and number of periods n.
"A/P": Find A Equal payment series given P Present Worth, interest i and number of periods n.
"F/A": Find F Future worth given A Equal payment series, interest i and number of periods n.
"A/F": Find A Equal payment series given F Future worth, interest i and number of periods n.
"P/G": Find P Present Worth given G Arithmetic Gradient, interest i and number of periods n.
"P/g": Find P Present Worth given g Geometric Gradient, A1 First payment, interest i and number of periods n.
]
i: Efective interest rate
n: Term
g: Geometric Gradient
'''
cf_asked = {
"P/F": "PV",
"F/P": "FV",
"P/A": "PV",
"A/P": "A",
"F/A": "FV",
"A/F": "A",
"P/G": "PV",
"F/G": "FV",
"A/G": "A",
"P/g": "PV"
}
cf_given = {
"P/F": "FV",
"F/P": "PV",
"P/A": "A",
"A/P": "PV",
"F/A": "A",
"A/F": "FV",
"P/G": "G",
"F/G": "G",
"A/G": "G",
"P/g": "A1"
}
self.CF = CF
self.F = F
self.i = i
self.n = n
self.g = g
self.values = {}
self.values[cf_given[self.F]] = self.CF
self.values['Factor'] = self.F
self.values['i'] = self.i
self.values['n'] = self.n
if self.g is not None:
self.values['g'] = self.g
factor_list = ["P/F", "F/P", "P/A", "A/P", "F/A", "A/F", "P/G", "F/G", "A/G","P/g"]
try:
if self.F in factor_list:
if self.F == "P/F":
value = self.CF * factor.pgivenfsp(self, self.i, self.n)
elif self.F == "F/P":
value = self.CF * factor.fgivenpsp(self,self.i, self.n)
elif self.F == "P/A":
value = self.CF * factor.pgivena(self, self.i, self.n)
elif self.F == "A/P":
value = self.CF * factor.agivenp(self, self.i, self.n)
elif self.F == "F/A":
value = self.CF * factor.fgivena(self, self.i, self.n)
elif self.F == "A/F":
value = self.CF * factor.agivenf(self, self.i, self.n)
elif self.F == "P/G":
value = self.CF * factor.pgivenag(self, self.i, self.n)
elif self.F == 'F/G':
value = self.CF * factor.fgivenag(self, self.i, self.n)
elif self.F == 'A/G':
value = self.CF * factor.agivenag(self, self.i, self.n)
elif self.F == "P/g":
if g is None:
print ('Input geometric gradient')
else:
value = self.CF * factor.pgivenga1(self, self.i, self.n, self.g)
self.values[cf_asked[self.F]] = value
return self.values
except:
raise Exception("Check arguments")
def pvp(self, a:float, i:float)->float:
'''
Perpetual present value. Find Present Worth (P) given
a Perpetual Uniform Serie (A).
Input arguments:
a: Perpetual Uniform Serie.
i: Interest rate per (uniform) period.
'''
self.a = a
self.i = i
return self.a/self.i
def _getcf(self, period, cf_tuples_list):
'''
This function is used to filter the tuples (p, cf) of a list
that share the same first element (p) of the tuple and
calculate the sum of the cash flows (cf) corresponding to
the filtered element (p).
Input arguments:
period: Period to filter
cf_tuples_list: List of tuples (p, cf)
'''
self.period = period
self.cf_tuples_list = cf_tuples_list
cf_tuples = list(filter(lambda x:self.period in x, self.cf_tuples_list))
if cf_tuples:
pcf = 0
for p, tcf in cf_tuples:
pcf += tcf
return (p, pcf)
else:
return (self.period, 0)
def npv(self, period_list:list, cf_list:list, i:float)->float:
'''
This function is used to estimate the net present value
from a list of cash flows, a list of the corresponding
periods to calculate the present value and an effective
interest rate.
Input arguments:
period_list: Period list
cf_list: Cash flow list
i: Effective interest rate
'''
self.period_list = period_list
self.cf_list = cf_list
self.i = i
p_len = len(self.period_list)
cf_len = len(self.cf_list)
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})."
#assert i > 0, f"Interest rate {i} must be greater than 0"
n_max=max(self.period_list) + 1
CFL = list(zip(self.period_list, self.cf_list))
ncf = []
for p in range (n_max):
self.p = p
self.cf_tuples_list=CFL
cf_tuples = time_value._getcf(self, self.p, self.cf_tuples_list)
ncf.append(cf_tuples)
dcf = [x[1] / (1+i)**x[0] for x in ncf ]
npv_ = sum(dcf)
return npv_
def npviv(self, cf_list:list, iv:list)->float:
'''
This function estimates the net present value for several cash
flows with different effective rates for each period. In this
case there should be only one cash flow and one interest rate
for each period. For the calculation to be consistent, the spacing
between periods must be uniform (monthly, bimonthly, annually, etc.)
and the effective interest rates for each period must correspond
to the same periodicity.
Input arguments:
cf_list
iv
'''
len_cf_list = len(cf_list)
len_iv = len(iv)
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"
self.period_list = list(range(len_iv))
self.cf_list = cf_list
self.iv = iv
n_max=max(self.period_list) + 1
CFL = list(zip(self.period_list, self.cf_list))
i = []
i_comp =1
for r in self.iv:
i_comp *= (1+r)
i.append(i_comp)
ncf = []
for p in range(n_max):
self.p = p
self.cf_tuples_list=CFL
_, cf = time_value._getcf(self, self.p,self.cf_tuples_list)
ncf.append((p, cf, i[p]))
dcf = [x[1] / x[2] for x in ncf ]
npv_ = sum(dcf)
self.period_list[0] = self.period_list[0] + npv_
return npv_
def vpn_terminal_value(self, cf_n: float, r:float, g:float, n:float)->float:
'''
This function returns two values. The first is the terminal value in period n.
The second returns the present value of this terminal value in period 0.
Input arguments:
cf_n : Cash flow at the end of period n
r: Discount cash flow rate at year n
g: Growth rate
n: Discount valuation period
'''
assert r != g, f"The interest rate {r} must be diferent from growth {g}"
self.cf_n = cf_n
self.r = r
self.g = g
self.n = n
self.tval = (self.cf_n * ( 1 + self.g)) / (self.r - self.g)
self.tvpv = self.tval * factor.pgivenfsp(self, self.r, self.n)
return self.tval, self.tvpv
def vpn_terminal_value_variable_rates(self, cf_n:float, rate_list:list, g:float)->float:
'''
This function returns two values. The first is the terminal value in period n.
The second returns the present value of this terminal value in period 0.
Input arguments:
cf_n : Cash flow at the end of period n
r: Discount cash flow rate at year n
g: Growth rate
'''
len_r = len(rate_list)
r = rate_list[len_r-1]
assert r != g, f"The interest rate {r} must be diferent from growth {g}"
self.r = r
self.cf_n = cf_n
self.r_list = rate_list
self.g = g
self.tval = (self.cf_n * ( 1 + self.g)) / (self.r - self.g)
cf_list = [0] * len_r
cf_list[len_r-1] = self.tval
self.cf_list = cf_list
self.tvpv = time_value.npviv(self, self.cf_list, self.r_list)
return self.tval, self.tvpv
class time_value_table(object):
"""
Cash Flow Tables
"""
def cfdataframe(self, cf_dic:dict):
'''
Passes the result of a dictionary of economic engineering formulas to a pandas dataframe.
'''
self.cf_dic= cf_dic
if self.cf_dic == None:
return None
if (self.cf_dic['Factor'] == 'P/F') or (self.cf_dic['Factor'] == 'F/P'):
pv = -round(self.cf_dic['PV'], 4)
fv = round(self.cf_dic['FV'], 4)
n = self.cf_dic['n']
x_data = range(n+1)
y_o_data = [pv] + [0.] * (n)
y_i_data = [0.] * (n) + [fv]
return DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Income", "Outcome"])
if (self.cf_dic['Factor'] == 'P/A') or (self.cf_dic['Factor'] == 'A/P'):
pv = -round(self.cf_dic['PV'], 4)
a = round(self.cf_dic['A'], 4)
n = cf_dic['n']
x_data = range(n+1)
y_i_data = [0.] + [a] * (n)
y_o_data = [0.] * (n+1)
y_o_data[0] = pv
return DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Income", "Outcome"])
if (self.cf_dic['Factor'] == 'F/A') or (self.cf_dic['Factor'] == 'A/F'):
fv = round(self.cf_dic['FV'], 4)
a = -round(self.cf_dic['A'], 4)
n = cf_dic['n']
x_data = range(n+1)
y_o_data = [0.] + [a] * (n)
y_i_data = [0.] * (n+1)
y_i_data[-1] = fv
return DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Income", "Outcome"])
if (self.cf_dic['Factor'] == 'P/G'):
pv = -round(self.cf_dic['PV'], 4)
ag = round(self.cf_dic['G'], 4)
n = cf_dic['n']
x_data = range(n+1)
y_ag_data = [0.] + [k * ag for k in range(n)]
y_o_data = [0.] * (n+1)
y_o_data[0] = pv
return DataFrame(list(zip(x_data, y_ag_data, y_o_data)), columns=["Period", "Gradient Income", "Outcome"])
if (self.cf_dic['Factor'] == 'A/G'):
a = -round(self.cf_dic['A'], 4)
ag = round(self.cf_dic['G'], 4)
n = cf_dic['n']
x_data = range(n+1)
y_ag_data = [0.] + [k * ag for k in range(n)]
y_o_data = [0.] + [a] * (n)
return DataFrame(list(zip(x_data, y_ag_data, y_o_data)), columns=["Period", "Gradient Income", "Outcome"])
if (self.cf_dic['Factor'] == 'F/G'):
fv = round(self.cf_dic['FV'], 4)
ag = -round(self.cf_dic['G'], 4)
n = cf_dic['n']
x_data = range(n+1)
y_ag_data = [0.] + [k * ag for k in range(n)]
y_o_data = [0.] * (n+1)
y_o_data[-1] = fv
return DataFrame(list(zip(x_data, y_ag_data, y_o_data)), columns=["Period", "Gradient Outcome", "Income"])
if (self.cf_dic['Factor'] == 'P/g'):
pv = -round(self.cf_dic['PV'], 4)
gg = round(self.cf_dic['g'], 4)
ba = round(self.cf_dic['A1'], 4)
n = cf_dic['n']
x_data = range(n+1)
y_i_data = [0] + [ba * (1 + gg)**k for k in range(n)]
y_o_data = [0.] * (n+1)
y_o_data[0] = pv
return DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Gradient Income", "Outcome"])
def npvtable(self, period_list:list, cf_list:list, i:float):
"""
Creates a pandas dataframe for a cash flow of a given length
input arguments:
period_list: Term cash flow list
cf_list: Cash flow list to evalute
i: cash flow interest rate
"""
self.period_list = period_list
self.cf_list = cf_list
self.i = i
p_len = len(self.period_list)
cf_len = len(self.cf_list)
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})."
assert i > 0, f"Interest rate {i} must be greater than 0"
n_max=max(self.period_list) + 1
CFL = list(zip(self.period_list, self.cf_list))
ncfl = []
for p in range(n_max):
cf_tuples = list(filter(lambda x:p in x, CFL))
if cf_tuples:
income = 0
outcome = 0
for n in range(len(cf_tuples)):
if cf_tuples[n][1] > 1:
income += cf_tuples[n][1]*1.0
outcome += 0.
else:
income += 0.
outcome += cf_tuples[n][1]*1.0
ncf = income + outcome
self.n = p
pv = ncf * factor.pgivenfsp(self, self.i,self.n)
ncfl.append((p, outcome, income, ncf, pv))
else:
ncfl.append((p, 0, 0, 0, 0))
return DataFrame(ncfl, columns=['Period', 'Outcome', 'Income', 'ncf', 'dcf'])
def npvsensitivitytable(self, period_list:list, cf_list:list, i_list:list):
'''
This function allows you to calculate different net present
values for the same cash flow from a list of different interest
rates.
Input arguments:
period_list: Period list
cf_list: Cash flow list
i_list: Effective interest rate list
'''
self.period_list = period_list
self.cf_list = cf_list
self.i_list = i_list
self.i_list.sort()
table = [(time_value.npv(self.period_list, self.cf_list, r), r) for r in self.i_list]
return DataFrame(table, columns=['npv', 'i'])
def npvivtable(self, period_list:list, cf_list:list, iv:list):
'''
Input arguments:
period_list: Cash flow term list
cf_list: Cash flow list
iv: Variable interest list
'''
len_period_list = len(period_list)
len_cf_list = len(cf_list)
len_iv = len(iv)
for p in period_list:
c = period_list.count(p)
if c > 1:
raise Exception(f"There should only be one cash flow per period. Period {p} has {c} elements")
assert len_period_list == len_cf_list and len_period_list == len_iv, f"The inpt list has not the same number of elements"
self.period_list = period_list
self.cf_list = cf_list
self.iv = iv
n_max=max(period_list) + 1
CFL = list(zip(self.period_list, self.cf_list))
i = []
i_comp =1
for r in self.iv:
i_comp *= (1+r)
i.append(i_comp)
ncf = []
income = 0
outcome = 0
for p in range (n_max):
self.p = p
self.cf_tuples_list=CFL
_, cf = time_value._getcf(self, self.p,self.cf_tuples_list)
if cf>0:
income = cf
outcome = 0
else:
income = 0
outcome = cf
if p ==0:
ie = (i[p]**(1))-1
else:
ie = (i[p]**(1/p))-1
dcf = cf/((1+ie)**p)
ncf.append((p, income, outcome, cf, i[p], ie, dcf))
df = DataFrame(ncf, columns=["Period", 'Outcome','Income',"ncf", "i_comp", "ie", "dcf"])
return df
def uniform_loan_amortization(self, loan_amount:float, rate:float, loan_term:int, periodicity:str='Y'):
"""
loan_amount: Amount to lend
rate: fixed interest rates during the loan period
loan_term: Duration of loan
periodicity: Frequency of loan payments ({'M':'Month', 'B':'Bimonth', 'Q':'Quarter','S': 'Semiannual', 'Y':'Year')
per_conv = ['M', 'B', 'Q','S', 'Y']
per_names = {'M':'Month', 'B':'Bimonth', 'Q':'Quarter','S': 'Semiannual', 'Y':'Year'}
"""
per_conv = ['M', 'B', 'Q','S', 'Y']
per_names = {'M':'Month', 'B':'Bimonth', 'Q':'Quarter','S': 'Semiannual', 'Y':'Year'}
assert periodicity in per_conv, f"Input 'Y' for Year, 'S' for Semiannual, 'Q' for Quarterly, 'B' for Bimonthly and 'M' for Monthly"
self.loan_amount= loan_amount
self.rate = rate
self.loan_term = loan_term
self.periodicity = periodicity
per = list(range(self.loan_term + 1))
beg_bal = []
pay_per = []
pri_per = []
int_per = []
tot_pay = []
tot_int = []
rem_bal = []
period_payment = round(self.loan_amount * factor.agivenp(self, i=rate, n=loan_term), 2)
pay_per.append(0)
beg_bal.append(loan_amount)
pri_per.append(0)
int_per.append(0)
tot_pay.append(0)
tot_int.append(0)
rem_bal.append(loan_amount)
for p in range(1, self.loan_term + 1):
beginning_balance = rem_bal[p-1]
period_interest = round(rem_bal[p-1] * self.rate, 2)
period_principal = period_payment - period_interest
total_principal = sum(pri_per) + period_principal
total_interest = sum(int_per) + period_interest
remaining_balance = beginning_balance - period_principal
beg_bal.append(beginning_balance)
pay_per.append(period_payment)
pri_per.append(period_principal)
int_per.append(period_interest)
tot_pay.append(total_principal)
tot_int.append(total_interest)
rem_bal.append(remaining_balance)
data = zip(per, beg_bal, pay_per, pri_per, int_per, tot_pay, tot_int, rem_bal)
period = per_names[self.periodicity]
columns = [period, 'Beginning balance', 'Payment', 'Principal', 'Interest', 'Total Payment', 'Total Interest', 'Remaining Balance' ]
amortization_table = DataFrame(data=data, columns=columns)
return amortization_table
def variable_payment_loan_amortization(self, loan_amount:float, rate:list, loan_term:int, periodicity:str='Y'):
"""
loan_amount: Amount to lend
rate: List of variable or fixed interest rates during the loan period
uniform capital payment: loan_amount / loan_term
loan_term: Duration of loan
periodicity: Frequency of loan payments ({'M':'Month', 'B':'Bimonth', 'Q':'Quarter','S': 'Semiannual', 'Y':'Year')
variable interest payment: Net balance * rate in period p
"""
assert isinstance(rate, list), "Argument rate must be a list"
assert len(rate)==loan_term, "Argument rate must has a len equal to loan_term"
per_conv = ['M', 'B', 'Q','S', 'Y']
per_names = {'M':'Month', 'B':'Bimonth', 'Q':'Quarter','S': 'Semiannual', 'Y':'Year'}
assert periodicity in per_conv, f"Input 'Y' for Year, 'S' for Semiannual, 'Q' for Quarterly, 'B' for Bimonthly and 'M' for Monthly"
self.loan_amount= loan_amount
# self.rate = rate
self.loan_term = loan_term
self.periodicity = periodicity
per = list(range(self.loan_term + 1))
beg_bal = []
pay_per = []
pri_per = []
int_per = []
tot_pay = []
tot_int = []
rem_bal = []
period_principal = round(self.loan_amount / self.loan_term, 2)
pay_per.append(0)
beg_bal.append(self.loan_amount)
pri_per.append(0)
int_per.append(0)
tot_pay.append(0)
tot_int.append(0)
rem_bal.append(self.loan_amount)
for p in range(1, self.loan_term + 1):
beginning_balance = rem_bal[p-1]
period_interest = round(rem_bal[p-1] * rate[p-1], 2)
period_payment = period_principal + period_interest
total_principal = sum(pri_per) + period_payment
total_interest = sum(int_per) + period_interest
remaining_balance = beginning_balance - period_principal
beg_bal.append(beginning_balance)
pay_per.append(period_payment)
pri_per.append(period_principal)
int_per.append(period_interest)
tot_pay.append(total_principal)
tot_int.append(total_interest)
rem_bal.append(remaining_balance)
data = zip(per, beg_bal, pay_per, pri_per, int_per, tot_pay, tot_int, rem_bal)
period = per_names[self.periodicity]
columns = [period, 'Beginning balance', 'Payment', 'Principal', 'Interest', 'Total Payment', 'Total Interest', 'Remaining Balance' ]
amortization_table = DataFrame(data=data, columns=columns)
return amortization_table
class time_value_plot(object):
def cf_plot_bar(self, cf_dic:dict):
"""
Cash Flow plot bars type
"""
self.cf_dic = cf_dic
if self.cf_dic == None:
self.cf_dic = {}
else:
self.cf_dic = cf_dic
if (self.cf_dic['Factor'] == 'P/F') or (self.cf_dic['Factor'] == 'F/P'):
pv = -round(self.cf_dic['PV'], 4)
fv = round(self.cf_dic['FV'], 4)
n = self.cf_dic['n']
x_data = range(n+1)
y_o_data = [pv] + [0] * (n)
y_i_data = [0.] * (n) + [fv]
df = DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Income", "Outcome"])
print(df)
if (self.cf_dic['Factor'] == 'P/F'):
title = f"{fv: .4f} * (P/F, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {-pv:.4f}"
else:
title = f"{-pv: .4f} * (F/P, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {fv:.4f}"
fig = bar(df,
x="Period",
y= ["Income", "Outcome"],
title= title ,
text_auto=True,
opacity=0.80,
facet_col_spacing= 0.0
)
y_max = round(fv,0) + 0.5
y_min = round(-pv, 0) + 0.5
pp = y_min / (y_min + y_max)
fig.update_layout( bargap=0.05,bargroupgap=0.75)
fig.update_traces(textangle=90, selector=dict(type='bar'))
fig.update_xaxes(position= pp,
anchor="free",
linecolor = "black",
tickfont=dict(size=12, color='black'),
ticks = "outside",
ticklabelposition = "outside left"
)
fig.update_layout(yaxis_range=[-y_min, y_max])
return(fig.show())
if (self.cf_dic['Factor'] == 'P/A') or (self.cf_dic['Factor'] == 'A/P'):
pv = -round(self.cf_dic['PV'], 4)
a = round(self.cf_dic['A'], 4)
n = cf_dic['n']
x_data = range(n+1)
y_i_data = [0.] + [a] * (n)
y_o_data = [0.] * (n+1)
y_o_data[0] = pv
df = DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Income", "Outcome"])
print(df)
if (self.cf_dic['Factor'] == 'P/A'):
title = f"{a: .4f} * (P/A, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {-pv:.4f}"
else:
title = f"{-pv: .4f} * (A/P, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {a:.2f}"
fig = bar(
df,
x="Period",
y=["Income", "Outcome"],
title= title ,
text_auto=True,
opacity=0.80,
facet_col_spacing= 0.0,
)
y_max = round(a,0) + 0.5
y_min = round(-pv, 0) + 0.5
pp = y_min / (y_min + y_max)
fig.update_layout(bargap=0.05,bargroupgap=0.75)
fig.update_traces(textangle=90, selector=dict(type='bar'))
fig.update_xaxes(position= pp,
anchor="free",
linecolor = "black",
tickfont=dict(size=12, color='black'),
ticks = "outside",
ticklabelposition = "outside left"
)
fig.update_yaxes(title="Cash Flow [$]")
fig.update_layout(yaxis_range=[-y_min, y_max])
fig.update_yaxes(title="Cash Flow [$]")
return(fig.show())
if (self.cf_dic['Factor'] == 'F/A') or (self.cf_dic['Factor'] == 'A/F'):
fv = round(self.cf_dic['FV'], 4)
a = -round(self.cf_dic['A'], 4)
n = cf_dic['n']
x_data = range(n+1)
y_o_data = [0.] + [a] * (n)
y_i_data = [0.] * (n+1)
y_i_data[-1] = fv
df = DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Income", "Outcome"])
print(df)
if (self.cf_dic['Factor'] == 'F/A'):
title = f"{-a: .4f} * (F/A, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {fv:.4f}"
else:
title = f"{fv: .4f} * (A/F, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {-a:.4f}"
fig = bar(df,
x="Period",
y=["Income", "Outcome"],
title= title ,
text_auto=True,
opacity=0.80,
facet_col_spacing= 0.0,
)
y_max = round(fv,0) + 10
y_min = round(-a, 0) + 10
pp = y_min / ( y_min + y_max)
fig.update_layout(bargap=0.05,bargroupgap=0.75)
fig.update_traces(textangle=90, selector=dict(type='bar'))
fig.update_xaxes(position= pp,
anchor="free",
linecolor = "black",
tickfont=dict(size=12, color='black'),
ticks = "outside",
ticklabelposition = "outside left"
)
fig.update_yaxes(title="Cash Flow [$]")
fig.update_layout(yaxis_range=[-y_min, y_max])
return(fig.show())
if (self.cf_dic['Factor'] == 'P/G'):
pv = -round(self.cf_dic['PV'], 4)
ag = round(self.cf_dic['G'], 4)
n = cf_dic['n']
x_data = range(n+1)
y_ag_data = [0.] + [k * ag for k in range(n)]
y_o_data = [0.] * (n+1)
y_o_data[0] = pv
df = DataFrame(list(zip(x_data, y_ag_data, y_o_data)), columns=["Period", "Gradient Income", "Outcome"])
print(df)
title = f"{ag:.2f} * (P/G, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {-pv:.4f}"
fig = bar(
df,
x="Period",
y=["Gradient Income", "Outcome"],
title= title ,
text_auto=True,
opacity=0.80,
facet_col_spacing= 0.0
)
y_max = round(ag * (n-1) * 1.5,0)
y_min = round(-pv, 0) + 0.5
pp = y_min / (y_min + y_max)
fig.update_layout( bargap=0.05,bargroupgap=0.75)
fig.update_traces(textangle=90, selector=dict(type='bar'))
fig.update_xaxes(position= pp,
anchor="free",
linecolor = "black",
tickfont=dict(size=12, color='black'),
ticks = "outside",
ticklabelposition = "outside left"
)
fig.update_yaxes(title="Cash Flow [$]")
fig.update_layout(yaxis_range=[-y_min, y_max])
return(fig.show())
if (self.cf_dic['Factor'] == 'A/G'):
a = -round(self.cf_dic['A'], 4)
ag = round(self.cf_dic['G'], 4)
n = cf_dic['n']
x_data = range(n+1)
y_ag_data = [0.] + [k * ag for k in range(n)]
y_o_data = [0.] + [a] * (n)
df = DataFrame(list(zip(x_data, y_ag_data, y_o_data)), columns=["Period", "Gradient Income", "Outcome"])
print(df)
title = f"{ag:.2f} * (A/G, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {-a:.4f}"
fig = bar(
df,
x="Period",
y=["Outcome", "Gradient Income"],
title= title ,
text_auto=True,
opacity=0.80,
facet_col_spacing= 0.0
)
y_max = round(ag * (n-1) * 1.5,0)
y_min = round(-a, 0) + 0.5
pp = y_min / (y_min + y_max)
fig.update_layout( bargap=0.05,bargroupgap=0.75)
fig.update_traces(textangle=90, selector=dict(type='bar'))
fig.update_xaxes(position= pp,
anchor="free",
linecolor = "black",
tickfont=dict(size=12, color='black'),
ticks = "outside",
ticklabelposition = "outside left"
)
fig.update_yaxes(title="Cash Flow [$]")
fig.update_layout(yaxis_range=[-y_min, y_max])
return(fig.show())
if (self.cf_dic['Factor'] == 'F/G'):
fv = round(self.cf_dic['FV'], 4)
ag = -round(self.cf_dic['G'], 4)
n = cf_dic['n']
x_data = range(n+1)
y_ag_data = [0.] + [k * ag for k in range(n)]
y_o_data = [0.] * (n+1)
y_o_data[-1] = fv
df = DataFrame(list(zip(x_data, y_ag_data, y_o_data)), columns=["Period", "Gradient Outcome", "Income"])
print(df)
title = f"{-ag:.2f} * (F/G, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {fv:.4f}"
fig = bar(
df,
x="Period",
y=["Income", "Gradient Outcome"],
title= title ,
text_auto=True,
opacity=0.80,
facet_col_spacing= 0.0
)
y_max = round(fv, 0) + 0.5
y_min = round(-ag * (n-1) * 1.5,0)
pp = y_min / (y_min + y_max)
fig.update_layout( bargap=0.05,bargroupgap=0.75)
fig.update_traces(textangle=90, selector=dict(type='bar'))
fig.update_xaxes(position= pp,
anchor="free",
linecolor = "black",
tickfont=dict(size=12, color='black'),
ticks = "outside",
ticklabelposition = "outside left"
)
fig.update_yaxes(title="Cash Flow [$]")
fig.update_layout(yaxis_range=[-y_min, y_max])
return(fig.show())
if (self.cf_dic['Factor'] == 'P/g'):
pv = -round(self.cf_dic['PV'], 4)
gg = round(self.cf_dic['g'], 4)
ba = round(self.cf_dic['A1'], 4)
n = cf_dic['n']
x_data = range(n+1)
y_i_data = [0] + [round(ba * (1 + gg)**k,4) for k in range(n)]
y_o_data = [0.] * (n+1)
y_o_data[0] = round(pv,4)
df = DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Gradient Income", "Outcome"])
print(df)
title = f"{ba: .2f} * (P/g, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}, g: {gg*100:.2f}) = {-pv:.4f}"
fig = bar(
df,
x="Period",
y=["Gradient Income", "Outcome"],
title= title ,
text_auto=True,
opacity=0.80,
facet_col_spacing= 0.0
)
y_max = round(ba * (1+gg)**(n-1) * 1.2,0 )
y_min = round(-pv, 0) + 0.5
pp = y_min / (y_min + y_max)
fig.update_layout( bargap=0.05,bargroupgap=0.75)
fig.update_traces(textangle=90, selector=dict(type='bar'))
fig.update_xaxes(position= pp,
anchor="free",
linecolor = "black",
tickfont=dict(size=12, color='black'),
ticks = "outside",
ticklabelposition = "outside left"
)
fig.update_layout(yaxis_range=[-y_min, y_max])
fig.update_yaxes(title="Cash Flow [$]")
return(fig.show())
def __arrowupdate(df_x, df_y):
counter = 0
arrow_list = []
text_list = []
for i in df_y.tolist():
if i != 0:
if i>0:
arrowcolor = 'rgb(77,7,252)'
xanchor = 'right'
text = round(i,2)
textangle = 0
visible=True
else:
arrowcolor = 'rgb(252,7,77)'
xanchor = 'left'
text = round(i,2)
textangle = 0
visible=True
arrow_aux=dict(x=df_x.values[counter],
y=df_y.values[counter],
xref='x',
ax=counter,
ay=0,
yref='y',
axref='x',
ayref='y',
text='',
showarrow=True,
arrowhead=1,
arrowsize=2,
arrowwidth=2,
arrowcolor=arrowcolor,
xanchor = xanchor,
yanchor='bottom'
)
text_aux = dict(text = text,
textangle = textangle,
visible=visible,
)
arrow_list.append(arrow_aux)
text_list.append(text_aux)
counter += 1
else:
counter += 1
return arrow_list, text_list
def cf_plot_arrow(self, cf_dic):
if self.cf_dic == None:
self.cf_dic = {}
else:
self.cf_dic = cf_dic
if (self.cf_dic['Factor'] == 'P/F') or (self.cf_dic['Factor'] == 'F/P'):
pv = -round(self.cf_dic['PV'], 4)
fv = round(self.cf_dic['FV'], 4)
n = self.cf_dic['n']
x_data = range(n+1)
y_o_data = [pv] + [0.] * (n+1)
y_i_data = [0.] * (n) + [fv]
df= DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Income", "Outcome"])
print(df)
if (self.cf_dic['Factor'] == 'P/F'):
title = f"{fv: .4f} * (P/F, i: {self.cf_dic['i']*100:.2f}% n: {self.cf_dic['n']}) = {-pv:.4f}"
else:
title = f"{-pv: .4f} * (F/P, i: {self.cf_dic['i']*100:.2f}% n: {self.cf_dic['n']}) = {fv:.4f}"
fig = bar(df,
x="Period",
y=["Income", "Outcome"],
title= title ,
text_auto=False,
opacity=0.0,
facet_col_spacing= 0.0,
)
y_max = round(fv,0) + 5
y_min = round(-pv, 0) + 5
pp = y_min / (y_min + y_max)
fig.update_layout( bargap=0.05,bargroupgap=0.75)
fig.update_traces(textangle=90, selector=dict(type='bar'))
fig.update_xaxes(position= pp,
anchor="free",
linecolor = "black",
tickfont=dict(size=12, color='black'),
ticks = "outside",
ticklabelposition = "outside left"
)
fig.update_layout(yaxis_range=[-y_min, y_max])
arrow_list_1, text_list_1 = time_value_plot.__arrowupdate(df['Period'], df["Income"])
arrow_list_2, text_list_2 = time_value_plot.__arrowupdate(df['Period'], df["Outcome"])
fig.update_layout(annotations=arrow_list_1 + arrow_list_2)
fig.update_layout(annotations=text_list_1 + text_list_2)
fig.update_traces(textposition='inside')
return(fig.show())
if (self.cf_dic['Factor'] == 'P/A') or (self.cf_dic['Factor'] == 'A/P'):
pv = -round(self.cf_dic['PV'], 4)
a = round(self.cf_dic['A'], 4)
n = cf_dic['n']
x_data = range(n+1)
y_i_data = [0.] + [a] * (n)
y_o_data = [0.] * (n+1)
y_o_data[0] = pv
df = DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Income", "Outcome"])
print(df)
if (self.cf_dic['Factor'] == 'P/A'):
title = f"{a: .4f} * (P/A, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {-pv:.4f}"
else:
title = f"{-pv: .4f} * (A/P, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {a:.2f}"
fig = bar(
df,
x="Period",
y=["Income", "Outcome"],
title= title ,
text_auto=False,
opacity=0.0,
facet_col_spacing= 0.0,
)
y_max = round(a,0) + 0.5
y_min = round(-pv, 0) + 0.5
pp = y_min / (y_min + y_max)
fig.update_layout( bargap=0.05,bargroupgap=0.75)
fig.update_traces(textangle=90, selector=dict(type='bar'))
fig.update_xaxes(position= pp,
anchor="free",
linecolor = "black",
tickfont=dict(size=12, color='black'),
ticks = "outside",
ticklabelposition = "outside left"
)
fig.update_layout(yaxis_range=[-y_min, y_max])
arrow_list_1, text_list_1 = time_value_plot.__arrowupdate(df['Period'], df["Income"])
arrow_list_2, text_list_2 = time_value_plot.__arrowupdate(df['Period'], df["Outcome"])
fig.update_layout(annotations=arrow_list_1 + arrow_list_2)
fig.update_layout(annotations=text_list_1 + text_list_2)
fig.update_traces(textposition='inside')
return(fig.show())
if (self.cf_dic['Factor'] == 'F/A') or (self.cf_dic['Factor'] == 'A/F'):
fv = round(self.cf_dic['FV'], 4)
a = -round(self.cf_dic['A'], 4)
n = cf_dic['n']
x_data = range(n+1)
y_o_data = [0.] + [a] * (n)
y_i_data = [0.] * (n+1)
y_i_data[-1] = fv
df = DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Income", "Outcome"])
print(df)
if (self.cf_dic['Factor'] == 'F/A'):
title = f"{-a: .4f} * (F/A, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {fv:.4f}"
else:
title = f"{fv: .4f} * (A/F, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {-a:.4f}"
fig = bar(df,
x="Period",
y=["Income", "Outcome"],
title= title ,
text_auto=False,
opacity=0.0,
facet_col_spacing= 0.0
)
y_max = round(fv,0) + 0.5
y_min = -round(a, 0) + 0.5
pp = y_min / (y_min + y_max)
fig.update_layout( bargap=0.05,bargroupgap=0.75)
fig.update_traces(textangle=90, selector=dict(type='bar'))
fig.update_xaxes(position= pp,
anchor="free",
linecolor = "black",
tickfont=dict(size=12, color='black'),
ticks = "outside",
ticklabelposition = "outside left"
)
fig.update_layout(yaxis_range=[-y_min, y_max])
arrow_list_1, text_list_1 = time_value_plot.__arrowupdate(df['Period'], df["Income"])
arrow_list_2, text_list_2 = time_value_plot.__arrowupdate(df['Period'], df["Outcome"])
fig.update_layout(annotations=arrow_list_1 + arrow_list_2)
fig.update_layout(annotations=text_list_1 + text_list_2)
fig.update_traces(textposition='inside')
return(fig.show())
if (self.cf_dic['Factor'] == 'P/G'):
pv = -round(self.cf_dic['PV'], 4)
ag = round(self.cf_dic['G'], 4)
n = cf_dic['n']
x_data = range(n+1)
y_ag_data = [0.] + [k * ag for k in range(n)]
y_o_data = [0.] * (n+1)
y_o_data[0] = pv
df = DataFrame(list(zip(x_data, y_ag_data, y_o_data)), columns=["Period", "Gradient Income", "Outcome"])
print(df)
title = f"{ag:.2f} * (P/G, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {-pv:.4f}"
fig = bar(
df,
x="Period",
y=["Gradient Income", "Outcome"],
title= title ,
text_auto=False,
opacity=0.0,
facet_col_spacing= 0.0
)
y_max = round(ag * (n-1) * 1.5,0)
y_min = round(-pv, 0) + 0.5
pp = y_min / (y_min + y_max)
fig.update_layout( bargap=0.05,bargroupgap=0.75)
fig.update_traces(textangle=90, selector=dict(type='bar'))
fig.update_xaxes(position= pp,
anchor="free",
linecolor = "black",
tickfont=dict(size=12, color='black'),
ticks = "outside",
ticklabelposition = "outside left"
)
fig.update_layout(yaxis_range=[-y_min, y_max])
arrow_list_1, text_list_1 = time_value_plot.__arrowupdate(df['Period'], df["Gradient Income"])
arrow_list_2, text_list_2 = time_value_plot.__arrowupdate(df['Period'], df["Outcome"])
fig.update_layout(annotations=arrow_list_1 + arrow_list_2)
fig.update_layout(annotations=text_list_1 + text_list_2)
fig.update_traces(textposition='inside')
return(fig.show())
if (self.cf_dic['Factor'] == 'A/G'):
a = -round(self.cf_dic['A'], 4)
ag = round(self.cf_dic['G'], 4)
n = cf_dic['n']
x_data = range(n+1)
y_ag_data = [0.] + [k * ag for k in range(n)]
y_o_data = [0.] + [a] * (n)
df = DataFrame(list(zip(x_data, y_ag_data, y_o_data)), columns=["Period", "Gradient Income", "Outcome"])
print(df)
title = f"{ag:.2f} * (A/G, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {-a:.4f}"
fig = bar(
df,
x="Period",
y=["Outcome", "Gradient Income"],
title= title ,
text_auto=False,
opacity=0.0,
facet_col_spacing= 0.0
)
y_max = round(ag * (n-1) * 1.5,0)
y_min = round(-a, 0) + 0.5
pp = y_min / (y_min + y_max)
fig.update_layout( bargap=0.05,bargroupgap=0.75)
fig.update_traces(textangle=90, selector=dict(type='bar'))
fig.update_xaxes(position= pp,
anchor="free",
linecolor = "black",
tickfont=dict(size=12, color='black'),
ticks = "outside",
ticklabelposition = "outside left"
)
fig.update_yaxes(title="Cash Flow [$]")
fig.update_layout(yaxis_range=[-y_min, y_max])
arrow_list_1, text_list_1 = time_value_plot.__arrowupdate(df['Period'], df["Gradient Income"])
arrow_list_2, text_list_2 = time_value_plot.__arrowupdate(df['Period'], df["Outcome"])
fig.update_layout(annotations=arrow_list_1 + arrow_list_2)
fig.update_layout(annotations=text_list_1 + text_list_2)
fig.update_traces(textposition='inside')
return(fig.show())
if (self.cf_dic['Factor'] == 'F/G'):
fv = round(self.cf_dic['FV'], 4)
ag = -round(self.cf_dic['G'], 4)
n = cf_dic['n']
x_data = range(n+1)
y_ag_data = [0.] + [k * ag for k in range(n)]
y_o_data = [0.] * (n+1)
y_o_data[-1] = fv
df = DataFrame(list(zip(x_data, y_ag_data, y_o_data)), columns=["Period", "Gradient Outcome", "Income"])
print(df)
title = f"{-ag:.2f} * (F/G, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}) = {fv:.4f}"
fig = bar(
df,
x="Period",
y=["Income", "Gradient Outcome"],
title= title ,
text_auto=False,
opacity=0.0,
facet_col_spacing= 0.0
)
y_max = round(fv, 0) + 0.5
y_min = round(-ag * (n-1) * 1.5,0)
pp = y_min / (y_min + y_max)
fig.update_layout( bargap=0.05,bargroupgap=0.75)
fig.update_traces(textangle=90, selector=dict(type='bar'))
fig.update_xaxes(position= pp,
anchor="free",
linecolor = "black",
tickfont=dict(size=12, color='black'),
ticks = "outside",
ticklabelposition = "outside left"
)
fig.update_yaxes(title="Cash Flow [$]")
fig.update_layout(yaxis_range=[-y_min, y_max])
arrow_list_1, text_list_1 = time_value_plot.__arrowupdate(df['Period'], df["Gradient Outcome"])
arrow_list_2, text_list_2 = time_value_plot.__arrowupdate(df['Period'], df["Income"])
fig.update_layout(annotations=arrow_list_1 + arrow_list_2)
fig.update_layout(annotations=text_list_1 + text_list_2)
fig.update_traces(textposition='inside')
return(fig.show())
if (self.cf_dic['Factor'] == 'P/g'):
pv = -round(self.cf_dic['PV'], 4)
gg = round(self.cf_dic['g'], 4)
ba = round(self.cf_dic['A1'], 4)
n = cf_dic['n']
x_data = range(n+1)
y_i_data = [0] + [round(ba * (1 + gg)**k,4) for k in range(n)]
y_o_data = [0.] * (n+1)
y_o_data[0] = round(pv,4)
df = DataFrame(list(zip(x_data, y_i_data, y_o_data)), columns=["Period", "Gradient Income", "Outcome"])
print(df)
title = f"{ba: .2f} * (P/g, i: {self.cf_dic['i']*100:.2f}%, n: {self.cf_dic['n']}, g: {gg*100:.2f}) = {-pv:.4f}"
fig = bar(
df,
x="Period",
y=["Gradient Income", "Outcome"],
title= title ,
text_auto=False,
opacity=0.0,
facet_col_spacing= 0.0
)
y_max = round(ba * (1+gg)**(n-1) * 1.2,0 )
y_min = round(-pv, 0) + 0.5
pp = y_min / (y_min + y_max)
fig.update_layout( bargap=0.05,bargroupgap=0.75)
fig.update_traces(textangle=90, selector=dict(type='bar'))
fig.update_xaxes(position= pp,
anchor="free",
linecolor = "black",
tickfont=dict(size=12, color='black'),
ticks = "outside",
ticklabelposition = "outside left"
)
fig.update_layout(yaxis_range=[-y_min, y_max])
fig.update_yaxes(title="Cash Flow [$]")
arrow_list_1, text_list_1 = time_value_plot.__arrowupdate(df['Period'], df["Gradient Income"])
arrow_list_2, text_list_2 = time_value_plot.__arrowupdate(df['Period'], df["Outcome"])
fig.update_layout(annotations=arrow_list_1 + arrow_list_2)
fig.update_layout(annotations=text_list_1 + text_list_2)
fig.update_traces(textposition='inside')
return(fig.show())
def npvplotbar(self, npvtable, i):
'''
Plot nominal cash flow stream from pandas data frame with npv
'''
# npvtable = npvtable[['Period', 'Income', 'Outcome']]
self.npvtable = npvtable
npv_ = self.npvtable['dcf'].sum()
if i==None:
cf_list = list(self.npvtable['ncf'])
period_list = list(self.npvtable['Period'])
i = compound_interest.irr(self, npw=npv_,period_list=period_list,cf_list=cf_list)
title = f"NPV: {npv_: .2f}, irr: {i: .4f}"
else:
title = f"NPV: {npv_: .2f}, i: {i: .4f}"
fig = bar(
npvtable,
x="Period",
y=["Income", "Outcome"],
title= title ,
text_auto=False,
opacity=0.8,
facet_col_spacing= 0.0
)
y_max = round(self.npvtable['ncf'].max() + 10)
if self.npvtable['ncf'].min() < 0:
y_min = round(self.npvtable['ncf'].min() + 10) * -1.
else:
y_min = y_max
pp = y_min / (y_min + y_max)
fig.update_layout( bargap=0.05,bargroupgap=0.75)
fig.update_traces(textangle=90, selector=dict(type='bar'))
fig.update_xaxes(position= pp,
anchor="free",
linecolor = "black",
tickfont=dict(size=12, color='black'),
ticks = "outside",
ticklabelposition = "outside left"
)
fig.update_layout(yaxis_range=[-y_min, y_max])
fig.update_yaxes(title="Cash Flow [$]")
return(fig.show())
def npvplotarrow(self, npvtable, i):
"""
Cash Flow plot arrows type
"""
# npvtable = npvtable[['Period', 'Income', 'Outcome']]
self.npvtable = npvtable
npv_ = self.npvtable['dcf'].sum()
if i==None:
cf_list = list(self.npvtable['ncf'])
period_list = list(self.npvtable['Period'])
i = compound_interest.irr(self, npw=npv_,period_list=period_list,cf_list=cf_list)
title = f"NPV: {npv_: .2f}, irr: {i: .4f}"
else:
title = f"NPV: {npv_: .2f}, i: {i: .4f}"
fig = bar(
self.npvtable,
x="Period",
y=["Income", "Outcome"],
title= title ,
text_auto=False,
opacity=0.0,
facet_col_spacing= 0.0
)
y_max = round(npvtable['ncf'].max() + 10)
if self.npvtable['ncf'].min() < 0:
y_min = round(self.npvtable['Outcome'].min() + 10) * -1.
else:
y_min = y_max
pp = y_min / (y_min + y_max)
fig.update_layout( bargap=0.05,bargroupgap=0.75)
fig.update_traces(textangle=90, selector=dict(type='bar'))
fig.update_xaxes(position= pp,
anchor="free",
linecolor = "black",
tickfont=dict(size=12, color='black'),
ticks = "outside",
ticklabelposition = "outside left"
)
fig.update_layout(yaxis_range=[-y_min, y_max])
fig.update_yaxes(title="Nominal Cash Flow [$]")
arrow_list_1, text_list_1 = time_value_plot.__arrowupdate(self.npvtable['Period'], self.npvtable["Income"])
arrow_list_2, text_list_2 = time_value_plot.__arrowupdate(self.npvtable['Period'], self.npvtable["Outcome"])
fig.update_layout(annotations=arrow_list_1 + arrow_list_2)
fig.update_layout(annotations=text_list_1 + text_list_2)
fig.update_traces(textposition='inside')
return(fig.show())
def amor_table_plot(self, loan_amount:float, rate:float, loan_term:int, periodicity:str):
'''
Plotting principal and interest payments over the repayment period
'''
self.loan_amount=loan_amount
self.rate=rate
self.loan_term=loan_term
self.periodicity=periodicity
df = time_value_table.uniform_loan_amortization(self,
self.loan_amount,
self.rate,
self.loan_term,
self.periodicity)
x = df.columns[0]
y = ['Principal', 'Interest']
payment = df.iloc[1,2]
title1 = 'Evolution of Principal and Interest payments over the repayment period'
title2 = f'Amount: {loan_amount: ,.2f}, Payment: {payment: ,.2f}, i: {rate: .2%}, Term: {loan_term}, Periodicity: {periodicity}'
title = title1 +'
' + title2
fig = area(df, x="Month", y=['Principal', 'Interest'], title=title)
return(fig.show())
def variable_amor_table_plot(self, loan_amount:float, rate:list, loan_term:int, periodicity:str):
'''
Plotting principal and interest payments over the repayment period
'''
assert isinstance(rate, list), "Argument rate must be a list"
assert len(rate)==loan_term, "Argument rate must has a len equal to loan_term"
self.loan_amount=loan_amount
self.rate=rate
self.loan_term=loan_term
self.periodicity=periodicity
df = time_value_table.variable_payment_loan_amortization(self,
self.loan_amount,
self.rate,
self.loan_term,
self.periodicity)
x = df.columns[0]
y = ['Principal', 'Interest']
payment = df.iloc[1,2]
title1 = 'Evolution of Principal and Interest payments over the repayment period'
title2 = f'Amount: {loan_amount: ,.2f}, Payment: {payment: ,.2f}, i: variable, Term: {loan_term}, Periodicity: {periodicity}'
title = title1 +'
' + title2
fig = area(df, x="Month", y=['Principal', 'Interest'], title=title)
return(fig.show())
class compound_interest(object):
def spi(self, pv: float, fv: float, n: float)->float:
'''
spi: Single Payment Interest
Input arguments:
pv: Present Value.
fv: Future Value.
'''
self.pv = pv
self.fv = fv
self.n = n
return ((self.fv/self.pv)**(1/self.n))-1
def ei(self, r: float, m: float)->float:
'''
ei: Effective Interest Per Time Period.
Input arguments:
r: Interest Rate For Same Time Period.
m: Number of Times Interest Is Compounded Per Stated Time Period.
'''
self.r = r
self.m = m
return (1 + self.r/self.m)**self.m - 1
def ipa(self, im:float)->float:
'''
ipa: Interest Paid In Advance Per Time Period.
Input arguments:
ipm: Interest Paid at Maturity Per Time Period.
'''
self.im = im
return self.im/(1 + self.im)
def ipm(self, ia:float)->float:
'''
ipm: Interest Paid at Maturity Per Time Period.
Input arguments:
ipa: Interest Paid In Advance Per Time Period.
'''
self.ia = ia
return self.ia/(1-self.ia)
def di(self, i: float, m: float)->float:
'''
di: Discrete interest Rate or Compounding interest rate.
Input arguments:
i: Effective Interest with Different Periodicity.
m: Relationship of periods between rates. In example:
- 12: Month to Year
- 6: Bimonthly to Year or Month to Half-year
- 4: Quaterly to Year
- 1/12: Year to Month
'''
self.i = i
self.m = m
return ((1+self.i)**(1/self.m) - 1)
def cci(self, r)->float:
'''
cci: Continuous Interest Rate to Discrete Interest Rate.
Input arguments:
r: Discrete Interest Rate For Same Time Period.
'''
self.r = r
return exp(self.r) -1
def dci(self,i)->float:
'''
dci: Discrete Interest Rate to Continuous Interest Rate.
Input argument:
i: Effective Interest Per Time Period.
'''
self.i = i
return log(1 + self.i)
def irr(self,npw, period_list:list, cf_list:list):
'''
iir: Internal Rate of Return.
Input arguments:
period_list: Period list.
cf_list: Cash flow list.
i: Effective interest rate.
'''
self.period_list = period_list
self.cf_list = cf_list
self.npw = npw
f = lambda x: npw - time_value.npv(self,period_list, cf_list, x)
irr_ = root(f, [0], tol=0.00000001)['x'][0]
return irr_