量化之行,始于数据
“機會和風險相關的全部信息都蘊藏于數據之中”是一個量化交易員的基礎信仰。
所以,一直以來我都有一個執念,就是想要收集全市場的實時交易數據。
國內有三家股票交易所、四家期貨交易,共所涉及數萬個標的,包括股票、債券、基金、指數、期貨、期權等。
交易時段,全市場實時交易數據流就像一座開閘泄洪的水壩,想以優雅的姿態站在下游轉存這些數據,并沒有想象中那么簡單。
經過數次迭代更新,終于實現了一個自己還算滿意的方案。未來希望能從這些數據中發現更多有趣的事情。
標的
首先,就是要明確當前市場有哪些在交易的標的。
期貨合約有固定的開始和結束時間,期權的合約還會根據行情變化隨時加掛,股票雖然相對穩定,但也有新股上市的情況。
好在,TraderApi提供了查詢全部標的列表的功能。雖然,我覺得這個查詢應該放在MdApi中,但是官方這樣設計,我們也就只能這樣用。
int ReqQryInstrument(CThostFtdcQryInstrumentField *pQryInstrument, int nRequestID) = 0;要用TraderApi,就要完成創建實例、連接柜臺、看穿式認證、登錄等一系列操作。
查詢到全部標的之后,根據交易所和標的類別分類存儲,方便后續根據交易所或者類別查詢到相關標的。
證券交易所有,上交所、深交所和北交所。期貨交易所有,中金所、上期所、上能所、大商所、鄭商所。
標的類別就比較多了,包括期貨、期權、股票、基金、債券、指數等。
實時行情
接下來,需要使用MdApi訂閱全部標的的實時行情。
MdApi收到推送的實時行情時會調用OnRtnDepthMarketData方法,所以,我們就需要把邏輯寫到這個方法中。
由于訂閱的標的比較多,要避免在這里進行效率比較低的操作,例如io。
我最初使用的方案是,把數據拷貝之后存放到list中:
void OnRtnDepthMarketData(CThostFtdcDepthMarketDataField *pDepthMarketDataField) {CThostFtdcDepthMarketDataField *pCopy = new CThostFtdcDepthMarketDataField;memcpy(pCopy, pDepthMarketDataField, sizeof(CThostFtdcDepthMarketDataField));std::lock_guard<std::mutex> lock(m_mutex);m_lDataList.push_back(pCopy) }在另一個線程中從list將數據取出來寫進文件中或者存入數據庫中:
void process() {CThostFtdcDepthMarketDataField *pData = NULL;{std::lock_guard<std::mutex> lock(m_mutex);if (m_lDataList.size() > 0){pData = m_lDataList.front();}}if (NULL != pData){saveToFile(pData);delete pData;std::lock_guard<std::mutex> lock(m_mutex);m_lDataList.pop_front();} }還有一種更高效的方案就是使用mmap將文件映射進內存,直接將收到的行情拷貝到映射出來的內存地址:
void OnRtnDepthMarketData(CThostFtdcDepthMarketDataField *pDepthMarketDataField) {m_pMMAPWriter->write(pDepthMarketDataField); }但是,需要維護寫入地址的指針,以及可能存在擴展文件等問題,所以,也相對比較復雜。
數據處理
當然,這一步并不需要實時來做。只要有tick數據源,隨時可以處理需要的數據。
為了省去盤后的麻煩,我選擇了實時合成1分鐘K線數據、日線數據、期貨加權指數,以及一些我感興趣的組合價格數據。
K線的邏輯比較簡單:
if (bIsMinuteStart) {Open = pData->LastPrice;High = pData->LastPrice;Low = pData->LastPrice;Close = pData->LastPrice; } else {Close = pData->LastPrice;if (Close > High){High = pData->LastPrice;}if (Close < Low){Low = pData->LastPrice;} }當然,還可以根據需要計算其他數據,例如,成交量、均價等。
期貨加權指數價格,是把某一個品種所有月份合約的價格以持倉量為權重計算出來的均價。
組合價格則是按照一個公式計算出來的價格,例如:a*IC2210 + b*IC2210,通常a為1,b為-1。
線程管理
最后,實踐中發現,為了提交效率,需要多個MdApi實例分別完成訂閱任務。另外,程序可能會部署在多臺服務器上同時運行。
這就要求能夠通過配置文件分配訂閱任務。
這也是前面提到的要根據交易所和標的類別分類的主要目的。
//上期所期貨 #define ENUM_DataFlag_SHFEFuture 'a' //上期所期權 #define ENUM_DataFlag_SHFEOption 'b' //上能所期貨 #define ENUM_DataFlag_INEFuture 'c' //上能所期權 #define ENUM_DataFlag_INEOption 'd' ……如果配置DataFlag為a,c,就表示這個實例負責訂閱上期所和上能所的期貨行情。
因為對數據處理部分的實時性沒有太高的要求,所以我只使用了一個線程按照順序讀取所有行情實例寫到本地的數據,合成k線、期貨指數、組合價格,然后寫入數據庫,最后記錄日志。
系統啟動之后就用N個MdApi線程+1個數據處理線程。
總結
- 上一篇: 有些视频不显示IDM的下载按钮
- 下一篇: 清风:数学建模算法、编程和写作培训