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

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

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

实际应用

原文:www.backtrader.com/blog/posts/2015-08-27-real-world-usage/real-world-usage/

最后,似乎将其扎根于开发 backtrader 是值得的。

在看到上周欧洲市场的情况看起来像世界末日之后,一个朋友问我是否可以查看我们图表软件中的数据,看看下跌范围与以前类似情况的比较如何。

当然我可以,但我说我可以做的不仅仅是看图表,因为我可以迅速:

  • 创建一个快速的LegDown指标来测量下跌的范围。它也可以被命名为HighLowRangeHiLoRange。幸运的是,如果认为有必要,可以通过alias来解决这个问题。
  • 创建一个LegDownAnalyzer,它将收集结果并对其进行排序。

这导致了一个额外的请求:

  • 在接下来的 5、10、15、20 天内(交易后…)的跌幅后的复苏。
    通过使用LegUp指标解决,它会将值写回以与相应的LegDown对齐。

这项工作很快就完成了(在我空闲时间的允许范围内),并与请求者共享了结果。但是…只有我看到了潜在问题:

  • 改进bt-run.py中的自动化程式
  • 多种策略/观察者/分析器以及分开的 kwargs
  • 直接将指标注入策略中,每个指标都带有 kwargs
  • 单一的绘图参数也接受 kwargs。
  • Analyzer API 中进行改进,以实现对结果的自动打印功能(结果以dict-like 实例返回),并具有直接的data访问别名。

尽管如此:

  • 由于我编写了一个混合声明并额外使用next来对齐LegDownLegUp值的实现组合,出现了一个隐晦的错误。
    这个错误是为了简化传递多个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__中创建指标,就像其他对象一样:StrategiesIndicators通常是常见的嫌疑人
    这些指标会自动注册到附加了分析器的策略中
  • 就像策略一样,Analyzerself.datas(一个数据数组)和它的别名:self.dataself.data0self.data1
  • 类似策略:nexstartstop钩子(这些在指标中不存在)在这种情况下用于:
  • nextstart: 记录策略的初始起始点
  • stop: 进行最终的计算,因为事情已经完成
  • 注意:在这种情况下不需要其他方法,如 startprenextnext
  • 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 将总共调用prenextnext 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变量正在跟踪每次调用prenextnext。前者在应用简单移动平均产生值之前调用。后者在简单移动平均产生值时调用。

关键:

  • 策略的长度(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()


相关文章
|
18天前
|
测试技术
BackTrader 中文文档(二十六)(2)
BackTrader 中文文档(二十六)
14 0
|
18天前
|
Oracle 数据可视化 关系型数据库
BackTrader 中文文档(二十六)(3)
BackTrader 中文文档(二十六)
13 0
|
18天前
BackTrader 中文文档(二十六)(1)
BackTrader 中文文档(二十六)
17 0
|
18天前
BackTrader 中文文档(二十五)(2)
BackTrader 中文文档(二十五)
15 0
|
18天前
|
数据可视化
BackTrader 中文文档(二十五)(4)
BackTrader 中文文档(二十五)
16 0
|
18天前
|
数据可视化 API Python
BackTrader 中文文档(二十五)(1)
BackTrader 中文文档(二十五)
14 0
|
18天前
|
C++
BackTrader 中文文档(二十五)(3)
BackTrader 中文文档(二十五)
14 0
|
18天前
|
算法 索引 Python
BackTrader 中文文档(二十七)(2)
BackTrader 中文文档(二十七)
21 0
|
18天前
|
索引
BackTrader 中文文档(二十七)(3)
BackTrader 中文文档(二十七)
16 0
|
18天前
BackTrader 中文文档(二十七)(1)
BackTrader 中文文档(二十七)
15 0