指标制定
如果必须开发任何东西(除了一个或多个获胜策略),这是一个自定义指标。
根据作者的说法,在平台内进行这样的开发是很容易的。
需要做到以下几点:
-
从指示符派生的类(直接或从已存在的子类派生)
-
定义它将保持的行
指示器必须至少有一行。如果从现有行派生,则可能已经定义了行
-
(可选)定义可以改变行为的参数
-
可选地提供/定制一些元素,这些元素可实现指示器的合理绘图
-
在
__init__
中提供完全定义的操作,并将其绑定(赋值)到指示器的行,或者提供next
和(可选的)once
方法如果在初始化期间可以使用逻辑/算术运算完全定义指示器,并且结果被分配到行:完成
在这种情况下,必须至少提供一个
next
,其中指示器必须为索引 0 处的行分配一个值跳动模式(批量操作)的计算优化可通过提供一次方法实现。
重要提示:幂等性
指示器为接收到的每个条形图生成输出。不必假设同一条线将发送多少次。运算必须是幂等的。
这背后的理由是:
- 同一条线(指数方向)可以多次发送,且其值会发生变化(即变化值为收盘价)
例如,这允许“重播”每日会话,但使用可以由 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)
完成!同样的行为。
笔记
注意在__init__
版本bt.Max
中是如何分配给行对象self.lines.dummyline
的。
bt.Max
返回一个行对象,该对象对于传递给指示器的每个条自动迭代。
如果改用max
的话,assignment 将毫无意义,因为指示符将有一个具有固定值的成员变量,而不是一行。
在next
期间,工作直接通过浮点值完成,并且可以使用标准max
内置
让我们回忆一下,self.lines.dummyline
是长符号,可以缩短为:
self.l.dummyline
甚至:
self.dummyline
只有在代码没有使用成员属性将其掩盖的情况下,才可能使用后者。
3rd和最新版本提供了一个额外的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
虽然听起来不错,但平台不知道最短周期是什么,即使参数名为“period”(名称可能有误导性,一些指标会收到几个不同用法的“period”)
在这种情况下,1st条已经调用了next
,所有东西都会爆炸,因为 get 无法返回所需的self.p.period
。
在解决问题之前,必须考虑以下因素:
- 传递到指示器的数据馈送可能已经带有一个最短周期
示例SimpleMovingAverage可在以下设备上进行:
-
常规数据源
默认最小周期为 1(只需等待进入系统的 1st条)
-
另一条移动平均线……而这条线又有一个周期
如果这是 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”和“信号”的命名行被指定为已携带声明(幕后)时段的对象
-
macd 从操作“me1-me2”中获取周期,而操作“me1-me2”又从 me1 和 me2 的周期中获取最大值(这两个周期都是不同周期的指数移动平均值)
-
信号直接取 macd 上指数移动平均值的周期。该 EMA 还考虑了已经存在的 macd 周期和计算自身所需的样本量(周期信号)
-
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”。
作为定期数据馈送,与收盘价相比,将产生 1s 和-1s。
虽然在绘图部分可以看到更多内容,并且在绘图世界中有一个行为端正、善良的公民,但可以添加以下几点:
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)