From f37440e6bbd3790f0177e6d4a4b1e2da824005a7 Mon Sep 17 00:00:00 2001 From: msincenselee Date: Wed, 30 Nov 2016 14:26:08 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=88=E5=B9=B6vnpy=20v1.3=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vn.trader/ctaAlgo/ctaBacktesting.py | 114 ++++++++---- vn.trader/ctaAlgo/ctaBase.py | 4 + vn.trader/ctaAlgo/ctaDemo.py | 11 +- vn.trader/ctaAlgo/ctaEngine.py | 60 +++++-- vn.trader/ctaAlgo/ctaLineBar.py | 259 +++++++++++++++++++++++++++- vn.trader/ctaAlgo/ctaPosition.py | 2 - vn.trader/ctaAlgo/ctaSetting.py | 12 +- vn.trader/ctaAlgo/ctaTemplate.py | 105 +---------- vn.trader/ctaAlgo/uiCtaWidget.py | 7 +- 9 files changed, 406 insertions(+), 168 deletions(-) diff --git a/vn.trader/ctaAlgo/ctaBacktesting.py b/vn.trader/ctaAlgo/ctaBacktesting.py index 963b394c..9d1bcedd 100644 --- a/vn.trader/ctaAlgo/ctaBacktesting.py +++ b/vn.trader/ctaAlgo/ctaBacktesting.py @@ -57,6 +57,9 @@ class BacktestingEngine(object): self.stopOrderDict = {} # 停止单撤销后不会从本字典中删除 self.workingStopOrderDict = {} # 停止单撤销后会从本字典中删除 + # 引擎类型为回测 + self.engineType = ENGINETYPE_BACKTESTING + # 回测相关 self.strategy = None # 回测策略 self.mode = self.BAR_MODE # 回测模式,默认为K线 @@ -401,8 +404,7 @@ class BacktestingEngine(object): else: self.writeCtaLog(u'MysqlDB未连接,请检查') - except MySQLdb.Error, e: - + except MySQLdb.Error as e: self.writeCtaLog(u'MysqlDB载入数据失败,请检查.Error {0}'.format(e)) def __dataToTick(self, data): @@ -494,15 +496,14 @@ class BacktestingEngine(object): else: 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])) # 出错后缺省返回 return startDate-timedelta(days=3) # ---------------------------------------------------------------------- - def runBackTestingWithArbTickFile(self, arbSymbol): + def runBackTestingWithArbTickFile(self,mainPath, arbSymbol): """运行套利回测(使用本地tickcsv数据) 参数:套利代码 SP rb1610&rb1701 added by IncenseLee @@ -567,38 +568,42 @@ class BacktestingEngine(object): 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): - 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)) cachefilename = u'{0}_{1}_{2}_{3}_{4}'.format(self.symbol,leg1,leg2, mainPath, testday.strftime('%Y%m%d')) arbTicks = self.__loadArbTicksFromLocalCache(cachefilename) + dt = None + 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 leg2Ticks = {} 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)) for row in reader: tick = CtaTickData() @@ -607,8 +612,26 @@ class BacktestingEngine(object): tick.symbol = self.symbol tick.date = testday.strftime('%Y%m%d') + tick.tradingDay = tick.date 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.volume = int(float(row['LVolume'])) @@ -622,27 +645,49 @@ class BacktestingEngine(object): or (tick.askPrice1 == float('1.79769E308') and tick.askVolume1 == 0): 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') - 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)) + dt = None 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: leg2Tick = leg2Ticks[dtStr] - arbTick = CtaTickData() - arbTick.vtSymbol = 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.volume = EMPTY_INT @@ -667,6 +712,8 @@ class BacktestingEngine(object): arbTicks.append(arbTick) + del leg2Ticks[dtStr] + # 保存到历史目录 if len(arbTicks) > 0: self.__saveArbTicksToLocalCache(cachefilename, arbTicks) @@ -790,7 +837,7 @@ class BacktestingEngine(object): if not (bar.datetime < self.dataStartDate or bar.datetime >= self.dataEndDate): self.newBar(bar) - except Exception, ex: + except Exception as ex: self.writeCtaLog(u'{0}:{1}'.format(Exception,ex)) continue @@ -1109,8 +1156,8 @@ class BacktestingEngine(object): # 从字典中删除该限价单 try: del self.workingLimitOrderDict[orderID] - except Exception,ex: - self.writeCtaError(u'{0}:{1}'.format(Exception,ex)) + except Exception as ex: + self.writeCtaError(u'{0}:{1}'.format(Exception, ex)) if self.calculateMode == self.REALTIME_MODE: self.realtimeCalculate() @@ -1187,6 +1234,7 @@ class BacktestingEngine(object): # 从字典中删除该限价单 del self.workingStopOrderDict[stopOrderID] + # 若采用实时计算净值 if self.calculateMode == self.REALTIME_MODE: self.realtimeCalculate() diff --git a/vn.trader/ctaAlgo/ctaBase.py b/vn.trader/ctaAlgo/ctaBase.py index 35b5e001..0f53b700 100644 --- a/vn.trader/ctaAlgo/ctaBase.py +++ b/vn.trader/ctaAlgo/ctaBase.py @@ -52,6 +52,10 @@ TICK_DB_NAME = 'VnTrader_Tick_Db' DAILY_DB_NAME = 'VnTrader_Daily_Db' MINUTE_DB_NAME = 'VnTrader_1Min_Db' +# 引擎类型,用于区分当前策略的运行环境 +ENGINETYPE_BACKTESTING = 'backtesting' # 回测 +ENGINETYPE_TRADING = 'trading' # 实盘 + # CTA引擎中涉及的数据类定义 from vtConstant import EMPTY_UNICODE, EMPTY_STRING, EMPTY_FLOAT, EMPTY_INT, COLOR_EQUAL diff --git a/vn.trader/ctaAlgo/ctaDemo.py b/vn.trader/ctaAlgo/ctaDemo.py index 4a690612..1530890a 100644 --- a/vn.trader/ctaAlgo/ctaDemo.py +++ b/vn.trader/ctaAlgo/ctaDemo.py @@ -61,6 +61,13 @@ class DoubleEmaDemo(CtaTemplate): """Constructor""" super(DoubleEmaDemo, self).__init__(ctaEngine, setting) + # 注意策略类中的可变对象属性(通常是list和dict等),在策略初始化时需要重新创建, + # 否则会出现多个策略实例之间数据共享的情况,有可能导致潜在的策略逻辑错误风险, + # 策略类中的这些可变对象属性可以选择不写,全都放在__init__下面,写主要是为了阅读 + # 策略时方便(更多是个编程习惯的选择) + self.fastMa = [] + self.slowMa = [] + #---------------------------------------------------------------------- def onInit(self): """初始化策略(必须由用户继承实现)""" @@ -76,14 +83,14 @@ class DoubleEmaDemo(CtaTemplate): def onStart(self): """启动策略(必须由用户继承实现)""" self.writeCtaLog(u'双EMA演示策略启动') - + self.putEvent() #---------------------------------------------------------------------- def onStop(self): """停止策略(必须由用户继承实现)""" self.writeCtaLog(u'双EMA演示策略停止') self.putEvent() - self.putEvent() + #---------------------------------------------------------------------- def onTick(self, tick): """收到行情TICK推送(必须由用户继承实现)""" diff --git a/vn.trader/ctaAlgo/ctaEngine.py b/vn.trader/ctaAlgo/ctaEngine.py index ae77773d..775ce3e9 100644 --- a/vn.trader/ctaAlgo/ctaEngine.py +++ b/vn.trader/ctaAlgo/ctaEngine.py @@ -77,6 +77,12 @@ class CtaEngine(object): # key为vtSymbol,value为PositionBuffer对象 self.posBufferDict = {} + # 引擎类型为实盘 + self.engineType = ENGINETYPE_TRADING + + # tick缓存 + self.tickDict = {} + # 注册事件监听 self.registerEvent() @@ -113,12 +119,19 @@ class CtaEngine(object): # 如果获取持仓缓存失败,则默认平昨 if not posBuffer: req.offset = OFFSET_CLOSE - # 否则如果有多头今仓,则使用平今 - elif posBuffer.longToday: - req.offset= OFFSET_CLOSETODAY - # 其他情况使用平昨 - else: + + # modified by IncenseLee 2016/11/08,改为优先平昨仓 + elif posBuffer.longYd : 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: req.direction = DIRECTION_SHORT @@ -136,12 +149,19 @@ class CtaEngine(object): # 如果获取持仓缓存失败,则默认平昨 if not posBuffer: req.offset = OFFSET_CLOSE - # 否则如果有空头今仓,则使用平今 - elif posBuffer.shortToday: - req.offset= OFFSET_CLOSETODAY - # 其他情况使用平昨 - else: + + #modified by IncenseLee 2016/11/08,改为优先平昨仓 + elif posBuffer.shortYd: 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) # 发单 @@ -191,12 +211,18 @@ class CtaEngine(object): self.writeCtaLog(u'从所有订单{0}中撤销{1}'.format(len(l), symbol)) for order in l: + + if symbol == EMPTY_STRING: + symbolCond = True + else: + symbolCond = order.symbol == symbol + if offset == EMPTY_STRING: offsetCond = True else: offsetCond = order.offset == offset - if order.symbol == symbol and offsetCond: + if symbolCond and offsetCond: req = VtCancelOrderReq() req.symbol = order.symbol req.exchange = order.exchange @@ -304,6 +330,9 @@ class CtaEngine(object): # 1. 获取事件的Tick数据 tick = event.dict_['data'] + # 缓存最新tick + self.tickDict[tick.vtSymbol] = tick + # 2.收到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.date = today - # 逐个推送到策略实例中 l = self.tickStrategyDict[tick.vtSymbol] for strategy in l: @@ -484,7 +512,7 @@ class CtaEngine(object): try: name = setting['name'] className = setting['className'] - except Exception, e: + except Exception as e: self.writeCtaLog(u'载入策略出错:%s' %e) return @@ -750,9 +778,3 @@ class PositionBuffer(object): else: self.longPosition -= trade.volume self.longYd -= trade.volume - - - - - - diff --git a/vn.trader/ctaAlgo/ctaLineBar.py b/vn.trader/ctaAlgo/ctaLineBar.py index c7883486..9c680f4e 100644 --- a/vn.trader/ctaAlgo/ctaLineBar.py +++ b/vn.trader/ctaAlgo/ctaLineBar.py @@ -196,6 +196,11 @@ class CtaLineBar(object): self.lineUpperBand = [] # 上轨 self.lineMiddleBand = [] # 中线 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指标计算数据 self.inputKdjLen = EMPTY_INT # KDJ指标的长度,缺省是9 @@ -497,12 +502,14 @@ class CtaLineBar(object): 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 @@ -1109,10 +1116,25 @@ class CtaLineBar(object): del self.lineMiddleBand[0] if len(self.lineLowerBand) > self.inputBollLen*8: 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): """KDJ指标""" @@ -1277,3 +1299,232 @@ class CtaLineBar(object): if DEBUGCTALOG: 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) \ No newline at end of file diff --git a/vn.trader/ctaAlgo/ctaPosition.py b/vn.trader/ctaAlgo/ctaPosition.py index 365be049..b3d5269c 100644 --- a/vn.trader/ctaAlgo/ctaPosition.py +++ b/vn.trader/ctaAlgo/ctaPosition.py @@ -73,7 +73,6 @@ class CtaPosition: """平、减仓""" if direction == DIRECTION_LONG: # 平空仓 Cover - if self.pos + vol > 0: self.writeCtaLog(u'异常,超出仓位,当前仓位:{0},平仓:{1}'.format(self.pos,vol)) self.strategy.pos = self.pos @@ -84,7 +83,6 @@ class CtaPosition: self.strategy.pos = self.pos if direction == DIRECTION_SHORT: # 平多仓 - if self.pos - vol < 0 : self.writeCtaLog(u'异常,超出仓位,当前仓位:{0},加仓:{1}'.format(self.pos, vol)) self.strategy.pos = self.pos diff --git a/vn.trader/ctaAlgo/ctaSetting.py b/vn.trader/ctaAlgo/ctaSetting.py index 196f2a75..448a43b9 100644 --- a/vn.trader/ctaAlgo/ctaSetting.py +++ b/vn.trader/ctaAlgo/ctaSetting.py @@ -8,13 +8,15 @@ 在CTA_setting.json中写入具体每个策略对象的类和合约设置。 ''' -from ctaTemplate import DataRecorder -from ctaDemo import DoubleEmaDemo +#from ctaTemplate import DataRecorder +#from ctaDemo import DoubleEmaDemo from strategy22_ArbitrageGrid import Strategy22 from strategy24_M15RB import Strategy24 +from strategy25_NonStdArbitrageGrid import Strategy25 STRATEGY_CLASS = {} -STRATEGY_CLASS['DataRecorder'] = DataRecorder -STRATEGY_CLASS['DoubleEmaDemo'] = DoubleEmaDemo +#STRATEGY_CLASS['DataRecorder'] = DataRecorder +#STRATEGY_CLASS['DoubleEmaDemo'] = DoubleEmaDemo STRATEGY_CLASS['Strategy22'] = Strategy22 -STRATEGY_CLASS['Strategy24'] = Strategy24 \ No newline at end of file +STRATEGY_CLASS['Strategy24'] = Strategy24 +STRATEGY_CLASS['Strategy25'] = Strategy25 \ No newline at end of file diff --git a/vn.trader/ctaAlgo/ctaTemplate.py b/vn.trader/ctaAlgo/ctaTemplate.py index 943d357e..ab4d0619 100644 --- a/vn.trader/ctaAlgo/ctaTemplate.py +++ b/vn.trader/ctaAlgo/ctaTemplate.py @@ -257,108 +257,9 @@ class CtaTemplate(object): def putEvent(self): """发出策略状态变化事件""" 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): - """Constructor""" - super(DataRecorder, self).__init__(ctaEngine, setting) + def getEngineType(self): + """查询当前运行的环境""" + 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) \ No newline at end of file diff --git a/vn.trader/ctaAlgo/uiCtaWidget.py b/vn.trader/ctaAlgo/uiCtaWidget.py index 31de7375..17065e50 100644 --- a/vn.trader/ctaAlgo/uiCtaWidget.py +++ b/vn.trader/ctaAlgo/uiCtaWidget.py @@ -8,6 +8,7 @@ CTA模块相关的GUI控制组件 from uiBasicWidget import QtGui, QtCore, BasicCell from eventEngine import * from time import sleep +import os ######################################################################## @@ -54,6 +55,8 @@ class CtaValueMonitor(QtGui.QTableWidget): cell = self.keyCellDict[k] cell.setText(unicode(v)) + self.resizeColumnsToContents() + self.resizeRowsToContents() ######################################################################## class CtaStrategyManager(QtGui.QGroupBox): @@ -183,7 +186,9 @@ class CtaEngineManager(QtGui.QWidget): #---------------------------------------------------------------------- def initUi(self): """初始化界面""" - self.setWindowTitle(u'CTA策略') + path = os.getcwd().rsplit('\\')[-1] + + self.setWindowTitle(u'{0} CTA策略'.format(path)) # 按钮 loadButton = QtGui.QPushButton(u'加载策略')