指标研究与多周期
哪些地方会用到指标 ?
回顾一下 Backtrader 的主要功能模块和回测流程(见:Backtrader 来了!)可以发现,只有在编写策略Strategy 时才会涉及到指标的计算和使用,而且是 Strategy 中的 __init__() 和 next() 方法涉及的最多。
建议在 __init__() 中提前计算指标
Strategy 中的 __init__() 函数在回测过程中只会在最开始的时候调用一次,
而 next() 会每个交易日依次循环调用多次,所以为了提高回测效率,建议先在 __init__() 中一次性计算好指标(甚至是交易信号),
然后在 next() 中调用已经算好的指标,这样能有效避免指标的重复计算,提高回测运行速度。建议遵循“__init__() 负责指标计算,next() 负责指标调用”的原则。
import backtrader.indicators as btind # 导入策略分析模块
class MyStrategy(bt.Strategy):
# 先在 __init__ 中提前算好指标
def __init__(self):
sma1 = btind.SimpleMovingAverage(self.data)
ema1 = btind.ExponentialMovingAverage()
close_over_sma = self.data.close > sma1
close_over_ema = self.data.close > ema1
sma_ema_diff = sma1 - ema1
# 生成交易信号
buy_sig = bt.And(close_over_sma, close_over_ema, sma_ema_diff > 0)
# 在 next 中直接调用计算好的指标
def next(self):
if buy_sig:
self.buy()
计算指标时的各种简写形式
class TestStrategy(bt.Strategy):
def __init__(self):
# 最简方式:直接省略指向的数据集
self.sma1 = btind.SimpleMovingAverage(period=5)
# 只指定第一个数据表格
self.sma2 = btind.SMA(self.data, period=5)
# 指定第一个数据表格的close 线
self.sma3 = btind.SMA(self.data.close, period=5)
# 完整写法
self.sma4 = btind.SMA(self.datas[0].lines[0], period=5)
# 指标函数也支持简写 SimpleMovingAverage → SMA
def next(self):
# 提取当前时间点
print('datetime', self.datas[0].datetime.date(0))
# 打印当日、昨日、前日的均线
print('sma1',self.sma1.get(ago=0, size=3))
print('sma2',self.sma2.get(ago=0, size=3))
print('sma3',self.sma3.get(ago=0, size=3))
print('sma4',self.sma4.get(ago=0, size=3))
cerebro = bt.Cerebro()
st_date = datetime.datetime(2019,1,2)
end_date = datetime.datetime(2021,1,28)
datafeed1 = bt.feeds.PandasData(dataname=data1, fromdate=st_date, todate=end_date)
cerebro.adddata(datafeed1, name='600466.SH')
datafeed2 = bt.feeds.PandasData(dataname=data2, fromdate=st_date, todate=end_date)
cerebro.adddata(datafeed2, name='603228.SH')
cerebro.addstrategy(TestStrategy)
rasult = cerebro.run()
调用指标时的各种简写形式
调用指标时会涉及 line 的索引和切片操作,为了使操作更加简便,
在 next() 中调用当前时刻指标值时,可以省略索引 [0] :即在 next() 中,self.sma5[0] ↔ self.sma5、self.data.close[0] ↔ self.data.close 等都是等价的,
省略了 [0] 的简写形式 self.sma5 、 self.data.close 等都默认指向当前值,自动索引当前值
class TestStrategy(bt.Strategy):
def __init__(self):
self.sma5 = btind.SimpleMovingAverage(period=5) # 5日均线
self.sma10 = btind.SimpleMovingAverage(period=10) # 10日均线
self.buy_sig = self.sma5 > self.sma10 # 5日均线上穿10日均线
def next(self):
# 提取当前时间点
print('datetime', self.datas[0].datetime.date(0))
# 打印当前值
print('close', self.data.close[0], self.data.close)
print('sma5', self.sma5[0], self.sma5)
print('sma10', self.sma10[0], self.sma10)
print('buy_sig', self.buy_sig[0], self.buy_sig)
# 比较收盘价与均线的大小
if self.data.close > self.sma5:
print('------收盘价上穿5日均线------')
if self.data.close[0] > self.sma10:
print('------收盘价上穿10日均线------')
if self.buy_sig:
print('------ buy ------')
cerebro = bt.Cerebro()
st_date = datetime.datetime(2019,1,2)
end_date = datetime.datetime(2021,1,28)
datafeed1 = bt.feeds.PandasData(dataname=data1, fromdate=st_date, todate=end_date)
cerebro.adddata(datafeed1, name='600466.SH')
cerebro.addstrategy(TestStrategy)
rasult = cerebro.run()
class TestStrategy(bt.Strategy):
def __init__(self):
self.sma5 = btind.SimpleMovingAverage(period=5) # 5日均线
self.sma10 = btind.SimpleMovingAverage(period=10) # 10日均线
# bt.And 中所有条件都满足时返回 1;有一个条件不满足就返回 0
self.And = bt.And(self.data>self.sma5, self.data>self.sma10, self.sma5>self.sma10)
# bt.Or 中有一个条件满足时就返回 1;所有条件都不满足时返回 0
self.Or = bt.Or(self.data>self.sma5, self.data>self.sma10, self.sma5>self.sma10)
# bt.If(a, b, c) 如果满足条件 a,就返回 b,否则返回 c
self.If = bt.If(self.data>self.sma5,1000, 5000)
# bt.All,同 bt.And
self.All = bt.All(self.data>self.sma5, self.data>self.sma10, self.sma5>self.sma10)
# bt.Any,同 bt.Or
self.Any = bt.Any(self.data>self.sma5, self.data>self.sma10, self.sma5>self.sma10)
# bt.Max,返回同一时刻所有指标中的最大值
self.Max = bt.Max(self.data, self.sma10, self.sma5)
# bt.Min,返回同一时刻所有指标中的最小值
self.Min = bt.Min(self.data, self.sma10, self.sma5)
# bt.Sum,对同一时刻所有指标进行求和
self.Sum = bt.Sum(self.data, self.sma10, self.sma5)
# bt.Cmp(a,b), 如果 a>b ,返回 1;否则返回 -1
self.Cmp = bt.Cmp(self.data, self.sma5)
def next(self):
print('---------- datetime',self.data.datetime.date(0), '------------------')
print('close:', self.data[0], 'ma5:', self.sma5[0], 'ma10:', self.sma10[0])
print('close>ma5',self.data>self.sma5, 'close>ma10',self.data>self.sma10, 'ma5>ma10', self.sma5>self.sma10)
print('self.And', self.And[0], self.data>self.sma5 and self.data>self.sma10 and self.sma5>self.sma10)
print('self.Or', self.Or[0], self.data>self.sma5 or self.data>self.sma10 or self.sma5>self.sma10)
print('self.If', self.If[0], 1000 if self.data>self.sma5 else 5000)
print('self.All',self.All[0], self.data>self.sma5 and self.data>self.sma10 and self.sma5>self.sma10)
print('self.Any', self.Any[0], self.data>self.sma5 or self.data>self.sma10 or self.sma5>self.sma10)
print('self.Max',self.Max[0], max([self.data[0], self.sma10[0], self.sma5[0]]))
print('self.Min', self.Min[0], min([self.data[0], self.sma10[0], self.sma5[0]]))
print('self.Sum', self.Sum[0], sum([self.data[0], self.sma10[0], self.sma5[0]]))
print('self.Cmp', self.Cmp[0], 1 if self.data>self.sma5 else -1)
cerebro = bt.Cerebro()
st_date = datetime.datetime(2019,1,2)
ed_date = datetime.datetime(2021,1,28)
datafeed1 = bt.feeds.PandasData(dataname=data1, fromdate=st_date, todate=ed_date)
cerebro.adddata(datafeed1, name='600466.SH')
cerebro.addstrategy(TestStrategy)
rasult = cerebro.run()
如何对齐不同周期的指标
通常情况下,操作的都是相同周期的数据,比如日度行情数据计算返回各类日度指标、周度行情数据计算返回各类周度指标、......,行情数据和指标的周期是一致的,时间也是对齐的。
但有时候也会遇到操作不同周期数据的情况,比如拿日度行情与月度指标作比较,日度行情每天都有数据,而月度指标每个月只有一个,2 条数据在时间上是没有对齐的,如下所示:
可以使用“ ( ) ”语法操作来对齐不同周期的数据,对齐的方向是“大周期向小周期对齐”,
可以选择指标对象中的某条 line 进行对齐,也可以对整个指标对象进行对齐。
在使用该语法时,要将 cerebro.run() 中的 runonce 设置为 False,才能实现对齐操作:
# self.data0 是日度行情、self.data1 是月度行情
self.month = btind.xxx(self.data1) # 计算返回的 self.month 指标也是月度的
# 选择指标对象中的第一条 line 进行对齐
self.sellsignal = self.data0.close < self.month.lines[0]()
# 对齐整个指标对象
self.month_ = self.month()
self.signal = self.data0.close < self.month_.lines[0]
cerebro.run(runonce=False)