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.

\$\endgroup\$
  • 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. Jan 22 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 Jan 22 at 23:20
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!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.