Trading Strategy: Back testing with Backtrader | by J Li | Towards Data Science

(This post is also available in my blog)
In one of my older post, I demonstrates how to compute technical indicators which can be combined logically to build a trading strategy. As emphasized in the post, one should validate how well the strategy does with backtesting before applying it in real market.
Backtesting is the process of applying a trading strategy or analytical method to historical data to see how accurately the strategy or method would have predicted actual results.- from investing answers
Backtest is like cross validation in machine learning. But it’s not exactly the same. Backtest requires splitting data into two parts like cross validation. One set is for training, the other is for validation purpose. The difference is training testing split can be randomly done for cross validation. While in trading backtesting, your data is time series. Your training data must be older than your testing data. Otherwise you peek in future which results in incorrect measurement of your strategy. So dataset split cannot be random in backtesting. Backtesting involves market simulation in real world. It could be hard and error-prone to implement your own backtesting libraries. Luckily there’s Backtrader.
Backtrader is an awesome open source python framework which allows you to focus on writing reusable trading strategies, indicators and analyzers instead of having to spend time building infrastructure. It supports backtesting for you to evaluate the strategy you come up with too!
With that being said, it is a free and complete solution for technical people to build their own strategies. Let’s start to have a glance.
pip install backtrader[plotting]
Backtrader has defined a strategy interface for you. You need to create a class with implement this interface. An important method is next() where you should make decision whether you should BUY, SELL or DO NOTHING based on the technical indicators in a specific day. A simple strategy looks like this.
import backtrader as btclass MyStrategy(bt.Strategy):    def __init__(self):        self.sma = bt.indicators.SimpleMovingAverage(period=15)    def next(self):        if self.sma > self.data.close:            # Do something            pass        elif self.sma < self.data.close:            # Do something else            pass
As you can see, backtrader has shipped with a set of common technical indicators. It means you don’t need to reply on your self or TA lib to compute technical indicators.
Backtrader also offers features in simulating trading in the marking. Once can factor the commission in your trading operation based on dollar or percentage.
cerebro.broker.setcommission(commission=0.001) 
Below is the whole example for demonstration of backtesting with Facebook historical market data. Note that, historical trading data is downloaded from Yahoo Finance. It also supports pandas dataframe. I have a post about collecting trading data with pandas here. The example consists of a simple TestStrategy and a driver piece of code that kick of the backtesting. The simple strategy only considers RSI for BUY/SELL signal. You should add more logics for your selected stocks.
from __future__ import (absolute_import, division, print_function,                        unicode_literals)import datetimeimport os.pathimport sysimport backtrader as btclass TestStrategy(bt.Strategy):    def log(self, txt, dt=None):        dt = dt or self.datas[0].datetime.date(0)        print('%s, %s' % (dt.isoformat(), txt))    def __init__(self):        self.dataclose = self.datas[0].close        self.order = None        self.buyprice = None        self.buycomm = None        self.sma = bt.indicators.SimpleMovingAverage(self.datas[0], period=15)        self.rsi = bt.indicators.RelativeStrengthIndex()    def notify_order(self, order):        if order.status in [order.Submitted, order.Accepted]:            return        if order.status in [order.Completed]:            if order.isbuy():                self.log(                    'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %                    (order.executed.price,                     order.executed.value,                     order.executed.comm))                self.buyprice = order.executed.price                self.buycomm = order.executed.comm            else:  # Sell                self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %                         (order.executed.price,                          order.executed.value,                          order.executed.comm))            self.bar_executed = len(self)        elif order.status in [order.Canceled, order.Margin, order.Rejected]:            self.log('Order Canceled/Margin/Rejected')        # Write down: no pending order        self.order = None    def notify_trade(self, trade):        if not trade.isclosed:            return        self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %                 (trade.pnl, trade.pnlcomm))    def next(self):        self.log('Close, %.2f' % self.dataclose[0])        print('rsi:', self.rsi[0])        if self.order:            return        if not self.position:            if (self.rsi[0] < 30):                self.log('BUY CREATE, %.2f' % self.dataclose[0])                self.order = self.buy(size=500)        else:            if (self.rsi[0] > 70):                self.log('SELL CREATE, %.2f' % self.dataclose[0])                self.order = self.sell(size=500)if __name__ == '__main__':    cerebro = bt.Cerebro()    cerebro.addstrategy(TestStrategy)    cerebro.broker.setcommission(commission=0.001)    datapath = 'FB.csv'    # Create a Data Feed    data = bt.feeds.YahooFinanceCSVData(        dataname=datapath,        fromdate=datetime.datetime(2013, 1, 1),        todate=datetime.datetime(2018, 8, 5),        reverse=True)    cerebro.adddata(data)    cerebro.broker.setcash(100000.0)    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())    cerebro.run()    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())    cerebro.plot()
At the end of execution, you can find out your final value of portfolio. As well, you are able to plot the stock price, technical indicators, your BUY/SELL operations and your portfolio value with regard to the time.
As you can see, this simple strategy works ok with FB as it captures a few buy and sell opportunities.
That’s it for backtesting with backtrader. If you want to dive deeper, I encourage you visit backtrader’s doc for more advanced usage. If you would like to learn more about Machine Learning there is a helpful series of courses in educative.io. These courses cover topics like basic ML, NLP, Image Recognition etc. Happy coding and trading!
Recommended reading:
Hands-On Machine Learning
Python for Data Analysis: Data Wrangling with Pandas, NumPy, and IPython
What Hedge Funds Really Do
My posts:
My posts about FAANG interview
My YouTube Channel
My posts about Finance and Tech
From CRUD web app dev to SDE in voice assistant — My ongoing Journey to Machine Learning
Full Stack Development Tutorial: Integrate AWS Lambda Serverless Service into Angular SPA
Full Stack Development Tutorial: Serving Trading Data with Serverless REST API running on AWS Lambda
Full Stack Development Tutorial: Visualize Trading Data on Angular SPA
Reinforcement Learning: Introduction to Q Learning