Overview of backtrader (Intermediate)

The previous posts went through a quick overview of the backtrader platform so that one could see the speed and simplicity of using backtrader to execute your trading strategies.

This post goes through the structure of the backtrader platform

  1. Data Feeds. The data for the trading strategy needs to be loaded into the backtester needs to be loaded in.
  2. Trading strategy. The trading strategy is the most complex and involved part of the programming process since this is where decisions are made to buy/sell the asset.
  3. Backtester settings. Cerebro is the name of the backtester. Cerebro requires several settings such as (i) Trading capital (ii) Broker Comissions (iii) Datafeed (iv) Trading strategy (v) Size of each trading position.
  4. Running backtest. Run the Cerebro backtester and print out all trades executed.
  5. Evaluating performance. Graphically evaluate the performance of the backtested trading strategy.

Import modules

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

Data

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

The __init__ function that initialies the strategy class will contain all the attributes (e.g., indicators - SMA, RSI, SMA, price data - volume, open price, close price) and the variabl

The next function is where the trading strategy is written based on the attributes

In [3]:
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 [4]:
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 [5]:
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[5]:
[[<backtrader.cerebro.OptReturn at 0x7a4ee2c14b38>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2bd70f0>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2bd19b0>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2c142b0>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2b79a58>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2ba94a8>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2b52518>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2af5c88>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2b19710>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2b011d0>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2b691d0>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2b832b0>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2b79e10>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2c5d780>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2c00898>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2bbb860>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2ad08d0>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2adff60>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2b5f198>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2ae7cf8>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2b444e0>]]

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