过滤器
此功能是对backtrader的较新添加,必须安装到现有内部。这使得它不是如所希望的那样灵活和 100%功能齐全,但在许多情况下它仍然可以达到目的。
尽管该实现试图允许即插即用过滤器链接,但现有的内部结构使其难以确保始终能够实现。因此,一些过滤器可能被链接,而另一些过滤器可能没有链接。
意图
- 转换数据馈送提供的值,以传递不同的数据馈送
开始实现是为了简化两个明显过滤器的实现,这两个过滤器可以通过大脑API 直接使用。这些是:
-
重采样
cerebro.resampledata
这里,过滤器转换传入的数据馈送的
timeframe
和compression
。例如:py (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
必须符合给定的接口,即:
-
接受此签名的可调用函数:
py callable(data, *args, **kwargs)
或
-
一个可以实例化和的类,称为
- 实例化时,
__init__
方法必须支持签名:
py def __init__(self, data, *args, **kwargs)
__call__
方法具有以下签名:
py def __call__(self, data, *args, **kwargs)
对于来自数据馈送的每个新传入值,都将调用该实例。
\*args
和\*kwargs
传递给__init__
的内容相同返回值:
``py *
True`: the inner data fetching loop of the data feed must retry fetching data from the feed, becaue the length of the stream was manipulatedFalse
even if data may have been edited (example: changedclose
price), the length of the stream has remain untouched ```
对于基于类的过滤器,可以实现 2 个附加方法
last
签字如下:
py 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
接口。这深入挖掘了反向交易者的内部。
此过滤器的使用:
- 一些数据源包含超出正常交易时间的数据,交易员可能对此不感兴趣。使用此过滤器时,只考虑会话中的条。
用于过滤器的数据伪 API
在上面的示例中,已经展示了过滤器如何调用data.backwards()
从流中删除当前条。来自数据馈送对象的有用调用是作为过滤器的伪 API 的:
-
data.backwards(size=1, force=False)
:通过向后移动逻辑指针,从数据流中删除大小条(默认为1
)。如果为force=True
,则物理存储也会被移除。移除物理存储是一项微妙的操作,只意味着对内部操作的攻击。
-
data.forward(value=float('NaN'), size=1)
:向前移动大小栏位,根据需要增加物理存储量,并填充value
-
data._addtostack(bar, stash=False)
:将bar
添加到堆栈中,以便后续处理。bar
是一个 iterable,包含的值与lines
的数据馈送值一样多。如果
stash=False
添加到堆栈中的条将在下一次迭代开始时立即被系统消耗。如果
stash=True
条将经历整个循环处理,包括可能被过滤器重新解析 -
data._save2stack(erase=False, force=False)
:将当前数据条保存到堆栈中,以便以后处理。如果是erase=True
,则调用data.backwards
并接收参数force
-
data._updatebar(bar, forward=False, ago=0)
:使用 iterablebar
中的值覆盖数据流ago
位置中的值。默认情况下ago=0
将更新当前栏。使用-1
,前一个。
另一个例子:粉红鱼过滤器
这是一个可以链接到另一个过滤器(即重播过滤器)的过滤器示例。粉红鱼的名字来自图书馆,它在主页上描述了这一想法:使用每日数据执行操作,而这只能通过日内数据实现。
要达到这一效果:
-
一根每日棒将被分成两部分:
OHL
和C
。 -
这两个片段通过重播链接,在流中发生以下事件:
py 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
条时,它被复制到一个互动表中,并被分解成:-
酒吧。因为这个概念实际上并不存在,收盘价被开盘价取代,真正形成一个
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