十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
小编给大家分享一下VNPY中如何实现从发送交易指令到交易所的源代码,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!
10年专注成都网站制作,企业网站设计,个人网站制作服务,为大家分享网站制作知识、方案,网站设计流程、步骤,成功服务上千家企业。为您提供网站建设,网站制作,网页设计及定制高端网站建设服务,专注于企业网站设计,高端网页制作,对发电机维修等多个领域,拥有丰富的网站营销经验。
在策略中,一般不直接调用sendOrder(), 而且用四个二次封装函数函数,这些都是在class CtaTemplate中定义的,这里面主要区别就是sendorder()函数的中ordertype制定不一样,用来区分是买开卖开等交易类型。
返回一个vtOrderIDList, 这个list里面包含vtOrderID,这个是个内部给号,可以用做追踪同一个order的状态。
def buy(self, price, volume, stop=False): """买开""" return self.sendOrder(CTAORDER_BUY, price, volume, stop) #---------------------------------------------------------------------- def sell(self, price, volume, stop=False): """卖平""" return self.sendOrder(CTAORDER_SELL, price, volume, stop) #---------------------------------------------------------------------- def short(self, price, volume, stop=False): """卖开""" return self.sendOrder(CTAORDER_SHORT, price, volume, stop) #---------------------------------------------------------------------- def cover(self, price, volume, stop=False): """买平""" return self.sendOrder(CTAORDER_COVER, price, volume, stop)
2. 接下来我们看看那sendOrder()源码,还在class CtaTemplate中定义;如果stop为True是本地停止单,这个停止单并没有发送给交易所,而是存储在内部,使用ctaEngine.sendStopOrder()函数; 否则这直接发送到交易所,使用ctaEngine.sendStopOrder函数。
这里会返回一个vtOrderIDList, 这个list里面包含vtOrderID,然后在被上面返回。这里补充一下,对于StopOrder真正触发的交易通常是涨停价或者跌停价发出的市价单(Market price),参数price只是触发条件;而普通sendOrder是真正按照参数price的限价单(Limit price)
def sendOrder(self, orderType, price, volume, stop=False): """发送委托""" if self.trading: # 如果stop为True,则意味着发本地停止单 if stop: vtOrderIDList = self.ctaEngine.sendStopOrder(self.vtSymbol, orderType, price, volume, self) else: vtOrderIDList = self.ctaEngine.sendOrder(self.vtSymbol, orderType, price, volume, self) return vtOrderIDList else: # 交易停止时发单返回空字符串 return []
3. 这里我们首先看看ctaEngine.sendStopOrder()函数,在class CtaEngine中定义的,首先实例初始化时候定义了两个字典,用来存放stoporder,区别一个是停止单撤销后删除,一个不会删除;还定义了一个字典,策略对应的所有orderID。
def __init__(self, mainEngine, eventEngine): ……… # 本地停止单字典 # key为stopOrderID,value为stopOrder对象 self.stopOrderDict = {} # 停止单撤销后不会从本字典中删除 self.workingStopOrderDict = {} # 停止单撤销后会从本字典中删除 # 保存策略名称和委托号列表的字典 # key为name,value为保存orderID(限价+本地停止)的集合 self.strategyOrderDict = {} ………
然后在函数sendStopOrder中,首先记录给本地停止单一个专门编号,就是前缀加上顺序编号,其中STOPORDERPREFIX 是 'CtaStopOrder.',那么第一条本地编码就是'CtaStopOrder.1'。 后面是这个单据信息;这里可以发现orderType其实是一个direction和offset的组合,交易方向direction有Long、short两个情况,交易对offset有open和close两个情况。组合就是上面买开,卖平等等。然后把这个stoporder放入字典,等待符合价格情况到达触发真正的发单。这里返回本地编码作为vtOrderIDList。
def sendStopOrder(self, vtSymbol, orderType, price, volume, strategy): """发停止单(本地实现)""" self.stopOrderCount += 1 stopOrderID = STOPORDERPREFIX + str(self.stopOrderCount) so = StopOrder() so.vtSymbol = vtSymbol so.orderType = orderType so.price = price so.volume = volume so.strategy = strategy so.stopOrderID = stopOrderID so.status = STOPORDER_WAITING if orderType == CTAORDER_BUY: so.direction = DIRECTION_LONG so.offset = OFFSET_OPEN elif orderType == CTAORDER_SELL: so.direction = DIRECTION_SHORT so.offset = OFFSET_CLOSE elif orderType == CTAORDER_SHORT: so.direction = DIRECTION_SHORT so.offset = OFFSET_OPEN elif orderType == CTAORDER_COVER: so.direction = DIRECTION_LONG so.offset = OFFSET_CLOSE # 保存stopOrder对象到字典中 self.stopOrderDict[stopOrderID] = so self.workingStopOrderDict[stopOrderID] = so # 保存stopOrderID到策略委托号集合中 self.strategyOrderDict[strategy.name].add(stopOrderID) # 推送停止单状态 strategy.onStopOrder(so) return [stopOrderID]
4. 下面是processStopOrder()函数,也在class CtaEngine中定义的,主要是当行情符合时候如何发送真正交易指令,因为stopOrderID不是tick交易重点,这里简单讲讲,具体请看源码。
当接收到tick时候,会查看tick.vtSymbol,是不是存在workingStopOrderDict的so.vtSymbol有一样的,如果有,再看tick.lastPrice价格是否可以满足触发阈值,如果满足,根据原来so的交易Direction,Long按照涨停价,Short按照跌停价发出委托。然后从workingStopOrderDic和strategyOrderDict移除该so,并更新so状态,并触发事件onStopOrder(so).
这里发现,so只是只是按照涨停价发单给交易所,并没有确保成绩,而且市价委托的实际交易vtOrderID也没有返回;从tick交易角度,再收到tick后再发送交易,本事也是有了延迟一tick。所以一般tick级别交易不建议使用stoporder。
def processStopOrder(self, tick): """收到行情后处理本地停止单(检查是否要立即发出)""" vtSymbol = tick.vtSymbol # 首先检查是否有策略交易该合约 if vtSymbol in self.tickStrategyDict: # 遍历等待中的停止单,检查是否会被触发 for so in self.workingStopOrderDict.values(): if so.vtSymbol == vtSymbol: longTriggered = so.direction==DIRECTION_LONG and tick.lastPrice>=so.price # 多头停止单被触发 shortTriggered = so.direction==DIRECTION_SHORT and tick.lastPrice<=so.price # 空头停止单被触发 if longTriggered or shortTriggered: # 买入和卖出分别以涨停跌停价发单(模拟市价单) if so.direction==DIRECTION_LONG: price = tick.upperLimit else: price = tick.lowerLimit # 发出市价委托 self.sendOrder(so.vtSymbol, so.orderType, price, so.volume, so.strategy) # 从活动停止单字典中移除该停止单 del self.workingStopOrderDict[so.stopOrderID] # 从策略委托号集合中移除 s = self.strategyOrderDict[so.strategy.name] if so.stopOrderID in s: s.remove(so.stopOrderID) # 更新停止单状态,并通知策略 so.status = STOPORDER_TRIGGERED so.strategy.onStopOrder(so)
5. 前面说了这么多,终于到了正主sendOrder(),也在class CtaEngine中定义的。代码较长,下面做了写缩减。
1)通过mainEngine.getContract获得这个品种的合约的信息,包括这个合约的名称,接口名gateway(国内期货就是ctp),交易所,最小价格变动等信息;
2)创建一个class
VtOrderReq的对象req,在vtObject.py中,这个py包括很多事务类的定义;然后赋值,包括contract获得信息,交易手数,和price,和priceType,这里只有限价单。
3)根据orderType赋值direction和offset,之前sendStopOrder中已经说了,就不重复。
4)然后跳到mainEngine.convertOrderReq(req),这里代码比较跳,分析下来,如果之前没有持仓,或者是直接返回[req];如果有持仓就调用PositionDetail.convertOrderReq(req),这个时候如果是平仓操作,就分析持仓量,和平今和平昨等不同操作返回拆分的出来[reqTd,reqYd],这里不展开。
5)
如果上一部没有返回[req],则委托有问题,直接返回控制。如果有[req],因为存在多个req情况,就遍历每个req,使用mainEngine.sendOrder发单,并保存返回的vtOrderID到orderStrategyDict[],strategyOrderDict[]两个字典;然后把vtOrderIDList返回。
def sendOrder(self, vtSymbol, orderType, price, volume, strategy): """发单""" contract = self.mainEngine.getContract(vtSymbol) req = VtOrderReq() req.symbol = contract.symbol …… # 设计为CTA引擎发出的委托只允许使用限价单 req.priceType = PRICETYPE_LIMITPRICE # CTA委托类型映射 if orderType == CTAORDER_BUY: req.direction = DIRECTION_LONG req.offset = OFFSET_OPEN …… # 委托转换 reqList = self.mainEngine.convertOrderReq(req) vtOrderIDList = [] if not reqList: return vtOrderIDList for convertedReq in reqList: vtOrderID = self.mainEngine.sendOrder(convertedReq, contract.gatewayName) # 发单 self.orderStrategyDict[vtOrderID] = strategy # 保存vtOrderID和策略的映射关系 self.strategyOrderDict[strategy.name].add(vtOrderID) # 添加到策略委托号集合中 vtOrderIDList.append(vtOrderID) self.writeCtaLog(u'策略%s发送委托,%s,%s,%s@%s' %(strategy.name, vtSymbol, req.direction, volume, price)) return vtOrderIDList
6. 在mainEngine.sendOrder中,这里不列举代码了,首先进行风控,如果到阈值就不发单,然后看gateway是否存在,如果存在,就调用gateway.sendOrder(orderReq)方法;下面用ctpgateway说明。class CtpGateway(VtGateway)是VtGateway是继承,把主要发单,返回上面都实现,同时对于不同的接口,比如外汇,数字货币,只要用一套接口标准就可以,典型继承使用。
CtpGateway.sendOrder实际是调用class CtpTdApi(TdApi)的,这个就是一套ctp交易交口,代码很简单,最后是调用封装好C++的ctp接口reqOrderInsert()。最关键返回的vtOrderID是接口名+顺序数。
def sendOrder(self, orderReq): """发单""" self.reqID += 1 self.orderRef += 1 req = {} req['InstrumentID'] = orderReq.symbol req['LimitPrice'] = orderReq.price req['VolumeTotalOriginal'] = orderReq.volume # 下面如果由于传入的类型本接口不支持,则会返回空字符串 req['OrderPriceType'] = priceTypeMap.get(orderReq.priceType, '') ....... # 判断FAK和FOK if orderReq.priceType == PRICETYPE_FAK: req['OrderPriceType'] = defineDict["THOST_FTDC_OPT_LimitPrice"] req['TimeCondition'] = defineDict['THOST_FTDC_TC_IOC'] req['VolumeCondition'] = defineDict['THOST_FTDC_VC_AV'] if orderReq.priceType == PRICETYPE_FOK: req['OrderPriceType'] = defineDict["THOST_FTDC_OPT_LimitPrice"] req['TimeCondition'] = defineDict['THOST_FTDC_TC_IOC'] req['VolumeCondition'] = defineDict['THOST_FTDC_VC_CV'] self.reqOrderInsert(req, self.reqID) # 返回订单号(字符串),便于某些算法进行动态管理 vtOrderID = '.'.join([self.gatewayName, str(self.orderRef)]) return vtOrderID
整个流程下来,不考虑stoporder,是ctaTemplate -> CtaEngine ->mainEngine ->ctpgateway ->CtpTdApi, 传到C++封装的接口。返回的就是vtOrderID; 因为存在平昨,平今还有锁仓,反手等拆分情况,返回的可能是一组。
以上是“VNPY中如何实现从发送交易指令到交易所的源代码”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注创新互联行业资讯频道!