Pretty often strategies you backtest have quite a lot of parameters and it’s pretty hard to find out which parameters work the best. Of course, you can change parameters manually and run backtest multiple times. But there are better ways to do that. In this article, I will show you how you run multiple backtests in Backtrader in Python and find the best working parameters configuration.
First, let’s import the libraries we need for this example:
import backtrader as bt
import backtrader.analyzers as btanalyzers
import pandas as pd
import matplotlib
from datetime import datetime
import qgrid
Next, we have to create a class for our strategy. To run parameter optimization in Backtrader you have to include parameters in class and use it in your indicator/signals calculations.
To add parameters you can just add a list of parameters with default values to the class like that:
params = (
('fast_length', 10),
('slow_length', 50)
)
And then use these parameters with self.params prefix when computing your indicators:
def __init__(self):
ma_fast = bt.ind.SMA(period = self.params.fast_length)
ma_slow = bt.ind.SMA(period = self.params.slow_length)
self.crossover = bt.ind.CrossOver(ma_fast, ma_slow)
Full class definition looks like that:
class MaCrossStrategy(bt.Strategy):
params = (
('fast_length', 10),
('slow_length', 50)
)
def __init__(self):
ma_fast = bt.ind.SMA(period = self.params.fast_length)
ma_slow = bt.ind.SMA(period = self.params.slow_length)
self.crossover = bt.ind.CrossOver(ma_fast, ma_slow)
def next(self):
if not self.position:
if self.crossover > 0:
self.buy()
elif self.crossover < 0:
self.close()
After doing that you can start adding you strategy to engine following way adding parameters:
cerebro.addstrategy(MaCrossStrategy, fast_length = 10, slow_length = 20)
After that, you can write a bunch of nested loops and inside it add strategy and run the engine multiple times to optimize your strategy parameters. But there is a better way to do that. You can add your strategy to the engine with optstrategy method specifying the range of parameters you want to test. It looks like that:
strats = cerebro.optstrategy(
MaCrossStrategy,
fast_length = range(1, 11),
slow_length = range(25, 76, 5))
When you add strategy this way Backtrader will understand that you want to optimize your strategy and will run the engine multiple times for all parameters combination.
Here is an example of the entire code for running it:
cerebro = bt.Cerebro()
data = bt.feeds.YahooFinanceData(dataname = 'AAPL', fromdate = datetime(2010, 1, 1), todate = datetime(2020, 1, 1))
cerebro.adddata(data)
strats = cerebro.optstrategy(
MaCrossStrategy,
fast_length = range(1, 11),
slow_length = range(25, 76, 5))
cerebro.broker.setcash(1000000.0)
cerebro.addsizer(bt.sizers.PercentSizer, percents = 10)
cerebro.addanalyzer(btanalyzers.SharpeRatio, _name = "sharpe")
cerebro.addanalyzer(btanalyzers.DrawDown, _name = "drawdown")
cerebro.addanalyzer(btanalyzers.Returns, _name = "returns")
back = cerebro.run()
So we saved the result of the run to back variable, now we have to parse it. The result of optimization is simply a list of backtesting results, so it’s pretty easy to parse it to a convenient format.
I’ll use Python’s list comprehension to extract parameters and metrics I need from optimizations results:
par_list = [[x[0].params.fast_length,
x[0].params.slow_length,
x[0].analyzers.returns.get_analysis()['rnorm100'],
x[0].analyzers.drawdown.get_analysis()['max']['drawdown'],
x[0].analyzers.sharpe.get_analysis()['sharperatio']
] for x in back]
This will output me list of lists of params and metrics. Next I will transform them to a Pandas DataFrame:
par_df = pd.DataFrame(par_list, columns = ['length_fast', 'length_slow', 'return', 'dd', 'sharpe'])
Now all results are in a nice DataFrame so you can view them with qgrid for example:
qgrid.show_grid(par_df)
This will output you a nice table:
In this table, you can view all your backtesting results, filter, and sort them. So it will be pretty easy to spot results that work the best for you.
Hi
Thnaks for your guide. I run your code in jupyter but optimization hit to a bug relating to multiprocessing. Do you know how to fix that ?
I have the same issue, Can anyone help us?
back = cerebro.run(maxcpus=1)
Good Luck!
Hi
Thnaks for your guide. I run your code in jupyter but optimization hit to a bug relating to multiprocessing. Do you know how to fix that ?
I have the same issue, Can anyone help us?
back = cerebro.run(maxcpus=1)
Good Luck!
@shoabir try: cerebro.run(maxcpus=1)
@shoabir try: cerebro.run(maxcpus=1)
Just change this line and it should be ok:
back = cerebro.run() –> back = cerebro.run(maxcpus=1)
Just change this line and it should be ok:
back = cerebro.run() –> back = cerebro.run(maxcpus=1)
Thank you so much
I run this program. But values for RETURN, MDD, SHARPE has different results from backtesting of each result separately
How can I fix this issue?
Best regards
Hamed Asgari
Are you able to use psar and use take profits and stop loss?