0

I have various types of financial securities. Each one of these securities shares a common set of methods. For instance, they all pay some amount of cash interest between two dates. Each security has a different way to calculate that amount, however, that can require totally different inputs. Think of this as trying to calculate the area of different shapes, but more security types and hence ways to calculate cash_interest will be added over time.

# fixed rate security
def cash_interest(self, start_date, end_date, fixed_rate) -> float

# floating rate security
def cash_interest(self, start_date, end_date, index_px_history, index_margin) -> float

# was it sunny outside security
def cash_interest(self, start_date, end_date, was_it_sunny_outside) -> float

How should I organize these securities with similar outputs? I'm using python but am looking for language-agnostic answers as well. I'm drawn to an OOP approach of subclasses given the common outputs. I want to be able to iterate over all the different securities and call the function cash_interest but am confused how I would know when to provide which input if I go the overloading approach.

If I go the approach of having one big cash_interest function with tons of optional parameters, I make it harder for other users to add their own custom securities since the main cash_interest function would need to be edited every time someone wanted to add a new security.

2
  • 1
    For instance, does the FixedRateSecurity really need a fixed_rate argument? Could that be passed into the constructor for that object? Sep 3 at 0:49
  • For fixed rate, it is very easy to move into constructor. See comment below about scenario analysis making moving the other arguments more difficult.
    – cpage
    Sep 3 at 2:26
1

This sounds like a case where you may want to apply the Strategy or Template Method OO pattern.

In the Strategy pattern, you would have a base class representing a financial security. In that class you'd either have a default implementation for cash_interest or cash_interest could be abstract, and require that each subclass have its own implementation. With this pattern, you would have a generic function that loops through an array of your financial securities, and calls cash_interest on each of those securities, but it would polymorphically call the appropriate implementation of cash_interst in the associated sub-class.

Using this pattern, you would have to implement the cash_interest function each time for each distinct security.

If there are commonalities in the algorithm for calculating cash_interest, where most of the algorithm is the same, but maybe there is nuance in specific portions, you could rather use the Template Method pattern.

In this pattern, you have a base class that implements the common parts of the cash_interest algorithm, and then delegates to abstract sub-functions to handle the nuance. Your subclasses would then implement the sub-functions to handle the particular nuance to the algorithm in question.

Using this pattern, you would have to implement the base algorithm of cash_interest in the base class, and then implement the nuanced parts in each of the sub-classes.

3
  • 1
    How would the loop know which parameters it needed to pass to the polymorphic cash_interest methods?
    – cpage
    Sep 3 at 2:18
  • @cpage, without knowing your algorithm for calculation, I won't be able to deterministically say. However, it's possible that the cash_interest function doesn't need to take in parameters as part of its interface, rather, the sub-classed object has member properties that provide the necessary parameter input for your calculation. Do you have a specific example of two different parameter sets that you'd need?
    – Mackers
    Sep 3 at 14:47
  • That last comment is mainly to say that what you need is different inputs into your calculation, but those inputs aren't REQUIRED to be provided through the function parameter interface. There can be a plethora of ways to get your inputs. For example, you could have them as member variables on the sub-class when you instantiate the sub-class; you could get the inputs by delegating to another object that does some kind of sub calculation; etc.
    – Mackers
    Sep 3 at 15:20
1

An object-oriented approach works well if the parameters to cash_interest are always the same. If the parameters required vary based on the security type, and those values are only known at the time interest is calculated, then it is difficult to create an abstraction. You need to decide at the time of calculation which algorithm and parameters are needed.

I wouldn't put this in the security-related classes. Instead, create a class that specializes in calculating interest for the different types of securities. Since you mention Python, there is nothing wrong with free-floating functions. Basically instead of passing self pass security. You are better off with well-named methods having clearly defined parameters where all necessary information is passed. Then you can write a test suite to verify the output.

def fixed_rate_cash_interest(security, start_date, end_date, fixed_rate) -> float

def floating_rate_cash_interest(security, start_date, end_date, index_px_history, index_margin) -> float

def was_it_sunny_outside_cash_interest(security, start_date, end_date, was_it_sunny_outside) -> float

If you can determine some of the parameters at the time of object construction, and those values remain valid for all interest calculations, then you might be able to tease out some common parameters. If you can do that, now polymorphism is an option. Perhaps the common parameters are the start and end dates, and the other values could be passed as constructor arguments:

security = FixedRateSecurity(.003);
interest = security.cash_interest(start_date, end_date)

security = FloatingRateSecurity(...)
interest = security.cash_interest(start_date, end_date)

security = WasItSunnyOutsideSecurity(weather_forecast)
interest = security.cash_interest(start_date, end_date)

This allows you to treat each security sub-type the same when calculating interest.

8
  • For the second option, would there be any relationship between the different security classes? Maybe all inherit from the same abstract base class Security? Also, I’m looking at scenario analysis so I would like to run the interest for different weather forecast for the WasItSunnyOutsideSecurity. How do you think about constructing separate objects for each scenario vs your first option?
    – cpage
    Sep 3 at 2:24
  • @cpage In Python there is no need to use abstract classes because it is a fully dynamic language. You just call the method and if such a method exists in the class then the computer will find it.
    – user253751
    Sep 3 at 9:35
  • 1
    Perhaps not weather_forecast but weather_source. You have to use the same forecast, or rate index, as the people who are selling you the security! It does you no good to base your calculations on T-bonds when the seller is actually paying you a rate based on LIBOR. You can have a FloatingRateSecurity(libor, 0.003), a FloatingRateSecurity(T_bond_rate, 0.003), a IsItSunnyOutsideSecurity(Melbourne_Australia_MetService), ...
    – user253751
    Sep 3 at 9:36
  • @cpage: Don't get too hung up on the names I suggested. You do not have a lot of information in your question about how interest is calculated for the different securities. I was just trying to give you a few ideas to start. You can change the names as you see fit. Sep 3 at 13:09
  • 1
    @cpage: I am envisioning either an abstract base class, or an interface. I would start out with an interface and only introduce an abstract base class if there is shared behavior. Sep 3 at 22:42
1

As another idea, you can create an InterestCalculator class that takes in a Security instance in the constructor. The InterestCalculator exposes a single interface (e.g. the cash_interest method) while internally dealing with different types of Security classes.

It looks something like this:

class Security:
    def __init__(
        self, 
        start_date: str, 
        end_date: str,
    ) -> None:
        self.start_date = start_date
        self.end_date = end_date


class FixedRateSecurity(Security):
    def __init__(
        self, 
        start_date: str, 
        end_date: str, 
        fixed_rate: float,
    ) -> None:
        super().__init__(start_date=start_date, end_date=end_date)
        self.fixed_rate = fixed_rate


class InterestCalculator:
    def __init__(
        self,
        security: Security,
    ) -> None:
        self.security = security

    def cash_interest(self) -> float:
        if isinstance(self.security, FixedRateSecurity):
            return self.fixed_rate_cash_interest()
        # if isinstance ... test for other Security classes here
        else:
            name = self.security.__class__.__name__
            mes = f'Cannot compute cash interest for {name} object'
            raise TypeError(mes)

    def fixed_rate_cash_interest(self) -> float:
        # self.security is guaranteed to be a FixedRateSecurity instance 
        return 1.0

The idea here is that, for every possible Security class you create (FloatingRateSecurity, WasItSunnyOutsideSecurity, ...), you add the corresponding InterestCalculator method that knows how to deal with that class, along with the corresponding if isinstance check in InterestCalculator.cash_interest.

As @Greg Burghardt pointed out in their answer, since this is Python you may want to have cash_interest as a free function, rather than having an entire InterestCalculator class. The function then takes the Security instance and passes it onwards to the corresponding downstream function. It then becomes something like:

def cash_interest(security: Security) -> float:
    if isinstance(security, FixedRateSecurity):
        return fixed_rate_cash_interest(security=security)
    # if isinstance ... test for other Security classes here
    else:
        name = self.security.__class__.__name__
        mes = f'Cannot compute cash interest for {name} object'
        raise TypeError(mes)

def fixed_rate_cash_interest(security: FixedRateSecurity) -> float:
    # security is guaranteed to be a FixedRateSecurity instance 
    return 1.0

This mostly depends on your preference - the free function approach is somewhat easier to test, but you may prefer to use classes if your code is or becomes more complex than the examples we're discussing.

Both approaches do the same thing: they add a layer between the Security instances and the calculations that normalizes how you should call your code later on. So for your question on how to "iterate over all the different securities and call the function", you will simply need to do this:

# class approach
for security in list_of_securities:
    ic = InterestCalculator(security=security)
    result = ic.cash_interest()

# free function approach
for security in list_of_securities:
    result = cash_interest(security=security)
2
  • For the free function, approach how will the was_it_sunny_outside security know it was sunny? I think this assumes the class has a self.was_it_sunny data member. I am looking to scenario test my was_it_sunny_outside security for different weather forecasts and see what cash interest will be. I need a way to pass in those different scenario arguments.
    – cpage
    Sep 3 at 18:04
  • I don't think there's enough information about WasItSunnyOutsideSecurity in your question for me to properly answer that - I was indeed under the impression that was_it_sunny_outside was an attribute. Considering this, here is the approach I can think off the top of my head: construct the security instance with all wheather forecasts under which you want to test it later on; make the function was_it_sunny_outside_cash_interest check the wheather forecasts, and test accordingly. You may even wrap forecasts in a WheatherForecasts class that is passed into the security upon construction.
    – jfaccioni
    Sep 3 at 18:20

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.