指标开发-自定义指标

如果必须开发任何东西(除了一个或多个获胜策略),这就是自定义指标

据作者说,平台内的这种开发很容易。

需要以下内容:

  • 从指标派生的类(直接或从已经存在的子类)
  • 定义它将持有的

    一个指标必须至少有 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__无论如何,该版本是最好的:

  • 一切都局限于初始化
  • nextonce(都经过优化,因为bt.Max已经有了它们)是自动提供的,无需使用索引和/或公式

无论是开发需要,指标还可以覆盖与next和关联的方法once

  • prenextnexstart
  • preonceoncestart

手动/自动最小周期

如果可能,平台将计算它,但可能需要手动操作。

这是一个简单移动平均线的潜在实现:

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)

 

评论被关闭。