vnpy/vn.trader/ctaAlgo/strategy/strategyKingKeltner.py
2017-03-06 23:02:52 +08:00

300 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# encoding: UTF-8
"""
基于King Keltner通道的交易策略适合用在股指上
展示了OCO委托和5分钟K线聚合的方法。
注意事项:
1. 作者不对交易盈利做任何保证,策略代码仅供参考
2. 本策略需要用到talib没有安装的用户请先参考www.vnpy.org上的教程安装
3. 将IF0000_1min.csv用ctaHistoryData.py导入MongoDB后直接运行本文件即可回测策略
"""
from __future__ import division
from ctaBase import *
from ctaTemplate import CtaTemplate
import talib
import numpy as np
########################################################################
class KkStrategy(CtaTemplate):
"""基于King Keltner通道的交易策略"""
className = 'KkStrategy'
author = u'用Python的交易员'
# 策略参数
kkLength = 32 # 计算通道中值的窗口数
kkDev = 2.2 # 计算通道宽度的偏差
initDays = 10 # 初始化数据所用的天数
fixedSize = 1 # 每次交易的数量
# 策略变量
bar = None # 1分钟K线对象
barMinute = EMPTY_STRING # K线当前的分钟
fiveBar = None # 1分钟K线对象
bufferSize = 100 # 需要缓存的数据的大小
bufferCount = 0 # 目前已经缓存了的数据的计数
highArray = np.zeros(bufferSize) # K线最高价的数组
lowArray = np.zeros(bufferSize) # K线最低价的数组
closeArray = np.zeros(bufferSize) # K线收盘价的数组
atrValue = 0 # 最新的ATR指标数值
kkMid = 0 # KK通道中轨
kkUp = 0 # KK通道上轨
kkDown = 0 # KK通道下轨
buyOrderID = None # OCO委托买入开仓的委托号
shortOrderID = None # OCO委托卖出开仓的委托号
orderList = [] # 保存委托代码的列表
# 参数列表,保存了参数的名称
paramList = ['name',
'className',
'author',
'vtSymbol',
'kkLength',
'kkDev']
# 变量列表,保存了变量的名称
varList = ['inited',
'trading',
'pos',
'atrValue',
'kkMid',
'kkUp',
'kkDown']
#----------------------------------------------------------------------
def __init__(self, ctaEngine, setting):
"""Constructor"""
super(KkStrategy, self).__init__(ctaEngine, setting)
#----------------------------------------------------------------------
def onInit(self):
"""初始化策略(必须由用户继承实现)"""
self.writeCtaLog(u'%s策略初始化' %self.name)
# 载入历史数据,并采用回放计算的方式初始化策略数值
initData = self.loadBar(self.initDays)
for bar in initData:
self.onBar(bar)
self.putEvent()
#----------------------------------------------------------------------
def onStart(self):
"""启动策略(必须由用户继承实现)"""
self.writeCtaLog(u'%s策略启动' %self.name)
self.putEvent()
#----------------------------------------------------------------------
def onStop(self):
"""停止策略(必须由用户继承实现)"""
self.writeCtaLog(u'%s策略停止' %self.name)
self.putEvent()
#----------------------------------------------------------------------
def onTick(self, tick):
"""收到行情TICK推送必须由用户继承实现"""
# 聚合为1分钟K线
tickMinute = tick.datetime.minute
if tickMinute != self.barMinute:
if self.bar:
self.onBar(self.bar)
bar = CtaBarData()
bar.vtSymbol = tick.vtSymbol
bar.symbol = tick.symbol
bar.exchange = tick.exchange
bar.open = tick.lastPrice
bar.high = tick.lastPrice
bar.low = tick.lastPrice
bar.close = tick.lastPrice
bar.date = tick.date
bar.time = tick.time
bar.datetime = tick.datetime # K线的时间设为第一个Tick的时间
self.bar = bar # 这种写法为了减少一层访问,加快速度
self.barMinute = tickMinute # 更新当前的分钟
else: # 否则继续累加新的K线
bar = self.bar # 写法同样为了加快速度
bar.high = max(bar.high, tick.lastPrice)
bar.low = min(bar.low, tick.lastPrice)
bar.close = tick.lastPrice
#----------------------------------------------------------------------
def onBar(self, bar):
"""收到Bar推送必须由用户继承实现"""
# 如果当前是一个5分钟走完
if bar.datetime.minute % 5 == 0:
# 如果已经有聚合5分钟K线
if self.fiveBar:
# 将最新分钟的数据更新到目前5分钟线中
fiveBar = self.fiveBar
fiveBar.high = max(fiveBar.high, bar.high)
fiveBar.low = min(fiveBar.low, bar.low)
fiveBar.close = bar.close
# 推送5分钟线数据
self.onFiveBar(fiveBar)
# 清空5分钟线数据缓存
self.fiveBar = None
else:
# 如果没有缓存则新建
if not self.fiveBar:
fiveBar = CtaBarData()
fiveBar.vtSymbol = bar.vtSymbol
fiveBar.symbol = bar.symbol
fiveBar.exchange = bar.exchange
fiveBar.open = bar.open
fiveBar.high = bar.high
fiveBar.low = bar.low
fiveBar.close = bar.close
fiveBar.date = bar.date
fiveBar.time = bar.time
fiveBar.datetime = bar.datetime
self.fiveBar = fiveBar
else:
fiveBar = self.fiveBar
fiveBar.high = max(fiveBar.high, bar.high)
fiveBar.low = min(fiveBar.low, bar.low)
fiveBar.close = bar.close
#----------------------------------------------------------------------
def onFiveBar(self, bar):
"""收到5分钟K线"""
# 撤销之前发出的尚未成交的委托(包括限价单和停止单)
for orderID in self.orderList:
self.cancelOrder(orderID)
self.orderList = []
# 保存K线数据
self.closeArray[0:self.bufferSize-1] = self.closeArray[1:self.bufferSize]
self.highArray[0:self.bufferSize-1] = self.highArray[1:self.bufferSize]
self.lowArray[0:self.bufferSize-1] = self.lowArray[1:self.bufferSize]
self.closeArray[-1] = bar.close
self.highArray[-1] = bar.high
self.lowArray[-1] = bar.low
self.bufferCount += 1
if self.bufferCount < self.bufferSize:
return
# 计算指标数值
self.atrValue = talib.ATR(self.highArray,
self.lowArray,
self.closeArray,
self.kkLength)[-1]
self.kkMid = talib.MA(self.closeArray, self.kkLength)[-1]
self.kkUp = self.kkMid + self.atrValue * self.kkDev
self.kkDown = self.kkMid - self.atrValue * self.kkDev
# 判断是否要进行交易
# 当前无仓位发送OCO开仓委托
if self.pos == 0:
self.sendOcoOrder(self.kkUp, self.kkDown, self.fixedSize)
# 持有多头仓位
elif self.pos > 0 and bar.close < self.kkUp:
orderID = self.sell(bar.close*0.97, abs(self.pos))
self.orderList.append(orderID)
# 持有空头仓位
elif self.pos < 0 and bar.close > self.kkDown:
orderID = self.buy(bar.close*1.03, abs(self.pos))
self.orderList.append(orderID)
# 发出状态更新事件
self.putEvent()
#----------------------------------------------------------------------
def onOrder(self, order):
"""收到委托变化推送(必须由用户继承实现)"""
pass
#----------------------------------------------------------------------
def onTrade(self, trade):
# 多头开仓成交后,撤消空头委托
if self.pos > 0:
self.cancelOrder(self.shortOrderID)
if self.buyOrderID in self.orderList:
self.orderList.remove(self.buyOrderID)
if self.shortOrderID in self.orderList:
self.orderList.remove(self.shortOrderID)
# 反之同样
elif self.pos < 0:
self.cancelOrder(self.buyOrderID)
if self.buyOrderID in self.orderList:
self.orderList.remove(self.buyOrderID)
if self.shortOrderID in self.orderList:
self.orderList.remove(self.shortOrderID)
# 发出状态更新事件
self.putEvent()
#----------------------------------------------------------------------
def sendOcoOrder(self, buyPrice, shortPrice, volume):
"""
发送OCO委托
OCO(One Cancel Other)委托:
1. 主要用于实现区间突破入场
2. 包含两个方向相反的停止单
3. 一个方向的停止单成交后会立即撤消另一个方向的
"""
# 发送双边的停止单委托,并记录委托号
self.buyOrderID = self.buy(buyPrice, volume, True)
self.shortOrderID = self.short(shortPrice, volume, True)
# 将委托号记录到列表中
self.orderList.append(self.buyOrderID)
self.orderList.append(self.shortOrderID)
if __name__ == '__main__':
# 提供直接双击回测的功能
# 导入PyQt4的包是为了保证matplotlib使用PyQt4而不是PySide防止初始化出错
from ctaBacktesting import *
from PyQt4 import QtCore, QtGui
# 创建回测引擎
engine = BacktestingEngine()
# 设置引擎的回测模式为K线
engine.setBacktestingMode(engine.BAR_MODE)
# 设置回测用的数据起始日期
engine.setStartDate('20120101')
# 设置产品相关参数
engine.setSlippage(0.2) # 股指1跳
engine.setRate(0.3/10000) # 万0.3
engine.setSize(300) # 股指合约大小
# 设置使用的历史数据库
engine.setDatabase(MINUTE_DB_NAME, 'IF0000')
# 在引擎中创建策略对象
d = {}
engine.initStrategy(KkStrategy, d)
# 开始跑回测
engine.runBacktesting()
# 显示回测结果
engine.showBacktestingResult()