Backtrader中文笔记之 Feed Data - Filters
Filters
This functionality is a relatively late addition to backtrader and had to be fitted to the already existing internals. This makes it to be not as flexible and 100% feature full as wished, but it can still serve the purpose in many cases.
这个功能是对backtrader的一个相对较晚的添加,现在已经安装到内部。这使得它不能像希望的那样灵活和100%的功能,但在许多情况下它仍然可以达到目的。
Although the implementation tried to allow plug and play filter chaining, the pre-existing internals made it difficult to ensure that could always be achieved. As such, some filters may be chained and some others may not.
尽管该实现尝试允许即插即用过滤器链接,但由于预先存在的内部结构,很难确保始终能够实现这一点。因此,一些过滤器可能是连锁的,而另一些则不是。
Purpose
目的
- Transform the values provided by a data feed to deliver a different data feed
- 数据输出改变了值,再交付给不同的数据传输
The implementation was started to simplify the implementation of the two obvious filters which can be directly used via the cerebro API. These are:
开始实现是为了简化两个明显的过滤器的实现,这两个过滤器可以通过cerebroAPI直接使用。这些是:
-
Resampling (
cerebro.resampledata
)Here the filter transforms the
timeframe
andcompression
of the incoming data feed. For example: - 在这里,过滤器转换传入数据源的时间框架和压缩。例如:
-
(Seconds, 1) -> (Days, 1)
That means that the original data feed is delivery bars with a resolution of 1 Second. The Resampling filter intercepts the data and buffers it until it can deliver a 1 Day bar. This will happen when a 1 Second bar from the next day is seen.
- 这意味着原始数据馈送是分辨率为1秒的bars。重采样过滤器截取数据并对其进行缓冲,直到它可以提供1天的数据条。这将发生在第二天的1秒条出现时。
-
Replaying (
cerebro.replaydata
)For the same timeframes as above, the filter would use the 1 Second resolution bars to rebuild the 1 Day bar.
- 对于与上述相同的时间段,过滤器将使用1秒分辨率条重建1天条形图。
-
That means that the 1 Day bar is delivered as many times as 1 Second bars are seen, updated to contain the latest information.
- 这意味着,1天的条形图与1秒的条形图的交付,显示的次数一样多,更新后包含最新的信息。
-
This simulates, for example, how an actual trading day has developed.
- 例如,这模拟了实际交易日的发展情况
-
-
-
-
Note
The length of the data,
len(data)
and therefore the length of the strategy remain unchanged as long as the day doesn’t change.数据的长度,len(数据)因此策略的长度保持不变,只要一天不变。
Filters at work
Given an existing data feed/source you use the
addfilter
method of the data feed:给定一个现有的数据源/源,您可以使用数据源的addfilter方法:
data = MyDataFeed(dataname=myname) data.addfilter(filter, *args, **kwargs) cerebro.addata(data)
And even if it happens to be compatible to the resample/replay filter the following can also be done:
甚至它可以兼容resample/replay过滤,以下可以这么操作:
data = MyDataFeed(dataname=myname) data.addfilter(filter, *args, **kwargs) cerebro.replaydata(data)
Filter Interface
A
filter
must conform to a given interface, being this:filter必须符合以下接口定义
A callable which accepts this signature:
一个可调用对象,它接受该签名(我觉的是函数返回一个可调用的函数)
callable(data, *args, **kwargs)
或者
A class which can be instantiated and called
一个类能被实例,实例能被调用
During instantiation the
__init__
method must support the signature:def __init__(self, data, *args, **kwargs)
The
__call__
method bears this signature:def __call__(self, data, *args, **kwargs)
The instance will be called for each new incoming values from the data feed. The
\*args
and\*kwargs
are the same passed to__init__
实例将操作从数据传输进来的每一个新的值。
\*args
和\*kwargs将传递给__init__
RETURN VALUES:
* `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 True 数据源内部数据的获取重试读取数据,因为流的长度还可以操作 * `False` even if data may have been edited (example: changed `close` price), the length of the stream has remain untouched
False 即使数据可能已经被编辑,流的长度保持不变In the case of a class based filter 2 additional methods can be implemented
添加了last方法
last
with the following signature:
def last(self, data, *args, **kwargs)
This will be called when the data feed is over, allowing the filter to deliver data it may have for example buffered. A typical case is resampling, because a bar is buffered until data from the next time period is seen. When the data feed is over, there is no new data to push the buffered data out.
这将在数据传输结束时调用,允许筛选器发送可能已缓存的数据。一种典型的情况是重新采样,因为一个数据条被缓冲,直到看到下一个时间段的数据。当数据提要结束时,没有新数据将缓冲数据推出。
last
offers the chance to push the buffered data out.last提供了将缓冲数据推出的机会。
Note
It is obvious that if the filter supports no arguments at all and will be added without any, the signatures can be simplified as in:
很明显,如果过滤器不支持任何参数,并且添加时不支持任何参数,签名可以简化为:
def __init__(self, data, *args, **kwargs) -> def __init__(self, data)
A Sample Filter
A very quick filter implementation:
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
This filter:
-
Uses
data.p.sessionstart
anddata.p.sessionend
(standard data feed parameters) to decide if a bar is in the session. - 使用data.p.sessionstart和data.p.sessionend(标准数据提要参数)来决定会话中是否有bar。
-
If in-the-session the return value is
False
to indicate nothing was done and the processing of the current bar can continue - 如果在会话中返回值为False,表示没有执行任何操作,并且可以继续处理当前栏
-
If not-in-the-session, the bar is removed from the stream and
True
is returned to indicate a new bar must be fetched. - 如果不在会话中,则从流中删除bar,并返回True,表示必须获取新的bar。
Note
The
data.backwards()
makes uses of theLineBuffer
interface. This digs deep into the internals of backtrader.backward()使用了LineBuffer接口。这就深入到了backtrader的内部。
The use of this filter:
- Some data feeds contain out of regular trading hours data, which may not be of interest to the trader. With this filter only in-session bars will be considered.
一些数据源超过出了正常交易时间的数据,交易员可能对这些数据不感兴趣。使用此过滤器时,只考虑会话内的条。
Data Pseudo-API for Filters
过滤器的数据伪api
In the example above it has been shown how the filter invokes
data.backwards()
to remove the current bar from the stream. Useful calls from the data feed objects which are meant as a pseudo-API for Filters are:在上面的示例中,已经展示了过滤器如何调用data.backwards()来从流中删除当前的工具条。来自datafeed对象的有用调用,这些过滤器的伪api是:
-
data.backwards(size=1, force=False)
: removes size bars from the data stream (default is1
) by moving the logical pointer backwards. Ifforce=True
, then the physical storage is also removed. data.backwards
(size=1,force=False):通过向后移动逻辑指针从数据流中删除大小条(默认值为1)。如果force=True,那么物理存储也会被移除。-
Removing the physical storage is a delicate operation and is only meant as a hack for internal operations.
- 删除物理存储是一个微妙的操作,它只意味着对内部操作的黑客攻击。
-
data.forward(value=float('NaN'), size=1)
: moves size bars the storage forward, increasing the physical storage if needed be and fills withvalue
data.
forward(value=float('NaN'), size=1):将存储的bar向前移动size,如果需要的话增加物理存储并填充值-
data._addtostack(bar, stash=False)
: addsbar
to a stack for later processing.bar
is an iterable containing as many values aslines
has the data feed. data.
_addtostack(bar, stash=False):将bar添加到堆栈中,以便稍后处理。bar是一个迭代器,它包含的值与数据传输的lines一样多。-
If
stash=False
the bar added to the stack will be consumed immediately by the system at the beginning of the next iteration. - 如果stash=False,系统将在下一次迭代开始时立即使用添加到堆栈中的条。
-
If
stash=True
the bar will undergo the entire loop processing including potentially being reparsed by filters - 如果stash=True,条将经历整个循环处理,包括可能被过滤器重新解析
-
data._save2stack(erase=False, force=False)
: saves the current data bar to the stack for later processing. Iferase=True
thendata.backwards
will be invoked and will receive the parameterforce
data.
_save2stack(erase=False, force=False):将当前的数据bar保存到堆栈中以供以后处理。如果erase=True
,则data.backwards
将被调用,并将接收参数force-
data._updatebar(bar, forward=False, ago=0)
: uses the values in the iterablebar
to overwrite the values in the data streamago
positions. With the defaultago=0
the current bar will updated. With-1
, the previous one. data._updatebar
(bar, forward=False, ago=0):使用可迭代条中的bar重写数据流中ago位置的值。在默认ago=0的情况下,当前栏将被更新。-1是之前的那个。
Another example: Pinkfish Filter
This is an example of a filter that can be chained, and is meant so, to another filter, namely the replay filter. The Pinkfish name is from the library which describes the idea in its main page: using daily data to execute operations which would only be possible with intraday data.
这是一个过滤器的例子,它可以链接到另一个过滤器,即重放过滤器。Pinkfish的名字来自于这个图书馆,它在主页上描述了这个想法:使用每日数据来执行操作,而这些操作只有在当天的数据中才能实现。
(废话一堆,就就要修改当天的数据)
To achieve the effect:
达到以下效果:
-
A daily bar will be broken in 2 componentes:
OHL
and thenC
. - 每天的日bar会被分成两部分:OHL和C。
-
Those 2 pieces are chained with replay to have the following happening in the stream:
这两个片段在replay链流中将发生以下事情。
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 ...
Logic:
-
When an
OHLC
bar is received it is copied into an interable and broken down to become: - 当一个OHLC的bar被接收,它被分解并赋值到一个可迭代对象
-
-
An
OHL
bar. Because this concept doesn’t actually exist the closing price is replaced with the opening price to really form anOHLO
bar. - OHLbar,因为这个实际上并不存在,收盘价被开盘价取代,形成了一个OHLObar
-
An
C
bar whic also doesn’t exist. The reality is that it will be delivered like a tickCCCC
- 一个Cbar也不存在。事实上,它将交付成CCCC的样子
-
The volume if distributed between the 2 parts
- 卷分布在两部分
-
The current bar is removed from the stream
- 将当前的bar从流中删除
-
The
OHLO
part is put onto the stack for immediate processing - 将OHLO部分放入堆栈中以便立即处理
-
The
CCCC
part is put into the stash for processing in the next round - CCCC部分放入储物箱进行下一轮加工
-
Because the stack has something for immediate processing the filter can return
False
to indicate it. - 因为堆栈有即时处理的内容,所以过滤器可以返回False来表示。
-
This filter works together with:
- The replay filter which puts together the
OHLO
andCCCC
parts to finally deliver anOHLC
bar. - replay过滤器将OHLO和CCCC部分放在一起,最终交付OHLC棒。
The use case:
- Seeing something like if the maximum today is the highest maximum in the last 20 sessions an issuing a
Close
order which gets executed with the 2nd tick. - 如果今天的最大值是过去20个交易日的最大值,那么就会在第2个点执行一个收盘指令。
The Code:
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
# 为该线在lines里面的索引 ohlprice = ohlbar[data.Open] + ohlbar[data.High] + ohlbar[data.Low]
# 重新定义ohl的Closebar的信息 ohlbar[data.Close] = ohlprice / 3.0 # oldbar的volume调整 vol = ohlbar[data.Volume] # adjust volume ohlbar[data.Volume] = vohl = int(vol * (1.0 - self.p.closevol)) # 调整OpenInterest 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
-
-
-
-