指标开发#

除了一个或多个获胜策略之外,如果必须开发其他内容,那就是自定义指标。

根据作者的说法,这个平台内进行开发非常简单。

需要做以下事情:

  • 从Indicator类派生出一个类(直接派生或从已存在的子类派生)

  • 定义它将包含的“线”

    一个指标至少必须有一条线。如果从已存在的指标派生,则可能已经定义了这些线。

  • 可选地定义可以改变行为的参数

  • 可选地提供/定制一些使指标能够合理绘图的元素

  • __init__ 中提供完全定义的操作,并将其与指标的线绑定(赋值)或者提供 next 和(可选) once 方法重要提示:幂等性


指标器对每个接收到的柱子产生一个输出。不能假设相同柱子会被发送多少次。操作必须是幂等的。

背后的原理是:

  • 相同的柱子(按索引计算)可以多次发送,其值会变化(即变化的值是收盘价)

这使得例如可以”重新播放”日会话,但使用的是分钟柱数据。

这也可以允许平台从实时数据源获取值。

一个虚拟(但实际可用)的指标器 *** ** ** ** ** ** ** ** ** ** ** ** ** 所以它可以是这样的:

```python 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。

使用next方法的相同指标:

```python 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 ,那么这个赋值将毫无意义,因为指标将具有一个固定值的成员变量,而不是一个线。

next 方法中,直接使用浮点数值进行计算,可以使用标准的 max 内置函数

让我们回顾一下 self.lines.dummyline 是长形式的表达方式,可以缩写为:

  • self.l.dummyline

甚至可以缩写为:

  • self.dummyline

只有当代码没有使用成员属性来屏蔽时,才可以使用后一种缩写形式。

第三个也是最后一个版本提供了一个额外的``once``方法来优化计算:```python 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”关联的方法: - 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

尽管看起来没有问题,但平台不知道最小周期是多少, 即使参数被命名为”period”(名称可能会误导,某些指标接收多个具有不同用途的”period”)

在这种情况下, next 将为第一个条形图调用,并且一切都会爆炸,因为get无法返回所需的 self.p.period

在解决这个问题之前,必须考虑以下几点:

  • 传递到指标的 数据源 可能已经具有 最小周期 样本 SimpleMovingAverage 可以在以下情况下进行:

  • 一个常规数据源

    这个默认的最小周期是1(只需等待进入系统的第一个柱子)

  • 另一个移动平均线…而这个移动平均线本身已经有一个*周期*

    如果这个周期是20,而我们的样本移动平均线也是20,那么最终得到的最小周期是40个柱子

    实际上,内部计算说的是39…因为一旦第一个移动平均线产生了一个柱子,这个柱子也算在下一个移动平均线的计算中,这样就产生了一个重叠的柱子,因此需要39个。

  • 其它也带有周期的指标/对象

缓解这种情况的方法如下:

```python
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 方法告诉系统考虑此指标所需的额外 period 周期,以及可能存在的最小周期。

有时这完全不需要,如果所有的计算都是用已经向系统传递了其周期需求的对象完成的。

快速的 MACD 实现,带有 Histogram 图:

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 的周期中的最大值(它们都是具有不同周期的指数移动平均线)

  • signal 直接取自 macd 上的指数移动平均线的周期。这个指数移动平均线还考虑了已经存在的 macd 周期和计算自身所需的样本数(period_signal)

  • histo 取两个操作数 “signal - macd” 的最大值。一旦两者都准备好,histo 也可以生成一个值

一个完整的自定义指标#

让我们开发一个简单的自定义指标,如果移动平均线(可以通过参数进行修改)在给定数据之上,则“指示”为True:

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的产生将与收盘价进行比较。

尽管在*绘图*部分可以看到更多内容,并且为了在绘图世界中拥有良好的品质,可以添加一些内容:

```python 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(

# 在1和-1上方和下方添加额外的边距 plotymargin=0.15,

# 在1.0和-1.0处绘制参考水平线 plothlines=[1.0, -1.0],

# 将y轴的刻度简化为1.0和-1.0 plotyticks=[1.0, -1.0])

# 用虚线绘制“overunder”线(仅有一条线) # ls代表了linestyle,并直接传递给matplotlib plotlines = dict(overunder=dict(ls=’–‘))

def _plotlabel(self):

# 此方法返回一个标签列表,将显示在绘图中指标名称的后面

# 周期始终必须存在 plabels = [self.p.period]

```# 如果不是默认的移动平均线,则只使用移动平均线 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)