CTP行情API
一、期货行情数据
Tick数据一般指市场上的逐笔数据,例如一笔委托会产生一笔行情,一笔成交也会产生一笔行情。目前国内期货交易所还不支持推送逐笔数据,只推送切片(快照)数据。
切片数据是指将一定时间内的逐笔数据统计成一个快照发出,一般是1秒2笔。CTP行情转发的交易所行情,500ms一次快照。
二、CTP行情
1、CTP行情API简介
CThostFtdcMdApi行情API接口包含CThostFtdcMdApi和CThostFtdcMdSpi,通过CThostFtdcMdApiApi向CTP发送命令,通过CThostFtdcMdSpi接收CTP响应。
(1)创建CTP API实例
CThostFtdcMdApi *pMarketDataApi = CThostFtdcMdApi::CreateFtdcMdApi(dirName);
调用CreateFtdcMdApi()创建API实例,pMarketDataApi,通过API实例发起各种请求,如连接服务器、用户登录、订阅合约、退订合约等。
(2)创建CTP API回调实例
MarketDataSource *pDataSource = new MarketDataSource(pMarketDataApi, this);
开发者需要继承上期技术提供的CThostFtdcMdSpi类,重写类方法,以处理服务器发过来的各类数据。
(3)注册CTP API回调实例
pMarketDataApi->RegisterSpi(pDataSource);
(4)连接行情前置服务器
pMarketDataApi_->RegisterFront((char *)serverAddr_.c_str()); pMarketDataApi_->Init();
连接请求发出后,OnFrontConnected()会响应请求,通常在OnFrontConnected()函数内进行用户登录。
(5)用户登录
在OnFrontConnected()函数内进行用户登录操作。
CThostFtdcReqUserLoginField loginReq; std::string BrokerID, InvestorID, Password; memset(&loginReq, 0, sizeof(loginReq)); strcpy(loginReq.BrokerID, BrokerID.c_str()); strcpy(loginReq.UserID, InvestorID.c_str()); strcpy(loginReq.Password, Password.c_str()); static int requestID = 0; int rt = pMarketDataApi->ReqUserLogin(&loginReq, requestID++);
登录请求后OnRspUserLogin()返回响应,如果登录成功,可以订阅合约。
(6)订阅期货合约
在OnRspUserLogin()函数内订阅期货合约。
const size_t count = contracts.size(); char *instruments[count]; pMarketDataApi_->SubscribeMarketData(instruments, (int)count);
(7)合约订阅响应
在OnRspSubMarketData函数内返回合约订阅结果,如果客户端订阅行情的请求是不合法的,返回服务器端给出的错误信息(pRspInfo);即使客户端发送的订阅请求是合法的,函数也会被回调,而返回的信息则是CTP:No Error。
(8)接收行情
void CTPMarketDataSource::OnRtnDepthMarketData(CThostFtdcDepthMarketDataField * pDepthMarketData);
合约订阅成功后,在交易时间段内,所订阅合约行情数据会源源不断的推送过来。在非交易时间段,经常会接收脏数据,脏数据通常是浮点数最大值。
行情数据落地时,如果直接在OnRtnDepthMarketData中将行情数据写入到CSV中,一旦IO写磁盘操作变多便会和API底层接收行情数据相互干扰,轻则写入文件中的数据落后于实时数据,重则直接导致订阅行情进程宕机。
解决方案有两种,一种是将SPI线程收数据和行情数据落地写入CSV两个操作隔离开来,SPI线程将收到行情数据写入队列,另起一个线程从队列里读取数据写入CSV;另一种是线收取完成切片的所有合约行情数据,然后再落地行情数据,由于切片行情数据收取完成后到下一个切片推送时存在数百ms的时间间隔,可以用于数据落地。
行情数据中有些字段是无效值,CTP统一用double的上限值1.7976931348623157e+308表示,如期货合约的PreDelta、CurrDelta、ClosePrice、SettlementPrice。
2、CTP行情推送规则
CTP行情推送规则为:
(1)1秒2次快照行情。
(2)有更新才推送,没有更新的合约推送。
(3)第一次连接后推送初始行情。
CTP推送行情的基本原则是每秒推送两次,但推送时间(毫秒级)并不是严格的000、500,即有可能推送时间是300、800。
通过CTP API连上CTP系统后,CTP会惯例的推送一笔最近的行情数据,用于提醒客户端当前市场是否有行情。
开盘前的集合竞价撮合阶段,交易所也会推送行情。开盘时刻的行情的时间戳可能大于等于500,也可能小于 500。
互联网环境下,开盘后CTP会推送合约的状态(如开盘后,合约状态更新为连续交易),消息推送时间与开盘后第一笔行情时间几乎是重合的。
3、CTP行情接收
CTPMarketDataSource.h文件:
#ifndef CTPMARKETDATASOURCE_H #define CTPMARKETDATASOURCE_H #include <vector> #include <iostream> #include <fstream> #include <unordered_map> #include <unordered_set> #include <string.h> #include <stdio.h> #include "ThostFtdcMdApi.h" class CTPMarketDataSource : public CThostFtdcMdSpi { public: explicit CTPMarketDataSource(const char* yml); void startMarketDataSource(); ~CTPMarketDataSource(); public: ///当客户端与交易后台建立起通信连接时(还未登录前)被调用。 void OnFrontConnected(); ///当客户端与交易后台通信连接断开时被调用。当发生这个情况后,API会自动重新连接,客户端可不做处理。 ///@param nReason 错误原因 /// 0x1001 网络读失败 /// 0x1002 网络写失败 /// 0x2001 接收心跳超时 /// 0x2002 发送心跳失败 /// 0x2003 收到错误报文 void OnFrontDisconnected(int nReason); ///心跳超时警告。当长时间未收到报文时,该方法被调用。 ///@param nTimeLapse 距离上次接收报文的时间 void OnHeartBeatWarning(int nTimeLapse); ///登录请求响应 void OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); ///登出请求响应 void OnRspUserLogout(CThostFtdcUserLogoutField *pUserLogout, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); ///错误应答 void OnRspError(CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); ///订阅行情应答 void OnRspSubMarketData(CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); ///取消订阅行情应答 void OnRspUnSubMarketData(CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); ///深度行情通知 void OnRtnDepthMarketData(CThostFtdcDepthMarketDataField *pDepthMarketData); private: CThostFtdcMdApi *m_pMdUserApi; }; #endif // CTPMARKETDATASOURCE_H
CTPMarketDataSource.cpp文件:
#include "CTPMarketDataSource.h" CTPMarketDataSource::CTPMarketDataSource(const char *yml) { // 创建行情API实例 m_pMdUserApi = CThostFtdcMdApi::CreateFtdcMdApi(); } void CTPMarketDataSource::startMarketDataSource() { // 注册事件类 m_pMdUserApi->RegisterSpi(this); // 设置行情前置地址 std::string FrontAddr; m_pMdUserApi->RegisterFront(const_cast<char *>(FrontAddr.c_str())); m_pMdUserApi->Init(); // 等到线程退出 m_pMdUserApi->Join(); } CTPMarketDataSource::~CTPMarketDataSource() { } void CTPMarketDataSource::OnFrontConnected() { CThostFtdcReqUserLoginField loginReq; std::string BrokerID, InvestorID, Password; memset(&loginReq, 0, sizeof(loginReq)); strcpy(loginReq.BrokerID, BrokerID.c_str()); strcpy(loginReq.UserID, InvestorID.c_str()); strcpy(loginReq.Password, Password.c_str()); static int requestID = 0; int rt = m_pMdUserApi->ReqUserLogin(&loginReq, requestID++); } void CTPMarketDataSource::OnFrontDisconnected(int nReason) { } void CTPMarketDataSource::OnHeartBeatWarning(int nTimeLapse) { } void CTPMarketDataSource::OnRspUserLogin( CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast) { bool bResult = pRspInfo && (pRspInfo->ErrorID != 0); if (bIsLast && !bResult) { printf("账户登录成功\n"); printf("交易日: %s", pRspUserLogin->TradingDay); printf("登录时间: %s", pRspUserLogin->LoginTime); printf("经纪商: %s", pRspUserLogin->BrokerID); printf("账户名: %s", pRspUserLogin->UserID); printf("SystemName: %s", pRspUserLogin->SystemName); printf("ApiVersion: %s", m_pMdUserApi->GetApiVersion()); // 读取合约配置 int instrumentNum = 6; char *instruments[instrumentNum]; int i = 0; // 开始订阅行情 int rt = m_pMdUserApi->SubscribeMarketData(instruments, instrumentNum); } } void CTPMarketDataSource::OnRspUserLogout( CThostFtdcUserLogoutField *pUserLogout, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast) { bool bResult = pRspInfo && (pRspInfo->ErrorID != 0); } void CTPMarketDataSource::OnRspError(CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast) { bool bResult = pRspInfo && (pRspInfo->ErrorID != 0); } void CTPMarketDataSource::OnRspSubMarketData( CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast) { bool bResult = pRspInfo && (pRspInfo->ErrorID != 0); if (!bResult) { // 订阅成功 } } void CTPMarketDataSource::OnRspUnSubMarketData( CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast) { bool bResult = pRspInfo && (pRspInfo->ErrorID != 0); } void CTPMarketDataSource::OnRtnDepthMarketData( CThostFtdcDepthMarketDataField *pDepthMarketData) { // 接收行情数据 // 处理订阅合约行情数据 }
main.cpp文件:
#include "CTPMarketDataSource.h" #include "Logger.h" int main(int argc, char *argv[]) { CTPMarketDataSource *pMdSource = new CTPMarketDataSource("config.yml"); pMdSource->startMarketDataSource(); return 0; }
CMakeLists.txt文件:
cmake_minimum_required(VERSION 3.17) PROJECT(CTPMarketDataSource) set(CMAKE_CXX_FLAGS "-fPIC -O3") set(CMAKE_INCLUDE_CURRENT_DIR ON) # 默认输出debug版 #SET(CMAKE_BUILD_TYPE "Release") SET(CMAKE_BUILD_TYPE "debug") set(CMAKE_CXX_STANDARD 11) set(CTP_INCLUDE ${CMAKE_SOURCE_DIR}/CTPLib/include) include_directories(${CTP_INCLUDE}) set(CTP_LIB ${CMAKE_SOURCE_DIR}/CTPLib/lib) link_directories(${CTP_LIB}) set(SOURCES main.cpp CTPMarketDataSource.cpp) add_executable(${PROJECT_NAME} main.cpp CTPMarketDataSource.cpp) target_link_libraries(${PROJECT_NAME} thostmduserapi_se pthread)
三、Sina行情
1、Sina行情简介
Sina财经提供实时行情数据的API接口可以用于获取股票和指数现货的行情,但通常存在延迟。通过访问http://hq.sinajs.cn/list=sz002307,sh600928
可以获取sz002307,sh600928的行情数据。
var hq_str_sz002307="北新路桥,8.700,8.820,9.080,9.310,8.630,9.080,9.100,108452484,968707402.800,42300,9.080,91100,9.070,17500,9.060,42200,9.050,19200,9.040,7048,9.100,16600,9.110,47300,9.120,20460,9.130,21400,9.140,2019-03-27,14:33:03,00"; var hq_str_sh600928="西安银行,11.470,11.550,11.560,11.890,11.280,11.560,11.570,101874307,1186320558.000,46300,11.560,82562,11.550,57300,11.540,64300,11.530,27000,11.520,1100,11.570,6100,11.580,12700,11.590,27727,11.600,40600,11.610,2019-03-27,14:33:02,00";
2、Sina行情获取
SinaMarketDataSource.h文件:
#include <unistd.h> #include <string> #include <sys/socket.h> #include <netdb.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <hpsocket/HPSocket4C.h> class SinaMarketDataSource { public: SinaMarketDataSource(){} void StartMarketDataSource(const char* yml); void Stop(); virtual ~SinaMarketDataSource(); private: bool SendHTTPRequest(const char *data); protected: static EnHandleResult __HP_CALL OnConnect(HP_HttpClient pSender, CONNID dwConnID); static EnHandleResult __HP_CALL OnHandShake(HP_HttpClient pSender, CONNID dwConnID); static EnHandleResult __HP_CALL OnSend(HP_HttpClient pSender, CONNID dwConnID, const BYTE *pData, int iLength); static EnHandleResult __HP_CALL OnReceive(HP_HttpClient pSender, CONNID dwConnID, const BYTE *pData, int iLength); static EnHandleResult __HP_CALL OnClose(HP_HttpClient pSender, CONNID dwConnID, EnSocketOperation enOperation, int iErrorCode); static EnHttpParseResult __HP_CALL OnMessageBegin(HP_HttpClient pSender, CONNID dwConnID); static EnHttpParseResult __HP_CALL OnStatusLine(HP_HttpClient pSender, CONNID dwConnID, USHORT usStatusCode, LPCSTR lpszDesc); static EnHttpParseResult __HP_CALL OnHeader(HP_HttpClient pSender, CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue); static EnHttpParseResult __HP_CALL OnHeadersComplete(HP_HttpClient pSender, CONNID dwConnID); static EnHttpParseResult __HP_CALL OnBody(HP_HttpClient pSender, CONNID dwConnID, const BYTE *pData, int iLength); static EnHttpParseResult __HP_CALL OnChunkHeader(HP_HttpClient pSender, CONNID dwConnID, int iLength); static EnHttpParseResult __HP_CALL OnChunkComplete(HP_HttpClient pSender, CONNID dwConnID); static EnHttpParseResult __HP_CALL OnMessageComplete(HP_HttpClient pSender, CONNID dwConnID); static EnHttpParseResult __HP_CALL OnUpgrade(HP_HttpClient pSender, CONNID dwConnID, EnHttpUpgradeType enUpgradeType); static EnHttpParseResult __HP_CALL OnParseError(HP_HttpClient pSender, CONNID dwConnID, int iErrorCode, LPCSTR lpszErrorDesc); static EnHandleResult __HP_CALL OnWSMessageHeader(HP_HttpClient pSender, CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], ULONGLONG ullBodyLen); static EnHandleResult __HP_CALL OnWSMessageBody(HP_HttpClient pSender, CONNID dwConnID, const BYTE *pData, int iLength); static EnHandleResult __HP_CALL OnWSMessageComplete(HP_HttpClient pSender, CONNID dwConnID); private: SinaMarketDataSource &operator=(const SinaMarketDataSource &); SinaMarketDataSource(const SinaMarketDataSource &); private: HP_HttpClient m_pHTTPClient; HP_HttpClientListener m_pHTTPClientListener; static std::unordered_map<std::string, std::string> m_sTickerMarketData; std::string m_URL; };
SinaMarketDataSource.cpp文件:
#include "SinaMarketDataSource.h" std::unordered_map<std::string, std::string> SinaMarketDataSource::m_sTickerMarketData; SinaMarketDataSource::SinaMarketDataSource() { m_URL = "http://hq.sinajs.cn?list=sh601003,sh601001"; m_pHTTPClientListener = ::Create_HP_HttpClientListener(); m_pHTTPClient = ::Create_HP_HttpClient(m_pHTTPClientListener); ::HP_Set_FN_HttpClient_OnConnect(m_pHTTPClientListener, OnConnect); ::HP_Set_FN_HttpClient_OnHandShake(m_pHTTPClientListener, OnHandShake); ::HP_Set_FN_HttpClient_OnReceive(m_pHTTPClientListener, OnReceive); ::HP_Set_FN_HttpClient_OnSend(m_pHTTPClientListener, OnSend); ::HP_Set_FN_HttpClient_OnClose(m_pHTTPClientListener, OnClose); ::HP_Set_FN_HttpClient_OnMessageBegin(m_pHTTPClientListener, OnMessageBegin); ::HP_Set_FN_HttpClient_OnStatusLine(m_pHTTPClientListener, OnStatusLine); ::HP_Set_FN_HttpClient_OnHeader(m_pHTTPClientListener, OnHeader); ::HP_Set_FN_HttpClient_OnHeadersComplete(m_pHTTPClientListener, OnHeadersComplete); ::HP_Set_FN_HttpClient_OnBody(m_pHTTPClientListener, OnBody); ::HP_Set_FN_HttpClient_OnChunkHeader(m_pHTTPClientListener, OnChunkHeader); ::HP_Set_FN_HttpClient_OnChunkComplete(m_pHTTPClientListener, OnChunkComplete); ::HP_Set_FN_HttpClient_OnMessageComplete(m_pHTTPClientListener, OnMessageComplete); ::HP_Set_FN_HttpClient_OnUpgrade(m_pHTTPClientListener, OnUpgrade); ::HP_Set_FN_HttpClient_OnParseError(m_pHTTPClientListener, OnParseError); ::HP_Set_FN_HttpClient_OnWSMessageHeader(m_pHTTPClientListener, OnWSMessageHeader); ::HP_Set_FN_HttpClient_OnWSMessageBody(m_pHTTPClientListener, OnWSMessageBody); ::HP_Set_FN_HttpClient_OnWSMessageComplete(m_pHTTPClientListener, OnWSMessageComplete); ::HP_HttpClient_SetUseCookie(m_pHTTPClient, true); ::HP_TcpClient_SetKeepAliveTime(m_pHTTPClient, 60000); } SinaMarketDataSource::~SinaMarketDataSource() { ::Destroy_HP_HttpClient(m_pHTTPClient); ::Destroy_HP_HttpClientListener(m_pHTTPClientListener); ::HP_HttpCookie_MGR_SaveToFile("./cookie", TRUE); } void SinaMarketDataSource::StartMarketDataSource(const char *yml) { bool ret = ::HP_Client_StartWithBindAddress(m_pHTTPClient, "183.232.24.241", 80, false, NULL); if (!ret) { printf("%s\n", ::HP_Client_GetLastErrorDesc(m_pHTTPClient)); } while (true) { // 发送行情查询请求 bool ok = SendHTTPRequest(m_URL.c_str()); if (!ok) { printf("%s\n", ::HP_Client_GetLastErrorDesc(m_pHTTPClient)); } // 行情数据每3秒推送一次 sleep(3); } } void SinaMarketDataSource::Stop() { ::HP_Client_Stop(m_pHTTPClient); } EnHandleResult __HP_CALL SinaMarketDataSource::OnConnect(HP_HttpClient pSender, CONNID dwConnID) { TCHAR szAddress[50]; int iAddressLen = sizeof(szAddress) / sizeof(TCHAR); USHORT usPort; ::HP_Client_GetRemoteHost(pSender, szAddress, &iAddressLen, &usPort); printf("connected to %s:%d successed\n", szAddress, usPort); return HR_OK; } EnHandleResult __HP_CALL SinaMarketDataSource::OnHandShake(HP_HttpClient pSender, CONNID dwConnID) { return HR_OK; } EnHandleResult __HP_CALL SinaMarketDataSource::OnSend(HP_HttpClient pSender, CONNID dwConnID, const BYTE *pData, int iLength) { return HR_OK; } EnHandleResult __HP_CALL SinaMarketDataSource::OnReceive(HP_HttpClient pSender, CONNID dwConnID, const BYTE *pData, int iLength) { return HR_OK; } EnHandleResult __HP_CALL SinaMarketDataSource::OnClose(HP_HttpClient pSender, CONNID dwConnID, EnSocketOperation enOperation, int iErrorCode) { TCHAR szAddress[50]; int iAddressLen = sizeof(szAddress) / sizeof(TCHAR); USHORT usPort; ::HP_Client_GetRemoteHost(pSender, szAddress, &iAddressLen, &usPort); printf("{}:{} closed\n", szAddress, usPort); return HR_OK; } EnHttpParseResult __HP_CALL SinaMarketDataSource::OnMessageBegin(HP_HttpClient pSender, CONNID dwConnID) { return HPR_OK; } EnHttpParseResult __HP_CALL SinaMarketDataSource::OnStatusLine(HP_HttpClient pSender, CONNID dwConnID, USHORT usStatusCode, LPCSTR lpszDesc) { return HPR_OK; } EnHttpParseResult __HP_CALL SinaMarketDataSource::OnHeader(HP_HttpClient pSender, CONNID dwConnID, LPCSTR lpszName, LPCSTR lpszValue) { return HPR_OK; } EnHttpParseResult __HP_CALL SinaMarketDataSource::OnHeadersComplete( HP_HttpClient pSender, CONNID dwConnID) { return HPR_OK; } EnHttpParseResult __HP_CALL SinaMarketDataSource::OnBody(HP_HttpClient pSender, CONNID dwConnID, const BYTE *pData, int iLength) { std::string response = (const char *)pData; // 接收并且处理行情数据 return HPR_OK; } EnHttpParseResult __HP_CALL SinaMarketDataSource::OnChunkHeader( HP_HttpClient pSender, CONNID dwConnID, int iLength) { return HPR_OK; } EnHttpParseResult __HP_CALL SinaMarketDataSource::OnChunkComplete( HP_HttpClient pSender, CONNID dwConnID) { return HPR_OK; } EnHttpParseResult __HP_CALL SinaMarketDataSource::OnMessageComplete( HP_HttpClient pSender, CONNID dwConnID) { return HPR_OK; } EnHttpParseResult __HP_CALL SinaMarketDataSource::OnUpgrade(HP_HttpClient pSender, CONNID dwConnID, EnHttpUpgradeType enUpgradeType) { return HPR_OK; } EnHttpParseResult __HP_CALL SinaMarketDataSource::OnParseError(HP_HttpClient pSender, CONNID dwConnID, int iErrorCode, LPCSTR lpszErrorDesc) { return HPR_OK; } EnHandleResult __HP_CALL SinaMarketDataSource::OnWSMessageHeader( HP_HttpClient pSender, CONNID dwConnID, BOOL bFinal, BYTE iReserved, BYTE iOperationCode, const BYTE lpszMask[4], ULONGLONG ullBodyLen) { return HR_OK; } EnHandleResult __HP_CALL SinaMarketDataSource::OnWSMessageBody(HP_HttpClient pSender, CONNID dwConnID, const BYTE *pData, int iLength) { return HR_OK; } EnHandleResult __HP_CALL SinaMarketDataSource::OnWSMessageComplete( HP_HttpClient pSender, CONNID dwConnID) { BYTE iOperationCode; ::HP_HttpClient_GetWSMessageState(pSender, nullptr, nullptr, &iOperationCode, nullptr, nullptr, nullptr); if (iOperationCode == 0x8) return HR_ERROR; return HR_OK; } bool SinaMarketDataSource::SendHTTPRequest(const char *url) { return HP_HttpClient_SendGet(m_pHTTPClient, url, NULL, 0); }
main.cpp文件:
#include "SinaMarketDataSource.h" int main(int argc, char *argv[]) { SinaMarketDataSource* source = new SinaMarketDataSource; source->StartMarketDataSource("config.yml"); return 0; }
CMakeLists.txt文件:
cmake_minimum_required(VERSION 3.17) PROJECT(SinaMarketDataSource) set(CMAKE_CXX_FLAGS "-fPIC") set(CMAKE_INCLUDE_CURRENT_DIR ON) # 默认输出debug版 #SET(CMAKE_BUILD_TYPE "Release") SET(CMAKE_BUILD_TYPE "debug") set(CMAKE_CXX_STANDARD 11) set(SOURCES main.cpp SinaMarketDataSource.cpp) add_executable(${PROJECT_NAME} ${SOURCES}) target_link_libraries(${PROJECT_NAME} hpsocket4c pthread)
推荐参考学习资料:
#CTP##行情API#