二进制数据源开发#

备注

在示例中使用的二进制文件 goog.fd 属于VisualChart并且不能与 backtrader 一起分发。

对于那些有兴趣直接使用二进制文件的用户,可以免费下载 VisualChart <http://www.visualchart.com> _。

CSV数据源开发已经展示了如何添加基于CSV的新数据源。现有的基类 CSVDataBase 提供了框架,减轻了大部分子类的工作量,大部分情况下,子类只需执行以下操作:

def _loadline(self, linetokens):

  # 在此处解析linetokens并将它们存入self.lines.close、self.lines.high等

  return True # 如果数据解析成功,则返回True,否则返回False

基类负责处理参数、初始化、打开文件、读取行、拆分行为标识符等额外事项,例如跳过不符合用户所定义的日期范围( fromdatetodate )的行。

开发非CSV数据源遵循同样的模式,只是不需要拆分行标识符。要做的事情:

  • 从 backtrader.feed.DataBase 派生

  • 添加你可能需要的任何参数

  • 如果需要初始化,请重写 __init__(self) 和/或 start(self)

  • 如果需要任何清理代码,请重写 stop(self)

  • 工作是在必须始终重写的方法内完成的: _load(self)

让我们看看 backtrader.feed.DataBase 已经提供的参数:

from backtrader.utils.py3 import with_metaclass

class DataBase(with_metaclass(MetaDataBase, dataseries.OHLCDateTime)):params = ((‘dataname’, None),

(‘fromdate’, datetime.datetime.min), (‘todate’, datetime.datetime.max), (‘name’, ‘’), (‘compression’, 1), (‘timeframe’, TimeFrame.Days), (‘sessionend’, None))

具有以下含义:

  • dataname 用于数据源识别如何获取数据。在 CSVDataBase 的情况下,该参数应该是文件的路径或已经是文件-like类型的对象。

  • fromdatetodate 定义将传递给策略的日期范围。在这个范围之外的数据源提供的任何值都将被忽略。

  • name 是美化用于绘图的目的

  • timeframe 指示时间的工作参照

    可能的取值: Ticks , Seconds , Minutes , Days , Weeks , MonthsYears

  • compression (默认为: 1)

    每个柱条的实际条数。注解。只在数据重采样/回放中有效。

  • compression - 如果传递了 sessionend (一个datetime.time对象),将被添加到数据源 datetime 行中,以便确定会话的结束时间

示例二进制数据源#

backtrader 已经为 VisualChart <http://www.visualchart.com> _的导出定义了一个CSV数据源( VChartCSVData ),但也可以直接读取二进制数据文件。

让我们开始吧(完整的数据源代码可以在底部找到)

初始化#

二进制的VisualChart数据文件可以包含每日数据(.fd扩展名)或股票内部数据(.min扩展名)。这里的参数 timeframe 将用于区分正在读取的文件类型。

__init__ 期间,为每种类型设置了不同的常量。

    def __init__(self):
        super(VChartData, self).__init__()

        # Use the informative "timeframe" parameter to understand if the
        # code passed as "dataname" refers to an intraday or daily feed
        if self.p.timeframe >= TimeFrame.Days:
            self.barsize = 28
            self.dtsize = 1
            self.barfmt = 'IffffII'
        else:
            self.dtsize = 2
            self.barsize = 32
            self.barfmt = 'IIffffII'

开始#

当开始进行回测时,数据源将会*启动*(在优化期间可以多次启动)在 start 方法中,除非传递了一个类似文件的对象,否则会打开二进制文件。

    def start(self):
        # the feed must start ... get the file open (or see if it was open)
        self.f = None
        if hasattr(self.p.dataname, 'read'):
            # A file has been passed in (ex: from a GUI)
            self.f = self.p.dataname
        else:
            # Let an exception propagate
            self.f = open(self.p.dataname, 'rb')

停止#

当回测结束时调用。

如果有文件被打开,将会关闭它

    def stop(self):
        # Close the file if any
        if self.f is not None:
            self.f.close()
            self.f = None

实际加载#

实际的工作在 _load 中完成。被调用来加载下一组数据,即下一个:日期时间、开盘价、最高价、最低价、收盘价、成交量、未平仓量。在 backtrader 中,“实际”时刻对应索引 0。

将会从打开的文件中读取一定数量的字节(由 __init__ 期间设置的常量确定),使用 struct 模块解析,如果需要还会进一步处理(比如对日期和时间进行 divmod 运算),并将结果存储在数据源的 “lines” 中:日期时间、开盘价、最高价、最低价、收盘价、成交量、未平仓量。

如果无法从文件中读取数据,则假定已经达到了文件末尾(EOF)。- 返回 False 来指示没有更多的数据可用

否则,如果数据已经被加载和解析:

  • 返回 True 来指示数据集的加载成功

    def _load(self):
        if self.f is None:
            # if no file ... no parsing
            return False

        # Read the needed amount of binary data
        bardata = self.f.read(self.barsize)
        if not bardata:
            # if no data was read ... game over say "False"
            return False

        # use struct to unpack the data
        bdata = struct.unpack(self.barfmt, bardata)

        # Years are stored as if they had 500 days
        y, md = divmod(bdata[0], 500)
        # Months are stored as if they had 32 days
        m, d = divmod(md, 32)
        # put y, m, d in a datetime
        dt = datetime.datetime(y, m, d)

        if self.dtsize > 1:  # Minute Bars
            # Daily Time is stored in seconds
            hhmm, ss = divmod(bdata[1], 60)
            hh, mm = divmod(hhmm, 60)
            # add the time to the existing atetime
            dt = dt.replace(hour=hh, minute=mm, second=ss)

        self.lines.datetime[0] = date2num(dt)

        # Get the rest of the unpacked data
        o, h, l, c, v, oi = bdata[self.dtsize:]
        self.lines.open[0] = o
        self.lines.high[0] = h
        self.lines.low[0] = l
        self.lines.close[0] = c
        self.lines.volume[0] = v
        self.lines.openinterest[0] = oi

        # Say success
        return True

其他二进制格式#

相同的模型可以应用于任何其他二进制源:

  • 数据库

  • 层次化数据存储

  • 在线数据源

步骤如下: - __init__ -> 用于实例的初始化代码,仅执行一次

  • start -> 回测开始(如果进行优化,则可能会运行多次)

    例如,这可能会打开数据库连接或与在线服务建立套接字连接。

  • stop -> 执行清理工作,如关闭数据库连接或套接字连接

  • _load -> 查询数据库或在线源以获取下一组数据,并将其加载到对象的 lines 中。标准字段包括:日期时间,开盘价,最高价,最低价,收盘价,成交量,持仓量

VChartData测试#

VCharData 从本地的”.fd”文件中加载2006年Google的数据。

这只涉及数据加载,因此不需要是 Strategy 的子类。

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

import datetime

import backtrader as bt
from vchart import VChartData


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

    # Add a strategy
    cerebro.addstrategy(bt.Strategy)

    ###########################################################################
    # Note:
    # The goog.fd file belongs to VisualChart and cannot be distributed with
    # backtrader
    #
    # VisualChart can be downloaded from www.visualchart.com
    ###########################################################################
    # Create a Data Feed
    datapath = '../../datas/goog.fd'
    data = VChartData(
        dataname=datapath,
        fromdate=datetime.datetime(2006, 1, 1),
        todate=datetime.datetime(2006, 12, 31),
        timeframe=bt.TimeFrame.Days
    )

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

    # Run over everything
    cerebro.run()

    # Plot the result
    cerebro.plot(style='bar')

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

import datetime
import struct

from backtrader.feed import DataBase
from backtrader import date2num
from backtrader import TimeFrame


class VChartData(DataBase):
    def __init__(self):
        super(VChartData, self).__init__()

        # Use the informative "timeframe" parameter to understand if the
        # code passed as "dataname" refers to an intraday or daily feed
        if self.p.timeframe >= TimeFrame.Days:
            self.barsize = 28
            self.dtsize = 1
            self.barfmt = 'IffffII'
        else:
            self.dtsize = 2
            self.barsize = 32
            self.barfmt = 'IIffffII'

    def start(self):
        # the feed must start ... get the file open (or see if it was open)
        self.f = None
        if hasattr(self.p.dataname, 'read'):
            # A file has been passed in (ex: from a GUI)
            self.f = self.p.dataname
        else:
            # Let an exception propagate
            self.f = open(self.p.dataname, 'rb')

    def stop(self):
        # Close the file if any
        if self.f is not None:
            self.f.close()
            self.f = None

    def _load(self):
        if self.f is None:
            # if no file ... no parsing
            return False

        # Read the needed amount of binary data
        bardata = self.f.read(self.barsize)
        if not bardata:
            # if no data was read ... game over say "False"
            return False

        # use struct to unpack the data
        bdata = struct.unpack(self.barfmt, bardata)

        # Years are stored as if they had 500 days
        y, md = divmod(bdata[0], 500)
        # Months are stored as if they had 32 days
        m, d = divmod(md, 32)
        # put y, m, d in a datetime
        dt = datetime.datetime(y, m, d)

        if self.dtsize > 1:  # Minute Bars
            # Daily Time is stored in seconds
            hhmm, ss = divmod(bdata[1], 60)
            hh, mm = divmod(hhmm, 60)
            # add the time to the existing atetime
            dt = dt.replace(hour=hh, minute=mm, second=ss)

        self.lines.datetime[0] = date2num(dt)

        # Get the rest of the unpacked data
        o, h, l, c, v, oi = bdata[self.dtsize:]
        self.lines.open[0] = o
        self.lines.high[0] = h
        self.lines.low[0] = l
        self.lines.close[0] = c
        self.lines.volume[0] = v
        self.lines.openinterest[0] = oi

        # Say success
        return True