天天看點

vnpy 查詢持倉量_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; 因為存在平昨,平今還有鎖倉,反手等拆分情況,傳回的可能是一組。