quickstart Guide快速入门

news2024/11/20 20:39:07

本文档参考backtrader官方文档,是官方文档的完整中文翻译,可作为backtrader中文教程、backtrader中文参考手册、backtrader中文开发手册、backtrader入门资料使用。

快速入门章节目录

  • 快速入门
    • 使用平台
    • 从0到100:一步一步的演示
      • 基本设置
      • 设置现金
      • 添加一个数据源
      • 我们的第一个策略
      • 给策略添加执行逻辑
      • 不仅要买...还要卖
      • 券商说:打钱!
      • 自定义策略:参数
      • 添加指标
      • 可视化检查:绘图
      • 让我们优化
      • 结论

快速入门

使用平台

让我们通过一系列的示例(从几乎空白到完全成熟的策略)来运行,但在此之前,让我们大致解释一下使用backtrader时的两个基本概念。

  1. Lines线

    数据源、指标和策略都有线

    一条线是一系列点的连续性,这些点连接在一起形成这条线。当谈论市场时,数据源通常每天有以下一组点:

    • Open, High, Low, Close, Volume, OpenInterest (开盘价、最高价、最低价、收盘价、成交量、持仓量)

    时间序列中的开盘价是一条线。因此,数据源通常有6条线。

    如果我们还考虑DateTime(这是单个点的实际参考),我们可以计算出7条线。

  2. 索引0的用法

    在访问线中的值时,使用索引:0访问当前值

    last输出值使用*-1访问。这符合Python可迭代对象的惯例(线可以迭代,因此是可迭代的),其中索引-1*用于访问可迭代/数组的last项。

    在我们的情况下,访问的是最后一个输出值。

    因此,索引0紧随*-1*之后,用于访问线中的当前时刻。

有了这个想法,如果我们想在初始化期间创建一个简单移动平均线的策略:

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

访问此移动平均线的当前值的最简单和最简单的方法是:

    av = self.sma[0]

无需知道已处理了多少个bar/分钟/天/月,因为0是当前时刻的唯一标识。

按照Python的传统,使用*-1*访问last输出值:

    previous_value = self.sma[-1]

当然,可以使用-2、-3等访问早期的输出值。

从0到100:一步一步的演示

基本设置

让我们开始吧。

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
# 导入Backtrader
import backtrader as bt

if __name__ == '__main__':
    # 创建一个Cerebro引擎实例
    cerebro = bt.Cerebro()
    # 打印初始资金
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
    # 执行回测
    cerebro.run()
    # 打印最终资金
    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

上面示例代码实现(虽然什么也没做,但是流程走完了):

  • 导入Backtrader
  • 实例化Cerebro引擎
  • 打印初始资金
  • 执行回测
  • 打印最终资金

代码的执行结果(小白注意不用拷贝执行,这是运行后得到的内容):

Starting Portfolio Value: 10000.00
Final Portfolio Value: 10000.00

在这个例子中:

  • 导入了backtrader

  • 实例化了Cerebro引擎

  • 生成的cerebro实例被告知run(循环数据)

  • 并打印出了结果

虽然看起来不起眼,但让我们明确指出一些东西:

  • Cerebro引擎在后台创建了一个broker实例
  • 该实例已经有一些现金可以开始交易

这种幕后经纪人实例化是平台中的一个常见特征,以简化用户的生活。如果用户没有设置经纪人,则会放置一个默认经纪人。

而10K的测试货币量是回测常用的一个货币价值。

设置现金

在金融世界中,只有输家才从10k开始。让我们改变现金并再次运行示例。

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())

执行后的输出为:

  Starting Portfolio Value: 1000000.00
  Final Portfolio Value: 1000000.00

本节任务完成,让我们提升任务的难度。

添加一个数据源

有现金很有趣,但所有这一切的目的是让策略在我们提供的数据源资产上自动操作,而不需要动手。

因此…没有数据源->没有乐趣。让我们将其添加到不断增长的示例中。

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

import datetime  # 用于日期时间对象
import os.path  # 用于管理路径
import sys  # 用于查找脚本名称(在argv [0]中)

# 导入backtrader平台
import backtrader as bt

if __name__ == '__main__':
    # 创建cerebro实体
    cerebro = bt.Cerebro()

    # 数据在样本的子文件夹中。需要找到脚本所在的位置
    # 因为它可以从任何地方调用
    modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
    datapath = os.path.join(modpath, '../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)

    # 将数据源添加到Cerebro
    cerebro.adddata(data)

    # 设置我们所需的现金起始值
    cerebro.broker.setcash(100000.0)

    # 打印出起始条件
    print('起始投资组合价值:%.2f' % cerebro.broker.getvalue())

    # 运行所有
    cerebro.run()

    # 打印出最终结果
    print('最终投资组合价值:%.2f' % cerebro.broker.getvalue())

执行后的输出为:

起始投资组合价值:100000.00
最终投资组合价值:100000.00

模板代码的数量略有增加,因为我们添加了:

  • 找出我们的示例脚本所在的位置,以便能够定位示例数据源文件

  • datetime对象以过滤我们将要操作的数据源中的数据

除此之外,Data Feed被创建并添加到cerebro中。

输出没有改变,如果它改变了,那将是一个奇迹。

注意:Yahoo Online以日期降序发送CSV数据,这不是标准约定。reversed=True参数考虑到CSV数据在文件中已经被反转并具有标准预期的日期升序。

我们的第一个策略

现金在broker中,Data Feed也在那里。我们马上就可以跑生意了。

让我们将策略放入方程式中,并打印每天(bar)的Close价格。

DataSeriesData Feeds中的基础类)对象具有访问众所周知的OHLC(Open High Low Close)每日值的别名。这应该简化我们的打印逻辑的创建。

# 导入所需模块
from __future__ import (absolute_import, division, print_function, unicode_literals)
import datetime  # 日期时间模块
import os.path  # 路径模块
import sys  # 系统模块

# 导入backtrader平台
import backtrader as bt

# 创建策略
class 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):
        # 保留对数据序列中`close`线的引用
        self.dataclose = self.datas[0].close

    def next(self):
        # 记录数据序列的收盘价
        self.log('收盘价, %.2f' % self.dataclose[0])

if __name__ == '__main__':
    # 创建cerebro实体
    cerebro = bt.Cerebro()

    # 添加策略
    cerebro.addstrategy(TestStrategy)

    # 数据在样本的子文件夹中。需要找到脚本所在的位置,因为它可以从任何地方调用
    modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
    datapath = os.path.join(modpath, '../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)

    # 将数据源添加到Cerebro
    cerebro.adddata(data)

    # 设置初始资金
    cerebro.broker.setcash(100000.0)

    # 打印初始条件
    print('初始资产价值: %.2f' % cerebro.broker.getvalue())

    # 运行策略
    cerebro.run()

    # 打印最终结果
    print('最终资产价值: %.2f' % cerebro.broker.getvalue())


执行后的输出为::

初始资产价值: 100000.00
2000-01-03, 收盘价, 26.27
2000-01-04, 收盘价, 23.95
  ...
  ...
  ...
2000-12-28, 收盘价, 27.63
2000-12-29, 收盘价, 25.85
最终资产价值: 100000.00

有人说股票市场是冒险的生意,但似乎并非如此。

让我们解释一些神奇的事:

  • 在调用__init__时,策略已经有了一个存在于平台中的数据列表

    这是一个标准的Python的list,可以按照它们插入的顺序访问数据。

    列表中的第一个数据self.datas[0]是用于交易操作的默认数据,并且为了保持所有策略元素同步(它是系统时钟

  • self.dataclose = self.datas[0].close保留对close line的引用。只需要一个间接级别即可访问关闭值。

  • 策略next方法将在系统时钟(self.datas[0])的每个柱上调用。这是真实的,直到其他事情进入比如indicators,它需要一些柱才能开始产生输出。稍后再说。

给策略添加执行逻辑

让我们尝试一些疯狂的想法,通过查看一些图表

  • 如果价格连续下跌3个会话… 买买买!!!
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import datetime  # 导入日期时间库
import os.path  # 导入路径管理库
import sys  # 导入系统库

# 导入backtrader平台
import backtrader as bt


# 创建策略
class 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):
        # 保留对数据序列中`close`线的引用
        self.dataclose = self.datas[0].close

    def next(self):
        # 记录数据序列的收盘价
        self.log('Close, %.2f' % self.dataclose[0])

        if self.dataclose[0] < self.dataclose[-1]:
            # 当前收盘价小于前一个收盘价

            if self.dataclose[-1] < self.dataclose[-2]:
                # 前一个收盘价小于前一个收盘价

                # 买入
                self.log('BUY CREATE, %.2f' % self.dataclose[0])
                self.buy()


if __name__ == '__main__':
    # 创建cerebro实体
    cerebro = bt.Cerebro()

    # 添加策略
    cerebro.addstrategy(TestStrategy)

    # 数据在样本的子文件夹中。需要找到脚本所在的位置
    # 因为它可以从任何地方调用
    modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
    datapath = os.path.join(modpath, '../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)

    # 将数据源添加到Cerebro
    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())

执行后的输出为:

Starting Portfolio Value: 100000.00
2000-01-03, Close, 26.27
2000-01-04, Close, 23.95
2000-01-05, Close, 22.68
2000-01-05, BUY CREATE, 22.68
2000-01-06, Close, 21.35
2000-01-06, BUY CREATE, 21.35
...
...
...
2000-12-20, BUY CREATE, 25.35
2000-12-21, Close, 26.24
2000-12-22, Close, 28.35
2000-12-26, Close, 27.52
2000-12-27, Close, 27.30
2000-12-27, BUY CREATE, 27.30
2000-12-28, Close, 27.63
2000-12-29, Close, 25.85
Final Portfolio Value: 99740.45

发出了几个买入订单,我们的组合价值减少了。显然缺少了一些重要的事情。

  • 订单已创建,但不知道是否执行,何时执行以及以什么价格执行。

    下一个示例将在此基础上构建,通过监听订单状态的通知来解决这个问题。

好奇的读者可能会问买了多少股票,买了哪些资产以及如何执行订单。在可能的情况下(在这种情况下),平台填补了这些空白:

  • self.datas[0](主数据,即系统时钟)是操作买卖的目标资产,如果没有指定其他资产的话(即默认操作datas[0])
  • 买卖股份数量由position sizer在幕后提供,使用固定股份,即默认值1。它将在稍后的示例中进行修改
  • 订单是市价执行的。经纪人(在前面的示例中显示)使用下一个条的开盘价执行此操作,因为那是当前正在检查的条下一个tick。
  • 订单迄今为止没有任何佣金(稍后会详细介绍)

不仅要买…还要卖

在了解如何进入市场(多头)之后,需要一个退出概念,即给策略添加从市场退出的逻辑(卖股)。

  • 幸运的是,Strategy对象提供了对默认数据源position属性的访问
  • 方法buysell返回已创建(尚未执行)的订单
  • 订单状态的更改将通过notify方法通知策略

退出概念将是一个简单的概念:

  • 在5个bar(第6个bar)之后退出,无论好坏

    请注意,1个bar的bar可以表示1分钟,1小时,1天,1周或其他任何长度的时间单位。

    尽管我们知道数据源是每日的,但策略不会对此做出任何假设。

此外,为了简化:

  • 仅在尚未进入市场时才允许购买订单

注意:next方法没有传递bar索引,因此似乎不清楚如何理解何时可能已经过去了5个bar,但是这已经以pythonic的方式建模:在对象上调用len,它将告诉您其lines的长度。
只需在操作中记录(保存在变量中)已经迭代过的bar的长度,然后查看当前长度是否相距5个bar。

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

import datetime  # 导入datetime模块
import os.path  # 导入os.path模块
import sys  # 导入sys模块

# 导入backtrader平台
import backtrader as bt


# 创建策略
class 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):
        # 保留对数据序列中`close`线的引用
        self.dataclose = self.datas[0].close

        # 跟踪待处理订单
        self.order = None

    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('买入已执行, %.2f' % order.executed.price)
            elif order.issell():
                self.log('卖出已执行, %.2f' % order.executed.price)

            self.bar_executed = len(self)

        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log('订单已取消/保证金不足/拒绝')

        # 记录:没有待处理订单
        self.order = None

    def next(self):
        # 仅记录参考系列的收盘价
        self.log('Close, %.2f' % self.dataclose[0])

        # 检查是否有待处理订单...如果有,我们不能发送第二个订单
        if self.order:
            return

        # 检查是否在市场中
        if not self.position:

            # 还没有...如果...
            if self.dataclose[0] < self.dataclose[-1]:
                    # 当前收盘价小于前一个收盘价

                    if self.dataclose[-1] < self.dataclose[-2]:
                        # 前一个收盘价小于前一个收盘价

                        # 买入
                        self.log('买入创建, %.2f' % self.dataclose[0])

                        # 记录已创建的订单,以避免产生第二个订单
                        self.order = self.buy()

        else:
            # 已经在市场中...我们可能会卖出
            if len(self) >= (self.bar_executed + 5):
                # 卖出
                self.log('卖出创建, %.2f' % self.dataclose[0])

                # 记录已创建的订单,以避免第二个订单
                self.order = self.sell()


if __name__ == '__main__':
    # 创建cerebro实体
    cerebro = bt.Cerebro()

    # 添加策略
    cerebro.addstrategy(TestStrategy)

    # 数据在样本的子文件夹中。需要找到脚本所在的位置
    # 因为它可以从任何地方调用
    modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
    datapath = os.path.join(modpath, '../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)

    # 将数据源添加到Cerebro
    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())


执行后的输出为(打印日志是源文档未翻译,但代码中打印内容已翻译):

Starting Portfolio Value: 100000.00
2000-01-03, Close, 26.27
2000-01-04, Close, 23.95
2000-01-05, Close, 22.68
2000-01-05, 买入创建, 22.68
2000-01-06, 买入已执行, 22.27
...
...
2000-12-20, Close, 25.35
2000-12-20, 买入创建, 25.35
2000-12-21, 买入已执行, 24.74
2000-12-21, Close, 26.24
...
2000-12-28, Close, 27.63
2000-12-29, Close, 25.85
2000-12-29, 卖出创建, 25.85
Final Portfolio Value: 100017.52

系统赚了钱…一定有什么问题

券商说:打钱!

这笔钱叫做佣金

让我们为每次操作(买入和卖出)添加合理的*0.1%*佣金率(是的,经纪人很贪婪……)

只需要一行代码:

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

熟悉平台后,我们想在买卖周期之后查看有无佣金的利润或损失。

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())

执行后的输出为:

Starting Portfolio Value: 100000.00
2000-01-03, Close, 26.27
2000-01-04, Close, 23.95
2000-01-05, Close, 22.68
2000-01-05, BUY CREATE, 22.68
2000-01-06, BUY EXECUTED, Price: 22.27, Cost: 22.27, Comm 0.02
...
...
...
2000-12-21, BUY EXECUTED, Price: 24.74, Cost: 24.74, Comm 0.02
2000-12-21, Close, 26.24
2000-12-22, Close, 28.35
2000-12-26, Close, 27.52
2000-12-27, Close, 27.30
2000-12-28, Close, 27.63
2000-12-29, Close, 25.85
2000-12-29, SELL CREATE, 25.85
Final Portfolio Value: 100016.06

上帝保佑!系统仍然赚了钱。

在继续之前,让我们通过过滤OPERATION PROFIT行来注意一些事情:

2000-01-14, OPERATION PROFIT, GROSS 1.97, NET 1.92
2000-02-07, OPERATION PROFIT, GROSS 3.48, NET 3.43
2000-02-28, OPERATION PROFIT, GROSS 4.23, NET 4.17
2000-03-13, OPERATION PROFIT, GROSS 3.28, NET 3.21
2000-03-22, OPERATION PROFIT, GROSS -0.39, NET -0.46
2000-04-07, OPERATION PROFIT, GROSS 2.31, NET 2.24
2000-04-20, OPERATION PROFIT, GROSS -1.83, NET -1.90
2000-05-02, OPERATION PROFIT, GROSS 5.15, NET 5.08
2000-05-11, OPERATION PROFIT, GROSS -3.53, NET -3.59
2000-05-30, OPERATION PROFIT, GROSS -1.39, NET -1.45
2000-07-05, OPERATION PROFIT, GROSS -1.53, NET -1.60
2000-07-14, OPERATION PROFIT, GROSS 1.97, NET 1.90
2000-07-28, OPERATION PROFIT, GROSS 0.14, NET 0.07
2000-08-08, OPERATION PROFIT, GROSS 4.11, NET 4.04
2000-08-21, OPERATION PROFIT, GROSS 0.97, NET 0.90
2000-09-15, OPERATION PROFIT, GROSS -4.00, NET -4.08
2000-09-27, OPERATION PROFIT, GROSS 1.22, NET 1.15
2000-10-13, OPERATION PROFIT, GROSS -2.81, NET -2.87
2000-10-26, OPERATION PROFIT, GROSS 2.84, NET 2.78
2000-11-06, OPERATION PROFIT, GROSS -3.39, NET -3.45
2000-11-16, OPERATION PROFIT, GROSS 1.22, NET 1.17
2000-12-01, OPERATION PROFIT, GROSS 2.45, NET 2.41
2000-12-18, OPERATION PROFIT, GROSS -0.06, NET -0.11

利润相加,最终数字为:

15.83(备注原文数字,实际执行代码结果和文档有差异,此处示意)

但系统在最后说了以下内容:

2000-12-29, SELL CREATE, 25.85
Final Portfolio Value: 100016.06

显然,15.83不是16.06。没有任何错误。15.83利润已经到手了。

不幸的是(或者幸运的是,为了更好地理解平台),在数据源最后一天有一个未平仓头寸。即使已经发送了SELL操作……它还没有被执行。

经纪人计算的最终组合价值考虑了2000-12-29的收盘价格。实际执行价格将在下一个交易日(即2001-1-3)设置。将数据源扩展到考虑这一天后,输出为:

2000-12-29, SELL CREATE, 25.85
2001-01-02, SELL EXECUTED, Price: 26.30, Cost: 24.74, Comm 0.03
2001-01-02, OPERATION PROFIT, GROSS 1.56, NET 1.51
2001-01-02, Close, 23.46
2001-01-02, BUY CREATE, 23.46
Final Portfolio Value: 100016.48

现在将前面的利润添加到已完成操作的利润中:

15.83 + 1.59 = 17.42(备注原文数字,实际执行代码结果和文档有差异,此处示意)

(忽略print语句中的舍入误差),这是初始100000货币单位以上的额外组合。

自定义策略:参数

在策略中硬编码一些值并没有什么实际意义,而且很难轻松更改它们。参数很有用。

定义参数很容易,看起来像这样:

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

这是一个标准的Python元组,里面有一些元组,以下可能更吸引人:

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

无论哪种格式化策略都允许在将策略添加到Cerebro引擎时进行参数化:

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

注意:下面的setsizing方法已弃用。这个内容是为了让任何人查看旧的源代码示例而保留的。源代码已经更新为使用:

# 已经弃用
cerebro.addsizer(bt.sizers.FixedSize, stake=10)``

可以阅读sizers相关章节查看更多。

在策略中使用参数很容易,因为它们存储在params属性中。如果我们例如想要设置固定的股份,我们可以在__init__期间将股份参数传递给position sizer

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

我们也可以使用stake参数和self.params.stake作为值来调用buysell

退出逻辑被修改:

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

考虑到所有这些,示例的演变如下:

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())

执行后的输出为:

Starting Portfolio Value: 100000.00
2000-01-03, Close, 26.27
2000-01-04, Close, 23.95
2000-01-05, Close, 22.68
2000-01-05, BUY CREATE, 22.68
2000-01-06, BUY EXECUTED, Price: 22.27, Cost: 222.70, Comm 0.22
...
...
...
2000-12-28, Close, 27.63
2000-12-29, Close, 25.85
2000-12-29, SELL CREATE, 25.85
Final Portfolio Value: 100160.58

为了看到差异,打印输出也已扩展以显示执行大小。

将股份乘以10后,显然发生了以下情况:利润和损失乘以10。而不是16.58,剩余现金现在是165.80

添加指标

指标可以简单理解为:用于衡量市场趋势和价格变化的工具。指标可以是基于价格、成交量或其他市场数据的计算结果。

听说过指标后,下一步任何人都会添加一个指标到策略中。毫无疑问,它们一定比简单的*3个较低的收盘价*策略好得多。

受PyAlgoTrade中的一个示例启发,使用简单移动平均线的策略。

  • 如果收盘价大于平均值,则以市价买入
  • 如果在市场上,如果收盘价小于平均值,则卖出
  • 市场上只允许有1个活动操作

大部分现有的代码可以保持不变。让我们在__init__中添加平均值,并保留对它的引用:

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

当然,进入和退出市场的逻辑将依赖于平均值。在代码中查找逻辑。

注意:: 起始现金将为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())

现在,在跳转到下一节之前,请仔细查看日志中显示的第一个日期:

  • 它不再是2K年的第一个交易日2000-01-03

    它是2000-01-24 …

缺失的天数并不不是真的缺失。而是平台已适应新的情况:

  • 已将指标(SimpleMovingAverage)添加到策略中。

  • 此指标需要X个条形图才能产生输出:例如:15

  • 2000-01-24是第15个条形图出现的日期

backtrader平台假定策略已经有了指标,有一个很好的理由,在决策过程中使用它。如果指标尚未准备好并产生值,则尝试做出决策是没有意义的。

  • 当所有指标已经达到产生值所需的最小周期时,next将首先被调用

  • 在示例中只有一个指标,但是策略可以有任意数量的指标。

执行后的输出为:

Starting Portfolio Value: 1000.00
2000-01-24, Close, 24.10
2000-01-25, Close, 25.10
2000-01-25, BUY CREATE, 25.10
2000-01-26, BUY EXECUTED, Price: 25.24, Cost: 252.40, Comm 0.00
...
...
...
2000-12-21, OPERATION PROFIT, GROSS -19.40, NET -19.40
2000-12-21, Close, 26.24
2000-12-21, BUY CREATE, 26.24
2000-12-22, BUY EXECUTED, Price: 27.02, Cost: 270.20, Comm 0.00
2000-12-22, Close, 28.35
2000-12-26, Close, 27.52
2000-12-27, Close, 27.30
2000-12-28, Close, 27.63
2000-12-29, Close, 25.85
2000-12-29, SELL CREATE, 25.85
Final Portfolio Value: 975.60

一个获胜的系统变成了一个失败的系统…而且没有佣金。很可能仅仅添加一个指标,说明指标并不是万能药。

注意:在PyAlgoTrade中使用相同的逻辑和数据会产生略有不同的结果(略微偏离)。查看整个打印输出会发现一些操作不完全相同。罪魁祸首再次是通常的嫌疑人:四舍五入

PyAlgoTrade在将分裂的调整收盘价应用于数据源值时不会将其舍入到小数点后2位。

backtrader提供的Yahoo数据源将在应用调整后将值向下舍入到2位小数。在打印值时,一切似乎都是相同的,但很明显,有时第5位小数起作用。

向下舍入到2位小数似乎更为现实,因为Marke Exchange仅允许每个资产有一定数量的小数位数(通常为股票的2个小数位数)

注意:Yahoo数据源(从版本1.8.11.99开始)允许指定是否进行舍入以及舍入到多少位小数)

可视化检查:绘图

打印或记录系统在每个bar的实际执行情况是可以的,但人类是视觉动物,因此提供一个图表视图输出机制无疑是正确的。

注意:要绘制图形,需要安装matplotlib

再次,默认情况下绘图可帮助平台用户。绘图是一个仅有的1行操作:

cerebro.plot()

在调用cerebro.run()后,位置肯定是在那里。

为了显示自动绘图功能和一些简单的自定义,将执行以下操作:

  • 添加第二个移动平均线(指数)。默认情况下将其与数据一起绘制(就像第一个一样)。
  • 添加第三个移动平均线(加权)。自定义绘制在自己的图中(即使不明智)
  • 添加随机(慢)。默认情况下不更改。
  • 添加MACD。默认情况下不更改。
  • 添加RSI。默认情况下不更改。
  • 对RSI应用移动平均线(简单)。默认情况下不更改(将与RSI一起绘制)
  • 添加AverageTrueRange。更改默认值以避免绘制。

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

注意:即使indicators没有显式添加到策略的成员变量中(例如self.sma = MovingAverageSimple…),它们也将自动注册到策略中,并影响next的最小周期,并成为绘图的一部分。

在示例中,只有RSI被添加到临时变量rsi中,其唯一目的是在其上创建一个MovingAverageSmoothed。

现在的例子:

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()

执行后的输出为:

Starting Portfolio Value: 1000.00
2000-02-18, Close, 26.05
2000-02-22, Close, 26.38
2000-02-22, BUY CREATE, 26.38
2000-02-23, BUY EXECUTED, Price: 26.77, Cost: 267.70, Comm 0.00
2000-02-23, Close, 28.05
...
...
...
2000-12-22, BUY EXECUTED, Price: 27.02, Cost: 270.20, Comm 0.00
2000-12-22, Close, 28.35
2000-12-26, Close, 27.52
2000-12-27, Close, 27.30
2000-12-28, Close, 27.63
2000-12-29, Close, 25.85
2000-12-29, SELL CREATE, 25.85
Final Portfolio Value: 982.30

最终结果已更改,即使逻辑没有。这是真的,但逻辑没有应用于相同数量的bar。

注意:如前所述,平台将在所有指标准备好生成值时首先调用next。在这个绘图示例中(在图表中非常清晰),MACD是最后一个完全准备好的指标(所有3条线都产生输出)。第一个BUY订单不再在2000年1月期间计划,而是接近2000年2月底。

图表:
请添加图片描述

让我们优化

许多交易书籍都说每个市场和每个交易的股票(或商品或…)都有不同的节奏。没有一种适合所有人的东西。

在绘图示例之前,当策略开始使用指标时,期间的默认值为15个bar。这是一个策略参数,可以在优化中使用该参数的值并查看哪个更适合市场。

注意:有很多关于优化和相关利弊的文献。但是建议总是指向同一个方向:不要过度优化。如果交易想法不合理,则优化可能会产生仅对回测数据集有效的正面结果。

示例已修改为优化简单移动平均线的期间。为了清晰起见,已删除与买入/卖出订单相关的任何输出

现在的例子:

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)

不是调用addstrategy将策略类添加到Cerebro中,而是调用optstrategy。而不是传递一个值,而是传递一系列值。

添加了Strategy钩子之一,stop方法,当数据用尽并且回测结束时将调用该方法。它用于在经纪人的投资组合中打印最终净值(之前在Cerebro中完成)

系统将为范围内的每个值执行策略。将输出以下内容:

2000-12-29, (MA Period 10) Ending Value 877.50
2000-12-29, (MA Period 11) Ending Value 878.70
2000-12-29, (MA Period 12) Ending Value 839.80
2000-12-29, (MA Period 13) Ending Value 899.90
2000-12-29, (MA Period 14) Ending Value 902.50
2000-12-29, (MA Period 15) Ending Value 975.60
2000-12-29, (MA Period 16) Ending Value 961.90
2000-12-29, (MA Period 17) Ending Value 952.60
2000-12-29, (MA Period 18) Ending Value 1011.00
2000-12-29, (MA Period 19) Ending Value 1039.40
2000-12-29, (MA Period 20) Ending Value 1073.20
2000-12-29, (MA Period 21) Ending Value 1055.10
2000-12-29, (MA Period 22) Ending Value 1057.60
2000-12-29, (MA Period 23) Ending Value 1021.50
2000-12-29, (MA Period 24) Ending Value 1018.80
2000-12-29, (MA Period 25) Ending Value 1012.40
2000-12-29, (MA Period 26) Ending Value 998.30
2000-12-29, (MA Period 27) Ending Value 983.10
2000-12-29, (MA Period 28) Ending Value 976.90
2000-12-29, (MA Period 29) Ending Value 984.20
2000-12-29, (MA Period 30) Ending Value 980.80

结果:

  • 对于小于18的期间,策略(无佣金)会亏钱。
  • 对于18到26之间的期间(包括两者),策略会赚钱。
  • 26以上再次失去了钱。

而该策略和给定数据集的获胜期为:

  • 20个bar,赢得1000 $的78.00个单位(7.8%)

注意::绘图示例中的额外指标已被删除,并且操作的开始仅受到正在优化的简单移动平均线的影响。因此,期间15的结果略有不同。

结论

示例逐步的展示了如何从一个简单的脚本到一个完整的交易系统,甚至可以绘制结果并进行优化。

可以做很多事情来提高获胜的机会:

  • 自定义指标

创建指标很容易(甚至绘制它们也很容易)

  • 仓位管理

对于许多人来说,资金管理是成功的关键

  • 订单类型(限价,止损,止损限价)

  • 其他一些

为了确保可以充分利用上述所有项目,文档提供了对它们(和其他主题)的深入了解。

查看目录并继续阅读…并开发。

祝你好运!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/591086.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ set类成员函数介绍 (set和multiset)

目录 &#x1f914;set模板介绍&#xff1a; &#x1f914;特点&#xff1a; &#x1f914;set的成员函数&#xff1a; &#x1f60a;set构造函数&#xff1a; &#x1f50d;代码实例&#xff1a; &#x1f50d;运行结果&#xff1a; &#x1f60a; set赋值函数&#xf…

IMX6ULL裸机篇之I2C相关寄存器

一. I2C实验 I2C时钟选择与传输速率 1. IMX6ULL的 I2C频率标准模式 100kbit/S&#xff0c;快速模式为 400Kbit/S 2. 时钟源选择 perclk_clk_rootipg_clk_root66MHz&#xff08;由之前的时钟实验章节可以知道是 66MHz&#xff09;。 二. I2C 寄存器配置 I2Cx_IFDR寄存器&…

《计算机组成原理》唐朔飞 第5章 输入输出系统 - 学习笔记

写在前面的话&#xff1a;此系列文章为笔者学习计算机组成原理时的个人笔记&#xff0c;分享出来与大家学习交流。使用教材为唐朔飞第3版&#xff0c;笔记目录大体与教材相同。 网课 计算机组成原理&#xff08;哈工大刘宏伟&#xff09;135讲&#xff08;全&#xff09;高清_…

chatgpt赋能python:Python中用什么表示空值?

Python中用什么表示空值&#xff1f; 在Python编程中&#xff0c;我们经常会遇到处理空值的场景。空值通常表示缺失的或未定义的值&#xff0c;这在数据处理和分析中尤其常见。那么&#xff0c;在Python中&#xff0c;究竟用什么来表示空值呢&#xff1f; None 在Python中&a…

6G显存玩转130亿参数大模型,仅需13行命令,RTX2060用户发来贺电

羊驼家族的Alpaca和Vicuna也都能运行&#xff0c;显存最低只需要6G&#xff0c;简直是低VRAM用户的福音有木有。 GitHub上的搭建教程火了之后&#xff0c;网友们纷纷跑来问苹果M2是不是也能跑。 这通操作的大致原理是利用最新版CUDA&#xff0c;可以将Transformer中任意数量的…

什么是先进存力?曙光存储:内铸数字底座,外成实践底气

5月26日&#xff0c;由DOIT联合中国电子学会共同举办的2023数据基础设施技术峰会在苏州举办。中科曙光存储产品事业部副总经理张新凤受邀参会&#xff0c;并在主论坛发表主题演讲&#xff0c;与数百位业内专业嘉宾伙伴共探存力发展未来。 什么样的存力能打造数字经济底座&#…

【笔记】【Javascript】javascript实现继承

前言 之前写过关于面向对象编程的文章&#xff0c;通过阅读别人的博客了解了一下Javascript实现继承的方法&#xff0c;并且使用图画的形式帮助了解&#xff0c;图是自己做的&#xff0c;若有偏差请读者帮忙指出&#xff0c;谢谢。笔记中有些个人理解后整理的笔记&#xff0c;…

基于STM32的ADC采样及各式滤波实现(HAL库,含VOFA+教程)

前言&#xff1a;本文为手把手教学ADC采样及各式滤波算法的教程&#xff0c;本教程的MCU采用STM32F103ZET6。以HAL库的ADC采样函数为基础进行教学&#xff0c;通过各式常见滤波的实验结果进行分析对比&#xff0c;搭配VOFA工具直观的展示滤波效果。ADC与滤波算法都是嵌入式较为…

MySQL进阶- Linux安装 和 索引

目录 Linux安装索引索引的概述索引的结构索引结构的介绍BtreeBtreeHash 索引的分类索引的语法&#xff08;创建&#xff0c;查看&#xff0c;删除等&#xff09;SQL性能分析SQL的执行频率&#xff08;查看SQL的执行频率&#xff09;慢查询日志show profilesexplain执行计划 索引…

video标签学习 xgplayer视频播放器分段播放mp4

文章目录 学习链接目标video标签自带视频和制作的视频区别video标签的src属性本地视频文件前端代码播放效果 服务器视频文件示例1后端代码前端代码播放效果 示例2后端代码前端代码播放效果 示例3后端配置前端代码播放效果 video对象video对象创建和获取video的属性video的方法v…

chatgpt赋能python:Python模块安装方法全解析

Python模块安装方法全解析 Python是一种功能强大的编程语言&#xff0c;拥有大量的开源库&#xff0c;这些库是在各种应用程序中使用的重要组件&#xff0c;它们能加速开发过程。不管你是初学者、中级者还是高级者&#xff0c;总会遇到需要安装第三方库的情况。但是安装库是一…

《Java并发编程实战》课程笔记(四)

互斥锁 原子性问题到底该如何解决呢&#xff1f; “同一时刻只有一个线程执行”这个条件非常重要&#xff0c;我们称之为互斥。如果我们能够保证对共享变量的修改是互斥的&#xff0c;那么&#xff0c;无论是单核 CPU 还是多核 CPU&#xff0c;就都能保证原子性了。 锁模型 …

Python连接达梦数据库

python如果想连接达梦数据库&#xff0c;必须要安装dmPython。 简介&#xff1a;dmPython 是 DM 提供的依据 Python DB API version 2.0 中 API 使用规定而开发的数据库访问接口。dmPython 实现这些 API&#xff0c;使 Python 应用程序能够对 DM 数据库进行访问。 dmPython 通…

数据库服务器

数据库服务器&#xff0c;联系Web服务器与DBMS的中间件是负责处理所有的应用程序服务器&#xff0c;包括在web服务器和后台的应用程序或数据库之间的事务处理和数据访问。 基本信息 中文名 数据库服务器 外文名 database server 功能 数据库服务器建立在数据库系统基础上&a…

系统漏洞利用与提权

任务二&#xff1a;系统漏洞利用与提权 任务环境说明&#xff1a; 服务器场景&#xff1a;PYsystem0033 服务器场景操作系统&#xff1a;Ubuntu 服务器场景用户名:未知 密码&#xff1a;未知 1.使用nmap扫描靶机系统&#xff0c;将靶机开放的端口号按从小到大的顺序作为F…

解决Vmware上的kali找不到virtualbox上的靶机的问题

解决kali找不到靶场ip问题的完整方法 1.配置靶机2.配置kali的虚拟网络3.配置kali中的eth0网络 1.配置靶机 靶机部署在Virtualbox上对其进行网络配置&#xff0c;选择连接方式为仅主机&#xff08;Host-Only&#xff09;网络。 2.配置kali的虚拟网络 在编辑中选择虚拟网络配…

chatgpt赋能python:Python中浮点数的表示方法

Python中浮点数的表示方法 在Python中&#xff0c;浮点数是一种数字类型&#xff0c;用于表示带有小数点的数值。但是&#xff0c;由于计算机在表示浮点数时存在精度限制&#xff0c;因此需要特别注意。本文将介绍Python中浮点数的表示方法及其可能导致的错误。 Python中浮点…

陕西发布!陕西省重点实验室申报条件类别、认定程序要求

本文整理了陕西省重点实验室申报条件&#xff0c;认定材料等相关内容&#xff0c;感兴趣的朋友快跟小编一起来看看吧&#xff01; 一、总体思路 本次省重点实验室布局建设工作以填补我省优势学科领域下无省级及以上科学与工程研究类科技创新基地的空白为主,同时兼顾前沿、新兴、…

MySQL基础- 多表查询 和 事务

目录 多表查询多表关系多表查询概述多表查询的分类内连接外连接自连接联合查询union&#xff0c;union all子查询标量子查询列子查询行子查询表子查询 综合练习小结 事务事务简介事务的操作四大特性ACID并发事务问题事务的隔离级别小结 多表查询 之前的SQL语句里的DQL只能进行…

数字图像学笔记 —— 18. 图像抖动算法

文章目录 为什么需要图像抖动图像抖动算法实现的基本思路常见图像抖动算法实现Floyd-Steinberg 抖动算法Atkinson 抖动算法算法实现 为什么需要图像抖动 在数字图像中&#xff0c;为了表示数字图像的细节&#xff0c;像素的颜色深度信息最少也是8位&#xff0c;即 0 − 256 0…