BackTrader 中文文档(二)(1)

简介: BackTrader 中文文档(二)

概念

平台概念

原文:www.backtrader.com/docu/concepts/

这是平台某些概念的集合。它试图收集可在使用平台时有用的信息片段。

开始之前

所有小代码示例都假设以下导入可用:

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,其为包含至少一个项目的数组/列表/可迭代对象(希望至少有一个项目,否则将引发异常)。

是的。数据源被添加到平台上,它们将按照它们被添加到系统中的顺序显示在策略内部。

注意

这也适用于指标,如果最终用户开发自己的自定义指标或者查看某些现有指标参考的源代码时。

数据源的快捷方式

可以直接使用附加自动成员变量访问 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.data0self.datas[0]

几乎一切都是数据源

不仅数据源是数据,也可以传递。指标操作的结果也是数据。

在上一个示例中,SimpleMovingAverageself.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都支持参数的概念。

  • 参数连同默认值声明为类属性(元组的元组或类似字典的对象)
  • 关键字参数(**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启用的对象。从最终用户的角度来看,这意味着:

  • 它可以容纳一个或多个线系列,其中线系列是一个值数组,将这些值放在一起形成一条线。

一个line(或lineseries)的很好的例子是由股票收盘价形成的线。这实际上是价格演变的一个众所周知的图表表示(称为Line on Close

平台的常规使用只关注访问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开发中使用

两个lines,即closesma,都可以查询一个点(索引 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.closeself.movav.sma
    但是如果实际上正在访问,则该符号并不像之前的符号那样清晰。

不是

使用这两个后续符号设置/分配行不受支持

Lines声明

如果正在开发一个Indicator,则必须声明该指标具有的lines

正如与params一样,这次以类属性的形式发生,作为元组。不支持字典,因为它们不按插入顺序存储事物。

对于简单移动平均线,应该这样做:

class SimpleMovingAverage(Indicator):
    lines = ('sma',)
    ...

如果将单个字符串传递给元组,则元组中需要跟随声明的逗号,否则字符串中的每个字母都将被解释为要添加到元组中的项目。这可能是 Python 语法出错的几个情况之一。

如前面的例子所示,此声明在Indicator中创建了一个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],这是 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

数据源的情况下,不会进行任何计算,因为它只是一个数据源。

长度

有一组点,并在执行过程中动态增长,因此可以随时通过调用标准的 Python len 函数来测量长度。

这适用于例如:

  • 数据源
  • 策略
  • 指标

数据源中存在另一个属性,当数据 预加载 时适用:

  • 方法 buflen

该方法返回数据源可用的实际条形图数。

lenbuflen 的区别

  • len 报告已处理了多少个条形图
  • buflen 报告为数据源加载了多少个条形图

如果两者返回相同的值,则要么没有预加载数据,要么条形图的处理已消耗所有预加载的条形图(除非系统连接到实时数据源,否则这将意味着处理结束)

行和参数的继承

一种元语言用于支持参数的声明。为了使其与标准 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')

逻辑是通过应用索引0 获取移动平均值和当前收盘价的当前值。

注意

实际上,对于索引0,并且在应用逻辑/算术运算符时,可以直接进行比较,如下所示:

if self.movav.lines.sma > self.data.lines.close:
    ...

在文档后面看运算符的解释。

设置是用于开发时使用的,例如,一个 Indicator,因为必须通过该指标设置当前输出值。

可以按以下方式计算当前获取/设置点的 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 vs -1的事情。例如,在策略中:

def next(self):
    if self.data.close[0] > self.data.close[-1]:
        print('Closing price is higher today')

当然,从-1之前设置的价格将使用-2、-3、...进行访问。

切片

backtrader不支持对lines对象进行切片,这是一种设计决策,遵循了[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是最新(先前)的交付值。这就是为什么在backtrader生态系统中从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)

行:延迟索引

[]操作符语法用于在next逻辑阶段提取单个值。 Lines对象支持另一种符号,通过延迟行对象__init__阶段访问值。

假设逻辑的兴趣是将前一个close值与简单移动平均值的实际值进行比较。而不是在每个next迭代中手动执行,可以生成一个预先定义的lines对象:

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对象,如果条件为True则返回1,如果为False则返回0

线耦合

运算符()可以像上面显示的那样与delay值一起使用,以提供lines对象的延迟版本。

如果使用语法WITHOUT提供delay值,则返回一个LinesCoupler lines对象。这旨在在操作datas具有不同时间框架的指标之间建立耦合。

具有不同时间框架的数据源具有不同的长度,在其上操作的指标复制数据的长度。例如:

  • 每年的日数据源大约有 250 个柱状图
  • 每年的周数据源有 52 个柱状图

尝试创建一个操作(例如),比较两个简单移动平均,每个操作在上述引用的数据上运行,会出现问题。不清楚如何将每日时间框架的 250 个柱状图与每周时间框架的 52 个柱状图匹配。

读者可以想象在后台进行date比较,以找出一天 - 一周的对应关系,但是:

  • 指标只是数学公式,没有日期时间信息。
    它们对环境一无所知,只知道如果数据提供足够的值,就可以进行计算。

()(空调用)符号来拯救:

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 日柱状图中

BackTrader 中文文档(二)(2)https://developer.aliyun.com/article/1489215

相关文章
|
5月前
|
Unix 索引 Python
BackTrader 中文文档(一)(2)
BackTrader 中文文档(一)
114 0
|
5月前
|
存储 编解码 API
BackTrader 中文文档(四)(1)
BackTrader 中文文档(四)
64 1
|
5月前
|
存储 安全 Unix
BackTrader 中文文档(四)(2)
BackTrader 中文文档(四)
55 0
|
5月前
BackTrader 中文文档(一)(4)
BackTrader 中文文档(一)
58 0
|
5月前
|
Python
BackTrader 中文文档(五)(4)
BackTrader 中文文档(五)
62 0
|
5月前
|
存储 编解码 网络架构
BackTrader 中文文档(二)(4)
BackTrader 中文文档(二)
86 0
|
5月前
|
索引 Python
BackTrader 中文文档(五)(3)
BackTrader 中文文档(五)
64 0
|
5月前
|
Python
BackTrader 中文文档(六)(3)
BackTrader 中文文档(六)
81 0
|
5月前
|
存储 缓存 Shell
BackTrader 中文文档(二)(3)
BackTrader 中文文档(二)
108 0
|
5月前
BackTrader 中文文档(五)(2)
BackTrader 中文文档(五)
72 0