VNPY 交易所返回委託和交易狀態到策略的原始碼分析

張國平發表於2018-07-28
  1. 主要分析兩個在類策略模型ctaTemplate的中的函式,onTrade和onOrder,其實兩個很相似,被別的其他例項呼叫,推入更新的Trade和Order例項,並執行函式內的程式碼。對於Tick級別的交易,還是還是會經常用到這兩個。下面是在ctaTemplate中的定義。

    def onOrder(self, order):
        """收到委託變化推送(必須由使用者繼承實現)"""
        # 對於無需做細粒度委託控制的策略,可以忽略onOrder
        pass
 
    # ----------------------------------------------------------------------
    def onTrade(self, trade):
        """收到成交推送(必須由使用者繼承實現)"""
        # 對於無需做細粒度委託控制的策略,可以忽略onOrder
        pass
  1. 2.  先去看看 order trade 是什麼樣的類,兩個都在 vtObject.py 裡面。理論上來說,在 tick 級別中高頻策略,當 order trade 發生變化後,使用 onOrder/onTrade 傳遞更新給策略;函式 onOrder/onTrade 裡面一般定義一些對應不同狀態進行的對應操作。

  2. 1) VtTradeData包含是成交的資料,其中最關鍵就是vtOrderID,可以和之前傳送交易返回的vtOrderID做對應,用來對應的交易訂單。其他諸如direction/offset/price/volume都是很重要;可以用來更新postion資料。

  3. 2) 類VtOrderData和之前VtQrderReq很像,但是不一樣,這個是記錄委託資訊狀態,req是交易請求,其中最關鍵的就是status,訂單狀態;這裡有四個狀態(ALLTRADED全部成交,PARTTRADED部分成交, NOTTRADED未成交,和CANCLLED拒單),這些屬性在ctpGateway.py定義的。

  4. class VtTradeData(VtBaseData):
        """成交資料類"""
     
        #----------------------------------------------------------------------
        def __init__(self):
            """Constructor"""
            super(VtTradeData, self).__init__()
                    # 程式碼編號相關
            self.symbol = EMPTY_STRING              # 合約程式碼
            self.exchange = EMPTY_STRING            # 交易所程式碼
            self.vtSymbol = EMPTY_STRING            # 合約在vt系統中的唯一程式碼,通常是 合約程式碼.交易所程式碼
            self.tradeID = EMPTY_STRING             # 成交編號
            self.vtTradeID = EMPTY_STRING           # 成交在vt系統中的唯一編號,通常是 Gateway名.成交編號
           
            self.orderID = EMPTY_STRING             # 訂單編號
            self.vtOrderID = EMPTY_STRING           # 訂單在vt系統中的唯一編號,通常是 Gateway名.訂單編號
               # 成交相關
            self.direction = EMPTY_UNICODE          # 成交方向
            self.offset = EMPTY_UNICODE             # 成交開平倉
            self.price = EMPTY_FLOAT                # 成交價格
            self.volume = EMPTY_INT                 # 成交數量
            self.tradeTime = EMPTY_STRING           # 成交時間
      
     
    ########################################################################
    class VtOrderData(VtBaseData):
        """訂單資料類"""
     
        #----------------------------------------------------------------------
        def __init__(self):
            """Constructor"""
            super(VtOrderData, self).__init__()
           
            # 程式碼編號相關
            self.symbol = EMPTY_STRING              # 合約程式碼
            self.exchange = EMPTY_STRING            # 交易所程式碼
            self.vtSymbol = EMPTY_STRING            # 合約在vt系統中的唯一程式碼,通常是 合約程式碼.交易所程式碼
            self.orderID = EMPTY_STRING             # 訂單編號
            self.vtOrderID = EMPTY_STRING           # 訂單在vt系統中的唯一編號,通常是 Gateway名.訂單編號
           
            # 報單相關
            self.direction = EMPTY_UNICODE          # 報單方向
            self.offset = EMPTY_UNICODE             # 報單開平倉
            self.price = EMPTY_FLOAT                # 報單價格
            self.totalVolume = EMPTY_INT            # 報單總數量
            self.tradedVolume = EMPTY_INT           # 報單成交數量
            self.status = EMPTY_UNICODE             # 報單狀態   
            self.orderTime = EMPTY_STRING           # 發單時間
            self.cancelTime = EMPTY_STRING          # 撤單時間
           
            # CTP/LTS相關
            self.frontID = EMPTY_INT                # 前置機編號
            self.sessionID = EMPTY_INT              # 連線編號
    

     3. 之前提到數次透過 onOrder/onTrade 傳遞最新 Order/Trade 狀態,這個負責處理的是一個系列過程,上層推手就是類 ctaEngine ,下面主要說下函式 processOrderEvent ,處理委託推送。其中傳入的 event 是一個事件物件,由一個 type_ 說明型別,和一個字典 dict_ 儲存具體的事件資料組成。可以理解為是上面 vtObject 的一個包裝盒, event Engine只要根據標籤 type_ ,就可以把具體資料傳給對應的下層處理者。這個關於 event 具體的後面再分析.

       這個函式,首先讀取了event字典中包好的order,因為存在手動發起交易情況 , 如果這個vtOrder是之前透過策略發出的,則呼叫callStrategyFunc來把這個order回傳到對應strategy . onOrder方法,如果是手動發出指令就算了。同時也分析狀態,如果在委託完成狀態,也更新strategyOrderDict字典,移除這個

def processOrderEvent(self, event):
        """處理委託推送"""
        order = event.dict_['data']
        vtOrderID = order.vtOrderID
       
        if vtOrderID in self.orderStrategyDict:
            strategy = self.orderStrategyDict[vtOrderID]           
           
            # 如果委託已經完成(拒單、撤銷、全成),則從活動委託集合中移除
            if order.status in self.STATUS_FINISHED:
                s = self.strategyOrderDict[strategy.name]
                if vtOrderID in s:
                    s.remove(vtOrderID)
           
            self.callStrategyFunc(strategy, strategy.onOrder, order)

4.  在往上追溯就到eventEngine,首先在 ctaEngine 初始化時候,會分配eventEngine例項,再透過下面程式碼註冊處理事件,當某類事件收到時候,呼叫對應的方法,比如事件型別EVENT_ORDER, 對應的方法是self.processOrderEvent。

   class ctaEngine
    def registerEvent(self):
        """註冊事件監聽"""
        self.eventEngine.register(EVENT_TICK, self.processTickEvent)
        self.eventEngine.register(EVENT_ORDER, self.processOrderEvent)
        self.eventEngine.register(EVENT_TRADE, self.processTradeEvent)
 
class eventEngine
    def register(self, type_, handler):
        """註冊事件處理函式監聽"""
        # 嘗試獲取該事件型別對應的處理函式列表,若無defaultDict會自動建立新的list
        handlerList = self.__handlers[type_]
       
        # 若要註冊的處理器不在該事件的處理器列表中,則註冊該事件
        if handler not in handlerList:
            handlerList.append(handler)

eventEngine 中的 register函式就是處理的方法透過 __handlers字典來對應,__handlers是defaultdict(list),是一種特殊的字典,最大特點就是如果同一個 key 值插入不同 value ,他不會像就普通 dict 用新的替代,而且變成 {key:[value1,value2 ……]} 這樣儲存。這樣就可以讓同一個 type ,可以有對應多個接收 handler

這裡有兩個event Engine, 按照官方說法,

  • EventEngine類使用了PyQt中的QTimer來實現定時器功能,由PyQt應用主執行緒中的Qt事件迴圈來定時觸發(無需新開單獨的執行緒),適合在帶有圖形介面的應用程式中使用(如examples/VnTrader);

  • EventEngine2類則是使用了一個單獨的執行緒來實現定時器功能,適合在無圖形介面的應用程式中使用(如examples/CtaTrading)。

來自 < >


5.  上面說了 eventEngine的組成 Event ,然後還有一個後面處理函式def __process(self, event)。 在一個內部佇列__queue中不停抓起 event ,透過檢索字典 __handlers來分配到對應的函式處理。那麼誰放入新的event呢,就是一個呼叫put(event)函式向事件佇列插入事件。這個時候發現一個特殊的 EVENT_TIMER ,看了半天,感覺可以理解為是一個節奏控制器,每一秒去做一次 process ;那麼對於高頻來說,可能換成 500 毫秒更合適。

下面是VNPY定義的EVENT事件。

# 系統相關
EVENT_TIMER = 'eTimer'                  # 計時器事件,每隔1秒傳送一次
EVENT_LOG = 'eLog'                      # 日誌事件,全域性通用
 
# Gateway相關
EVENT_TICK = 'eTick.'                   # TICK行情事件,可後接具體的vtSymbol
EVENT_TRADE = 'eTrade.'                 # 成交回報事件
EVENT_ORDER = 'eOrder.'                 # 報單回報事件
EVENT_POSITION = 'ePosition.'           # 持倉回報事件
EVENT_ACCOUNT = 'eAccount.'             # 賬戶回報事件
EVENT_CONTRACT = 'eContract.'           # 合約基礎資訊回報事件
EVENT_ERROR = 'eError.'                 # 錯誤回報事件

6.  現在想著是誰在不停的給這個內部佇列放入 order/trick 狀態的 event , 而在 ctp G ate 這個類中,在其父類 vtGate 中有 on Or de r 方法,很規範的打包 order evet ,然後放到佇列裡面。還有分析後發現在 Mainengine 對整個 eventEngine 進行管理,並透過 addGateway 透過中把在事件引擎和交易介面管理。

    def onOrder(self, order):
        """訂單變化推送"""
        # 通用事件
        event1 = Event(type_=EVENT_ORDER)
        event1.dict_['data'] = order
        self.eventEngine.put(event1)
       
        # 特定訂單編號的事件
        event2 = Event(type_=EVENT_ORDER+order.vtOrderID)
        event2.dict_['data'] = order
        self.eventEngine.put(event2)

7. 在至上是 class CtpTdApi(TdApi) 這個類的,讀取 data 中的 order 相關資料,建立 order ,推送到上面的這個 onOrder 裡面 ; 在往上就有點頭大了,這個 data 資訊應該是從編譯底層返回的。

 def onRtnOrder(self, data):
        """報單回報"""
        # 更新最大報單編號
        newref = data['OrderRef']
        self.orderRef = max(self.orderRef, int(newref))
       
        # 建立報單資料物件
        order = VtOrderData()
        order.gatewayName = self.gatewayName
       
        # 儲存程式碼和報單號
        order.symbol = data['InstrumentID']
        order.exchange = exchangeMapReverse[data['ExchangeID']]
        order.vtSymbol = order.symbol #'.'.join([order.symbol, order.exchange])
       
        order.orderID = data['OrderRef']
        # CTP的報單號一致性維護需要基於frontID, sessionID, orderID三個欄位
        # 但在本介面設計中,已經考慮了CTP的OrderRef的自增性,避免重複
        # 唯一可能出現OrderRef重複的情況是多處登入並在非常接近的時間內(幾乎同時發單)
        # 考慮到VtTrader的應用場景,認為以上情況不會構成問題
        order.vtOrderID = '.'.join([self.gatewayName, order.orderID])       
       
        order.direction = directionMapReverse.get(data['Direction'], DIRECTION_UNKNOWN)
        order.offset = offsetMapReverse.get(data['CombOffsetFlag'], OFFSET_UNKNOWN)
        order.status = statusMapReverse.get(data['OrderStatus'], STATUS_UNKNOWN)           
           
        # 價格、報單量等數值
        order.price = data['LimitPrice']
        order.totalVolume = data['VolumeTotalOriginal']
        order.tradedVolume = data['VolumeTraded']
        order.orderTime = data['InsertTime']
        order.cancelTime = data['CancelTime']
        order.frontID = data['FrontID']
        order.sessionID = data['SessionID']
       
        # 推送
        self.gateway.onOrder(order)

總體來看,eventEngine這個是一個總的驅動,在內部queue這個傳送帶,分發做了字典裡面型別標記的Event例項給對應的處理物件;ctpGateway這個透過put把新的event放入queue中。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/22259926/viewspace-2168561/,如需轉載,請註明出處,否則將追究法律責任。

相關文章