在backtrader的概念和開發過程中應用的關鍵概念之一是靈活性。 Python 的元編程和自省功能曾經是(現在仍然是)保持許多東西靈活同時仍然能夠交付的基礎。
一篇舊帖子顯示了擴展概念。
基礎知識:
from backtrader.feeds import GenericCSVData class GenericCSV_PE(GenericCSVData): lines = ('pe',) # Add 'pe' to already defined lines
完畢。 backtrader
在後台定義了最常用的行:OHLC。
如果我們深入研究GenericCSV_PE
的最後一個方面,繼承的加上新定義的行的總和將產生以下行:
('close', 'open', 'high', 'low', 'volume', 'openinterest', 'datetime', 'pe',)
這可以隨時使用getlinealiases
方法檢查(適用於 DataFeeds、Indicators、Strategies 和Observers )
該機制是靈活的,通過深入了解內部結構,您實際上可以獲得任何東西,但事實證明這還不夠。
Ticket #60詢問是否支持高頻數據,即:Bid/Ask 數據。這意味著 OHLC 形式的預定義行層次結構是不夠的。買入價和賣出價、交易量和交易數量可以根據現有的 OHLC 字段進行調整,但感覺不自然。如果只關心買價和賣價,就會有太多的領域沒有觸及。
這需要一個已與Release 1.2.1.88一起實施的解決方案。這個想法可以概括為:
- 現在不僅可以擴展現有的層次結構,還可以用新的層次結構替換層次結構
只有一個約束:
必須存在一個
datetime
時間字段(希望包含有意義的datetime
時間信息)之所以如此,是因為
backtrader
需要一些東西來進行同步(多個數據、多個時間框架、重新採樣、重放),就像阿基米德需要槓桿一樣。
這是它的工作原理:
from backtrader.feeds import GenericCSVData class GenericCSV_BidAsk(GenericCSVData): linesoverride = True lines = ('bid', 'ask', 'datetime') # Replace hierarchy with this one
完畢。
好吧,不完全。但這僅僅是因為我們正在考慮從 csv 源加載行。由於linesoverride= True
設置,層次結構實際上已經被出價替換,詢問日期時間定義。
原始的GenericCSVData
類解析 csv 文件並需要提示與行對應的字段所在的位置。原來的定義是:
class GenericCSVData(feed.CSVDataBase): params = ( ('nullvalue', float('NaN')), ('dtformat', '%Y-%m-%d %H:%M:%S'), ('tmformat', '%H:%M:%S'), ('datetime', 0), ('time', -1), # -1 means not present ('open', 1), ('high', 2), ('low', 3), ('close', 4), ('volume', 5), ('openinterest', 6), )
輕觸即可完成新的層次結構重新定義類:
from backtrader.feeds import GenericCSVData class GenericCSV_BidAsk(GenericCSVData): linesoverride = True lines = ('bid', 'ask', 'datetime') # Replace hierarchy with this one params = (('bid', 1), ('ask', 2))
表明買價是 csv 流中的第 1 字段,而賣價是第 2 字段。我們沒有改變基類中的 datetime #0 定義。
為這種場合製作一個小數據文件有助於:
TIMESTAMP,BID,ASK 02/03/2010 16:53:50,0.5346,0.5347 02/03/2010 16:53:51,0.5343,0.5347 02/03/2010 16:53:52,0.5543,0.5545 02/03/2010 16:53:53,0.5342,0.5344 02/03/2010 16:53:54,0.5245,0.5464 02/03/2010 16:53:54,0.5460,0.5470 02/03/2010 16:53:56,0.5824,0.5826 02/03/2010 16:53:57,0.5371,0.5374 02/03/2010 16:53:58,0.5793,0.5794 02/03/2010 16:53:59,0.5684,0.5688
在方程中添加一個小測試腳本(為那些直接訪問源代碼中的示例的人提供更多內容)(參見最後的完整代碼):
$ ./bidask.py
輸出不言自明:
1: 2010-02-03T16:53:50 - Bid 0.5346 - 0.5347 Ask 2: 2010-02-03T16:53:51 - Bid 0.5343 - 0.5347 Ask 3: 2010-02-03T16:53:52 - Bid 0.5543 - 0.5545 Ask 4: 2010-02-03T16:53:53 - Bid 0.5342 - 0.5344 Ask 5: 2010-02-03T16:53:54 - Bid 0.5245 - 0.5464 Ask 6: 2010-02-03T16:53:54 - Bid 0.5460 - 0.5470 Ask 7: 2010-02-03T16:53:56 - Bid 0.5824 - 0.5826 Ask 8: 2010-02-03T16:53:57 - Bid 0.5371 - 0.5374 Ask 9: 2010-02-03T16:53:58 - Bid 0.5793 - 0.5794 Ask 10: 2010-02-03T16:53:59 - Bid 0.5684 - 0.5688 Ask
瞧!買入/賣出價格已被正確讀取、解析和解釋,並且該策略已經能夠通過 self.data 訪問數據饋送中的 .bid 和 .ask行。
不過,重新定義線條層次結構會引發一個廣泛的問題,那就是已經預定義的指標的使用。
示例:隨機指標是一個依靠收盤價、最高價和最低價來計算其輸出的指標
即使我們將 Bid 作為收盤價(因為是第一個),也只有一個其他價格元素(Ask)而不是另外兩個。從概念上講,Ask 與高低無關
從事這些領域工作並在高頻交易領域進行操作(或研究)的人很可能並不關心隨機指標作為選擇指標
移動平均線等其他指標非常好。他們對這些領域的含義或暗示沒有任何假設,並且很樂意接受任何東西。因此可以這樣做:
mysma = backtrader.indicators.SMA(self.data.bid, period=5)
最後5 個投標價格的移動平均線將被交付
測試腳本已經支持添加 SMA。讓我們執行:
$ ./bidask.py --sma --period=3
輸出:
3: 2010-02-03T16:53:52 - Bid 0.5543 - 0.5545 Ask - SMA: 0.5411 4: 2010-02-03T16:53:53 - Bid 0.5342 - 0.5344 Ask - SMA: 0.5409 5: 2010-02-03T16:53:54 - Bid 0.5245 - 0.5464 Ask - SMA: 0.5377 6: 2010-02-03T16:53:54 - Bid 0.5460 - 0.5470 Ask - SMA: 0.5349 7: 2010-02-03T16:53:56 - Bid 0.5824 - 0.5826 Ask - SMA: 0.5510 8: 2010-02-03T16:53:57 - Bid 0.5371 - 0.5374 Ask - SMA: 0.5552 9: 2010-02-03T16:53:58 - Bid 0.5793 - 0.5794 Ask - SMA: 0.5663 10: 2010-02-03T16:53:59 - Bid 0.5684 - 0.5688 Ask - SMA: 0.5616
筆記
繪圖仍然依賴於數據饋送中存在的open
、 high
價、 low
、 close
和volume
。
某些情況可以通過簡單地在關閉時繪製一條線並僅採用對像中定義的第一條線來直接覆蓋。但是必須開發一個健全的模型。對於即將發布的backtrader
版本
測試腳本用法:
$ ./bidask.py --help usage: bidask.py [-h] [--data DATA] [--dtformat DTFORMAT] [--sma] [--period PERIOD] Bid/Ask Line Hierarchy optional arguments: -h, --help show this help message and exit --data DATA, -d DATA data to add to the system (default: ../../datas/bidask.csv) --dtformat DTFORMAT, -dt DTFORMAT Format of datetime in input (default: %m/%d/%Y %H:%M:%S) --sma, -s Add an SMA to the mix (default: False) --period PERIOD, -p PERIOD Period for the sma (default: 5)
以及測試腳本本身(包含在backtrader
資源中)
from __future__ import (absolute_import, division, print_function, unicode_literals) import argparse import backtrader as bt import backtrader.feeds as btfeeds import backtrader.indicators as btind class BidAskCSV(btfeeds.GenericCSVData): linesoverride = True # discard usual OHLC structure # datetime must be present and last lines = ('bid', 'ask', 'datetime') # datetime (always 1st) and then the desired order for params = ( # (datetime, 0), # inherited from parent class ('bid', 1), # default field pos 1 ('ask', 2), # default field pos 2 ) class St(bt.Strategy): params = (('sma', False), ('period', 3)) def __init__(self): if self.p.sma: self.sma = btind.SMA(self.data, period=self.p.period) def next(self): dtstr = self.data.datetime.datetime().isoformat() txt = '%4d: %s - Bid %.4f - %.4f Ask' % ( (len(self), dtstr, self.data.bid[0], self.data.ask[0])) if self.p.sma: txt += ' - SMA: %.4f' % self.sma[0] print(txt) def parse_args(): parser = argparse.ArgumentParser( description='Bid/Ask Line Hierarchy', formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument('--data', '-d', action='store', required=False, default='../../datas/bidask.csv', help='data to add to the system') parser.add_argument('--dtformat', '-dt', required=False, default='%m/%d/%Y %H:%M:%S', help='Format of datetime in input') parser.add_argument('--sma', '-s', action='store_true', required=False, help='Add an SMA to the mix') parser.add_argument('--period', '-p', action='store', required=False, default=5, type=int, help='Period for the sma') return parser.parse_args() def runstrategy(): args = parse_args() cerebro = bt.Cerebro() # Create a cerebro data = BidAskCSV(dataname=args.data, dtformat=args.dtformat) cerebro.adddata(data) # Add the 1st data to cerebro # Add the strategy to cerebro cerebro.addstrategy(St, sma=args.sma, period=args.period) cerebro.run() if __name__ == '__main__': runstrategy()