-
For this question, I'm simplifying everything to the basics so people can follow along with the existing quick start documentation. In the documentation we see an example of a Simple Moving Average Cross strategy class. from backtesting import Strategy
from backtesting.lib import crossover
class SmaCross(Strategy):
# Define the two MA lags as *class variables*
# for later optimization
n1 = 10
n2 = 20
def init(self):
# Precompute the two moving averages
self.sma1 = self.I(SMA, self.data.Close, self.n1)
self.sma2 = self.I(SMA, self.data.Close, self.n2)
def next(self):
# If sma1 crosses above sma2, close any existing
# short trades, and buy the asset
if crossover(self.sma1, self.sma2):
self.position.close()
self.buy()
# Else, if sma1 crosses below sma2, close any existing
# long trades, and sell the asset
elif crossover(self.sma2, self.sma1):
self.position.close()
self.sell() We then call this class in as such from backtesting import Backtest
bt = Backtest(GOOG, SmaCross, cash=10_000, commission=.002)
stats = bt.run()
stats First Part of the questionWe are hard coding the n1 and n2 as class variables because later on we would like to use them for optimization purposes, however, suppose also I wanted to create multiple versions of this class with different parameters for n1 and n2. Maybe an smacross_fast, smacross_medium and smacross_slow. When I first started working with this, I would just copy and paste the entire class change the name and the variables. Definitely not DRY programming. In my mind I would think that I could just create a different run of Backtest like this: bt_fast = Backtest(GOOG, SmaCross(n1=1,n2=10), cash=10000)
fast_stats = bt_fast.run() But of course that doesn't work because it is trying to work with the init and setting instance variables rather than class variables. Over time my method evolved to creating child classes of the original like this: class testing_inheritance(SmaCross): #inheriting the strategy attributes from above.
n1=1
n2=10
def next(self): #After much trial and error I learned to not place an init(self) function here.
super().next()
#-----Then call the original SmaCross-----#
bt=Backtest(GOOG,SmaCross, cash=10000, commission=.002)
stats=bt.run()
stats
bt.plot()
#-----And then call the inherited SmaCross#
bt2=Backtest(GOOG,testing_inheritance, cash=10000, commission=.002)
stats2=bt2.run()
stats2
bt2.plot() That cut out the unnecessary need to copy and paste the entire next() logic but I still felt like I was angering the python gods. By filling up my strategy.py file with a bunch of duplicate strategies that I may or may not want to keep later on and having to import each one into my main.py by name. So I came up with another hack that I'm sure is incorrect and I know will have @kernc shaking his head as he reads this. def set_params(params,cls):
"""
set the class parameters for a given strategy class based on the
comma separated list of "param1=val,param2=val2...." format
I am confident there is a better way to do this but I don't know what that is.
"""
d = dict(x.split("=") for x in params.split(","))
for k,v in d.items():
setattr(cls,k,int(v)) # setattr() is the function that assigns new class variables to a class.
return cls
smacross_fast = SmaCross #create an instance of the original class
params = "n1=1,n2=10" #create a string to house your preferred paramaters for the new strategy class
smacross_fast = set_params(params, smacross_fast) #assign the new class parameters to the new class.
# print the __dict__ to see if it worked
print(smacross_fast.__dict__) Before you roll your eyes, let me explain a bit about why this mattered to me. The optimization capabilities from scikit that you @kernc and the other volunteers implemented is fantastic. I was playing around and wanted to memorialize some of them by saving the parameters and creating permanent base classes on the fly so I could later compare the results different in and out of sample data time periods. I was trying to demonstrate the risks of overfitting. Each time I ran an optimization, I would store the parameters in a variable for example, optimal_params_2020_sample = optimized_stats_2020._strategy #gets the class variables from the optimized strategy
optimal_params_2019_sample = optimized_stats_2019._strategy #gets class variables from 2019 optimization period
# ...then I did some string logic to remove the strategy name and extract just the parameters as a string....
optimized_2020_class = base_strategy_class # create an instance of the original strategy class
optimized_2020_class = set_params(optimal_params_in_sample,optimized_2019_class)
optimized_2019_class = base_strategy_class # create an instance of the original strategy class
optimized_2019_class = set_params(optimal_params_in_sample,optimized_2019_class) Even the above is less than good, but it has been working for me. Hopefully someone will be gentle on me and think of this as a teaching opportunity to encourage others to ask dumb questions like this. If you haven't figured it out, I'm asking specifically for someone or many people to comment on all of the things that I am doing incorrectly and could be doing better. I'm not asking to debug a specific problem. All the code works, it's just "hack-y" |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 2 replies
-
stats = bt.run(n1=1, n2=10)
# or
params = {'n1': 1, 'n2': 10}
stats = bt.run(**params) Is this what you were looking for? Re:
This does not actually create anything. >>> smacross_fast is SmaCross
True |
Beta Was this translation helpful? Give feedback.
Backtest.run()
takes variable assignments as keyword arguments.Is this what you were looking for?
Re:
This does not actually create anything.
smacross_fast
is the sameSmaCross
class.