Backtrader 量化回测实践(3)—— 主要对象理解

Backtrader 量化回测实践(3)—— 主要对象理解

在策略中使用的大部分数据形式:包括Indicator 、signal 、逻辑操作符、数学计算、切片访问的研究测试。
重新回顾一下使用的主要对象,理解对象的性质,如何在实践中应用 。

1.Data Feeds

首先是data ,数据对象的核心是Lines 。数据元素则是python的数据类型:float/ string / int

        print('data:')
        print(type(self.data))
        print(type(self.data.close))
        print(type(self.data.close[0]))

结果是:

data:
<class ‘backtrader.feeds.pandafeed.PandasData’>
<class ‘backtrader.linebuffer.LineBuffer’>
<class ‘float’>

2.Strategy

在策略中,将使用Indicator ,signal ,数据计算,数据切片的处理,数据处理之后的对象是什么类型,将影响在策略中如何合理使用这些对象。

(1)Indicator

最简单的Indicator ,就是是内置的方法,使用SMA指示器:

        # indicator
        self.sma1 = btind.SimpleMovingAverage(self.data.close, period=self.p.period2)
        self.sma2 = btind.SimpleMovingAverage(self.data.close, period=self.p.period1)

看一下sma1 和 sma2的数据类型

sma:
<class ‘backtrader.indicators.sma.SimpleMovingAverage’>
<class ‘backtrader.indicators.sma.SimpleMovingAverage’>

定义的指示器的SimpleMovingAverage 类型。
从源代码中,sma.py中,类的初始化部分,说明SimpleMovingAverage也是lines的子类:

self.lines[0] = Average(self.data, period=self.p.period)

初始化代码:

    def __init__(self):
        # Before super to ensure mixins (right-hand side in subclassing)
        # can see the assignment operation and operate on the line
        self.lines[0] = Average(self.data, period=self.p.period)

        super(MovingAverageSimple, self).__init__()

再看一下sma的长度:

print(self.sma1.buflen(),self.sma2.buflen(),self.data.close.buflen())

结果:

1217 1217 1217

参考Backtrader的数据类型说明

理解: data和sma 都是 一样的长度,实际上data是pandas的dataframe ,只不过data.close是索引为日期的dataframe ,而sma是索引为数字的dataframe 。都是在Backtrader中被重写定义了新的数据类型。
但是两者之间的数据关系,是一一对应的。
在next()中,数据的日期和sma数据是对应的。

print(self.data0.datetime.date(),self.sma1[0],self.signal[0])     

结果:

2016-02-19 23.8685 0.0

理解:
data0 :数据源0
self.data0.datetime.date():当前数据的索引datetime的值
sma1[0] :sma1当前的数据值
在next()执行过程中,同步移动。

(2)signal

在init中直接生成操作信号标志:

        # indicator signal 
        self.signal = btind.CrossOver(self.sma2, self.sma1)
        self.signal1 = btind.CrossUp(self.data.close, self.sma1)
        self.signal2 = btind.CrossOver(self.data.close, self.sma2)
        
        # And 逻辑判断
        self.signal3 = bt.And(self.signal1,self.signal2)
        
        print('signal:')
        print(type (self.signal),'\n',type(self.signal1),'\n',type(self.signal2),'\n',type(self.signal3))

执行结果:

signal:
<class 'backtrader.indicators.crossover.CrossOver'> 
 <class 'backtrader.indicators.crossover.CrossUp'> 
 <class 'backtrader.indicators.crossover.CrossOver'> 
 <class 'backtrader.functions.And'>

源代码分析:
在crossover.py中基类_CrossBase定义:

class _CrossBase(Indicator):
    _mindatas = 2

    lines = ('cross',)

    plotinfo = dict(plotymargin=0.05, plotyhlines=[0.0, 1.0])

    def __init__(self):
        nzd = NonZeroDifference(self.data0, self.data1)

        if self._crossup:
            before = nzd(-1) < 0.0  # data0 was below or at 0
            after = self.data0 > self.data1
        else:
            before = nzd(-1) > 0.0  # data0 was above or at 0
            after = self.data0 < self.data1

        self.lines.cross = And(before, after)

可以看到,_CrossBase使用的数据是self.data ,所以运算后的数据,也是基于self.data的数据类型。

self.lines.cross = And(before, after)
1 ,True ,就是向上
0,False,就是向下

在CrossOver类中,初始化:


    def __init__(self):
        upcross = CrossUp(self.data, self.data1)
        downcross = CrossDown(self.data, self.data1)

        self.lines.crossover = upcross - downcross

因此,处理后的数据都是lines 。

(3)逻辑操作符

不同信号之间的逻辑判断,例如:

        # And 逻辑判断
        self.signal3 = bt.And(self.signal1,self.signal2)

self.signal3的数据类型是:

<class ‘backtrader.functions.And’>

看源码,functions.py 中定义的继承顺序:

  1. from .linebuffer import LineActions
  2. class Logic(LineActions): # 逻辑运算的基类 就是 Logic ,继承了LineActions
  3. class MultiLogic(Logic): # MultiLogic 继承了Logic
  4. class MultiLogicReduce(MultiLogic): # MultiLogicReduce 继承了MultiLogic
  5. class And(MultiLogicReduce): # And类继承了 MultiLogicReduce

理解:
逻辑运算两个Lines的数据类型,之后生成新的数据类型<class ‘backtrader.functions.And’> ,实际上也是一个lines的数据类的子类。
在next()执行过程中,索引是0 ,同步数据移动 。

(4)数学
        # SumN 数学计算
        self.average = btind.SumN(self.data.close,period=self.p.period3) / self.p.period3 
        print('math:')
        print(type(self.average))
        print(self.average[0])

结果:

math:
<class ‘backtrader.linebuffer.LinesOperation’>
24.171666666666667

查看源代码,在Indicators下basicops.py 。
对象继承顺序:

  1. from . import Indicator # 导入Indicator 类
  2. class PeriodN(Indicator): # 建基类 PeriodN
  3. class OperationN(PeriodN): # OperationN继承PeriodN
  4. class Highest(OperationN): class Lowest(OperationN): class SumN(OperationN): # 常用的数据比较计算

理解:
最大值,最小值,求和,之后生成新的数据类型<class ‘backtrader.linebuffer.LinesOperation’>,实际上也是一个Indicator的数据类的子类,归根结底还是Lines。
在next()执行过程中,索引是0 ,同步数据移动 。

(5)切片

Backtrader使用了[0]和[-1]索引的模式,导致bt不支持对Lines对象进行切片的操作,不能以下方式访问Lines:

self.data.close[a:b]

说明:
get的参数ago表示获取数据的起点,ago=0即为当前最新的数据,size表示获取数据的个数,size=1表示只获取1个数据。因此,上面的代码就表示只获取当前最新的1个数据,就是当前值。
如果没有ago参数,就表示从当前默认0 开始。

返回数据:
通过get得到的数据是array,不是Lines对象,最左侧数据为最早的数据,最右侧数据为最新的数据。
例子:
从当前,取3个数据
从前1个,取5个数据
从前2个,取5个数据

     mysma1 = self.sma1.get(ago=0, size=3)  # ago和size
     myclose = self.data.close.get(ago=-1, size=5)  # ago和size
     mysignal = self.signal.get(ago=-2, size=5)  # ago和size
     print('ago size:')
     print(mysma1)
     print(myclose)
     print(mysignal)

结果:

ago size:
array(‘d’, [23.8835, 23.8685, 23.863999999999997])
array(‘d’, [23.27, 24.04, 23.94, 23.45, 23.3])
array(‘d’, [0.0, 0.0, -1.0, 0.0, 0.0])

说明:
signal 信号,其实是以0 1的方式表示在array中。
通过get方法的数据,就是numpy的array,操作就是同numpy的计算。

3.小结

  • 在Strategy 中使用的Indicator 、signal ,逻辑操作符,数学计算,都是lines的子类,在next()中,都是[0] [-1] ,操作同data 。
  • 对lines的data用get方法,取得的数据是array数据,按numpy的操作计算即可。

4.测试代码

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

class MyStrategy(bt.Strategy):
    params = (
        ('period1', 5),
        ('period2', 20),
        ('period3', 30),
    )
    def __init__(self):
        # indicator
        self.sma1 = btind.SimpleMovingAverage(self.data.close, period=self.p.period2)
        self.sma2 = btind.SimpleMovingAverage(self.data.close, period=self.p.period1)
        print('data:')
        print(type(self.data))
        print(type(self.data.close))
        print(type(self.data.close[0]))
        
        
        print('sma:')
        print(type (self.sma1),'\n',type(self.sma2))
        
        # indicator signal 
        self.signal = btind.CrossOver(self.sma2, self.sma1)
        self.signal1 = btind.CrossUp(self.data.close, self.sma1)
        self.signal2 = btind.CrossOver(self.data.close, self.sma2)
        
        # And 逻辑判断
        self.signal3 = bt.And(self.signal1,self.signal2)
        
        print('signal:')
        print(type (self.signal),'\n',type(self.signal1),'\n',type(self.signal2),'\n',type(self.signal3))
        
        
        # SumN 数学计算
        self.average = btind.SumN(self.data.close,period=self.p.period3) / self.p.period3 
        print('math:')
        print(type(self.average))
        

    def next(self):
        print(self.sma1.buflen(),self.sma2.buflen(),self.data.close.buflen())
        print(len(self.sma1),len(self.sma2),len(self.data.close))
        print('date ,signal:')
        print(self.data0.datetime.date(),self.sma1[0],self.signal[0])     
        print('sma:')
        print(self.sma1.getlinealiases())
        print(type(self.sma1[0]))
        
        
        # 没有仓位,可以买入
        if not self.position :
            if self.data.close > self.sma2 : # self.data.close[0] > self.sma2[0] 
                self.buy()
                print('Buy:',self.data.datetime.date(),self.sma1[0],self.sma2[-1],self.signal[-2])
                mysma1 = self.sma1.get(ago=0, size=3)  # ago和size
                myclose = self.data.close.get(ago=-1, size=5)  # ago和size
                mysignal = self.signal.get(ago=-2, size=5)  # ago和size
                print('ago size:')
                print(mysma1)
                print(myclose)
                print(mysignal)
                print(self.average[0])

        # 有仓位,可以卖出        
        if self.position :
            if self.data.close < self.sma2 :
                self.sell()
                print('Sell:',self.data.datetime.date(),self.sma1[0],self.sma2[0],self.signal[0])
                self.line[0] = math.fsum(self.data.get(0, size=self.p.period1)) 

if __name__ == '__main__':            
    cerebro = bt.Cerebro()
    # 初始资金 100,000,000
    cerebro.broker.setcash(100000.0)

    # 获取数据
    start_date = datetime.datetime(2016, 1, 1)  # 回测开始时间
    end_date = datetime.datetime(2020, 12, 31)  # 回测结束时间
    stock_df = get_code('000858.SZ')

    data1 = btfeeds.PandasData(dataname=stock_df, fromdate=start_date, todate=end_date)  # 加载数据
    cerebro.adddata(data1)
    
    cerebro.addstrategy(MyStrategy)
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
    # 遍历所有数据
    cerebro.run()
    # 打印最后结果
    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())    
                

结果示例:

Starting Portfolio Value: 100000.00
data:
<class 'backtrader.feeds.pandafeed.PandasData'>
<class 'backtrader.linebuffer.LineBuffer'>
<class 'float'>
sma:
<class 'backtrader.indicators.sma.SimpleMovingAverage'> 
 <class 'backtrader.indicators.sma.SimpleMovingAverage'>
signal:
<class 'backtrader.indicators.crossover.CrossOver'> 
 <class 'backtrader.indicators.crossover.CrossUp'> 
 <class 'backtrader.indicators.crossover.CrossOver'> 
 <class 'backtrader.functions.And'>
math:
<class 'backtrader.linebuffer.LinesOperation'>
1217 1217 1217
30 30 30
date ,signal:
2016-02-19 23.8685 0.0
sma:
('sma',)
<class 'float'>
1217 1217 1217
31 31 31
date ,signal:
2016-02-22 23.863999999999997 0.0
sma:
('sma',)
<class 'float'>
Buy: 2016-02-22 23.863999999999997 23.6 0.0
ago size:
array('d', [23.8835, 23.8685, 23.863999999999997])
array('d', [23.27, 24.04, 23.94, 23.45, 23.3])
array('d', [0.0, 0.0, -1.0, 0.0, 0.0])
24.171666666666667
1217 1217 1217
32 32 32
date ,signal:
2016-02-23 23.8185 0.0
sma:
('sma',)
<class 'float'>
1217 1217 1217
33 33 33
date ,signal:
2016-02-24 23.7975 0.0
sma:
('sma',)
<class 'float'>
1217 1217 1217
34 34 34
date ,signal:
2016-02-25 23.7495 0.0
sma:
('sma',)
<class 'float'>
Sell: 2016-02-25 23.7495 23.684 0.0
1217 1217 1217
35 35 35
date ,signal:
2016-02-26 23.7175 0.0
sma:
('sma',)
<class 'float'>
1217 1217 1217
36 36 36
date ,signal:
2016-02-29 23.6185 0.0
sma:
('sma',)
<class 'float'>
1217 1217 1217
37 37 37
date ,signal:
2016-03-01 23.5825 0.0
sma:
('sma',)
<class 'float'>
1217 1217 1217
38 38 38
date ,signal:
2016-03-02 23.6285 0.0
sma:
('sma',)
<class 'float'>
Buy: 2016-03-02 23.6285 23.2 0.0
ago size:
array('d', [23.6185, 23.5825, 23.6285])
array('d', [24.19, 23.08, 23.44, 22.46, 22.83])
array('d', [0.0, 0.0, 0.0, 0.0, 0.0])
23.766333333333332
1217 1217 1217
39 39 39
date ,signal:
2016-03-03 23.7195 0.0
sma:
('sma',)
<class 'float'>
1217 1217 1217
40 40 40
date ,signal:
2016-03-04 23.794999999999998 0.0
sma:
('sma',)
<class 'float'>
1217 1217 1217
41 41 41
date ,signal:
2016-03-07 23.862000000000002 1.0
sma:
('sma',)
<class 'float'>
1217 1217 1217
42 42 42
date ,signal:
2016-03-08 23.905 0.0
sma:
('sma',)
<class 'float'>
1217 1217 1217
43 43 43
date ,signal:
2016-03-09 23.93 0.0
sma:
('sma',)
<class 'float'>
Sell: 2016-03-09 23.93 24.976 0.0
1217 1217 1217
44 44 44
date ,signal:
2016-03-10 23.971 0.0
sma:
('sma',)
<class 'float'>
Buy: 2016-03-10 23.971 24.976 0.0
ago size:
array('d', [23.905, 23.93, 23.971])
array('d', [24.5, 25.17, 25.36, 25.06, 24.79])
array('d', [0.0, 0.0, 0.0, 1.0, 0.0])
23.920333333333335
1217 1217 1217
45 45 45
date ,signal:
2016-03-11 24.057 0.0
sma:
('sma',)
<class 'float'>
1217 1217 1217
46 46 46
date ,signal:
2016-03-14 24.166 0.0
sma:
('sma',)
<class 'float'>
1217 1217 1217
47 47 47
date ,signal:
2016-03-15 24.240000000000002 0.0
  • 18
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值