指标开发#
除了一个或多个获胜策略之外,如果必须开发其他内容,那就是自定义指标。
根据作者的说法,这个平台内进行开发非常简单。
需要做以下事情:
从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”关联的方法: -
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
将为第一个条形图调用,并且一切都会爆炸,因为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)