BackTrader 中文文档(三)(1)https://developer.aliyun.com/article/1489225
样本二进制数据源
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
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) ########################################################################### # 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
数据 - 多时间框架
原文:
www.backtrader.com/docu/data-multitimeframe/data-multitimeframe/
有时,投资决策是根据不同的时间框架进行的:
- 每周评估趋势
- 每日执行入场
或者 5 分钟对比 60 分钟。
这意味着需要在backtrader
中组合多个时间框架的数据以支持这种组合。
平台已经内置了对此的本地支持。最终用户只需遵循这些规则:
- 具有最小时间框架(因此具有更多柱状图)的数据必须是添加到 Cerebro 实例的第一个数据
- 数据必须正确地对齐日期时间,以便平台能够理解它们的含义
此外,最终用户可以自由地在较短/较大的时间框架上应用指标。当然:
- 应用于较大时间框架的指标将产生较少的柱状图
平台还将考虑以下内容
- 较大时间框架的最小周期
可能会有最小周期的副作用,这可能导致在策略添加到 Cerebro 后需要消耗几个数量级的较小时间框架柱状图才能开始执行。
内置的cerebro.resample
将用于创建较大的时间框架。
以下是一些示例,但首先是测试脚本的来源。
# Load the Data datapath = args.dataname or '../../datas/2006-day-001.txt' data = btfeeds.BacktraderCSVData(dataname=datapath) cerebro.adddata(data) # First add the original data - smaller timeframe tframes = dict(daily=bt.TimeFrame.Days, weekly=bt.TimeFrame.Weeks, monthly=bt.TimeFrame.Months) # Handy dictionary for the argument timeframe conversion # Resample the data if args.noresample: datapath = args.dataname2 or '../../datas/2006-week-001.txt' data2 = btfeeds.BacktraderCSVData(dataname=datapath) # And then the large timeframe cerebro.adddata(data2) else: cerebro.resampledata(data, timeframe=tframes[args.timeframe], compression=args.compression) # Run over everything cerebro.run()
步骤:
- 加载数据
- 根据用户指定的参数重新采样
该脚本还允许加载第二个数据 - 将数据添加到 cerebro
- 将重新采样的数据(更大的时间框架)添加到 cerebro
- 运行
示例 1 - 每日和每周
脚本的调用:
$ ./multitimeframe-example.py --timeframe weekly --compression 1
输出图表:
示例 2 - 每日和每日压缩(2 根柱状图合并为 1 根)
脚本的调用:
$ ./multitimeframe-example.py --timeframe daily --compression 2
输出图表:
示例 3 - 带有 SMA 的策略
尽管绘图很好,但这里的关键问题是展示较大的时间框架如何影响系统,特别是当涉及到起始点时
该脚本可以使用--indicators
来添加一个策略,该策略在较小和较大时间框架数据上创建周期为 10的简单移动平均线。
如果只考虑较小的时间框架:
next
将在第 10 根柱状图之后首先被调用,这是简单移动平均线需要产生数值的时间
注意:请记住,策略监视创建的指标,并且只有当所有指标都产生数值时才调用next
。其理念是,最终用户已经添加了指标以在逻辑中使用它们,因此如果指标没有产生数值,则不应进行任何逻辑操作。
但在这种情况下,较大的时间框架(每周)会延迟调用next
,直到每周数据上的简单移动平均线产生数值,这需要… 10 周。
该脚本覆盖了nextstart
,它只被调用一次,默认调用next
以显示第一次调用的时间。
调用 1:
只有较小的时间框架,每日,获得一个简单移动平均线
命令行和输出
$ ./multitimeframe-example.py --timeframe weekly --compression 1 --indicators --onlydaily -------------------------------------------------- nextstart called with len 10 --------------------------------------------------
以及图表。
调用 2:
两个时间框架都有一个简单移动平均线
命令行:
$ ./multitimeframe-example.py --timeframe weekly --compression 1 --indicators -------------------------------------------------- nextstart called with len 50 -------------------------------------------------- -------------------------------------------------- nextstart called with len 51 -------------------------------------------------- -------------------------------------------------- nextstart called with len 52 -------------------------------------------------- -------------------------------------------------- nextstart called with len 53 -------------------------------------------------- -------------------------------------------------- nextstart called with len 54 --------------------------------------------------
这里有两件事需要注意:
- 策略在 50 个周期后而不是 10 个周期后首次调用。
这是因为应用于较大(每周)时间框架的简单移动平均线在 10 周后产生一个值……那就是10 周 * 5 天 / 周 …… 50 天
nextstart
被调用了 5 次,而不是只有 1 次。
这是混合时间框架并且(在这种情况下仅有一个)指标应用于较大时间框架的自然副作用。
较大时间框架的简单移动平均产生了 5 次相同的值,而同时消耗了 5 个每日的条形图。
并且因为周期的开始由较大的时间框架控制,nextstart
被调用了 5 次。
以及图表。
结论
在 backtrader
中,可以使用多个时间框架的数据,无需特殊对象或调整:只需先添加较小的时间框架。
测试脚本。
from __future__ import (absolute_import, division, print_function, unicode_literals) import argparse import backtrader as bt import backtrader.feeds as btfeeds import backtrader.indicators as btind class SMAStrategy(bt.Strategy): params = ( ('period', 10), ('onlydaily', False), ) def __init__(self): self.sma_small_tf = btind.SMA(self.data, period=self.p.period) if not self.p.onlydaily: self.sma_large_tf = btind.SMA(self.data1, period=self.p.period) def nextstart(self): print('--------------------------------------------------') print('nextstart called with len', len(self)) print('--------------------------------------------------') super(SMAStrategy, self).nextstart() def runstrat(): args = parse_args() # Create a cerebro entity cerebro = bt.Cerebro(stdstats=False) # Add a strategy if not args.indicators: cerebro.addstrategy(bt.Strategy) else: cerebro.addstrategy( SMAStrategy, # args for the strategy period=args.period, onlydaily=args.onlydaily, ) # Load the Data datapath = args.dataname or '../../datas/2006-day-001.txt' data = btfeeds.BacktraderCSVData(dataname=datapath) cerebro.adddata(data) # First add the original data - smaller timeframe tframes = dict(daily=bt.TimeFrame.Days, weekly=bt.TimeFrame.Weeks, monthly=bt.TimeFrame.Months) # Handy dictionary for the argument timeframe conversion # Resample the data if args.noresample: datapath = args.dataname2 or '../../datas/2006-week-001.txt' data2 = btfeeds.BacktraderCSVData(dataname=datapath) # And then the large timeframe cerebro.adddata(data2) else: cerebro.resampledata(data, timeframe=tframes[args.timeframe], compression=args.compression) # Run over everything cerebro.run() # Plot the result cerebro.plot(style='bar') def parse_args(): parser = argparse.ArgumentParser( description='Multitimeframe test') parser.add_argument('--dataname', default='', required=False, help='File Data to Load') parser.add_argument('--dataname2', default='', required=False, help='Larger timeframe file to load') parser.add_argument('--noresample', action='store_true', help='Do not resample, rather load larger timeframe') parser.add_argument('--timeframe', default='weekly', required=False, choices=['daily', 'weekly', 'monhtly'], help='Timeframe to resample to') parser.add_argument('--compression', default=1, required=False, type=int, help='Compress n bars into 1') parser.add_argument('--indicators', action='store_true', help='Wether to apply Strategy with indicators') parser.add_argument('--onlydaily', action='store_true', help='Indicator only to be applied to daily timeframe') parser.add_argument('--period', default=10, required=False, type=int, help='Period to apply to indicator') return parser.parse_args() if __name__ == '__main__': runstrat()
BackTrader 中文文档(三)(3)https://developer.aliyun.com/article/1489229