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”]
#繼承自spi基類mdapi。CThostFtdcMdSpi
class CFtdcMdSpi(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”
#實現onfrontconnect函式,在裡面呼叫登入,第7步
self。tapi。ReqUserLogin(loginfield,0)
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線的要求也不一樣,這裡提幾點思考,就不一一列舉解答了。
根據最新價LastPrice更新得到的highPrice一定是真的最高價嗎?
上下午收盤分別是11:30和15:00,那收到11:30:00。500和15:30:00。500ms的行情如何處理?
非主力合約有的很長時間才來一個tick,如何處理?
建議大家可以一邊做一邊對應快期等終端對比,得到自己滿意的k線資料。一般有k線資料就可以直接計算指標得到訊號量便於交易,為了更直觀地看到K線,這裡也提供下PyQt + PyQtGraph實現的K線圖,原始碼一樣提供在github上。程式碼參考了github上uiKLine和vnpy兩個開源專案,大家可以看我github上fork的這兩個專案。感謝兩位作者!
往期推薦
● CTP程式化交易入門系列之一:準備
● CTP程式化交易入門系列之二:API基本架構及初始化
● 什麼是穿透式監管,需要投資者做什麼?
下節預告
● CTP API獲取行情常見問題及解答
關注我們 ,一起學習程式化交易吧!