到目前為止, 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對象、創建大小/價格、執行價格/大小/剩餘大小和其他詳細信息訂單將被執行的
priceago是按順序查找數量和價格元素的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()