第一次接触CTP-API的开发者,往往会被各种订单状态和标识符搞得晕头转向。我清楚地记得自己早期开发时,经常遇到"明明发了订单却找不到对应成交"的情况。问题的根源在于没有理解CTP系统中不同阶段使用的不同标识符体系。
在订单生命周期中,CTP会使用多组不同的标识符来标记同一笔订单。比如在报单刚发出时使用的是RequestID,进入交易所后又会生成OrderSysID。如果只用单一标识符去跟踪,就会出现"跟丢"订单的情况。更复杂的是,成交回报(OnRtnTrade)和报单回报(OnRtnOrder)使用的标识符还不完全一致,这就导致很多开发者无法正确关联成交和原始报单。
RequestID是开发者最熟悉的标识符,它在ReqOrderInsert接口中由客户端生成。这个ID的最大特点是完全由开发者控制,你可以用自增数字、时间戳或者任何有业务意义的编号。我在项目中常用"日期+序列号"的组合,比如20230815001表示8月15日的第1笔订单。
cpp复制// 典型的使用方式
static int request_id = 0;
int GenerateRequestID() {
return ++request_id;
}
CThostFtdcInputOrderField order = {0};
//...填充其他字段
int nRequestID = GenerateRequestID();
api->ReqOrderInsert(&order, nRequestID);
但要注意,RequestID只能跟踪到OnRtnOrder层面的回报,无法用于成交回报(OnRtnTrade)的关联。这是因为CTP系统设计上成交回报中不包含RequestID字段。这也是很多新手容易踩的坑。
当订单进入CTP系统后,会由三个字段共同确定其唯一性:
这组标识符可以跟踪订单在CTP内部的全生命周期。我建议在客户端维护一个<FrontID, SessionID, OrderRef>到本地订单的映射表。当收到OnRtnOrder回调时,可以通过这三个字段快速定位到本地订单记录。
cpp复制// 订单映射表示例
std::map<std::tuple<int, int, std::string>, LocalOrder> order_map;
void OnRtnOrder(CThostFtdcOrderField *pOrder) {
auto key = std::make_tuple(pOrder->FrontID, pOrder->SessionID, pOrder->OrderRef);
if(order_map.find(key) != order_map.end()) {
// 更新本地订单状态
}
}
当订单被交易所接受后,会分配一个OrderSysID。这时就需要使用ExchangeID+OrderSysID这组标识符来跟踪订单。这组标识符的特点是:
在实际开发中,我通常会建立两套索引:一套基于FrontID+SessionID+OrderRef用于CTP层面的跟踪,另一套基于ExchangeID+OrderSysID用于交易所层面的跟踪。当收到OnRtnTrade时,只能用后者进行关联。
让我们通过一个具体案例来看标识符的使用时机。假设我们报单买入螺纹钢1手:
cpp复制CThostFtdcInputOrderField order = {0};
strcpy(order.OrderRef, "00001"); // 设置OrderRef
int nRequestID = 1001; // 设置RequestID
api->ReqOrderInsert(&order, nRequestID);
cpp复制void OnRtnOrder(CThostFtdcOrderField *pOrder) {
if(pOrder->OrderStatus == THOST_FTDC_OST_Unknown) {
// 记录FrontID、SessionID
int front_id = pOrder->FrontID;
int session_id = pOrder->SessionID;
std::string order_ref = pOrder->OrderRef;
}
}
cpp复制void OnRtnOrder(CThostFtdcOrderField *pOrder) {
if(pOrder->OrderStatus == THOST_FTDC_OST_NoTradeQueueing) {
std::string exchange_id = pOrder->ExchangeID;
std::string order_sys_id = pOrder->OrderSysID;
// 建立ExchangeID+OrderSysID索引
}
}
cpp复制void OnRtnTrade(CThostFtdcTradeField *pTrade) {
std::string exchange_id = pTrade->ExchangeID;
std::string order_sys_id = pTrade->OrderSysID;
// 通过ExchangeID+OrderSysID查找原始订单
}
撤单操作涉及另一组标识符 - OrderActionRef。在ReqOrderAction请求中需要填写:
cpp复制CThostFtdcInputOrderActionField action = {0};
action.FrontID = original_front_id;
action.SessionID = original_session_id;
strcpy(action.OrderRef, original_order_ref.c_str());
strcpy(action.OrderActionRef, GenerateOrderActionRef().c_str());
api->ReqOrderAction(&action, nRequestID);
撤单成功后,会在OnRtnOrder中收到OrderStatus为THOST_FTDC_OST_Canceled的回报。这里有个易错点:即使撤单成功,也可能先收到部分成交的OnRtnTrade,然后才收到撤单成功的OnRtnOrder。
经过多个项目的实践,我总结出一个高效的订单管理方案。核心是维护三个映射表:
cpp复制std::map<int, std::string> request_id_map;
cpp复制struct CTPOrderKey {
int front_id;
int session_id;
std::string order_ref;
// 重载<运算符用于map排序
};
std::map<CTPOrderKey, LocalOrder> ctp_order_map;
cpp复制struct ExchangeOrderKey {
std::string exchange_id;
std::string order_sys_id;
// 重载<运算符
};
std::map<ExchangeOrderKey, LocalOrder> exchange_order_map;
订单状态管理本质上是一个状态机。我推荐使用明确的枚举值来管理状态流转:
cpp复制enum class OrderState {
PENDING, // 已发送等待响应
CTP_ACCEPTED, // CTP已接受
EXCH_ACCEPTED,// 交易所已接受
PART_TRADED, // 部分成交
ALL_TRADED, // 全部成交
CANCELED, // 已撤单
REJECTED // 已拒绝
};
// 状态转换逻辑
void UpdateOrderState(LocalOrder& order, CThostFtdcOrderField* pOrder) {
switch(pOrder->OrderStatus) {
case THOST_FTDC_OST_NoTradeQueueing:
order.state = OrderState::EXCH_ACCEPTED;
break;
case THOST_FTDC_OST_PartTradedQueueing:
order.state = OrderState::PART_TRADED;
break;
// 其他状态处理...
}
}
在实际运行中,有几个常见异常需要特别注意:
针对这些问题,我的解决方案是:
cpp复制class OrderReconciler {
public:
void ProcessEvent(OrderEvent event) {
event_queue_[event.order_id].push_back(event);
TryReconcile(event.order_id);
}
private:
void TryReconcile(const std::string& order_id) {
auto& events = event_queue_[order_id];
// 实现状态协调逻辑...
}
std::map<std::string, std::deque<OrderEvent>> event_queue_;
};
当订单量很大时(比如高频交易场景),标识符查询可能成为性能瓶颈。我通常采用以下优化手段:
cpp复制#include <unordered_map>
std::unordered_map<CTPOrderKey, LocalOrder, CTPOrderKeyHash> ctp_order_map;
cpp复制class OrderPool {
public:
LocalOrder* Allocate() {
if(pool_.empty()) {
return new LocalOrder();
}
auto* order = pool_.back();
pool_.pop_back();
return order;
}
void Release(LocalOrder* order) {
pool_.push_back(order);
}
private:
std::vector<LocalOrder*> pool_;
};
cpp复制#include <shared_mutex>
std::shared_mutex order_map_mutex;
// 读操作
{
std::shared_lock lock(order_map_mutex);
auto it = order_map.find(key);
}
// 写操作
{
std::unique_lock lock(order_map_mutex);
order_map[key] = order;
}
订单跟踪系统离不开完善的日志记录。我建议记录以下关键信息:
cpp复制void LogOrderEvent(const LocalOrder& order, const std::string& event) {
std::cout << std::format("[{}] Order {} changed to {}: FrontID={}, OrderRef={}, OrderSysID={}",
GetCurrentTime(), order.id, event,
order.front_id, order.order_ref, order.order_sys_id);
}
cpp复制void SaveRawMessage(const std::string& msg_type, const void* data) {
std::ofstream fout("ctp_messages.log", std::ios::app);
fout << "[" << GetCurrentTime() << "] " << msg_type << ": "
<< ToHexString(data, sizeof(data)) << "\n";
}
cpp复制class OrderAudit {
public:
void RecordAction(const std::string& order_id,
const std::string& action,
bool success) {
audit_log_[order_id].emplace_back(action, success, GetCurrentTime());
}
private:
std::map<std::string, std::vector<AuditRecord>> audit_log_;
};
为了验证订单跟踪系统的正确性,我总结了一套模拟测试方案:
python复制# 示例测试脚本
def test_partial_then_cancel():
# 发送报单
order_id = api.insert_order(...)
# 模拟部分成交
mock.exchange_ack(order_id, qty=5)
# 发送撤单
api.cancel_order(order_id)
# 验证最终状态
assert api.get_order(order_id).status == "PART_CANCELED"
在实际项目中,我会为每个标识符关联路径编写专门的测试用例,确保所有可能的流转路径都被覆盖。