二进制数据源开发
原文: https://www.backtrader.com/docu/datafeed-develop-general/datafeed-develop-general/
笔记
示例goog.fd中使用的二进制文件属于 VisualChart,不能与backtrader一起分发。
对直接使用二进制文件感兴趣的用户可免费下载VisualChart。
CSV 数据源开发展示了如何添加新的基于 CSV 的数据源。现有的基类 CSVDataBase 提供了一个框架,将大部分工作从子类中移除,在大多数情况下,子类只需执行以下操作:
def _loadline(self, linetokens):
  # parse the linetokens here and put them in self.lines.close,
  # self.lines.high, etc
  return True # if data was parsed, else ... return False 
基类负责参数、初始化、打开文件、读取行、将行拆分为令牌,以及跳过不符合最终用户定义的日期范围(fromdate、todate)的行等其他事情。
开发非 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的情况下,该参数意味着指向文件或已经是类似文件的对象的路径。
- 
fromdate和todate定义将传递给策略的日期范围。提要提供的任何超出此范围的值都将被忽略
- 
name是用于绘图目的的示意图
- 
timeframe表示临时工作基准电位值: Ticks、Seconds、Minutes、Days、Weeks、Months和Years
- 
compression(默认值:1)每根钢筋的实际钢筋数量。提供有用信息的仅在数据重采样/重放时有效。 
- 
compression
- 
sessionend如果传递(datetime.time 对象),将添加到 datafeeddatetime行,该行允许标识会话结束
二进制数据源示例
backtrader已经为VisualChart的导出定义了一个 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中完成。调用以加载下一组数据,在本例中为下一组:datetime、open、high、low、close、volume、openinterest。在backtrader中,“实际”力矩对应于指数 0。
将从打开的文件中读取大量字节(由__init__期间设置的常数确定),使用struct模块进行解析,如果需要进一步处理(如使用 divmod 对日期和时间进行操作),并存储在数据提要的lines中:datetime、open、high、low、close、volume、openinterest。
如果无法从文件中读取数据,则假定已达到文件末尾(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中。标准字段为:datetime、open、high、low、close、volume、openinterest
数据测试
VCharData从谷歌 2006 年的本地“.fd”文件加载数据。
它只是关于加载数据,所以甚至不需要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') 
VChartData 完整代码
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 


