9f6ea1fd5c
2. 将vn.trader下的所有接口统一放到了一个单独的gateway文件夹中,并实现自动识别和加载
317 lines
12 KiB
Python
317 lines
12 KiB
Python
# encoding: UTF-8
|
||
"""
|
||
This file tweaks ctaTemplate Module to suit multi-TimeFrame strategies.
|
||
"""
|
||
|
||
from ctaBase import *
|
||
from ctaTemplate import CtaTemplate
|
||
import numpy as np
|
||
|
||
########################################################################
|
||
class BreakOut(CtaTemplate):
|
||
|
||
"""
|
||
"infoArray" 字典是用来储存辅助品种信息的, 可以是同品种的不同分钟k线, 也可以是不同品种的价格。
|
||
|
||
调用的方法:
|
||
价格序列:
|
||
self.infoArray["数据库名 + 空格 + collection名"]["close"]
|
||
self.infoArray["数据库名 + 空格 + collection名"]["high"]
|
||
self.infoArray["数据库名 + 空格 + collection名"]["low"]
|
||
|
||
单个价格:
|
||
self.infoBar["数据库名 + 空格 + collection名"]
|
||
返回的值为一个ctaBarData 或 None
|
||
"""
|
||
|
||
#----------------------------------------------------------------------
|
||
def __init__(self, ctaEngine, setting):
|
||
"""日内突破交易策略, 出场方式非常多, 本文件使用指标出场"""
|
||
|
||
className = 'BreakOut'
|
||
author = 'Joe'
|
||
super(BreakOut, self).__init__(ctaEngine, setting)
|
||
|
||
# 设置辅助品种数据字典
|
||
self.infoArray = {}
|
||
self.initInfobar = {}
|
||
self.infoBar = {}
|
||
|
||
# 缓存数据量
|
||
self.bufferSize = 100
|
||
self.bufferCount = 0
|
||
self.initDays = 10
|
||
|
||
# 设置参数
|
||
self.pOBO_Mult = 0.5 # 计算突破点位
|
||
# self.pProtMult = 2 # 止损的ATR倍数
|
||
# self.pProfitMult = 2 # 止盈相对于止损的倍数
|
||
# self.SlTp_On = False # 止损止盈功能
|
||
# self.EODTime = 15 # 设置日内平仓时间
|
||
|
||
self.vOBO_stretch = EMPTY_FLOAT
|
||
self.vOBO_initialpoint = EMPTY_FLOAT
|
||
self.vOBO_level_L = EMPTY_FLOAT
|
||
self.vOBO_level_S = EMPTY_FLOAT
|
||
|
||
self.orderList = []
|
||
|
||
# 参数列表,保存了参数的名称
|
||
paramList = ['name',
|
||
'className',
|
||
'author',
|
||
'pOBO_Mult',
|
||
'pProtMult',
|
||
'pProfitMult',
|
||
'SlTp_On',
|
||
'EODTime']
|
||
|
||
# 变量列表,保存了变量的名称
|
||
varList = ['vOBO_stretch',
|
||
'vOBO_initialpoint',
|
||
'vOBO_level_L',
|
||
'vOBO_level_S']
|
||
|
||
# ----------------------------------------------------------------------
|
||
def onInit(self):
|
||
"""初始化策略(必须由用户继承实现)"""
|
||
self.writeCtaLog(u'%s策略初始化' % self.name)
|
||
|
||
# 载入历史数据,并采用回放计算的方式初始化策略数值
|
||
initData = self.loadBar(self.initDays)
|
||
for bar in initData:
|
||
|
||
# 推送新数据, 同时检查是否有information bar需要推送
|
||
# Update new bar, check whether the Time Stamp matching any information bar
|
||
ibar = self.checkInfoBar(bar)
|
||
self.onBar(bar, infobar=ibar)
|
||
|
||
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 checkInfoBar(self, bar):
|
||
"""在初始化时, 检查辅助品种数据的推送(初始化结束后, 回测时不会调用)"""
|
||
|
||
initInfoCursorDict = self.ctaEngine.initInfoCursor
|
||
|
||
# 如果"initInfobar"字典为空, 初始化字典, 插入第一个数据
|
||
# If dictionary "initInfobar" is empty, insert first data record
|
||
if self.initInfobar == {}:
|
||
for info_symbol in initInfoCursorDict:
|
||
try:
|
||
self.initInfobar[info_symbol] = next(initInfoCursorDict[info_symbol])
|
||
except StopIteration:
|
||
print "Data of information symbols is empty! Input is a list, not str."
|
||
raise
|
||
|
||
# 若有某一品种的 TimeStamp 和执行报价的 TimeStamp 匹配, 则将"initInfobar"中的数据推送,
|
||
# 然后更新该品种的数据
|
||
# If any symbol's TimeStamp is matched with execution symbol's TimeStamp, return data
|
||
# in "initInfobar", and update new data.
|
||
temp = {}
|
||
for info_symbol in self.initInfobar:
|
||
|
||
data = self.initInfobar[info_symbol]
|
||
|
||
# Update data only when Time Stamp is matched
|
||
|
||
if (data is not None) and (data['datetime'] <= bar.datetime):
|
||
|
||
try:
|
||
temp[info_symbol] = CtaBarData()
|
||
temp[info_symbol].__dict__ = data
|
||
self.initInfobar[info_symbol] = next(initInfoCursorDict[info_symbol])
|
||
except StopIteration:
|
||
self.initInfobar[info_symbol] = None
|
||
self.ctaEngine.output("No more data for initializing %s." % (info_symbol,))
|
||
else:
|
||
temp[info_symbol] = None
|
||
|
||
return temp
|
||
|
||
# ----------------------------------------------------------------------
|
||
def updateInfoArray(self, infobar):
|
||
"""收到Infomation Data, 更新辅助品种缓存字典"""
|
||
|
||
for name in infobar:
|
||
|
||
data = infobar[name]
|
||
|
||
# Construct empty array
|
||
if len(self.infoArray) < len(infobar) :
|
||
self.infoArray[name] = {
|
||
"close": np.zeros(self.bufferSize),
|
||
"high": np.zeros(self.bufferSize),
|
||
"low": np.zeros(self.bufferSize),
|
||
"open": np.zeros(self.bufferSize)
|
||
}
|
||
|
||
if data is None:
|
||
pass
|
||
|
||
else:
|
||
self.infoArray[name]["close"][0:self.bufferSize - 1] = \
|
||
self.infoArray[name]["close"][1:self.bufferSize]
|
||
self.infoArray[name]["high"][0:self.bufferSize - 1] = \
|
||
self.infoArray[name]["high"][1:self.bufferSize]
|
||
self.infoArray[name]["low"][0:self.bufferSize - 1] = \
|
||
self.infoArray[name]["low"][1:self.bufferSize]
|
||
self.infoArray[name]["open"][0:self.bufferSize - 1] = \
|
||
self.infoArray[name]["open"][1:self.bufferSize]
|
||
|
||
self.infoArray[name]["close"][-1] = data.close
|
||
self.infoArray[name]["high"][-1] = data.high
|
||
self.infoArray[name]["low"][-1] = data.low
|
||
self.infoArray[name]["open"][-1] = data.open
|
||
|
||
# ----------------------------------------------------------------------
|
||
def onBar(self, bar, **kwargs):
|
||
"""收到Bar推送(必须由用户继承实现)"""
|
||
|
||
# Update infomation data
|
||
# "infobar"是由不同时间或不同品种的品种数据组成的字典, 如果和执行品种的 TimeStamp 不匹配,
|
||
# 则传入的是"None", 当time stamp和执行品种匹配时, 传入的是"Bar"
|
||
if "infobar" in kwargs:
|
||
self.infoBar = kwargs["infobar"]
|
||
self.updateInfoArray(kwargs["infobar"])
|
||
|
||
# 若读取的缓存数据不足, 不考虑交易
|
||
self.bufferCount += 1
|
||
if self.bufferCount < self.bufferSize:
|
||
return
|
||
|
||
# 计算指标数值
|
||
a = np.sum(self.infoArray["TestData @GC_1D"]["close"])
|
||
if a == 0.0:
|
||
return
|
||
|
||
# Only updating indicators when information bar changes
|
||
# 只有在30min或者1d K线更新后才更新指标
|
||
TradeOn = False
|
||
if any([i is not None for i in self.infoBar]):
|
||
TradeOn = True
|
||
self.vRange = self.infoArray["TestData @GC_1D"]["high"][-1] -\
|
||
self.infoArray["TestData @GC_1D"]["low"][-1]
|
||
self.vOBO_stretch = self.vRange * self.pOBO_Mult
|
||
self.vOBO_initialpoint = self.infoArray["TestData @GC_1D"]["close"][-1]
|
||
self.vOBO_level_L = self.vOBO_initialpoint + self.vOBO_stretch
|
||
self.vOBO_level_S = self.vOBO_initialpoint - self.vOBO_stretch
|
||
|
||
self.atrValue30M = talib.abstract.ATR(self.infoArray["TestData @GC_30M"])[-1]
|
||
|
||
# 判断是否要进行交易
|
||
|
||
# 当前无仓位
|
||
if (self.pos == 0 and TradeOn == True):
|
||
|
||
# 撤销之前发出的尚未成交的委托(包括限价单和停止单)
|
||
for orderID in self.orderList:
|
||
self.cancelOrder(orderID)
|
||
self.orderList = []
|
||
|
||
# 若上一个30分钟K线的最高价大于OBO_level_L
|
||
# 且当前的价格大于OBO_level_L, 则买入
|
||
if self.infoArray["TestData @GC_30M"]["high"][-1] > self.vOBO_level_L:
|
||
|
||
if bar.close > self.vOBO_level_L:
|
||
|
||
self.buy(bar.close + 0.5, 1)
|
||
|
||
# 下单后, 在下一个30Min K线之前不交易
|
||
TradeOn = False
|
||
|
||
# 若上一个30分钟K线的最高价低于OBO_level_S
|
||
# 且当前的价格小于OBO_level_S, 则卖出
|
||
elif self.infoArray["TestData @GC_30M"]["low"][-1] < self.vOBO_level_S:
|
||
|
||
if bar.close < self.vOBO_level_S:
|
||
|
||
self.short(bar.close - 0.5, 1)
|
||
|
||
# 下单后, 在下一个30Min K线之前不交易
|
||
TradeOn = False
|
||
|
||
# 持有多头仓位
|
||
elif self.pos > 0:
|
||
|
||
# 当价格低于initialpoint水平, 出场
|
||
if bar.close < self.vOBO_initialpoint:
|
||
self.sell(bar.close - 0.5 , 1)
|
||
|
||
# 持有空头仓位
|
||
elif self.pos < 0:
|
||
|
||
# 当价格高于initialpoint水平, 出场
|
||
if bar.close > self.vOBO_initialpoint:
|
||
self.cover(bar.close + 0.5, 1)
|
||
|
||
|
||
# 发出状态更新事件
|
||
self.putEvent()
|
||
|
||
# ----------------------------------------------------------------------
|
||
def onOrder(self, order):
|
||
"""收到委托变化推送(必须由用户继承实现)"""
|
||
pass
|
||
|
||
# ----------------------------------------------------------------------
|
||
def onTrade(self, trade):
|
||
pass
|
||
|
||
|
||
if __name__ == '__main__':
|
||
# 提供直接双击回测的功能
|
||
# 导入PyQt4的包是为了保证matplotlib使用PyQt4而不是PySide,防止初始化出错
|
||
from ctaBacktestMultiTF import *
|
||
from PyQt4 import QtCore, QtGui
|
||
import time
|
||
|
||
'''
|
||
创建回测引擎
|
||
设置引擎的回测模式为K线
|
||
设置回测用的数据起始日期
|
||
载入历史数据到引擎中
|
||
在引擎中创建策略对象
|
||
|
||
Create backtesting engine
|
||
Set backtest mode as "Bar"
|
||
Set "Start Date" of data range
|
||
Load historical data to engine
|
||
Create strategy instance in engine
|
||
'''
|
||
engine = BacktestEngineMultiTF()
|
||
engine.setBacktestingMode(engine.BAR_MODE)
|
||
engine.setStartDate('20120101')
|
||
engine.setEndDate('20150101')
|
||
engine.setDatabase("TestData", "@GC_1M", info_symbol=[("TestData","@GC_30M"),
|
||
("TestData","@GC_1D")])
|
||
|
||
# Set parameters for strategy
|
||
engine.initStrategy(BreakOut, {})
|
||
|
||
# 设置产品相关参数
|
||
engine.setSlippage(0.2) # 股指1跳
|
||
engine.setCommission(0.3 / 10000) # 万0.3
|
||
engine.setSize(1) # 股指合约大小
|
||
|
||
# 开始跑回测
|
||
start = time.time()
|
||
|
||
engine.runBacktesting()
|
||
|
||
# 显示回测结果
|
||
engine.showBacktestingResult()
|
||
|
||
print 'Time consumed:%s' % (time.time() - start) |