做国内期货对接,几乎绕不开 CTP。这套由上海期货信息技术有限公司(上期技术)开发的系统装在几乎每一家期货公司的机房里,客户程序用 ThostFtdcTraderApi 连的就是它。它是理解"期货公司系统长什么样"的最好样本,因为它的架构选择非常经典:无状态接入层 + 有状态核心 + 日终结算旁路。
三层:交易、结算、风控
CTP 官方把系统切成三层。
交易层(交易前置 Front + 交易核心 TServer)面向客户和交易员。前置只做连接管理、协议编解码、流量控制,把请求路由给核心;核心是订单、持仓、资金的唯一权威,维护每张报单的状态机,并通过报盘进程把订单送到交易所。注意 CTP 自己不做撮合——撮合永远在交易所,柜台核心是订单状态的搬运工和保管员。
结算层(结算系统)日终跑批量:接收交易所结算文件、算盈亏和保证金、生成客户账单。交易时段内它基本不工作,只做核心的热备。
风控层分两块:事前检查(资金、持仓、限仓、自成交)嵌在交易核心的报单路径里,毫秒级拦截;独立的监控程序做盘中实时盯市和异常告警。
前置为什么必须无状态
一家期货公司几千个客户并发连接,前置单机撑不住,必须横向扩容多实例。横向扩容的前提是任何前置都能接管任何连接:前置本身不保存订单状态,客户会话的快照(订单、持仓、成交)全部由核心通过私有流重放。客户端断线后换一台前置重新登录,照样从断点续上。
这个设计和交易所前置是同一个思路(《CTP 与交易所前置的链路》篇展开)。代价是核心成了单点:所有写路径汇聚到一组互备进程。这是用可用性换一致性——订单状态机的权威只能有一个,否则同一客户从两台前置同时撤单,状态就会乱序。CTPmini(CTP2)的演进也保持这个模型不动,只在核心进程内部把吞吐做上去。
FIB:进程间的消息总线
前置与核心之间、核心与结算之间通过 FIB(Futures Information Bus)通信,支持发布订阅和请求响应两种模式。客户登录后订阅自己的私有流,行情走公共流。所有消息带单调递增的序列号,这是断线续传的锚点:重连时客户端上报最后收到的序号,服务端把缺口重放,应用层只需保证处理幂等。
API 的两个类和回调模型
客户端 API 分两个类:ThostFtdcTraderApi(交易,请求/回报)和 ThostFtdcMdApi(行情,订阅/推送)。调用模式都是"注册回调 → 发起 Req 请求 → 异步收 OnRsp/OnRtn"。新手有两个必踩的坑:
- 请求和回报不在一个线程:Rsp 和 Rtn 由 API 内部回调线程触发,业务代码要自己保证线程安全。标准做法是把回报塞进队列,由业务线程统一消费;
- 到达序不等于因果序:网络抖动时 OnRtnOrder 可能先于 OnRspOrderInsert 到达,处理逻辑要以订单状态机的合法迁移为准,而不是假设时序。
另外提一句版本现状:CTP1(老版,求稳)和 CTPmini(CTP2,性能优化版)目前多数期货公司并行运行,客户可以选连哪套。对开发者这意味着网关要做两套连接配置,但两边 API 接口基本一致,业务代码差异不大。
这套取舍为什么能统治行业十几年
CTP 靠的不是性能数字,是架构上的克制:核心保持单写权威、前置无状态可水平扩、结算与交易彻底分离、所有异步消息带序号可重放。它回答了分布式系统里最难的那道题——在必须强一致的钱和仓上,宁可牺牲一点可用性。后来很多自研柜台原样照搬这套结构,换个名字而已。