数据供稿 - 过滤器
过滤器
此功能是相对较晚添加到backtrader中的,并且必须适应已经存在的内部结构。这使得它不像希望的那样灵活和 100%功能齐全,但在许多情况下仍然可以实现目的。
尽管实现尝试允许即插即用的过滤器链接,但是现有的内部结构使得很难确保总是可以实现。因此,一些过滤器可能被链接,而另一些则可能没有。
目的
- 转换由数据源提供的值以提供不同的数据源
实现是为了简化可以通过cerebro API 直接使用的两个明显过滤器的实现。这些是:
- 重新采样(
cerebro.resampledata
)
在这里,过滤器转换了传入数据源的时间框架
和压缩
。例如:
(Seconds, 1) -> (Days, 1)`
- 这意味着原始数据提供的是分辨率为1 秒的柱状图。 重新采样过滤器拦截数据并将其缓冲,直到可以提供1 天柱状图。当看到下一天的1 秒柱状图时,将会发生这种情况。
- 重播(
cerebro.replaydata
)
对于上述相同的时间段,该过滤器将使用1 秒分辨率的柱状图来重建1 天柱状图。
这意味着1 天柱状图会被提供与看到的1 秒柱状图一样多次,并更新以包含最新信息。
例如,这模拟了实际交易日的发展方式。
注意
数据的长度,len(data)
以及策略的长度在天不变的情况下保持不变。
过滤器工作中
给定现有的数据源,您可以使用数据源的addfilter
方法:
data = MyDataFeed(dataname=myname) data.addfilter(filter, *args, **kwargs) cerebro.addata(data)
即使它恰好与重新采样/重播过滤器兼容,也可以执行以下操作:
data = MyDataFeed(dataname=myname) data.addfilter(filter, *args, **kwargs) cerebro.replaydata(data)
过滤器接口
一个filter
必须符合给定的接口,即:
- 一个可调用的函数,接受此签名:
callable(data, *args, **kwargs)`
或者
- 一个可以实例化和调用的类
- 在实例化期间,
__init__
方法必须支持此签名:
def __init__(self, data, *args, **kwargs)`
__call__
方法具有以下签名:
def __call__(self, data, *args, **kwargs)`
- 对于来自数据源的每个新输入值,将调用该实例。
\*args
和\*kwargs
与__init__
传递的相同。
返回值:
* `True`: the inner data fetching loop of the data feed must retry fetching data from the feed, becaue the length of the stream was manipulated * `False` even if data may have been edited (example: changed `close` price), the length of the stream has remain untouched`
- 在基于类的过滤器的情况下,可以实现 2 个附加方法
- 具有以下签名的
last
:
def last(self, data, *args, **kwargs)`
- 当数据源结束时,将调用此方法,允许过滤器传递它可能已经缓冲的数据。一个典型的情况是重新采样,因为柱状图被缓冲,直到看到下一个时间段的数据。当数据源结束时,没有新数据来推送缓冲的数据出去。
last
提供了将缓冲数据推送出去的机会。
注意
很明显,如果过滤器根本不支持任何参数,并且将添加而无任何参数,则签名可以简化为:
def __init__(self, data, *args, **kwargs) -> def __init__(self, data)
一个示例过滤器
一个非常快速的过滤器实现:
class SessionFilter(object): def __init__(self, data): pass def __call__(self, data): if data.p.sessionstart <= data.datetime.time() <= data.p.sessionend: # bar is in the session return False # tell outer data loop the bar can be processed # bar outside of the regular session times data.backwards() # remove bar from data stack return True # tell outer data loop to fetch a new bar
这个过滤器:
- 使用
data.p.sessionstart
和data.p.sessionend
(标准数据源参数)来判断条是否在会话中。 - 如果在会话中,返回值为
False
,以指示未执行任何操作,可以继续处理当前条目 - 如果不在会话中,则从流中移除该条,并返回
True
以指示必须获取新条。
注意data.backwards()
利用LineBuffer
接口。 这深入到backtrader的内部。
这种过滤器的用法:
- 一些数据源包含非常规交易时间外的数据,这些数据可能不受交易者的关注。 使用此过滤器仅将会话内条考虑在内。
用于过滤器的数据伪-API
在上述示例中,已经展示了过滤器如何调用data.backwards()
以从流中移除当前条。 数据源对象的有用调用,旨在作为过滤器的伪-API:
data.backwards(size=1, force=False)
:通过将逻辑指针向后移动来从数据流中删除size条(默认为1
)。 如果force=True
,则物理存储也将被删除。
移除物理存储是一项细致的操作,仅用作内部操作的黑客方法。data.forward(value=float('NaN'), size=1)
:将size条移动到存储区向前移动,必要时增加物理存储并用value
填充。data._addtostack(bar, stash=False)
:将bar
添加到堆栈以供以后处理。bar
是一个包含与数据源的lines
一样多的值的可迭代对象。
如果stash=False
,则添加到堆栈的条将立即被系统消耗,在下一次迭代开始时。
如果stash=True
,则条将经历整个循环处理,包括可能被过滤器重新解析data._save2stack(erase=False, force=False)
:将当前数据条保存到堆栈以供以后处理。 如果erase=True
,则将调用data.backwards
并将接收参数force
。data._updatebar(bar, forward=False, ago=0)
:使用可迭代对象bar
中的值来覆盖数据流中ago
位置的值。 默认情况下,ago=0
将更新当前条。 如果是-1
,则是前一个。
另一个示例:粉鱼过滤器
这是一个可以链接的过滤器的示例,并且旨在如此,以连接到另一个过滤器,即replay 过滤器。 Pinkfish名称来自于其主页描述该想法的库:使用每日数据执行仅在分时数据中才可能的操作。
要实现效果:
- 每日条将分解为 2 个组件:
OHL
然后是C
。 - 这两个部分通过重播链接以在流中发生以下情况:
With Len X -> OHL With Len X -> OHLC With Len X + 1 -> OHL With Len X + 1 -> OHLC With Len X + 2 -> OHL With Len X + 2 -> OHLC ...`
逻辑:
- 收到
OHLC
条时,将其复制到一个可迭代对象中,并分解为以下内容:
- 一个
OHL
条。 由于这个概念实际上并不存在,收盘价格被替换为开盘价格以真正形成一个OHLO
条。 - 一个不存在的
C
条,实际上会被传递为一个刻度CCCC
- 成交量在这两部分之间分配
- 当前条被从流中移除
OHLO
部分被放入堆栈中进行即时处理CCCC
部分被放入存储区,在下一轮处理中处理- 因为堆栈中有一些需要立即处理的内容,过滤器可以返回
False
来指示这一点。
这个过滤器与以下内容一起工作:
- 重播 过滤器将
OHLO
和CCCC
部分组合在一起,最终生成一个OHLC
条。
使用案例:
- 看到像是如果今天的最大值是过去 20 个交易日中的最高最大值,就发出一个
Close
订单,该订单在第 2 个刻度执行。
代码:
class DaySplitter_Close(bt.with_metaclass(bt.MetaParams, object)): ''' Splits a daily bar in two parts simulating 2 ticks which will be used to replay the data: - First tick: ``OHLX`` The ``Close`` will be replaced by the *average* of ``Open``, ``High`` and ``Low`` The session opening time is used for this tick and - Second tick: ``CCCC`` The ``Close`` price will be used for the four components of the price The session closing time is used for this tick The volume will be split amongst the 2 ticks using the parameters: - ``closevol`` (default: ``0.5``) The value indicate which percentage, in absolute terms from 0.0 to 1.0, has to be assigned to the *closing* tick. The rest will be assigned to the ``OHLX`` tick. **This filter is meant to be used together with** ``cerebro.replaydata`` ''' params = ( ('closevol', 0.5), # 0 -> 1 amount of volume to keep for close ) # replaying = True def __init__(self, data): self.lastdt = None def __call__(self, data): # Make a copy of the new bar and remove it from stream datadt = data.datetime.date() # keep the date if self.lastdt == datadt: return False # skip bars that come again in the filter self.lastdt = datadt # keep ref to last seen bar # Make a copy of current data for ohlbar ohlbar = [data.lines[i][0] for i in range(data.size())] closebar = ohlbar[:] # Make a copy for the close # replace close price with o-h-l average ohlprice = ohlbar[data.Open] + ohlbar[data.High] + ohlbar[data.Low] ohlbar[data.Close] = ohlprice / 3.0 vol = ohlbar[data.Volume] # adjust volume ohlbar[data.Volume] = vohl = int(vol * (1.0 - self.p.closevol)) oi = ohlbar[data.OpenInterest] # adjust open interst ohlbar[data.OpenInterest] = 0 # Adjust times dt = datetime.datetime.combine(datadt, data.p.sessionstart) ohlbar[data.DateTime] = data.date2num(dt) # Ajust closebar to generate a single tick -> close price closebar[data.Open] = cprice = closebar[data.Close] closebar[data.High] = cprice closebar[data.Low] = cprice closebar[data.Volume] = vol - vohl ohlbar[data.OpenInterest] = oi # Adjust times dt = datetime.datetime.combine(datadt, data.p.sessionend) closebar[data.DateTime] = data.date2num(dt) # Update stream data.backwards(force=True) # remove the copied bar from stream data._add2stack(ohlbar) # add ohlbar to stack # Add 2nd part to stash to delay processing to next round data._add2stack(closebar, stash=True) return False # initial tick can be further processed from stack
Filters Reference
SessionFilter
class backtrader.filters.SessionFilter(data)
此类可以作为过滤器应用于数据源,并将过滤掉超出常规会话时间的盘中 bar(即:盘前/盘后市场数据)
这是一个“非简单”的过滤器,必须管理数据的堆栈(在初始化和 call 期间传递)
它不需要“last”方法,因为它没有需要传递的内容
SessionFilterSimple
class backtrader.filters.SessionFilterSimple(data)
此类可以作为过滤器应用于数据源,并将过滤掉超出常规会话时间的盘中 bar(即:盘前/盘后市场数据)
这是一个“简单”过滤器,不必管理数据的堆栈(在初始化和 call 期间传递)
它不需要“last”方法,因为它没有需要传递的内容
Bar 管理将由 SimpleFilterWrapper 类完成,该类在 DataBase.addfilter_simple 调用时添加
SessionFilller
class backtrader.filters.SessionFiller(data)
声明会话开始/结束时间内的数据源的 Bar Filler。
填充 bar 是使用声明的 Data Source timeframe
和 compression
构建的(用于计算中间缺失的时间)
参数:
- fill_price (def: None):
如果传递了 None,则将使用前一个 bar 的收盘价。例如,要得到一个 bar,该 bar 需要时间,但不会在图表中显示… 使用 float(‘Nan’) - fill_vol (def: float(‘NaN’)):
用于填充缺失的 volume 的值 - fill_oi (def: float(‘NaN’)):
用于填充缺失的持仓量的值 - skip_first_fill (def: True):
在看到第一个有效 bar 时,不要从 sessionstart 填充到该 bar
CalendarDays
class backtrader.filters.CalendarDays(data)
在交易日中添加缺失的日历天的 Bar Filler
参数:
- fill_price (def: None):
0:用于填充 0 或 None 的给定值:使用最后已知的收盘价 -1:使用最后一个 bar 的中点(High-Low 平均值)
- fill_vol (def: float(‘NaN’)):
用于填充缺失的 volume 的值 - fill_oi (def: float(‘NaN’)):
用于填充缺失的持仓量的值
BarReplayer_Open
class backtrader.filters.BarReplayer_Open(data)
此过滤器将一个 bar 拆分为两部分:
Open
:将使用 bar 的开盘价提供一个初始价格 bar,在该 bar 中,四个组件(OHLC)相等
该初始 bar 的 volume/openinterest 字段为 0OHLC
:原始 bar 包含原始的volume
/openinterest
拆分模拟了一次回放,无需使用 replay 过滤器。
DaySplitter_Close
class backtrader.filters.DaySplitter_Close(data)
将每日 bar 拆分为两部分,模拟用于重放数据的 2 个 tick:
- 第一个 tick:
OHLX
Close
将被替换为Open
、High
和Low
的 平均值
此 tick 使用会话开放时间
和
- 第二个 tick:
CCCC
Close
价格将用于价格的四个组成部分
会话结束时间用于此点
体积将根据以下参数分配给 2 个点:
closevol
(默认:0.5
)该值表示从 0.0 到 1.0 的绝对比例,应分配给收盘点。其余将分配给OHLX
点。
此过滤器意在与 cerebro.replaydata
一同使用
平均趋势烛(HeikinAshi)
类 backtrader.filters.HeikinAshi(data)
此过滤器重新构建开盘价、最高价、最低价、收盘价以绘制平均趋势烛
参见:
* [`en.wikipedia.org/wiki/Candlestick_chart#Heikin_Ashi_candlesticks`](https://en.wikipedia.org/wiki/Candlestick_chart#Heikin_Ashi_candlesticks) * [`stockcharts.com/school/doku.php?id=chart_school:chart_analysis:heikin_ashi`](http://stockcharts.com/school/doku.php?id=chart_school:chart_analysis:heikin_ashi)
砖形图(Renko)
类 backtrader.filters.Renko(data)
修改数据流以绘制砖形图(或砖块)
参数:
hilo
(默认:False)使用最高价和最低价而不是收盘价来决定是否需要新砖size
(默认:None)每个砖的考虑大小autosize
(默认:20.0)如果size为None,则将用于自动计算砖的大小(简单地将当前价格除以给定值)dynamic
(默认:False)如果True并使用autosize,则移至新砖时将重新计算砖的大小。这当然会消除砖的完美对齐。align
(默认:1.0)用于对齐砖的价格边界的因子。例如,如果价格为3563.25,而align为10.0,则得到的对齐价格将为3560。计算方式:
- 3563.25 / 10.0 = 356.325
- 四舍五入并去除小数 -> 356
- 356 * 10.0 -> 3560
参见:
* [`stockcharts.com/school/doku.php?id=chart_school:chart_analysis:renko`](http://stockcharts.com/school/doku.php?id=chart_school:chart_analysis:renko)
雅虎数据源注意事项
2017 年 5 月,雅虎停止了用于历史数据下载的现有 csv 格式的 API。
一个新的 API(此处命名为 v7
)迅速被标准化并已实施。
这也带来了实际 CSV 下载格式的更改。
使用 v7 API/格式
从版本 1.9.49.116
开始,这是默认行为。只需简单选择
YahooFinanceData
用于在线下载YahooFinanceCSVData
用于离线下载的文件
使用传统的 API/格式
要使用旧的 API/格式
- 将在线雅虎数据源实例化为:
data = bt.feeds.YahooFinanceData( ... version='', ... )`
- 离线雅虎数据源为:
data = bt.feeds.YahooFinanceCSVData( ... version='', ... )`
- 可能在线服务会回来(该服务没有任何公告地停止了… 也有可能会回来)
或者
- 仅对在更改发生之前下载的离线文件,也可以进行以下操作:
data = bt.feeds.YahooLegacyCSV( ... ... )`
- 新的
YahooLegacyCSV
简单地使用version=''
进行自动化。
Pandas 数据源示例
注意
必须安装 pandas
及其依赖项
支持 Pandas Dataframes 似乎是许多人关注的问题,他们依赖于已经可用的用于不同数据源(包括 CSV)的解析代码以及 Pandas 提供的其他功能。
数据源的重要声明。
注意
这些只是声明。不要盲目复制此代码。请参见下面示例中的实际用法
class PandasData(feed.DataBase): ''' The ``dataname`` parameter inherited from ``feed.DataBase`` is the pandas DataFrame ''' params = ( # Possible values for datetime (must always be present) # None : datetime is the "index" in the Pandas Dataframe # -1 : autodetect position or case-wise equal name # >= 0 : numeric index to the colum in the pandas dataframe # string : column name (as index) in the pandas dataframe ('datetime', None), # Possible values below: # None : column not present # -1 : autodetect position or case-wise equal name # >= 0 : numeric index to the colum in the pandas dataframe # string : column name (as index) in the pandas dataframe ('open', -1), ('high', -1), ('low', -1), ('close', -1), ('volume', -1), ('openinterest', -1), )
上述从 PandasData
类中摘录的片段显示了键:
- 实例化期间传递给类的
dataname
参数保存了 Pandas Dataframe
此参数从基类feed.DataBase
继承 - 新参数使用
DataSeries
中常规字段的名称,并遵循以下约定
datetime
(默认值:无)- None:datetime 是 Pandas Dataframe 中的“索引”
- -1:自动检测位置或大小写相等的名称
= 0:对应 pandas 数据帧中的列的数字索引
- string:pandas 数据帧中的列名(作为索引)
open
,high
,low
,high
,close
,volume
,openinterest
(默认值:全部为 -1)- None:列不存在
- -1:自动检测位置或大小写相等的名称
= 0:对应 pandas 数据帧中的列的数字索引
- string:pandas 数据帧中的列名(作为索引)
一个小示例应该能够加载标准的 2006 示例,已由 Pandas
解析,而不是直接由 backtrader
解析
运行示例以使用 CSV 数据中的现有“标题”:
$ ./panda-test.py -------------------------------------------------- Open High Low Close Volume OpenInterest Date 2006-01-02 3578.73 3605.95 3578.73 3604.33 0 0 2006-01-03 3604.08 3638.42 3601.84 3614.34 0 0 2006-01-04 3615.23 3652.46 3615.23 3652.46 0 0
相同但告诉脚本跳过标题:
$ ./panda-test.py --noheaders -------------------------------------------------- 1 2 3 4 5 6 0 2006-01-02 3578.73 3605.95 3578.73 3604.33 0 0 2006-01-03 3604.08 3638.42 3601.84 3614.34 0 0 2006-01-04 3615.23 3652.46 3615.23 3652.46 0 0
第 2 次运行使用的是 tells pandas.read_csv
:
- 跳过第一行输入(将
skiprows
关键字参数设置为 1) - 不查找标题行(将
header
关键字参数设置为 None)
backtrader
对 Pandas 的支持尝试自动检测是否使用了列名,否则使用数字索引,并相应地采取行动,尝试提供最佳匹配。
下图是对成功的致敬。Pandas Dataframe 已正确加载(在两种情况下)
测试的示例代码。
from __future__ import (absolute_import, division, print_function, unicode_literals) import argparse import backtrader as bt import backtrader.feeds as btfeeds import pandas def runstrat(): args = parse_args() # Create a cerebro entity cerebro = bt.Cerebro(stdstats=False) # Add a strategy cerebro.addstrategy(bt.Strategy) # Get a pandas dataframe datapath = ('../../datas/2006-day-001.txt') # Simulate the header row isn't there if noheaders requested skiprows = 1 if args.noheaders else 0 header = None if args.noheaders else 0 dataframe = pandas.read_csv(datapath, skiprows=skiprows, header=header, parse_dates=True, index_col=0) if not args.noprint: print('--------------------------------------------------') print(dataframe) print('--------------------------------------------------') # Pass it to the backtrader datafeed and add it to the cerebro data = bt.feeds.PandasData(dataname=dataframe) cerebro.adddata(data) # Run over everything cerebro.run() # Plot the result cerebro.plot(style='bar') def parse_args(): parser = argparse.ArgumentParser( description='Pandas test script') parser.add_argument('--noheaders', action='store_true', default=False, required=False, help='Do not use header rows') parser.add_argument('--noprint', action='store_true', default=False, help='Print the dataframe') return parser.parse_args() if __name__ == '__main__': runstrat()
数据源参考
AbstractDataBase
行:
* close * low * high * open * volume * openinterest * datetime
参数:
* dataname (None) * name () * compression (1) * timeframe (5) * fromdate (None) * todate (None) * sessionstart (None) * sessionend (None) * filters ([]) * tz (None) * tzinput (None) * qcheck (0.0) * calendar (None)
BackTrader 中文文档(四)(2)https://developer.aliyun.com/article/1489242