0

Let me illustrate with an example.

Say I want to create a class Returns that generates the returns of a stock for example. Returns can be arithmetic or logarithmic, and I want to be able to choose at instantiation which I want. For the moment, I do the following:

def arithmetic(prices):
    daily_ret = []
    for i rows in prices:
        returns.append(prices(i)-prices(i-1))
    return daily_ret

def logarithmic(prices):
    daily_ret = []
    for i rows in prices:
        returns.append(np.ln(prices(i) / prices(i-1))
    return daily_ret

class Returns:
    def __init__(self, stock_ticker, return_calc=arithmetic):
        self.stock_ticker = stock_ticker
        self.return_calc = return_calc

    def calculate_returns(self):
        get_price_data_from_ticker = whatever_data_source()
        if self.return_calc = 'arithmetic':
            returns = arithmetic(get_price_data_from_ticker)
        elif self.return_calc = 'logarithmic':
            returns = logarithmic(get_price_data_from_ticker)
        return returns

x = Returns('AAPL US Equity', return_calc='logarithmic')
print(x.calculate_returns())

This seems like the code is just badly written, and I'm missing something obvious because I've had the same question over and over.

Another thing that I've done is use getattr to dynamically get the name of the function or class I'll use and pass on arguments. Also another thing I've found is using globals() to dynamically create classes.

I'm wondering if there's a macro construction that I'm missing, because even if these 2 solutions above feel quite nice, it feels like there's a deeper structural issue with the code.

Language is Python 3

3 Answers 3

2

In this case, I think using different classes is probably the way to go.

from abc import ABC, abstractmethod

class Returns(ABC):
    def __init__(self, stock_ticker):
        self.stock_ticker = stock_ticker
    def get_price_data_from_ticker(self):
        pass # FIXME: implement getting stuff from the data source here
    @abstractmethod
    def calculate_returns(self, prices):
        pass
    def get_returns(self):
        return self.calculate_returns(self.get_price_data_from_ticker())

class ArithmeticReturns(Returns):
    def calculate_returns(self, prices):
        return [prices(i) - prices(i - 1) for i in prices]

class LogarithmicReturns(Returns):
    def calculate_returns(self, prices):
        return [np.ln(prices(i) / prices(i - 1)) for i in prices]

x = LogarithmicReturns('AAPL US Equity')
print(x.get_returns())

Concepts used:

  • Abstract Base Classes: Returns is now a class that cannot be substantiated, only subclasses that implement all abstract methods can. This means both subclasses implement the same interface without having to do all of the heavy lifting themselves.
  • List comprehensions: l = [a + 1 for a in exp] is equivalent to

    l = []
    for a in exp:
        l.append(a + 1)
    
  • Separation of concerns: calculate_returns is now no longer concerned with how data is collected from the ticker, it purely calculates the returns based on the prices given to it.

Your code contained syntax errors in arithmetic and logarithmic in the loops, so you will need to fix that. If prices is a list, I would use something like [current_price - previous_price for current_price, previous_price in zip(prices[1:], prices)], but I decided to leave it as is in case prices is actually a strange iterable that iterates over the indexes and is indexed by calling.

2
  • 1
    generally speaking, this is Strategy design pattern Commented Nov 20, 2018 at 5:27
  • Isn't a class a little too Java-like for Python? To each language its own, injecting a class instance when all you need is a function seems a little heavy to me!
    – gcali
    Commented Apr 5, 2019 at 13:43
1

I think you're on the right way; but using some dependency injection might work better in your case. You could do something like this:

def arithmetic(prices):
    daily_ret = []
    for i rows in prices:
        returns.append(prices(i)-prices(i-1))
    return daily_ret

def logarithmic(prices):
    daily_ret = []
    for i rows in prices:
        returns.append(np.ln(prices(i) / prices(i-1))
    return daily_ret

class Returns:
    def __init__(self, stock_ticker, return_calc=arithmetic):
        self.stock_ticker = stock_ticker
        self.return_calc = return_calc

    def calculate_returns(self):
        get_price_data_from_ticker = whatever_data_source()
        return self.return_calc(get_price_data_from_ticker)

x = Returns('AAPL US Equity', return_calc=logarithmic)
print(x.calculate_returns())

by directly passing the function instead of passing a string constant to identify which function to use.

By the way, this is what you were doing in this line:

def __init__(self, stock_ticker, return_calc=arithmetic):

which means that the default value that you are passing right now isn't a string, but directly the function

0

A simpler method is to include both functions as class methods, but assign the visible method in the constructor, like

self.calculate_return = self.logarithmic if .... else self.arithmetic

Then the caller will always use the proper function without doing a test and a deeper method call each time. You can also do this with subclasses but this is simpler, if a little scary for non-python coders.

Your Answer

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

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