BackTrader 中文文档(二十七)(4)

简介: BackTrader 中文文档(二十七)

BackTrader 中文文档(二十七)(3)https://developer.aliyun.com/article/1505479

保存/保持统计信息。

目前为止,backtrader 还没有实现任何跟踪观察者值并将它们存储到文件中的机制。最好的方法是:

  • 在策略的 start 方法中打开一个文件。
  • 在策略的 next 方法中写入值。

考虑到 DrawDown 观察者,可以这样做:

class MyStrategy(bt.Strategy):
    def start(self):
        self.mystats = open('mystats.csv', 'wb')
        self.mystats.write('datetime,drawdown, maxdrawdown\n')
    def next(self):
        self.mystats.write(self.data.datetime.date(0).strftime('%Y-%m-%d'))
        self.mystats.write(',%.2f' % self.stats.drawdown.drawdown[-1])
        self.mystats.write(',%.2f' % self.stats.drawdown.maxdrawdown-1])
        self.mystats.write('\n')

要保存索引 0 的值,在所有观察者都被处理后,可以将一个自定义观察者添加到系统中作为最后一个观察者,将值保存到一个 csv 文件中。

数据源开发

原文:www.backtrader.com/blog/posts/2015-08-11-datafeed-development/datafeed-development/

添加一个基于 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

这在 CSV 数据源开发中已经展示过了。

基类负责参数、初始化、文件打开、读取行、将行拆分为标记以及跳过不符合用户定义的日期范围(fromdatetodate)的行等其他事项。

开发非 CSV 数据源遵循相同的模式,而不需要深入到已拆分的行标记。

要做的事情:

  • 源自 backtrader.feed.DataBase
  • 添加任何可能需要的参数
  • 如果需要初始化,请重写__init__(self)和/或start(self)
  • 如果需要任何清理代码,请重写stop(self)
  • 工作发生在必须始终被重写的方法内:_load(self)

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

class DataBase(six.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的情况下,此参数应该是文件的路径或已经是类似文件的对象。
  • fromdatetodate定义了将传递给策略的日期范围。数据源提供的任何超出此范围的值将被忽略
  • name是用于绘图目的的装饰性内容
  • timeframecompression是装饰性和信息性的。它们在数据重采样和数据重播中真正发挥作用。
  • 如果传递了sessionend(一个 datetime.time 对象),它将被添加到数据源的datetime行中,从而可以识别会话结束的时间

示例二进制数据源

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中完成的。调用以加载下一组数据,此处为下一个:日期时间、开盘价、最高价、最低价、收盘价、成交量、持仓量。在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 年的数据。

这只涉及加载数据,因此甚至不需要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)
    # 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
相关文章
|
5天前
|
索引
BackTrader 中文文档(二十七)(3)
BackTrader 中文文档(二十七)
10 0
|
5天前
|
算法 索引 Python
BackTrader 中文文档(二十七)(2)
BackTrader 中文文档(二十七)
15 0
|
5天前
BackTrader 中文文档(二十七)(1)
BackTrader 中文文档(二十七)
10 0
|
5天前
|
算法 索引
BackTrader 中文文档(二十九)(1)
BackTrader 中文文档(二十九)
11 0
|
5天前
BackTrader 中文文档(二十九)(3)
BackTrader 中文文档(二十九)
10 0
|
5天前
BackTrader 中文文档(二十九)(2)
BackTrader 中文文档(二十九)
11 0
|
5天前
|
存储 API
BackTrader 中文文档(二十九)(4)
BackTrader 中文文档(二十九)
14 0
|
5天前
|
Python
BackTrader 中文文档(二十八)(1)
BackTrader 中文文档(二十八)
10 0
|
5天前
|
存储 算法 C++
BackTrader 中文文档(二十八)(3)
BackTrader 中文文档(二十八)
11 0
|
5天前
BackTrader 中文文档(二十八)(2)
BackTrader 中文文档(二十八)
11 0