BackTrader 中文文档(二十六)(3)https://developer.aliyun.com/article/1505466
实际应用
原文:
www.backtrader.com/blog/posts/2015-08-27-real-world-usage/real-world-usage/
最后,似乎将其扎根于开发 backtrader 是值得的。
在看到上周欧洲市场的情况看起来像世界末日之后,一个朋友问我是否可以查看我们图表软件中的数据,看看下跌范围与以前类似情况的比较如何。
当然我可以,但我说我可以做的不仅仅是看图表,因为我可以迅速:
- 创建一个快速的
LegDown
指标来测量下跌的范围。它也可以被命名为HighLowRange
或HiLoRange
。幸运的是,如果认为有必要,可以通过alias
来解决这个问题。 - 创建一个
LegDownAnalyzer
,它将收集结果并对其进行排序。
这导致了一个额外的请求:
- 在接下来的 5、10、15、20 天内(交易后…)的跌幅后的复苏。
通过使用LegUp
指标解决,它会将值写回以与相应的LegDown
对齐。
这项工作很快就完成了(在我空闲时间的允许范围内),并与请求者共享了结果。但是…只有我看到了潜在问题:
- 改进
bt-run.py
中的自动化程式
- 多种策略/观察者/分析器以及分开的 kwargs
- 直接将指标注入策略中,每个指标都带有 kwargs
- 单一的绘图参数也接受 kwargs。
- 在
Analyzer
API 中进行改进,以实现对结果的自动打印功能(结果以dict
-like 实例返回),并具有直接的data
访问别名。
尽管如此:
- 由于我编写了一个混合声明并额外使用
next
来对齐LegDown
和LegUp
值的实现组合,出现了一个隐晦的错误。
这个错误是为了简化传递多个Lines
的单个数据而引入的,以便Indicators
可以对每条线进行操作作为单独的数据。
后者将我推向:
- 添加一个与
LineDelay
相反的背景对象以“看”到“未来”。
实际上这意味着实际值被写入过去的数组位置。
一旦所有这些都就位了,就是重新测试以上请求提出的(小?)挑战,看看如何更轻松地解决以及更快地(在实现时间上)解决的时候了。
最后,执行和结果为从 1998 年至今的 Eurostoxx 50 期货:
bt-run.py \ --csvformat vchartcsv \ --data ../datas/sample/1998-2015-estx50-vchart.txt \ --analyzer legdownup \ --pranalyzer \ --nostdstats \ --plot ==================== == Analyzers ==================== ########## legdownupanalyzer ########## Date,LegDown,LegUp_5,LegUp_10,LegUp_15,LegUp_20 2008-10-10,901.0,331.0,69.0,336.0,335.0 2001-09-11,889.0,145.0,111.0,239.0,376.0 2008-01-22,844.0,328.0,360.0,302.0,344.0 2001-09-21,813.0,572.0,696.0,816.0,731.0 2002-07-24,799.0,515.0,384.0,373.0,572.0 2008-01-23,789.0,345.0,256.0,319.0,290.0 2001-09-17,769.0,116.0,339.0,405.0,522.0 2008-10-09,768.0,102.0,0.0,120.0,208.0 2001-09-12,764.0,137.0,126.0,169.0,400.0 2002-07-23,759.0,331.0,183.0,285.0,421.0 2008-10-16,758.0,102.0,222.0,310.0,201.0 2008-10-17,740.0,-48.0,219.0,218.0,116.0 2015-08-24,731.0,nan,nan,nan,nan 2002-07-22,729.0,292.0,62.0,262.0,368.0 ... ... ... 2001-10-05,-364.0,228.0,143.0,286.0,230.0 1999-01-04,-370.0,219.0,99.0,-7.0,191.0 2000-03-06,-382.0,-60.0,-127.0,-39.0,-161.0 2000-02-14,-393.0,-92.0,90.0,340.0,230.0 2000-02-09,-400.0,-22.0,-46.0,96.0,270.0 1999-01-05,-438.0,3.0,5.0,-107.0,5.0 1999-01-07,-446.0,-196.0,-6.0,-82.0,-50.0 1999-01-06,-536.0,-231.0,-42.0,-174.0,-129.0
2015 年 8 月的下跌在第 13 个位置显示出来。显然是一个不常见的事件,尽管有更大的事件发生过。
针对指向上升的后续腿要做的事情对于统计学家和聪明的数学头脑来说要多得多,而对我来说则要少得多。
关于LegUpDownAnalyzer
的实现细节(在末尾看到整个模块代码):
- 它在
__init__
中创建指标,就像其他对象一样:Strategies
,Indicators
通常是常见的嫌疑人
这些指标会自动注册到附加了分析器的策略中 - 就像策略一样,
Analyzer
有self.datas
(一个数据数组)和它的别名:self.data
、self.data0
、self.data1
… - 类似策略:
nexstart
和stop
钩子(这些在指标中不存在)在这种情况下用于:
nextstart
: 记录策略的初始起始点stop
: 进行最终的计算,因为事情已经完成
- 注意:在这种情况下不需要其他方法,如
start
、prenext
和next
LegDownUpAnalyzer
方法print
已经被重写,不再调用pprint
方法,而是创建计算的 CSV 打印输出
经过许多讨论,因为我们将 --plot
加入了混合中 … 图表。
最后是由 bt-run
加载的 legupdown
模块。
from __future__ import (absolute_import, division, print_function, unicode_literals) import itertools import operator import six from six.moves import map, xrange, zip import backtrader as bt import backtrader.indicators as btind from backtrader.utils import OrderedDict class LegDown(bt.Indicator): ''' Calculates what the current legdown has been using: - Current low - High from ``period`` bars ago ''' lines = ('legdown',) params = (('period', 10),) def __init__(self): self.lines.legdown = self.data.high(-self.p.period) - self.data.low class LegUp(bt.Indicator): ''' Calculates what the current legup has been using: - Current high - Low from ``period`` bars ago If param ``writeback`` is True the value will be written backwards ``period`` bars ago ''' lines = ('legup',) params = (('period', 10), ('writeback', True),) def __init__(self): self.lu = self.data.high - self.data.low(-self.p.period) self.lines.legup = self.lu(self.p.period * self.p.writeback) class LegDownUpAnalyzer(bt.Analyzer): params = ( # If created indicators have to be plotteda along the data ('plotind', True), # period to consider for a legdown ('ldown', 10), # periods for the following legups after a legdown ('lups', [5, 10, 15, 20]), # How to sort: date-asc, date-desc, legdown-asc, legdown-desc ('sort', 'legdown-desc'), ) sort_options = ['date-asc', 'date-des', 'legdown-desc', 'legdown-asc'] def __init__(self): # Create the legdown indicator self.ldown = LegDown(self.data, period=self.p.ldown) self.ldown.plotinfo.plot = self.p.plotind # Create the legup indicators indicator - writeback is not touched # so the values will be written back the selected period and therefore # be aligned with the end of the legdown self.lups = list() for lup in self.p.lups: legup = LegUp(self.data, period=lup) legup.plotinfo.plot = self.p.plotind self.lups.append(legup) def nextstart(self): self.start = len(self.data) - 1 def stop(self): # Calculate start and ending points with values start = self.start end = len(self.data) size = end - start # Prepare dates (key in the returned dictionary) dtnumslice = self.strategy.data.datetime.getzero(start, size) dtslice = map(lambda x: bt.num2date(x).date(), dtnumslice) keys = dtslice # Prepare the values, a list for each key item # leg down ldown = self.ldown.legdown.getzero(start, size) # as many legs up as requested lups = [up.legup.getzero(start, size) for up in self.lups] # put legs down/up together and interleave (zip) vals = [ldown] + lups zvals = zip(*vals) # Prepare sorting options if self.p.sort == 'date-asc': reverse, item = False, 0 elif self.p.sort == 'date-desc': reverse, item = True, 0 elif self.p.sort == 'legdown-asc': reverse, item = False, 1 elif self.p.sort == 'legdown-desc': reverse, item = True, 1 else: # Default ordering - date-asc reverse, item = False, 0 # Prepare a sorted array of 2-tuples keyvals_sorted = sorted(zip(keys, zvals), reverse=reverse, key=operator.itemgetter(item)) # Use it to build an ordereddict self.ret = OrderedDict(keyvals_sorted) def get_analysis(self): return self.ret def print(self, *args, **kwargs): # Overriden to change default behavior (call pprint) # provides a CSV printout of the legs down/up header_items = ['Date', 'LegDown'] header_items.extend(['LegUp_%d' % x for x in self.p.lups]) header_txt = ','.join(header_items) print(header_txt) for key, vals in six.iteritems(self.ret): keytxt = key.strftime('%Y-%m-%d') txt = ','.join(itertools.chain([keytxt], map(str, vals))) print(txt)
数据 - 重放
原文:
www.backtrader.com/blog/posts/2015-08-25-data-replay/data-replay/
时间已经过去,针对一个完全形成和关闭的柱状图测试策略是好的,但可以更好。
这就是数据重放的用武之地。如果:
- 策略在时间框架 X(例如:每日)上运行
和
- 较小时间框架 Y(例如:1 分钟)的数据可用
数据重放正如其名称所示:
Replay a daily bar using the 1 minute data
当然,这并不是市场实际发展的方式,但比孤立地查看每日完整和关闭的柱状图要好得多:
If the strategy operates in realtime during the formation of the daily bar, the approximation of the formation of the bar gives a chance to replicate the actual behavior of the strategy under real conditions
将数据重放付诸实践遵循backtrader
的常规使用模式
- 加载数据源
- 将数据传递给
DataReplayer
,这是另一个将在加载的数据源上工作的数据源 - 将新的数据源传递给 cerebro
- 添加一个策略
- 然后运行… 禁用预加载*
注意
当数据被重放时,无法支持预加载,因为每个柱状图实际上是实时构建的。
为了演示,将在每周基础上重放标准的 2006 年日常数据。这意味着:
- 最终将有 52 个柱状图,每周一个
- Cerebro 将总共调用
prenext
和next
255 次,这是每日柱状图的原始计数
技巧:
- 当每周柱状图形成时,策略的长度(
len(self)
)将保持不变。 - 每个新周,长度将增加一
以下是一些示例,但首先是测试脚本的源代码,其中加载数据并传递给重放器…并且使用preload=False
来禁用预加载(强制)
dataname=datapath) tframes = dict( daily=bt.TimeFrame.Days, weekly=bt.TimeFrame.Weeks, monthly=bt.TimeFrame.Months) # Handy dictionary for the argument timeframe conversion # Resample the data data_replayed = bt.DataReplayer( dataname=data, timeframe=tframes[args.timeframe], compression=args.compression) # First add the original data - smaller timeframe cerebro.adddata(data_replayed) # Run over everything cerebro.run(preload=False)
示例 - 将每日重放为每周
脚本的调用:
$ ./data-replay.py --timeframe weekly --compression 1
不幸的是,图表无法向我们展示背景中发生的真实情况,因此让我们看看控制台输出:
prenext len 1 - counter 1 prenext len 1 - counter 2 prenext len 1 - counter 3 prenext len 1 - counter 4 prenext len 1 - counter 5 prenext len 2 - counter 6 ... ... prenext len 9 - counter 44 prenext len 9 - counter 45 ---next len 10 - counter 46 ---next len 10 - counter 47 ---next len 10 - counter 48 ---next len 10 - counter 49 ---next len 10 - counter 50 ---next len 11 - counter 51 ---next len 11 - counter 52 ---next len 11 - counter 53 ... ... ---next len 51 - counter 248 ---next len 51 - counter 249 ---next len 51 - counter 250 ---next len 51 - counter 251 ---next len 51 - counter 252 ---next len 52 - counter 253 ---next len 52 - counter 254 ---next len 52 - counter 255
正如我们所看到的,内部的self.counter
变量正在跟踪每次调用prenext
或next
。前者在应用简单移动平均产生值之前调用。后者在简单移动平均产生值时调用。
关键:
- 策略的长度(len(self))每 5 个柱状图(每周 5 个交易日)发生变化
该策略实际上看到:
- 每周柱状图是如何在 5 次快照中发展的。
再次强调,这并不复制市场的实际逐笔(甚至不是分钟、小时)发展,但比实际看到柱状图要好。
可视化输出是周线图表,这是系统正在进行测试的最终结果。
示例 2 - 每日到每日带压缩
当然,“重放”也可以应用于相同的时间框架,但具有压缩。
控制台:
$ ./data-replay.py --timeframe daily --compression 2 prenext len 1 - counter 1 prenext len 1 - counter 2 prenext len 2 - counter 3 prenext len 2 - counter 4 prenext len 3 - counter 5 prenext len 3 - counter 6 prenext len 4 - counter 7 ... ... ---next len 125 - counter 250 ---next len 126 - counter 251 ---next len 126 - counter 252 ---next len 127 - counter 253 ---next len 127 - counter 254 ---next len 128 - counter 255
这次我们得到了预期的一半柱状图,因为请求的压缩因子为 2。
图表:
结论
可以对市场发展进行重建。通常会提供一组较小的时间范围数据,可以用来离散地重播系统运行的时间范围。
测试脚本。
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 = btind.SMA(self.data, period=self.p.period) def start(self): self.counter = 0 def prenext(self): self.counter += 1 print('prenext len %d - counter %d' % (len(self), self.counter)) def next(self): self.counter += 1 print('---next len %d - counter %d' % (len(self), self.counter)) def runstrat(): args = parse_args() # Create a cerebro entity cerebro = bt.Cerebro(stdstats=False) cerebro.addstrategy( SMAStrategy, # args for the strategy period=args.period, ) # Load the Data datapath = args.dataname or '../datas/sample/2006-day-001.txt' data = btfeeds.BacktraderCSVData( dataname=datapath) tframes = dict( daily=bt.TimeFrame.Days, weekly=bt.TimeFrame.Weeks, monthly=bt.TimeFrame.Months) # Handy dictionary for the argument timeframe conversion # Resample the data data_replayed = bt.DataReplayer( dataname=data, timeframe=tframes[args.timeframe], compression=args.compression) # First add the original data - smaller timeframe cerebro.adddata(data_replayed) # Run over everything cerebro.run(preload=False) # Plot the result cerebro.plot(style='bar') def parse_args(): parser = argparse.ArgumentParser( description='Pandas test script') parser.add_argument('--dataname', default='', required=False, help='File Data to Load') 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('--period', default=10, required=False, type=int, help='Period to apply to indicator') return parser.parse_args() if __name__ == '__main__': runstrat()