Optimizing parameters of the trading strategy
In this post, we show how to optimize variables in your strategy. In our previous post on introducing indicators in to the backtester, we found that moving average over 15 days led to the a losing trading strategy. Thus, we might want to evalute which is the best period (i.e., 5, 10, 15, 20) to use to ensure that our trading strategy is profitable. As a rule, it is best not to over optimize your trading strategy as it will not generalize well in an out-of-sample test.
- We set
printlog
to False in the Strategy as we do not want a print of trade in each backtest that is applied to each parameter that is being optimized. - We add an
if
statement to thelog
function in the Strategy class that only logs data if the settings are set to True. - We use
cerebro.optstrategy()
instead ofcerebro.addstrategy()
. We optimize the strategy over a range of MA periods from 10 to 31.
The code additions here are in the following cells:
- Strategy Class
- Backtest Settings
Import modules¶
import datetime
import os.path
import sys
import backtrader as bt
import matplotlib.pyplot as plt
Data¶
homepath = os.getenv('HOME')
datapath = os.path.join(homepath, 'github/backtrader/datas/orcl-1995-2014.txt')
data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
fromdate=datetime.datetime(2000,1,1),
todate = datetime.datetime(2000,12,31),
reverse=False)
Strategy Class¶
In params
, set the printlog
to False. This is because when we are optimizing over different parameters, we don't want to see all the trades that are executed each time a different backtest is applied to each parameter. We just want to see what the Final value of the portfolio is when each parameter is applied in the backtest.
In the log
function, we add an additional if
statement to only log data if self.params.printlog
or doprint
are True.
We add a stop
function, that prints out the MA period
that is being backtested and the corresponding portfolio value.
class Strat2_BGTMA_SLSMA(bt.Strategy):
params = (
('maperiod',15), # Tuple of tuples containing any variable settings required by the strategy.
('printlog',False), # Stop printing the log of the trading strategy
)
def __init__(self):
self.dataclose= self.datas[0].close # Keep a reference to the "close" line in the data[0] dataseries
self.order = None # Property to keep track of pending orders. There are no orders when the strategy is initialized.
self.buyprice = None
self.buycomm = None
# Add SimpleMovingAverage indicator for use in the trading strategy
self.sma = bt.indicators.SimpleMovingAverage(
self.datas[0], period=self.params.maperiod)
def log(self, txt, dt=None, doprint=False):
if self.params.printlog or doprint: # Add if statement to only log of printlog or doprint is True
dt = dt or self.datas[0].datetime.date(0)
print('{0},{1}'.format(dt.isoformat(),txt))
def notify_order(self, order):
# 1. If order is submitted/accepted, do nothing
if order.status in [order.Submitted, order.Accepted]:
return
# 2. If order is buy/sell executed, report price executed
if order.status in [order.Completed]:
if order.isbuy():
self.log('BUY EXECUTED, Price: {0:8.2f}, Size: {1:8.2f} Cost: {2:8.2f}, Comm: {3:8.2f}'.format(
order.executed.price,
order.executed.size,
order.executed.value,
order.executed.comm))
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else:
self.log('SELL EXECUTED, {0:8.2f}, Size: {1:8.2f} Cost: {2:8.2f}, Comm{3:8.2f}'.format(
order.executed.price,
order.executed.size,
order.executed.value,
order.executed.comm))
self.bar_executed = len(self) #when was trade executed
# 3. If order is canceled/margin/rejected, report order canceled
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
self.order = None
def notify_trade(self,trade):
if not trade.isclosed:
return
self.log('OPERATION PROFIT, GROSS {0:8.2f}, NET {1:8.2f}'.format(
trade.pnl, trade.pnlcomm))
def next(self):
# Log the closing prices of the series from the reference
self.log('Close, {0:8.2f}'.format(self.dataclose[0]))
if self.order: # check if order is pending, if so, then break out
return
# since there is no order pending, are we in the market?
if not self.position: # not in the market
if self.dataclose[0] > self.sma[0]:
self.log('BUY CREATE {0:8.2f}'.format(self.dataclose[0]))
self.order = self.buy()
else: # in the market
if self.dataclose[0] < self.sma[0]:
self.log('SELL CREATE, {0:8.2f}'.format(self.dataclose[0]))
self.order = self.sell()
def stop(self):
self.log('MA Period: {0:8.2f} Ending Value: {1:8.2f}'.format(
self.params.maperiod,
self.broker.getvalue()),
doprint=True)
Backtest settings¶
Rather than using cerebro.addstrategy()
, we now use cerebro.optstrategy()
as we are optimizing the strategy over a variety of parameters in our SMA indicator. In this case we are testing all the maperiod
from 10, 11, 12, ..., 30, 31 bars.
cerebro = bt.Cerebro()
cerebro.adddata(data)
strats = cerebro.optstrategy(
Strat2_BGTMA_SLSMA,
maperiod=range(10,31),
printlog=False)
cerebro.addsizer(bt.sizers.FixedSize,stake=10)
cerebro.broker.setcash(1000.0)
cerebro.broker.setcommission(commission=0.0)
Run backtest¶
The maxcpus
allows us to trigger how many cpus to run in parallel when performing the optimization. We find that the SMA strategy is profitable when we use values between 18 to 25 days.
cerebro.run(maxcpus=1)
Conclusion¶
In this post, we learnt how to optimize parameters in our trading strategy.
- We learnt how to use
cerebro.optstrategy
where we indicate which parameter we want to optimize and the range of parameters to evaluate the strategy's performance. - We altered the log function of the Strategy class to only print the values of the parameter being optimized and its corresponding portfolio value.
Comments
Comments powered by Disqus