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 and compression 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 and data.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 the LineBuffer 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 is 1) by moving the logical pointer backwards. If force=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 with value

            • data.forward(value=float('NaN'), size=1):将存储的bar向前移动size,如果需要的话增加物理存储并填充值
            • data._addtostack(bar, stash=False): adds bar to a stack for later processing. bar is an iterable containing as many values as lines 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. If erase=True then data.backwards will be invoked and will receive the parameter force

            • data._save2stack(erase=False, force=False):将当前的数据bar保存到堆栈中以供以后处理。如果erase=True,则data.backwards将被调用,并将接收参数force
            • data._updatebar(bar, forward=False, ago=0): uses the values in the iterable bar to overwrite the values in the data stream ago positions. With the default ago=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 then C.

            • 每天的日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 an OHLO bar.

              • OHLbar,因为这个实际上并不存在,收盘价被开盘价取代,形成了一个OHLObar
              • An C bar whic also doesn’t exist. The reality is that it will be delivered like a tick CCCC

              • 一个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 and CCCC parts to finally deliver an OHLC 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

             

             

             

             

posted @ 2020-08-12 18:03  就是想学习  阅读(1180)  评论(0编辑  收藏  举报