It is said that Python can be used for quantitative investing, but many people do not know how to do it, and even think it is very advanced knowledge, but it is not true. Anyone can backtest a simple strategy with only a little Python.

Backtrader is a Python based automated backtesting framework written by German Daniel Rodriguez. It is an easy to understand and use quantitative investment framework. Today we are going to try out a simple quantitative strategy Backtrader.

Of course, the first article will give you a head start using the simplest investment strategy. By following this article, you will be able to learn this simple quantification strategy:

Buy: 5-day price Moving Average (MA5) and 10-day Price Moving Average (MA10) Form average gold fork (MA5 wears MA10) Principle: Recent gains

Selling: 5-day price moving Average (MA5) and 10-day price moving Average (MA10) form a dead cross between averages (MA10 below MA5) Principle: Recently in a downtrend

Does this strategy really work? The average person would have to spend a lifetime trading stocks to find out what it actually does, but using Python to do a quantitative verification, you can quickly get the answer.

1. Prepare

Before you begin, make sure Python and PIP are successfully installed on your computer. If not, please visit this article: Super Detailed Python Installation Guide to install it.

In Windows, open Cmd(Start – Run – Cmd). In Apple, open Terminal(command+ space enter Terminal).

Of course, I recommend that you use the VSCode editor, Copy this code, install dependency modules in the terminal below the editor, what a comfortable thing to do: the best companion to Python programming – VSCode detailed guide.

Enter the following command to install the dependency modules required for this article:

pip install backtrader
Copy the code

If Successfully installed XXX is displayed, the installation is successful. The complete data and source code of this article can be downloaded at the back of the official account.

2. Basic use

Before you begin, you must know the data structure characteristics of Backtrader:

self.dataclose[0] # Closing price for the day
self.dataclose[-1] # Yesterday's closing price
self.dataclose[-2] # Yesterday's closing price
Copy the code

I was also struck by the author’s logic when I first started using this, that it was possible to play this way, in short, keep this feature in mind, otherwise you might not understand the strategy at all.

2.1 Capital and commission

After Backtrader initializes the model, the initial capital can be set through the broker, as shown below:

# -*- coding:utf-8 -*-
# Python utility guide
# Quantitative Investing turns out to be so easy (1)
# 2020/04/12

import backtrader as bt

if __name__ == '__main__':

    Initialize the model
    cerebro = bt.Cerebro()
    # Set initial capitalCerebro. Broker. Setcash (100000.0)# Funds before strategy execution
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

    cerebro.run()

    # Funds after strategy execution
    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

Copy the code

In real life stock trading, you need to pay a certain commission for each transaction, such as $5,000 ($5 commission for each transaction of $10,000). In Backtrader, you just need to set the following:

Cerebro. Broker. Setcommission (0.005)Copy the code

So the set you want to set the number of shares that you buy in each trade, and you can do that

cerebro.addsizer(bt.sizers.FixedSize, stake=100)
Copy the code

2.2 Loading Data

Backtrader calls Data sets as Data Feeds. The default Data set is Yahoo stock Data, which can be loaded in the following ways:

    data = bt.feeds.YahooFinanceCSVData(
        dataname='Data file location',
        fromdate=datetime.datetime(2000, 1, 1),
        todate=datetime.datetime(2000, 12, 31)
    )
Copy the code

Of course, it is also possible to load your own data, but you need to set the meaning of each column. For example, if the open price is in column 4, then open=3 (starting from 0), as shown below:

    data = bt.feeds.GenericCSVData(
        dataname='Data file location',
        datetime=2,
        open=3,
        high=4,
        low=5,
        close=6,
        volume=10,
        dtformat=('%Y%m%d'),
        fromdate=datetime(2010, 1, 1),
        todate=datetime(2020, 4, 12)
    )
Copy the code

Next, I will use my own data to test back, so that I can feel the substitution.

2.3 Construction Strategy

Building a policy using Backtrader is a simple matter. You just need to inherit Backtrader’s policy class and rewrite some methods to implement the policy. For example, rewrite our own log function:

class TestStrategy(bt.Strategy):
    """Inherit and build your Own BT strategy"""

    def log(self, txt, dt=None, doprint=False):
        ' ''Log function for uniform output log format'' '
        if doprint:
            dt = dt or self.datas[0].datetime.date(0)
            print('%s, %s' % (dt.isoformat(), txt))
Copy the code

Most importantly, rewrite our own trading strategies, such as the golden cross we mentioned at the beginning of the moving average:

class TestStrategy(bt.Strategy):
    """Inherit and build your Own BT strategy"""

    def next(self):
        # record closing price
        self.log('Close, %.2f' % self.dataclose[0])

        # Whether the order is being placed, if so, the second order cannot be submitted
        if self.order:
            return

        # Whether you have bought
        if not self.position:

            If MA5 > MA10 indicates a rally, buy
            if self.sma5[0] > self.sma10[0]:
                self.log('BUY CREATE, %.2f' % self.dataclose[0])
                self.order = self.buy()

        else:
            If MA5 < MA10, indicating decline, sell
            if self.sma5[0] < self.sma10[0]:
                self.log('SELL CREATE, %.2f' % self.dataclose[0])
                self.order = self.sell()
Copy the code

Useful? We’ll find out when we test back.

2.4 Adding Indicators

Backtrader has built-in calculation methods for many indicators, such as moving average, MACD, RSI, etc. For this article, we only need MA, which is set as follows:

self.sma5 = bt.indicators.SimpleMovingAverage(
            self.datas[0], period=5)
Copy the code

Where datas[0] is the first data set, period refers to the number of days of the moving average, such as 5, and returns the relevant data for MA5.

3. Perform a policy rollback

In order to verify the strategy we mentioned at the beginning, WE used the stock data of Kweichow Moutai 600519.sh from January 1, 2020 to now (2020/04/12). The complete data and source code can be downloaded in the background of the official account.

Name the data 600519.csv and save it in the current folder. The main function is as follows:

if __name__ == '__main__':

    Initialize the model
    cerebro = bt.Cerebro()

    # Build strategy
    strats = cerebro.addstrategy(TestStrategy)
    Buy 100 shares at a time
    cerebro.addsizer(bt.sizers.FixedSize, stake=100)

    Load data into the model
    data = bt.feeds.GenericCSVData(
        dataname='600519.csv',
        fromdate=datetime.datetime(2010, 1, 1),
        todate=datetime.datetime(2020, 4, 12),
        dtformat='%Y%m%d',
        datetime=2,
        open=3,
        high=4,
        low=5,
        close=6,
        volume=10
    )

    cerebro.adddata(data)

    # Set initial capital and commissionCerebro cerebro. Broker. Setcash (1000000.0). The broker. Setcommission (0.005)# Funds before strategy execution
    print('Start-up capital: %.2f' % cerebro.broker.getvalue())

    # Policy execution
    cerebro.run()

Copy the code

Finally, the completion strategy is completed. Our Backtrader strategy is as follows. For the readability of the public account, some unimportant codes are removed here.

class TestStrategy(bt.Strategy):
    """Inherit and build your Own BT strategy"""

    def log(self, txt, dt=None, doprint=False):
        ' ''Log function for uniform output log format'' '
        if doprint:
            dt = dt or self.datas[0].datetime.date(0)
            print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):

        Initialize the relevant data
        self.dataclose = self.datas[0].close
        self.order = None
        self.buyprice = None
        self.buycomm = None

        # 5-day moving average
        self.sma5 = bt.indicators.SimpleMovingAverage(
            self.datas[0], period=5)
        # 10-day moving average
        self.sma10 = bt.indicators.SimpleMovingAverage(
            self.datas[0], period=10)

    def notify_order(self, order):
        ""Arguments: Order {object} -- Order status"""
        if order.status in [order.Submitted, order.Accepted]:
            Do not do anything if the order has already been processed
            return

        Check if the order is completed
        if order.status in [order.Completed]:
            if order.isbuy():
                self.buyprice = order.executed.price
                self.buycomm = order.executed.comm
            self.bar_executed = len(self)

        # The order was rejected for lack of funds, etc
        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log('Order Canceled/Margin/Rejected')

        Order status processing completed, set to null
        self.order = None

    def notify_trade(self, trade):
        ""Arguments: Trade {object} -- Trade status""
        if not trade.isclosed:
            return

        # show transaction gross margin and net profit
        self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
                 (trade.pnl, trade.pnlcomm), doprint=True)

    def next(self):
        ' ''Next execution'' '

        # record closing price
        self.log('Close, %.2f' % self.dataclose[0])

        # Whether the order is being placed, if so, the second order cannot be submitted
        if self.order:
            return

        # Whether you have bought
        if not self.position:
            If MA5 > MA10 indicates a rally, buy
            if self.sma5[0] > self.sma10[0]:
                self.order = self.buy()
        else:
            If MA5 < MA10, indicating decline, sell
            if self.sma5[0] < self.sma10[0]:
                self.order = self.sell()

    def stop(self):
        self.log(uEnding Value %.2f' %
                 (self.broker.getvalue()), doprint=True)
Copy the code

The code looks long, but when you remove the comments, it’s pretty simple logic. What was the effect? Take a look at the picture below:

As you can see, our initial capital was 1 million yuan and we traded 100 shares each time. Although we occasionally made profits, if we strictly followed this strategy for ten years, we would eventually lose 50,000 yuan. Of course, in real life, there will be times when you add to your position in good times and you subtract from it in bad times, but for now this is just a simple buy and sell strategy.

But this simple implementation, often the best way to help you rationally analyze the rationality of the strategy, if a strategy always needs you to subjectively to add or reduce positions, that strategy is bound to have problems. A really good strategy, in terms of probability, will always be profitable with simple backtesting.

So does this pure, simple golden fork of a moving average work? I think the effect is limited. There are a lot of strategies out there, so you can try other strategies, and let me know if they work (funny).

So that’s the end of our article, if you’d like to see our Python tutorial today, please stay tuned and give us a thumbs up/check out if it was helpful. If you have any questions, please leave them in the comments below and we’ll be patient to answer them!


The Python Utility Guide is more than just a guide

Backtrader: How to Use Backtrader: How to Use Backtrader: How to Use Backtrader: How to use Backtrader