加载除高开低收OHLC之外的更多扩展数据 – backtrader中文教程
在 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 行。
不过,重新定义线条层次结构会引发一个广泛的问题,那就是已经预定义的Indicators的使用。
- 示例:随机指标是一个依靠收盘价、最高价 和最低价来计算其输出的指标
即使我们将Bid作为收盘价(因为是第一个),也只有一个其他价格元素(Ask)而不是另外两个。从概念上讲,Ask与高低无关
从事这些领域工作并在高频交易领域进行操作(或研究)的人很可能并不关心 随机指标作为选择指标
- 移动平均线等其他指标非常好。他们对这些领域的含义或暗示没有任何假设,并且很乐意接受任何东西。因此可以这样做:
mysma = backtrader.indicators.SMA(self.data.bid, period=5)
测试脚本已经支持添加SMA。让我们执行:
最后 5 个投标价格的移动平均线将被交付
$ ./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
Tips:绘图仍然依赖于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()
评论被关闭。