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