本文继续记录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的伪可迭代对象来实现的。
博客内容只用于交流学习,不构成投资建议,盈亏自负!
个人博客:http://coderx.com.cn/(优先更新)
项目最新代码:https://gitee.com/sl/quant_from_scratch
欢迎大家转发、留言。有微信群用于学习交流,感兴趣的读者请扫码加微信!
如果认为博客对您有帮助,可以扫码进行捐赠,感谢!
微信二维码 | 微信捐赠二维码 |
---|---|