平台概念
这是平台的一些概念的集合。它试图收集在使用平台时有用的信息位。
出发前
所有迷你代码示例都假定以下导入可用:
import backtrader as bt
import backtrader.indicators as btind
import backtrader.feeds as btfeeds
笔记
访问指示器和馈送等子模块的替代语法:
import backtrader as bt
然后:
thefeed = bt.feeds.OneOfTheFeeds(...)
theind = bt.indicators.SimpleMovingAverage(...)
数据源-传递它们
平台工作的基础将通过策略完成。这些将通过数据源。平台最终用户无需关心接收它们:
数据馈送以数组的形式自动提供给策略的成员变量和指向数组位置的快捷方式
快速预览策略派生类声明并运行平台:
class MyStrategy(bt.Strategy):
params = dict(period=20)
def __init__(self):
sma = btind.SimpleMovingAverage(self.datas[0], period=self.params.period)
...
cerebro = bt.Cerebro()
...
data = btfeeds.MyFeed(...)
cerebro.adddata(data)
...
cerebro.addstrategy(MyStrategy, period=30)
...
请注意以下事项:
-
策略的
__init__
方法未接收到*args
或**kwargs
(可能仍在使用) -
存在一个成员变量
self.datas
,该变量为数组/列表/iterable,至少包含一项(希望如此,否则将引发异常)
的确如此。数据源被添加到平台中,它们将按照添加到系统中的顺序显示在策略中。
笔记
这也适用于Indicators
,最终用户是否应该开发自己的自定义指示器,或者在查看某些现有指示器参考的源代码时
数据源的快捷方式
self.datas 数组项可以通过其他自动成员变量直接访问:
-
self.data
目标self.datas[0]
-
self.dataX
目标self.datas[X]
然后举例说明:
class MyStrategy(bt.Strategy):
params = dict(period=20)
def __init__(self):
sma = btind.SimpleMovingAverage(self.data, period=self.params.period)
...
省略数据源
上述示例可进一步简化为:
class MyStrategy(bt.Strategy):
params = dict(period=20)
def __init__(self):
sma = btind.SimpleMovingAverage(period=self.params.period)
...
self.data
已从SimpleMovingAverage
的调用中完全移除。如果这样做,指示器(在本例中为SimpleMovingAverage
接收正在创建的对象的第一个数据策略,即self.data
(又名self.data0
或self.datas[0]
)
几乎所有东西都是一个数据源
不仅数据源是数据,而且可以传递。Indicators
和Operations
的结果也是数据。
在前面的示例中,SimpleMovingAverage
接收self.datas[0]
作为操作的输入。具有操作和额外指示器的示例:
class MyStrategy(bt.Strategy):
params = dict(period1=20, period2=25, period3=10, period4)
def __init__(self):
sma1 = btind.SimpleMovingAverage(self.datas[0], period=self.p.period1)
# This 2nd Moving Average operates using sma1 as "data"
sma2 = btind.SimpleMovingAverage(sma1, period=self.p.period2)
# New data created via arithmetic operation
something = sma2 - sma1 + self.data.close
# This 3rd Moving Average operates using something as "data"
sma3 = btind.SimpleMovingAverage(something, period=self.p.period3)
# Comparison operators work too ...
greater = sma3 > sma1
# Pointless Moving Average of True/False values but valid
# This 4th Moving Average operates using greater as "data"
sma3 = btind.SimpleMovingAverage(greater, period=self.p.period4)
...
基本上,所有东西都被转换成一个对象,一旦对它进行操作,它就可以用作数据馈送。
参数
大多数平台中的其他class
都支持参数的概念。
-
参数和默认值声明为类属性(元组的元组或类似 dict 的对象)
-
扫描关键词 args(
**kwargs
)以查找匹配的参数,如果找到,则将其从**kwargs
中删除,并将值分配给相应的参数 -
通过访问成员变量
self.params
(简写为self.p
),最终可以在类的实例中使用参数
前面的快速策略预览已经包含了一个参数示例,但是为了冗余起见,再次强调,只关注参数。使用元组:
class MyStrategy(bt.Strategy):
params = (('period', 20),)
def __init__(self):
sma = btind.SimpleMovingAverage(self.data, period=self.p.period)
并使用dict
:
class MyStrategy(bt.Strategy):
params = dict(period=20)
def __init__(self):
sma = btind.SimpleMovingAverage(self.data, period=self.p.period)
线
同样,平台中的所有其他对象都是启用了Lines
的对象。从最终用户的角度来看,这意味着:
- 它可以容纳多个直线系列中的一个,作为一个直线系列。一个值数组将值放在一个图表中,它们将形成一条直线。
线(或线系列的一个很好的例子是股票收盘价形成的线。这实际上是一个著名的价格演变图表(称为收盘时的线)
平台的正常使用只涉及访问lines
。前面的迷你战略示例稍加扩展,再次派上用场:
class MyStrategy(bt.Strategy):
params = dict(period=20)
def __init__(self):
self.movav = btind.SimpleMovingAverage(self.data, period=self.p.period)
def next(self):
if self.movav.lines.sma[0] > self.data.lines.close[0]:
print('Simple Moving Average is greater than the closing price')
两个带lines
的物体已经暴露:
-
self.data
它有一个lines
属性,该属性依次包含一个close
属性 -
self.movav
是一个SimpleMovingAverage
指示器,它有一个lines
属性,该属性依次包含一个sma
属性
笔记
由此可以明显看出,lines
被命名为。也可以按照声明顺序顺序访问它们,但这只能用于Indicator
开发
而行即close
和sma
都可以查询一个点(索引 0来比较数值。
确实存在对行的速记访问:
-
xxx.lines
可缩短为xxx.l
-
xxx.lines.name
可缩短为xxx.lines_name
-
战略和指标等复杂对象提供了对数据行的快速访问
-
self.data_name
提供对self.data.lines.name
的直接访问 -
也适用于编号的数据变量:
self.data1_name
->self.data1.lines.name
-
此外,可以通过以下方式直接访问线路名称:
-
self.data.close
和self.movav.sma
但是如果实际正在访问行,则符号没有前一条那么清楚。
不
不支持设置/分配后面两个符号的行
行声明
如果正在开发一个指示器,则必须声明该指示器所拥有的行。
与参数一样,这一次仅作为元组作为类属性发生。不支持词典,因为它们不按插入顺序存储内容。
对于简单的移动平均线,可以这样做:
class SimpleMovingAverage(Indicator):
lines = ('sma',)
...
笔记
如果将单个字符串传递给元组,则元组中需要声明后面的逗号,否则字符串中的每个字母都将被解释为要添加到元组的项。可能是 Python 语法出错的少数几个地方之一。
如前一个示例所示,该声明在指标中创建了一个sma
行,该行稍后可在策略逻辑中访问(也可能由其他指标创建更复杂的指标)
For development 有时以通用的非命名方式访问行很有用,这就是编号访问方便的地方:
self.lines[0]
指向self.lines.sma
如果定义了更多的行,则可以使用索引 1、2 和更高的值访问它们。
当然,确实存在额外的速记版本:
-
self.line
指向self.lines[0]
-
self.lineX
指向self.lines[X]
-
self.line_X
指向self.lines[X]
在接收数据源的对象内部,这些数据源下面的行也可以通过数字快速访问:
-
self.dataY
指向self.data.lines[Y]
-
self.dataX_Y
指向self.dataX.lines[X]
,这是self.datas[X].lines[Y]
的完整短硬版本
访问数据源中的lines
在数据源中,也可以访问lines
,而不必访问lines
。这使得与close
这样的价格打交道更加自然。
例如:
data = btfeeds.BacktraderCSVData(dataname='mydata.csv')
...
class MyStrategy(bt.Strategy):
...
def next(self):
if self.data.close[0] > 30.0:
...
这似乎比同样有效的:if self.data.lines.close[0] > 30.0:
更自然。这不适用于Indicators
,理由如下:
- 一个
Indicator
可以有一个属性close
,该属性保存一个中间计算,该属性随后被传递到实际的lines
,也被命名为close
在数据馈送的情况下,不进行计算,因为它只是一个数据源。
线路透镜
行有一组点,在执行过程中动态增长,因此可以通过调用标准 Pythonlen
函数随时测量长度。
例如,这适用于:
-
数据源
-
策略
-
指标
当数据为预加载时,另一个属性适用于数据馈送:
- 方法
buflen
该方法返回数据馈送可用的实际条数。
len
与buflen
的区别
-
len
报告已处理的条数 -
buflen
报告为数据馈送加载的条的总数
如果两者都返回相同的值,则表示未预加载任何数据或钢筋加工已消耗所有预加载钢筋(除非系统连接到实时进给,否则这将意味着加工结束)
行和参数的继承
一种元语言支持参数和行的声明。已尽一切努力使其与标准 Python 继承规则兼容。
参数继承
继承应按预期工作:
-
支持多重继承
-
基类中的参数是继承的
-
如果多个基类定义相同的参数,则使用继承列表中最后一个类的默认值
-
如果在子类中重新定义了相同的参数,则新的默认值将接管基类的默认值
行继承
-
支持多重继承
-
继承所有基类的行。当命名为行时,如果相同的名称在基类中被多次使用,则一行的版本将只有一个
索引:0 和-1
线如前所示为线系列,在一起画时有一组点符合一条线(如沿时间轴将所有收盘价连接在一起时)
要以常规代码访问这些点,我们可以选择对当前的获取/设置瞬间使用基于0的方法。
策略只会获得值。指示器也会设置值。
在前面的快速策略示例中,简要介绍了next
方法:
def next(self):
if self.movav.lines.sma[0] > self.data.lines.close[0]:
print('Simple Moving Average is greater than the closing price')
逻辑是通过应用指数0
得到移动平均线的当前值和当前收盘价。
笔记
实际上,对于索引0
和应用逻辑/算术运算符时,可直接进行比较,如下所示:
if self.movav.lines.sma > self.data.lines.close:
...
请参阅本文档后面的操作员说明。
设置意味着在开发指示器时使用,例如,因为当前输出值必须由指示器设置。
SimpleMovingAverage 可为当前 get/set 点计算,如下所示:
def next(self):
self.line[0] = math.fsum(self.data.get(0, size=self.p.period)) / self.p.period
访问以前的设置点是按照 Python 在访问数组/iterable 时对-1
的定义建模的
- 它指向数组的最后一项
该平台将最后一个设置项(当前 LIFT 获取/设置点之前)考虑为 T0 T0。
因此,将当前的close
与之前的close
进行比较是0
与-1
的比较。在战略中,例如:
def next(self):
if self.data.close[0] > self.data.close[-1]:
print('Closing price is higher today')
当然,逻辑上,-1
之前设置的价格将通过-2, -3, ...
访问。
切片
backtrader不支持行对象的切片,这是遵循[0]
和[-1]
索引方案的设计决策。对于常规的可索引 Python 对象,您可以执行以下操作:
myslice = self.my_sma[0:] # slice from the beginning til the end
但请记住,0
的选择……它实际上是当前交付的价值,后面没有任何内容。也:
myslice = self.my_sma[0:-1] # slice from the beginning til the end
同样,0
是当前值,-1
是最新(先前)交付值。这就是为什么在反向交易者生态系统中,来自0
->-1
的一部分毫无意义。
如果要支持切片,它将如下所示:
myslice = self.my_sma[:0] # slice from current point backwards to the beginning
或:
myslice = self.my_sma[-1:0] # last value and current value
或:
myslice = self.my_sma[-3:-1] # from last value backwards to the 3rd last value
吃一片
仍然可以获取具有最新值的数组。语法:
myslice = self.my_sma.get(ago=0, size=1) # default values shown
这将返回一个 arry,其值为1
值(size=1
,当前时刻0
作为向后看的起始点。
从当前时间点获取 10 个值(即:最后 10 个值):
myslice = self.my_sma.get(size=10) # ago defaults to 0
当然,数组具有您期望的顺序。最左边的值是最旧的,最右边的值是最新的(它是一个常规 python 数组,而不是一个行对象)
要获取仅跳过当前点的最后 10 个值,请执行以下操作:
myslice = self.my_sma.get(ago=-1, size=10)
行:延迟索引
[]
运算符语法用于在next
逻辑阶段提取单个值。行对象支持在__init__
阶段通过延迟行对象对值进行寻址的附加符号。
假设逻辑中的兴趣在于将之前的收盘值与简单移动平均值的实际值进行比较。不必在每次next
迭代中手动执行,而是可以生成预屏蔽行对象:
class MyStrategy(bt.Strategy):
params = dict(period=20)
def __init__(self):
self.movav = btind.SimpleMovingAverage(self.data, period=self.p.period)
self.cmpval = self.data.close(-1) > self.sma
def next(self):
if self.cmpval[0]:
print('Previous close is higher than the moving average')
此处使用的是(delay)
符号:
-
这提供了一个
close
价格的复制品,但延迟了-1
。比较
self.data.close(-1) > self.sma
生成另一个行对象,如果条件为True
则返回1
,如果条件为False
则返回0
线耦合
操作员()
可以如上所示使用delay
值来提供行对象的延迟版本。
如果使用的语法为,而未提供delay
值,则返回LinesCoupler
行对象。这意味着在不同时间段的数据上运行的指标之间建立耦合。
不同时间段的数据馈送具有不同的长度,并且在其上操作的指示器复制数据的长度。例子:
-
每日数据馈送每年大约有 250 巴
-
每周数据馈送每年有 52 条
尝试创建一个比较 2简单移动平均数的操作(例如),上面引用的数据上的每个操作都会中断。目前尚不清楚如何将每日时间范围内的 250 条与每周时间范围内的 52 条进行匹配。
读者可以想象在背景中进行date
比较,找出一周一天的通信,但是:
-
Indicators
只是数学公式,没有日期时间信息他们对环境一无所知,只知道如果数据提供足够的值,就可以进行计算。
()
(空呼叫)符号用于救援:
class MyStrategy(bt.Strategy):
params = dict(period=20)
def __init__(self):
# data0 is a daily data
sma0 = btind.SMA(self.data0, period=15) # 15 days sma
# data1 is a weekly data
sma1 = btind.SMA(self.data1, period=5) # 5 weeks sma
self.buysig = sma0 > sma1()
def next(self):
if self.buysig[0]:
print('daily sma is greater than weekly sma1')
此处较大的时间段指示器sma1
与sma1()
的每日时间段耦合。这将返回一个与较大数量的sma0
条兼容的对象,并复制sma1
生成的值,有效地将 52 条每周条分散到 250 条每日条中
运算符,使用自然构造
为了实现“易用性”目标,平台允许(在 Python 的约束范围内)使用操作符。为了进一步提高这一目标,运营商的使用分为两个阶段。
阶段 1-运算符创建对象
已经看到了一个例子,即使没有明确说明这一点。在指标和策略等对象的初始化阶段(__init__
方法),操作员创建可操作、分配或保留的对象,作为策略逻辑评估阶段的参考。
再一次,SimpleMovingAverage 的潜在实现,进一步细分为步骤。
SimpleMovingAverage 指示器__init__
中的代码可能如下所示:
def __init__(self):
# Sum N period values - datasum is now a *Lines* object
# that when queried with the operator [] and index 0
# returns the current sum
datasum = btind.SumN(self.data, period=self.params.period)
# datasum (being *Lines* object although single line) can be
# naturally divided by an int/float as in this case. It could
# actually be divided by anothr *Lines* object.
# The operation returns an object assigned to "av" which again
# returns the current average at the current instant in time
# when queried with [0]
av = datasum / self.params.period
# The av *Lines* object can be naturally assigned to the named
# line this indicator delivers. Other objects using this
# indicator will have direct access to the calculation
self.line.sma = av
在策略初始化期间,将显示一个更完整的用例:
class MyStrategy(bt.Strategy):
def __init__(self):
sma = btind.SimpleMovinAverage(self.data, period=20)
close_over_sma = self.data.close > sma
sma_dist_to_high = self.data.high - sma
sma_dist_small = sma_dist_to_high < 3.5
# Unfortunately "and" cannot be overridden in Python being
# a language construct and not an operator and thus a
# function has to be provided by the platform to emulate it
sell_sig = bt.And(close_over_sma, sma_dist_small)
上述操作完成后,卖出信号是一个行对象,可以在以后的策略逻辑中使用,表示是否满足条件。
第 2 阶段-真实的操作员
让我们首先记住,一个策略有一个next
方法,它对系统处理的每一个条都被调用。这是操作员实际处于第 2 阶段模式的地方。以前面的示例为基础:
class MyStrategy(bt.Strategy):
def __init__(self):
self.sma = sma = btind.SimpleMovinAverage(self.data, period=20)
close_over_sma = self.data.close > sma
self.sma_dist_to_high = self.data.high - sma
sma_dist_small = sma_dist_to_high < 3.5
# Unfortunately "and" cannot be overridden in Python being
# a language construct and not an operator and thus a
# function has to be provided by the platform to emulate it
self.sell_sig = bt.And(close_over_sma, sma_dist_small)
def next(self):
# Although this does not seem like an "operator" it actually is
# in the sense that the object is being tested for a True/False
# response
if self.sma > 30.0:
print('sma is greater than 30.0')
if self.sma > self.data.close:
print('sma is above the close price')
if self.sell_sig: # if sell_sig == True: would also be valid
print('sell sig is True')
else:
print('sell sig is False')
if self.sma_dist_to_high > 5.0:
print('distance from sma to hig is greater than 5.0')
这不是一个非常有用的策略,只是一个例子。在第 2 阶段,运算符返回期望值(如果测试真值,则返回布尔值;如果将它们与浮点值进行比较,则返回浮点值),算术运算也会返回期望值。
笔记
请注意,比较实际上并没有使用[]运算符。这是为了进一步简化事情。
if self.sma > 30.0:
…将self.sma[0]
与30.0
进行比较(1st行和当前值)
if self.sma > self.data.close:
…将self.sma[0]
与self.data.close[0]
进行比较
一些非重写运算符/函数
Python 不允许覆盖所有内容,因此提供了一些函数来处理这些情况。
笔记
仅用于在阶段 1 中创建对象,这些对象随后提供值。
操作员:
-
and
->And
-
or
->Or
逻辑控制:
if
->If
功能:
-
any
->Any
-
all
->All
-
cmp
->Cmp
-
max
->Max
-
min
->Min
-
sum
->Sum
Sum
实际使用math.fsum
作为底层操作,因为平台使用浮点数,应用规则sum
可能会影响精度。 -
reduce
->Reduce
这些公用设施操作员/功能在 iterables 上运行。iterables 中的元素可以是常规 Python 数值类型(int、float 等),也可以是带有行的对象。
生成非常愚蠢的购买信号的示例:
class MyStrategy(bt.Strategy):
def __init__(self):
sma1 = btind.SMA(self.data.close, period=15)
self.buysig = bt.And(sma1 > self.data.close, sma1 > self.data.high)
def next(self):
if self.buysig[0]:
pass # do something here
很明显,如果sma1
高于高点,则必须高于收盘点。但重点是说明bt.And
的用法。
使用bt.If
:
class MyStrategy(bt.Strategy):
def __init__(self):
sma1 = btind.SMA(self.data.close, period=15)
high_or_low = bt.If(sma1 > self.data.close, self.data.low, self.data.high)
sma2 = btind.SMA(high_or_low, period=15)
细分:
-
在
period=15
的data.close
上生成SMA
-
然后
bt.If
该sma的值大于close
,返回low
,否则返回high
请记住,调用
bt.If
时不会返回任何实际值。它返回一个行对象,类似于SimpleMovingAverage。稍后系统运行时将计算这些值
-
生成的
bt.If
行对象随后被馈送至 2ndSMA
,该 2nd有时使用low
价格,有时使用high
价格进行计算
那些函数也采用数值。修改后的相同示例:
class MyStrategy(bt.Strategy):
def __init__(self):
sma1 = btind.SMA(self.data.close, period=15)
high_or_30 = bt.If(sma1 > self.data.close, 30.0, self.data.high)
sma2 = btind.SMA(high_or_30, period=15)
现在 2nd移动平均线根据sma
对close
的逻辑状态,使用30.0
或high
价格进行计算
笔记
值30
在内部转换为一个伪 iterable,该伪 iterable 始终返回30