# encoding: UTF-8 from math import floor from vnpy.trader.vtConstant import (EMPTY_INT, EMPTY_FLOAT, EMPTY_STRING, EMPTY_UNICODE, DIRECTION_LONG, DIRECTION_SHORT, STATUS_ALLTRADED, STATUS_CANCELLED, STATUS_REJECTED) ######################################################################## class StAlgoTemplate(object): """价差算法交易模板""" MODE_LONGSHORT = u'双向' MODE_LONGONLY = u'做多' MODE_SHORTONLY = u'做空' SPREAD_LONG = 1 SPREAD_SHORT = 2 #---------------------------------------------------------------------- def __init__(self, algoEngine, spread): """Constructor""" self.algoEngine = algoEngine # 算法引擎 self.spreadName = spread.name # 价差名称 self.spread = spread # 价差对象 self.algoName = EMPTY_STRING # 算法名称 self.active = False # 工作状态 self.mode = self.MODE_LONGSHORT # 工作模式 self.buyPrice = EMPTY_FLOAT # 开平仓价格 self.sellPrice = EMPTY_FLOAT self.shortPrice = EMPTY_FLOAT self.coverPrice = EMPTY_FLOAT self.maxPosSize = EMPTY_INT # 最大单边持仓量 self.maxOrderSize = EMPTY_INT # 最大单笔委托量 #---------------------------------------------------------------------- def updateSpreadTick(self, spread): """""" raise NotImplementedError #---------------------------------------------------------------------- def updateSpreadPos(self, spread): """""" raise NotImplementedError #---------------------------------------------------------------------- def updateTrade(self, trade): """""" raise NotImplementedError #---------------------------------------------------------------------- def updateOrder(self, order): """""" raise NotImplementedError #---------------------------------------------------------------------- def updateTimer(self): """""" raise NotImplementedError #---------------------------------------------------------------------- def start(self): """""" raise NotImplementedError #---------------------------------------------------------------------- def stop(self): """""" raise NotImplementedError #---------------------------------------------------------------------- def setBuyPrice(self, buyPrice): """设置买开的价格""" self.buyPrice = buyPrice #---------------------------------------------------------------------- def setSellPrice(self, sellPrice): """设置卖平的价格""" self.sellPrice = sellPrice #---------------------------------------------------------------------- def setShortPrice(self, shortPrice): """设置卖开的价格""" self.shortPrice = shortPrice #---------------------------------------------------------------------- def setCoverPrice(self, coverPrice): """设置买平的价格""" self.coverPrice = coverPrice #---------------------------------------------------------------------- def setMode(self, mode): """设置算法交易方向""" self.mode = mode #---------------------------------------------------------------------- def setMaxOrderSize(self, maxOrderSize): """设置最大单笔委托数量""" self.maxOrderSize = maxOrderSize #---------------------------------------------------------------------- def setMaxPosSize(self, maxPosSize): """设置最大持仓数量""" self.maxPosSize = maxPosSize #---------------------------------------------------------------------- def putEvent(self): """发出算法更新事件""" self.algoEngine.putAlgoEvent(self) #---------------------------------------------------------------------- def writeLog(self, content): """输出算法日志""" prefix = ' '.join([self.spreadName, self.algoName]) content = ':'.join([prefix, content]) self.algoEngine.writeLog(content) #---------------------------------------------------------------------- def getAlgoParams(self): """获取算法参数""" d = { "spreadName": self.spreadName, "algoName": self.algoName, "buyPrice": self.buyPrice, "sellPrice": self.sellPrice, "shortPrice": self.shortPrice, "coverPrice": self.coverPrice, "maxOrderSize": self.maxOrderSize, "maxPosSize": self.maxPosSize, "mode": self.mode } return d #---------------------------------------------------------------------- def setAlgoParams(self, d): """设置算法参数""" self.buyPrice = d.get('buyPrice', EMPTY_FLOAT) self.sellPrice = d.get('sellPrice', EMPTY_FLOAT) self.shortPrice = d.get('shortPrice', EMPTY_FLOAT) self.coverPrice = d.get('coverPrice', EMPTY_FLOAT) self.maxOrderSize = d.get('maxOrderSize', EMPTY_INT) self.maxPosSize = d.get('maxPosSize', EMPTY_INT) self.mode = d.get('mode', self.MODE_LONGSHORT) ######################################################################## class SniperAlgo(StAlgoTemplate): """狙击算法(市价委托)""" FINISHED_STATUS = [STATUS_ALLTRADED, STATUS_CANCELLED, STATUS_REJECTED] #---------------------------------------------------------------------- def __init__(self, algoEngine, spread): """Constructor""" super(SniperAlgo, self).__init__(algoEngine, spread) self.algoName = u'Sniper' self.quoteInterval = 2 # 主动腿报价撤单再发前等待的时间 self.quoteCount = 0 # 报价计数 self.hedgeInterval = 2 # 对冲腿对冲撤单再发前的等待时间 self.hedgeCount = 0 # 对冲计数 self.activeVtSymbol = spread.activeLeg.vtSymbol # 主动腿代码 self.passiveVtSymbols = [leg.vtSymbol for leg in spread.passiveLegs] # 被动腿代码列表 # 缓存每条腿对象的字典 self.legDict = {} self.legDict[spread.activeLeg.vtSymbol] = spread.activeLeg for leg in spread.passiveLegs: self.legDict[leg.vtSymbol] = leg self.hedgingTaskDict = {} # 被动腿需要对冲的数量字典 vtSymbol:volume self.legOrderDict = {} # vtSymbol: list of vtOrderID self.orderTradedDict = {} # vtOrderID: tradedVolume #---------------------------------------------------------------------- def updateSpreadTick(self, spread): """价差行情更新""" self.spread = spread # 若算法没有启动则直接返回 if not self.active: return # 若当前已有主动腿委托则直接返回 if (self.activeVtSymbol in self.legOrderDict and self.legOrderDict[self.activeVtSymbol]): return # 允许做多 if self.mode == self.MODE_LONGSHORT or self.mode == self.MODE_LONGONLY: # 买入 if (spread.netPos >= 0 and spread.netPos < self.maxPosSize and spread.askPrice <= self.buyPrice): self.quoteActiveLeg(self.SPREAD_LONG) self.writeLog(u'买入开仓') # 卖出 elif (spread.netPos > 0 and spread.bidPrice >= self.sellPrice): self.quoteActiveLeg(self.SPREAD_SHORT) self.writeLog(u'卖出平仓') # 允许做空 if self.mode == self.MODE_LONGSHORT or self.mode == self.MODE_SHORTONLY: # 做空 if (spread.netPos <= 0 and spread.netPos > -self.maxPosSize and spread.bidPrice >= self.shortPrice): self.quoteActiveLeg(self.SPREAD_SHORT) self.writeLog(u'卖出开仓') # 平空 elif (spread.netPos < 0 and spread.askPrice <= self.coverPrice): self.quoteActiveLeg(self.SPREAD_LONG) self.writeLog(u'买入平仓') #---------------------------------------------------------------------- def updateSpreadPos(self, spread): """价差持仓更新""" self.spread = spread #---------------------------------------------------------------------- def updateTrade(self, trade): """成交更新""" pass #---------------------------------------------------------------------- def updateOrder(self, order): """委托更新""" if not self.active: return vtOrderID = order.vtOrderID vtSymbol = order.vtSymbol newTradedVolume = order.tradedVolume lastTradedVolume = self.orderTradedDict.get(vtOrderID, 0) # 检查是否有新的成交 if newTradedVolume > lastTradedVolume: self.orderTradedDict[vtOrderID] = newTradedVolume # 缓存委托已经成交数量 volume = newTradedVolume - lastTradedVolume # 计算本次成交数量 if vtSymbol == self.activeVtSymbol: self.newActiveLegTrade(vtSymbol, order.direction, volume) else: self.newPassiveLegTrade(vtSymbol, order.direction, volume) # 处理完成委托 if order.status in self.FINISHED_STATUS: vtOrderID = order.vtOrderID vtSymbol = order.vtSymbol # 从委托列表中移除该委托 orderList = self.legOrderDict.get(vtSymbol, None) if orderList and vtOrderID in orderList: orderList.remove(vtOrderID) # 检查若是被动腿,且已经没有未完成委托,则执行对冲 if not orderList and vtSymbol in self.passiveVtSymbols: self.hedgePassiveLeg(vtSymbol) #---------------------------------------------------------------------- def updateTimer(self): """计时更新""" if not self.active: return self.quoteCount += 1 self.hedgeCount += 1 # 计时到达报价间隔后,则对尚未成交的主动腿委托全部撤单 # 收到撤单回报后清空委托列表,等待下次价差更新再发单 if self.quoteCount > self.quoteInterval: self.cancelLegOrder(self.activeVtSymbol) self.quoteCount = 0 # 计时到达对冲间隔后,则对尚未成交的全部被动腿委托全部撤单 # 收到撤单回报后,会自动发送新的对冲委托 if self.hedgeCount > self.hedgeInterval: self.cancelAllPassiveLegOrders() self.hedgeCount = 0 #---------------------------------------------------------------------- def start(self): """启动""" # 如果已经运行则直接返回状态 if self.active: return self.active # 做多检查 if self.mode != self.MODE_SHORTONLY: if self.buyPrice >= self.sellPrice: self.writeLog(u'启动失败,允许多头交易时BuyPrice必须小于SellPrice') return self.active # 做空检查 if self.mode != self.MODE_LONGONLY: if self.shortPrice <= self.coverPrice: self.writeLog(u'启动失败,允许空头交易时ShortPrice必须大于CoverPrice') return self.active # 多空检查 if self.mode == self.MODE_LONGSHORT: if self.buyPrice >= self.coverPrice: self.writeLog(u'启动失败,允许双向交易时BuyPrice必须小于CoverPrice') return self.active if self.shortPrice <= self.sellPrice: self.writeLog(u'启动失败,允许双向交易时ShortPrice必须大于SellPrice') return self.active # 启动算法 self.quoteCount = 0 self.hedgeCount = 0 self.active = True self.writeLog(u'算法启动') return self.active #---------------------------------------------------------------------- def stop(self): """停止""" if self.active: self.hedgingTaskDict.clear() self.cancelAllOrders() self.active = False self.writeLog(u'算法停止') return self.active #---------------------------------------------------------------------- def sendLegOrder(self, leg, legVolume): """发送每条腿的委托""" vtSymbol = leg.vtSymbol volume = abs(legVolume) payup = leg.payup # 发送委托 if legVolume > 0: price = leg.askPrice if leg.shortPos > 0: orderList = self.algoEngine.cover(vtSymbol, price, volume, payup) else: orderList = self.algoEngine.buy(vtSymbol, price, volume, payup) elif legVolume < 0: price = leg.bidPrice if leg.longPos > 0: orderList = self.algoEngine.sell(vtSymbol, price, volume, payup) else: orderList = self.algoEngine.short(vtSymbol, price, volume, payup) # 保存到字典中 if vtSymbol not in self.legOrderDict: self.legOrderDict[vtSymbol] = orderList else: self.legOrderDict[vtSymbol].extend(orderList) #---------------------------------------------------------------------- def quoteActiveLeg(self, direction): """发出主动腿""" spread = self.spread # 首先计算不带正负号的价差委托量 if direction == self.SPREAD_LONG: spreadVolume = min(spread.askVolume, self.maxPosSize - spread.netPos, self.maxOrderSize) # 有价差空头持仓的情况下,则本次委托最多平完空头 if spread.shortPos > 0: spreadVolume = min(spreadVolume, spread.shortPos) else: spreadVolume = min(spread.bidVolume, self.maxPosSize + spread.netPos, self.maxOrderSize) # 有价差多头持仓的情况下,则本次委托最多平完多头 if spread.longPos > 0: spreadVolume = min(spreadVolume, spread.longPos) if spreadVolume <= 0: return # 加上价差方向 if direction == self.SPREAD_SHORT: spreadVolume = -spreadVolume # 计算主动腿委托量 leg = self.legDict[self.activeVtSymbol] legVolume = spreadVolume * leg.ratio self.sendLegOrder(leg, legVolume) self.writeLog(u'发出新的主动腿%s狙击单' %self.activeVtSymbol) self.quoteCount = 0 # 重置主动腿报价撤单等待计数 #---------------------------------------------------------------------- def hedgePassiveLeg(self, vtSymbol): """被动腿对冲""" if vtSymbol not in self.hedgingTaskDict: return orderList = self.legOrderDict.get(vtSymbol, None) if orderList: return legVolume = self.hedgingTaskDict[vtSymbol] leg = self.legDict[vtSymbol] self.sendLegOrder(leg, legVolume) self.writeLog(u'发出新的被动腿%s对冲单' %vtSymbol) #---------------------------------------------------------------------- def hedgeAllPassiveLegs(self): """执行所有被动腿对冲""" for vtSymbol in self.hedgingTaskDict.keys(): self.hedgePassiveLeg(vtSymbol) self.hedgeCount = 0 # 重置被动腿对冲撤单等待计数 #---------------------------------------------------------------------- def newActiveLegTrade(self, vtSymbol, direction, volume): """新的主动腿成交""" # 输出日志 self.writeLog(u'主动腿%s成交,方向%s,数量%s' %(vtSymbol, direction, volume)) # 将主动腿成交带上方向 if direction == DIRECTION_SHORT: volume = -volume # 计算主动腿成交后,对应的价差仓位 spread = self.spread activeRatio = spread.activeLeg.ratio spreadVolume = round(volume / activeRatio) # 四舍五入求主动腿成交量对应的价差份数 # 计算价差新仓位,对应的被动腿需要对冲部分 for leg in self.spread.passiveLegs: newHedgingTask = leg.ratio * spreadVolume if leg.vtSymbol not in self.hedgingTaskDict: self.hedgingTaskDict[leg.vtSymbol] = newHedgingTask else: self.hedgingTaskDict[leg.vtSymbol] += newHedgingTask # 发出被动腿对冲委托 self.hedgeAllPassiveLegs() #---------------------------------------------------------------------- def newPassiveLegTrade(self, vtSymbol, direction, volume): """新的被动腿成交""" if vtSymbol in self.hedgingTaskDict: # 计算完成的对冲数量 if direction == DIRECTION_LONG: hedgedVolume = volume else: hedgedVolume = -volume # 计算剩余尚未完成的数量 self.hedgingTaskDict[vtSymbol] -= hedgedVolume # 如果已全部完成,则从字典中移除 if not self.hedgingTaskDict[vtSymbol]: del self.hedgingTaskDict[vtSymbol] # 输出日志 self.writeLog(u'被动腿%s成交,方向%s,数量%s' %(vtSymbol, direction, volume)) #---------------------------------------------------------------------- def cancelLegOrder(self, vtSymbol): """撤销某条腿的委托""" if vtSymbol not in self.legOrderDict: return orderList = self.legOrderDict[vtSymbol] if not orderList: return for vtOrderID in orderList: self.algoEngine.cancelOrder(vtOrderID) self.writeLog(u'撤单%s的所有委托' %vtSymbol) #---------------------------------------------------------------------- def cancelAllOrders(self): """撤销全部委托""" for orderList in self.legOrderDict.values(): for vtOrderID in orderList: self.algoEngine.cancelOrder(vtOrderID) self.writeLog(u'全部撤单') #---------------------------------------------------------------------- def cancelAllPassiveLegOrders(self): """撤销全部被动腿委托""" cancelPassive = False for vtSymbol in self.passiveVtSymbols: if vtSymbol in self.legOrderDict and self.legOrderDict[vtSymbol]: self.cancelLegOrder(vtSymbol) cancelPassive = True # 只有确实发出撤单委托时,才输出信息 if cancelPassive: self.writeLog(u'被动腿全撤')