Backtrader体积填充

  |  

到目前为止, backtrader中的默认交易填充策略相当简单明了:

  • 忽略音量

笔记

2016 年 7 月 15 日

更正了实现中的错误并更新了示例以close头寸并在休息后重复。

下面的最后一次测试运行(和相应的图表)来自更新示例

这是基于两个前提:

  • 市场交易流动性足以一次性完全吸收买/卖订单

  • 真实的体积匹配需要一个真实的世界

    一个简单的例子是Fill or Kill订单。即使到分时分辨率和足够的成交量反向交易经纪人也无法知道市场上碰巧有多少额外的参与者来区分这样的订单是否会匹配或不会匹配到Fill部分,或者是否命令应该是Kill

但是在1.5.2.93版本中,可以为经纪人指定一个filler ,以便在运行订单时将交易考虑在内。此外,还有 3 个初始填充物已进入发行版:

  • FixedSize :每天使用固定的匹配大小(例如:1000 个单位),前提是当前柱至少有 1000 个单位

  • FixedBarPerc : 使用总柱交易的百分比来尝试匹配订单

  • BarPointPerc :在价格范围-的范围内均匀分布柱状图,并使用对应於单个价格点的交易百分比

创建填充物

backtrader生态系统中的填充器可以是任何与以下签名匹配的可调用对象:

callable(order, price, ago)

在哪里:

  • order是要运行的订单

    该对象允许访问作为操作目标的data对象、创建大小/价格、运行价格/大小/剩余大小和其他详细信息

  • 订单将被运行的price

  • ago是按顺序查找数量和价格元素的data索引

    在几乎所有情况下,这将是0 (当前时间点),但在一个极端的情况下以涵盖Close订单,这可能是-1

    例如,要访问条形音量,请运行以下操作:

    barvolume = order.data.volume[ago]
    

可调用对象可以是函数,例如支持__call__方法的类的实例,例如:

class MyFiller(object):
    def __call__(self, order, price, ago):
        pass

向代理添加填充器

最直接的方法是使用set_filler

import backtrader as bt

cerebro = Cerebro()
cerebro.broker.set_filler(bt.broker.filler.FixedSize())

第二种选择是完全替换broker ,尽管这可能仅适用于已重写部分功能的BrokerBack的子类:

import backtrader as bt

cerebro = Cerebro()
filler = bt.broker.filler.FixedSize()
newbroker = bt.broker.BrokerBack(filler=filler)
cerebro.broker = newbroker

样本

backtrader源包含一个名为volumefilling的示例,它允许测试一些集成的fillers器(最初是全部)

该示例使用名为datas/2006- volume -day-001.txt的源中的默认数据示例。

例如没有填充物的运行:

$ ./volumefilling.py --stakeperc 20.0

输出:

Len,Datetime,Open,High,Low,Close,Volume,OpenInterest
0001,2006-01-02,3602.00,3624.00,3596.00,3617.00,164794.00,1511674.00
++ STAKE VOLUME: 32958.0
-- NOTIFY ORDER BEGIN
Ref: 1
...
Alive: False
-- NOTIFY ORDER END
-- ORDER REMSIZE: 0.0
++ ORDER COMPLETED at data.len: 2
0002,2006-01-03,3623.00,3665.00,3614.00,3665.00,554426.00,1501792.00
...

大部分输入已被跳过,因为它相当冗长,但摘要是:

  • 看到第一个柱20% ( –stakeperc 20.0) 将用于发出买单

  • 从输出中可以看出,在backtrader的默认行为下,订单已完全匹配一次。没有查看已运行

笔记

经纪人在样本中分配了大量现金,以确保它能够承受许多测试情况

使用FixedSize体积填料和每条最多1000单位的另一次运行:

$ ./volumefilling.py --stakeperc 20.0 --filler FixedSize --filler-args size=1000

输出:

Len,Datetime,Open,High,Low,Close,Volume,OpenInterest
0001,2006-01-02,3602.00,3624.00,3596.00,3617.00,164794.00,1511674.00
++ STAKE VOLUME: 32958.0
-- NOTIFY ORDER BEGIN
...
-- NOTIFY ORDER END
-- ORDER REMSIZE: 0.0
++ ORDER COMPLETED at data.len: 34
0034,2006-02-16,3755.00,3774.00,3738.00,3773.00,502043.00,1662302.00
...

现在:

  • 选择的保持不变,为32958

  • 运行在34条完成,这似乎是合理的,因为从第 2 条到第 34 条……已经看到了 33 条。每根柱线匹配\ 1000` 单位显然需要 33 根柱线才能完成运行

这不是一个伟大的成就,所以让我们去FixedBarPerc

$ ./volumefilling.py --stakeperc 20.0 --filler FixedBarPerc --filler-args perc=0.75

输出:

...
-- NOTIFY ORDER END
-- ORDER REMSIZE: 0.0
++ ORDER COMPLETED at data.len: 11
0011,2006-01-16,3635.00,3664.00,3632.00,3660.00,273296.00,1592611.00
...

这次:

  • 跳过开始,订单仍为32958

  • 运行使用 0.75% 的柱成交量来匹配请求。

  • 它需要从第 2 小节到第 11 小节(10 小节)才能完成。

这更有趣,但让我们看看现在使用BarPointPerc进行更动态的分配会发生什么:

$ ./volumefilling.py --stakeperc 20.0 --filler BarPointPerc --filler-args minmov=1.0,perc=10.0

输出:

...
-- NOTIFY ORDER END
-- ORDER REMSIZE: 0.0
++ ORDER COMPLETED at data.len: 22
0022,2006-01-31,3697.00,3718.00,3681.00,3704.00,749740.00,1642003.00
...

会发生什么:

  • 与大小相同的初始分配(跳过)到32958的顺序

  • 完全运行需要 2 到 22 时间(21 条)

  • 填充器使用1.0minmov (资产的最小价格变动)在-范围内均匀分布交易

  • 分配给给定价格点的交易量的10%用于订单匹配

对于任何对如何在每个柱上部分匹配订单感兴趣的人,检查一次运行的完整输出可能值得花时间。

笔记

在 1.5.3.93 中纠正错误并更新示例以在休息后close操作

现金增加到一个更疯狂的数额以避免追加保证金并激活绘图:

$ ./volumefilling.py --filler FixedSize --filler-args size=10000 --stakeperc 10.0 --plot --cash 500e9

与其查看极其冗长的输出,不如看一下已经讲述了故事的图表。

样品用途:

usage: volumefilling.py [-h] [--data DATA] [--cash CASH]
                        [--filler {FixedSize,FixedBarPerc,BarPointPerc}]
                        [--filler-args FILLER_ARGS] [--stakeperc STAKEPERC]
                        [--opbreak OPBREAK] [--fromdate FROMDATE]
                        [--todate TODATE] [--plot]

Volume Filling Sample

optional arguments:
  -h, --help            show this help message and exit
  --data DATA           Data to be read in (default: ../../datas/2006-volume-
                        day-001.txt)
  --cash CASH           Starting cash (default: 500000000.0)
  --filler {FixedSize,FixedBarPerc,BarPointPerc}
                        Apply a volume filler for the execution (default:
                        None)
  --filler-args FILLER_ARGS
                        kwargs for the filler with format:
                        arg1=val1,arg2=val2... (default: None)
  --stakeperc STAKEPERC
                        Percentage of 1st bar to use for stake (default: 10.0)
  --opbreak OPBREAK     Bars to wait for new op after completing another
                        (default: 10)
  --fromdate FROMDATE, -f FROMDATE
                        Starting date in YYYY-MM-DD format (default: None)
  --todate TODATE, -t TODATE
                        Ending date in YYYY-MM-DD format (default: None)
  --plot                Plot the result (default: False)

编码

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse
import datetime
import os.path
import time
import sys


import backtrader as bt


class St(bt.Strategy):
    params = (
        ('stakeperc', 10.0),
        ('opbreak', 10),
    )

    def notify_order(self, order):
        print('-- NOTIFY ORDER BEGIN')
        print(order)
        print('-- NOTIFY ORDER END')
        print('-- ORDER REMSIZE:', order.executed.remsize)

        if order.status == order.Completed:
            print('++ ORDER COMPLETED at data.len:', len(order.data))
            self.doop = -self.p.opbreak

    def __init__(self):
        pass

    def start(self):
        self.callcounter = 0
        txtfields = list()
        txtfields.append('Len')
        txtfields.append('Datetime')
        txtfields.append('Open')
        txtfields.append('High')
        txtfields.append('Low')
        txtfields.append('Close')
        txtfields.append('Volume')
        txtfields.append('OpenInterest')
        print(','.join(txtfields))

        self.doop = 0

    def next(self):
        txtfields = list()
        txtfields.append('%04d' % len(self))
        txtfields.append(self.data0.datetime.date(0).isoformat())
        txtfields.append('%.2f' % self.data0.open[0])
        txtfields.append('%.2f' % self.data0.high[0])
        txtfields.append('%.2f' % self.data0.low[0])
        txtfields.append('%.2f' % self.data0.close[0])
        txtfields.append('%.2f' % self.data0.volume[0])
        txtfields.append('%.2f' % self.data0.openinterest[0])
        print(','.join(txtfields))

        # Single order
        if self.doop == 0:
            if not self.position.size:
                stakevol = (self.data0.volume[0] * self.p.stakeperc) // 100
                print('++ STAKE VOLUME:', stakevol)
                self.buy(size=stakevol)

            else:
                self.close()

        self.doop += 1


FILLERS = {
    'FixedSize': bt.broker.filler.FixedSize,
    'FixedBarPerc': bt.broker.filler.FixedBarPerc,
    'BarPointPerc': bt.broker.filler.BarPointPerc,
}


def runstrat():
    args = parse_args()

    datakwargs = dict()
    if args.fromdate:
        fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
        datakwargs['fromdate'] = fromdate

    if args.todate:
        fromdate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
        datakwargs['todate'] = todate

    data = bt.feeds.BacktraderCSVData(dataname=args.data, **datakwargs)

    cerebro = bt.Cerebro()
    cerebro.adddata(data)

    cerebro.broker.set_cash(args.cash)
    if args.filler is not None:
        fillerkwargs = dict()
        if args.filler_args is not None:
            fillerkwargs = eval('dict(' + args.filler_args + ')')

        filler = FILLERS[args.filler](**fillerkwargs)
        cerebro.broker.set_filler(filler)

    cerebro.addstrategy(St, stakeperc=args.stakeperc, opbreak=args.opbreak)

    cerebro.run()
    if args.plot:
        cerebro.plot(style='bar')


def parse_args():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Volume Filling Sample')

    parser.add_argument('--data', required=False,
                        default='../../datas/2006-volume-day-001.txt',
                        help='Data to be read in')

    parser.add_argument('--cash', required=False, action='store',
                        default=500e6, type=float,
                        help=('Starting cash'))

    parser.add_argument('--filler', required=False, action='store',
                        default=None, choices=FILLERS.keys(),
                        help=('Apply a volume filler for the execution'))

    parser.add_argument('--filler-args', required=False, action='store',
                        default=None,
                        help=('kwargs for the filler with format:\n'
                              '\n'
                              'arg1=val1,arg2=val2...'))

    parser.add_argument('--stakeperc', required=False, action='store',
                        type=float, default=10.0,
                        help=('Percentage of 1st bar to use for stake'))

    parser.add_argument('--opbreak', required=False, action='store',
                        type=int, default=10,
                        help=('Bars to wait for new op after completing '
                              'another'))

    parser.add_argument('--fromdate', '-f', required=False, default=None,
                        help='Starting date in YYYY-MM-DD format')

    parser.add_argument('--todate', '-t', required=False, default=None,
                        help='Ending date in YYYY-MM-DD format')

    parser.add_argument('--plot', required=False, action='store_true',
                        help=('Plot the result'))

    return parser.parse_args()


if __name__ == '__main__':
    runstrat()

推荐阅读

相关文章

Backtrader细分佣金计划

不久前,委员会计划的实施进行了重新设计。最重要的是:涉及的部分返工: 保留原始的佣金信息类和行为 打开大门,轻松创建用户定义的佣金 将格式 xx% 作为新佣金方案的默认值,而不是 0.xx(只是一个品味问题),保持行为可配置 扩展委员会概述了基本要素。

Backtrader佣金计划

backtrader 的诞生是出于必要。我自己的...有一种感觉,我控制着自己的回溯测试平台,可以尝试新的想法。但是,在这样做并且从一开始就完全 open 采购它时,很明显它必须有一种方法来满足他人的需求和愿望。 作为未来的交易者,我本可以选择基于点的计算和每轮佣金的固定价格,但这将是一个错误。

Backtrader分数大小

首先,让我们用两行总结反向交易方法的工作原理:它就像一个带有基本构建块 ( Cerebro ) 的建筑套件,可以插入许多不同的部件基本分布包含许多部分,如指针、分析器、观察器、 Sizer 、过滤器、数据馈送、经纪人、佣金/资产信息方案,...

Backtrader 教程:绘图 - 同一轴

上一篇future-spot 将原始数据和稍微(随机)修改的数据绘制在同一空间上,但不在同一轴上。从该帖子中恢复第一张图片。有人能看见:图表左右两侧有不同的刻度当查看在原始数据周围振荡+- 50点的摆动红线(随机数据)时,这一点最为明显。在图表上,视觉印像是这些随机数据大多总是高于原始数据。

Backtrader期货补偿与现货补偿

版本1.9.32.116 增加了对社区中呈现的有趣用例 的支持 以期货开始交易,包括实物交割 让一个指针告诉你一些事情 如果需要, close 现货价格操作,有效地取消实物交割,无论是为了接收货物还是为了必须交付货物(并希望获利)来头寸。

Backtrader绘制日期范围

该版本1.9.31.x 增加了制作部分绘图的功能。 使用策略实例中保存的完整时间戳数组的索引 或者使用实际datetime.date 或 datetime.datetime 实例来限制必须绘制的内容。 一切都超过标准cerebro.plot。

Backtrader混合时间帧

1.3.0.92版本带来了混合来自不同时间帧的数据(来自 data feeds 和/或指针)的可能性。 到版本:https://github.com/mementum/backtrader/发布/标签/1.3.0.92 背景:指示器是智能哑对象。 他们很聪明,因为他们可以进行复杂的计算。

Backtrader教程:经纪商

经纪商仿真器该模拟支持不同的订单类型,根据当前现金检查提交的订单现金需求,跟踪每次反复运算的cerebro 现金和价值,并在不同数据上保持当前位置。

Backtrader教程:尺寸调整器

智能质押 策略提供了交易方法,即:buy和 sell close。

Backtrader熊猫数据馈送

在一些小的增强功能和一些OrderDict调整中,以获得更好的Python 2.6支持, backtrader 的最新版本增加了对分析Pandas Dataframe或Time Series数据的支持。