backtrader自定义指标的方法讲解 – backtrader中文教程
指标开发-自定义指标
如果必须开发任何东西(除了一个或多个获胜策略),这就是自定义指标。
据作者说,平台内的这种开发很容易。
需要以下内容:
- 从指标派生的类(直接或从已经存在的子类)
- 定义它将持有的行
一个指标必须至少有 1 行。如果从现有的派生,则可能已经定义了行
- 可选择定义可以改变行为的参数
- 可选地提供/定制一些能够合理绘制指标的元素
- 提供一个完全定义的操作,
__init__
绑定(赋值)到指标的行,或者提供next
和(可选)once
方法如果可以在初始化期间使用逻辑/算术运算完全定义指标并将结果分配给该行:完成
如果不是这种情况,至少
next
必须提供一个指标必须为索引 0 处的行分配一个值的位置可以通过提供一次方法来优化runonce模式(批处理操作)的计算。
重要提示:幂等性
指标为他们收到的每根柱线产生一个输出。无需假设同一条柱将被发送多少次。操作必须是幂等的。
这背后的理由:
- 同一根柱线 (index-wise) 可以多次发送变化值(即变化值是收盘价)
例如,这可以“重播”每日会话,但使用可以由 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,则为 0.0 或 self.params.value。
相同的指标,但使用下一个方法:
class DummyInd(bt.Indicator): lines = ('dummyline',) params = (('value', 5),) def next(self): self.lines.dummyline[0] = max(0.0, self.params.value)
完毕!相同的行为。
Tips:请注意__init__
版本bt.Max
中如何使用分配给 Line 对象self.lines.dummyline
。
bt.Max
返回一个为传递给指标的每根柱线自动迭代的线对象。
如果max
改为使用,分配将毫无意义,因为指标将有一个具有固定值的成员变量,而不是一条线。
在next
工作期间直接使用浮点值完成并且max
可以使用标准内置
让我们回想一下,这self.lines.dummyline
是长符号,它可以缩短为:
self.l.dummyline
甚至:
self.dummyline
后者只有在代码没有用成员属性掩盖这一点时才有可能。
第 3版和最后一个版本提供了一种额外的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
已经有了它们)是自动提供的,无需使用索引和/或公式
无论是开发需要,指标还可以覆盖与next
和关联的方法once
:
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
虽然听起来不错,但平台不知道最小周期是多少,即使参数命名为“周期”(名称可能会产生误导,某些指标会接收多个具有不同用法的“周期”)
在这种情况下next
,已经为第一个 bar 调用了,并且一切都会爆炸,因为 get 无法返回所需的self.p.period
。
在解决这种情况之前,必须考虑以下几点:
- 传递给指标的数据馈送可能已经带有最短周期
示例SimpleMovingAverage:
- 常规数据馈送
这有一个默认的最小周期 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”的命名行被分配了已经带有声明(在幕后)周期的对象
- macd 从“me1 – me2”操作中获取周期,该操作又从 me1 和 me2 的周期中获取最大值(它们都是具有不同周期的指数移动平均线)
- 信号直接采用 MACD 上的指数移动平均线的周期。此 EMA 还考虑了已经存在的 macd 周期和所需的样本量 (period_signal) 来计算自身
- histo 取两个操作数“signal – 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”。
作为常规数据馈送的数据,与收盘价相比将产生 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)
评论被关闭。