体积填充
原文: https://www.backtrader.com/blog/posts/2016-07-14-volume-filling/volume-filling/
到目前为止,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
例如,要访问条形图卷,请执行以下操作:
py 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
样本
反向交易者来源包含一个名为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
...
大部分输入已被跳过,因为它相当冗长,但摘要如下:
-
当看到 1st条
20%
(–StatePerc 20.0将被用于发布购买订单 -
从输出中可以看出,由于反向交易者的默认行为,订单在一次交易中完全匹配。没有对卷执行任何查看
笔记
经纪人在样本中分配了大量现金,以确保其能够经受住许多测试情况
另一次使用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()