一、CTP交易API简介
1、CTP交易API简介
CThostFtdcTraderApi交易API接口包含CThostFtdcTraderApi和CThostFtdcTraderSpi,通过CThostFtdcTraderApi向CTP发送操作请求,通过CThostFtdcTraderSpi接收CTP操作响应。
2、CTP API交易流程
(1)创建CTP API实例
CThostFtdcTraderApi *pTradeApi = CThostFtdcTraderApi::CreateFtdcTraderApi(dirName);
API实例用于发起各种请求,如连接服务器、用户登录、报单、撤单、查询持仓、查询资金等。
(2)创建CTP API回调实例
CFtdcTradeSpi *pTradeSpi = new CFtdcTradeSpi(pTradeApi, this);
开发者需要实现上期CThostFtdcTraderSpi类,重写类方法,处理CTP服务端回传的各类数据。
(3)注册回调实例
pTradeApi->RegisterSpi(pTradeSpi);
(4)连接前置服务器
pTradeApi_->RegisterFront((char *)serverAddr_.c_str());
pTradeApi_->Init();
连接请求发出后,OnFrontConnected()会响应请求。
(5)身份认证
在用户登录前,CTP服务端要求对客户端进行身份认证,客户端通过认证后才能请求登录。
身份认证功能是否启用在期货公司的业务人员使用的结算平台上是可以进行配置的。期货公司可以选择关闭身份认证功能,则客户端可不必进行身份认证。否则期货公司需要在结算平台上维护该客户端程序的认证码(AuthCode)。
请求进行身份认证使用的函数接口为 ReqAuthenticate(请求身份认证)和 OnRspAuthenticate(服务端返回的身份认证的响应)。
(6)用户登录
OnRspAuthenticate()函数内可以调用登录函数pTradeApi->ReqUserLogin()完成用户登录操作。
登录成功后,OnRspUserLogin中参数pRspUserLogin中包含前置编号(FrontID)、会话编号(SessionID)、最大报单编号(MaxOrderRef)。
前置编号:客户端连接到的前置机的编号。
会话编号:客户端连接到前置机的连接会话编号。
最大报单编号:每一笔报单都有一个唯一的不重复的编号(OrderRef)。客户端若不赋值,服务端自动赋值;客户端若赋值,可从MaxOrderRef向上逐一递增,防止与其它报单重复,也可以根据时间戳指定唯一不重复编号即可。
通过ReqUserLogout登出系统,会先将现有链接断开,再重新建立一个新链接,重新登录后 SessionID会重置,因此MaxOrderRef 会重新从0计数。
(7)Topic订阅
OnRspUserLogin()函数内订阅Topic完成后,可以正常下单交易。
pTradeApi_->SubscribePublicTopic(THOST_TERT_QUICK);
pTradeApi_->SubscribePrivateTopic(THOST_TERT_QUICK);
CTP公有流和私有流提供三种订阅方式,
TERT_RESTART:从本交易日开始重传。
TERT_RESUME:从上次收到的续传。
TERT_QUICK:只传送登录后的内容。
由于国内期货在交易日内首次登录时需要做投资者结算结果确认操作,因此需要登录成功时请求投资者结算结果确认。
int ReqSettlementInfoConfirm(CThostFtdcSettlementInfoConfirmField *pSettlementInfoConfirm, int nRequestID);
(8)报单操作
virtual int ReqOrderInsert(CThostFtdcInputOrderField *pInputOrder, int nRequestID) = 0;
报单操作如下:
CThostFtdcInputOrderField reqField;
memset(&reqField, 0, sizeof(CThostFtdcInputOrderField));
// 报单条件
pTradeApi_->ReqOrderInsert(&reqField, orderInsertReqId_);
需要正确填写买卖方向、开仓、平仓、市价、限价、委托数量、委托价格等,调用ReqOrderInsert()报单。
调用函数ReqOrderInsert报单后,如果在CTP端验资验仓等通不过,则客户端会回调函数OnRspOrderInsert和OnErrRtnOrderInsert;如果通过则先回调一次OnRtnOrder。
CTP再将报单报往交易所,如果交易所验资验仓没通过将返回错误给CTP,此时客户端会回调OnRtnOrder;如果交易所检查正确则此时会再次回调OnRtnOrder。
InvestorID:投资者代码
InstrumentID:合约代码
Direction:买卖方向
CombOffsetFlag:开平标识
CombHedgeFlag:投机套保标识
LimitPrice:价格
VolumeTotalOriginal:数量
TimeCondition:有效期类型
OrderPriceType:报单价格条件
VolumeCondition:成交量类型
MinVolume:最小成交量
ContingentCondition:触发条件
StopPrice:止损价
IsSwapOrder:互换单标志
ExchangeID:交易所代码
(9)报单响应
void CFtdcTradeSpi::OnRspOrderInsert(CThostFtdcInputOrderField *pInputOrder, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast);
CTP为了简化逻辑,实际上报单后并不会被回调,会在CTP检查不通过时回调。
(10)订单状态回报
void CFtdcTradeSpi::OnRtnOrder(CThostFtdcOrderField *pOrder);
当委托状态发生变化时,会被回调。常见委托状态主要有:未知、未成交还在队列中、部分成交还在队列中、完全成交等。一次报单,如果数量比较多,一般不会一次全部成交,而是会分多批次成交,所以会不断被回调。随着不断回调,每次返回的委托量、成交量、剩余量等数据会不断变更。
(11)成交回报
void CFtdcTradeSpi::OnRtnTrade(CThostFtdcTradeField *pTrade);
返回每一条信息都是成交信息,包含成交量、成交价、成交费用等。
二、CTP报单指令
1、OrderPriceType订单价格类型
限价(LimitPrice):执行价格必须同等于或好于委托中指定的限价,即仅和撮合队列中价格等于或好于指定价的对手方进行撮合,CTP标识 THOST_FTDC_OPT_LimitPrice。
市价(AnyPrice):委托能够以市场上任何可得到的价格执行,即可以和撮合队列中的任何对手方报单进行撮合,CTP标识THOST_FTDC_OPT_AnyPrice。
最优价(BestPrice):仅和撮合队列中排在最前面的1个价格的对手方报单进行撮合,CTP标识 THOST_FTDC_OPT_BestPrice。
五档市价(FiveLevelPrice):有一定的价格保护带,只和撮合队列中排在最前面的5个价格的对手方报单进行撮合,CTP标识THOST_FTDC_OPT_FiveLevelPrice。
2、TimeCondition时间条件
THOST_FTDC_TC_IOC:立即完成,否则撤销
THOST_FTDC_TC_GFS:本节有效
THOST_FTDC_TC_GFD:当日有效
THOST_FTDC_TC_GTC:撤销前有效
THOST_FTDC_TC_GFA:集合竞价有效
3、VolumeCondition成交量条件
任何数量(AV):以可能的最大数量执行,CTP标识THOST_FTDC_VC_AV。
最小数量(MV):委托包含必须执行的一个最小数量,如执行不成功则委托被完全取消,CTP标识THOST_FTDC_VC_MV。
全部数量(CV):委托必须按指定数量全额执行,如执行不成功则委托被完全取消,CTP标识THOST_FTDC_VC_CV。
4、ContingentCondition触发条件
THOST_FTDC_CC_Immediately:报单立即有效。
THOST_FTDC_CC_Touch:止损单。
THOST_FTDC_CC_TouchProfit:止盈单。
THOST_FTDC_CC_ParkedOrder:预埋单。预埋单是指预埋在CTP服务端,需要非交易时间报入,开市后自动报往交易所。
5、限价单
LIMIT普通限价单:下单后,在指定的时间范围内(本节有效、当日有效等),等待报单的数量全部成交。
FAK(Fill And kill):报单被交易所接收后,交易所会扫描市场行情,在当时行情下能立即成交多少手即参与撮合成交多少手,剩余数量立即全部撤销。
FOK(Fill Or Kill):报单被交易所接收后,交易所会扫描市场行情,如果在当时的市场行情下报单可以立即全部成交,则报单会参与撮合成交,否则立即全部撤销。
三、报单
1、报单
报单是指将买卖期货合约指令通过API函数发送到CTP柜台,CTP柜台收到后会对该笔订单进行一系列的检查,检查通过后再发送到交易所。
CTP的整个报单过程不需要编写报单流控逻辑,因为CTP没有报单频率限制,但有查询频率限制;易盛交易API查询没频率限制,报单有频率限制。
调用函数ReqOrderInsert报单后,如果在CTP端校验通不过,则客户端会回调函数OnRspOrderInsert和OnErrRtnOrderInsert;如果CTP端校验通过则先回调一次OnRtnOrder,CTP再将报单报往交易所;如果交易所校验没通过,返回错误给CTP,客户端会回调OnRtnOrder;如果交易所校验通过,则会再次回调OnRtnOrder。
virtual int ReqOrderInsert(CThostFtdcInputOrderField *pInputOrder, int nRequestID) = 0;
CThostFtdcInputOrderField用于填入报单参数,nRequestID用于填订单请求的编号。
BrokerID:经纪公司代码
InvestorID:投资者代码
ExchangeID:交易所代码,CFFEX、CZCE、DCE、INE、SHFE
InstrumentID:合约代码
OrderPriceType:报单价格类型
Direction:买卖方向
LimitPrice:价格
VolumeTotalOriginal:数量
CombOffsetFlag:组合开平标志,THOST_FTDC_OF_Open是开仓,THOST_FTDC_OF_Close是平仓/平昨,THOST_FTDC_OF_CloseToday是平今。
CombHedgeFlag:组合投机套保标志
ContingentCondition:触发条件,通常为THOST_FTDC_CC_Immediately,即报单立即有效。THOST_FTDC_CC_Touch和THOST_FTDC_CC_TouchProfit是止损止盈单,需要交易所支持才能填。THOST_FTDC_CC_ParkedOrder是预埋单。
TimeCondition:有效期类型,THOST_FTDC_TC_GFD指当日有效,报单会挂在交易所直到成交或收盘自动撤销。THOST_FTDC_TC_IOCIOC是立即完成否则撤销
VolumeCondition:成交量类型
MinVolume:最小成交量。
ForceCloseReason:强平原因
限价GFD单:
CThostFtdcInputOrderField orderfield = {0};
strcpy(orderfield.BrokerID, "9999");
strcpy(orderfield.InvestorID, "000001");
strcpy(orderfield.ExchangeID, “SHFE”);
strcpy(orderfield.InstrumentID, "au1912");
orderfield.OrderPriceType = THOST_FTDC_OPT_LimitPrice;
orderfield.Direction = THOST_FTDC_D_Sell;
orderfield.LimitPrice = 400.0;
orderfield.VolumeTotalOriginal = 10;
orderfield.ContingentCondition = THOST_FTDC_CC_Immediately;
orderfield.CombOffsetFlag[0] = THOST_FTDC_OF_Open;
orderfield.CombHedgeFlag[0] = THOST_FTDC_HF_Speculation;
orderfield.TimeCondition = THOST_FTDC_TC_GFD ;
orderfield.VolumeCondition = THOST_FTDC_VC_AV;
orderfield.ForceCloseReason = THOST_FTDC_FCC_NotForceClose;
int ret = g_pTradeapi->ReqOrderInsert(&orderfield, 0);
限价FOK单:
CThostFtdcInputOrderField orderfield ={0};
strcpy(orderfield.BrokerID, "9999");
strcpy(orderfield.InvestorID, "000001");
strcpy(orderfield.ExchangeID, “SHFE”);
strcpy(orderfield.InstrumentID, "au1912");
orderfield.OrderPriceType = THOST_FTDC_OPT_LimitPrice;
orderfield.Direction = THOST_FTDC_D_Sell;
orderfield.LimitPrice = 400.0;
orderfield.VolumeTotalOriginal = 10;
orderfield.ContingentCondition = THOST_FTDC_CC_Immediately;
orderfield.CombOffsetFlag[0] = THOST_FTDC_OF_Open;
orderfield.CombHedgeFlag[0] = THOST_FTDC_HF_Speculation;
orderfield.TimeCondition = THOST_FTDC_TC_IOC;
orderfield.VolumeCondition = THOST_FTDC_VC_CV;
orderfield.ForceCloseReason = THOST_FTDC_FCC_NotForceClose;
int ret = g_pTradeapi->ReqOrderInsert(&orderfield, 0);
2、订单索引
报单请求和报单回报通常使用下列字段确定一笔订单。
FrontID:前置编号
SessionID:会话编号
OrderRef:报单引用
ExchangeID:交易所代码
TraderID:交易所交易员代码
OrderLocalID:CTP本地报单编号
OrderSysID:交易所订单编号
(1)FrontID + SessionID + OrderRef
FrontID是CTP后台前置编号, SessionID是会话链接的编号,登录成功回报中会返回FrontID和SessionID,本次会话连接中不变。
OrderRef返回客户报单请求时填写的对应字段,如果没填,CTP会自动递增地为字段赋值。因此,每次报单时可以用FrontID + SessionID + OrderRef组成一个key在本地标识存入唯一的一笔报单,当有报单回报返回时,可以根据回报中三个字段找出原始的请求报单,再用回报中的状态来更新原始请求报单的当前状态。撤单时可以用三个字段填入到请求撤单对应的字段进行撤单。
当客户报单有成交时,CTP会推送OnRtnTrade成交回报,成交回报中没有FrontID 和SessionID字段,只有OrderRef字段。
(2)ExchangeID + TraderID + OrderLocalID
OrderLocalID是CTP的订单号,客户无法改变。OrderLocalID在ExchangeID、TraderID两个字段固定时单调递增,每次回报返回给客户。
3)ExchangeID + OrderSysID
OrderSysID是报单报入到交易所时交易所给订单编的唯一编号。报单请求首次到达CTP,风控通过后返回第1个OnRtnOrder回报,此时还没有报入到交易所,所以回报中OrderSysID为空。撤单时可以使用OrderSysID指定订单。
开发中订单索引维护有两种方案:
(1)报单时用FrontID + SessionID + OrderRef维护报单,当第2个OnRtnOder回来后,找到原始报单,将OrderSysID填入到本地报单中,后续使用ExchangeID + OrderSysID维护订单。
(2)本地维护OrderRef字段,当前交易日内不管多少链接都一直单调递增,每次根据OrderRef就可以确定唯一报单。但多策略多链接交易可能存在问题,会涉及到多个API链接间OrderRef字段的互相同步。
3、正确回报
报单请求后,如果CTP检查通过则返回正确回报,回调只有OnRtnOrder函数(如有成交则会单独回调OnRtnTrade函数)。回调参数类型为CThostFtdcOrderField。
OrderStatus:报单状态
OrderSubmitStatus:报单提交状态
StatusMsg:状态信息
VolumeTotalOriginal:即原始报单数量
VolumeTraded:今成交数量,订单累计成交量
VolumeTotal:剩余数量,订单剩余未成交数量
OrderStatus订单状态如下:
THOST_FTDC_OST_AllTraded:全部成交
THOST_FTDC_OST_PartTradedQueueing:部分成交还在队列中THOST_FTDC_OST_PartTradedNotQueueing:部分成交不在队列中
THOST_FTDC_OST_NoTradeQueueing:未成交还在队列中
THOST_FTDC_OST_NoTradeNotQueueing:未成交不在队列中
THOST_FTDC_OST_Canceled:撤单
THOST_FTDC_OST_Unknown:未知
THOST_FTDC_OST_NotTouched:尚未触发
THOST_FTDC_OST_Touched:已触发
4、CTP拒单
如果报单被CTP直接拒单,会回调OnRspOrderInsert和OnErrRtnOrderInsert函数。
OnRspOrderInsert是当前报单者收到的回调,OnErrRtnOrderInsert是账户下所有会话链接都会收到的回调。
5、交易所拒单
报单通过CTP柜台风控检查报往交易所后,被交易所拒单,只回调函数OnRtnOrder,订单会被动撤单。
回报OnRtnOrder中的OrderStatus为THOST_FTDC_OST_Canceled撤单,OrderSubmitStatus为THOST_FTDC_OSS_InsertRejected报单已经被拒绝,即说明订单被交易所拒单(处于被动撤单状态),StatusMsg字段有具体的交易所拒单原因。
6、成交回报
报单回报是指每次报单状态有更新CTP发回给API用户的消息,成交回报是指每次报单有成交时CTP发回给用户的成交通知消息。如果有成交必然对应报单状态变化,所以成交回报前会先有报单回报。
CThostFtdcTradeField字段如下:
ExchangeID:交易所代码
TradeID:成交编号
Direction:买卖方向
TradeID:交易所对每一笔成交的编号,因为除郑商所外的交易所对成交时互为对手方的两笔成交编号是一样的,所以还需要加上Direction字段。
OrderRef:报单引用
OrderSysID:交易所报单编号
TraderID:交易所交易员代码
OrderLocalID:CTP本地报单编号
InstrumentID:合约代码
Direction:买卖方向
OffsetFlag:开平标志
Price:成交价格
Volume:成交数量
四、撤单
1、撤单
调用ReqOrderAction后,如果在CTP端校验通过,则会回调OnRtnOrder并提交撤单指令到交易所;如果在CTP端校验不通过,则会回调OnRspOrderAction和OnErrRtnOrderAction。如果交易所校验通过,则通过OnRtnOrder返回撤单回报;如果交易所校验不通过,则通过OnRtnOrder和OnErrRtnOrderAction返回交易所撤单拒绝回报。
virtual int ReqOrderAction(CThostFtdcInputOrderActionField * pInputOrderAction, int nRequestID) = 0;
CTP报单回报返回的OrderSysID是右对齐的,撤单填写时必须和CTP返回的一致。OrderSysID有效位为12个字节,撤单时要右对齐填写。
如果报单时OrderRef为客户自己赋值,撤单时也要同样赋值。如果报单时填空,则CTP会自动赋值,OrderRef也是右对齐的。撤单时也要原样右对齐填写。
2、错误回报
如果撤单被CTP或交易所拒单,会回调OnRspOrderAction或OnErrRtnOrderAction函数,打印回调函数中的参数pRspInfo就可以得知被拒原因。
3、正确回报
如果撤单请求通过CTP检查,返回的第1个OnRtnOrder为报单在CTP中的现状态,和上一次收到的OnRtnOrder一致。返回的第2个OnRtnOrder表明报单已经成功撤销,撤单回报中OrderSubmitStatus为THOST_FTDC_OSS_Accepted,OrderStatus为THOST_FTDC_OST_Canceled。
五、CTP查询
1、资金查询
virtual int ReqQryTradingAccount(CThostFtdcQryTradingAccountField *pQryTradingAccount, int nRequestID) = 0;
virtual void OnRspQryTradingAccount(CThostFtdcTradingAccountField *pTradingAccount, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast);
2、持仓查询
virtual int ReqQryInvestorPosition(CThostFtdcQryInvestorPositionField *pQryInvestorPosition, int nRequestID) = 0;
CThostFtdcQryInvestorPositionField无必填字段,选填字段InstrumentID,如果只想查某个合约持仓,可以只填nstrumentID字段;如果不填任何字段,则查询当前账户的所有持仓记录。
CThostFtdcQryInvestorPositionField qrypositionfield = {0};
pTradeApi->ReqQryInvestorPosition(&qrypositionfield, 0);
持仓查询响应函数:
void CFtdcTradeSpi::OnRspQryInvestorPosition(CThostFtdcInvestorPositionField *pInvestorPosition, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
账户下持仓数据不会一次全部返回,而是分批返回,开发者需要根据多空方向分别做汇总计算。
返回结果在第一个参数中pInvestorPosition中,参数类型为CThostFtdcInvestorPositionField,有6个字段可以区分唯一一条持仓记录,作为持仓记录键值(Key)。
InstrumentID:合约代码
BrokerID:经纪公司代码
InvestorID:投资者代码
PosiDirection:持仓多空方向,PosiDirection是枚举值,'2'表示多头持仓;'3'表示空头持仓,同一合约的不同方向的持仓在CTP是通过不同的记录来返回的。
HedgeFlag:投机套保标志
PositionDate:持仓日期,‘1’表示当前交易日持仓;‘2’表示是历史仓(昨仓)。
上期所、能源中心将今仓和昨仓分为两条记录,根据PositionDate可以区分出今仓和昨仓,根据PosiDirection区分空头和多头持仓。
其它期货交易所的今仓昨仓在一条记录,根据PosiDirection区分空头和多头持仓。
Position:当前键值下的总持仓,随着客户开仓平仓而变动
TodayPosition:当前键值下的今日新持仓数量,随着客户开仓平今而变动
YdPosition:当前键值下的昨仓,是一个静态值,为当前交易日开始时合约的历史仓位,不会随着客户平昨仓而减小;当前实际昨仓为Position-TodayPosition。
LongFrozen:多头冻结
ShortFrozen:空头冻结
冻结是指已报单但未成交的数量,对于多头持仓,多头冻结是指新开多头未成交的数量,空头冻结是指平多头仓位报单未成交的数量;空头持仓与之相反。
3、委托查询
virtual int ReqQryOrder(CThostFtdcQryOrderField *pQryOrder, int nRequestID) = 0;
virtual void OnRspQryOrder(CThostFtdcOrderField *pOrder, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast);
4、成交查询
virtual int ReqQryTrade(CThostFtdcQryTradeField *pQryTrade, int nRequestID) = 0;
virtual void OnRspQryTrade(CThostFtdcTradeField *pTrade, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast);