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.

  1. 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.
  2. We add an if statement to the log function in the Strategy class that only logs data if the settings are set to True.
  3. We use cerebro.optstrategy() instead of cerebro.addstrategy(). We optimize the strategy over a range of MA periods from 10 to 31.

The code additions here are in the following cells:

  1. Strategy Class
  2. Backtest Settings

Import modules

In [7]:
import datetime
import os.path
import sys
import backtrader as bt
import matplotlib.pyplot as plt

Data

In [8]:
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.

In [9]:
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.

In [10]:
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.

In [11]:
cerebro.run(maxcpus=1)
2000-12-29,MA Period:    10.00 Ending Value:   877.50
2000-12-29,MA Period:    11.00 Ending Value:   878.70
2000-12-29,MA Period:    12.00 Ending Value:   839.80
2000-12-29,MA Period:    13.00 Ending Value:   899.90
2000-12-29,MA Period:    14.00 Ending Value:   902.50
2000-12-29,MA Period:    15.00 Ending Value:   975.60
2000-12-29,MA Period:    16.00 Ending Value:   961.90
2000-12-29,MA Period:    17.00 Ending Value:   952.60
2000-12-29,MA Period:    18.00 Ending Value:  1011.00
2000-12-29,MA Period:    19.00 Ending Value:  1039.40
2000-12-29,MA Period:    20.00 Ending Value:  1073.20
2000-12-29,MA Period:    21.00 Ending Value:  1055.10
2000-12-29,MA Period:    22.00 Ending Value:  1057.60
2000-12-29,MA Period:    23.00 Ending Value:  1021.50
2000-12-29,MA Period:    24.00 Ending Value:  1018.80
2000-12-29,MA Period:    25.00 Ending Value:  1012.40
2000-12-29,MA Period:    26.00 Ending Value:   998.30
2000-12-29,MA Period:    27.00 Ending Value:   983.10
2000-12-29,MA Period:    28.00 Ending Value:   976.90
2000-12-29,MA Period:    29.00 Ending Value:   984.20
2000-12-29,MA Period:    30.00 Ending Value:   980.80
Out[11]:
[[<backtrader.cerebro.OptReturn at 0x7d54a5bb05c0>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5be16d8>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5af7390>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a9f198>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a49940>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a773c8>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a21470>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a10d68>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a70a90>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5ade470>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a9f160>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5b20438>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5b22ef0>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5b60208>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5b59a20>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5be1e80>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a4a6a0>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5ab9d68>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a9df60>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a5f278>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a65c18>]]

Conclusion

In this post, we learnt how to optimize parameters in our trading strategy.

  1. 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.
  2. We altered the log function of the Strategy class to only print the values of the parameter being optimized and its corresponding portfolio value.
In [ ]:
 

Comments

Comments powered by Disqus