如果必須開發任何東西(除了一個或多個獲勝策略之外),那麼這個東西就是一個自定義指標。
根據作者的說法,平臺內的這種開發很容易。
需要滿足以下條件:
-
從指標派生的類(直接或從現有的子類派生)
-
定義它將保持lines
指標必須至少具有 1 line。如果從現有派生,則可能已定義line
-
(可選)定義可以改變行為的參數
-
可選地提供/自定義一些元素,這些元素可以合理地繪製指標
-
提供
__init__
完全定義的操作,並將綁定(賦值)到指標的 line,或者提供next
和(可選)once
方法如果在初始化期間可以使用邏輯/算術運算完全定義指標,並將結果分配給 line: 完成
如果不是這種情況,至少必須提供 a
next
,其中指標必須為索引 0 處的 line賦值通過提供一次方法,可以優化運行 模式(批處理操作)的計算。
重要提示:冪等性
指標為它們收到的每個柱產生一個輸出。無需假設同一柱將被發送多少次。操作必須是冪等的。
這背後的基本原理:
- 同一根柱線(指數方面)可以多次發送,值會發生變化(即變化的值是收盤價)
例如,這可以「重放」每日會話,但使用可以由5分鐘柱組成的日內數據。
它還可以允許平臺從即時源中獲取值。
虛擬(但功能性)指示器
那麼它可以是:
class DummyInd(bt.Indicator): lines = ('dummyline',) params = (('value', 5),) def __init__(self): self.lines.dummyline = bt.Max(0.0, self.params.value)
做!指標將始終輸出相同的值:0.0 或 self.params.value(如果它恰好大於 0.0)。
相同的指標,但使用下一個方法:
class DummyInd(bt.Indicator): lines = ('dummyline',) params = (('value', 5),) def next(self): self.lines.dummyline[0] = max(0.0, self.params.value)
做!相同的行為。
注意
請注意如何在__init__
版本中 bt.Max
用於分配給 Line 物件 self.lines.dummyline
。
bt.Max
返回一個 lines 物件,該對象針對傳遞給指標的每個柱自動反覆運算。
如果max
被使用,assigment將是毫無意義的,因為指標將具有具有固定值的成員變數,而不是 line。
在工作期間next
直接使用浮點值完成,並且可以使用標準 max
內置
讓我們回想一下,self.lines.dummyline
這是長符號,它可以縮短為:
self.l.dummyline
甚至:
self.dummyline
後者只有在代碼沒有用成員屬性遮蓋它時才有可能。
第 3版 和第 last 版提供了優化計算的附加 once
方法:
class DummyInd(bt.Indicator): lines = ('dummyline',) params = (('value', 5),) def next(self): self.lines.dummyline[0] = max(0.0, self.params.value) def once(self, start, end): dummy_array = self.lines.dummyline.array for i in xrange(start, end): dummy_array[i] = max(0.0, self.params.value)
更有效,但開發該once
方法已迫使划傷表面。實際上,已經研究了膽量。
無論如何,該__init__
版本都是最好的:
-
一切都局限於初始化
-
next
和once
(兩者都經過優化,因為bt.Max
已經有了它們)自動提供,無需使用索引和/或公式
無論是開發需要的,指標還可以覆蓋與 和 once
相關的next
方法:
-
prenext
和nexstart
-
preonce
和oncestart
手動/自動最短週期
如果可能的話,平臺將計算它,但可能需要手動操作。
以下是簡單移動平均線的潛在實現:
class SimpleMovingAverage1(Indicator): lines = ('sma',) params = (('period', 20),) def next(self): datasum = math.fsum(self.data.get(size=self.p.period)) self.lines.sma[0] = datasum / self.p.period
雖然看起來不錯,但平臺不知道最小周期是什麼,即使參數被命名為“period”(名稱可能具有誤導性,並且某些指標會收到幾個具有不同用法的“period”)
在這種情況下next
,已經調用了第1個 柱,並且everthing會爆炸,因為get無法返回所需的 self.p.period
。
在解決這種情況之前,必須考慮一些事情:
- 傳遞給指標的 data feeds 可能已經具有 最小週期
示例 SimpleMovingAverage 可以在例如以下方面完成:
-
一般 data feed
這有一個預設的最小週期 1 (只需等待進入系統的第 1 根 柱線)
-
另一條移動平均線...而這又已經有一個句號
如果這是 20,並且我們的樣本移動平均線也有 20,我們最終得到的最小週期為 40 根柱線
實際上,內部計算顯示39 ...因為一旦第一條移動平均線產生了一根柱線,這就會計入下一條移動平均線,這會創建一個重疊的柱線,因此需要39個。
-
其他也帶有周期的指標/物件
緩解這種情況的做法如下:
class SimpleMovingAverage1(Indicator): lines = ('sma',) params = (('period', 20),) def __init__(self): self.addminperiod(self.params.period) def next(self): datasum = math.fsum(self.data.get(size=self.p.period)) self.lines.sma[0] = datasum / self.p.period
該addminperiod
方法是告訴系統考慮該指標所需的額外週期柱,以達到可能存在的任何最小週期。
有時,如果所有計算都是使用已經將其週期需求傳達給系統的物件完成的,則絕對不需要這樣做。
使用直方圖的快速 MACD 實現:
from backtrader.indicators import EMA class MACD(Indicator): lines = ('macd', 'signal', 'histo',) params = (('period_me1', 12), ('period_me2', 26), ('period_signal', 9),) def __init__(self): me1 = EMA(self.data, period=self.p.period_me1) me2 = EMA(self.data, period=self.p.period_me2) self.l.macd = me1 - me2 self.l.signal = EMA(self.l.macd, period=self.p.period_signal) self.l.histo = self.l.macd - self.l.signal
做!無需考慮最小週期。
-
EMA
代表指數移動平均線(平臺內置別名)這個(已經在平臺中)已經說明瞭它需要什麼
-
指標 「macd」 和 「signal」 的命名 lines 正在被分配對象,這些對象已經帶有聲明的(幕後)週期
-
macd 從操作「me1 - me2」中獲取週期,而操作又從 me1 和 me2 的週期中獲取最大值(這兩個週期都是具有不同週期的指數移動平均線)
-
信號直接取麥克德上指數移動平均線的週期。此 EMA 還考慮了已經存在的 macd 週期和計算自身所需的樣本量 (period_signal)
-
histo 取兩個操作數“信號 - macd”中的最大值。一旦兩者都準備好了,histo也可以產生價值
-
完整的自訂指標
讓我們開發一個簡單的自定義指標,它「指示」移動平均線(可以用參數修改)是否高於給定數據:
import backtrader as bt import backtrader.indicators as btind class OverUnderMovAv(bt.Indicator): lines = ('overunder',) params = dict(period=20, movav=btind.MovAv.Simple) def __init__(self): movav = self.p.movav(self.data, period=self.p.period) self.l.overunder = bt.Cmp(movav, self.data)
做!如果平均值高於數據,則指標的值為“1”;如果低於數據,則指標的值為“-1”。
作為常規 data feed 1和-1將與 close 價格相比產生1和-1的數據。
雖然在繪圖部分可以看到更多,並且在繪圖世界中有一個舉止和善良的公民,但可以添加一些東西:
import backtrader as bt import backtrader.indicators as btind class OverUnderMovAv(bt.Indicator): lines = ('overunder',) params = dict(period=20, movav=bt.ind.MovAv.Simple) plotinfo = dict( # Add extra margins above and below the 1s and -1s plotymargin=0.15, # Plot a reference horizontal line at 1.0 and -1.0 plothlines=[1.0, -1.0], # Simplify the y scale to 1.0 and -1.0 plotyticks=[1.0, -1.0]) # Plot the line "overunder" (the only one) with dash style # ls stands for linestyle and is directly passed to matplotlib plotlines = dict(overunder=dict(ls='--')) def _plotlabel(self): # This method returns a list of labels that will be displayed # behind the name of the indicator on the plot # The period must always be there plabels = [self.p.period] # Put only the moving average if it's not the default one plabels += [self.p.movav] * self.p.notdefault('movav') return plabels def __init__(self): movav = self.p.movav(self.data, period=self.p.period) self.l.overunder = bt.Cmp(movav, self.data)