4、交易接口协议:FIX协议详解(会话层、应用层)、国内CTP接口协议、上交所/深交所交易接口差异
做量化交易,说白了就是跟交易所「对话」。但交易所不是人,它只认特定的「语言」和「规矩」。今天我们就来聊聊这些规矩——也就是交易接口协议。
我个人习惯把接口协议分成两类:国际通用的FIX协议,和咱们国内特色的CTP、沪深交易所接口。它们各有各的脾气,搞不清楚的话,你的交易系统可能连门都进不去。
4.1 FIX协议:国际通用的「普通话」
FIX(Financial Information eXchange)协议,是国际金融圈用得最广的协议。我最早接触它是在做美股交易系统的时候,当时觉得这玩意儿真啰嗦,后来才发现它设计得其实很巧妙。
4.1.1 会话层:怎么「握手」和「告别」
FIX协议分两层:会话层和应用层。会话层管的是「怎么建立连接、怎么维持心跳、怎么断开」。你想想看,两个系统之间要传几万笔订单,总得有个规矩吧?
会话层的核心是「登录-心跳-登出」三步走:
- 登录(Logon):客户端发一个35=A的消息,带上用户名、密码、目标ID。服务端回一个35=A表示同意。
- 心跳(Heartbeat):每隔一段时间(比如30秒)发一个0号消息。如果连续几次没收到,就认为连接断了。
- 登出(Logout):发35=5的消息,礼貌地说再见。
嗯,这里要注意:心跳间隔不能太短,否则网络压力大;也不能太长,否则断线了都不知道。我建议设成30秒,这是行业惯例。
关键字段速查表:
| 标签号 | 字段名 | 说明 |
|---|---|---|
| 35 | MsgType | 消息类型(0=心跳,5=登出,8=执行报告,D=新订单) |
| 49 | SenderCompID | 发送方ID |
| 56 | TargetCompID | 接收方ID |
| 34 | MsgSeqNum | 消息序列号(必须连续递增) |
| 52 | SendingTime | 发送时间 |
4.1.2 应用层:订单怎么「说」
应用层管的是业务逻辑——下单、撤单、查成交。说白了,就是告诉交易所「我要买100股茅台,价格1800」。
一个典型的FIX新订单消息长这样:
8=FIX.4.2|9=78|35=D|49=CLIENT1|56=EXCHANGE|34=3|52=20250315-10:30:00.000|
11=ORD12345|21=1|55=600519|54=1|38=100|44=1800.00|40=2|59=1|10=123|
我来拆解一下:
- 35=D:新订单消息
- 11=ORD12345:客户端订单ID(必须唯一)
- 55=600519:股票代码(贵州茅台)
- 54=1:买卖方向(1=买,2=卖)
- 38=100:数量
- 44=1800.00:价格
- 40=2:订单类型(2=限价单)
- 59=1:有效期(1=当日有效)
我曾经在项目中遇到过一个问题:序列号(34号字段)没有严格递增,结果被交易所直接拒掉了。FIX协议对序列号要求很严,你发完第3条消息,下一条必须是4,不能跳号。
避坑指南:我曾经因为序列号重置问题,导致整个交易链路中断。后来我养成了一个习惯——每次登录成功后,把序列号从1开始重新计数,并且用本地文件持久化存储。这样即使程序重启,序列号也不会乱。
4.2 国内CTP接口协议:期货市场的「方言」
CTP(Comprehensive Transaction Platform)是上海期货信息技术有限公司开发的,国内期货市场几乎都在用。它跟FIX不一样,用的是二进制协议,效率更高。
CTP的核心特点:
- 二进制编码:比FIX的文本格式更紧凑,解析速度更快
- 请求-响应模式:每个请求都有对应的响应,不像FIX那样异步
- 强类型定义:字段类型固定,比如价格用double,数量用int
CTP的接口调用方式很直接。比如下单:
CThostFtdcInputOrderField order;
memset(&order, 0, sizeof(order));
strcpy(order.BrokerID, "9999");
strcpy(order.InvestorID, "100001");
strcpy(order.InstrumentID, "rb2505");
order.Direction = THOST_FTDC_D_Buy;
order.CombOffsetFlag[0] = THOST_FTDC_OF_Open;
order.LimitPrice = 3500.0;
order.VolumeTotalOriginal = 10;
order.OrderPriceType = THOST_FTDC_OPT_LimitPrice;
// 调用下单接口
int ret = pTradeApi->ReqOrderInsert(&order, ++nRequestID);
你看,CTP的接口设计得很「C++风格」——先填充结构体,再调用API。我个人觉得这种方式比FIX更直观,但灵活性差一些。比如你想加一个自定义字段,CTP就不支持。
注意:CTP的API是线程不安全的。我曾经在项目中同时开了两个线程调用ReqOrderInsert,结果导致程序崩溃。后来我加了一个互斥锁,才解决了问题。记住:所有API调用必须在同一个线程里,或者加锁保护。
4.3 上交所/深交所交易接口差异
国内股票市场,上交所和深交所的接口协议也不一样。虽然它们都基于TCP,但细节上差别很大。
我整理了一个对比表,方便你快速了解:
| 对比项 | 上交所(STEP) | 深交所(SZSE) |
|---|---|---|
| 协议基础 | 基于FIX 4.4,叫STEP协议 | 自定义二进制协议 |
| 消息格式 | 文本格式,类似FIX | 二进制格式,更紧凑 |
| 登录方式 | FIX标准的Logon消息 | 专有的登录请求包 |
| 订单类型 | 支持FIX标准订单类型 | 支持限价、市价、FOK等 |
| 行情推送 | 独立的行情通道 | 交易和行情共用通道 |
| 会话管理 | 严格的心跳机制 | 相对宽松 |
具体来说,上交所的STEP协议跟FIX很像,但做了一些本地化改造。比如它增加了「交易所代码」字段,用来区分上海和深圳。而深交所的协议更「原生态」,直接用二进制包,解析起来稍微麻烦一点。
举个例子,上交所的订单消息:
8=FIX.4.4|9=120|35=D|49=001|56=SSE|34=5|52=20250315-10:30:00|
11=CLT001|55=600000|54=1|38=1000|44=10.50|40=2|59=1|453=1|448=001|447=D|452=1|10=234|
深交所的订单消息(二进制格式,用十六进制表示):
02 00 00 00 // 消息长度
01 00 // 消息类型(新订单)
00 01 // 会话ID
00 00 00 05 // 序列号
00 00 00 01 // 客户端ID
00 00 00 64 // 股票代码(100=浦发银行)
00 01 // 买卖方向(买)
00 00 03 E8 // 数量(1000)
00 00 00 0A // 价格(10.0,放大100倍)
你看,深交所的协议更「硬核」,每个字段都是固定长度、固定位置。解析的时候不能像FIX那样用「标签=值」的方式,得按字节偏移量来读。
我的建议:如果你同时对接上交所和深交所,最好封装一个统一的接口层。把STEP和二进制协议都转换成内部通用的数据结构。这样上层业务代码就不用关心底层协议差异了。我在做量化平台时就是这么干的,省了不少事。
4.4 协议选择与实战建议
说了这么多,到底该选哪个协议?
- 做国际市场:FIX是标配,几乎所有海外券商都支持
- 做国内期货:CTP是主流,效率高,但学习曲线陡
- 做国内股票:上交所用STEP,深交所用二进制协议,都得会
我个人习惯是:先搞清楚交易所支持什么协议,再决定技术方案。不要为了炫技而强行用FIX,也不要因为CTP效率高就盲目选它。适合的才是最好的。
最后分享一个经验:不管用哪个协议,一定要做好日志记录。我曾经因为日志没打全,排查一个订单丢失问题花了整整两天。后来我养成了习惯——每个发送的消息都记录原始报文,每个接收的消息也记录。这样出了问题,直接翻日志就能定位。
避坑指南:我曾经在对接深交所时,因为字节序(大端/小端)搞反了,导致解析出来的价格全是错的。后来我写了一个单元测试,用已知的报文数据做验证,才发现了问题。记住:二进制协议一定要做端到端的测试,别信文档。
这张图把今天讲的内容串起来了。你可以看到,FIX、CTP、沪深交易所各有各的定位和特点。做量化交易系统,这些协议都得懂一点,但不用全部精通。根据你的业务场景,选一个主攻方向就行。