简介深交所Level2行情数据接口规范V1.11是深圳证券交易所发布的最新行情数据接口标准面向高频交易、量化交易及涨停板交易等追求精确行情的专业人士系统定义了Level2与STEP格式的行情数据获取和处理规则。文档覆盖集中竞价、盘后定价交易、期权、港股通、债券现券等复杂业务场景并明确了快照行情、逐笔委托、证券实时状态等消息的字段与频道代码对用户行情系统的扩展兼容也给出了具体要求。资源为1个PDF文件压缩包约682KB内容包含完整的接口修订历史、会话机制、行情类别定义及数据格式说明可作为行情系统开发与策略研究的一手参考资料。该规范自2013年发布后历经多轮优化更新当前已有1992人学习适合从事低频与高频量化、券商接口对接以及交易所数据研究的工程师深入研读。1. 深交所 Level2 行情接入第一步先读懂 STEP 接口规范 V1.11做量化的人第一次接深交所 Level2 行情十有八九以为拿到授权之后就是开 TCP 端口收数据结果动手才发现真正的门槛不是网络编程而是那一堆 FAST 模板、频道代码和 MDEntryType。我接过几次这套行情从裸报文到能稳定解析出逐笔委托每一步都绕不开这份《深交所 STEP 行情数据接口规范 V1.11》。它定义了行情网关 MDGW 和用户系统之间的会话机制、消息结构、频道划分以及逐笔行情重传逻辑属于那种「看着是标准文档实际是开发蓝本」的东西。本文按接入开发的顺序拆这份规范把端口参数、消息结构、重传机制和容易翻车的细节一次讲清适合准备做 L2 行情接入的量化开发、高频交易团队和信息商系统工程师。2. 协议选型与消息结构STEPFAST 这套组合到底怎么拆2.1 三层协议的分工逻辑深交所这套行情接口不是裸 TCP 报文而是遵循 STEP 1.2 和 FIX 5.0 SP2 协议规范并在上面叠加了 FAST 编码。很多第一次接触的人会被这三层绕晕其实分工很清晰STEPSecurities Trading Exchange Protocol负责会话层管登录、心跳、连接恢复这些事协议基础是 FIX 的扩展。FASTFIX Adapted for STreaming负责消息体的编码压缩把行情字段用模板方式编码减少带宽占用。应用层消息则在 STEP 消息层里封装每条消息的载荷是 FAST 编码后的二进制。所以收到一个行情包时拆包顺序是先按 STEP 消息层格式取出标准头、ChannelNo频道代码、RawDataLength 和 RawData然后再把 RawData 里的 FAST 字节做解码拿到真正可读的行情字段。2.2 FAST 模板 ID 的编码区间含义规范里明确给出了 FAST 模板 ID 的编码规则这个在做解码器初始化时直接用得上模板 ID 区间用途3000 - 3999公共消息4000 - 15999实时行情数据公共消息里包括频道心跳、重传消息、用户信息报告、快照行情频道统计、业务拒绝消息、逐笔占位消息等。实时行情数据则是市场实时状态、证券实时状态、公告、快照行情、逐笔委托和逐笔成交这些真正的高频数据。开发时模板 ID 就是分拣消息类型的依据收到 FAST 字节后先解码出 TemplateID就知道该按哪套字段映射去解析。2.3 STEP 消息层的字段拼装方式每一条完整的应用层消息外层结构是统一的 STEP 消息层。标准头、频道代码、FAST 消息体长度、RawData 消息体、标准尾。这里有一个细节值得注意RawData 里可能包含本频道的多条 FAST 编码消息而且解码每条 FAST 消息体之前都必须重置解码器的 FAST 字典前值。这个「重置字典」的要求是个高频踩坑点后面避坑章节再展开。2.4 兼容性设计的五个原则V1.11 规范在兼容性要求上做了五个明确约束这也是这套接口能在多年迭代中保持稳定的核心新增行情频道不使用的系统可以不作变更自动忽略。新增应用层消息类型不使用的系统可以不改造自动忽略。快照或逐笔行情新增行情类别MDStreamID不用的系统可自动忽略。行情快照消息新增行情条目类别MDEntryType不用的系统可自动忽略。证券实时状态消息新增开关类别SecuritySwitchType不用的系统可自动忽略。这意味着解析端在做字段映射时不能写死「认得的必须全部处理、认不得就报错」的逻辑而是要做成白名单过滤加未知字段跳过的模式。我一般会把模板字段表做成配置驱动新增类别时只加配置不改代码。3. 会话机制与端口参数9129/9130 双端口背后的接入节奏3.1 双服务端口设计实时与重传分离行情网关给用户系统提供两个服务端口实时数据端口默认为 9129重传服务端口默认为 9130。每一个端口只能建立一个 TCP/IP 连接。而且只有网络版行情网关才提供重传服务端口现场版使用单向卫星线路没有数据重传功能。这个双端口设计对系统架构的影响很直接实时连接负责全天候收流数据重传连接只在检测到消息丢失时才发起请求。两个连接的职责不能搞混也不能在同一个连接上既收实时数据又发重传申请——规范里重传请求是走单独会话的。3.2 登录参数与通信版本号建立会话时登录消息Logon的 DefaultApplVerID 字段必须填写当前版本对应的通信版本号V1.11 接口规范对应的通信版本号是 1.02。字段填错或者缺失登录基本会被网关拒绝。这块我踩过一次当时拿旧项目的登录参数直接改 IP 就接新网关报文结构全对但是版本号写的是上一版的值网关直接断开连接排查半天才定位到 DefaultApplVerID。另外行情网关和用户系统必须位于同一个安全网络中两者之间传输的数据是非加密的。所以网络层安全要靠接入方自己的内网保障做部署规划时得提前把防火墙策略和网段隔离方案想清楚。3.3 流量控制与断线重连策略行情网关和用户系统之间设有流量控制机制。如果用户系统处理速度跟不上导致网关累积的待发送消息超过设定阈值行情网关会直接断开连接。注意它不会降速、不会丢弃就是硬断开。断开之后用户系统应该立即尝试重新建立连接。规范要求在重建会话后通过应用层的消息恢复机制获取缺失的数据而不是指望会话层恢复。对于逐笔行情恢复手段就是记录号检测加重传请求对于公告则是用公告概要比对后逐个申请补传。3.4 会话建立与容错路径用户系统与行情网关之间最多可建立两个会话实时数据会话和重传服务会话。会话建立过程遵循《轻量级 STEP 会话层接口规范》。容错逻辑很直接系统故障后重新建立会话再走应用层恢复。我一般在工程上会把连接管理做成独立模块包含两个连接的状态机实时连接断开立即重连重连成功后自动发送重传请求拉取断线期间丢失的逐笔数据。重传连接断开立即重连但要等到有重传需求时再发起请求避免占用网关资源。4. 行情数据解析快照、逐笔、公告三类消息的落地要点4.1 快照行情MDStreamID 与 MDEntryType 的映射关系快照行情消息是最常用的数据频道代码为 101x 到 107x 对应不同的交易品种其中 x 取值 0-9。股票用 101x、基金 102x、可转债 103x、权证 104x、期权 105x、债券通用质押式回购 106x、债券现券交易行情 107x。另外还有深交所指数快照行情频道 10、国证指数快照行情频道 11、港股实时行情频道 5001。快照消息里每个行情条目都带 MDEntryType字段几十种比如买盘价、卖盘价、加权平均价、参考价、按盘价等。每个快照频道可能包含多个类别的行情每类行情都有自己的发布频率。解析快照的注意点在于不能假设每个快照包里的条目顺序相同必须按 MDEntryType 做分发而不是按固定偏移量取字段。下面是快照行情消息解析时的判定逻辑示意def dispatch_snapshot(entry_type: int, payload: dict): # MDStreamID 决定行情类别MDEntryType 决定行情条目 if entry_type 0: # 买盘价 handle_bid_price(payload) elif entry_type 1: # 卖盘价 handle_ask_price(payload) elif entry_type 9: # 加权平均价 handle_weighted_avg(payload) elif entry_type xi: # 参考价 handle_reference_price(payload) else: # 新增行情条目不关心则自动忽略不抛异常 log_unknown_entry(entry_type)这段逻辑的核心是未知条目只记日志不中断对应规范里「自动忽略新增行情条目类别」的兼容性要求。4.2 逐笔行情记录号跳变检测与重传请求逐笔行情是 Level2 里最有价值的增量数据分逐笔委托和逐笔成交两类。逐笔委托消息 UA201逐笔成交消息 UA202。股票、基金、可转债、权证、期权的逐笔频道分别用 201x 到 205x。每个逐笔行情消息里都带频道代码和消息记录号 ApplSeqNum。记录号在频道内从 1 开始顺序递增。收到消息后做跳变检测如果收到的记录号大于已收到的最大记录号加 1说明中间丢了消息。比如当前最大记录号是 10新收到一条记录号是 12那记录号 11 的消息就是丢了。def check_seq_gap(channel_no: int, new_seq: int, last_seq: int) - bool: # 跳变检测记录号等于或小于 last_seq 视为重复忽略 if new_seq last_seq: return False # 记录号大于 last_seq 1 说明出现缺失需重传 if new_seq last_seq 1: missing_from last_seq 1 missing_to new_seq - 1 send_resend_request(channel_no, missing_from, missing_to) return True重传请求通过 UA002 消息发送关键字段包括 ResendType1逐笔行情数据2公告信息、ChannelNo、ApplBegSeqNum、ApplEndSeqNum。ApplEndSeqNum0 时行情网关会把它设置为收到重传请求时该频道数据在内存中的最大值。每个频道的逐笔行情发送完毕后行情网关会发送一笔频道结束消息 EndOfChannel。4.3 公告消息公告概要与逐文件重传公告消息和逐笔行情不同每个公告文件都有一个唯一 ID。新到达行情网关的公告文件会推送给用户系统但建立连接之前可能丢失的公告文件只能主动申请重传。规范给出的方式是完成登录后立即申请重传公告概要。公告概要里包含所有已发布公告文件的 ID拿到概要后逐个比对发现本地缺失或版本变化的文件再按照 ID 申请重传具体公告文件。这一段流程如果做晚了可能出现盘中临时公告漏收的情况。我现在的做法是在登录成功回调里直接发一个 ResendType2、NewsID 为空的重传请求先把概要拿回来再决定后续动作。4.4 公共消息心跳、频道统计与用户信息报告公共消息里有几条容易被忽略但很影响稳定性的频道心跳 UA001每 3 秒发送一次心跳消息没有记录号。用户系统如果超过两倍心跳间隔没收到任何数据包应该判定行情网关故障断开并重连。快照行情频道统计每个快照频道会周期性上报统计数据可以用来监控频道数据完整性。用户信息报告 UA003只有信息商用户系统需要发送固定频道 5000上报当前连接的终端用户数目。券商自用系统不需要发。用户信息报告的字段包括 OrigTime 数据生成时间、VersionCode01现场版02网络版、UserNum 用户数目。如果是做信息商转发系统这个定时上报一定记得实现不然网关端会收不到用户量统计。4.5 消息流维度业务拒绝与逐笔占位V1.11 还增加了业务拒绝消息和逐笔占位消息。业务拒绝消息用于行情网关在业务层面拒绝某些请求时返回原因文本在 Text 域中返回。逐笔占位消息用于逐笔行情在某个时间点没有实际成交或委托数据时占位保证序列号连续或频道时间轴完整。解析程序遇到这两类消息不要当异常处理直接按模板 ID 分到对应逻辑分支即可。5. 接入开发避坑指南记录号跳变、字典重置与重连排查5.1 现象FAST 解码偶发乱码重启后恢复原因多条 FAST 编码消息打包在同一个 RawData 里解码器没有在每条消息前重置字典前值。FAST 协议里大量字段用 previous value 作为基准字典状态不重置会导致字段错位、数值完全错乱。解决在循环解码 RawData 中的多条消息时每次解码新消息前强制重置解码器的字典状态。代码上不要只调一次解码接口要把重置逻辑放在循环体内。def decode_fast_messages(raw_data: bytes): decoder.reset() # 必须每条消息解码前重置字典 length len(raw_data) offset 0 while offset length: template_id, fields decoder.decode(raw_data, offset) handle_message(template_id, fields) offset decoder.consumed_bytes()5.2 现象逐笔数据偶尔缺失但程序不报错原因只检查了新记录号是否大于旧记录号没有做「大于旧记录号 1」的跳变检测。如果行情网关重连后重新从记录号 N 开始发送而本地已有记录号 N那么新到的 N 会被当成重复包忽略但中间 N1、N2 的数据已经永久丢失。解决严格按规范走。收到记录号等于或小于本地最大记录号的直接忽略大于本地最大记录号 1 的立即发起重传请求补齐缺口。5.3 现象连接断开后重连但数据一直对不上原因重连成功后直接进入实时接收没有先申请断线期间的缺失数据。实时通道恢复的只是当前时刻的数据流断线期间产生的逐笔数据不会自动补发。解决重连成功后的第一件事不是等第一笔行情而是用 UA002 发起逐笔数据重传。根据本地记录号的最大值和断线时间估算范围或者用一个较大的 ApplEndSeqNum0 让网关补到当前内存最大值。5.4 现象公告文件收不到很久以后才发现缺失原因只在盘中靠推送接收公告没有在登录后主动申请公告概要。连接建立之前推送的公告文件不会重复发送一遍。解决登录成功后立即发送 ResendType2、NewsID 为空的 UA002 消息申请公告概要。收到概要后建一个本地公告 ID 列表与概要比对缺失的逐个按 NewsID 申请重传。规范的措辞是「建议完成登录动作后立即申请」我实际用下来这应该是强制项而不是建议项。5.5 现象行情网关没发数据本地却迟迟不重连原因把「没有任何数据包」和「没有行情数据」混为一谈了。频道空闲时会有独立的心跳消息只要连接还活着心跳就会持续到达。超过两倍心跳间隔即 6 秒完全没有数据才说明网关侧可能出了问题。解决监控模块以「最后收到任意消息的时间」为基准超过 6 秒无消息才触发断开和重连逻辑。不要用「最后收到行情数据的时间」做判断。5.6 现象现场版行情网关重传请求无响应原因现场版使用单向卫星通信没有地面网络回传通道因此没有重传服务端口 9130自然不支持重传。重传请求在这种情况下是无效的。解决接入前先确认自己用的是现场版还是网络版。现场版接入方案要考虑数据完整性策略一般做法是加一个备用地面网络通道交叉验证或者靠快照行情做全场校准。V1.11 规范的修订历史里多次提到两版差异接入前这一项必须先确认。6. 把说明书翻译成行情系统模板 ID、心跳监控与升级习惯规范最终要落到工程实现上才算数。我做了几套 Level2 行情接入之后沉淀下来几个固定的处理习惯分享出来供参考。模板 ID 的注册机制是解码端的地基。FAST 模板消息种类几十个不同频道同名消息还有消息层和业务层的区别。我一般把模板 ID 做成一张注册表每条消息类型绑定一个处理函数新增模板只注册不修改既有逻辑。收到一个新模板 ID 时如果注册表里没有对应处理函数就记未知消息日志但绝不能抛异常拖垮整个接收线程。这一条和规范里的兼容性要求完全一致做成机制之后后续版本升级基本不用改主流程。心跳监控的粒度建议做到频道级别而不是连接级别。每个频道每 3 秒发送一次心跳连接的健康状态其实由频道心跳兜底。连接层断了有 TCP 保证但频道静默才是真正危险的状态——连接还在数据却停了。所以我每个频道单独维护一个 last_recv_time每秒巡检一次超过 6 秒未收到该频道任何消息就告警并触发重连。这个监控成本很低但能兜住大多数网关侧的隐性故障。订阅过滤的配置化也值得提前做。频道代码区间那么多一个系统不一定全要股票逐笔、期权逐笔、港股快照、公告频道、国证指数各取所需。把订阅列表做成配置文件解析器只管按频道代码路由需要新增一个分类时只改配置不改代码。字段映射的兼容层设计是 V1.11 修订历史里反复强调的事。这些年行情规范经历了证券实时状态消息的字段替换、逐笔委托消息删除三个字段、盘后定价大宗交易更名、新增债券现券交易行情、新增债券竞买行情。这些变化如果不做前向兼容每次版本升级都要折腾一次全量回归。我现在的做法是所有 FAST 字段映射通过模板描述文件XML 或 JSON驱动解析器在运行时加载字段定义而不是在 Python 或 C 代码里硬编码字段 tag。这样新版本出来后只需要更新模板描述文件代码基本不动。最后说一个让我吃过亏的习惯。每次拿到新版本规范我先不看正文而是先翻修订历史。V1.11 相比 V1.10 增加了债券现券交易业务竞买行情相比 V1.09 增加了债券现券交易业务快照和逐笔行情、调整了债券相关行情频道、增加了逐笔占位消息。这些变更对应的是新增模板、新增频道、新增消息类型我的系统必须先知道哪个模板 ID 会变、哪个频道代码会新增再去做兼容层适配。从那以后我每次拿到新版本规范都强制自己先过一遍修订历史再逐章核对模板定义和频道说明这份 V1.11 的接入蓝本就是这样一点点啃下来的。希望帮到你。本文还有配套的精品资源点击获取