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體積填充

到目前為止, backtrader中的默認交易量填充策略相當簡單明了:忽略音量筆記2016 年 7 月 15 日更正了實現中的錯誤並更新了示例以close頭寸並在休息後重複。

Backtrader優化改進

backtrader 1.8.12.99版改進了多處理期間數據饋送和結果的管理方式。

Backtrader教程:分析儀

無論是回溯測試還是交易,能夠分析交易系統的性能是瞭解是否不僅獲得了利潤,而且是否在風險太大的情況下實現了利潤,或者與參考資產(或無風險資產)相比,是否真的值得付出努力的關鍵。 這就是物件家族的用武之Analyzer 地:提供對所發生事件甚至實際發生的事情的分析。

Backtrader做空現金

從一開始,反向交易者就可以做空任何東西,包括類似股票和類似期貨的工具。當做空時,現金減少,被賣空資產的價值用於總淨清算價值。從一側移除並添加到另一側可以保持平衡。人們似乎更喜歡增加現金,這可能會增加支出。在1.9.7.105版本中,經紀人已將默認行為更改為添加現金和移除價值。

Backtrader對逐筆報價數據重新取樣

backtrader 已經可以從分鐘數據中重新採樣。接受價格變動數據不是問題,只需將 4 個常用欄位(open、 high、 low、 close)設置為價格變動值。 但是傳遞要重新採樣的逐筆報價數據再次生成相同的數據。作為或版本 1.1.11.88,情況已不再如此。

Backtrader標杆

backtrader 包括 2 種不同類型的物件,可幫助進行跟蹤: Observers Analyzers 工單 #89 是關於添加資產基準測試的。明智的是,人們實際上可能有一個策略,即使積極,也低於簡單地跟蹤資產所能提供的策略。

Backtrader教程:數據饋送 - 擴展 (Extending DataFeed)

GitHub 中的問題實際上是在推動文檔部分的完成,或者説明我瞭解我是否backtrader 具有我從一開始就設想的易用性和靈活性以及在此過程中做出的決定。 在本例中為問題 #9。

Backtrader買入/賣出箭頭

backtrader 旨在嘗試提供易用性。創建指標和其他常見嫌疑人應該很容易。 當然,定製現有項目也應該是交易的一部分。 社區中的一個主題,BuySell Arrows,它起源於從問題遷移而來的,就是一個很好的例子。

Backtrader 教程:數據饋送 - 開發 - 常規

筆記示例goog.fd中使用的二進製文件屬於 VisualChart,不能與backtrader一起分發。對直接使用二進製文件感興趣的人可以免費下載VisualChart 。 CSV數據饋送開發已經展示瞭如何添加新的基於 CSV 的數據饋送。

Backtrader終極振蕩器

backtrader開發啟動時的目標之一是使開發新的指標變得非常容易(至少對作者本人而言),以在數學和視覺上測試想法。 門票#102 是關於將 UltimateOscillator 添加到 backtrader 注意 它將在下一個版本中添加,同時可以使用下面的代碼使用它。 票證中所示的參考: 以及: 無需在這裡重複。