BackTrader 中文文档(五)(2)https://developer.aliyun.com/article/1489255
策略参考
内置策略的参考
MA_CrossOver
别名:
* SMA_CrossOver
这是一个仅限开多头的策略,基于移动平均线交叉
注意:
* Although the default
买入逻辑:
* No position is open on the data * The `fast` moving averagecrosses over the `slow` strategy to the upside.
卖出逻辑:
* A position exists on the data * The `fast` moving average crosses over the `slow` strategy to the downside
订单执行类型:
* Market
线:
* datetime
参数:
* fast (10) * slow (30) * _movav (<class ‘backtrader.indicators.sma.SMA’>)
SignalStrategy
这个Strategy
的子类旨在使用信号自动运行。
信号通常是指标,预期输出值为:
> 0
是一个long
指示< 0
是一个short
指示
有 5 种信号类型,分为 2 组。
主要组:
LONGSHORT
:此信号的long
和short
指示都会被采纳LONG
:
long
指示被认为是开多头short
指示被认为是用于关闭多头仓位。但是:- 如果系统中存在
LONGEXIT
(见下文)信号,则会用于退出多头 - 如果有
SHORT
信号可用且没有LONGEXIT
可用,则会用于在开启short
之前关闭long
SHORT
:
short
指示被认为是开空头long
指示被认为是用于关闭空头仓位。但是:- 如果系统中存在
SHORTEXIT
(见下文)信号,则会用于退出空头 - 如果有
LONG
信号可用且没有SHORTEXIT
可用,则会用于在开启long
之前关闭short
退出组:
这两个信号旨在覆盖其他信号并提供退出long
/short
仓位的标准。
LONGEXIT
:short
指示被认为是用于退出long
仓位SHORTEXIT
:long
指示被认为是用于退出short
仓位
订单发出
订单执行类型为Market
,有效性为None
(直到取消为止)
参数:
signals
(默认:[]
):一个列表/元组的列表/元组,允许实例化信号并分配到正确的类型
预计通过cerebro.add_signal
来管理此参数_accumulate
(默认:False
):允许进入市场(多头/空头),即使已经在市场中_concurrent
(默认:False
):允许即使有订单正在等待执行,也可以发出订单_data
(默认:None
):如果系统中存在多个数据,这是订单的目标。这可以是
None
:系统中的第一个数据将被使用- 一个
int
:表示在该位置插入的数据 - 一个
str
:在创建数据时给定的名称(参数name
)或在使用cerebro.adddata(..., name=)
将其添加到 cerebro 时给定的名称 - 一个
data
实例
线:
* datetime
参数:
* signals ([]) * _accumulate (False) * _concurrent (False) * _data (None)
指标
使用指标
在平台中,指标可以在两个地方使用:
- 在策略内部
- 在其他指标内部
指标的作用
Indicators
始终在Strategy中的__init__
期间实例化- 在
next
期间使用/检查指标值(或其派生的值)
有一个重要的公理需要考虑:
- 在
__init__
期间声明的任何Indicator
(或派生值)将在调用next
之前预先计算。
让我们来看一下差异和操作模式。
__init__
与next
事情的工作方式如下:
- 在
__init__
期间涉及lines对象的任何操作都会生成另一个lines对象 - 在
next
期间涉及lines对象的任何操作都会产生常规的 Python 类型,如浮点数和布尔值。
在__init__
期间
在__init__
期间操作的示例:
hilo_diff = self.data.high - self.data.low
变量hilo_diff
保存对在调用next
之前预先计算的lines对象的引用,并且可以使用标准数组表示法[]
访问
很明显,对于数据源的每个条数据,它包含了高低之间的差异。
这也适用于将简单的lines(如 self.data 数据源中的 lines)与复杂的指标混合使用时:
sma = bt.SimpleMovingAverage(self.data.close) close_sma_diff = self.data.close - sma
现在close_sma_diff
再次包含一个line对象。
使用逻辑运算符:
close_over_sma = self.data.close > sma
现在生成的lines对象将包含一个布尔数组。
在next
期间
操作示例(逻辑运算符):
close_over_sma = self.data.close > self.sma
使用等效数组(索引从 0 开始的表示法):
close_over_sma = self.data.close[0] > self.sma[0]
在这种情况下,close_over_sma
产生一个布尔值,该值是通过将self.data.close
和self.sma
应用到[0]
运算符返回的两个浮点值进行比较的结果
__init__
与next
为什么
逻辑简化(以及随之的易用性)是关键。计算和大部分相关逻辑可以在__init__
期间声明,在next
期间将实际操作逻辑保持最小化。
实际上还有一个附加好处:速度(由于在开头解释的预计算)
一个完整的示例,在__init__
期间生成一个buy信号:
class MyStrategy(bt.Strategy): def __init__(self): sma1 = btind.SimpleMovingAverage(self.data) ema1 = btind.ExponentialMovingAverage() close_over_sma = self.data.close > sma1 close_over_ema = self.data.close > ema1 sma_ema_diff = sma1 - ema1 buy_sig = bt.And(close_over_sma, close_over_ema, sma_ema_diff > 0) def next(self): if buy_sig: self.buy()
注意
Python 的and
运算符不能被重载,迫使平台定义自己的And
。其他构造也是如此,比如Or
和If
显然,__init__
期间的“声明式”方法将next
的膨胀(其中实际策略工作发生)最小化。
(不要忘记还有一个加速因素)
注意
当逻辑变得非常复杂并涉及多个操作时,通常更好的做法是将其封装在一个Indicator
内部。
一些注意事项
在上面的示例中,与其他平台相比,backtrader
中已经简化了两件事情:
- 声明的
Indicators
既不会得到一个parent参数(就像它们被创建的策略一样),也不会调用任何类型的“register”方法/函数。
尽管如此,策略仍将触发Indicators
的计算和因操作而生成的任何 lines 对象(如sma - ema
) ExponentialMovingAverage
在没有self.data
的情况下被实例化
这是故意的。如果没有传递data
,则将自动传递父级的第一个数据(在本例中,正在创建的策略)
指标绘图
首先和最重要的是:
- 声明的
Indicators
将自动绘制(如果调用了 cerebro.plot) - 来自操作的 lines 对象不会被绘制(如
close_over_sma = self.data.close > self.sma
)
如果需要,有一个辅助的LinePlotterIndicator
,它可以使用以下方法绘制此类操作:
close_over_sma = self.data.close > self.sma LinePlotterIndicator(close_over_sma, name='Close_over_SMA')`
name
参数为此指标持有的 单个 线条命名。
控制绘图
在开发 Indicator
时,可以添加一个 plotinfo
声明。它可以是元组的元组(2 个元素)、一个 dict
或一个 OrderedDict
。它看起来像:
class MyIndicator(bt.Indicator): .... plotinfo = dict(subplot=False) ....
该值稍后可以按如下方式访问(和设置)(如果需要的话):
myind = MyIndicator(self.data, someparam=value) myind.plotinfo.subplot = True
该值甚至可以在实例化期间设置:
myind = MyIndicator(self.data, someparams=value, subplot=True)
subplot=True
将传递给(幕后)实例化的成员变量 plotinfo
,用于指标。
plotinfo
提供以下参数来控制绘图行为:
plot
(默认值:True
)
指标是否要绘制或不绘制subplot
(默认值:True
)
是否在不同窗口中绘制指标。对于像移动平均这样的指标,默认值更改为False
plotname
(默认值:''
)
将绘图名称设置为显示在图表上。空值意味着将使用指标的规范名称(class.__name__
)。这有一些限制,因为 Python 标识符不能使用例如算术运算符。
像 DI+ 这样的指标将声明如下:
class DIPlus(bt.Indicator): plotinfo=dict(plotname='DI+')`
- 使图表“更美观”
plotabove
(默认值:False
)
通常将指标绘制在它们操作的数据下方(那些subplot=True
的指标)。将此设置为True
将使指标绘制在数据之上。plotlinelabels
(默认值:False
)
用于“指标”上的“指标”。如果计算 RSI 的 SimpleMovingAverage,则绘图通常会显示相应绘制线的名称“SimpleMovingAverage”。这是“Indicator”的名称,而不是实际绘制的线的名称。
这种默认行为是有意义的,因为用户通常希望看到使用 RSI 创建了 SimpleMovingAverage。
如果将值设置为True
,则将使用 SimpleMovingAverage 中线的实际名称。plotymargin
(默认值:0.0
)
要在指标的顶部和底部留下的边距量(0.15
-> 15%)。有时matplotlib
绘图会延伸到轴的顶部/底部,可能希望有一些边距plotyticks
(默认值:[]
)
用于控制绘制的 y 轴刻度
如果传递了一个空列表,“y ticks”将自动计算。对于像随机指标这样的指标,将其设置为已知的行业标准可能是有意义的,例如:[20.0, 50.0, 80.0]
。
一些指标提供诸如upperband
和lowerband
之类的参数,实际上用于操作 y ticks。plothlines
(默认值:[]
)
用于控制沿指标轴绘制水平线。
如果传递了一个空列表,将不会绘制任何水平线。
对于像随机指标这样的指标,绘制已知的行业标准线可能是有意义的,例如:[20.0, 80.0]
。
一些指标提供诸如upperband
和lowerband
之类的参数,实际上用于操作水平线。plotyhlines
(默认值:[]
)
用于同时使用单个参数控制 plotyticks 和 plothlines。plotforce
(默认值:False
)
如果由于某种原因你认为某个指标应该绘制但却没有绘制……将此设置为True
作为最后的手段。
指标开发
如果除了一个或多个获胜策略之外,还必须开发其他内容,则此内容是自定义指标。
根据作者的说法,平台内的这种开发是简单的。
需要以下内容:
- 从指标派生的类(直接或从已经存在的子类)
- 定义它将保持的线条
指标必须至少有一条线。如果是从现有指标派生的,则可能已经定义了线条(们) - 可选地定义可以更改行为的参数
- 可选地提供/自定义一些元素,以便合理地绘制指标
- 在
__init__
中提供完全定义的操作,并将其绑定(分配)到指标的线条(们)上,否则提供next
和(可选)once
方法
如果可以在初始化期间使用逻辑/算术运算完全定义指标,并将结果分配给线条:完成
如果不是这种情况,至少必须提供一个next
,在该方法中指标必须将一个值分配给索引为 0 的线条(们)
可以通过提供once方法来实现对runonce模式(批量操作)的计算优化。
重要提示:幂等性
指标为它们接收的每个柱状图生成输出。不必假设同一柱状图将被发送多少次。操作必须是幂等的。
其背后的原理是:
- 同一柱状图(索引)可以多次发送,其值会发生变化(即变化的值是收盘价)
这使得例如,“重播”每日会话,但使用可能由 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
将其分配给 Line 对象self.lines.dummyline
。
bt.Max
返回一个lines对象,该对象会自动为传递给指标的每个柱状图迭代。
如果使用max
,则赋值将毫无意义,因为指标将具有具有固定值的成员变量,而不是线条。
在next
过程中,直接使用浮点值进行工作,并且可以使用标准的max
内置函数
让我们回顾一下self.lines.dummyline
是长格式的,可以缩写为:
self.l.dummyline
甚至可以:
self.dummyline
后者仅在代码没有将其与成员属性混淆时才可能存在。
第三个版本提供了一个额外的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”)
在这种情况下,next
将被调用,已经为第 1 个柱,一切都将爆炸,因为 get 不能返回所需的 self.p.period
。
在解决这种情况之前,必须考虑以下事项:
- 提供给指标的数据源可能已经具有最小周期
例如,可以对样本SimpleMovingAverage进行处理:
- 一个常规数据源
这有一个默认的最小周期为 1(只需等待进入系统的第 1 个柱形图) - 另一个移动平均……而这又已经有一个周期
如果这是 20,再次我们的示例移动平均值也是 20,我们最终会得到一个最小周期为 40 个柱的周期
实际上,内部计算说 39 …… 因为一旦第一个移动平均产生了一个柱,这就计为下一个移动平均,从而创建了一个重叠的柱,因此需要 39 个。 - 其他也携带周期的指标/对象
BackTrader 中文文档(五)(4)https://developer.aliyun.com/article/1489266