1
\$\begingroup\$

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.

asked Jan 22, 2019 at 11:55
\$\endgroup\$
2
  • 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\$ Commented 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\$ Commented Jan 22, 2019 at 23:20

1 Answer 1

1
\$\begingroup\$

Using the strategy pattern from the GOF would be a solution, but I don't speak python...

  1. create an abstract class ValueCalculator with the functions calculate_value_to_time(self, time)and calculate_value(self)
  2. create three different classes that implement the interface: ValueCalculatorWithContinuous, ValueCalculatorWithDue and ValueCalculatorWithImmediate
  3. each class offers the special implementation of the formula
  4. create a self.valueCalculator-property for the Annuity-class
  5. instantiate the correct ValueCalculator, decided from the annuity_type, and set the self.valueCalculator in the __init__-part to this
  6. 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!

answered Jan 23, 2019 at 9:27
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.