逃离 OHLC 土地
原文: https://www.backtrader.com/blog/posts/2016-03-08-escape-from-ohlc-land/escape-from-ohlc-land/
在 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
方法随时检查(适用于数据源、指标、策略和观察者)
该机制是灵活的,通过在内部插入一点,实际上你可以得到任何东西,但已经证明这是不够的。
票证询问是否支持高频数据,即:买卖数据。这意味着以OHLC形式预定义的行层次结构是不够的。出价和询问价格、交易量和交易数量可以根据现有的OHLC字段进行调整,但这并不自然。如果你只关心出价和询问价格,那么会有太多字段未被触及。
这需要一个解决方案,该解决方案已在版本 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
设置,层次结构实际上已经被替换为的bid,ask datetime定义。
原始的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。我们保留了基类中未触及的datetime0 定义。
为这种场合制作一个小数据文件有助于:
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
瞧!Bid/Ask价格已经正确阅读、解析和解释,策略已经能够通过self.data访问数据馈送中的.Bid和.Ask行。
然而,重新定义行层次结构带来了一个广泛的问题,即已经预定义的指标的使用。
-
示例:随机是一个依赖收盘、高和低价格计算其产量的指标
即使我们认为关于出价作为收盘(因为是第一个),也只有一个价格元素(询问,而不是两个。而概念上的提问与高和低无关
在这些领域工作并在高频交易领域进行操作(或研究)的人很可能与随机作为选择指标无关
-
其他指标如移动平均线都非常好。他们对字段的含义或暗示不作任何假设,并乐于接受任何东西。因此,我们可以:
py 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
。
某些情况下,只需在关闭的上绘制一条线,并在对象中仅取 1st定义的线即可直接覆盖。但必须建立一个健全的模式。对于即将发布的版本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()