如果必须开发任何东西(除了一个或多个获胜策略之外),那么这个东西就是一个自定义指针。
根据作者的说法,平台内的这种开发很容易。
需要满足以下条件:
-
从指针派生的类(直接或从现有的子类派生)
-
定义它将保持lines
指针必须至少具有 1 line。如果从现有派生,则可能已定义line
-
(可选)定义可以改变行为的参数
-
可选地提供/自定义一些元素,这些元素可以合理地绘制指针
-
提供
__init__
完全定义的操作,并将绑定(赋值)到指针的 line,或者提供next
和(可选)once
方法如果在初始化期间可以使用逻辑/算术运算完全定义指针,并将结果分配给 line: 完成
如果不是这种情况,至少必须提供 a
next
,其中指针必须为索引 0 处的 line赋值通过提供一次方法,可以优化运行 模式(批处理操作)的计算。
重要提示:幂等性
指针为它们收到的每个柱产生一个输出。无需假设同一柱将被发送多少次。操作必须是幂等的。
这背后的基本原理:
- 同一根柱线(指数方面)可以多次发送,值会发生变化(即变化的值是收盘价)
例如,这可以「重放」每日会话,但使用可以由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 或 self.params.value(如果它恰好大于 0.0)。
相同的指针,但使用下一个方法:
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
被使用,assigment将是毫无意义的,因为指针将具有具有固定值的成员变量,而不是 line。
在工作期间next
直接使用浮点值完成,并且可以使用标准 max
内置
让我们回想一下,self.lines.dummyline
这是长符号,它可以缩短为:
self.l.dummyline
甚至:
self.dummyline
后者只有在代码没有用成员属性遮盖它时才有可能。
第 3版 和第 last 版提供了优化计算的附加 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
已经有了它们)自动提供,无需使用索引和/或公式
无论是开发需要的,指针还可以覆盖与 和 once
相关的next
方法:
-
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
,已经调用了第1个 柱,并且everthing会爆炸,因为get无法返回所需的 self.p.period
。
在解决这种情况之前,必须考虑一些事情:
- 传递给指针的 data feeds 可能已经具有 最小周期
示例 SimpleMovingAverage 可以在例如以下方面完成:
-
一般 data feed
这有一个缺省的最小周期 1 (只需等待进入系统的第 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」 的命名 lines 正在被分配对象,这些对象已经带有声明的(幕后)周期
-
macd 从操作「me1 - me2」中获取周期,而操作又从 me1 和 me2 的周期中获取最大值(这两个周期都是具有不同周期的指数移动平均线)
-
信号直接取麦克德上指数移动平均线的周期。此 EMA 还考虑了已经存在的 macd 周期和计算自身所需的样本量 (period_signal)
-
histo 取两个操作数“信号 - 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”。
作为常规 data feed 1和-1将与 close 价格相比产生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)