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
理解: 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 中定义的继承顺序:
- from .linebuffer import LineActions
- class Logic(LineActions): # 逻辑运算的基类 就是 Logic ,继承了LineActions
- class MultiLogic(Logic): # MultiLogic 继承了Logic
- class MultiLogicReduce(MultiLogic): # MultiLogicReduce 继承了MultiLogic
- 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 。
对象继承顺序:
- from . import Indicator # 导入Indicator 类
- class PeriodN(Indicator): # 建基类 PeriodN
- class OperationN(PeriodN): # OperationN继承PeriodN
- 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