In the code shown below, I have created 3 classes. The first of these are an 'Payment' class and a 'Annuity' class, which allow the user to create objects representing individual or recurring payments respectively.
The third of these classes is a 'Cashflow' class, which takes a combination of Payment and Annuity object to construct a cashflow, and returns allows the user to calculate various related properties. One example of such a property is the discounted payback period, which is the earliest point in time that the net present value of the cashflows given is equal to zero.
import math
class Annuity():
#Initialise function
def __init__(self, annuity_type, effective_interest_rate, term, payment_rate = 1, payment_frequency = 1):
self.annuity_type = annuity_type
self.effective_interest_rate = effective_interest_rate
self.term = term
self.payment_rate = payment_rate
self.payment_frequency = payment_frequency
#Properties
@property
def present_value(self):
if self.annuity_type == 'continuous':
return self.payment_rate * (1 - (1 + self.effective_interest_rate) ** -self.term) / math.log(1 + self.effective_interest_rate)
elif self.annuity_type == 'due':
return self.payment_rate * (1 - (1 + self.effective_interest_rate) ** -self.term) / (self.effective_interest_rate / (1 + self.effective_interest_rate))
elif self.annuity_type == 'immediate':
return self.payment_rate * (1 - (1 + self.effective_interest_rate) ** -self.term) / self.effective_interest_rate
@property
def time_payable(self):
return self.term
#Functions
def present_value_to_time(self, time):
if time >= self.term:
return self.present_value
elif self.annuity_type == 'continuous':
return self.payment_rate * (1 - (1 + self.effective_interest_rate) ** -time) / math.log(1 + self.effective_interest_rate)
elif self.annuity_type == 'due':
return self.payment_rate * (1 - (1 + self.effective_interest_rate) ** -time) / (self.effective_interest_rate / (1 + self.effective_interest_rate))
elif self.annuity_type == 'immediate':
return self.payment_rate * (1 - (1 + self.effective_interest_rate) ** -time) / self.effective_interest_rate
class Payment():
#Initialise function
def __init__(self, effective_interest_rate, time_payable, ammount):
self.effective_interest_rate = effective_interest_rate
self.time_payable = time_payable
self.ammount = ammount
#Properties
@property
def present_value(self):
return self.ammount * (1 + self.effective_interest_rate) ** -self.time_payable
#Functions
def present_value_to_time(self, time):
if time < self.time_payable:
return 0
else:
return self.present_value
class Cashflow(object):
#Initialise function
def __init__(self, cashflow = []):
self.cashflow = cashflow
#Properties
@property
def net_present_value(self):
npv = 0
for cf in self.cashflow:
npv += cf.present_value
return npv
@property
def cashflow_timings(self):
cashflow_timings = []
for cf in self.cashflow:
cashflow_timings.append(cf.time_payable)
cashflow_timings.sort()
return cashflow_timings
@property
def discounted_payback_period(self):
n1 = min(self.cashflow_timings)
n2 = max(self.cashflow_timings) + 1
discounted_payback_period = 0
for t in range(0, 6):
for n in range(n1, n2):
n /= 10 ** t
n += discounted_payback_period
if self.net_present_value_to_time(n) >= 0:
discounted_payback_period = n - (1 / 10 ** t)
n1 = 0
n2 = 11
break
return round(discounted_payback_period, 5)
#Functions
def add_to_cashflow(self, cashflow_item):
self.cashflow.append(cashflow_item)
def net_present_value_to_time(self, time):
net_present_value_to_time = 0
for cf in self.cashflow:
net_present_value_to_time += cf.present_value_to_time(time)
return net_present_value_to_time
An example of how one might use the above classes is as follows:
my_cashflow = Cashflow()
a1 = Annuity('continuous', 0.1, 5, 10000)
a2 = Annuity('continuous', 0.1, 5, -2000)
p1 = Payment(0.1, 0, -25000)
p2 = Payment(0.1, 6, -5000)
my_cashflow.add_to_cashflow(a1)
my_cashflow.add_to_cashflow(a2)
my_cashflow.add_to_cashflow(p1)
my_cashflow.add_to_cashflow(p2)
print(my_cashflow.discounted_payback_period)
Does anyone have any suggestions as to how the above code might be improved? In particular, I was hoping that there might be a more 'elegant' solution than the use of if
/ elif
statements used in the Annuity class.
-
1\$\begingroup\$ Using the strategy pattern from the GOF would be a solution, but I don't speak python... See sourcemaking.com/design_patterns/strategy/python/1 as an example \$\endgroup\$lars k.– lars k.2019年01月22日 13:50:55 +00:00Commented Jan 22, 2019 at 13:50
-
\$\begingroup\$ Hi, cheers for your response. Unfortunately, the strategy described there goes somewhat over my head, on account of my current lack of knowledge of python. I'll try deciphering it with the help of Google though! \$\endgroup\$M Smith– M Smith2019年01月22日 23:20:21 +00:00Commented Jan 22, 2019 at 23:20
1 Answer 1
Using the strategy pattern from the GOF would be a solution, but I don't speak python...
- create an abstract class
ValueCalculator
with the functionscalculate_value_to_time(self, time)
andcalculate_value(self)
- create three different classes that implement the interface:
ValueCalculatorWithContinuous
,ValueCalculatorWithDue
andValueCalculatorWithImmediate
- each class offers the special implementation of the formula
- create a
self.valueCalculator
-property for the Annuity-class - instantiate the correct ValueCalculator, decided from the annuity_type, and set the
self.valueCalculator
in the__init__
-part to this - then you can shorten the functions to
#Functions
def present_value_to_time(self, time):
if time >= self.term:
return self.present_value
else:
return self.valueCalculator.calculate_value_to_time(self, time)
Hope that helps. Watch out, this is not REAL Python code!