VNPY,從傳送交易指令到交易所的原始碼分析
在嘗試寫tick級別的策略,由於交易反饋時間要求高,感覺需要對單個order的事有個全面瞭解,就花了些時間嘗試性去分析了VNPY中 從傳送交易指令(sendOrder())到交易所,和接收成交返回資訊(onOrder()/onTrade())的程式碼。如果有錯誤或者遺漏,請指正。這裡先將傳送
-
在策略中,一般不直接呼叫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; 因為存在平昨,平今還有鎖倉,反手等拆分情況,返回的可能是一組。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/22259926/viewspace-2158790/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- VNPY 交易所返回委託和交易狀態到策略的原始碼分析原始碼
- Android系統原始碼分析-Broadcast傳送Android原始碼AST
- 數字交易所原始碼大全原始碼
- NFT交易所繫統執行規則分析 | NFT交易所繫統開發原始碼示例原始碼
- 數字貨幣交易所APP開發原始碼案例分析APP原始碼
- 傳送kafka訊息的shell指令碼Kafka指令碼
- (demo)交易所App開發功能|交易所繫統開發原始碼APP原始碼
- HashMap從認識到原始碼分析HashMap原始碼
- 交易所開發(穩定版)/交易所繫統開發(python開發)/交易所開發(原始碼版)Python原始碼
- 交易所開發(海外版)丨交易所繫統開發(Python)丨 交易所繫統原始碼功能Python原始碼
- shell指令碼:批次傳送curl請求指令碼
- HashMap:從原始碼分析到面試題HashMap原始碼面試題
- 交易所開發(海外版)/交易所繫統開發(案例詳細)/交易所繫統原始碼及demo原始碼
- swap交易所繫統(原始碼)丨swap交易所繫統開發(去中心化交易所開發詳細)原始碼中心化
- 交易所開發(海外版)丨交易所繫統開發(多語言)丨交易所成熟原始碼版原始碼
- 交易所繫統丨交易所繫統開發(上線版)丨交易所開發詳細原始碼部署原始碼
- 虛擬幣交易所繫統開發流程及原始碼分析介紹原始碼
- Spring原始碼分析之AOP從解析到呼叫Spring原始碼
- Element 指令clickoutside原始碼分析IDE原始碼
- 交易所開發(海外版)丨交易所繫統開發(Demo)交易所專案系統開發(原始碼定製)原始碼
- 深度解析:交易所繫統開發(海外版)交易所繫統開發(多語言)交易所開發(原始碼部署)原始碼
- swap去中心化交易所繫統開發(正式版)丨swap交易所原始碼部署中心化原始碼
- BSEX交易所繫統開發(開發方案)丨BSEX交易所開發(原始碼詳情)原始碼
- Uniswap交易所繫統開發(穩定版)丨Uniswap交易所開發成品及原始碼原始碼
- 以太坊交易池原始碼分析原始碼
- 30332資料傳送指令
- 去中心化交易所開發正式版丨去中心化交易所繫統開發技術詳細及原始碼分析中心化原始碼
- 從 Masscan, Zmap 原始碼分析到開發實踐原始碼
- 以太坊原始碼分析(26)core-txpool交易池原始碼分析原始碼
- 永續合約交易所繫統開發搭建邏輯分析 | (原始碼demo示例)原始碼
- [原始碼分析] 分散式任務佇列 Celery 之 傳送Task & AMQP原始碼分散式佇列MQ
- Gradle指令碼:上傳Apk到蒲公英Gradle指令碼APK
- Swap去中心化交易所/系統開發技術/Swap智慧合約交易所原始碼搭建中心化原始碼
- swap交易所繫統開發(成熟技術)交易所中心繫統開發流程(原始碼搭建)原始碼
- 區塊鏈交易所開發方案整理(原始碼示例)區塊鏈原始碼
- Laravel 重置密碼傳送郵件分析Laravel密碼
- 以太坊原始碼分析(12)交易資料分析原始碼
- 從原始碼分析Axios原始碼iOS