本文继续记录bt相关的概念内容。
Lines
顾名思义,Lines就是线,比如一段时期的收盘价是一条线,一段时期的成交量是一条线,一段时期的5日均线也是一条线,股票软件上的所有技术指标基本都可以处理为线。在bt中也是主要通过访问线(Lines)来进行各种计算。通过一个小例子来看一下Lines在bt中的使用:
class MyStrategy(bt.Strategy):
params = dict(period=20)
def __init__(self):
self.movav = btind.SimpleMovingAverage(self.data, period=self.p.period)
def next(self):
if self.movav.lines.sma[0] > self.data.lines.close[0]:
print('Simple Moving Average is greater than the closing price')
在上面的代码中,有两处访问了Lines对象:
- self.movav是一个均线(SimpleMovingAverage)技术指标对象,包含一个lines属性,在lines中又包含sma这样一条line
- self.data包含一个lines属性,在lines中又包含close这样一条line
像Data Feeds一样,Lines也有快捷访问的方式:
- xxx.l 对应于xxx.lines
- xxx.lines_name 对应于xxx.lines.name
- Strategy和Indicator这样复杂的对象提供了对Lines的快捷访问方式:
– self.data_name 对应于self.data.lines.name
– self.data1_name对应于self.data1.lines.name - 也可以直接使用lines的名字而不显式的使用 .l 或者 .lines 进行访问,例如:self.data.close(对应于self.data.lines.close)、self.movav.sma(对应于self.movav.lines.sma)
需要注意的是,后两种访问方式是不支持赋值(Setting/Assigning)的。
声明Lines
在开发新的Indicator时,Lines需要被声明。前面介绍了参数(Parameter)在类内被定义时,可以使用元组和字典两种形式,而Lines的声明只支持使用元组的形式,例如定义均线(SimpleMovingAverage)的代码如下:
class SimpleMovingAverage(Indicator):
lines = ('sma',)
...
代码里声明了sma这样一条line,后续sma就可以被Strategy或者其他的Indicator进行访问了。这里需要注意,代码中’sma’后面的逗号不能省略,测试后会发现如果逗号被省略lines会被Python3.8解析为string,而不是元组。
Lines是按声明的顺序保存在Indicator中的,bt除了支持使用名字来访问Lines外,还可以按照声明顺序用索引访问Lines,比如在上面的例子中,self.lines[0]就对应的是self.lines.sma。如果有更多Lines,那就可以用中括号[]加上1、2等等来进行索引访问。
Lines的其他快捷访问方式有:
- self.line 对应于self.lines[0]
- self.lineX 对应于self.lines[X]
- self.line_X 对应于self.lines[X]
上面是Lines在Indicator中的快捷访问方式,在Data Feeds中的Lines也有如下快捷访问方式可用:
- self.dataY 对应于self.data.lines[Y]
- self.dataX_Y 对应于self.dataX.lines[X]、self.datas[X].lines[Y]
在Data Feeds中访问Lines
在Data Feeds中访问Lines时,代码中经常会省略不写lines,例如:
data = btfeeds.BacktraderCSVData(dataname='mydata.csv')
...
class MyStrategy(bt.Strategy):
...
def next(self):
if self.data.close[0] > 30.0:
...
这里的self.data.close[0]实际上是self.data.lines.close[0]的简写,但是看上去更简短自然。
需要注意的是,**在Data Feeds中,我们可以代码里省略掉lines,但是在Indicator中访问Lines对象时,就不能省略。**原因是bt在实现Indicator的过程中,可能使用了类似于close之类的成员变量保存了一些中间计算结果,如果使用时在代码中省略掉lines(self.close)来访问这些变量,可能会产生错误或者非预期的结果。在Data Feeds中就不存在这些问题,lines只是一系列的数据源。
Lines的长度函数
主要涉及len和buflen两个函数:
- len返回的是已经被处理过的K线数目,可以应用于Data Feeds、Strategy、Indicator
- buflen返回的是预加载后的K线总数,只能应用于Data Feeds
这样有两种可能len和buflen返回相同的值(不考虑实时在线Data Feeds的情况):
- 没有数据被加载
- 所有加载数据都已经被处理过
Lines和Parameters的继承
Parameters的继承
- 支持多继承
- 基类的Parameters将被继承
- 如果多个基类中定义了相同名称的Parameters,那么继承列表中最后一个基类的该参数默认值,将被子类使用。
- 如果子类中重新定义了相同的参数,那么这个新的参数的默认值将被使用。
Lines的继承
- 支持多继承
- 所有基类的Lines都会被子类继承。如果不同基类中含有相同命名的Lines,那么只会保留一条该命名的line(具体哪条文档中没有说明)。
索引0和-1
在Strategy中只读取Lines的数据,在Indicator中对Lines既做读取也可以做写入操作。当访问Lines时,使用索引0来访问当前K线的数据,就像前面在Strategy访问Lines的例子中:
def next(self):
if self.movav.lines.sma[0] > self.data.lines.close[0]:
print('Simple Moving Average is greater than the closing price')
使用索引0来访问当前K线的sma及close值。在访问当前值时,索引0是可以省略,下面的代码和上面示例中条件语句实现相同的功能:
if self.movav.lines.sma > self.data.lines.close:
下面的例子展示了在开发新的Indicator时,使用索引0,对Lines进行赋值的过程。这里以SimpleMovingAverage技术指标为例:
def next(self):
self.line[0] = math.fsum(self.data.get(0, size=self.p.period)) / self.p.period
在Lines中,访问当前数据使用索引0,访问前一组数据使用索引-1,这和Python中使用-1访问数据中最后一个数据是不同的。下面的例子展示了在Strategy中,使用索引0和-1来比较当前收盘价和前一收盘价的代码:
def next(self):
if self.data.close[0] > self.data.close[-1]:
print('Closing price is higher today')
同样,可以使用-2、-3等等访问再向前(过去)的数据。
博客内容只用于交流学习,不构成投资建议,盈亏自负!
个人博客:http://coderx.com.cn/(优先更新)
项目最新代码:https://gitee.com/sl/quant_from_scratch
欢迎大家转发、留言。有微信群用于学习交流,感兴趣的读者请扫码加微信!
如果认为博客对您有帮助,可以扫码进行捐赠,感谢!
微信二维码 | 微信捐赠二维码 |
---|---|
![]() | ![]() |