数据 - 回放#

时间已经过去了,对一个完全形成并关闭的柱状图进行策略测试是不错的,但也可以更好。

这就是 数据回放 发挥作用的地方。如果:

  • 策略在一个时间框架为X(例:每日)的数据上运作

并且

  • 较小时间框架Y(例:1分钟)的数据可用

数据回放正是其名所示的功能:

通过使用1分钟的数据回放每日柱状图

当然,这并不完全是市场的实际发展方式,但它比孤立地观察每日完全形成并关闭的柱状图要好得多:

如果策略在每日柱状图形成的过程中实时运行,那么柱状图形成的近似值会给予复制该策略在实际情况下的行为的机会将 *数据回放(Data Replay)* 实施按照 ``backtrader`` 的常规使用模式操作:

- 加载数据源

- 通过 ``replaydata`` 将数据传递给cerebro

- 添加策略

注意

在回放数据时,不支持预加载,因为每个bar实际上是实时构建的。 这将在任何 Cerebro 实例中自动禁用。

可以传递给 replaydata 的参数:

  • timeframe (默认值:bt.TimeFrame.Days)

    目标时间框架,必须等于或大于源时间框架才能发挥作用

  • compression (默认值:1)压缩所选值“n”为1个条

扩展参数(如非必要,请勿修改):

  • bar2edge (默认:True)

    重新调整使用时间边界作为封闭条的目标。例如,使用“ticks -> 5秒”,生成的5秒钟的条将与xx:00,xx:05,xx:10 …对齐。

  • adjbartime (默认:False)

    使用边界处的时间来调整交付的重新取样条的时间,而不是最后一个已见时间戳。例如,如果重新取样为“5秒”,则条的时间将被调整为hh:mm:05,即使最后一个已见时间戳是hh:mm:04.33。

    注意

    只有当 bar2edge 为True时,才会调整时间。如果条没有对齐到边界,调整时间是没有意义的。

  • rightedge (默认:True)

    使用时间边界的右边缘设置时间。如果假设为 False,并且将重新采样的间隔压缩为 5 秒,则从 hh:mm:00 到 hh:mm:04 之间的时间将是 hh:mm:00(起始边界)。

如果假设为 True,则时间的使用边界将是 hh:mm:05(结束边界)。

为了进行示例工作,将以每周为基础回放标准的 2006 年每日数据。这意味着:

  • 最终将有 52 个条形图,每周一个

  • Cerebro 总共将调用 prenextnext 255 次,这是每日条形图的原始数量

技巧是:

  • 当形成一个周条形图时,策略的长度( len(self) )将保持不变。

  • 每到新的一周,长度会增加一个

以下是一些示例,但首先是测试脚本的源代码,其中加载数据并使用 replaydata 传递给 cerebro,然后再 run

```python # 将文件路径设置为 data.csv datafile = ‘data.csv’

import backtrader as bt

class MyStrategy(bt.Strategy):
def next(self):

print(self.data.datetime.date())

# 初始化 Cerebro 引擎 cerebro = bt.Cerebro()

# 加载 CSV 数据 data = bt.feeds.GenericCSVData(

dataname=datafile, dtformat=’%Y-%m-%d’, # 从 CSV 文件的第 3 列开始是开盘价 open=2, # 从 CSV 文件的第 4 列开始是最高价 high=3, # 从 CSV 文件的第 5 列开始是最低价 low=4, # 从 CSV 文件的第 6 列开始是收盘价 close=5, # 从 CSV 文件中将第 0, 1, 2 行忽略掉 skiprows=3, # 每次加载 7 条数据作为一周的数据 timeframe=bt.TimeFrame.Weeks, compression=1

)

# 将数据加载到 Cerebro 引擎中 cerebro.adddata(data)

# 将策略加载到 Cerebro 引擎中 cerebro.addstrategy(MyStrategy)

# 运行 Cerebro 引擎 cerebro.run() ``` 例子 - 从每日重播到每周重播 =============================

执行脚本的方法:

$ ./replay-example.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 - 日线到日线的压缩#

当然,“回放”也可以应用于同一时间框架,但是需要进行压缩。

控制台输出:

$ ./replay-example.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/2006-day-001.txt'
    data = btfeeds.BacktraderCSVData(dataname=datapath)

    # Handy dictionary for the argument timeframe conversion
    tframes = dict(
        daily=bt.TimeFrame.Days,
        weekly=bt.TimeFrame.Weeks,
        monthly=bt.TimeFrame.Months)

    # First add the original data - smaller timeframe
    cerebro.replaydata(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='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()