Python量化交易学习笔记(55)——backtrader的一些基本概念3

本文继续记录bt相关的概念内容。

切片

由于在设计时,bt使用了前文所提到[0]和[-1]索引的模式,导致bt不支持对Lines对象进行切片的操作,也就是不可以通过以下方式访问Lines:

myslice = self.my_sma[x:y]  # 不支持此切片操作 

获取切片

不过bt也提供了自己获取切片的方式,下面展示几个例子。

myslice = self.my_sma.get(ago=0, size=1)  # ago和size均为默认值

get的参数ago表示获取数据的起点,ago=0即为当前最新的数据,size表示获取数据的个数,size=1表示只获取1个数据。因此,上面的代码就表示只获取当前最新的1个数据,也就是当前值。

如果想获取最近的10个值,则可以使用下面的代码获得:

myslice = self.my_sma.get(size=10)  # ago默认为0

通过get得到的数据是1个array(不是Lines对象),最左侧数据为最早的数据,最右侧数据为最新的数据。

如果想跳过当前数据,获取之前的10个数据,可以用下面的代码实现:

myslice = self.my_sma.get(ago=-1, size=10)

Lines的延迟索引

在Strategy的next函数中,使用[]运算符可以提取Lines的单个值。此外,在Strategy的__init__阶段,Lines支持通过延迟索引来访问延时的Lines对象。

比如,某个Strategy要比较前1日的收盘价和当日的均线的大小关系,那么可以在__init__阶段进行如下实现:

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

这里就用到了Lines的延迟索引,通过self.data.close(-1)将close进行复制,并且做延时-1,生成了1个延迟的Lines对象。在该对象中,前1组close数据与当前时刻对齐,前2组close数据与前1时刻对齐,依次类推。然后通过运算符计算self.data.close(-1) > self.sma得到1个新的Lines对象self.cmpval。

Lines耦合

Lines耦合主要用于解决不同时间段(分时、日、周、月)数据长度不一致的问题。

在Lines的延迟索引中,使用运算符()结合参数延迟长度值delay来生成延迟的Lines对象。如果只使用运算符(),而不使用参数,则会生成1个类型为LinesCoupler的lines对象,来建立不同时间段的数据耦合对应关系。

举例说明

不同时间段的Data Feeds有不同的长度,技术指标(Indicator)在同时处理不同时间段的Data Feeds时就需要复制部分数据,例如:

  • 每年大概有250条左右日(daily)数据
  • 每年大概有52条左右周(weekly)数据

假设策略要比较日均线与周均线的大小,如果不做处理,Indicator无法将250条日线数据如何和52条周线数据耦合成对。这就是引入Lines耦合的原因,通过使用运算符(),来对不同时间段数据进行耦合成对:

class MyStrategy(bt.Strategy):
    params = dict(period=20)
    def __init__(self):
        # data0 是日线数据
        sma0 = btind.SMA(self.data0, period=15)  # 15日均线
        # data1 是周线数据
        sma1 = btind.SMA(self.data1, period=5)  # 5周均线
        # 不同时间段数据对齐后比较
        self.buysig = sma0 > sma1()
    def next(self):
        if self.buysig[0]:
            print('daily sma is greater than weekly sma1')

这里对大时间段(周线)数据sma1使用运算符(),即sma1(),通过复制sma1的数据,将sma1与小时间段(日线)数据sma0进行耦合成对。

运算符(Operators)

在bt中,不同阶段使用运算符会得到不同的结果。

阶段1 - 生成新对象

阶段1对应于__init__阶段。

在Indicator和Strategy的__init__阶段,运算符的计算结果是新的对象。仍以SimpleMovingAverage技术指标为例,它的__init__可能像如下方式实现:

def __init__(self):
    # 累加period周期的值 - datasum是1个Lines对象
    datasum = btind.SumN(self.data, period=self.params.period)
    # datasum (仅有1条line的Lines对象) 
    # 用于除以1个int/float类型的值(也可以用于除以另1个Lines对象)
    # 得到1个新的Lines对象,被赋给av
    av = datasum / self.params.period
    # 将已被该指标声明的line sma赋值为av
    self.line.sma = av

再来看一个在Strategy初始化中使用运算符的例子:

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
        # 在Python中 'and' 是不能被重写的,因此bt中提供了bt.And函数来实现与运算
        sell_sig = bt.And(close_over_sma, sma_dist_small)

代码中的’>’、’-’、’<'运算符均会生成新的对象,最后通过bt.And生成新的Lines对象sell_sig,在后续的策略逻辑中就可以根据sell_sig的值判断是否卖出。

阶段2 - 返回预期的值

阶段2对应于next函数阶段。

在next函数阶段,运算符就按照Python本身的运算符功能进行工作,计算结果就是运算符预期的返回值。基于上文中的例子:

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
        self.sell_sig = bt.And(close_over_sma, sma_dist_small)
    def next(self):
        # 条件判断使用的运算符,返回True或者False
        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:  # 如果用sell_sig == True 也可以
            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')

代码中的’>’、’<'运算符会按照计算结果返回True或者False。同样如果在next中使用其他算术运算符(+、-、*、/ 等等),这些运算符也会按照Python标准的计算功能得到运算结果。

需要说明的是,上面的代码使用了部分快捷简写方式:

  • if self.sma > 30.0: … 比较的是 self.sma[0] 和 30.0 的大小关系
  • if self.sma > self.data.close: … 比较的是 self.sma[0] 和 self.data.close[0] 的大小关系

非重写运算符/函数

由于无法重写Python中的一些运算符和函数,bt实现了对应的函数来提供相关功能,这些函数将只在上面提到的阶段1中起作用,下面是对应函数的列表。

运算符

  • and -> And
  • or -> Or

逻辑控制

  • if -> If

函数

  • any -> Any
  • all -> All
  • cmp -> Cmp
  • max -> Max
  • min -> Min
  • sum -> Sum
  • reduce -> Reduce

这些运算符和函数都是作用在可迭代对象上的,可迭代对象的元素可以是普通的Python数值类型(ints,floats等等),也可以是Lines对象。

一个简单的使用bt.And生成买入信号示例:

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

使用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)

分解开看:

  • 首先生成1个SMA对象sma1
  • 然后使用bt.If判断sma1是否大于收盘价close,如果是,则返回low,否则返回high。这里bt.If返回的是一个Lines对象,并赋给high_or_low。当bt.If被调用时,不会有实际的值返回,当bt系统运行后,这些值才会被真正的计算。
  • bt.If返回的Lines对象被送到第2个SMA中用于计算sma2,在计算时有时使用最低值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)

这里把前一个例子进行了简单修改,使用bt.If判断sma1是否大于收盘价close,如果是,则返回30.0,否则返回high。这里实际上是bt在内部把30转化为一个始终返回30的伪可迭代对象来实现的。

欢迎大家关注、点赞、转发、留言,感谢支持!
微信群用于学习交流,感兴趣的读者请扫码加微信!
QQ群(676186743)用于资料共享,欢迎加入!

在这里插入图片描述
在这里插入图片描述

Guess you like

Origin blog.csdn.net/m0_46603114/article/details/118881393