合并vnpy v1.3版本

This commit is contained in:
msincenselee 2016-11-30 14:26:08 +08:00
parent 6c44da565d
commit f37440e6bb
9 changed files with 406 additions and 168 deletions

View File

@ -57,6 +57,9 @@ class BacktestingEngine(object):
self.stopOrderDict = {} # 停止单撤销后不会从本字典中删除 self.stopOrderDict = {} # 停止单撤销后不会从本字典中删除
self.workingStopOrderDict = {} # 停止单撤销后会从本字典中删除 self.workingStopOrderDict = {} # 停止单撤销后会从本字典中删除
# 引擎类型为回测
self.engineType = ENGINETYPE_BACKTESTING
# 回测相关 # 回测相关
self.strategy = None # 回测策略 self.strategy = None # 回测策略
self.mode = self.BAR_MODE # 回测模式默认为K线 self.mode = self.BAR_MODE # 回测模式默认为K线
@ -401,8 +404,7 @@ class BacktestingEngine(object):
else: else:
self.writeCtaLog(u'MysqlDB未连接请检查') self.writeCtaLog(u'MysqlDB未连接请检查')
except MySQLdb.Error, e: except MySQLdb.Error as e:
self.writeCtaLog(u'MysqlDB载入数据失败请检查.Error {0}'.format(e)) self.writeCtaLog(u'MysqlDB载入数据失败请检查.Error {0}'.format(e))
def __dataToTick(self, data): def __dataToTick(self, data):
@ -494,15 +496,14 @@ class BacktestingEngine(object):
else: else:
self.writeCtaLog(u'MysqlDB未连接请检查') self.writeCtaLog(u'MysqlDB未连接请检查')
except MySQLdb.Error, e: except MySQLdb.Error as e:
self.writeCtaLog(u'MysqlDB载入数据失败请检查.Error {0}: {1}'.format(e.arg[0],e.arg[1])) self.writeCtaLog(u'MysqlDB载入数据失败请检查.Error {0}: {1}'.format(e.arg[0],e.arg[1]))
# 出错后缺省返回 # 出错后缺省返回
return startDate-timedelta(days=3) return startDate-timedelta(days=3)
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
def runBackTestingWithArbTickFile(self, arbSymbol): def runBackTestingWithArbTickFile(self,mainPath, arbSymbol):
"""运行套利回测使用本地tickcsv数据) """运行套利回测使用本地tickcsv数据)
参数套利代码 SP rb1610&rb1701 参数套利代码 SP rb1610&rb1701
added by IncenseLee added by IncenseLee
@ -567,38 +568,42 @@ class BacktestingEngine(object):
self.output(u'回测日期:{0}'.format(testday)) self.output(u'回测日期:{0}'.format(testday))
# 白天数据 # 白天数据
self.__loadArbTicks(u'SHFE',testday,leg1,leg2) self.__loadArbTicks(mainPath,testday,leg1,leg2)
# 夜盘数据 # 夜盘数据
self.__loadArbTicks(u'SHFE_night', testday, leg1, leg2) self.__loadArbTicks(mainPath+'_night', testday, leg1, leg2)
def __loadArbTicks(self,mainPath,testday,leg1,leg2): def __loadArbTicks(self,mainPath,testday,leg1,leg2):
leg1File = u'z:\\ticks\\{0}\\{1}\\{2}\\{3}\\{4}.txt' \
.format(mainPath,testday.strftime('%Y%m'), self.symbol, testday.strftime('%m%d'), leg1)
if not os.path.isfile(leg1File):
self.writeCtaLog(u'{0}文件不存在'.format(leg1File))
return
leg2File = u'z:\\ticks\\{0}\\{1}\\{2}\\{3}\\{4}.txt' \
.format(mainPath,testday.strftime('%Y%m'), self.symbol, testday.strftime('%m%d'), leg2)
if not os.path.isfile(leg2File):
self.writeCtaLog(u'{0}文件不存在'.format(leg2File))
return
self.writeCtaLog(u'加载回测日期:{0}\{1}的价差tick'.format(mainPath, testday)) self.writeCtaLog(u'加载回测日期:{0}\{1}的价差tick'.format(mainPath, testday))
cachefilename = u'{0}_{1}_{2}_{3}_{4}'.format(self.symbol,leg1,leg2, mainPath, testday.strftime('%Y%m%d')) cachefilename = u'{0}_{1}_{2}_{3}_{4}'.format(self.symbol,leg1,leg2, mainPath, testday.strftime('%Y%m%d'))
arbTicks = self.__loadArbTicksFromLocalCache(cachefilename) arbTicks = self.__loadArbTicksFromLocalCache(cachefilename)
dt = None
if len(arbTicks) < 1: if len(arbTicks) < 1:
leg1File = u'z:\\ticks\\{0}\\{1}\\{2}\\{3}\\{4}.txt' \
.format(mainPath, testday.strftime('%Y%m'), self.symbol, testday.strftime('%m%d'), leg1)
if not os.path.isfile(leg1File):
self.writeCtaLog(u'{0}文件不存在'.format(leg1File))
return
leg2File = u'z:\\ticks\\{0}\\{1}\\{2}\\{3}\\{4}.txt' \
.format(mainPath, testday.strftime('%Y%m'), self.symbol, testday.strftime('%m%d'), leg2)
if not os.path.isfile(leg2File):
self.writeCtaLog(u'{0}文件不存在'.format(leg2File))
return
# 先读取leg2的数据到目录以日期时间为key # 先读取leg2的数据到目录以日期时间为key
leg2Ticks = {} leg2Ticks = {}
leg2CsvReadFile = file(leg2File, 'rb') leg2CsvReadFile = file(leg2File, 'rb')
reader = csv.DictReader((line.replace('\0', '') for line in leg2CsvReadFile), delimiter=",") #reader = csv.DictReader((line.replace('\0',' ') for line in leg2CsvReadFile), delimiter=",")
reader = csv.DictReader(leg2CsvReadFile, delimiter=",")
self.writeCtaLog(u'加载{0}'.format(leg2File)) self.writeCtaLog(u'加载{0}'.format(leg2File))
for row in reader: for row in reader:
tick = CtaTickData() tick = CtaTickData()
@ -607,8 +612,26 @@ class BacktestingEngine(object):
tick.symbol = self.symbol tick.symbol = self.symbol
tick.date = testday.strftime('%Y%m%d') tick.date = testday.strftime('%Y%m%d')
tick.tradingDay = tick.date
tick.time = row['Time'] tick.time = row['Time']
tick.datetime = datetime.strptime(tick.date + ' ' + tick.time, '%Y%m%d %H:%M:%S.%f')
try:
tick.datetime = datetime.strptime(tick.date + ' ' + tick.time, '%Y%m%d %H:%M:%S.%f')
except Exception as ex:
self.writeCtaError(u'日期转换错误:{0},{1}:{2}'.format(tick.date + ' ' + tick.time, Exception, ex))
continue
# 修正毫秒
if tick.datetime.replace(microsecond = 0) == dt:
# 与上一个tick的时间去除毫秒后相同,修改为500毫秒
tick.datetime=tick.datetime.replace(microsecond = 500)
tick.time = tick.datetime.strftime('%H:%M:%S.%f')
else:
tick.datetime = tick.datetime.replace(microsecond=0)
tick.time = tick.datetime.strftime('%H:%M:%S.%f')
dt = tick.datetime
tick.lastPrice = float(row['LastPrice']) tick.lastPrice = float(row['LastPrice'])
tick.volume = int(float(row['LVolume'])) tick.volume = int(float(row['LVolume']))
@ -622,27 +645,49 @@ class BacktestingEngine(object):
or (tick.askPrice1 == float('1.79769E308') and tick.askVolume1 == 0): or (tick.askPrice1 == float('1.79769E308') and tick.askVolume1 == 0):
continue continue
leg2Ticks[tick.date + ' ' + tick.time] = tick dtStr = tick.date + ' ' + tick.time
if dtStr in leg2Ticks:
self.writeCtaError(u'日内数据重复,异常,数据时间为:{0}'.format(dtStr))
else:
leg2Ticks[dtStr] = tick
leg1CsvReadFile = file(leg1File, 'rb') leg1CsvReadFile = file(leg1File, 'rb')
reader = csv.DictReader((line.replace('\0', '') for line in leg1CsvReadFile), delimiter=",") #reader = csv.DictReader((line.replace('\0',' ') for line in leg1CsvReadFile), delimiter=",")
reader = csv.DictReader(leg1CsvReadFile, delimiter=",")
self.writeCtaLog(u'加载{0}'.format(leg1File)) self.writeCtaLog(u'加载{0}'.format(leg1File))
dt = None
for row in reader: for row in reader:
dtStr = ' '.join([testday.strftime('%Y%m%d'), row['Time']])
arbTick = CtaTickData()
arbTick.date = testday.strftime('%Y%m%d')
arbTick.time = row['Time']
try:
arbTick.datetime = datetime.strptime(arbTick.date + ' ' + arbTick.time, '%Y%m%d %H:%M:%S.%f')
except Exception as ex:
self.writeCtaError(u'日期转换错误:{0},{1}:{2}'.format(arbTick.date + ' ' + arbTick.time, Exception, ex))
continue
# 修正毫秒
if arbTick.datetime.replace(microsecond=0) == dt:
# 与上一个tick的时间去除毫秒后相同,修改为500毫秒
arbTick.datetime = arbTick.datetime.replace(microsecond=500)
arbTick.time = arbTick.datetime.strftime('%H:%M:%S.%f')
else:
arbTick.datetime = arbTick.datetime.replace(microsecond=0)
arbTick.time = arbTick.datetime.strftime('%H:%M:%S.%f')
dt = arbTick.datetime
dtStr = ' '.join([arbTick.date, arbTick.time])
if dtStr in leg2Ticks: if dtStr in leg2Ticks:
leg2Tick = leg2Ticks[dtStr] leg2Tick = leg2Ticks[dtStr]
arbTick = CtaTickData()
arbTick.vtSymbol = self.symbol arbTick.vtSymbol = self.symbol
arbTick.symbol = self.symbol arbTick.symbol = self.symbol
arbTick.date = testday.strftime('%Y%m%d')
arbTick.time = row['Time']
arbTick.datetime = datetime.strptime(arbTick.date + ' ' + arbTick.time, '%Y%m%d %H:%M:%S.%f')
arbTick.lastPrice = EMPTY_FLOAT arbTick.lastPrice = EMPTY_FLOAT
arbTick.volume = EMPTY_INT arbTick.volume = EMPTY_INT
@ -667,6 +712,8 @@ class BacktestingEngine(object):
arbTicks.append(arbTick) arbTicks.append(arbTick)
del leg2Ticks[dtStr]
# 保存到历史目录 # 保存到历史目录
if len(arbTicks) > 0: if len(arbTicks) > 0:
self.__saveArbTicksToLocalCache(cachefilename, arbTicks) self.__saveArbTicksToLocalCache(cachefilename, arbTicks)
@ -790,7 +837,7 @@ class BacktestingEngine(object):
if not (bar.datetime < self.dataStartDate or bar.datetime >= self.dataEndDate): if not (bar.datetime < self.dataStartDate or bar.datetime >= self.dataEndDate):
self.newBar(bar) self.newBar(bar)
except Exception, ex: except Exception as ex:
self.writeCtaLog(u'{0}:{1}'.format(Exception,ex)) self.writeCtaLog(u'{0}:{1}'.format(Exception,ex))
continue continue
@ -1109,8 +1156,8 @@ class BacktestingEngine(object):
# 从字典中删除该限价单 # 从字典中删除该限价单
try: try:
del self.workingLimitOrderDict[orderID] del self.workingLimitOrderDict[orderID]
except Exception,ex: except Exception as ex:
self.writeCtaError(u'{0}:{1}'.format(Exception,ex)) self.writeCtaError(u'{0}:{1}'.format(Exception, ex))
if self.calculateMode == self.REALTIME_MODE: if self.calculateMode == self.REALTIME_MODE:
self.realtimeCalculate() self.realtimeCalculate()
@ -1187,6 +1234,7 @@ class BacktestingEngine(object):
# 从字典中删除该限价单 # 从字典中删除该限价单
del self.workingStopOrderDict[stopOrderID] del self.workingStopOrderDict[stopOrderID]
# 若采用实时计算净值
if self.calculateMode == self.REALTIME_MODE: if self.calculateMode == self.REALTIME_MODE:
self.realtimeCalculate() self.realtimeCalculate()

View File

@ -52,6 +52,10 @@ TICK_DB_NAME = 'VnTrader_Tick_Db'
DAILY_DB_NAME = 'VnTrader_Daily_Db' DAILY_DB_NAME = 'VnTrader_Daily_Db'
MINUTE_DB_NAME = 'VnTrader_1Min_Db' MINUTE_DB_NAME = 'VnTrader_1Min_Db'
# 引擎类型,用于区分当前策略的运行环境
ENGINETYPE_BACKTESTING = 'backtesting' # 回测
ENGINETYPE_TRADING = 'trading' # 实盘
# CTA引擎中涉及的数据类定义 # CTA引擎中涉及的数据类定义
from vtConstant import EMPTY_UNICODE, EMPTY_STRING, EMPTY_FLOAT, EMPTY_INT, COLOR_EQUAL from vtConstant import EMPTY_UNICODE, EMPTY_STRING, EMPTY_FLOAT, EMPTY_INT, COLOR_EQUAL

View File

@ -61,6 +61,13 @@ class DoubleEmaDemo(CtaTemplate):
"""Constructor""" """Constructor"""
super(DoubleEmaDemo, self).__init__(ctaEngine, setting) super(DoubleEmaDemo, self).__init__(ctaEngine, setting)
# 注意策略类中的可变对象属性通常是list和dict等在策略初始化时需要重新创建
# 否则会出现多个策略实例之间数据共享的情况,有可能导致潜在的策略逻辑错误风险,
# 策略类中的这些可变对象属性可以选择不写全都放在__init__下面写主要是为了阅读
# 策略时方便(更多是个编程习惯的选择)
self.fastMa = []
self.slowMa = []
#---------------------------------------------------------------------- #----------------------------------------------------------------------
def onInit(self): def onInit(self):
"""初始化策略(必须由用户继承实现)""" """初始化策略(必须由用户继承实现)"""
@ -76,14 +83,14 @@ class DoubleEmaDemo(CtaTemplate):
def onStart(self): def onStart(self):
"""启动策略(必须由用户继承实现)""" """启动策略(必须由用户继承实现)"""
self.writeCtaLog(u'双EMA演示策略启动') self.writeCtaLog(u'双EMA演示策略启动')
self.putEvent()
#---------------------------------------------------------------------- #----------------------------------------------------------------------
def onStop(self): def onStop(self):
"""停止策略(必须由用户继承实现)""" """停止策略(必须由用户继承实现)"""
self.writeCtaLog(u'双EMA演示策略停止') self.writeCtaLog(u'双EMA演示策略停止')
self.putEvent() self.putEvent()
self.putEvent()
#---------------------------------------------------------------------- #----------------------------------------------------------------------
def onTick(self, tick): def onTick(self, tick):
"""收到行情TICK推送必须由用户继承实现""" """收到行情TICK推送必须由用户继承实现"""

View File

@ -77,6 +77,12 @@ class CtaEngine(object):
# key为vtSymbolvalue为PositionBuffer对象 # key为vtSymbolvalue为PositionBuffer对象
self.posBufferDict = {} self.posBufferDict = {}
# 引擎类型为实盘
self.engineType = ENGINETYPE_TRADING
# tick缓存
self.tickDict = {}
# 注册事件监听 # 注册事件监听
self.registerEvent() self.registerEvent()
@ -113,12 +119,19 @@ class CtaEngine(object):
# 如果获取持仓缓存失败,则默认平昨 # 如果获取持仓缓存失败,则默认平昨
if not posBuffer: if not posBuffer:
req.offset = OFFSET_CLOSE req.offset = OFFSET_CLOSE
# 否则如果有多头今仓,则使用平今
elif posBuffer.longToday: # modified by IncenseLee 2016/11/08改为优先平昨仓
req.offset= OFFSET_CLOSETODAY elif posBuffer.longYd :
# 其他情况使用平昨
else:
req.offset = OFFSET_CLOSE req.offset = OFFSET_CLOSE
else:
req.offset = OFFSET_CLOSETODAY
# 否则如果有多头今仓,则使用平今
#elif posBuffer.longToday:
# req.offset= OFFSET_CLOSETODAY
# 其他情况使用平昨
#else:
# req.offset = OFFSET_CLOSE
elif orderType == CTAORDER_SHORT: elif orderType == CTAORDER_SHORT:
req.direction = DIRECTION_SHORT req.direction = DIRECTION_SHORT
@ -136,12 +149,19 @@ class CtaEngine(object):
# 如果获取持仓缓存失败,则默认平昨 # 如果获取持仓缓存失败,则默认平昨
if not posBuffer: if not posBuffer:
req.offset = OFFSET_CLOSE req.offset = OFFSET_CLOSE
# 否则如果有空头今仓,则使用平今
elif posBuffer.shortToday: #modified by IncenseLee 2016/11/08改为优先平昨仓
req.offset= OFFSET_CLOSETODAY elif posBuffer.shortYd:
# 其他情况使用平昨
else:
req.offset = OFFSET_CLOSE req.offset = OFFSET_CLOSE
else:
req.offset = OFFSET_CLOSETODAY
# 否则如果有空头今仓,则使用平今
#elif posBuffer.shortToday:
# req.offset= OFFSET_CLOSETODAY
# 其他情况使用平昨
#else:
# req.offset = OFFSET_CLOSE
vtOrderID = self.mainEngine.sendOrder(req, contract.gatewayName) # 发单 vtOrderID = self.mainEngine.sendOrder(req, contract.gatewayName) # 发单
@ -191,12 +211,18 @@ class CtaEngine(object):
self.writeCtaLog(u'从所有订单{0}中撤销{1}'.format(len(l), symbol)) self.writeCtaLog(u'从所有订单{0}中撤销{1}'.format(len(l), symbol))
for order in l: for order in l:
if symbol == EMPTY_STRING:
symbolCond = True
else:
symbolCond = order.symbol == symbol
if offset == EMPTY_STRING: if offset == EMPTY_STRING:
offsetCond = True offsetCond = True
else: else:
offsetCond = order.offset == offset offsetCond = order.offset == offset
if order.symbol == symbol and offsetCond: if symbolCond and offsetCond:
req = VtCancelOrderReq() req = VtCancelOrderReq()
req.symbol = order.symbol req.symbol = order.symbol
req.exchange = order.exchange req.exchange = order.exchange
@ -304,6 +330,9 @@ class CtaEngine(object):
# 1. 获取事件的Tick数据 # 1. 获取事件的Tick数据
tick = event.dict_['data'] tick = event.dict_['data']
# 缓存最新tick
self.tickDict[tick.vtSymbol] = tick
# 2.收到tick行情后优先处理本地停止单检查是否要立即发出 # 2.收到tick行情后优先处理本地停止单检查是否要立即发出
self.processStopOrder(tick) self.processStopOrder(tick)
@ -327,7 +356,6 @@ class CtaEngine(object):
ctaTick.datetime = datetime.strptime(' '.join([today, tick.time]), '%Y%m%d %H:%M:%S.%f') ctaTick.datetime = datetime.strptime(' '.join([today, tick.time]), '%Y%m%d %H:%M:%S.%f')
ctaTick.date = today ctaTick.date = today
# 逐个推送到策略实例中 # 逐个推送到策略实例中
l = self.tickStrategyDict[tick.vtSymbol] l = self.tickStrategyDict[tick.vtSymbol]
for strategy in l: for strategy in l:
@ -484,7 +512,7 @@ class CtaEngine(object):
try: try:
name = setting['name'] name = setting['name']
className = setting['className'] className = setting['className']
except Exception, e: except Exception as e:
self.writeCtaLog(u'载入策略出错:%s' %e) self.writeCtaLog(u'载入策略出错:%s' %e)
return return
@ -750,9 +778,3 @@ class PositionBuffer(object):
else: else:
self.longPosition -= trade.volume self.longPosition -= trade.volume
self.longYd -= trade.volume self.longYd -= trade.volume

View File

@ -196,6 +196,11 @@ class CtaLineBar(object):
self.lineUpperBand = [] # 上轨 self.lineUpperBand = [] # 上轨
self.lineMiddleBand = [] # 中线 self.lineMiddleBand = [] # 中线
self.lineLowerBand = [] # 下轨 self.lineLowerBand = [] # 下轨
self.lineBollStd =[] # 标准差
self.lastBollUpper = EMPTY_FLOAT # 最后一根K的Boll上轨数值与MinDiff取整
self.lastBollMiddle = EMPTY_FLOAT # 最后一根K的Boll中轨数值与MinDiff取整
self.lastBollLower = EMPTY_FLOAT # 最后一根K的Boll下轨数值与MinDiff取整+1
# K线的KDJ指标计算数据 # K线的KDJ指标计算数据
self.inputKdjLen = EMPTY_INT # KDJ指标的长度,缺省是9 self.inputKdjLen = EMPTY_INT # KDJ指标的长度,缺省是9
@ -497,12 +502,14 @@ class CtaLineBar(object):
or (tick.datetime.hour == 2 and tick.datetime.minute == 30): or (tick.datetime.hour == 2 and tick.datetime.minute == 30):
endtick = True endtick = True
# 夜盘1:30收盘
if self.shortSymbol in NIGHT_MARKET_SQ2 and tick.datetime.hour == 1 and tick.datetime.minute == 00: if self.shortSymbol in NIGHT_MARKET_SQ2 and tick.datetime.hour == 1 and tick.datetime.minute == 00:
endtick = True endtick = True
# 夜盘23:00收盘
if self.shortSymbol in NIGHT_MARKET_SQ3 and tick.datetime.hour == 23 and tick.datetime.minute == 00: if self.shortSymbol in NIGHT_MARKET_SQ3 and tick.datetime.hour == 23 and tick.datetime.minute == 00:
endtick = True endtick = True
# 夜盘23:30收盘
if self.shortSymbol in NIGHT_MARKET_ZZ or self.shortSymbol in NIGHT_MARKET_DL: if self.shortSymbol in NIGHT_MARKET_ZZ or self.shortSymbol in NIGHT_MARKET_DL:
if tick.datetime.hour == 23 and tick.datetime.minute == 30: if tick.datetime.hour == 23 and tick.datetime.minute == 30:
endtick = True endtick = True
@ -1109,10 +1116,25 @@ class CtaLineBar(object):
del self.lineMiddleBand[0] del self.lineMiddleBand[0]
if len(self.lineLowerBand) > self.inputBollLen*8: if len(self.lineLowerBand) > self.inputBollLen*8:
del self.lineLowerBand[0] del self.lineLowerBand[0]
if len(self.lineBollStd) > self.inputBollLen * 8:
del self.lineBollStd[0]
# 1标准差
std = (upper[-1] - lower[-1]) / (self.inputBollStdRate*2)
self.lineBollStd.append(std)
u = round(upper[-1], 2)
self.lineUpperBand.append(u) # 上轨
self.lastBollUpper = u - u % self.minDiff # 上轨取整
m = round(middle[-1], 2)
self.lineMiddleBand.append(m) # 中轨
self.lastBollMiddle = m - m % self.minDiff # 中轨取整
l = round(lower[-1], 2)
self.lineLowerBand.append(l) # 下轨
self.lastBollLower = l - l % self.minDiff # 下轨取整
self.lineUpperBand.append(upper[-1])
self.lineMiddleBand.append(middle[-1])
self.lineLowerBand.append(lower[-1])
def __recountKdj(self): def __recountKdj(self):
"""KDJ指标""" """KDJ指标"""
@ -1277,3 +1299,232 @@ class CtaLineBar(object):
if DEBUGCTALOG: if DEBUGCTALOG:
self.strategy.writeCtaLog(u'['+self.name+u'-DEBUG]'+content) self.strategy.writeCtaLog(u'['+self.name+u'-DEBUG]'+content)
class CtaDayBar(object):
"""日线"""
# 区别:
# -使用tick模式时当tick到达后最新一个lineBar[-1]是当前的正在拟合的bar不断累积tick传统按照OnBar来计算的话是使用LineBar[-2]。
# -使用bar模式时当一个bar到达时lineBar[-1]是当前生成出来的Bar,不再更新
TICK_MODE = 'tick'
BAR_MODE = 'bar'
# 参数列表,保存了参数的名称
paramList = ['vtSymbol']
def __init__(self, strategy, onBarFunc, setting=None, ):
self.paramList.append('inputPreLen')
self.paramList.append('minDiff')
self.paramList.append('shortSymbol')
self.paramList.append('name')
# 输入参数
self.name = u'DayBar'
self.mode = self.TICK_MODE
self.inputPreLen = EMPTY_INT # 1
# OnBar事件回调函数
self.onBarFunc = onBarFunc
self.lineBar = []
self.currTick = None
self.lastTick = None
self.shortSymbol = EMPTY_STRING # 商品的短代码
self.minDiff = 1 # 商品的最小价格单位
def onTick(self, tick):
"""行情更新"""
if self.currTick is None:
self.currTick = tick
self.__drawLineBar(tick)
self.lastTick = tick
def addBar(self, bar):
"""予以外部初始化程序增加bar"""
l1 = len(self.lineBar)
if l1 == 0:
bar.datetme = bar.datetime.replace(minute=0, second=0)
bar.time = bar.datetime.strftime('%H:%M:%S')
self.lineBar.append(bar)
return
# 与最后一个BAR的时间比对判断是否超过K线的周期
lastBar = self.lineBar[-1]
if bar.tradingDay != lastBar.datetime:
bar.datetme = bar.datetime.replace(minute=0, second=0)
bar.time = bar.datetime.strftime('%H:%M:%S')
self.lineBar.append(bar)
self.onBar(lastBar)
return
# 更新最后一个bar
# 此段代码针对一部分短周期生成长周期的k线更新如3根5分钟k线合并成1根15分钟k线。
lastBar.close = bar.close
lastBar.high = max(lastBar.high, bar.high)
lastBar.low = min(lastBar.low, bar.low)
lastBar.mid4 = round((2 * lastBar.close + lastBar.high + lastBar.low) / 4, 2)
lastBar.mid5 = round((2 * lastBar.close + lastBar.open + lastBar.high + lastBar.low) / 5, 2)
def __firstTick(self, tick):
""" K线的第一个Tick数据"""
self.bar = CtaBarData() # 创建新的K线
self.bar.vtSymbol = tick.vtSymbol
self.bar.symbol = tick.symbol
self.bar.exchange = tick.exchange
self.bar.openInterest = tick.openInterest
self.bar.open = tick.lastPrice # O L H C
self.bar.high = tick.lastPrice
self.bar.low = tick.lastPrice
self.bar.close = tick.lastPrice
# K线的日期时间
self.bar.tradingDay = tick.tradingDay # K线所在的交易日期
self.bar.date = tick.date # K线的日期夜盘的话与交易日期不同哦
self.bar.datetime = tick.datetime
# K线的日期时间去除分钟、秒设为第一个Tick的时间
self.bar.datetime = self.bar.datetime.replace(minute=0, second=0, microsecond=0)
self.bar.time = self.bar.datetime.strftime('%H:%M:%S')
self.barFirstTick = True # 标识该Tick属于该Bar的第一个tick数据
self.lineBar.append(self.bar) # 推入到lineBar队列
# ----------------------------------------------------------------------
def __drawLineBar(self, tick):
"""生成 line Bar """
l1 = len(self.lineBar)
# 保存第一个K线数据
if l1 == 0:
self.__firstTick(tick)
return
# 清除480周期前的数据
if l1 > 60 * 8:
del self.lineBar[0]
# 与最后一个BAR的时间比对判断是否超过5分钟
lastBar = self.lineBar[-1]
# 处理日内的间隔时段最后一个tick如10:15分11:30分15:00 和 2:30分
endtick = False
if (tick.datetime.hour == 10 and tick.datetime.minute == 15) \
or (tick.datetime.hour == 11 and tick.datetime.minute == 30) \
or (tick.datetime.hour == 15 and tick.datetime.minute == 00) \
or (tick.datetime.hour == 2 and tick.datetime.minute == 30):
endtick = True
# 夜盘1:30收盘
if self.shortSymbol in NIGHT_MARKET_SQ2 and tick.datetime.hour == 1 and tick.datetime.minute == 00:
endtick = True
# 夜盘23:00收盘
if self.shortSymbol in NIGHT_MARKET_SQ3 and tick.datetime.hour == 23 and tick.datetime.minute == 00:
endtick = True
# 夜盘23:30收盘
if self.shortSymbol in NIGHT_MARKET_ZZ or self.shortSymbol in NIGHT_MARKET_DL:
if tick.datetime.hour == 23 and tick.datetime.minute == 30:
endtick = True
# 满足时间要求,tick的时间(夜盘21点或者日盘9点上一个tick为日盘收盘时间
if (tick.datetime.hour == 21 or tick.datetime.hour == 9 ) and 14 <= self.lastTick.datetime.hour <= 15:
# 创建并推入新的Bar
self.__firstTick(tick)
# 触发OnBar事件
self.onBar(lastBar)
else:
# 更新当前最后一个bar
self.barFirstTick = False
# 更新最高价、最低价、收盘价、成交量
lastBar.high = max(lastBar.high, tick.lastPrice)
lastBar.low = min(lastBar.low, tick.lastPrice)
lastBar.close = tick.lastPrice
# 更新Bar的颜色
if lastBar.close > lastBar.open:
lastBar.color = COLOR_RED
elif lastBar.close < lastBar.open:
lastBar.color = COLOR_BLUE
else:
lastBar.color = COLOR_EQUAL
def displayLastBar(self):
"""显示最后一个Bar的信息"""
msg = u'[' + self.name + u']'
if len(self.lineBar) < 2:
return msg
if self.mode == self.TICK_MODE:
displayBar = self.lineBar[-2]
else:
displayBar = self.lineBar[-1]
msg = msg + u'{0} o:{1};h{2};l:{3};c:{4}'. \
format(displayBar.date + ' ' + displayBar.time, displayBar.open, displayBar.high,
displayBar.low, displayBar.close)
return msg
def onBar(self, bar):
"""OnBar事件"""
self.__recountPreHighLow()
# 回调上层调用者
self.onBarFunc(bar)
# ----------------------------------------------------------------------
def __recountPreHighLow(self):
"""计算 K线的前周期最高和最低"""
if self.inputPreLen <= 0: return # 不计算
# 1、lineBar满足长度才执行计算
if len(self.lineBar) < self.inputPreLen:
self.writeCtaLog(u'数据未充分,当前{0}r数据数量{1}计算High、Low需要{2}'.
format(self.name, len(self.lineBar), self.inputPreLen))
return
# 2.计算前inputPreLen周期内(不包含当前周期的Bar高点和低点
preHigh = EMPTY_FLOAT
preLow = EMPTY_FLOAT
if self.mode == self.TICK_MODE:
idx = 2
else:
idx = 1
for i in range(len(self.lineBar)-idx, len(self.lineBar)-idx-self.inputPreLen, -1):
if self.lineBar[i].high > preHigh or preHigh == EMPTY_FLOAT:
preHigh = self.lineBar[i].high # 前InputPreLen周期高点
if self.lineBar[i].low < preLow or preLow == EMPTY_FLOAT:
preLow = self.lineBar[i].low # 前InputPreLen周期低点
# 保存
if len(self.preHigh) > self.inputPreLen * 8:
del self.preHigh[0]
self.preHigh.append(preHigh)
# 保存
if len(self.preLow)> self.inputPreLen * 8:
del self.preLow[0]
self.preLow.append(preLow)

View File

@ -73,7 +73,6 @@ class CtaPosition:
"""平、减仓""" """平、减仓"""
if direction == DIRECTION_LONG: # 平空仓 Cover if direction == DIRECTION_LONG: # 平空仓 Cover
if self.pos + vol > 0: if self.pos + vol > 0:
self.writeCtaLog(u'异常,超出仓位,当前仓位:{0},平仓:{1}'.format(self.pos,vol)) self.writeCtaLog(u'异常,超出仓位,当前仓位:{0},平仓:{1}'.format(self.pos,vol))
self.strategy.pos = self.pos self.strategy.pos = self.pos
@ -84,7 +83,6 @@ class CtaPosition:
self.strategy.pos = self.pos self.strategy.pos = self.pos
if direction == DIRECTION_SHORT: # 平多仓 if direction == DIRECTION_SHORT: # 平多仓
if self.pos - vol < 0 : if self.pos - vol < 0 :
self.writeCtaLog(u'异常,超出仓位,当前仓位:{0},加仓:{1}'.format(self.pos, vol)) self.writeCtaLog(u'异常,超出仓位,当前仓位:{0},加仓:{1}'.format(self.pos, vol))
self.strategy.pos = self.pos self.strategy.pos = self.pos

View File

@ -8,13 +8,15 @@
在CTA_setting.json中写入具体每个策略对象的类和合约设置 在CTA_setting.json中写入具体每个策略对象的类和合约设置
''' '''
from ctaTemplate import DataRecorder #from ctaTemplate import DataRecorder
from ctaDemo import DoubleEmaDemo #from ctaDemo import DoubleEmaDemo
from strategy22_ArbitrageGrid import Strategy22 from strategy22_ArbitrageGrid import Strategy22
from strategy24_M15RB import Strategy24 from strategy24_M15RB import Strategy24
from strategy25_NonStdArbitrageGrid import Strategy25
STRATEGY_CLASS = {} STRATEGY_CLASS = {}
STRATEGY_CLASS['DataRecorder'] = DataRecorder #STRATEGY_CLASS['DataRecorder'] = DataRecorder
STRATEGY_CLASS['DoubleEmaDemo'] = DoubleEmaDemo #STRATEGY_CLASS['DoubleEmaDemo'] = DoubleEmaDemo
STRATEGY_CLASS['Strategy22'] = Strategy22 STRATEGY_CLASS['Strategy22'] = Strategy22
STRATEGY_CLASS['Strategy24'] = Strategy24 STRATEGY_CLASS['Strategy24'] = Strategy24
STRATEGY_CLASS['Strategy25'] = Strategy25

View File

@ -257,108 +257,9 @@ class CtaTemplate(object):
def putEvent(self): def putEvent(self):
"""发出策略状态变化事件""" """发出策略状态变化事件"""
self.ctaEngine.putStrategyEvent(self.name) self.ctaEngine.putStrategyEvent(self.name)
########################################################################
class DataRecorder(CtaTemplate):
"""
纯粹用来记录历史数据的工具基于CTA策略
建议运行在实际交易程序外的一个vn.trader实例中
本工具会记录Tick和1分钟K线数据
"""
className = 'DataRecorder'
author = u'用Python的交易员'
# 策略的基本参数
name = EMPTY_UNICODE # 策略实例名称
vtSymbol = EMPTY_STRING # 交易的合约vt系统代码
# 策略的变量
bar = None # K线数据对象
barMinute = EMPTY_STRING # 当前的分钟,初始化设为-1
# 变量列表,保存了变量的名称
varList = ['inited',
'trading',
'pos',
'barMinute']
#---------------------------------------------------------------------- #----------------------------------------------------------------------
def __init__(self, ctaEngine, setting): def getEngineType(self):
"""Constructor""" """查询当前运行的环境"""
super(DataRecorder, self).__init__(ctaEngine, setting) return self.ctaEngine.engineType
#----------------------------------------------------------------------
def onInit(self):
"""初始化"""
self.writeCtaLog(u'数据记录工具初始化')
#----------------------------------------------------------------------
def onStart(self):
"""启动策略(必须由用户继承实现)"""
self.writeCtaLog(u'数据记录工具启动')
self.putEvent()
#----------------------------------------------------------------------
def onStop(self):
"""停止策略(必须由用户继承实现)"""
self.writeCtaLog(u'数据记录工具停止')
self.putEvent()
#----------------------------------------------------------------------
def onTick(self, tick):
"""收到行情TICK推送"""
# 收到Tick后首先插入到数据库里
self.insertTick(tick)
# 计算K线
tickMinute = tick.datetime.minute
if tickMinute != self.barMinute: # 如果分钟变了则把旧的K线插入数据库并生成新的K线
if self.bar:
self.onBar(self.bar)
bar = CtaBarData() # 创建新的K线目的在于防止之前K线对象在插入Mongo中被再次修改导致出错
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的时间
bar.volume = tick.volume
bar.openInterest = tick.openInterest
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
bar.volume = bar.volume + tick.volume # 成交量是累加的
bar.openInterest = tick.openInterest # 持仓量直接更新
#----------------------------------------------------------------------
def onOrder(self, order):
"""收到委托变化推送"""
pass
#----------------------------------------------------------------------
def onTrade(self, trade):
"""收到成交推送"""
pass
#----------------------------------------------------------------------
def onBar(self, bar):
"""收到Bar推送"""
self.insertBar(bar)

View File

@ -8,6 +8,7 @@ CTA模块相关的GUI控制组件
from uiBasicWidget import QtGui, QtCore, BasicCell from uiBasicWidget import QtGui, QtCore, BasicCell
from eventEngine import * from eventEngine import *
from time import sleep from time import sleep
import os
######################################################################## ########################################################################
@ -54,6 +55,8 @@ class CtaValueMonitor(QtGui.QTableWidget):
cell = self.keyCellDict[k] cell = self.keyCellDict[k]
cell.setText(unicode(v)) cell.setText(unicode(v))
self.resizeColumnsToContents()
self.resizeRowsToContents()
######################################################################## ########################################################################
class CtaStrategyManager(QtGui.QGroupBox): class CtaStrategyManager(QtGui.QGroupBox):
@ -183,7 +186,9 @@ class CtaEngineManager(QtGui.QWidget):
#---------------------------------------------------------------------- #----------------------------------------------------------------------
def initUi(self): def initUi(self):
"""初始化界面""" """初始化界面"""
self.setWindowTitle(u'CTA策略') path = os.getcwd().rsplit('\\')[-1]
self.setWindowTitle(u'{0} CTA策略'.format(path))
# 按钮 # 按钮
loadButton = QtGui.QPushButton(u'加载策略') loadButton = QtGui.QPushButton(u'加载策略')