协议方式接收二代组播行情
本文介绍协议方式下如何通过mdfront接收二代组播行情。
1. 术语介绍
协议方式:指不通过CTP封装的API,而是自行通过二代行情协议规范解码数据报文,从而获取实时行情的方式。
二代组播行情(下文简称二代行情):交易所以组播方式提供的实时五档行情。因为是组播,所以接收端必须在内部网络并且要加入组播组。
mdfront:CTP交易系统中一个接收交易所二代组播行情的组件。该组件提供查询组播合约接口,可通过API函数ReqQryMulticastInstrument获取。
2. 说明
交易所不允许投资者直接交易所报盘网去接收二代行情,但期货公司可以将二代行情转发出来给投资者使用。
二代行情包含了快照查询和增量行情实时推送功能,其中,增量行情仅包含了变动值,例如价格变动多少、成交量变动多少,只有通过快照+增量的方式才能得到一笔完整的行。而期货公司只能转发增量行情,不能转发快照查询。所以投资者除了接收增量行情外,还需要想办法获取行情快照。
行情快照建议通过mdfront获取,只需订阅行情即可。订阅方法参见行情接口。
最后,增量行情还有个问题,即报文里的合约是以数字编号命名,例如编号100。而CTP的行情都是合约名称,例如rb2002,为了解决合约编号到合约名称的映射关系,mdfront提供了ReqQryMulticastInstrument查询接口,用户可以通过mdapi的ReqQryMulticastInstrument查询到映射关系。
注意,普通front不提供ReqQryMulticastInstrument查询接口,调用不会返回有效数据。
综上所述,需要有三个部分才能拼装出完整的行情,分别为增量行情、行情快照和组播合约映射关系。
3. 方法
下面是推荐方法,仅做参考:
1、订阅CTP行情。
可以连接mdfront组件接收行情,因为CTP端的行情是完整的行情,而非增量,所以可以作为基准快照,在后续拼装完整行情的时候使用。
2、订阅期货公司转发的二代行情。
具有解码能力的投资者,可以订阅该行情,并参考交易所二代行情协议规范做相应解码。解码后会得到增量行情,但是这里要注意,这个增量行情还不能直接拿来用,因为该行情里没有InstrumentID,只有合约编号InstrumentNo,而CTP mdfront的行情里是InstrumentID,两者无法关联。为了解决这个问题,新的mdapi增加了ReqQryMulticastInstrument接口,该接口会返回InstrumentID和InstrumentNo的对应关系,有了这个函数便可以拼出完整的行情。注意,仅新的组播mdfront支持该函数,普通行情front和老mdfront不支持该函数!完整行情拼装方法如下:
a) 盘前接入
开盘后,CTP推送每tick快照行情,转发行情则推送每tick增量行情。投资者程序判断同一合约两边的UpdateTime和UpdateMillisec。如果一致,则将mdfront的这笔tick快照行情作为该合约的基准快照,后续的增量行情都在此基础上做拼装,得到笔笔完整行情。
b) 盘中接入
盘中,如果客户接入,则方法同上述a,投资者程序实时判断两边的UpdateTime和UpdateMillisec,直到遇到一致,则作为基准快照。
c) 盘中丢行情
交易所的二代行情带行情序号,如果行情序号不连续了,说明行情丢包了。此时应该停止行情组装工作,重新寻找最新的行情快照,方法同a。
4. 代码示例
下面代码演示了如何接入mdfront前置,查询组播合约,并订阅行情。
// mduserhandle.h
#include "ThostFtdcMdApi.h"
#include <stdio.h>
#include <Windows.h>
TThostFtdcInstrumentIDType g_chInstrumentID;
class CMduserHandler : public CThostFtdcMdSpi
{
private:
CThostFtdcMdApi *m_mdApi;
public:
void connect()
{
//创建并初始化API
m_mdApi = CThostFtdcMdApi::CreateFtdcMdApi("", true, true);
m_mdApi->RegisterSpi(this);
m_mdApi->RegisterFront("tcp://218.28.130.102:41413");
m_mdApi->Init();
}
//登陆
void login()
{
CThostFtdcReqUserLoginField t = {0};
while (m_mdApi->ReqUserLogin(&t, 1)!=0) Sleep(1000);
}
//请求查询组播合约
void ReqQryMulticastInstrument()
{
CThostFtdcQryMulticastInstrumentField a = { 0 };
a.TopicID = 1001;
strcpy_s(a.InstrumentID, "cu1905");
while (m_mdApi->ReqQryMulticastInstrument(&a, 1)!=0) Sleep(1000);
}
void OnRspQryMulticastInstrument(CThostFtdcMulticastInstrumentField *pMulticastInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
{
if (pMulticastInstrument)
{
strcpy_s(g_chInstrumentID,pMulticastInstrument->InstrumentID);
}
}
// 订阅行情
void subscribe()
{
char **ppInstrument=new char * [50];
ppInstrumentID[0] = g_chInstrumentID;
while (m_mdApi->SubscribeMarketData(ppInstrument, 1)!=0) Sleep(1000);
}
//接收行情
void OnRtnDepthMarketData(CThostFtdcDepthMarketDataField *pDepthMarketData)
{
printf("OnRtnDepthMarketData\n");
}
};
// main.cpp
#include "mduserhandle.h"
int main(int argc, char* argv[])
{
CMduserHandler *mduser = new CMduserHandler;
mduser->connect();
mduser->login();
mduser->ReqQryMulticastInstrument();
mduser->subscribe();
Sleep(INFINITE);
}