近来忙于毕业找工作,也不知道能不能继续在量化界混了。周末比较闲,抽空研究了一下vn.py。有人说,为什么学那么多的回测平台呀。其实我个人觉得,做cta的话,两个回测平台还是要的,这样,当你的策略出现和你预计不符,而你有无法在代码逻辑层面找到问题的时候,你就可以用另外一个平台试一下,来看看到底是你的策略本身就不行,还是你的代码有着当前水平无法察觉的问题,甚至,可能回测平台本身存在一个bug。所以笔者之前学习的backtrader和pyalgotrade的目的就是这个,但是后续对于pyalgotrade没怎么用。前段时间看到vn.py和某Q开头的开源项目在网上开战,刚入门python的小朋友可能还不知道他们争论的是什么。
闲话不说,总而言之,个人觉得,vn.py还是一个很不错的期货cta的平台,而且,不仅仅局限于回测,还可以直接当一个交易系统使用,整个代码框架也是相当不错的,只是似乎对刚入门的小朋友不是特别友好,入门级别的教程比较少。
vn.py用的数据库是mongdb,笔者也不知道为什么要这样,是为了速度么还是单纯表示与众不同的。说真的,笔者还是习惯用传统的sql数据库,不过还好,mongodb也入过门。怎么安装就不说了,安装好之后别忘了开启服务。
笔者使用的nv.py-1.7版本,本文主要介绍一下回测功能吧。在下载下来的文件夹下面有一个examples文件夹,里面有一个CtaBacktesting文件,里面有一个notebook,一个IF的分钟数据,还有三个python文件。
我们先来运行一下loadCsv这个python文件,如果一切顺利的话,我们可以看到数据被写入mongdb的信息不断print出来。这个文件的功能就是告诉我们如何把数据存到数据库里。仔细看一下源码,其实可以发现,vn.py还是支持很多数据格式的,什么通达信上直接导到本地的数据啊之类的,功能还是很全的,很接地气。相对于国外的开源项目来说,基本都是要自己写数据格式处理程序的。有好处也有坏处吧。
然后我们就可以打开notebook文件,然后不断运行就可以了。
这里,笔者用DualThrust来作为例子讲解一下,毕竟这么经典的策略,大家很容易找到原理,也容易理解。
和别的回测项目一样,我们要现有一个回测的核心,在vn.py中叫做engine,引擎,还是比较好理解的。
engine = BacktestingEngine()
然后自然的,就是进行一些设置了。
设置的话,那么无非就是数据和回测方式。数据的话,设置是使用tick模式还是bar模式,笔者目前还没有研究tick模式,而且我们的数据也是分钟bar,所以选择BAR_MODE,然后就是设置数据库的名称,vn.py已经定义好了名称了,当然,我们可以按照我们的数据品种自己定义。
# 设置回测使用的数据
engine.setBacktestingMode(engine.BAR_MODE) # 设置引擎的回测模式为K线
engine.setDatabase(MINUTE_DB_NAME, 'IF0000') # 设置使用的历史数据库
engine.setStartDate('20120101') # 设置回测用的数据起始日期
engine.setEndDate('20120201')
# 配置回测引擎参数
engine.setSlippage(0.2) # 设置滑点为股指1跳
engine.setRate(0.3/10000) # 设置手续费万0.3
engine.setSize(300) # 设置股指合约大小
engine.setPriceTick(0.2) # 设置股指最小价格变动
engine.setCapital(1000000) # 设置回测本金
注:本文出现的大部分代码都是来自于vn.py的notebook文件里面自带的,并不是本人编写的。
接下来就是最核心的策略部分了。和很多开源平台一样,策略的封装就是一个类。
from vnpy.trader.app.ctaStrategy.strategy.strategyDualThrust import DualThrustStrategy
vn.py的作者已经写了一个DualThrust的策略了。
然后就是设置策略的参数,策略的参数是以字典形式传入的。
# 在引擎中创建策略对象
d = {} # 策略参数配置
engine.initStrategy(DualThrustStrategy, d) # 创建策略对象
# 运行回测
engine.runBacktesting() # 运行回测
上面的d就是。至于有什么参数,则与策略类的编写有关。
整个DualThrust的实现就不说了,大家可以去文件里面找怎么实现的。主要说一下框架吧。
# 参数列表,保存了参数的名称
paramList = ['name',
'className',
'author',
'vtSymbol',
'k1',
'k2']
在类里面,定义了参数表。我们知道,DualThrust如果只考虑前面一天的价格的话,也就是N为1的话,其实参数就是两个,上轨K和下轨K,也就是这里的K1和K2,大家修改上面的参数字典d就可以了。不传参数的话,当然就是默认参数。
然后就是所有事件驱动类型的回测框架都会有的那个触发函数了,也就是我们所有逻辑的核心。
def onBar(self, bar):
print 'on_bar method'
print bar.datetime.date()
"""收到Bar推送(必须由用户继承实现)"""
# 撤销之前发出的尚未成交的委托(包括限价单和停止单)
for orderID in self.orderList:
self.cancelOrder(orderID)
self.orderList = []
# 计算指标数值
self.barList.append(bar)
if len(self.barList) <= 2:
return
else:
self.barList.pop(0)
lastBar = self.barList[-2]
# 新的一天
if lastBar.datetime.date() != bar.datetime.date():
# 注意,这段代码其实只在改变日期那一天执行一次
# 如果已经初始化
if self.dayHigh:
self.range = self.dayHigh - self.dayLow
self.longEntry = bar.open + self.k1 * self.range
self.shortEntry = bar.open - self.k2 * self.range
self.dayOpen = bar.open
self.dayHigh = bar.high
self.dayLow = bar.low
self.longEntered = False
self.shortEntered = False
else:
self.dayHigh = max(self.dayHigh, bar.high)
self.dayLow = min(self.dayLow, bar.low)
# 尚未到收盘
if not self.range:
return
if bar.datetime.time() < self.exitTime:
if self.pos == 0:
if bar.close > self.dayOpen:
if not self.longEntered:
vtOrderID = self.buy(self.longEntry, self.fixedSize, stop=True)
self.orderList.append(vtOrderID)
else:
if not self.shortEntered:
vtOrderID = self.short(self.shortEntry, self.fixedSize, stop=True)
self.orderList.append(vtOrderID)
# 持有多头仓位
elif self.pos > 0:
self.longEntered = True
# 多头止损单
vtOrderID = self.sell(self.shortEntry, self.fixedSize, stop=True)
self.orderList.append(vtOrderID)
# 空头开仓单
if not self.shortEntered:
vtOrderID = self.short(self.shortEntry, self.fixedSize, stop=True)
self.orderList.append(vtOrderID)
# 持有空头仓位
elif self.pos < 0:
self.shortEntered = True
# 空头止损单
vtOrderID = self.cover(self.longEntry, self.fixedSize, stop=True)
self.orderList.append(vtOrderID)
# 多头开仓单
if not self.longEntered:
vtOrderID = self.buy(self.longEntry, self.fixedSize, stop=True)
self.orderList.append(vtOrderID)
# 收盘平仓
else:
if self.pos > 0:
vtOrderID = self.sell(bar.close * 0.99, abs(self.pos))
self.orderList.append(vtOrderID)
elif self.pos < 0:
vtOrderID = self.cover(bar.close * 1.01, abs(self.pos))
self.orderList.append(vtOrderID)
# 发出状态更新事件
self.putEvent()
在vn.py中叫onBar这个函数。逻辑还是很简单的。
总而言之,这是一个不错的工具,二DualThrust其实也是一个相对不错的策略,和还有一个也是日内交易的策略,忘记叫啥了,不但简单,而且效果巨好。后期如果找到工作了,还在量化界混的话,写个教程,如何在树莓派上跑几个这样的策略,然后放在印钱(血本无归)。
记录一下坑:
虽然vnpy出了2,但是不知道为什么多多少少都会出问题,而且,2之后的vnpy更加傻瓜化了,不太那么程序员,总觉得哪里怪怪的。所以在新的电脑上重新配了一些vnpy1.X的版本,结果发现了以前的坑。虽然之前都解决了,但是重新解决起来居然有点遗忘,所以写一下吧。mongodb什么的就不说,因为我的自己的版本是不一样的,改过很多东西,不需要这个数据库。
大概会遇到这么两个问题,一个是queue。这个在python2里面是Queue在3里面才叫queue,所以安装一个future就好了,也就是pip install future。
另外一个是talib的问题。这个可以使用conda安装。conda install -c quantopian ta-lib就可以了。
然后是pyecharts的问题,这个问题不是vnpy的问题,是自己扩展可视化模块的问题。pyecharts做了一个很大的升级,导致api接口整个变掉了。后续还在考虑用什么来进行可视化,所以这一部分不是特别重要。直接拷了一份原来的pyecharts包,然后安装了一下lml就可以了。
vnpy确实是一个大蠕虫,功能很全,但是真的要好好用的话,需要先删繁,后面再自己扩展。