CTP-API开发系列之六:交易登录及查询流程
今天开始分享程序代码,主要以python语言为主。关于CTP-API的开发,前面进行了一些铺垫,不了解的朋友可以先看一下之前的文章,欢迎大家继续关注并订阅。
前情回顾
CTP-API开发系列之一:各版本更新说明(持续更新)
CTP-API开发系列之二:问题汇总(持续更新)
CTP-API开发系列之三:柜台系统简介
CTP-API开发系列之四:接口对接准备
CTP-API开发系列之五:SimNow环境介绍
全局配置参数
配置字段说明见之前的文章:CTP-API开发系列之五:SimNow环境介绍
交易初始化流程
def init_tradeapi():
"初始化交易api"
tradeapi = api.CThostFtdcTraderApi_CreateFtdcTraderApi("logs//trade_con//")
log.info("1.CreateFtdcTraderApi:" + tradeapi.GetApiVersion())
tradespi = CTradeSpi(tradeapi)
log.info("2.RegisterFront:" + TradeFrontAddr)
tradeapi.RegisterFront(TradeFrontAddr)
log.info("3.RegisterSpi")
tradeapi.RegisterSpi(tradespi)
log.info("4.SubscribePrivateTopic")
tradeapi.SubscribePrivateTopic(api.THOST_TERT_QUICK)
log.info("5.SubscribePublicTopic")
tradeapi.SubscribePublicTopic(api.THOST_TERT_NONE)
log.info("6.Init")
tradeapi.Init()
log.info("7.Join")
tradeapi.Join()
7步初始化交易api
1.创建交易api,参数"logs//trade_con//"指定flow目录,用于存放生成的.con文件,不传默认存放到当前路径。
注意:如果指定了路径,必须提前创建好文件夹
2.设置交易托管系统(期货公司柜台系统)的网络通讯地址,可以注册一个或多个地址。
如果注册多个,会以最先建立TCP连接的地址作为当前连接地址进行连接,建立的连接如果中断,api会自动尝试重连。
3.注册一个派生自 CThostFtdcTraderSpi 接口类的实例,该实例将完成事件处理。
4.订阅私有流。该方法要在Init 方法前调用。若不调用则不会收到私有流的数据。推荐使用THOSTTERTRESTART方式订阅私有流。
THOST_TERT_RESTART:从本交易日开始重传(推荐)
THOST_TERT_RESUME:从上次收到的续传
THOST_TERT_QUICK:只传送登录后私有流的内容
5.订阅公共流。该方法要在Init 方法前调用。若不调用,默认RESTART模式订阅。
THOST_TERT_RESTART:从本交易日开始重传
THOST_TERT_RESUME:从上次收到的续传
THOST_TERT_QUICK:只传送登录后私有流的内容
THOST_TERT_NONE:取消订阅公有流
6.初始化运行环境,只有调用后,接口才开始发起前置的连接请求,也就是第2步设置的地址,连接建立成功后会回调 OnFrontConnected 函数,认证动作就是在该函数中进行。
7.等待一个接口实例线程的结束。
注:上述2,3,4,5步没有先后顺序之分,但必须在6,7步之前
订阅私有流注意事项
1.订阅私有流主要涉及交易、行权、银期等操作的推送通知。比如在A设备报的单,这个单子的状态变化会主动推送到该投资者目前所有在线的设备上。涉及的常用函数有:
/// 在该文件中 ThostFtdcTraderApi.h ,以OnRtn开头的函数
///报单通知
virtual void OnRtnOrder(CThostFtdcOrderField *pOrder) {};
///成交通知
virtual void OnRtnTrade(CThostFtdcTradeField *pTrade) {};
///执行宣告通知
virtual void OnRtnExecOrder(CThostFtdcExecOrderField *pExecOrder) {};
///期权自对冲通知
virtual void OnRtnOptionSelfClose(CThostFtdcOptionSelfCloseField *pOptionSelfClose) {};
///期货发起银行资金转期货通知
virtual void OnRtnFromBankToFutureByFuture(CThostFtdcRspTransferField *pRspTransfer) {};
///银行发起期货资金转银行通知
virtual void OnRtnFromFutureToBankByBank(CThostFtdcRspTransferField *pRspTransfer) {};
订阅公共流注意事项
1.公共流主要涉及合约交易状态通知:
/// 在该文件中 ThostFtdcTraderApi.h
///合约交易状态通知
virtual void OnRtnInstrumentStatus(CThostFtdcInstrumentStatusField *pInstrumentStatus) {};
2.合约的交易状态有以下几种:
/// 在该文件中 ThostFtdcUserApiDataType.h
/
///TFtdcInstrumentStatusType是一个合约交易状态类型
/
///开盘前
#define THOST_FTDC_IS_BeforeTrading '0'
///非交易
#define THOST_FTDC_IS_NoTrading '1'
///连续交易
#define THOST_FTDC_IS_Continous '2'
///集合竞价报单
#define THOST_FTDC_IS_AuctionOrdering '3'
///集合竞价价格平衡
#define THOST_FTDC_IS_AuctionBalance '4'
///集合竞价撮合
#define THOST_FTDC_IS_AuctionMatch '5'
///收盘
#define THOST_FTDC_IS_Closed '6'
3.问题:如下图所示,流量飙升的几个时间点,几乎所有的合约都会推送(一个合约一个合约进行回调),这些时间点的流量压力以及自己程序的处理压力都会比较大,尤其对于中继服务的开发,应该避免这样的订阅方式,上述demo中我采用的是“取消订阅”。
4.随着大量期权合约的上市,上期技术也发流量飙升的问题,并在v6.5.1版本新增了“取消订阅”的方式。当时还引发了生产故障,很多客户终端加载合约过慢导致无法打开。
交易认证登录流程
def OnFrontConnected(self) -> "void":
log.info("OnFrontConnected tradefront")
authfield = api.CThostFtdcReqAuthenticateField()
authfield.BrokerID = BROKERID
authfield.UserID = USERID
authfield.AppID = AppID
authfield.AuthCode = AuthCode
self.tapi.ReqAuthenticate(authfield, 0)
log.info("send ReqAuthenticate: " + api_struct_serialize(authfield))
def OnRspAuthenticate(self, pRspAuthenticateField: 'CThostFtdcRspAuthenticateField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":
log.info('OnRspAuthenticate: ' + api_struct_serialize(pRspAuthenticateField))
if not pRspInfo.ErrorID:
loginfield = api.CThostFtdcReqUserLoginField()
loginfield.BrokerID = BROKERID
loginfield.UserID = USERID
loginfield.Password = PASSWORD
loginfield.UserProductInfo = "ctp_quant"
self.tapi.ReqUserLogin(loginfield, 0)
log.info("send ReqUserLogin: " + api_struct_serialize(loginfield))
OnFrontConnected
前面提到过,在与柜台系统连接建立成功后会回调该函数,然后进行认证,调用ReqAuthenticate函数。所有涉及的配置字段请参考之前的文章 CTP-API开发系列之五:SimNow环境介绍,里面有介绍关键字段。
OnRspAuthenticate
认证成功后(ErrorID == 0),填写登录相关信息,调用ReqUserLogin接口进行登录。
/// 在error.xml文件中
<error id="NONE" value="0" prompt="CTP:正确"/>
交易数据查询流程
登录成功后,就需要进行一些列的基础数据查询操作,基本就是一请求一响应的模式,下面的函数都可以在 ThostFtdcTraderApi.h 头文件中找到定义,就不一一解释了,最后会把日志截图放上来。
def OnRspUserLogin(self, pRspUserLogin: 'CThostFtdcRspUserLoginField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":
log.info("OnRspUserLogin: " + api_struct_serialize(pRspUserLogin))
qryinfofield = api.CThostFtdcQrySettlementInfoField()
qryinfofield.BrokerID = BROKERID
qryinfofield.InvestorID = USERID
qryinfofield.TradingDay = pRspUserLogin.TradingDay
self.tapi.ReqQrySettlementInfo(qryinfofield, 0) ## 请求查询投资者结算结果
log.info("send ReqQrySettlementInfo: " + api_struct_serialize(qryinfofield))
def OnRspQrySettlementInfo(self, pSettlementInfo: 'CThostFtdcSettlementInfoField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":
log.info("OnRspQrySettlementInfo: " + api_struct_serialize(pSettlementInfo))
pSettlementInfoConfirm = api.CThostFtdcSettlementInfoConfirmField()
pSettlementInfoConfirm.BrokerID = BROKERID
pSettlementInfoConfirm.InvestorID = USERID
self.tapi.ReqSettlementInfoConfirm(pSettlementInfoConfirm, 0) ## 投资者结算结果确认
log.info("send ReqSettlementInfoConfirm: " + api_struct_serialize(pSettlementInfoConfirm))
def OnRspSettlementInfoConfirm(self, pSettlementInfoConfirm: 'CThostFtdcSettlementInfoConfirmField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":
log.info("OnRspSettlementInfoConfirm: " + api_struct_serialize(pSettlementInfoConfirm))
pQryTradingAccount = api.CThostFtdcQryTradingAccountField()
pQryTradingAccount.BrokerID = BROKERID
pQryTradingAccount.InvestorID = USERID
self.tapi.ReqQryTradingAccount(pQryTradingAccount, 0) ## 请求查询资金账户
log.info("send ReqQryTradingAccount: " + api_struct_serialize(pQryTradingAccount))
def OnRspQryTradingAccount(self, pTradingAccount: 'CThostFtdcTradingAccountField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":
log.info("OnRspQryTradingAccount: " + api_struct_serialize(pTradingAccount))
pQryOrder = api.CThostFtdcQryOrderField()
pQryOrder.BrokerID = BROKERID
pQryOrder.InvestorID = USERID
self.tapi.ReqQryOrder(pQryOrder, 0) ## ///请求查询报单
log.info("send ReqQryOrder: " + api_struct_serialize(pQryOrder))
def OnRspQryOrder(self, pOrder: 'CThostFtdcOrderField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":
log.info("OnRspQryOrder: " + api_struct_serialize(pOrder))
if bIsLast:
pQryTrade = api.CThostFtdcQryTradeField()
pQryTrade.BrokerID = BROKERID
pQryTrade.InvestorID = USERID
self.tapi.ReqQryTrade(pQryTrade, 0) ## ///请求查询成交
log.info("send pQryTrade: " + api_struct_serialize(pQryTrade))
def OnRspQryTrade(self, pTrade: 'CThostFtdcTradeField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":
log.info("OnRspQryTrade: " + api_struct_serialize(pTrade))
if bIsLast:
pQryInvestorPosition = api.CThostFtdcQryInvestorPositionField()
pQryInvestorPosition.BrokerID = BROKERID
pQryInvestorPosition.InvestorID = USERID
self.tapi.ReqQryInvestorPosition(pQryInvestorPosition, 0) ## ///请求查询投资者持仓
log.info("send ReqQryInvestorPosition: " + api_struct_serialize(pQryInvestorPosition))
def OnRspQryInvestorPosition(self, pInvestorPosition: 'CThostFtdcInvestorPositionField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":
log.info("OnRspQryInvestorPosition: " + api_struct_serialize(pInvestorPosition))
if bIsLast:
pQryInstrument = api.CThostFtdcQryInstrumentField()
pQryInstrument.ProductID = 'rb'
self.tapi.ReqQryInstrument(pQryInstrument, 0) ## ///请求查询合约
log.info("send ReqQryInstrument: " + api_struct_serialize(pQryInstrument))
def OnRspQryInstrument(self, pInstrument: 'CThostFtdcInstrumentField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":
# 过滤期货合约
if pInstrument.ProductClass == api.THOST_FTDC_PC_Futures:
self.subIDs.append(pInstrument.InstrumentID)
if bIsLast:
log.info("OnRspQryInstrument isLast! size:" + str(len(self.subIDs)))
log.debug(self.subIDs)
新增查询分类合约的接口(v6.5.1版本)
1.上述demo中,只查询rb品种相关的合约,此外还可以按照以下四个类别进行过滤,都不填的话查询所有合约(包括期权合约但没有组合以及非交易的合约)
2.CTP合约信息可分为可交易合约和非交易合约,非交易合约数据量占比较大。新增查询分类合约接口可依据查询请求域交易类型TradingType字段查询指定合约信息。
///请求查询分类合约:
virtual int ReqQryClassifiedInstrument(CThostFtdcQryClassifiedInstrumentField *pQryClassifiedInstrument, int nRequestID) = 0;
///请求查询分类合约响应:
virtual void OnRspQryClassifiedInstrument(CThostFtdcInstrumentField *pInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
///查询分类合约
struct CThostFtdcQryClassifiedInstrumentField
{
///合约代码
TThostFtdcInstrumentIDType InstrumentID;
///交易所代码
TThostFtdcExchangeIDType ExchangeID;
///合约在交易所的代码
TThostFtdcExchangeInstIDType ExchangeInstID;
///产品代码
TThostFtdcInstrumentIDType ProductID;
///合约交易状态
TThostFtdcTradingTypeType TradingType;
///合约分类类型
TThostFtdcClassTypeType ClassType;
};
/// 其中合约类型TradingType的枚举值为:
///所有状态
#define TD_ALL '0'
///交易
#define TD_TRADE '1'
///非交易
#define TD_UNTRADE '2'
/// 分类类型ClassType的枚举值为:
///所有合约
#define INS_ALL '0'
///期货、即期、期转现、Tas、金属指数合约
#define INS_FUTURE '1'
///期货期权、现货期权合约
#define INS_OPTION '2'
///组合合约
#define INS_COMB '3' //对应产品类型字段Productclass 为组合类型
日志截图
为了方便对结构体字段、值进行分析,实现了一个api_struct_serialize函数,将ctp-api中的结构体及值以冒号逗号的形式进行输出。
下节预告
交易认证登录成功了,一些基础数据也查询到了,此外还有合约保证金率查询接口和合约手续费率查询接口,这也是比较慢的两个接口。
后面会分享一些实盘基础数据查询与存储的方法,总的思路就是盘前可以慢慢查,盘中避免查;盘中服务如果重启,则使用本来存储的数据,避免API线程的频繁占用,影响交易报单回推数据的接收。
下节会用代码实现报单操作,以及收到报单回推数据的字段分析,结合柜台系统的架构,详细拆解报单状态的流转,为后续的仓位管理做铺垫。