Quickstart

Note

The data files used in the quickstart guide are updated from time to time, which means that the adjusted close changes and with it the close (and the other components). That means that the actual output may be different to what was put in the documentation at the time of writing.

Using the platform

Let’s run through a series of examples (from almost an empty one to a fully fledged strategy) but not without before roughly explaining 2 basic concepts when working with backtrader

  1. Lines

    Data Feeds, Indicators and Strategies have lines.

    A line is a succession of points that when joined together form this line. When talking about the markets, a Data Feed has usually the following set of points per day:

    • Open, High, Low, Close, Volume, OpenInterest

    The series of “Open”s along time is a Line. And therefore a Data Feed has usually 6 lines.

    If we also consider “DateTime” (which is the actual reference for a single point), we could count 7 lines.

  2. Index 0 Approach

    When accessing the values in a line, the current value is accessed with index: 0

    And the “last” output value is accessed with -1. This in line with Python conventions for iterables (and a line can be iterated and is therefore an iterable) where index -1 is used to access the “last” item of the iterable/array.

    In our case is the last output value what’s getting accessed.

    As such and being index 0 right after -1, it is used to access the current moment in line.

With that in mind and if we imagine a Strategy featuring a Simple Moving average created during initialization:

self.sma = SimpleMovingAverage(.....)

The easiest and simplest way to access the current value of this moving average:

av = self.sma[0]

There is no need to know how many bars/minutes/days/months have been processed, because “0” uniquely identifies the current instant.

Following pythonic tradition, the “last” output value is accessed using -1:

previous_value = self.sma[-1]

Of course earlier output values can be accessed with -2, -3, …

From 0 to 100: the samples

Basic Setup

Let’s get running.

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import backtrader as bt

if __name__ == '__main__':
    cerebro = bt.Cerebro()

    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

    cerebro.run()

    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

After the execution the output is:
执行后,输出为:

Starting Portfolio Value: 10000.00
Final Portfolio Value: 10000.00

In this example: 在本例中:

  • backtrader was imported backtrader是进口的

  • The Cerebro engine was instantiated
    Cerebro引擎已实例化

  • The resulting cerebro instance was told to run (loop over data)
    生成的大脑实例被告知运行(数据循环)

  • And the resulting outcome was printed out
    结果打印出来

Although it doesn’t seem much, let’s point out something explicitly shown:
虽然看起来不多,但让我们明确指出一些内容:

  • The Cerebro engine has created a broker instance in the background
    Cerebro引擎在后台创建了一个broker实例

  • The instance already has some cash to start with
    该实例已经有一些现金可供启动

This behind the scenes broker instantiation is a constant trait in the platform to simplify the life of the user. If no broker is set by the user, a default one is put in place.
这种后台代理实例化是平台中简化用户生活的一个不变特征。如果用户没有设置代理,则会设置一个默认的代理。

And 10K monetary units is a usual value with some brokers to begin with.
对于一些经纪人来说,1万货币单位是一个通常的价值。

Setting the Cash 设置现金

In the world of finance, for sure only “losers” start with 10k. Let’s change the cash and run the example again.
在金融界,只有“失败者”才能从1万开始。让我们换现金,重新运行这个例子。

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import backtrader as bt

if __name__ == '__main__':
    cerebro = bt.Cerebro()
    cerebro.broker.setcash(100000.0)

    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

    cerebro.run()

    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

After the execution the output is:
执行后,输出为:

Starting Portfolio Value: 1000000.00
Final Portfolio Value: 1000000.00

Mission accomplished. Let’s move to tempestuous waters.
任务完成。让我们进入汹涌的水域。

Adding a Data Feed
添加数据馈送

Having cash is fun, but the purpose behind all this is to let an automated strategy multiply the cash without moving a finger by operating on an asset which we see as a Data Feed
拥有现金是很有趣的,但这一切背后的目的是通过对我们视为数据馈送的资产进行操作,让自动化策略在不动手指的情况下使现金倍增

Ergo … No Data Feed -> No Fun. Let’s add one to the ever growing example.
呃…没有数据馈送->没有乐趣。让我们在不断增长的例子中添加一个。

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import datetime  # For datetime objects
import os.path  # To manage paths
import sys  # To find out the script name (in argv[0])

# Import the backtrader platform
import backtrader as bt

if __name__ == '__main__':
    # Create a cerebro entity
    cerebro = bt.Cerebro()

    # Datas are in a subfolder of the samples. Need to find where the script is
    # because it could have been called from anywhere
    modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
    datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

    # Create a Data Feed
    data = bt.feeds.YahooFinanceCSVData(
        dataname=datapath,
        # Do not pass values before this date
        fromdate=datetime.datetime(2000, 1, 1),
        # Do not pass values after this date
        todate=datetime.datetime(2000, 12, 31),
        reverse=False)

    # Add the Data Feed to Cerebro
    cerebro.adddata(data)

    # Set our desired cash start
    cerebro.broker.setcash(100000.0)

    # Print out the starting conditions
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

    # Run over everything
    cerebro.run()

    # Print out the final result
    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

After the execution the output is:
执行后,输出为:

Starting Portfolio Value: 1000000.00
Final Portfolio Value: 1000000.00

The amount of boilerplate has grown slightly, because we added:
样板的数量略有增长,因为我们添加了:

  • Finding out where our example script is to be able to locate the sample Data Feed file
    找出我们的示例脚本能够定位示例数据馈送文件的位置

  • Having datetime objects to filter on which data from the Data Feed we will be operating
    有日期时间对象来过滤我们将要操作的数据源中的哪些数据

Aside from that, the Data Feed is created and added to cerebro.
除此之外,数据馈送被创建并添加到大脑中。

The output has not changed and it would be a miracle if it had.
产量没有变化,如果有,那将是一个奇迹。

Note 笔记

Yahoo Online sends the CSV data in date descending order, which is not the standard convention. The reversed=True prameter takes into account that the CSV data in the file has already been reversed and has the standard expected date ascending order.
Yahoo Online按日期降序发送CSV数据,这不是标准约定。reversed=True prameter考虑到文件中的CSV数据已经被反转,并且具有标准的预期日期升序。

Our First Strategy 我们的第一个战略

The cash is in the broker and the Data Feed is there. It seems like risky business is just around the corner.
现金在经纪人那里,数据馈送在那里。风险业务似乎指日可待。

Let’s put a Strategy into the equation and print the “Close” price of each day (bar).
让我们在等式中加入一个策略,并打印出每天的“收盘”价格(条形图)。

DataSeries (the underlying class in Data Feeds) objects have aliases to access the well known OHLC (Open High Low Close) daily values. This should ease up the creation of our printing logic.
DataSeries(数据馈送中的基础类)对象具有别名,用于访问众所周知的OHLC(打开-高-低-关闭)每日值。这将简化我们的打印逻辑的创建。

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import datetime  # For datetime objects
import os.path  # To manage paths
import sys  # To find out the script name (in argv[0])

# Import the backtrader platform
import backtrader as bt


# Create a Stratey
class TestStrategy(bt.Strategy):

    def log(self, txt, dt=None):
        ''' Logging function for this strategy'''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # Keep a reference to the "close" line in the data[0] dataseries
        self.dataclose = self.datas[0].close

    def next(self):
        # Simply log the closing price of the series from the reference
        self.log('Close, %.2f' % self.dataclose[0])


if __name__ == '__main__':
    # Create a cerebro entity
    cerebro = bt.Cerebro()

    # Add a strategy
    cerebro.addstrategy(TestStrategy)

    # Datas are in a subfolder of the samples. Need to find where the script is
    # because it could have been called from anywhere
    modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
    datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

    # Create a Data Feed
    data = bt.feeds.YahooFinanceCSVData(
        dataname=datapath,
        # Do not pass values before this date
        fromdate=datetime.datetime(2000, 1, 1),
        # Do not pass values before this date
        todate=datetime.datetime(2000, 12, 31),
        # Do not pass values after this date
        reverse=False)

    # Add the Data Feed to Cerebro
    cerebro.adddata(data)

    # Set our desired cash start
    cerebro.broker.setcash(100000.0)

    # Print out the starting conditions
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

    # Run over everything
    cerebro.run()

    # Print out the final result
    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

After the execution the output is:
执行后,输出为:

Starting Portfolio Value: 100000.00
2000-01-03T00:00:00, Close, 27.85
2000-01-04T00:00:00, Close, 25.39
2000-01-05T00:00:00, Close, 24.05
...
...
...
2000-12-26T00:00:00, Close, 29.17
2000-12-27T00:00:00, Close, 28.94
2000-12-28T00:00:00, Close, 29.29
2000-12-29T00:00:00, Close, 27.41
Final Portfolio Value: 100000.00

Someone said the stockmarket was risky business, but it doesn’t seem so.
有人说股市是有风险的,但事实并非如此。

Let’s explain some of the magic:
让我们来解释一下其中的一些神奇之处:

  • Upon init being called the strategy already has a list of datas that are present in the platform
    调用init后,策略已经有了平台中存在的数据列表

    This is a standard Python list and datas can be accessed in the order they were inserted.
    这是一个标准的Python列表,可以按照插入的顺序访问数据。

    The first data in the list self.datas[0] is the default data for trading operations and to keep all strategy elements synchronized (it’s the system clock)
    列表self.datas[0]中的第一个数据是交易操作和保持所有策略元素同步的默认数据(这是系统时钟)

  • self.dataclose = self.datas[0].close keeps a reference to the close line. Only one level of indirection is later needed to access the close values.
    self.dataclose=self.datas[0]。close保留对闭合线的引用。以后只需要一个间接级别来访问关闭值。

  • The strategy next method will be called on each bar of the system clock (self.datas[0]). This is true until other things come into play like indicators, which need some bars to start producing an output. More on that later.
    策略下一个方法将在系统时钟的每个条上调用(self.datas[0])。这是真的,直到其他事情发挥作用,比如指标,需要一些条来开始产生输出。稍后会详细介绍。

Adding some Logic to the Strategy
为策略添加一些逻辑

Let’s try some crazy idea we had by looking at some charts
让我们看看图表,试试我们的一些疯狂想法

  • If the price has been falling 3 sessions in a row … BUY BUY BUY!!!
    如果价格连续下跌3个交易日…买买!!!
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import datetime  # For datetime objects
import os.path  # To manage paths
import sys  # To find out the script name (in argv[0])

# Import the backtrader platform
import backtrader as bt


# Create a Stratey
class TestStrategy(bt.Strategy):

    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # Keep a reference to the "close" line in the data[0] dataseries
        self.dataclose = self.datas[0].close

    def next(self):
        # Simply log the closing price of the series from the reference
        self.log('Close, %.2f' % self.dataclose[0])

        if self.dataclose[0] < self.dataclose[-1]:
            # current close less than previous close

            if self.dataclose[-1] < self.dataclose[-2]:
                # previous close less than the previous close

                # BUY, BUY, BUY!!! (with all possible default parameters)
                self.log('BUY CREATE, %.2f' % self.dataclose[0])
                self.buy()


if __name__ == '__main__':
    # Create a cerebro entity
    cerebro = bt.Cerebro()

    # Add a strategy
    cerebro.addstrategy(TestStrategy)

    # Datas are in a subfolder of the samples. Need to find where the script is
    # because it could have been called from anywhere
    modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
    datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

    # Create a Data Feed
    data = bt.feeds.YahooFinanceCSVData(
        dataname=datapath,
        # Do not pass values before this date
        fromdate=datetime.datetime(2000, 1, 1),
        # Do not pass values before this date
        todate=datetime.datetime(2000, 12, 31),
        # Do not pass values after this date
        reverse=False)

    # Add the Data Feed to Cerebro
    cerebro.adddata(data)

    # Set our desired cash start
    cerebro.broker.setcash(100000.0)

    # Print out the starting conditions
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

    # Run over everything
    cerebro.run()

    # Print out the final result
    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

After the execution the output is:
执行后,输出为:

Starting Portfolio Value: 100000.00
2000-01-03, Close, 27.85
2000-01-04, Close, 25.39
2000-01-05, Close, 24.05
2000-01-05, BUY CREATE, 24.05
2000-01-06, Close, 22.63
2000-01-06, BUY CREATE, 22.63
2000-01-07, Close, 24.37
...
...
...
2000-12-20, BUY CREATE, 26.88
2000-12-21, Close, 27.82
2000-12-22, Close, 30.06
2000-12-26, Close, 29.17
2000-12-27, Close, 28.94
2000-12-27, BUY CREATE, 28.94
2000-12-28, Close, 29.29
2000-12-29, Close, 27.41
Final Portfolio Value: 99725.08

Several “BUY” creation orders were issued, our porftolio value was decremented. A couple of important things are clearly missing.
发布了几个“购买”创建订单,我们的porftolio价值减少了。有几件重要的事情显然缺失了。

  • The order was created but it is unknown if it was executed, when and at what price.
    订单已创建,但尚不清楚是否执行,何时执行,以何种价格执行。

    The next example will build upon that by listening to notifications of order status.
    下一个示例将在此基础上,通过侦听订单状态的通知。

The curious reader may ask how many shares are being bought, what asset is being bought and how are orders being executed. Where possible (and in this case it is) the platform fills in the gaps:
好奇的读者可能会问,有多少股票被购买,什么资产被购买,订单是如何执行的。在可能的情况下(在这种情况下是这样),平台会填补缺口:

  • self.datas[0] (the main data aka system clock) is the target asset if no other one is specified
    self.datas[0](主数据,也称为系统时钟)是目标资产,如果没有指定其他资产

  • The stake is provided behind the scenes by a position sizer which uses a fixed stake, being the default “1”. It will be modified in a later example
    桩号由一个使用固定桩号(默认值为“1”)的测位器在幕后提供。它将在后面的示例中进行修改

  • The order is executed “At Market”. The broker (shown in previous examples) executes this using the opening price of the next bar, because that’s the 1st tick after the current under examination bar.
    订单在“市场”执行。经纪人(如前面的示例所示)使用下一个条的开盘价执行此操作,因为这是当前正在检查的条后面的1 st 勾号。

  • The order is executed so far without any commission (more on that later)
    到目前为止,该命令是在没有任何佣金的情况下执行的(稍后将详细介绍)

Do not only buy … but SELL
不仅要买…还要卖

After knowing how to enter the market (long), an “exit concept” is needed and also understanding whether the strategy is in the market.
在知道如何进入市场(做多)后,需要一个“退出概念”,并了解策略是否在市场中。

  • Luckily a Strategy object offers access to a position attribute for the default data feed
    幸运的是,Strategy对象提供了对默认数据馈送的位置属性的访问

  • Methods buy and sell return the created (not yet executed) order
    方法买入和卖出返回已创建(尚未执行)的订单

  • Changes in orders’ status will be notified to the strategy via a notify method
    订单状态的变化将通过通知方法通知给策略

The “exit concept” will be an easy one:
“退出概念”将是一个简单的概念:

  • Exit after 5 bars (on the 6th bar) have elapsed for good or for worse
    无论好坏,在经过5个小节(6 th 小节上)后退出

    Please notice that there is no “time” or “timeframe” implied: number of bars. The bars can represent 1 minute, 1 hour, 1 day, 1 week or any other time period.
    请注意,这里没有“时间”或“时间框架”的含义:酒吧的数量。条形图可以表示1分钟、1小时、1天、1周或任何其他时间段。

    Although we know the data source is a daily one, the strategy makes no assumption about that.
    尽管我们知道数据源是日常的,但该策略对此没有任何假设。

Additionally and to simplify:
此外,为了简化:

  • Do only allow a Buy order if not yet in the market
    只有在市场上还没有订单的情况下才允许购买

Note 笔记

The next method gets no “bar index” passed and therefore it seems obscure how to understand when 5 bars may have elapsed, but this has been modeled in pythonic way: call len on an object and it will tell you the length of its lines. Just write down (save in a variable) at which length in an operation took place and see if the current length is 5 bars away.
下一个方法没有传递“条形索引”,因此似乎不清楚如何理解何时可能已经过了5个条形,但这是以Python的方式建模的:在对象上调用len,它会告诉你它的行的长度。只需写下(保存在变量中)操作发生的长度,看看当前长度是否为5巴。

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import datetime  # For datetime objects
import os.path  # To manage paths
import sys  # To find out the script name (in argv[0])

# Import the backtrader platform
import backtrader as bt


# Create a Stratey
class TestStrategy(bt.Strategy):

    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # Keep a reference to the "close" line in the data[0] dataseries
        self.dataclose = self.datas[0].close

        # To keep track of pending orders
        self.order = None

    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
            return

        # Check if an order has been completed
        # Attention: broker could reject order if not enough cash
        if order.status in [order.Completed]:
            if order.isbuy():
                self.log('BUY EXECUTED, %.2f' % order.executed.price)
            elif order.issell():
                self.log('SELL EXECUTED, %.2f' % order.executed.price)

            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 next(self):
        # Simply log the closing price of the series from the reference
        self.log('Close, %.2f' % self.dataclose[0])

        # Check if an order is pending ... if yes, we cannot send a 2nd one
        if self.order:
            return

        # Check if we are in the market
        if not self.position:

            # Not yet ... we MIGHT BUY if ...
            if self.dataclose[0] < self.dataclose[-1]:
                    # current close less than previous close

                    if self.dataclose[-1] < self.dataclose[-2]:
                        # previous close less than the previous close

                        # BUY, BUY, BUY!!! (with default parameters)
                        self.log('BUY CREATE, %.2f' % self.dataclose[0])

                        # Keep track of the created order to avoid a 2nd order
                        self.order = self.buy()

        else:

            # Already in the market ... we might sell
            if len(self) >= (self.bar_executed + 5):
                # SELL, SELL, SELL!!! (with all possible default parameters)
                self.log('SELL CREATE, %.2f' % self.dataclose[0])

                # Keep track of the created order to avoid a 2nd order
                self.order = self.sell()


if __name__ == '__main__':
    # Create a cerebro entity
    cerebro = bt.Cerebro()

    # Add a strategy
    cerebro.addstrategy(TestStrategy)

    # Datas are in a subfolder of the samples. Need to find where the script is
    # because it could have been called from anywhere
    modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
    datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

    # Create a Data Feed
    data = bt.feeds.YahooFinanceCSVData(
        dataname=datapath,
        # Do not pass values before this date
        fromdate=datetime.datetime(2000, 1, 1),
        # Do not pass values before this date
        todate=datetime.datetime(2000, 12, 31),
        # Do not pass values after this date
        reverse=False)

    # Add the Data Feed to Cerebro
    cerebro.adddata(data)

    # Set our desired cash start
    cerebro.broker.setcash(100000.0)

    # Print out the starting conditions
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

    # Run over everything
    cerebro.run()

    # Print out the final result
    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

After the execution the output is:
执行后,输出为:

Starting Portfolio Value: 100000.00
2000-01-03T00:00:00, Close, 27.85
2000-01-04T00:00:00, Close, 25.39
2000-01-05T00:00:00, Close, 24.05
2000-01-05T00:00:00, BUY CREATE, 24.05
2000-01-06T00:00:00, BUY EXECUTED, 23.61
2000-01-06T00:00:00, Close, 22.63
2000-01-07T00:00:00, Close, 24.37
2000-01-10T00:00:00, Close, 27.29
2000-01-11T00:00:00, Close, 26.49
2000-01-12T00:00:00, Close, 24.90
2000-01-13T00:00:00, Close, 24.77
2000-01-13T00:00:00, SELL CREATE, 24.77
2000-01-14T00:00:00, SELL EXECUTED, 25.70
2000-01-14T00:00:00, Close, 25.18
...
...
...
2000-12-15T00:00:00, SELL CREATE, 26.93
2000-12-18T00:00:00, SELL EXECUTED, 28.29
2000-12-18T00:00:00, Close, 30.18
2000-12-19T00:00:00, Close, 28.88
2000-12-20T00:00:00, Close, 26.88
2000-12-20T00:00:00, BUY CREATE, 26.88
2000-12-21T00:00:00, BUY EXECUTED, 26.23
2000-12-21T00:00:00, Close, 27.82
2000-12-22T00:00:00, Close, 30.06
2000-12-26T00:00:00, Close, 29.17
2000-12-27T00:00:00, Close, 28.94
2000-12-28T00:00:00, Close, 29.29
2000-12-29T00:00:00, Close, 27.41
2000-12-29T00:00:00, SELL CREATE, 27.41
Final Portfolio Value: 100018.53

Blistering Barnacles!!! The system made money … something must be wrong
起泡的藤壶!!!这个系统赚钱了……肯定出了问题

The broker says: Show me the money!
经纪人说:给我看看钱!

And the money is called “commission”.
这笔钱被称为“佣金”。

Let’s add a reasonable 0.1% commision rate per operation (both for buying and selling … yes the broker is avid …)
让我们为每次操作增加合理的0.1%佣金(包括买卖…是的,经纪人很热衷…)

A single line will suffice for it:
一行就足够了:

# 0.1% ... divide by 100 to remove the %
cerebro.broker.setcommission(commission=0.001)

Being experienced with the platform we want to see the profit or loss after a buy/sell cycle, with and without commission.
有了平台的经验,我们希望看到买卖周期后的利润或亏损,包括佣金和不佣金。

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import datetime  # For datetime objects
import os.path  # To manage paths
import sys  # To find out the script name (in argv[0])

# Import the backtrader platform
import backtrader as bt


# Create a Stratey
class TestStrategy(bt.Strategy):

    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # Keep a reference to the "close" line in the data[0] dataseries
        self.dataclose = self.datas[0].close

        # To keep track of pending orders and buy price/commission
        self.order = None
        self.buyprice = None
        self.buycomm = None

    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
            return

        # Check if an order has been completed
        # Attention: broker could reject order if not enough cash
        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')

        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):
        # Simply log the closing price of the series from the reference
        self.log('Close, %.2f' % self.dataclose[0])

        # Check if an order is pending ... if yes, we cannot send a 2nd one
        if self.order:
            return

        # Check if we are in the market
        if not self.position:

            # Not yet ... we MIGHT BUY if ...
            if self.dataclose[0] < self.dataclose[-1]:
                    # current close less than previous close

                    if self.dataclose[-1] < self.dataclose[-2]:
                        # previous close less than the previous close

                        # BUY, BUY, BUY!!! (with default parameters)
                        self.log('BUY CREATE, %.2f' % self.dataclose[0])

                        # Keep track of the created order to avoid a 2nd order
                        self.order = self.buy()

        else:

            # Already in the market ... we might sell
            if len(self) >= (self.bar_executed + 5):
                # SELL, SELL, SELL!!! (with all possible default parameters)
                self.log('SELL CREATE, %.2f' % self.dataclose[0])

                # Keep track of the created order to avoid a 2nd order
                self.order = self.sell()


if __name__ == '__main__':
    # Create a cerebro entity
    cerebro = bt.Cerebro()

    # Add a strategy
    cerebro.addstrategy(TestStrategy)

    # Datas are in a subfolder of the samples. Need to find where the script is
    # because it could have been called from anywhere
    modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
    datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

    # Create a Data Feed
    data = bt.feeds.YahooFinanceCSVData(
        dataname=datapath,
        # Do not pass values before this date
        fromdate=datetime.datetime(2000, 1, 1),
        # Do not pass values before this date
        todate=datetime.datetime(2000, 12, 31),
        # Do not pass values after this date
        reverse=False)

    # Add the Data Feed to Cerebro
    cerebro.adddata(data)

    # Set our desired cash start
    cerebro.broker.setcash(100000.0)

    # Set the commission - 0.1% ... divide by 100 to remove the %
    cerebro.broker.setcommission(commission=0.001)

    # Print out the starting conditions
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

    # Run over everything
    cerebro.run()

    # Print out the final result
    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

After the execution the output is:
执行后,输出为:

Starting Portfolio Value: 100000.00
2000-01-03T00:00:00, Close, 27.85
2000-01-04T00:00:00, Close, 25.39
2000-01-05T00:00:00, Close, 24.05
2000-01-05T00:00:00, BUY CREATE, 24.05
2000-01-06T00:00:00, BUY EXECUTED, Price: 23.61, Cost: 23.61, Commission 0.02
2000-01-06T00:00:00, Close, 22.63
2000-01-07T00:00:00, Close, 24.37
2000-01-10T00:00:00, Close, 27.29
2000-01-11T00:00:00, Close, 26.49
2000-01-12T00:00:00, Close, 24.90
2000-01-13T00:00:00, Close, 24.77
2000-01-13T00:00:00, SELL CREATE, 24.77
2000-01-14T00:00:00, SELL EXECUTED, Price: 25.70, Cost: 25.70, Commission 0.03
2000-01-14T00:00:00, OPERATION PROFIT, GROSS 2.09, NET 2.04
2000-01-14T00:00:00, Close, 25.18
...
...
...
2000-12-15T00:00:00, SELL CREATE, 26.93
2000-12-18T00:00:00, SELL EXECUTED, Price: 28.29, Cost: 28.29, Commission 0.03
2000-12-18T00:00:00, OPERATION PROFIT, GROSS -0.06, NET -0.12
2000-12-18T00:00:00, Close, 30.18
2000-12-19T00:00:00, Close, 28.88
2000-12-20T00:00:00, Close, 26.88
2000-12-20T00:00:00, BUY CREATE, 26.88
2000-12-21T00:00:00, BUY EXECUTED, Price: 26.23, Cost: 26.23, Commission 0.03
2000-12-21T00:00:00, Close, 27.82
2000-12-22T00:00:00, Close, 30.06
2000-12-26T00:00:00, Close, 29.17
2000-12-27T00:00:00, Close, 28.94
2000-12-28T00:00:00, Close, 29.29
2000-12-29T00:00:00, Close, 27.41
2000-12-29T00:00:00, SELL CREATE, 27.41
Final Portfolio Value: 100016.98

God Save the Queen!!! The system still made money.
上帝保佑女王!!!这个系统仍然赚钱。

Before moving on, let’s notice something by filtering the “OPERATION PROFIT” lines:
在继续之前,让我们通过过滤“OPERATION PROFIT”行来注意一些事情:

2000-01-14T00:00:00, OPERATION PROFIT, GROSS 2.09, NET 2.04
2000-02-07T00:00:00, OPERATION PROFIT, GROSS 3.68, NET 3.63
2000-02-28T00:00:00, OPERATION PROFIT, GROSS 4.48, NET 4.42
2000-03-13T00:00:00, OPERATION PROFIT, GROSS 3.48, NET 3.41
2000-03-22T00:00:00, OPERATION PROFIT, GROSS -0.41, NET -0.49
2000-04-07T00:00:00, OPERATION PROFIT, GROSS 2.45, NET 2.37
2000-04-20T00:00:00, OPERATION PROFIT, GROSS -1.95, NET -2.02
2000-05-02T00:00:00, OPERATION PROFIT, GROSS 5.46, NET 5.39
2000-05-11T00:00:00, OPERATION PROFIT, GROSS -3.74, NET -3.81
2000-05-30T00:00:00, OPERATION PROFIT, GROSS -1.46, NET -1.53
2000-07-05T00:00:00, OPERATION PROFIT, GROSS -1.62, NET -1.69
2000-07-14T00:00:00, OPERATION PROFIT, GROSS 2.08, NET 2.01
2000-07-28T00:00:00, OPERATION PROFIT, GROSS 0.14, NET 0.07
2000-08-08T00:00:00, OPERATION PROFIT, GROSS 4.36, NET 4.29
2000-08-21T00:00:00, OPERATION PROFIT, GROSS 1.03, NET 0.95
2000-09-15T00:00:00, OPERATION PROFIT, GROSS -4.26, NET -4.34
2000-09-27T00:00:00, OPERATION PROFIT, GROSS 1.29, NET 1.22
2000-10-13T00:00:00, OPERATION PROFIT, GROSS -2.98, NET -3.04
2000-10-26T00:00:00, OPERATION PROFIT, GROSS 3.01, NET 2.95
2000-11-06T00:00:00, OPERATION PROFIT, GROSS -3.59, NET -3.65
2000-11-16T00:00:00, OPERATION PROFIT, GROSS 1.28, NET 1.23
2000-12-01T00:00:00, OPERATION PROFIT, GROSS 2.59, NET 2.54
2000-12-18T00:00:00, OPERATION PROFIT, GROSS -0.06, NET -0.12

Adding up the “NET” profits the final figure is:
把“净”利润加起来,最后的数字是:

15.83

But the system said the following at the end:
但该系统在最后说了以下内容:

2000-12-29T00:00:00, SELL CREATE, 27.41
Final Portfolio Value: 100016.98

And obviously 15.83 is not 16.98. There is no error whatsoever. The “NET” profit of 15.83 is already cash in the bag.
很明显,15.83不是16.98。没有任何错误。15.83的“净”利润已经是囊中羞涩。

Unfortunately (or fortunately to better understand the platform) there is an open position on the last day of the Data Feed. Even if a SELL operation has been sent … IT HAS NOT YET BEEN EXECUTED.
不幸的是(或者幸运的是,为了更好地了解平台),在数据馈送的最后一天有一个开放位置。即使发送了SELL操作……它也尚未执行。

The “Final Portfolio Value” calculated by the broker takes into account the “Close” price on 2000-12-29. The actual execution price would have been set on the next trading day which happened to be 2001-01-02. Extending the Data Feed” to take into account this day the output is:
经纪人计算的“最终投资组合价值”考虑了2000-12-29的“收盘”价格。实际执行价格将在下一个交易日确定,恰好是2001-01-02。扩展数据馈送”以考虑这一天的输出是:

2001-01-02T00:00:00, SELL EXECUTED, Price: 27.87, Cost: 27.87, Commission 0.03
2001-01-02T00:00:00, OPERATION PROFIT, GROSS 1.64, NET 1.59
2001-01-02T00:00:00, Close, 24.87
2001-01-02T00:00:00, BUY CREATE, 24.87
Final Portfolio Value: 100017.41

Now adding the previous NET profit to the completed operation’s net profit:
现在将以前的净利润添加到已完成运营的净利润中:

15.83 + 1.59 = 17.42

Which (discarding rounding errors in the “print” statements) is the extra Portfolio above the initial 100000 monetary units the strategy started with.
哪一个(放弃“打印”报表中的舍入错误)是策略开始时超过初始100000货币单位的额外投资组合。

Customizing the Strategy: Parameters
自定义策略:参数

It would a bit unpractical to hardcode some of the values in the strategy and have no chance to change them easily. Parameters come in handy to help.
对策略中的一些值进行硬编码有点不切实际,而且没有机会轻易更改它们。参数非常有用。

Definition of parameters is easy and looks like:
参数的定义很容易,看起来像:

params = (('myparam', 27), ('exitbars', 5),)

Being this a standard Python tuple with some tuples inside it, the following may look more appealling to some:
由于这是一个标准的Python元组,其中包含一些元组,以下内容对一些人来说可能更具吸引力:

params = (
    ('myparam', 27),
    ('exitbars', 5),
)

With either formatting parametrization of the strategy is allowed when adding the strategy to the Cerebro engine:
在将策略添加到Cerebro引擎时,允许对策略进行格式化参数化:

# Add a strategy
cerebro.addstrategy(TestStrategy, myparam=20, exitbars=7)

Note 笔记

The setsizing method below is deprecated. This content is kept here for anyone looking at old samples of the sources. The sources have been update to use:
下面的 setsizing 方法已弃用。这些内容保留在这里,供任何查看旧来源样本的人使用。源已更新为使用:

cerebro.addsizer(bt.sizers.FixedSize, stake=10)``

Please read the section about sizers
请阅读有关尺码器的部分

Using the parameters in the strategy is easy, as they are stored in a “params” attribute. If we for example want to set the stake fix, we can pass the stake parameter to the position sizer like this durint init:
在策略中使用参数很容易,因为它们存储在“params”属性中。例如,如果我们想设置桩号固定,我们可以在初始化过程中将桩号参数传递给位置大小器,如下所示:

# Set the sizer stake from the params
self.sizer.setsizing(self.params.stake)

We could have also called buy and sell with a stake parameter and self.params.stake as the value.
我们也可以用一个share参数和self.parms.stake作为值来调用买入和卖出。

The logic to exit gets modified:
退出的逻辑被修改:

# Already in the market ... we might sell
if len(self) >= (self.bar_executed + self.params.exitbars):

With all this in mind the example evolves to look like:
考虑到所有这些,示例演变为:

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import datetime  # For datetime objects
import os.path  # To manage paths
import sys  # To find out the script name (in argv[0])

# Import the backtrader platform
import backtrader as bt


# Create a Stratey
class TestStrategy(bt.Strategy):
    params = (
        ('exitbars', 5),
    )

    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # Keep a reference to the "close" line in the data[0] dataseries
        self.dataclose = self.datas[0].close

        # To keep track of pending orders and buy price/commission
        self.order = None
        self.buyprice = None
        self.buycomm = None

    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
            return

        # Check if an order has been completed
        # Attention: broker could reject order if not enough cash
        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')

        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):
        # Simply log the closing price of the series from the reference
        self.log('Close, %.2f' % self.dataclose[0])

        # Check if an order is pending ... if yes, we cannot send a 2nd one
        if self.order:
            return

        # Check if we are in the market
        if not self.position:

            # Not yet ... we MIGHT BUY if ...
            if self.dataclose[0] < self.dataclose[-1]:
                    # current close less than previous close

                    if self.dataclose[-1] < self.dataclose[-2]:
                        # previous close less than the previous close

                        # BUY, BUY, BUY!!! (with default parameters)
                        self.log('BUY CREATE, %.2f' % self.dataclose[0])

                        # Keep track of the created order to avoid a 2nd order
                        self.order = self.buy()

        else:

            # Already in the market ... we might sell
            if len(self) >= (self.bar_executed + self.params.exitbars):
                # SELL, SELL, SELL!!! (with all possible default parameters)
                self.log('SELL CREATE, %.2f' % self.dataclose[0])

                # Keep track of the created order to avoid a 2nd order
                self.order = self.sell()

if __name__ == '__main__':
    # Create a cerebro entity
    cerebro = bt.Cerebro()

    # Add a strategy
    cerebro.addstrategy(TestStrategy)

    # Datas are in a subfolder of the samples. Need to find where the script is
    # because it could have been called from anywhere
    modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
    datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

    # Create a Data Feed
    data = bt.feeds.YahooFinanceCSVData(
        dataname=datapath,
        # Do not pass values before this date
        fromdate=datetime.datetime(2000, 1, 1),
        # Do not pass values before this date
        todate=datetime.datetime(2000, 12, 31),
        # Do not pass values after this date
        reverse=False)

    # Add the Data Feed to Cerebro
    cerebro.adddata(data)

    # Set our desired cash start
    cerebro.broker.setcash(100000.0)

    # Add a FixedSize sizer according to the stake
    cerebro.addsizer(bt.sizers.FixedSize, stake=10)

    # Set the commission - 0.1% ... divide by 100 to remove the %
    cerebro.broker.setcommission(commission=0.001)

    # Print out the starting conditions
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

    # Run over everything
    cerebro.run()

    # Print out the final result
    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

After the execution the output is:
执行后,输出为:

Starting Portfolio Value: 100000.00
2000-01-03T00:00:00, Close, 27.85
2000-01-04T00:00:00, Close, 25.39
2000-01-05T00:00:00, Close, 24.05
2000-01-05T00:00:00, BUY CREATE, 24.05
2000-01-06T00:00:00, BUY EXECUTED, Size 10, Price: 23.61, Cost: 236.10, Commission 0.24
2000-01-06T00:00:00, Close, 22.63
...
...
...
2000-12-20T00:00:00, BUY CREATE, 26.88
2000-12-21T00:00:00, BUY EXECUTED, Size 10, Price: 26.23, Cost: 262.30, Commission 0.26
2000-12-21T00:00:00, Close, 27.82
2000-12-22T00:00:00, Close, 30.06
2000-12-26T00:00:00, Close, 29.17
2000-12-27T00:00:00, Close, 28.94
2000-12-28T00:00:00, Close, 29.29
2000-12-29T00:00:00, Close, 27.41
2000-12-29T00:00:00, SELL CREATE, 27.41
Final Portfolio Value: 100169.80

In order to see the difference, the print outputs have also been extended to show the execution size.
为了看到差异,打印输出也进行了扩展,以显示执行大小。

Having multiplied the stake by 10, the obvious has happened: the profit and loss has been multiplied by 10. Instead of 16.98, the surplus is now 169.80
将股份乘以10,显而易见的事情发生了:损益乘以10。盈余现在是169.80,而不是16.98

Adding an indicator 添加指示器

Having heard of indicators, the next thing anyone would add to the strategy is one of them. For sure they must be much better than a simple “3 lower closes” strategy.
听说过指标后,接下来任何人都会在战略中添加其中一个指标。可以肯定的是,它们一定比简单的“3个低位收盘”策略要好得多。

Inspired in one of the examples from PyAlgoTrade a strategy using a Simple Moving Average.
PyAlgoTrade的一个例子启发了使用简单移动平均线的策略。

  • Buy “AtMarket” if the close is greater than the Average
    如果收盘价高于平均值,则买入“AtMarket”

  • If in the market, sell if the close is smaller than the Average
    如果在市场上,如果收盘价小于平均值,则卖出

  • Only 1 active operation is allowed in the market
    市场上只允许1个活动操作

Most of the existing code can be kept in place. Let’s add the average during init and keep a reference to it:
大多数现有的代码都可以保留在适当的位置。让我们在初始化期间添加平均值,并保留对它的引用:

self.sma = bt.indicators.MovingAverageSimple(self.datas[0], period=self.params.maperiod)

And of course the logic to enter and exit the market will rely on the Average values. Look in the code for the logic.
当然,进入和退出市场的逻辑将取决于平均值。在代码中查找逻辑。

Note 笔记

The starting cash will be 1000 monetary units to be in line with the PyAlgoTrade example and no commission will be applied
起始现金将为1000个货币单位,与PyAlgoTrade的例子一致,不收取佣金

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import datetime  # For datetime objects
import os.path  # To manage paths
import sys  # To find out the script name (in argv[0])

# Import the backtrader platform
import backtrader as bt


# Create a Stratey
class TestStrategy(bt.Strategy):
    params = (
        ('maperiod', 15),
    )

    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # Keep a reference to the "close" line in the data[0] dataseries
        self.dataclose = self.datas[0].close

        # To keep track of pending orders and buy price/commission
        self.order = None
        self.buyprice = None
        self.buycomm = None

        # Add a MovingAverageSimple indicator
        self.sma = bt.indicators.SimpleMovingAverage(
            self.datas[0], period=self.params.maperiod)

    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
            return

        # Check if an order has been completed
        # Attention: broker could reject order if not enough cash
        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')

        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):
        # Simply log the closing price of the series from the reference
        self.log('Close, %.2f' % self.dataclose[0])

        # Check if an order is pending ... if yes, we cannot send a 2nd one
        if self.order:
            return

        # Check if we are in the market
        if not self.position:

            # Not yet ... we MIGHT BUY if ...
            if self.dataclose[0] > self.sma[0]:

                # BUY, BUY, BUY!!! (with all possible default parameters)
                self.log('BUY CREATE, %.2f' % self.dataclose[0])

                # Keep track of the created order to avoid a 2nd order
                self.order = self.buy()

        else:

            if self.dataclose[0] < self.sma[0]:
                # SELL, SELL, SELL!!! (with all possible default parameters)
                self.log('SELL CREATE, %.2f' % self.dataclose[0])

                # Keep track of the created order to avoid a 2nd order
                self.order = self.sell()


if __name__ == '__main__':
    # Create a cerebro entity
    cerebro = bt.Cerebro()

    # Add a strategy
    cerebro.addstrategy(TestStrategy)

    # Datas are in a subfolder of the samples. Need to find where the script is
    # because it could have been called from anywhere
    modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
    datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

    # Create a Data Feed
    data = bt.feeds.YahooFinanceCSVData(
        dataname=datapath,
        # Do not pass values before this date
        fromdate=datetime.datetime(2000, 1, 1),
        # Do not pass values before this date
        todate=datetime.datetime(2000, 12, 31),
        # Do not pass values after this date
        reverse=False)

    # Add the Data Feed to Cerebro
    cerebro.adddata(data)

    # Set our desired cash start
    cerebro.broker.setcash(1000.0)

    # Add a FixedSize sizer according to the stake
    cerebro.addsizer(bt.sizers.FixedSize, stake=10)

    # Set the commission
    cerebro.broker.setcommission(commission=0.0)

    # Print out the starting conditions
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

    # Run over everything
    cerebro.run()

    # Print out the final result
    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

Now, before skipping to the next section LOOK CAREFULLY to the first date which is shown in the log:
现在,在跳到下一节之前,请仔细查看日志中显示的第一个日期:

  • It’ no longer 2000-01-03, the first trading day in the year 2K.
    它不再是2000-01-03,2K年的第一个交易日。

    It’s 2000-01-24 … Who has stolen my cheese?
    现在是2000-01-24…谁偷了我的奶酪?

The missing days are not missing. The platform has adapted to the new circumstances:
错过的日子并没有错过。该平台已适应新情况:

  • An indicator (SimpleMovingAverage) has been added to the Strategy.
    战略中增加了一个指标(SimpleMovingAverage)。

  • This indicator needs X bars to produce an output: in the example: 15
    此指示器需要X条才能产生输出:在示例中:15

  • 2000-01-24 is the day in which the 15th bar occurs
    2000-01-24是15 th 棒发生的日子

The backtrader platform assumes that the Strategy has the indicator in place for a good reason, to use it in the decision making process. And it makes no sense to try to make decisions if the indicator is not yet ready and producing values.
backtrader平台假设策略有充分的理由将指标用于决策过程。如果指标还没有准备好并产生价值,那么尝试做出决定是没有意义的。

  • next will be 1st called when all indicators have already reached the minimum needed period to produce a value
    下一个将是在所有指标都已达到生成值所需的最小周期时调用1 st

  • In the example there is a single indicator, but the strategy could have any number of them.
    在这个例子中,只有一个指标,但策略可以有任意数量的指标。

After the execution the output is:
执行后,输出为:

Starting Portfolio Value: 1000.00
2000-01-24T00:00:00, Close, 25.55
2000-01-25T00:00:00, Close, 26.61
2000-01-25T00:00:00, BUY CREATE, 26.61
2000-01-26T00:00:00, BUY EXECUTED, Size 10, Price: 26.76, Cost: 267.60, Commission 0.00
2000-01-26T00:00:00, Close, 25.96
2000-01-27T00:00:00, Close, 24.43
2000-01-27T00:00:00, SELL CREATE, 24.43
2000-01-28T00:00:00, SELL EXECUTED, Size 10, Price: 24.28, Cost: 242.80, Commission 0.00
2000-01-28T00:00:00, OPERATION PROFIT, GROSS -24.80, NET -24.80
2000-01-28T00:00:00, Close, 22.34
2000-01-31T00:00:00, Close, 23.55
2000-02-01T00:00:00, Close, 25.46
2000-02-02T00:00:00, Close, 25.61
2000-02-02T00:00:00, BUY CREATE, 25.61
2000-02-03T00:00:00, BUY EXECUTED, Size 10, Price: 26.11, Cost: 261.10, Commission 0.00
...
...
...
2000-12-20T00:00:00, SELL CREATE, 26.88
2000-12-21T00:00:00, SELL EXECUTED, Size 10, Price: 26.23, Cost: 262.30, Commission 0.00
2000-12-21T00:00:00, OPERATION PROFIT, GROSS -20.60, NET -20.60
2000-12-21T00:00:00, Close, 27.82
2000-12-21T00:00:00, BUY CREATE, 27.82
2000-12-22T00:00:00, BUY EXECUTED, Size 10, Price: 28.65, Cost: 286.50, Commission 0.00
2000-12-22T00:00:00, Close, 30.06
2000-12-26T00:00:00, Close, 29.17
2000-12-27T00:00:00, Close, 28.94
2000-12-28T00:00:00, Close, 29.29
2000-12-29T00:00:00, Close, 27.41
2000-12-29T00:00:00, SELL CREATE, 27.41
Final Portfolio Value: 973.90

In the name of the King!!! A winning system turned into a losing one … and that with no commission. It may well be that simply adding an indicator is not the universal panacea.
以国王的名义!!!一个获胜的系统变成了一个失败的系统…而且没有佣金。很可能仅仅增加一个指标并不是万能的灵丹妙药。

Note 笔记

The same logic and data with PyAlgoTrade yields a slightly different result (slightly off). Looking at the entire printout reveals that some operations are not exactly the same. Being the culprit again the usual suspect: rounding.
PyAlgoTrade的相同逻辑和数据产生的结果略有不同(略有偏离)。查看整个打印输出显示,有些操作并不完全相同。再次成为罪魁祸首——通常的嫌疑人:围捕。

PyAlgoTrade does not round the datafeed values when applying the divided “adjusted close” to the data feed values.
PyAlgoTrade在将除法“调整后的接近值”应用于数据馈送值时不会对数据馈送值进行四舍五入。

The Yahoo Data Feed provided by backtrader rounds the values down to 2 decimals after applying the adjusted close. Upon printing the values everything seems the same, but it’s obvious that sometimes that 5th place decimal plays a role.
backtrader提供的雅虎数据馈送在应用调整后的收盘价后将数值四舍五入至2位小数。打印值时,一切看起来都一样,但很明显,有时小数点后5位起到了一定作用。

Rounding down to 2 decimals seems more realistic, because Market Exchanges do only allow a number of decimals per asset (being that 2 decimals usually for stocks)
四舍五入到2位小数似乎更现实,因为市场交易所只允许每种资产有一定数量的小数(通常是股票的2位小数)

Note 笔记

The Yahoo Data Feed (starting with version 1.8.11.99 allows to specify if rounding has to happen and how many decimals)
Yahoo数据馈送(从 1.8.11.99 版本开始,允许指定是否必须进行舍入以及小数位数)

Visual Inspection: Plotting
目视检查:绘图

A printout or log of the actual whereabouts of the system at each bar-instant is good but humans tend to be visual and therefore it seems right to offer a view of the same whereabouts as chart.
系统在每个条形图瞬间的实际位置的打印输出或日志是好的,但人类往往是可见的,因此提供与图表相同位置的视图似乎是正确的。

Note 笔记

To plot you need to have matplotlib installed
要打印,需要安装matplotlib

Once again defaults for plotting are there to assist the platform user. Plotting is incredibly a 1 line operation:
再次默认的绘图是为了帮助平台用户。绘图是一项令人难以置信的单线操作:

cerebro.plot()

Being the location for sure after cerebro.run() has been called.
在调用了cerebro.run()之后,肯定是该位置。

In order to display the automatic plotting capabilities and a couple of easy customizations, the following will be done:
为了显示自动打印功能和一些简单的自定义,将执行以下操作:

  • A 2nd MovingAverage (Exponential) will be added. The defaults will plot it (just like the 1st) with the data.
    将添加一个2 nd 移动平均值(指数)。默认值将用数据绘制它(就像1#一样)。

  • A 3rd MovingAverage (Weighted) will be added. Customized to plot in an own plot (even if not sensible)
    将添加一个3 rd 移动平均值(加权)。自定义为在自己的绘图中绘图(即使不合理)

  • A Stochastic (Slow) will be added. No change to the defaults.
    将添加随机(慢速)。默认值不变。

  • A MACD will be added. No change to the defaults.
    将添加一个MACD。默认值不变。

  • A RSI will be added. No change to the defaults.
    将添加RSI。默认值不变。

  • A MovingAverage (Simple) will be applied to the RSI. No change to the defaults (it will be plotted with the RSI)
    移动平均值(简单)将应用于RSI。默认值不变(将与RSI一起绘制)

  • An AverageTrueRange will be added. Changed defaults to avoid it being plotted.
    将添加一个AverageTrueRange。更改了默认值以避免打印。

The entire set of additions to the init method of the Strategy:
对策略的init方法的整个添加集:

# Indicators for the plotting show
bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
bt.indicators.WeightedMovingAverage(self.datas[0], period=25).subplot = True
bt.indicators.StochasticSlow(self.datas[0])
bt.indicators.MACDHisto(self.datas[0])
rsi = bt.indicators.RSI(self.datas[0])
bt.indicators.SmoothedMovingAverage(rsi, period=10)
bt.indicators.ATR(self.datas[0]).plot = False

Note 笔记

Even if indicators are not explicitly added to a member variable of the strategy (like self.sma = MovingAverageSimple…), they will autoregister with the strategy and will influence the minimum period for next and will be part of the plotting.
即使指标没有明确添加到策略的成员变量中(如self.sma=MovingAverageSimple…),它们也会自动注册策略,并将影响下一个的最短周期,并将成为绘图的一部分。

In the example only RSI is added to a temporary variable rsi with the only intention to create a MovingAverageSmoothed on it.
在本例中,只有RSI被添加到临时变量RSI中,其唯一目的是在其上创建MovingAverageSmoothed。

The example now: 现在的例子是:

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import datetime  # For datetime objects
import os.path  # To manage paths
import sys  # To find out the script name (in argv[0])

# Import the backtrader platform
import backtrader as bt


# Create a Stratey
class TestStrategy(bt.Strategy):
    params = (
        ('maperiod', 15),
    )

    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # Keep a reference to the "close" line in the data[0] dataseries
        self.dataclose = self.datas[0].close

        # To keep track of pending orders and buy price/commission
        self.order = None
        self.buyprice = None
        self.buycomm = None

        # Add a MovingAverageSimple indicator
        self.sma = bt.indicators.SimpleMovingAverage(
            self.datas[0], period=self.params.maperiod)

        # Indicators for the plotting show
        bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
        bt.indicators.WeightedMovingAverage(self.datas[0], period=25,
                                            subplot=True)
        bt.indicators.StochasticSlow(self.datas[0])
        bt.indicators.MACDHisto(self.datas[0])
        rsi = bt.indicators.RSI(self.datas[0])
        bt.indicators.SmoothedMovingAverage(rsi, period=10)
        bt.indicators.ATR(self.datas[0], plot=False)

    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
            return

        # Check if an order has been completed
        # Attention: broker could reject order if not enough cash
        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):
        # Simply log the closing price of the series from the reference
        self.log('Close, %.2f' % self.dataclose[0])

        # Check if an order is pending ... if yes, we cannot send a 2nd one
        if self.order:
            return

        # Check if we are in the market
        if not self.position:

            # Not yet ... we MIGHT BUY if ...
            if self.dataclose[0] > self.sma[0]:

                # BUY, BUY, BUY!!! (with all possible default parameters)
                self.log('BUY CREATE, %.2f' % self.dataclose[0])

                # Keep track of the created order to avoid a 2nd order
                self.order = self.buy()

        else:

            if self.dataclose[0] < self.sma[0]:
                # SELL, SELL, SELL!!! (with all possible default parameters)
                self.log('SELL CREATE, %.2f' % self.dataclose[0])

                # Keep track of the created order to avoid a 2nd order
                self.order = self.sell()


if __name__ == '__main__':
    # Create a cerebro entity
    cerebro = bt.Cerebro()

    # Add a strategy
    cerebro.addstrategy(TestStrategy)

    # Datas are in a subfolder of the samples. Need to find where the script is
    # because it could have been called from anywhere
    modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
    datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

    # Create a Data Feed
    data = bt.feeds.YahooFinanceCSVData(
        dataname=datapath,
        # Do not pass values before this date
        fromdate=datetime.datetime(2000, 1, 1),
        # Do not pass values before this date
        todate=datetime.datetime(2000, 12, 31),
        # Do not pass values after this date
        reverse=False)

    # Add the Data Feed to Cerebro
    cerebro.adddata(data)

    # Set our desired cash start
    cerebro.broker.setcash(1000.0)

    # Add a FixedSize sizer according to the stake
    cerebro.addsizer(bt.sizers.FixedSize, stake=10)

    # Set the commission
    cerebro.broker.setcommission(commission=0.0)

    # Print out the starting conditions
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

    # Run over everything
    cerebro.run()

    # Print out the final result
    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

    # Plot the result
    cerebro.plot()

After the execution the output is:
执行后,输出为:

Starting Portfolio Value: 1000.00
2000-02-18T00:00:00, Close, 27.61
2000-02-22T00:00:00, Close, 27.97
2000-02-22T00:00:00, BUY CREATE, 27.97
2000-02-23T00:00:00, BUY EXECUTED, Size 10, Price: 28.38, Cost: 283.80, Commission 0.00
2000-02-23T00:00:00, Close, 29.73
...
...
...
2000-12-21T00:00:00, BUY CREATE, 27.82
2000-12-22T00:00:00, BUY EXECUTED, Size 10, Price: 28.65, Cost: 286.50, Commission 0.00
2000-12-22T00:00:00, Close, 30.06
2000-12-26T00:00:00, Close, 29.17
2000-12-27T00:00:00, Close, 28.94
2000-12-28T00:00:00, Close, 29.29
2000-12-29T00:00:00, Close, 27.41
2000-12-29T00:00:00, SELL CREATE, 27.41
Final Portfolio Value: 981.00

The final result has changed even if the logic hasn’t. This is true but the logic has not been applied to the same number of bars.
即使逻辑没有改变,最终结果也发生了变化。这是真的,但逻辑没有应用于相同数量的条。

Note 笔记

As explained before, the platform will first call next when all indicators are ready to produce a value. In this plotting example (very clear in the chart) the MACD is the last indicator to be fully ready (all 3 lines producing an output). The 1st BUY order is no longer scheduled during Jan 2000 but close to the end of Feb 2000.
如前所述,当所有指标都准备好产生一个值时,平台将首先调用next。在这个绘图示例中(在图表中非常清楚),MACD是最后一个完全准备好的指标(所有3行都产生输出)。1 st 购买订单不再安排在2000年1月,而是接近2000年2月底。

The chart: 图表:

!image

Let’s Optimize 让我们优化

Many trading books say each market and each traded stock (or commodity or ..) have different rythms. That there is no such thing as a one size fits all.
许多交易书籍说,每个市场和每个交易的股票(或商品或..)都有不同的节奏。不存在一刀切的做法。

Before the plotting sample, when the strategy started using an indicator the period default value was 15 bars. It’s a strategy parameter and this can be used in an optimization to change the value of the parameter and see which one better fits the market.
在绘制示例之前,当策略开始使用指示器时,周期默认值为15巴。这是一个策略参数,可以在优化中使用它来改变参数的值,看看哪一个更适合市场。

Note 笔记

There is plenty of literature about Optimization and associated pros and cons. But the advice will always point in the same direction: do not overoptimize. If a trading idea is not sound, optimizing may end producing a positive result which is only valid for the backtested dataset.
有很多关于优化和相关利弊的文献。但建议总是指向同一个方向:不要过度优化。如果一个交易想法不合理,优化可能最终会产生一个只对经过回溯测试的数据集有效的积极结果。

The sample is modified to optimize the period of the Simple Moving Average. For the sake of clarity any output with regards to Buy/Sell orders has been removed
修改样本以优化简单移动平均线的周期。为清楚起见,已删除与买入/卖出订单有关的任何输出

The example now: 现在的例子是:

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import datetime  # For datetime objects
import os.path  # To manage paths
import sys  # To find out the script name (in argv[0])


# Import the backtrader platform
import backtrader as bt


# Create a Stratey
class TestStrategy(bt.Strategy):
    params = (
        ('maperiod', 15),
        ('printlog', False),
    )

    def log(self, txt, dt=None, doprint=False):
        ''' Logging function fot this strategy'''
        if self.params.printlog or doprint:
            dt = dt or self.datas[0].datetime.date(0)
            print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # Keep a reference to the "close" line in the data[0] dataseries
        self.dataclose = self.datas[0].close

        # To keep track of pending orders and buy price/commission
        self.order = None
        self.buyprice = None
        self.buycomm = None

        # Add a MovingAverageSimple indicator
        self.sma = bt.indicators.SimpleMovingAverage(
            self.datas[0], period=self.params.maperiod)

    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
            return

        # Check if an order has been completed
        # Attention: broker could reject order if not enough cash
        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):
        # Simply log the closing price of the series from the reference
        self.log('Close, %.2f' % self.dataclose[0])

        # Check if an order is pending ... if yes, we cannot send a 2nd one
        if self.order:
            return

        # Check if we are in the market
        if not self.position:

            # Not yet ... we MIGHT BUY if ...
            if self.dataclose[0] > self.sma[0]:

                # BUY, BUY, BUY!!! (with all possible default parameters)
                self.log('BUY CREATE, %.2f' % self.dataclose[0])

                # Keep track of the created order to avoid a 2nd order
                self.order = self.buy()

        else:

            if self.dataclose[0] < self.sma[0]:
                # SELL, SELL, SELL!!! (with all possible default parameters)
                self.log('SELL CREATE, %.2f' % self.dataclose[0])

                # Keep track of the created order to avoid a 2nd order
                self.order = self.sell()

    def stop(self):
        self.log('(MA Period %2d) Ending Value %.2f' %
                 (self.params.maperiod, self.broker.getvalue()), doprint=True)


if __name__ == '__main__':
    # Create a cerebro entity
    cerebro = bt.Cerebro()

    # Add a strategy
    strats = cerebro.optstrategy(
        TestStrategy,
        maperiod=range(10, 31))

    # Datas are in a subfolder of the samples. Need to find where the script is
    # because it could have been called from anywhere
    modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
    datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

    # Create a Data Feed
    data = bt.feeds.YahooFinanceCSVData(
        dataname=datapath,
        # Do not pass values before this date
        fromdate=datetime.datetime(2000, 1, 1),
        # Do not pass values before this date
        todate=datetime.datetime(2000, 12, 31),
        # Do not pass values after this date
        reverse=False)

    # Add the Data Feed to Cerebro
    cerebro.adddata(data)

    # Set our desired cash start
    cerebro.broker.setcash(1000.0)

    # Add a FixedSize sizer according to the stake
    cerebro.addsizer(bt.sizers.FixedSize, stake=10)

    # Set the commission
    cerebro.broker.setcommission(commission=0.0)

    # Run over everything
    cerebro.run(maxcpus=1)

Instead of calling addstrategy to add a stratey class to Cerebro, the call is made to optstrategy. And instead of passing a value a range of values is passed.
不是调用addstrategy来向Cerebro添加strategy类,而是调用optstrategy。而不是传递一个值,而是传递一系列值。

One of the “Strategy” hooks is added, the stop method, which will be called when the data has been exhausted and backtesting is over. It’s used to print the final net value of the portfolio in the broker (it was done in Cerebro previously)
添加了一个“策略”挂钩,即stop方法,当数据用完并且回溯测试结束时,将调用该方法。它用于打印经纪人中投资组合的最终净值(之前在Cerebro中完成)

The system will execute the strategy for each value of the range. The following will be output:
系统将针对范围的每个值执行策略。将输出以下内容:

2000-12-29, (MA Period 10) Ending Value 880.30
2000-12-29, (MA Period 11) Ending Value 880.00
2000-12-29, (MA Period 12) Ending Value 830.30
2000-12-29, (MA Period 13) Ending Value 893.90
2000-12-29, (MA Period 14) Ending Value 896.90
2000-12-29, (MA Period 15) Ending Value 973.90
2000-12-29, (MA Period 16) Ending Value 959.40
2000-12-29, (MA Period 17) Ending Value 949.80
2000-12-29, (MA Period 18) Ending Value 1011.90
2000-12-29, (MA Period 19) Ending Value 1041.90
2000-12-29, (MA Period 20) Ending Value 1078.00
2000-12-29, (MA Period 21) Ending Value 1058.80
2000-12-29, (MA Period 22) Ending Value 1061.50
2000-12-29, (MA Period 23) Ending Value 1023.00
2000-12-29, (MA Period 24) Ending Value 1020.10
2000-12-29, (MA Period 25) Ending Value 1013.30
2000-12-29, (MA Period 26) Ending Value 998.30
2000-12-29, (MA Period 27) Ending Value 982.20
2000-12-29, (MA Period 28) Ending Value 975.70
2000-12-29, (MA Period 29) Ending Value 983.30
2000-12-29, (MA Period 30) Ending Value 979.80

Results: 后果

  • For periods below 18 the strategy (commissionless) loses money.
    在18岁以下的时期,策略(无佣金)会赔钱。

  • For periods between 18 and 26 (both included) the strategy makes money.
    在18岁到26岁之间(两者都包括在内),这种策略可以赚钱。

  • Above 26 money is lost again.
    26元以上再次亏损。

And the winning period for this strategy and the given data set is:
该策略和给定数据集的获胜期为:

  • 20 bars, which wins 78.00 units over 1000 $/€ (a 7.8%)
    20个酒吧,赢得78.00个单位超过1000美元/欧元(7.8%)

Note 笔记

The extra indicators from the plotting example have been removed and the start of operations is only influenced by the Simple Moving Average which is being optimized. Hence the slightly different results for period 15
绘图示例中的额外指示器已被删除,操作的开始仅受正在优化的简单移动平均值的影响。因此,第15期的结果略有不同

Conclusion 结论

The incremental samples have shown how to go from a barebones script to a fully working trading system which even plots the results and can be optimized.
增量样本展示了如何从一个简单的脚本到一个完全工作的交易系统,该系统甚至可以绘制结果并进行优化。

A lot more can be done to try to improve the chances of winning:
要想提高获胜几率,还需要做更多的工作:

  • Self defined Indicators 自定义指标

    Creating an indicator is easy (and even plotting them is easy)
    创建指示器很容易(甚至绘制指示器也很容易)

  • Sizers

    Money Management is for many the key to success
    对许多人来说,理财是成功的关键

  • Order Types (limit, stop, stoplimit) 

  • Some others 

To ensure all the above items can be fully utilized the documentation provides an insight into them (and other topics) 

Look in the table of contents and keep on reading … and developing. 

Best of luck