Backtrader中文笔记之Platform Concepts(平台介绍)。

这里将介绍平台的一些概念的集合。它试图收集在使用该平台时有用的信息位。

在开始之前

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

import backtrader as bt
import backtrader.indicators as btind
import backtrader.feeds as btfeeds

  注意访问子模块的另外方法比如indicators 和 feeds

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)

    ...

  self.data == self.datas[0]

忽略数据传递

上面的示例可以简化如下:

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])

几乎所有的东西是数据饲料

不仅数据饲料是数据,可以四处传递。指标线和行动结果也是数据

在前面的例子中,最简单的平均数是接收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"
    # 操作指标线1 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) ...

  基本上每样东西都可以转换成对象,一旦被操作,它就可以成为数据饲料。

参数

平台中的大多数其他类都支持参数的概念。

参数和默认值被声明为类属性(元组套元组或类似于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)

  使用字典

class MyStrategy(bt.Strategy):
    params = dict(period=20)

    def __init__(self):
        sma = btind.SimpleMovingAverage(self.data, period=self.p.period)

  

Lines

同样平台中几乎所有的其他对象独有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):
# 20日均线的点大于收盘价 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是一个简单的平均指标,它有一个lines属性,该属性依次包含一个sma属性

注意

很明显,行被命名了。也可以按照声明的顺序访问它们,但这只能在指示符开发中使用

同时可以查询close和sma这两条线,取一个点(index 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 and self.movav.sma

但是如果行被直接访问,这个符号就没有像前一个这么清楚了。

注意

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

Lines 声明

 如果正在开发指示符,则必须声明指示符所包含的行。

与params一样,这一次它作为类属性只作为元组发生。字典不受支持,因为它们不按照插入顺序存储内容。

对于简单的移动平均,可以这样做:

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

    ...  

注意

对于简单的移动平均值,可以这样做:如果向元组传递单个字符串,则在元组中需要声明后的逗号,否则字符串中的每个字母将被解释为要添加到元组中的项。这可能是Python语法出错的少数几个地方之一。(元祖不要少了逗号)

如上例所示,此声明在指标中创建了一个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 points to self.data.lines[Y]

  • self.dataX_Y points to self.dataX.lines[X] which is a full shorthard version of self.datas[X].lines[Y]

注意数字的话不用下划线连接

在数据饲料中访问lines

在数据饲料中访问lines可以忽略lines,这使的它更加自然的工作,像收盘价

比如:

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:

同样的道理也不适用于指标,其理由是:

指示符可以具有一个属性close,该属性包含一个中间计算,稍后将该计算传递给同样名为close的实际行

指标使需要计算的。

对于数据提要,不需要进行计算,因为它只是一个数据源。

Lines len

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

这适用于例如:

数据传输

策略

指标

一个附加的属性适用于数据馈送时,数据是预先加载:

方法 buflen

len和buflen的区别

len报告有多少bars已经被处理

buflen报告了为数据饲料加载的条的总数

如果两者返回相同的值,要么没有预加载数据,

要么对条的处理消耗了所有预加载条(除非系统连接到实时提要,否则这将意味着处理结束)

self.data下的数据线使一样的,因为没有预加载数据,通过data下提供的数据做了均线,返回的数值就不一样了,len返回的是处理的数据,buflen返回预加载的数据,也就是所有bar的点数量

Lines与Params的继承

有一种元语言可以支持params和lines的声明。为了使它与标准Python继承规则兼容,已经做出了各种努力

Params继承

继承应该像预期的那样工作:

支持多重继承

继承基类中的参数

如果多个基类定义相同的参数,则使用继承列表中最后一个类的默认值

如果在子类中重新定义相同的参数,则新的默认值将接管基类的默认值

Lines继承

支持多重继承

所有基类中的行都是继承的。如果同一个名称在基类中使用了不止一次,那么该行只有一个版本

索引:0和-1

Lines就想前面看的是线的索引,并且有一组点,它们在一起绘制时符合一条线(比如在沿着时间轴将所有收盘价合并在一起时)

要通过常规代码访问这些点,可以适用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:
    ...

  

请参阅文档后面对操作符的解释。

设置是在开发的时候使用的。比如,一个指标,因为指标的输出用来设置另一个指标

self.data.get(0,xx)返货的是一个当前坐标以前xx位的数组

def next(self):
  self.line[0] = math.fsum(self.data.get(0, size=self.p.period)) / self.p.period  

访问前面设置的点,可以通过Python定义的模型,当访问数组或可迭代对象时,通过-1

这个将指向数组的最后一项

该平台认为最后一个设置项(当前状态get/set点之前)为-1

因此对比当前close与前一个close就是0与-1的事情。在策略中,可以这样

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

  

当谈按照这个逻辑,-2,-3能够方位-1之前的价格

Slicing(切片)

backtrader不支持lines对象的切片,这是遵循[0]和[-1]索引方案的设计决策。使用常规的可索引Python对象,你会做如下事情:

# 这里感觉错误,Python的切片应该是取头不取尾的

myslice = self.my_sma[0:]  # slice from the beginning til the end

  在lines 但是请记住,对于0的选择…它实际上是当前交付的价值,在它之后没有任何东西。另外:

myslice = self.my_sma[0:-1]  # slice from the beginning til the end

  再次…0是当前值,-1是最近(先前)交付的值。这就是为什么从0 -> -1在backtrader的生态系统中毫无意义。

如果切片曾经被支持,它将看起来像:

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

  

前面讲的一堆没用的,下面才是讲到如何切片

Getting a slice(获得切片)

一个数组任然可以获取最新的值,语法如下:

myslice = self.my_sma.get(ago=0, size=1)  # default values shown

  这将返回一个带有1个值(size=1)的array(数组),并讲当前时刻0作为向后查看的起始点

从当前时间获取最近的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:DELAYED indexing

[]操作语法用于在nex方法逻辑中提取单个值。在__init__阶段,Lines对象支持通过一个延迟的对象来处理额外符号地址的值。

我们假设,逻辑的兴趣在于比较简单移动平均的实际值和之前的接近值。与其在下一次迭代中手动操作,还不如生成一个预封闭的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):
# 取值每个点应该是0或1值,成立输出 if self.cmpval[0]: print('Previous close is higher than the moving average')

  

这里使用了(延迟)表示法

这提供了一个副本,是延迟-1的

然后,生成一个对照self.data.close(-1) > self.sma的lines对象,如果条件为真返回1,条件为假返回0

Lines Coupling(耦合)

如上所示,适用带有延迟值的操作符()来生成lines对象的延迟版本.

如果适用的语法没有提供延迟值,则返回一个LinesCoupler lines对象。这意味着在不同的时间框架内对数据指标的操作进行耦合

不同时间框架的数据提要有不同的长度,对它们进行操作的指标线会复制数据的长度。例子:

每天的数据传输每年大约有250个bars

每周的数据feed每年有52bars

尝试创建一个操作(比如)来比较两个简单的移动平均线。在数据上引用,每次操作都会中断。我们不清楚如何将日线框架的250bars与周线的52bars进行匹配。

读者可以想象在在日期比较后台找打了天-周的对应。

指示符只是数学公式,没有日期时间信息

他们对环境一无所知,只知道如果数据提供了足够的值,就可以进行计算。

这个()空调用来拯救我们

class MyStrategy(bt.Strategy):
    params = dict(period=20)

    def __init__(self):

        # data0 is a daily data
        # 来至文件1
        sma0 = btind.SMA(self.data0, period=15)  # 15 days sma
        # data1 is a weekly data
        # 来至文件2
        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中大量的bars,并复制了sma1产生的值,有效的将52的周bars扩展到250个日bars

这个需要两个不同的时间框架表导入

Operators, using natural constructs

操作符,适用自然构造

为了实现“易于使用”的目标,该平台允许(在Python的约束下)使用操作符。为了进一步提高这一目标,对操作符的使用已分两个阶段打破

Stage 1 - Operators Create Objects

阶段1- 操作符创建对象

我们已经看到一个这样的示例,即使它没有表明用于此。在对象的初始化阶段(__init__方法)比如Indicators(指标)、Strategies(策略),操作符可以创建可以操作、分配或保留的对象,以供以后在战略逻辑评估阶段适用。

再一次实现了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
        # 最高价低于3.5
        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
        
# 通过And要求两个逻辑都成立 sell_sig = bt.And(close_over_sma, sma_dist_small)

  

在执行了上述操作之后,sell_sig是一个Lines对象,可以稍后在策略的逻辑中使用,指示条件是否满足。

Stage 2 - Operators true to nature

阶段2 操作符真正的性质

让我们记住,一个策略有一个next方法,该方法被系统进程的每个bar调用。这就是操作符实际上处于阶段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: … compares self.sma[0] to 30.0 (1st line and current value)

if self.sma > self.data.close: … compares self.sma[0] to self.data.close[0]

  如果取当前值[0],把[]去除了

Some non-overriden operators/functions

一些没有被重写的操作符方法

Python不允许覆盖所有内容,因此提供了一些函数来处理这种情况。

注意

Only meant to be used during Stage 1, to create objects which later provide values.

仅在阶段1中使用,用于创建以后提供值的对象。

Operators:

  • and -> And

  • or -> Or

Logic Control:

  • if -> If

Functions:

  • any -> Any

  • all -> All

  • cmp -> Cmp

  • max -> Max

  • min -> Min

  • sum -> Sum

总和实际使用math.fsum作为底层的操作,因为平台浮点数和应用规则和可能对精度产生影响的作品。

  • reduce -> Reduce

这些实用程序操作符/函数对迭代进行操作。迭代器中的元素可以是常规的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的用法。

Using 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)

  分解:

基于收盘价生成15日均线SMA

然后

  • bt.If the value of the sma is larger than close, return low, else return high
  • bt.If如果收盘价大于15日均线价返回最低价,否则返回最高价。
  • Remember that no actual value is being returned when bt.If is being invoked. It returns a Lines object which is just like a SimpleMovingAverage.

请记住正在调用bt.if返回的没有实际价格。它返回了一个LINES对象,就像SimpleMovingAverage

The values will be calculated later when the system runs

这些值将在售后系统运行时计算

The generated bt.If Lines object is then fed to a 2nd SMA which will sometimes use the low prices and sometimes the high prices for the calculation

生成的bt.If Lines对象然后被送入第二SMA,该SMA有时使用低价格,有时使用高价格进行计算

Those functions take also numeric values. The same example with a modification:

这些函数也接受数值。同样的例子有一个修改:

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)

  

Now the 2nd moving average uses either 30.0 or the high prices to perform the calculation, depending on the logic status of sma vs close

现在第二移动平均线使用30.0或高价格执行计算,取决于sma vs close的逻辑状态

注意

The value 30 is transformed internally into a pseudo-iterable which always returns 30

值30在内部被转换为一个总是返回30的伪迭代

原文地址:https://www.cnblogs.com/sidianok/p/13440799.html