CTP程序化交易入门系列之三:获取实时行情及k线合成

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/yishuihan1212/article/details/100186182

前面两篇有了基础知识的准备,这一篇讲通过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获取行情常见问题及解答

关注公众号,一起学习程序化交易!

 

 

猜你喜欢

转载自blog.csdn.net/yishuihan1212/article/details/100186182