此功能是 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