I have a number of classes that implement smoothing techniques on a series of prices.
I am trying to figure the best way of implementing any of these smoothing classes within the __call__
function of another class, where some operation is then performed on the smoothed series:
i.e.
class NoSmoother:
def __init__(self, prices):
self.prices = prices
def __call__(self):
return self.prices
class MASmoother:
def __init__(self, prices):
self.prices = prices
def __call__(self, window):
return self.prices.rolling(window).mean().to_frame("price")
class ThatDoesSomethingWithSmoothedPrices():
def __init__(self, prices):
self.prices = prices
def __call__(self, smoother=ma_smoother, window=3)
smoothed_prices = SomeBuilderClassThatCallsTheCorrectSmootherClass()
As you can see, I would need the factory/builder class to implement NoSmoother
if say smoother = None, otherwise, it would call the appropriate smoothing class. Of course, the returned object can vary from simple to complex, for example, if I smooth the prices using a Kalman Filter, the class can expect many more parameters.
Currently, my code instantiates class ThatDoesSomethingWithSmoothedPrices()
with a price series, then calls by passing a **config.
Desired Output:
Ideally, I would like to be able to call any smoothing class from within the call function of
class ThatDoesSomethingWithSmoothedPrices()
.
Example implementation:
configs = {'smoother': MASmoother, 'window': 3}
processor = ThatDoesSomethingWithSmoothedPrices(prices)
output = processor(**config)
My attempt:
class Smoother:
def __init__(self, prices):
self.prices = prices
def __call__(self, smoother, *args, **kwargs):
return partial(smoother, **kwargs)
def ma_smoother(self, window: int = 3):
return self.prices.rolling(window).mean().to_frame("price")
def no_smoother(self):
return self.prices
class ThatDoesSomethingWithSmoothedPrices:
def __init__(self, prices):
self.prices = prices
def __call__(self, smooth_method = 'no_smoother'):
smoother = Smoother(prices)
prices_smoothed = smoother(**configs)
# do other things
if __name__ == '__main__':
configs = {'smoother': 'ma_smoother', window=3}
label = ThatDoesSomethingWithSmoothedPrices(**configs)
Any help greatly appreciated.
Santiago Trujillo
For simplicity, if you don't have a lot of state, I'd just use regular functions.
You can use functools.partial()
to partially apply a function, i.e. in this case set the MA window:
from functools import partial
def no_smoother(values):
return values
def ma_smoother(values, *, window):
return values.rolling(window).mean().to_frame("price")
def get_prices():
...
def get_smoothed_prices(smoother):
prices = get_prices()
return smoother(prices)
get_smoothed_prices(smoother=no_smoother)
get_smoothed_prices(smoother=partial(ma_smoother, window=3))
Based on the edit in the question:
configs = {'smoother': MASmoother, 'window': 3}
processor = ThatDoesSomethingWithSmoothedPrices(prices)
output = processor(**config)
would be expressed as something like
def construct_smoother(smoother, **kwargs):
return partial(smoother, **kwargs)
smoother = construct_smoother(**configs)
# ...
Given your latest update you need to pass in the configs
as well:
configs = {'smoother': MASmoother, 'window': 3}
processor = ThatDoesSomethingWithSmoothedPrices(configs, prices)
output = processor(**config)
So that means ThatDoesSomethingWithSmoothedPrices
could be like this:
def ThatDoesSomethingWithSmoothedPrices(configs, prices):
smoother = configs['smoother'](prices)
return smoother
To create a factory, you can create a class method inside a class.
# declaration
class Factory:
# class variables (static)
id = 0
@classmethod
def new(cls):
return object()
# usage
obj = Factory.new()
If you have a class that needs arguments in the constructor, then you can pass variable number of arguments. Basically *
removes brackets around the list
or dict
you are passing.
# declaration
def function(var1, var2, *args, **kwargs):
# transfer variable arguments to other function
return SomeObejct(*args, **kwargs)
# usage
obj = function(1, 2, a, b, c, key=value)
In your case, you would do something like this:
# declaration
# you can also pass classes as arguments
def __call__(self, smoother=MASmoother, window=3, *args, **kwargs)
smoothed_prices = smoother(*args, **kwargs)
return smoothed_prices
# usage
smth = ThatDoesSomethingWithSmoothedPrices()
smoother1 = smth()
smoother2 = smth(NoSmoother, 2)
smoother3 = smth(NoSmoother, 2, arg1, arg2, key=value)