CTP-API开发系列之八:报撤单代码实现
- CTP-API开发系列之八:报撤单代码实现
- 前情回顾
- 函数实现
- 缓存FrontID 和 SessionID
- 报单函数实现
- 撤单函数实现
- 调用示例
- 报单(形成挂单)
- 对挂单进行撤单
- 报单(立即成交)
- 注意事项
- 委托、成交映射关系
- 分笔成交情况
- 日志截图
- 下节预告
CTP-API开发系列之八:报撤单代码实现
前情回顾
在前面的章节,分享了交易API认证登录以及基础数据查询的代码流程(CTP-API开发系列之六:交易登录及查询流程),并介绍了报撤单及回报顺序(CTP-API开发系列之七:报撤单及回报顺序),这节继续分享报单、撤单相关的代码实现。
函数实现
在之前代码基础上,新增以下类成员变量:
# 登录成功,从响应结构体中获取,用于报单跟踪
FrontID = 0
SessionID = 0
# 报单引用编号:递增,使用时需转成字符串
MyOrderRefInt = 1000# key: str(FrontID) + "_" + str(SessionID) + "_" + OrderRef
# value: CThostFtdcInputOrderField
MyOrders = {}
缓存FrontID 和 SessionID
def OnRspUserLogin(self, pRspUserLogin: 'CThostFtdcRspUserLoginField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":log.info("OnRspUserLogin: " + api_struct_serialize(pRspUserLogin))if not pRspInfo.ErrorID:# 登录成功,从响应结构体中获取,用于报单跟踪self.FrontID = pRspUserLogin.FrontIDself.SessionID = pRspUserLogin.SessionID
报单函数实现
### 具体枚举值见CTP-API头文件ThostFtdcUserApiDataType.h
def ReqorderfieldInsert(self, InstrumentID, EXCHANGEID, DIRECTION, OFFSET, PRICE, VOLUME):"""报单操作"""orderfield = api.CThostFtdcInputOrderField()orderfield.BrokerID = BROKERIDorderfield.UserID = USERIDorderfield.InvestorID = USERIDorderfield.OrderRef = self.getOrderRef() # 报单编号:从1000递增,使用时需转成字符串orderfield.ExchangeID = EXCHANGEIDorderfield.InstrumentID = InstrumentIDorderfield.Direction = DIRECTIONorderfield.CombOffsetFlag = OFFSETorderfield.LimitPrice = PRICEorderfield.OrderPriceType = api.THOST_FTDC_OPT_LimitPrice # 限价orderfield.VolumeTotalOriginal = VOLUMEorderfield.ContingentCondition = api.THOST_FTDC_CC_Immediatelyorderfield.TimeCondition = api.THOST_FTDC_TC_GFDorderfield.VolumeCondition = api.THOST_FTDC_VC_AVorderfield.CombHedgeFlag = api.THOST_FTDC_HF_Speculationorderfield.ForceCloseReason = api.THOST_FTDC_FCC_NotForceCloseorderfield.IsAutoSuspend = 0# 本地报单记录key = f"{self.FrontID}_{self.SessionID}_{orderfield.OrderRef}"self.MyOrders[key] = orderfieldself.tapi.ReqOrderInsert(orderfield, 0)log.info("send ReqOrderInsert key: " + key)log.info("send ReqOrderInsert: " + api_struct_serialize(orderfield))
撤单函数实现
撤单有两种方式,官方文档推荐使用第一种
- 第一种方法,使用OrderSysID撤单
- 第二种方法,使用FrontID+SessionID+OrderRef撤单
def ReqorderfieldAction(self,OrderSysID,ExchangeID,InstrumentID):"""撤单操作(第一种方式OrderSysID)"""orderfield = api.CThostFtdcInputOrderActionField()orderfield.BrokerID = BROKERIDorderfield.UserID = USERIDorderfield.InvestorID = USERIDorderfield.OrderSysID = OrderSysIDorderfield.ExchangeID = ExchangeIDorderfield.InstrumentID = InstrumentIDorderfield.ActionFlag = api.THOST_FTDC_AF_Deleteself.tapi.ReqOrderAction(orderfield, 0)log.info('send ReqOrderAction: ' + api_struct_serialize(orderfield))
调用示例
报单(形成挂单)
在 OnRspQryInstrument 合约查询回调函数中,合约查询完成后调用(bIsLast==True)
# rb2405 合约:买入开仓 3手 委托价格3600(低于最新价形成挂单,用于撤单)
self.ReqorderfieldInsert("rb2405", "SHFE", api.THOST_FTDC_DEN_Buy, api.THOST_FTDC_OF_Open, 3600, 3)
对挂单进行撤单
在 OnRtnOrder 报单回报函数中,对上述产生的挂单-“未成交”状态的单子进行撤单操作
def OnRtnOrder(self, pOrder: 'CThostFtdcOrderField') -> "void":log.info("OnRtnOrder: " + api_struct_serialize(pOrder))key = f"{pOrder.FrontID}_{pOrder.SessionID}_{pOrder.OrderRef}".replace(' ', '')# 本地报单回推流if key in self.MyOrders.keys():log.info(f'local order: {key}')log.info(f' InstrumentID:{pOrder.InstrumentID},Direction:{pOrder.Direction},CombOffsetFlag:{pOrder.CombOffsetFlag},OrderSubmitStatus:{pOrder.OrderSubmitStatus},VolumeTraded:{pOrder.VolumeTraded},VolumeTotal:{pOrder.VolumeTotal},LimitPrice:{pOrder.LimitPrice},OrderStatus:{pOrder.OrderStatus},StatusMsg:{pOrder.StatusMsg}')## 如果未成交,5秒后主动撤单if pOrder.OrderStatus is api.THOST_FTDC_OST_NoTradeQueueing:thread = threading.Thread(target=self.ReqorderfieldAction, args=(pOrder.OrderSysID,pOrder.ExchangeID,pOrder.InstrumentID))thread.start()thread.join()## 如果已撤单,修改价格重新报单if pOrder.OrderStatus is api.THOST_FTDC_OST_Canceled:# rb2405 买入开仓 3手 委托价格3630(高于最新价立即成交,从OnRtnTrade可以看到最终成交价格为3628)self.ReqorderfieldInsert("rb2405", "SHFE", api.THOST_FTDC_DEN_Buy, api.THOST_FTDC_OF_Open, 3630, 3)# 外部报单回推流else:log.info('outside order: ' + key)log.info(f' InstrumentID:{pOrder.InstrumentID},Direction:{pOrder.Direction},CombOffsetFlag:{pOrder.CombOffsetFlag},OrderSubmitStatus:{pOrder.OrderSubmitStatus},VolumeTraded:{pOrder.VolumeTraded},VolumeTotal:{pOrder.VolumeTotal},LimitPrice:{pOrder.LimitPrice},OrderStatus:{pOrder.OrderStatus},StatusMsg:{pOrder.StatusMsg}')
报单(立即成交)
同样还是在 OnRtnOrder 报单回报函数中,对上述“已撤单”成功后,进行新的报单操作。
以下是simnow快期3软件上,这两笔委托单的截图,方便对照验证:
注意事项
第二笔报单价格3630,最终撮合成交的价格是3628。最终成交价格在OnRtnOrder 是没有的,需要在 OnRtnTrade 成交回报推送函数中获得
def OnRtnTrade(self, pTrade: 'CThostFtdcTradeField') -> "void":log.info("OnRtnTrade: " + api_struct_serialize(pTrade))
委托、成交映射关系
1.报单前唯一标识维护:FrontID+SessionID+OrderRef,并进行缓存
# 本地报单记录
key = f"{self.FrontID}_{self.SessionID}_{orderfield.OrderRef}"
self.MyOrders[key] = orderfield
如下日志截图中两笔单子的唯一标识分别是:“1_-172308391_1000”和“1_-172308391_1001”2.在OnRtnOrder函数中可以找到这三个字段:FrontID+SessionID+OrderRef
同时会有 ExchangeID、OrderSysID 等字段(当报单被交易所拒绝后,交易所不会分配OrderSysID,此时仍要使用第1组序号跟踪报单)3.在OnRtnOrder函数中没有第1组序号,需要使用2中的 ExchangeID、OrderSysID,跟踪报单最终的成交价(Price)
分笔成交情况
在simnow环境中不会产生分笔成交的情况,在这里我从实盘环境中,做了一个分笔成交的情况,重点需要关注OnRtnOrder函数中的OrderStatus、VolumeTraded、VolumeTotal字段的变化情况,以及OnRtnTrade函数中Volume字段的变化情况。
以下截图报了一笔10手的单子,OnRtnOrder函数回调了7次;OnRtnTrade 函数回调了4次:
/// 具体枚举值见CTP-API头文件ThostFtdcUserApiDataType.h
/
///TFtdcOrderStatusType是一个报单状态类型
/
///全部成交
#define THOST_FTDC_OST_AllTraded '0'
///部分成交还在队列中
#define THOST_FTDC_OST_PartTradedQueueing '1'
///部分成交不在队列中
#define THOST_FTDC_OST_PartTradedNotQueueing '2'
///未成交还在队列中
#define THOST_FTDC_OST_NoTradeQueueing '3'
///未成交不在队列中
#define THOST_FTDC_OST_NoTradeNotQueueing '4'
///撤单
#define THOST_FTDC_OST_Canceled '5'
///未知
#define THOST_FTDC_OST_Unknown 'a'
///尚未触发
#define THOST_FTDC_OST_NotTouched 'b'
///已触发
#define THOST_FTDC_OST_Touched 'c'
typedef char TThostFtdcOrderStatusType;/
///TFtdcOrderSubmitStatusType是一个报单提交状态类型
/
///已经提交
#define THOST_FTDC_OSS_InsertSubmitted '0'
///撤单已经提交
#define THOST_FTDC_OSS_CancelSubmitted '1'
///修改已经提交
#define THOST_FTDC_OSS_ModifySubmitted '2'
///已经接受
#define THOST_FTDC_OSS_Accepted '3'
///报单已经被拒绝
#define THOST_FTDC_OSS_InsertRejected '4'
///撤单已经被拒绝
#define THOST_FTDC_OSS_CancelRejected '5'
///改单已经被拒绝
#define THOST_FTDC_OSS_ModifyRejected '6'
typedef char TThostFtdcOrderSubmitStatusType;
日志截图
下节预告
交易相关常用的功能基本已经实现完成,上述报撤单对期权也是支持的,只需要传递不同的期权合约参数。后面会分享行权和自对冲相关业务的代码。
相比交易API的功能,行情API的功能就要简单的多了,后面在分享行情API代码的基础上,会与交易API的demo进行结合,实现一个简易的自动化交易程序(当行情满足一定条件自动进行报单,并按照设定的条件自动进行止盈止损)。