這個問題或多或少地出現了幾次:這樣:
- backtrader如何最好/規範地實現這一點或那樣?
作為 backtrader 的目標之一,可以靈活地 支援盡可能多的情況和用例,答案很簡單:“至少在幾種方式上”。針對指標進行匯總,對於這些問題最常發生的情況:
-
方法中
__init__
100% 聲明性 -
100%逐步
next
的方法 -
對於聲明性部分無法涵蓋所有必需計算的複雜方案,混合上述兩者
快速查看 backtrader 中的內置指標可發現所有這些指標都是以聲明性方式實現的。原因
-
更容易做到
-
更易於閱讀
-
更優雅
-
自動管理矢量化和基於偶數的實現
什麼?!?!自動實現的矢量化??
是的。如果指標完全在方法內部__init_
實現,那麼Python中元類和運算元重載的魔力將提供以下內容
-
向量化實現(運行回溯測試時的預設設定)
-
基於事件的實現(例如,用於即時交易)
另一方面,如果在方法中next
實現的指標的任何部分:
-
這是直接用於基於事件的運行的代碼。
-
矢量化將通過在每個數據點的
next
後台調用方法來類比注意
這意味著,即使特定指標沒有矢量化實現,所有其他擁有它的指標仍將以矢量化方式運行。
資金流量指數:一個例子
社區使用者@Rodrigo Brito 發佈了一個版本的「資金流量指數」 指標,該指標使用該 next
方法進行實施。
代碼
class MFI(bt.Indicator): lines = ('mfi', 'money_flow_raw', 'typical', 'money_flow_pos', 'money_flow_neg') plotlines = dict( money_flow_raw=dict(_plotskip=True), money_flow_pos=dict(_plotskip=True), money_flow_neg=dict(_plotskip=True), typical=dict(_plotskip=True), ) params = ( ('period', 14), ) def next(self): typical_price = (self.data.close[0] + self.data.low[0] + self.data.high[0]) / 3 money_flow_raw = typical_price * self.data.volume[0] self.lines.typical[0] = typical_price self.lines.money_flow_raw[0] = money_flow_raw self.lines.money_flow_pos[0] = money_flow_raw if self.lines.typical[0] >= self.lines.typical[-1] else 0 self.lines.money_flow_neg[0] = money_flow_raw if self.lines.typical[0] <= self.lines.typical[-1] else 0 pos_period = math.fsum(self.lines.money_flow_pos.get(size=self.p.period)) neg_period = math.fsum(self.lines.money_flow_neg.get(size=self.p.period)) if neg_period == 0: self.lines.mfi[0] = 100 return self.lines.mfi[0] = 100 - 100 / (1 + pos_period / neg_period)
注意
保持原樣,包括長 lines ,必須水平滾動
@Rodrigo布里托已經注意到,臨時lines的使用(除了mfi
)lines除外)可能是承認優化的東西。確實,但以作者*的拙見,實際上一切都承認一些優化。
為了有共同的工作基礎,可以使用StockCharts的「資金流量指數」定義,並看到上面的實現是好的。這是連結:
有了這個,指標的快速規範實現MFI
class MFI_Canonical(bt.Indicator): lines = ('mfi',) params = dict(period=14) def __init__(self): tprice = (self.data.close + self.data.low + self.data.high) / 3.0 mfraw = tprice * self.data.volume flowpos = bt.ind.SumN(mfraw * (tprice > tprice(-1)), period=self.p.period) flowneg = bt.ind.SumN(mfraw * (tprice < tprice(-1)), period=self.p.period) mfiratio = bt.ind.DivByZero(flowpos, flowneg, zero=100.0) self.l.mfi = 100.0 - 100.0 / (1.0 + mfiratio)
人們應該能夠立即注意到
-
定義了單個 line
mfi
。沒有臨時工。 -
事情看起來更乾淨,不需要
[0]
陣列索引 -
這裡或那裡沒有單曲
if
-
更緊湊,更易讀
如果一個人繪製一個圖表,兩者都針對相同的數據集運行,它看起來像這樣
該圖表顯示,規範版本和非規範版本顯示相同的值和發展,除了在開頭。
-
非規範版本從一開始就提供價值
-
它傳遞的是無意義的值(100.0 直到它傳遞 1 個額外的值,這也是不好的),因為它不能正確傳遞
相比之下:
-
規範版本在達到最小預熱期 后自動開始提供值。
-
不需要人工干預(它必須肯定是「人工智慧」或「機器學習」,...雙關語 )
查看受影響區域的 close圖片
注意
當然,在非規範版本中,可以通過這樣做來緩解這種情況:
- 子類化,其中
bt.ind.PeriodN
已經有一個period
參數,並且知道如何處理它(並在 期間__init__
調用super
)
另請注意,規範版本也考慮了公式中可能的零除情況的分步next
代碼。
if neg_period == 0: self.lines.mfi[0] = 100 return self.lines.mfi[0] = 100 - 100 / (1 + pos_period / neg_period)
而這是另一種方式
mfiratio = bt.ind.DivByZero(flowpos, flowneg, zero=100.0) self.l.mfi = 100.0 - 100.0 / (1.0 + mfiratio)
沒有許多lines,一個return
語句和對輸出line的不同賦值,有計算的單個聲明mfiratio
和單個賦值(遵循StockCharts公式)到輸出linemfi
。
結論
希望這能揭示一些在以規範(即:聲明性__init_
)或非規範方式(逐步與陣列索引) next
實現某些內容時的差異。