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和nexstartpreonce和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)
评论被关闭。