资讯动态

深交所Level-2行情接口实战:从协议解析到逐笔盘口重建

发布时间:2026/10/13 21:19:15 来源:尧图企业网站定制
简介深圳证券交易所2021年8月发布的《Level2行情数据接口规范V1.11》是量化交易、高频交易及行情系统开发人员必须掌握的技术文档。规范以STEP协议和FAST编码为基础完整定义了Level2行情快照、逐笔委托、逐笔成交等消息格式并详细说明频道代码、开关类别、业务拒绝消息以及盘后定价交易、港股通、期权备兑、债券现券等业务场景的字段约定对追求精确挂单和涨停板策略的开发者尤其关键。文档保留了2013年至2021年的完整修订历史包括2014年增加快照行情频道统计消息2016年支持港股通开关2020年加入盘后定价大宗交易2021年新增债券竞买行情等关键变更同时明确了接口兼容性要求帮助读者理解演进脉络、规避字段兼容性问题。资源包为单份PDF大小仅682KB内容为深交所官方原文适合与最新交易所文档对照查阅可用于开发解码器、验证行情网关连接及排查字段错误。该规范在CSDN已有1991人学习浏览是深交所Level2行情接入开发中的高价值参考资料。1. 深交所Level-2行情数据接口规范从读懂V1.11到跑通第一帧行情拿到一份深交所level2行情数据接口规范V1.11第一反应往往不是从头读文档而是先去找现成的代码示例。这类行情接口规范有个共同特点每个字节怎么排列、每种消息怎么定义都写得很细却几乎不提供完整可运行样例。做量化回测的人想拿逐笔委托还原盘口做行情展示的人想解析十档快照做风险监控的人想实时统计资金流向三类人读的是同一份规范落地复杂度却相差数倍。这里想按协议结构、联调路径、数据解析、高频避坑四个环节展开目标是让新手能按步骤接进来让熟手能对照排查自己的问题。2. 协议骨架先于数据字段会话管理、消息头与订阅通道2.1 把V1.11拆成三层来读链路、消息、业务读深交所level2行情数据接口规范V1.11我一般不会从第一页顺序往后翻而是先看目录把整份规范按功能拆成三层。链路层描述TCP连接怎么建立、怎么保活、断线重连有什么限制消息层定义每条消息都带有的公共头部通常包含消息类型、消息长度、序列号业务层才是真正有价值的行情数据快照、逐笔委托、逐笔成交都在这一层定义。三层拆法最大的价值是定位问题快。数据乱码先查消息头行情断流先查链路层字段值对不上再回业务层比对定义。很多新手第一遍读规范就把精力放在逐笔成交的字段清单上到真正拼报文时才发现消息头里长度字段的语义理解错了整包数据都解不开。我习惯边读边做一张消息头速查卡把消息头各字段的偏移、宽度、类型抄在一张纸上标注清楚是定长还是变长、字节序是小端还是大端。这张纸在联调阶段比规范原文翻得还频繁排查问题时能省掉大半翻文档的时间。消息头里最重要的三个字段是消息类型、消息长度和序列号。消息类型决定消息体按哪张字段表解释消息长度决定当前消息的边界是解决TCP分包和粘包的核心依据序列号用来检查消息是否丢包、是否乱序。需要注意这三个字段在V1.11里的位置、宽度和语义要以文档自身定义为准不要拿其他市场的接口经验直接套用字段宽度差一个字节解出来的数值就会完全错乱。另外要提醒一句读规范时脑子里始终带着我最终要解出哪个字段、用来做什么这个问题。目标是算实时买卖压力就重点读逐笔委托的买卖方向、价格和数量目标是画分时成交就重点读逐笔成交的成交价格和成交量。带着目标读比从头到尾抄字段效率高得多也能更快发现规范里哪些字段对你没用、哪些字段是必须的。2.2 登录、心跳与重连会话生命周期里的三个关键参数接口联调的第一步不是收行情而是登录。登录报文通常包含账号、令牌和若干协商参数校验字段的计算方式一般是账号加令牌配合约定算法生成。规范对登录失败的定义往往很细账号错误、令牌过期、并发连接数超限、IP不在白名单各有各的错误码。实际联调中遇到最多的情况是登录明明成功却收不到任何行情数据这类问题九成出在订阅环节而不是登录本身。登录报文里有一个容易看走眼的细节很多行情接口的登录响应不是立即返回而是隔一小段时间才到达。如果客户端在登录请求发出后立刻进入等待推送状态可能把稍后到达的登录响应当成未知消息丢掉导致会话状态错乱。联调时应该在登录请求发出后的几秒内保持对响应的监听把登录响应的消息类型也纳入分发逻辑不要只处理行情消息。登录之后是心跳。行情接口是长连接服务端不会无限等待一个沉默的客户端常见机制是客户端定期发送心跳包服务端在超时窗口内没收到任何字节就主动断开。心跳间隔这个参数值得认真对待设太短心跳报文挤占带宽一台机器接入多个行情源时尤其明显设太长一次网络抖动就可能超过服务端超时导致掉线。我一般把心跳间隔取为规范允许最短值的1.5到2倍同时在应用层做独立的超时统计连续几个心跳周期没有任何数据到达就主动发起重连。重连策略是经常被忽略的第三个参数。网关侧通常有连接数限制断线后立即重连很容易被判定为频繁连接而拒绝。常见做法是退避重连第一次失败等1秒第二次3秒第三次5秒之后封顶30秒。联调阶段这套策略显得多余但到了盘中连续掉线时它直接决定程序是自动恢复还是一直撞墙。参数建议取值说明心跳间隔规范允许最短值的1.52倍太短浪费带宽太长容易被中间设备清理应用层超时连续35个心跳周期用于本地主动断链不等服务端踢人重连退避1秒→3秒→5秒→封顶30秒避免触发网关的频繁连接限制2.3 订阅通道快照、逐笔委托、逐笔成交的取舍登录之后客户端需要向网关发送订阅请求网关才会推送行情。深交所Level-2的行情数据按内容可分成三类快照、逐笔委托、逐笔成交。快照是周期性生成的盘口截面相当于每隔固定时间拍一张市场状态照片逐笔委托是每一笔挂单明细逐笔成交是每一笔成交明细。规范会为这些数据类型分配一个或多个消息类型编码订阅请求里要填的正是这些编码。订阅这一步有两个容易踩的坑。第一个是账号权限不是所有Level-2账号都有权限收到全部类型数据订阅了没有权限的消息类型网关通常不会报错只会默默不推送。排查收不到逐笔时第一件事不是改代码而是确认账号权限覆盖范围。第二个坑是订阅维度按股票代码订阅和按市场全量订阅报文格式差别很大。全量订阅简单但数据量大处理不过来时消息会在客户端积压按代码订阅灵活却要维护动态代码表处理停牌、代码变更这些边缘情况。联调初期不要一上来就同时订阅全部类型先只订阅快照把一条消息的解析链路跑通再增加逐笔委托最后加逐笔成交每增加一类就验证一次解析结果。这个顺序能让问题范围缩得很小不至于三类数据同时推过来出了错都不知道该查哪条链路。V1.11对订阅请求的格式和参数范围有明确约定联调前值得把这一节反复读两遍。3. 逐笔委托与逐笔成交Level-2盘口还原的数据基础3.1 快照消息十档盘口与行情统计字段普通行情一般只有五档盘口Level-2快照的核心提升是十档买一到买十、卖一到卖十的价格和委托量都会推送。快照消息体里除了盘口还包含最新价、成交量、成交额、开高低收等行情统计字段。需要注意成交量这类字段通常是累计值从零开始累加不是每帧之间的增量。要做增量计算得自己用相邻两帧求差这个差值才对应这一帧时间间隔内的成交。快照是按周期推送的不是事件驱动。这个性质在做盘口重建时影响很大快照只是某个时刻的截面两次快照之间发生的逐笔变动最终都被下一帧快照校准。我一开始接入时就养成一个习惯把快照当基准把逐笔当增量二者不一致时以快照为准做校准这个习惯让后续的盘口还原少走了很多弯路。快照消息里还有一个必须关注的字段是行情生成时间。不同版本规范里这个时间字段的精度和语义不完全一样有的精确到毫秒有的精确到百微秒。做历史回放时如果拿客户端的本地接收时间做K线切片会引入网络延迟误差用行情自带的时间字段才是正确做法。字段精度是多少要回到V1.11的字段定义里确认不能想当然。字段类别典型内容解析注意盘口类买一至买十价量、卖一至卖十价量价格和数量的单位、特殊值编码以规范为准成交类最新价、成交量、成交额、开高低收成交量多为累计值增量需自行求差统计类加权均价、换手率等不同版本口径不同回测前先做口径核对时间类行情生成时间精度随版本变化回放对齐用该字段快照还有一个应用方向是计算资金流向。用逐笔成交按买卖方向累加可以算出主动买入、主动卖出的规模这个指标在普通Level-1行情里算不出来。但计算前要确认规范里的买卖方向字段语义有的版本用买和卖两个枚举有的版本用主动买、主动卖、未知三态语义不同统计结果会完全不同。3.2 逐笔委托与逐笔成交从字段复原每笔交易逐笔委托记录每一笔挂单的细节核心字段包括委托号、委托价格、委托数量、买卖方向、委托类型。委托号是不是全局唯一要看规范具体说明很多行情接口的委托号只在某个范围内唯一。做历史数据落库时建议直接用委托号加股票代码加日期做联合主键否则长期数据里很容易撞键后续关联查询会莫名多出重复行。逐笔成交记录每一笔成交核心字段包括成交号、成交价格、成交量、买方委托号、卖方委托号等。它把一笔成交拆成买方和卖方两个委托号天然适合还原谁和谁在什么价位成交了多少。但如果只是要算累计成交量、成交额就不要去关联委托表直接按成交价和成交量累加即可。关联两张大表是重活性能开销大而且一旦某个委托号在数据流里缺失关联结果会非常难查。还有一个细节逐笔委托和逐笔成交是两条独立数据流成交记录里的委托号与委托记录里的委托号存在引用关系但两条流未必严格同步。做跨流关联之前一定要先评估数据完整性比如先统计每条成交是否能找到对应的委托。如果完整性达不到预期就要调整方案不要硬做关联否则只会得到一份难以解释的半对账结果。3.3 盘口重建快照基准与逐笔增量的对账逻辑把Level-2数据用出价值最常见的一件事是重建盘口维护一个十档队列逐笔委托到达就更新队列逐笔成交到达就扣减对应挂单量。听起来直接实际做起来会发现快照和逐笔很难对得齐。问题出在两类数据的推送时序不同。快照是周期推送逐笔是事件推送某一笔委托可能发生在上一次快照生成之后、下一次快照生成之前也可能刚好跨过快照边界。如果拿一帧快照初始化盘口然后把之后到达的逐笔全部应用一遍盘口会持续累积偏差已经成交但未撤掉的挂单残留在盘口上。见过三种处理方案。第一种以快照为基准只应用快照之后到达的逐笔同时每隔一段固定时间用新快照强制校准把漂移纠正回来。第二种是同时缓存快照和逐笔用行情自带时间戳把两类消息对齐到同一个时间窗口再按窗口顺序重放。第三种完全不用快照只用逐笔自建盘口灵活性最高但要求逐笔数据绝对连续中间丢一条记录盘口就永久错位且无法自查。实盘环境里前两种更稳第三种适合历史数据研究场景。不管选哪种方案都要在本地维护一份已处理序列号的记录。快照和逐笔各有各的序列号空间本地状态记录到哪个位置重连续传时才能接着处理。很多重连之后数据错乱的案例本质都是没记住处理进度重连后又从新数据开始处理中间漏了一段本地状态和真实市场状态从此不一致。4. 联调最小路径从登录到解析一帧快照的完整闭环4.1 联调前要准备的四样东西第一样是一个权限覆盖目标数据的Level-2账号。所谓覆盖目标数据是指这个账号能订阅到计划解析的全部消息类型尤其是逐笔。先用权限齐全的账号把链路跑通再换受限账号验证权限控制逻辑这个顺序最省时间也能避免一开始就把问题引向权限。第二样是网关地址和端口。一份规范会对应多个服务地址行情接入、行情文件下载、状态查询用的往往是不同端口。联调时把配置整理到单独配置项里不要硬编码在代码中否则切换测试环境和真实环境时要改一堆地方还容易漏改漏配。第三样是规范文档和自制的消息头速查卡。PDF版规范用来按字段名快速检索速查卡用来在做报文解析时对照偏移量。速查卡上我会额外标注每个字段的取值范围这些信息在排查异常值时非常有用比如价格字段解出个负数能立刻联想到方向字段取值错误。第四样是能打印收发原始字节的调试工具。最低要求是在程序里加一个开关把收到的每条消息前十六个字节用十六进制打印出来。别只在出错时才加一开始就开着等链路跑通了再关掉你会少踩很多坑因为很多解析问题只有对着原始字节才能定位。联调顺序上建议先把登录和心跳跑通再订阅快照最后才加逐笔。每加一种消息类型都确认前一种没有被影响。这个顺序看起来慢实际上能省下来回排查的时间也让每一步的失败点都足够明确。4.2 登录、心跳与消息分发的最小代码骨架下面这段是最小联调框架覆盖登录后的主循环读消息头、按长度取消息体、按消息类型分发。字节序、消息头长度和字段偏移在V1.11里怎么定义就以它为准代码里的数值只是联调占位。import socket import struct # 联调最小框架登录后循环读消息头按消息类型分发 # 常量在联调时按V1.11规范的实际定义替换不要直接上线 HEADER_LEN 16 # 消息头总字节数占位值 MSG_TYPE_OFFSET 0 # 消息类型字段在头部中的偏移 MSG_LEN_OFFSET 2 # 消息长度字段在头部中的偏移 SNAPSHOT_TYPE 1 # 快照消息类型占位值 ORDER_TYPE 2 # 逐笔委托消息类型占位值 TRADE_TYPE 3 # 逐笔成交消息类型占位值 class Level2Client: def __init__(self, host, port, user, token): self.sock socket.create_connection((host, port), timeout10) self.buffer b self.user user self.token token def login(self): # 登录报文按规范逐字段拼装校验字段通常由账号令牌计算 # 注意登录响应可能在几秒后才返回主循环要能处理它 pass def subscribe(self, codes): # 订阅报文核心是消息类型编码列表权限不足时网关会静默丢弃 pass def _recv_exact(self, n): # 关键点先收满 n 个字节解决TCP粘包和半包 while len(self.buffer) n: chunk self.sock.recv(65536) if not chunk: raise ConnectionError(连接已断开需要进入重连流程) self.buffer chunk data, self.buffer self.buffer[:n], self.buffer[n:] return data def next_message(self): header self._recv_exact(HEADER_LEN) # H 表示小端序读16位无符号整数字节序以规范为准 msg_type struct.unpack_from(H, header, MSG_TYPE_OFFSET)[0] msg_len struct.unpack_from(H, header, MSG_LEN_OFFSET)[0] body self._recv_exact(msg_len - HEADER_LEN) return msg_type, body def handle_snapshot(self, body): pass def handle_order(self, body): pass def handle_trade(self, body): pass def run(self): while True: msg_type, body self.next_message() if msg_type SNAPSHOT_TYPE: self.handle_snapshot(body) elif msg_type ORDER_TYPE: self.handle_order(body) elif msg_type TRADE_TYPE: self.handle_trade(body) # 未定义类型不要直接丢弃先打印头部字节再决定逻辑说明_recv_exact是这套框架的核心它保证每次调用都取到完整指定长度的字节把TCP粘包和半包问题挡在业务逻辑之外。struct.unpack_from按规范声明的字节序和宽度从消息头固定偏移处解出消息类型和消息长度再用消息长度减去消息头长度精确知道消息体有多长。参数说明HEADER_LEN、MSG_TYPE_OFFSET、MSG_LEN_OFFSET三个常量联调时必须改成V1.11里的实际值。字节序用小端还是大端不要凭经验猜直接看规范说明。登录和订阅函数先留空实现但主循环的分发逻辑要先把未知消息类型打印出来联调时很多诡异问题都是靠这一行打印定位的。4.3 快照与逐笔解析的三个差异点快照和逐笔在解析方式上有三处明显差异处理不好会埋下隐患。第一处是消息体结构快照通常是单条大消息字段多、定长字段为主逐笔则是一条消息里包含多条记录需要先从消息体里读出记录条数再循环解析每条记录。记录条数的计算方式在规范里有明确约定通常是消息体长度除以单条记录长度也可能有单独的条数字段。第二处差异在更新方式。快照消息到达后本地十档盘口整体替换逐笔消息到达后是逐条增量更新。增量更新对消息的连续性和顺序要求极高一旦漏了一条委托本地盘口就再也回不到真实状态。相比之下快照随时能把盘口纠正回来这就是前面反复强调快照是基准的原因。第三处差异是性能边界。逐笔数据量远大于快照一条逐笔消息里可能包含几十条记录盘中高峰期的消息速率会很高。如果解析逻辑落在接收线程里很容易出现积压。常见做法是接收线程只做分包和字节拷贝把解析和业务处理丢给独立线程池两者之间用有界队列衔接队列满了就记录丢弃并预警。对比维度快照逐笔委托/逐笔成交消息体结构单条大消息字段多一条消息含N条记录本地更新方式整体覆盖逐条增量解析重心字段偏移和填充字节记录条数循环与内存复用性能瓶颈低频压力小高频需异步处理5. Level-2接口落地避坑五个高频问题与排查顺序联调Level-2接口的过程本质上是在跟字节序、时序和权限做斗争。以下五条是实践中反复出现的问题每条按现象、原因、解决的顺序写排查时建议按这个顺序来能少走很多弯路。5.1 登录成功却收不到任何行情现象登录报文返回成功心跳保持正常但行情通道完全静默连一帧快照都收不到。原因九成是订阅环节的问题。订阅请求没发出去、订阅的消息类型编码填错、或者账号没有对应数据类型的权限。网关对无权限的订阅通常不报错只是不推送这让问题更难定位。解决先在代码里确认订阅请求确实发到了网关上。再把订阅范围改成全市场、全类型排除股票代码维度的问题。最后联系行情服务提供方确认账号权限这一步最容易被忽略也最常是根因。5.2 盘中行情断流重连后又恢复现象程序运行几十分钟或几个小时后行情停止推送应用层没有任何报错。手动重启连接后恢复正常但过一阵又断。原因中间网络设备的空闲超时把长时间没有字节流动的连接回收了。很多行情网关的心跳只作用于控制连接行情数据通道没有独立心跳低频时段没有行情可推连接就被设备判定为空闲并清理。解决缩短心跳间隔让连接始终有字节在流动同时在应用层做独立超时检测连续几个心跳周期收不到任何字节主动断开重连。断流后的续传要处理好顺序先重连、再订阅、然后等待下一帧快照校准本地状态不要一恢复就处理增量。5.3 解析出来的字段值明显异常现象个别价格字段解出来是负数或者天文数字时间字段完全不对字符串字段乱码。这类问题查出来之前像玄学查出来之后只能怪自己没先翻规范。原因最常见的是字节序反了小端数据按大端解或者反过来数值会完全错乱。其次是消息头长度字段理解错导致消息体多读或少读后续所有字段偏移全错位。还有一种情况是把规范里的保留字段当成了业务字段。解决抓一帧已知内容的原始字节手工按规范逐字段拆解和程序解析结果对比。不要用模拟数据测直接用网关推送的真实报文。排查时先确认消息头三个字段解对了再往下看业务字段。5.4 逐笔和快照数量对不上现象同一只股票把全天逐笔成交的成交量累加起来和快照里的总成交量不一致差出不少。原因两类数据的推送时序不同快照是周期截面逐笔是事件流。如果拿本地接收时间做归类统计网络延迟会直接把一部分逐笔划到错误的快照区间里两个数字自然对不上。另外逐笔数据若在采集或传输中丢失累计值也会偏小。解决统一用行情自带的时间字段做统计不要用本地接收时间。用一个可复现的时段做交叉验证取某只股票某个交易日的数据按行情时间重放逐笔累计成交量应该与收盘后快照总成交量基本一致。差异超过合理范围就说明有丢包需要检查采集链路。5.5 程序跑一会儿后延迟越来越大现象刚启动时行情延迟只有几毫秒运行一段时间后延迟涨到几十毫秒甚至秒级消息积压越来越严重。原因接收线程里做了太多事情解析、落库、推送任何一个环节变慢都会拖垮接收。逐笔高峰期消息量很大单线程处理不过来队列越积越长。另一种隐蔽原因是日志打印过多在盘中高频行情下每条消息打一行日志都会成为性能黑洞。解决接收线程只负责读字节和按长度分包解析放到独立线程落库用批量写入下游不消费就丢弃并告警。日志联调时全开实盘直接降到错误级别这个习惯能让延迟问题减少一半。6. 验证解析正确性用行情自洽性做回归测试的两个技巧6.1 用全天累计值做交叉验证验证解析是否正确不需要去对什么外部参考值行情数据自身就是自洽的。拿一只股票一个交易日的数据把逐笔成交的成交量、成交额分别累加再和当天最后一帧快照里的累计成交量、成交额对比。差异如果在一个很小的比例内说明逐笔解析和快照解析基本都对了如果对不上先检查是不是有丢包再检查字段位宽和时间精度。这个验证最好做成独立校验脚本每次改完解析代码都跑一遍历史数据。我习惯把校验数据固定选在几个成交活跃、成交清淡、盘中停牌过的股票上覆盖不同数据形态。这样每次重构解析逻辑几分钟就能确认没有把行情解坏。6.2 用下一帧快照校准盘口重建结果另一个自洽性校验是盘口重建用一帧快照初始化本地十档应用这帧快照之后到达的逐笔然后把重建结果和下一帧真实快照逐价位对比。同价位上挂单量基本一致说明逐笔应用逻辑正确如果出现系统性偏差多半是委托类型里的撤销、撤单处理没做对。实际项目里的做法是把对比结果按价位记录成差异表跑完一个时段后人工抽查几个时点。这个方法抓出过一次很隐蔽的问题某种特殊委托类型在逐笔委托里出现时被当成普通挂单处理导致盘口挂单量持续偏高。如果没有快照校准这类问题几乎不可能被发现等上线了再想补就来不及了。最后说一个习惯接任何Level-2行情接口我都会保留一份原始报文的抓包存档日期、股票、时段标注清楚。所有解析代码的回归测试都基于这份存档做而不是依赖实盘环境。线上环境追求稳定回归测试的确定性只能靠存档来保证。这套方法在几次实际联调里帮我省下了大量排查时间希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑