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线程的频繁占用,影响交易报单回推数据的接收。

下节会用代码实现报单操作,以及收到报单回推数据的字段分析,结合柜台系统的架构,详细拆解报单状态的流转,为后续的仓位管理做铺垫。

  • 46
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值