到目前为止, 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.0
的minmov
(资产的最小价格变动)在高-低范围内均匀分布交易量分配给给定价格点的交易量的
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()