此功能是 backtrader 的相对较新的补充,必须安装到已经存在的内部结构中。这使得它不像希望的那样灵活且100%功能齐全,但在许多情况下它仍然可以达到目的。
尽管该实现试图允许随插即用的筛检程序链接,但预先存在的内部结构使得很难确保始终可以实现。因此,某些筛选器可能是链接的,而其他一些筛选器可能不是。
目的
- 转换 data feed 提供的值以提供不同的 data feed
该实现的开始是为了简化两个明显的筛检程序的实现,这两个筛检程序可以通过 cerebro API直接使用。这些是:
-
重采样 (
cerebro.resampledata)这个处,筛选器转换
timeframe传入data feed的与compression。例如:(Seconds, 1) -> (Days, 1)
这意味着原始 data feed 是分辨率为 1 秒的交割柱。重采样筛选器截获数据并对其进行缓冲,直到它可以提供 1 天柱。当看到第二天的 1 秒柱线时,就会发生这种情况。
-
重播 (
cerebro.replaydata)对于与上述相同的时间范围,筛检程序将使用1秒分辨率柱来重建1日柱。
这意味着 1 日柱的传递次数与看到 1 秒柱的次数一样多,并更新以包含最新信息。
例如,这模拟了实际交易日是如何形成的。
注意
数据的长度,
len(data)因此只要日期不改变,策略的长度就保持不变。
工作中的筛检程序
给定现有的 data feed/source,addfilter 您可以使用 data feed的方法:
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)
将为每个来自 data feed的新传入值调用该实例。和
\*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
在基于类的筛检程序的情况下,可以实现其他方法
last具有以下签名:
def last(self, data, *args, **kwargs)
这将在 data feed 结束时调用,允许筛选器提供它可能具有例如缓冲的数据。典型的情况是重新采样,因为柱线被缓冲,直到看到下一个时间段的数据。 data feed 结束后,没有新数据可以将缓冲数据推出。
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(标准 data feed 参数)来决定柱线是否在会话中。 -
如果在会话中,则返回值
False指示未运行任何操作,并且当前柱的处理可以继续 -
如果不在会话中,则该栏将从流中删除并
True返回以指示必须获取新栏。注意
接口
data.backwards()的LineBuffer用途。这深入挖掘了 backtrader的内部。
此过滤器的使用:
- 一些 data feeds 包含非正常交易时间数据,交易者可能对此不感兴趣。使用此筛选器时,将仅考虑会话中柱。
用于筛选器的数据伪 API
在上面的范例中,已显示筛选器如何调用data.backwards() 以从流中删除当前柱。来自 data feed 对象的有用调用(旨在作为过滤器的伪 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是一个可反复运算的可反复运算,其中包含与 data feed一样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,上一个。
另一个例子:粉红鱼筛检程序
这是一个可以链接到另一个筛检程序(即重播筛检程序)的过滤器的示例,并且意味着如此。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 -
如果分布在两个部分之间的volume
-
当前条形图已从流中删除
-
将
OHLO零件放入堆栈上进行即时处理 -
将
CCCC零件放入储藏室中,以便在下一轮中进行处理 -
由于堆栈具有可立即处理的内容,因此筛选器可以返回
False以指示它。
-
此筛选器可与以下各项配合使用:
- 重播筛检程序将 和
CCCC部分组合在一起OHLO,最终提供条OHLC形图。
用例:
- 看到这样的事情,如果今天的最大值是last20个会话中的最高最大值,则发出一个订单,该订单以第2个
Close价格变动运行。
代码:
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