backtrader框架介绍 – backtrader中文教程
backtrader框架介绍
这是框架的一些概念的集合。它试图收集对使用框架有用的信息。
在开始之前
所有迷你代码示例都假定以下导入可用:
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(...)
数据馈送 – 传递它们
平台工作的基础将通过Strategies完成。这些将通过Data Feeds。平台最终用户不需要关心接收它们:
数据馈送以数组的形式自动提供给策略的成员变量和数组位置的快捷方式
战略派生类声明和运行平台的快速预览:
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) ...
请注意以下事项:
- 否
*args
或**kwargs
正在通过策略的__init__
方法接收(它们可能仍在使用) - 存在一个成员变量
self.datas
,它是数组/列表/可迭代,至少包含一项(希望或否则会引发异常)
就是这样。数据馈送被添加到平台,它们将按照它们添加到系统的顺序显示在策略中。
Tips:这也适用于Indicators
,如果最终用户开发自己的自定义指标或查看一些现有指标参考的源代码时
数据馈送的快捷方式
可以使用附加的自动成员变量直接访问 self.datas 数组项:
self.data
targetsself.datas[0]
self.dataX
targetsself.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
人都支持 参数的概念。
- 参数连同默认值被声明为类属性(元组的元组或类似字典的对象)
- 扫描关键字 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
启用的对象。从最终用户的角度来看,这意味着:
- 它可以容纳多个线系列中的一个,作为线系列,值的数组是将这些值放在图表中它们将形成一条线。
线(或lineseries )的一个很好的例子是由股票的收盘价形成的线。这实际上是价格演变的著名图表表示(称为收盘线)
平台的正常使用只与访问 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
属性
Tips:从这里应该很明显,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
但是,如果实际上正在访问行,则该表示法不像前一个表示法那样清晰。
声明
如果正在开发指标,则必须声明指标所具有的行。
与params一样,这一次仅作为一个元组作为类属性发生。不支持字典,因为它们不按照插入顺序存储内容。
对于简单移动平均线,它会这样完成:
class SimpleMovingAverage(Indicator): lines = ('sma',) ...
如上例所示,此声明在指标sma
中创建了一条线, 以后可以在策略逻辑中访问(并且可能由其他指标访问以创建更复杂的指标)
对于开发来说,有时以通用的非命名方式访问行很有用,这就是编号访问派上用场的地方:
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]
which 是完整的shorthard版本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
推理:
- An
Indicator
可以有一个属性,该属性close
包含一个中间计算,稍后将其传递给实际lines
也命名的close
在Data Feeds的情况下,不会进行任何计算,因为它只是一个数据源。
线长
线len
有一组点并在执行过程中动态增长,因此可以通过调用标准 Python 函数随时测量长度。
这适用于例如:
- 数据馈送
- 策略
- 指标
预加载数据时,附加属性适用于数据馈送:
- 方法
buflen
该方法返回数据馈送可用的实际柱数。
len
和之间的区别buflen
len
报告已处理的柱数buflen
报告已为数据馈送加载的柱总数
如果两者都返回相同的值,则要么没有预加载数据,要么处理的柱线已经消耗了所有预加载的柱线(除非系统连接到实时源,这将意味着处理结束)
行和参数的继承
有一种元语言来支持Params和 Lines的声明。已尽一切努力使其与标准 Python 继承规则兼容。
参数继承
继承应该按预期工作:
- 支持多重继承
- 基类的参数被继承
- 如果多个基类定义相同的参数,则使用继承列表中最后一个类的默认值
- 如果在子类中重新定义了相同的参数,则新的默认值将取代基类的默认值
行继承
- 支持多重继承
- 继承所有基类的行。如果在基类中多次使用相同的名称,则被命名的行将只有一个版本
索引:0 和 -1
如前所述,线是线系列,并且在绘制在一起时具有一组符合线的点(例如将所有收盘价沿时间轴连接在一起时)
要在常规代码中访问这些点,选择使用基于0的方法来处理当前的get/set瞬间。
策略只会获得价值。指标也设定值。
从前面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')
逻辑是通过应用 index获取移动平均线的当前值和当前收盘价0
。
实际上对于索引0
和应用逻辑/算术运算符时,可以直接进行比较,如下所示:
if self.movav.lines.sma > self.data.lines.close: ...
请参阅文档后面的操作员说明。
设置意味着在开发时使用,例如,指标,因为当前输出值必须由指标设置。
可以为当前获取/设置点计算 SimpleMovingAverage,如下所示:
def next(self): self.line[0] = math.fsum(self.data.get(0, size=self.p.period)) / self.p.period
访问先前的设置点已按照 Python-1
在访问数组/可迭代时的定义进行建模
- 它指向数组的最后一项
平台认为最后一个设置项(在当前实时获取/设置点之前)是-1
.
因此,将当前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
这将返回一个带有1
值 ( size=1
) 的数组,并将当前时刻0
作为向后看的起点。
从当前时间点获取 10 个值(即:最后 10 个值):
myslice = self.my_sma.get(size=10) # ago defaults to 0
当然,数组具有您期望的顺序。最左边的值是最旧的,最右边的值是最新的(它是一个常规的 Python 数组,而不是一个lines对象)
要获取仅跳过当前点的最后 10 个值:
myslice = self.my_sma.get(ago=-1, size=10)
行:延迟索引
运算符语法用于在逻辑阶段[]
提取单个值 。Lines对象支持在阶段期间通过延迟行对象来处理值的附加符号。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
生成另一个 lines对象,1
如果条件是True
或者0
如果False
线路耦合
()
如上所示,该运算符可以与delay
value 一起使用,以提供lines对象的延迟版本。
如果在不提供delay
值 的情况下使用语法,则返回LinesCoupler
lines对象。这是为了在对不同时间框架的数据进行操作的指标之间建立耦合。
不同时间范围的数据馈送具有不同的长度,并且在它们上运行的指标复制数据的长度。例子:
- 每日数据馈送每年大约有 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)
完成上述操作后,sell_sig是一个Lines对象,稍后可以在 Strategy 的逻辑中使用,指示条件是否满足。
第 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
第一行 和当前值)比较
if self.sma > self.data.close:
……self.sma[0]
比较 self.data.close[0]
一些非覆盖的运算符/函数
Python 不允许覆盖所有内容,因此提供了一些函数来应对这些情况。
运营商:
and
->And
or
->Or
逻辑控制:
if
->If
功能:
any
->Any
all
->All
cmp
->Cmp
max
->Max
min
->Min
sum
->Sum
Sum
实际上math.fsum
用作基础操作,因为平台使用浮点数并且应用正则sum
可能会对精度产生影响。reduce
->Reduce
这些实用操作符/函数对可迭代对象进行操作。可迭代对象中的元素可以是常规的 Python 数字类型(整数、浮点数等),也可以是带有Lines的对象。
一个产生非常愚蠢的买入信号的例子:
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)
分解:
- 生成一个
SMA
_data.close
period=15
- 接着
bt.If
sma的值大于close
,返回low
,否则返回high
请记住,
bt.If
调用时没有返回实际值。它返回一个类似于 SimpleMovingAverage的Lines对象。稍后系统运行时会计算这些值
- 然后将生成的
bt.If
Lines对象馈送到 2 ndSMA
,它有时会使用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)
现在第二条移动平均线使用或者30.0
价格high
来执行计算,这取决于sma
vs的逻辑状态close
30
在内部转换为始终返回的伪迭代30