01.31 vn.trader的tick-to-trade延時測試

tick-to-trade延時

對於量化交易平臺而言,最重要的技術指標之一就是所謂的tick-to-trade延時,即從底層API收到一個tick行情推送的tick_time,到平臺內部處理完畢再調用底層API發出委託的trade_time,中間所耗費的時間。

通常出於純粹測試平臺性能的目的,會採用收到行情後不進行任何策略邏輯計算,立即發出委託的方式來實現。考慮目前vn.trader的用戶群中最常見的情景,本文中使用Windows系統和CTP接口來進行測試。

為了實現這種精度比較高的延時測試,需要在底層的C++ API相關的封裝中加入計時相關的代碼,這裡選擇用Windows系統提供的QueryPerformanceCounter和QueryPerformanceFrequency函數,具體可以參考這篇文章。

在MdApi中測量tick_time

當行情接口MdApi的行情推送回調函數被觸發時,需要立即記錄下當前的時間戳。

<code>void MdApi::OnRtnDepthMarketData(CThostFtdcDepthMarketDataField *pDepthMarketData)
{
Task task = Task();

LARGE_INTEGER t;
QueryPerformanceCounter(&t);
task.task_time = t.QuadPart;
//task.task_time = std::clock();

task.task_name = ONRTNDEPTHMARKETDATA;

if (pDepthMarketData)
{

task.task_data = *pDepthMarketData;
}
else
{
CThostFtdcDepthMarketDataField empty_data = CThostFtdcDepthMarketDataField();
memset(&empty_data, 0, sizeof(empty_data));
task.task_data = empty_data;
}
this->task_queue.push(task);
};
/<code>

然後在處理C++行情結構體並生成Python字典時,把時間戳和計時頻率作為額外的數據放入字典中。

<code>void MdApi::processRtnDepthMarketData(Task task)
{
PyLock lock;
CThostFtdcDepthMarketDataField task_data = any_cast<cthostftdcdepthmarketdatafield>(task.task_data);
dict data;
data["HighestPrice"] = task_data.HighestPrice;
data["BidPrice5"] = task_data.BidPrice5;
data["BidPrice4"] = task_data.BidPrice4;
data["BidPrice1"] = task_data.BidPrice1;
data["BidPrice3"] = task_data.BidPrice3;
data["BidPrice2"] = task_data.BidPrice2;
data["LowerLimitPrice"] = task_data.LowerLimitPrice;
data["OpenPrice"] = task_data.OpenPrice;
data["AskPrice5"] = task_data.AskPrice5;
data["AskPrice4"] = task_data.AskPrice4;
data["AskPrice3"] = task_data.AskPrice3;
data["PreClosePrice"] = task_data.PreClosePrice;
data["AskPrice1"] = task_data.AskPrice1;
data["PreSettlementPrice"] = task_data.PreSettlementPrice;
data["AskVolume1"] = task_data.AskVolume1;
data["UpdateTime"] = task_data.UpdateTime;
data["UpdateMillisec"] = task_data.UpdateMillisec;
data["AveragePrice"] = task_data.AveragePrice;
data["BidVolume5"] = task_data.BidVolume5;
data["BidVolume4"] = task_data.BidVolume4;
data["BidVolume3"] = task_data.BidVolume3;
data["BidVolume2"] = task_data.BidVolume2;
data["PreOpenInterest"] = task_data.PreOpenInterest;
data["AskPrice2"] = task_data.AskPrice2;
data["Volume"] = task_data.Volume;
data["AskVolume3"] = task_data.AskVolume3;
data["AskVolume2"] = task_data.AskVolume2;
data["AskVolume5"] = task_data.AskVolume5;

data["AskVolume4"] = task_data.AskVolume4;
data["UpperLimitPrice"] = task_data.UpperLimitPrice;
data["BidVolume1"] = task_data.BidVolume1;
data["InstrumentID"] = task_data.InstrumentID;
data["ClosePrice"] = task_data.ClosePrice;
data["ExchangeID"] = task_data.ExchangeID;
data["TradingDay"] = task_data.TradingDay;
data["PreDelta"] = task_data.PreDelta;
data["OpenInterest"] = task_data.OpenInterest;
data["CurrDelta"] = task_data.CurrDelta;
data["Turnover"] = task_data.Turnover;
data["LastPrice"] = task_data.LastPrice;
data["SettlementPrice"] = task_data.SettlementPrice;
data["ExchangeInstID"] = task_data.ExchangeInstID;
data["LowestPrice"] = task_data.LowestPrice;
data["ActionDay"] = task_data.ActionDay;

//保存測試時間
data["tick_time"] = task.task_time;
data["frequency_time"] = this->t.QuadPart;
this->onRtnDepthMarketData(data);
};
/<cthostftdcdepthmarketdatafield>/<code>

在TdApi中測量trade_time

行情進入Python環境中處理完畢,會調用底層API發送委託,委託發送成功後需要立即記錄當時的時間戳trade_time,並返回到Python環境中從而實現測量tick_time和trade_time之間的延時。

<code>LONGLONG TdApi::reqOrderInsert(dict req, int nRequestID)
{
CThostFtdcInputOrderField myreq = CThostFtdcInputOrderField();
memset(&myreq, 0, sizeof(myreq));
getChar(req, "ContingentCondition", &myreq.ContingentCondition);
getStr(req, "CombOffsetFlag", myreq.CombOffsetFlag);
getStr(req, "UserID", myreq.UserID);
getDouble(req, "LimitPrice", &myreq.LimitPrice);
getInt(req, "UserForceClose", &myreq.UserForceClose);
getChar(req, "Direction", &myreq.Direction);
getInt(req, "IsSwapOrder", &myreq.IsSwapOrder);
getInt(req, "VolumeTotalOriginal", &myreq.VolumeTotalOriginal);
getChar(req, "OrderPriceType", &myreq.OrderPriceType);

getChar(req, "TimeCondition", &myreq.TimeCondition);
getInt(req, "IsAutoSuspend", &myreq.IsAutoSuspend);
getDouble(req, "StopPrice", &myreq.StopPrice);
getStr(req, "InstrumentID", myreq.InstrumentID);
getStr(req, "ExchangeID", myreq.ExchangeID);
getInt(req, "MinVolume", &myreq.MinVolume);
getChar(req, "ForceCloseReason", &myreq.ForceCloseReason);
getStr(req, "BrokerID", myreq.BrokerID);
getStr(req, "CombHedgeFlag", myreq.CombHedgeFlag);
getStr(req, "GTDDate", myreq.GTDDate);
getStr(req, "BusinessUnit", myreq.BusinessUnit);
getStr(req, "OrderRef", myreq.OrderRef);
getStr(req, "InvestorID", myreq.InvestorID);
getChar(req, "VolumeCondition", &myreq.VolumeCondition);
getInt(req, "RequestID", &myreq.RequestID);

int i = this->api->ReqOrderInsert(&myreq, nRequestID);

//延時測試相關
LARGE_INTEGER order_time;
QueryPerformanceCounter(&order_time);
return order_time.QuadPart;
};
/<code>

感謝Boost.Python的強大封裝功能,可以自動實現long long(LONGLONG)類型到Python整數類型的轉換,用戶無需另外處理。

在Python環境中計算tick-to-trade延時

在Python環境中,我們已經通過行情推送的tick字典獲取了tick_time,又通過調用發送委託請求後的返回數值獲取了trade_time,此時在Python環境中計算兩者之間的差值即我們需要的tick_to_trade延時。

<code>#----------------------------------------------------------------------
def orderTest(self, event):
""""""
tick = event.dict_['data']

req = VtOrderReq()

req.symbol = tick.symbol
req.price = tick.lastPrice
req.direction = DIRECTION_LONG
req.offset = OFFSET_OPEN
req.priceType = PRICETYPE_LIMITPRICE
req.volume = 1

order_time = self.sendOrder(req, tick.gatewayName)

print u'耗時:', (order_time - tick.tick_time)*1000/tick.frequency_time, u'毫秒'
/<code>

在主引擎MainEngine中添加該事件處理函數,監聽所有的EVENT_TICK類型的事件,收到事件推送後立即發送一個開多1手的限價委託。需要注意的是通過QueryPerformanceCounter獲取的tick_time和trade_time是系統時間的計數,需要除以計時頻率才能轉換成具體的時間(秒),乘以1000後轉化為毫秒時間。

以上函數在主引擎初始化時註冊到了事件引擎中。

<code>########################################################################
class MainEngine(object):
"""主引擎"""

#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
# 創建事件引擎
self.eventEngine = EventEngine2()
self.eventEngine.start()

# 創建數據引擎
self.dataEngine = DataEngine(self.eventEngine)

# MongoDB數據庫相關
self.dbClient = None # MongoDB客戶端對象

# 調用一個個初始化函數

self.initGateway()

# 擴展模塊
self.ctaEngine = CtaEngine(self, self.eventEngine)
self.drEngine = DrEngine(self, self.eventEngine)
self.rmEngine = RmEngine(self, self.eventEngine)

self.eventEngine.register(EVENT_TICK, self.orderTest)
/<code>

注意需要手動在TradingWidget中訂閱一些合約來收到tick推送。

測試結果

作者測試機器配置:Core i7-6700K 4.0G/16G/Windows 7

測試結果:228個數據點,平均tick_to_trade延時22.6毫秒

當vn.trader底層的行情接口收到一個新的行情數據推送後,最快差不多隻需要百分之二秒的時間就可以完成事件引擎處理,並調用交易接口發出委託。這個速度對於追求微秒級交易延時的超高頻策略而言可能無法滿足,但對於大部分目標延時在毫秒級以上的常規高頻策略應該說是基本沒有問題。

同時考慮到測試時使用的是Windows系統,且帶GUI圖形界面的形式,其實還存在著相當大的提升空間,有興趣的朋友不妨自己試試看,歡迎把測試結果分享給我。


分享到:


相關文章: