如果必須開發任何東西(除了一個或多個獲勝策略之外),那麼這個東西就是一個自定義指標。
根據作者的說法,平臺內的這種開發很容易。
需要滿足以下條件:
-
從指標派生的類(直接或從現有的子類派生)
-
定義它將保持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)