最近一致在用BollChannel轨道策略,也是VNPY原包提供的模版策略,修改了也不少,这里面就随便分析。BollChannel策略是很精巧的逻辑。基于15分钟K线轮询,利用阻止单跟随15分钟间的市场变动。
class BollChannelStrategy(CtaTemplate):
"""基于布林通道的交易策略"""
在VNPY中,每个策略都是一个类,继承CtaTemplate,这样也就继承了被实盘engine和回测engine调用启用的这样一套使用接口。
className = 'BollChannelStrategy'
author = u'用Python的交易员'
这两个参数是作为信息,体现在实盘运行界面中,可以看到和白色框中,是策略参数;下面黄色框中,是系统按市场盘面信息
和策略参数计算出的策略变量。
bollWindow = 22 #布林通道窗口数
bollDev = 3.1 #布林通道的偏差
cciWindow = 20 # CCI窗口数
atrWindow = 25 # ATR窗口数
slMultiplier = 5.0 #计算止损距离的乘数
initDays = 10 #初始化数据所用的天数
fixedSize = 1 #每次交易的数量
barMins = 15
这些是策略参数,其中bollWindow和bollDev是用来计算布林线轨道信息,主要是上轨和下轨数值;
cciWindow是用来计算CCI Vaule,Commodity Channel lndex中文顺势指标,这个指标具体搜索,这边用来判断市场多空。
atrWindos是用来计算atrValue,atr是Average true range,意为平均真实波动幅度,计算具体搜索,这里用来计算波动幅度,为平仓作为标准
slMultiplier是一个计算止损的乘数。
initDays是定义初始的天数,虽然是15分钟k线,理论需要需要前几天数据用来计算策略变量。
fixedSize是每笔下单手数
barMins分析周期,用默认用15分钟k线。
这些参数都有默认值
#策略变量
bollUp = 0 #布林通道上轨
bollDown = 0 #布林通道下轨
cciValue = 0 # CCI指标数值
atrValue = 0 # ATR指标数值
intraTradeHigh = 0 #持仓期内的最高点
intraTradeLow = 0 #持仓期内的最低点
longStop = 0 #多头止损
shortStop = 0 #空头止损
这些变量是系统计算,会在GUI界面中体现。
#参数列表,保存了参数的名称
paramList = ['name',
'className',
'author',
'vtSymbol',
'bollWindow',
'bollDev',
'cciWindow',
'atrWindow',
'slMultiplier',
'initDays',
'fixedSize',
'barMins']
用list承载参数,这个可以在回测用dict结构读取,比如{'bollWindow':15,'bollDev':3.4000000000000004,'slMultiplier':5.0,'cciWindow':20,'atrWindow':25, 'barMins':15}
实盘engine是读取CTA_setting.json文件中的,同一个策略的结构都是一样。
#变量列表,保存了变量的名称
varList = ['inited',
'trading',
'pos',
'bollUp',
'bollDown',
'cciValue',
'atrValue',
'intraTradeHigh',
'intraTradeLow',
'longStop',
'shortStop']
这个没什么好说,理论也是可以导入作为测试初始数据,但是没有测试过。
#同步列表,保存了需要保存到数据库的变量名称
syncList = ['pos',
'intraTradeHigh',
'intraTradeLow']
后面同步方法时候,会把这几个数据写入数据库。其实会发现,保持的数据并不多,写入Mongodb的VnTrader_Log_Db。
#----------------------------------------------------------------------
def __init__(self, ctaEngine, setting):
"""Constructor"""
super(BollChannelStrategy, self).__init__(ctaEngine, setting)
self.bg = BarGenerator(self.onBar, self.barMins, self.onXminBar) #创建K线合成器对象
self.bg30 = BarGenerator(self.onBar, 28, self.on30minBar)
self.am = ArrayManager()
对象初始化函数__init__,也是继承CtaTemplate,其中setting参数就是前面说的策略参数dict,导入后会更新策略对象参数。这里系统主要是通过实盘engine和回测engine来使用策略对象。
然后是定义k线合成器,是class BarGenerator实现,生成按照barMins,这里是15分钟来生成,当到barMins时候,系统会调用onXminBar的函数,这里也定义28分钟的K线,不过后面的方法是空的。
最后是实现class ArrayManager,这个类是K线序列管理工具,负责K线合并和指标计算。
#----------------------------------------------------------------------
def on30minBar(self, bar):
""""""
这里这个为空
#----------------------------------------------------------------------
def onInit(self):
"""初始化策略(必须由用户继承实现)"""
self.writeCtaLog(u'%s策略初始化' %self.name)
#载入历史数据,并采用回放计算的方式初始化策略数值
initData = self.loadBar(self.initDays)
for bar in initData:
self.onBar(bar)
self.putEvent()
这个是策略初始化时候操作,回测engine,和实盘engine都会调用,就是你看到绿色框中按钮,如果实盘按下,会调用这个函数;主要是按照初始化天数载入历史数据,这里的initData是一个包含bar信息的list,一般通过tushare能够获得就是1分钟bar。
然后回跑,这里会调用k线合成器里面的updateBar函数,计算出策略变量的值。最近把更新信息显示在前台。
最后和事件引擎相关;具体可以看看GitHub里面介绍。我的理解是把这个函数对应的事件放入事务队列,通知GUI更新,
Bar的数据像如下,是dict结构,volume是交易量,exchange是交易所,symbol是品种,其他就是最高点最低点,和开盘价已经收盘价格。这个是在mongoDB里面VnTrader_1Min_Db的,没有品种就是一个collection。
{
"_id":ObjectId("5acb43ccf1ed36627c163b1d"),
"volume":1796.0,
"gatewayName":"",
"exchange":"SHFE",
"symbol":"rb1810",
"datetime":ISODate("2018-01-02T09:01:00.000+0000"),
"high":3612.0,
"rawData":null,
"time":"090100",
"date":"20180102",
"close":3611.0,
"openInterest":NumberInt(0),
"open":3609.0,
"vtSymbol":"rb1810.SHFE",
"low":3606.0
}
#----------------------------------------------------------------------
def onStart(self):
"""启动策略(必须由用户继承实现)"""
self.writeCtaLog(u'%s策略启动' %self.name)
self.putEvent()
然后是启动策略,系统会通知前台GUI更新。
#----------------------------------------------------------------------
def onStop(self):
"""停止策略(必须由用户继承实现)"""
self.writeCtaLog(u'%s策略停止' %self.name)
self.putEvent()
然后是启动策略,系统会通知前台GUI更新。
#----------------------------------------------------------------------
def onTick(self, tick):
"""收到行情TICK推送(必须由用户继承实现)"""
self.bg.updateTick(tick)
然后是收到tick,会调BarGenerator K线合成器的方法,根据tick数据合成一分钟bar,tick数据是报价和成交数据,updateTick函数会算出最近一分钟的最高价最低价,开盘和收盘价,还有成交量等bar需要数据。现在一般历史数据都是基于一分钟bar。国内期货交易商现在只提供一级的买卖报价信息,不像股票交易所提供5买入卖出高低5级别。基本做高频交易的路子就给封死了
#----------------------------------------------------------------------
def onBar(self, bar):
"""收到Bar推送(必须由用户继承实现)"""
self.bg.updateBar(bar)
然后是收到bar,调用BarGenerator K线合成器的方法,更新一分钟bar信息。
#----------------------------------------------------------------------
def onXminBar(self, bar):
这个是最核心的函数,就是每隔15分钟k线后,运行的方法。
"""收到X分钟K线"""
#全撤之前发出的委托
self.cancelAll()
全撤所有委托,这里的委托并不是一定实际券商的委托,而且本地委托单;尤其像上期所不支持stoporder,都是通过本地条件单实现。这里是取消这些委托。
#保存K线数据
am = self.am
am.updateBar(bar)
if not am.inited:
return
这里计算复制arraymanger,用AM去处理;这里说下,下面是ArrayManager的updateBar方法,缓存size默认是100,里面这些属性都是存储在list;
如果list没有填满100条,视为没有初始化完成,inited为默认false, count> 100时候,inited = True,初始化完成;
如果填满100条了,把后99条前推一位,把最新一条插入到最后。就是下面代码表示的。
这里面就是最初initdays的作用,通过十天数据,完成初始化。最近这里bar是15分钟bar。
然后还有一个情况是没有满100条时候,其实也是同样操作,因为新数据是从后端插入,也是整体前移直到填满100条后。
- classArrayManager(object):
- ……
- defupdateBar(self,bar):
- """更新K线"""
- self.count+=1
- ifnotself.initedandself.count>=self.size:
- self.inited=True
- self.openArray[0:self.size-1]=self.openArray[1:self.size]
- self.highArray[0:self.size-1]=self.highArray[1:self.size]
- self.lowArray[0:self.size-1]=self.lowArray[1:self.size]
- self.closeArray[0:self.size-1]=self.closeArray[1:self.size]
- self.volumeArray[0:self.size-1]=self.volumeArray[1:self.size]
- self.openArray[-1]=bar.open
- self.highArray[-1]=bar.high
- self.lowArray[-1]=bar.low
- self.closeArray[-1]=bar.close
- self.volumeArray[-1]=bar.volume
#计算指标数值
self.bollUp, self.bollDown = am.boll(self.bollWindow, self.bollDev)
self.cciValue = am.cci(self.cciWindow)
self.atrValue = am.atr(self.atrWindow)
AM根据那100条bar,可以按照策略参数,可以计算出策略变量,bollUp,BollDown,cciValue和atrVaule。AM具体计算方法可以查询,有些是是vnpy实现,有些是Ta-lib。
#判断是否要进行交易
#当前无仓位,发送开仓委托
if self.pos == 0:
self.intraTradeHigh = bar.high
self.intraTradeLow = bar.low
if self.cciValue > 0:
self.buy(self.bollUp, self.fixedSize, True)
elif self.cciValue < 0:
self.short(self.bollDown, self.fixedSize, True)
这个是是否开仓,根据cciVaule,如果大于0,视为多方市场,开多买入,如果小于0,视为空方市场,开空单。多单开仓价格是bollup布林线上轨价格,空单价格是布林线下轨价格。这里的True是stop order阻止单,及当实际价格突破布林线上轨下单,如果没有突破,实际并没有下单。空单同理。但是这里k线是15分钟,其实存在15分钟时候脉冲式突破上轨,系统按照市价单下单,但是买在最高点情况。这个也是这个策略一个商榷地方。还有一个就是更新intraTradeHigh和intraTradeLow。
#持有多头仓位
elif self.pos > 0:
self.intraTradeHigh = max(self.intraTradeHigh, bar.high)
self.intraTradeLow = bar.low
self.longStop = self.intraTradeHigh - self.atrValue * self.slMultiplier
self.sell(self.longStop, abs(self.pos), True)
如果持有多单,按照bar.high和self.intraTradeHigh选出价高者,这个intraTradeHigh是多单开仓时候Kbar的最高点。卖出价格是这个价格减去atrVaule和Multipier乘机。也是开阻止单,当实际价格下跌到卖出价格时候按照市场价格销售。
#持有空头仓位
elif self.pos < 0:
self.intraTradeHigh = bar.high
self.intraTradeLow = min(self.intraTradeLow, bar.low)
self.shortStop = self.intraTradeLow + self.atrValue * self.slMultiplier
self.cover(self.shortStop, abs(self.pos), True)
和持有多单销售同理。
#同步数据到数据库
self.saveSyncData()
#发出状态更新事件
self.putEvent()
保存pos仓位,intraTradeHigh和intraTradeLow到数据库,更新GUI界面。
#----------------------------------------------------------------------
def onOrder(self, order):
"""收到委托变化推送(必须由用户继承实现)"""
pass
#----------------------------------------------------------------------
def onTrade(self, trade):
#发出状态更新事件
self.putEvent()
#----------------------------------------------------------------------
def onStopOrder(self, so):
"""停止单推送"""
pass
其他一些数据,为空
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/22259926/viewspace-2156067/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/22259926/viewspace-2156067/