CTP程序化交易入門系列之三:獲取實時行情及k線合成

前面兩篇有了基礎知識的準備,這一篇講通過CTP API獲取實時行情,錄入csv,實時合成k線。github上開源了錄入csv及合成k線代碼,後臺回覆pyctp可獲取。先上兩張效果圖:

                                                                                       圖1 csv數據

                                                                                  圖2 1分鐘K線圖

一、CTP行情API介紹

CTP API分爲行情和交易兩類,兩者是完全獨立的,所以如果只對行情感興趣的話,只用如下行情API相關部分三個文件就可以。

thosttraderapi.py

_thostmduserapi.pyd

thostmduserapi_se.dll

API中的重點函數,請求:

//登錄函數,確認連上CTP後首先需要登錄
def ReqUserLogin(self, pReqUserLoginField: 'CThostFtdcReqUserLoginField', nRequestID: 'int') -> "int":
//訂閱函數,即通過這個函數來向CTP請求訂閱哪些合約的實時行情
//第一個參數類型爲list,寫成["au1912","IC1909"]的形式,第二個參數必須爲前面list的長度
def SubscribeMarketData(self, ppInstrumentID: 'char *[]', nCount: 'int') -> "int":

回報:

//行情回報函數,其中pDepthMarketData類內即爲每次實時行情的相關數據
def OnRtnDepthMarketData(self, pDepthMarketData: 'CThostFtdcDepthMarketDataField') -> "void":

那麼究竟可以獲取到合約的哪些行情數據呢?從類型CThostFtdcDepthMarketDataField 中的字段就可以看出來,在thosttraderapi.py文件中搜CThostFtdcDepthMarketDataField類型即可看出有哪些字段,主要有更新時間UpdateTime,最新成交價LastPrice ,買賣一檔的價格及數量BidPrice1,BidVolume1,AskPrice1,AskVolume1,累計成交量Volume等。

二、訂閱獲取行情的步驟

代碼非常簡單,50行內即可訂閱全市場行情。通過上一章的學習應該知道CTP的API是異步回調的機制,底層dll在客戶訂閱成功後會自動推送訂閱合約的實時行情。代碼邏輯時序圖如下:

                                                                               圖3 訂閱行情時序圖

對應的主函數如下:

def main():
    mduserapi=mdapi.CThostFtdcMdApi_CreateFtdcMdApi() #第1步
    mduserspi=CFtdcMdSpi(mduserapi)  #第2步
    '''以下是生產環境'''
    #mduserapi.RegisterFront("tcp://180.168.146.187:10101")  #第3步
    '''以下是7*24小時環境'''
    mduserapi.RegisterFront("tcp://180.168.146.187:10131")
    mduserapi.RegisterSpi(mduserspi) #第4步
    mduserapi.Init()    #第5步,API正式啓動,dll底層會自動去連上面註冊的地址
    mduserapi.Join()    #join的目的是爲了阻塞主線程,可以用sleep代替

回調實例類CFtdcMdSpi如下:

import thostmduserapi as mdapi
'''需要訂閱的合約list'''
subID=["au1912","IC1909","i2001","TA001"]

class CFtdcMdSpi(mdapi.CThostFtdcMdSpi):  #繼承自spi基類mdapi.CThostFtdcMdSpi

    def __init__(self,tapi):
        mdapi.CThostFtdcMdSpi.__init__(self)
        self.tapi=tapi
        
    def OnFrontConnected(self) -> "void":
        print ("OnFrontConnected")
        loginfield = mdapi.CThostFtdcReqUserLoginField()
        loginfield.BrokerID="8000"
        loginfield.UserID="000005"
        loginfield.Password="123456"
        loginfield.UserProductInfo="python dll"
        self.tapi.ReqUserLogin(loginfield,0) #實現onfrontconnect函數,在裏面調用登錄,第7步
   
    def OnRspUserLogin(self, pRspUserLogin: 'CThostFtdcRspUserLoginField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":
        print (f"OnRspUserLogin, SessionID={pRspUserLogin.SessionID},ErrorID={pRspInfo.ErrorID},ErrorMsg={pRspInfo.ErrorMsg}")
        #繼承實現登錄回調,登錄成功後去訂閱,第9步
        ret=self.tapi.SubscribeMarketData([id.encode('utf-8') for id in subID],len(subID))     
   
    def OnRtnDepthMarketData(self, pDepthMarketData: 'CThostFtdcDepthMarketDataField') -> "void":
        #繼承收取訂閱行情,第11步,在這裏將pDepthMarketData數據存入csv即可錄得數據
        print(f"InstrumentID={pDepthMarketData.InstrumentID},LastPrice={pDepthMarketData.LastPrice}") 

看總共就30+行代碼,就完成了訂閱收取行情的工作。如果將subID列表中填入入全市場合約,就能訂閱得到全市場的行情,是不是很簡單?

四、由CTP API得到K線數據

首先需要區分下tick數據和切片(快照)數據有什麼區別。

tick數據一般是指市場上的逐筆數據,例如一筆委託會產生一筆行情,一筆成交也會產生一筆行情。目前國內期貨交易所還不支持推送這種逐筆的數據,只推送切片(快照)數據。

切片數據是指將一定時間內的逐筆數據統計成一個快照發出,一般是1秒2筆。但鄭商所有點特殊,可能是1s多筆,就不展開來講了。

CTP發出的行情正是轉發的交易所的行情,所以也是500ms一次快照。一般業內也將這個快照數據稱之爲tick,雖然這不是真正的tick,但我們依照慣例,下面都稱之爲tick數據。

很多客戶做交易更關心K線數據,用K線數據計算信號。CTP不推送K線數據,所以需要客戶自己根據tick數據計算得出。

K線數據的基本要素有Time、Volume、Price、Open、High、Low、Close這6個值,可以根據這個週期內CTP的tick數據中的UpdateTime、 LastPrice、 Volume三個字段算出。我們以1分鐘K線爲例,邏輯如下:

#根據行情中的UpdateTime字段判斷是否爲新1分鐘
st= pDepthMarketData.UpdateTime.split(':')
if not self.bar:
    newMinitue = True
else:
    if int(st[1]) == self.bar.updateTime.minute :
        newMinitue = False
    else:
        newMinitue = True
#如果是新1分鐘,生成一個新k線變量,CBarData結構體中有OHLC,time等K線字段
if newMinitue :
   self.bar = CBarData()
   self.bar.instrumentID = pDepthMarketData.InstrumentID
   self.bar.exchangeID = pDepthMarketData.ExchangeID
   self.bar.updateTime = time(int(st[0]),int(st[1]),0,0)
   self.bar.volume = 0
   self.bar.openInterest = pDepthMarketData.OpenInterest
   self.bar.openPrice = pDepthMarketData.LastPrice
   self.bar.highPrice = pDepthMarketData.LastPrice
   self.bar.lowPrice = pDepthMarketData.LastPrice
   self.bar.closePrice = pDepthMarketData.LastPrice
else :
#如果不是新1分鐘,將最新價與HL價相比然後更新,更新C價 
   self.bar.highPrice = max(self.bar.highPrice, pDepthMarketData.LastPrice)
   self.bar.lowPrice = min(self.bar.lowPrice, pDepthMarketData.LastPrice)
   self.bar.closePrice = pDepthMarketData.LastPrice
   self.bar.openInterest = pDepthMarketData.OpenInterest
#注意Volume字段是累計成交量,所以這個時間段內成交量爲該值與上一時間段末成交量的差值
if not self.lastVolume:
   self.bar.volume += max(pDepthMarketData.Volume-self.lastVolume,0)
   self.lastVolume = pDepthMarketData.Volume
   #打印實時k線數據   
   print(f"{bar update[pDepthMarketData.UpdateTime],O[self.bar.openPrice],H[self.bar.highPrice],L[self.bar.lowPrice],C[self.bar.closePrice]}")   

有這一段代碼加入到上面的OnRtnDepthMarketData函數中,就能獲得1分鐘K線數據了。其餘的3、5、10、15、30分鐘這類的K線數據獲取方式原理也相似。

當然要得到令自己滿意的k線數據還是有很多坑要自己踩過才知道,每個人對K線的要求也不一樣,這裏提幾點思考,就不一一列舉解答了。

  1. 根據最新價LastPrice更新得到的highPrice一定是真的最高價嗎?
  2. 上下午收盤分別是11:30和15:00,那收到11:30:00.500和15:30:00.500ms的行情如何處理?
  3. 非主力合約有的很長時間纔來一個tick,如何處理?

建議大家可以一邊做一邊對應快期等終端對比,得到自己滿意的k線數據。

一般有k線數據就可以直接計算指標得到信號量便於交易,爲了更直觀地看到K線,這裏也提供下PyQt + PyQtGraph實現的K線圖,源碼一樣提供在github上。

代碼參考了github上uiKLine和vnpy兩個開源項目,大家可以看我github上fork的這兩個項目。感謝兩位作者!

 

下節預告:

CTP API獲取行情常見問題及解答

關注公衆號,一起學習程序化交易!

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章