ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深交所Level2行情接口V1.11:会话机制、FAST解码与量化接入实战

深交所Level2行情接口V1.11:会话机制、FAST解码与量化接入实战 简介深圳证券交易所发布的《Level2行情数据接口规范V1.11》是一份面向高频交易、量化交易及涨停板交易者的官方技术标准。文件为PDF格式共1个文件压缩包大小约682KB内容涵盖STEP行情接口的会话机制、快照行情、逐笔委托与逐笔成交、证券实时状态等消息定义并完整记录了2013年至2021年的历次修订说明。规范中详细解释了Level2行情特有的五档盘口、买卖队列及新增的盘后定价大宗交易、债券现券竞买、期权备兑等开关类别同时明确了用户行情系统对新增条目的兼容性处理方式。对于需要自主开发或优化行情接入模块的量化团队与接口开发人员这份PDF可直接作为接口对接、字段排查与版本差异对照的权威参考。已有1990人学习下载适合熟悉证券交易业务且希望精确掌握深交所Level2数据格式的技术人员。1. 深交所Level2行情接口V1.11做量化先看懂这份数据合同做涨停板盯盘或者自研量化行情引擎的人第一次翻《深交所Level2行情数据接口规范V1.11》通常会被目录劝退三十多页的工程技术标准从会话机制一路写到FAST模板ID。但你做的策略越依赖盘口这份规范就越是绕不开的“数据合同”。它定义了程序怎么连行情网关MDGW、快照和逐笔行情按什么结构推送、消息丢了怎么重传。适合三类人写接入程序的工程师、做量化策略的研究员以及想搞清楚Level2比Level1多了哪些有效信息的进阶交易者。把这份规范啃明白你才敢把交易决策托付给自研的数据管道。2. STEP会话与消息封装双端口分工、FAST解码与登录参数规范的会话机制部分常常被跳过这个习惯在别的项目里没事在行情接入里会害人。后面的消息定义全建立在会话稳定这个前提上连接参数和超时判不好后面全白搭。我拆这份规范时最先做的是把两个端口和各自的能力边界画出来再决定程序怎么组织线程。2.1 连接参数9129实时、9130重传一个端口只能建一条连接行情网关MDGW给接入用户提供两个服务端口实时数据端口默认9129重传服务端口默认9130。规范写得很死每一个端口只能建立一个TCP/IP连接。这意味着你没法像连数据库那样开连接池分摊流量架构上只能一条连接跑到底。现场版行情网关走单向卫星没有重传端口只有网络版才支持9130。这个差别直接决定你的补数方案后面避坑章节会细讲。连接的安全模型也要提前知道网关和VSS必须处于同一个安全网络中传输不带加密数据安全靠接入用户自己的网络保障。所以别指望在公网上裸连MDGW券商或信息商生产环境里一般走内网或专线。流量控制是另一个容易被低估的规则如果VSS处理不过来网关待发送消息堆积超过阈值它会直接断开连接而不是慢慢等你。换句话说收包线程的消费速度必须跟得上峰值行情否则重连不是可选操作而是必然操作。2.2 消息封装STEP消息层包FAST消息体解码前必须先重置字典表4-1给出的STEP消息层结构是理解整个协议的地基每个应用消息都有Standard Header然后是10201 ChannelNo频道代码、95 RawDataLength记录FAST部分字节数、96 RawData装FAST编码后的消息体。96里可以包含本频道的多条FAST消息解码前要求先重置FAST字典前值。我第一次实现时偷懒没重置结果偶发出现价格跳变的脏数据查了两天才定位到是前值串了。FAST模板ID编码规则也要背下来3000-3999是公共消息4000-15999是实时行情数据。频道心跳模板ID是3001重传消息是3002用户信息报告是3003。解码器拿到一个包按模板ID分支就够了。FAST消息层定义里标注了域名称、FAST操作符和占位标志。操作符为default且占位为Y的字段一定会出现在字节流里占位为N的字段可以省略解码器要按默认值生成少处理一个占位标志都会让后续字段错位。2.3 登录与心跳通信版本号填1.02心跳间隔3秒超时翻倍登录消息的DefaultApplVerID要填通信版本号1.02这是规范反复强调的。注意文档版本V1.11和通信版本号1.02不是一个东西V1.11是这份文档的版本1.02是登录报文的版本号两者别混。体系内可以建两个会话一个实时数据、一个重传数据登录后各自独立。规范本意是兼容FIX标准会话协议但特别点了一句会话层恢复机制不能作为真正的消息恢复机制缺失数据要靠应用层重传消息UA002。频道心跳发送间隔3秒由10201 ChannelNo指定频道同时带上1350 ApplLastSeqNum最后一条行情消息记录号和10205 EndOfChannel频道结束标志。每个频道空闲时发独立心跳心跳本身没有记录号。判断网关故障的标准是超过两倍心跳间隔没收到数据包也就是6秒。我一般把最后接收时间戳记在频道级别任何一个频道超过6秒没动静就触发断线重连流程。2.4 容错与恢复断开重连走应用层补数别指望会话层会话层恢复是给FIX兼容用的真正的补数是重传消息UA002。这里有个普遍误用我见过不止一个团队用会话层序列号判断行情丢没丢最后对不上账。逐笔行情唯一可靠的完整性依据是频道内的消息记录号记录号从1递增出现跳号才代表丢失。断线重连之后先等新数据流回暖再根据记录号缺口发起重传请求。实时数据会话和重传数据会话是两条独立TCP连接重传请求走9130网关按到达顺序排队处理。所以批量重传请求要控制并发别一口气塞几十个一个没处理完不会处理下一个。3. 行情数据类别与关键字段快照、逐笔、公告怎么选频道3.1 频道规划从代码区间看懂深交所的行情布局表3-1的频道表信息量很大。市场实时状态和证券实时状态在频道1公告走频道2。10是深交所指数与成交量统计指标快照11是国证指数快照。101x到105x按品种分开股票、基金、可转债、权证、期权x是0到9的数字说明股票这种大流量品种用了一整段频道。逐笔行情对应201x-205x与快照的品种段一一对应。盘后定价大宗交易快照在300x盘后定价交易快照在301x债券分销在3021。债券质押式回购快照106x、匹配成交逐笔206x债券现券快照107x、匹配成交逐笔207x。401x是报价与大额逐笔通道包含债券匹配大额逐笔申报及成交、意向申报、点击成交报价及成交、询价成交及协商成交1.11版本还加入了竞买委托和竞买成交。5000是信息商的用户信息报告5001是港股实时行情。从这个频道表能得出一个选型结论做A股量化最少要订阅101x加201x再加上频道1的实时状态做债券就要看106x、107x和206x、207x。每个行情网关可以配置只接收某些频道登录前要确认网关侧给你开了哪些频道的权限收不到数据先查这个。3.2 快照行情消息MDStreamID、MDEntryType与TradingPhaseCode快照行情消息的标准消息类型是WSTEP层带10201频道代码FAST层里MDStreamID标识行情类别。从修订历史看MDStreamID的取值持续在扩2016年加了港股实时行情6302020年加盘后定价交易类别2021年又加债券现券交易业务行情快照和竞买行情。快照行情是定时发布、不能重传的每类行情有各自的发布频率所以同一个频道里不同MDStreamID的数据到达节奏也不一样客户端要做时间戳对齐而不是假设同时到达。MDEntryType是行情条目类别也就是盘口上具体有什么条目。规范演进里顺序加了加权平均价9、加权平均价涨跌BPxj、昨收盘加权平均价xk、按盘价xh、参考价xi2020年港股开市前时段又加了买盘上限价xr、买盘下限价xs、卖盘上限价xt、卖盘下限价xu。传统Level2的五档买卖盘在这些类别里只是最基础的。我的习惯是解码器维护一张MDEntryType映射表表里没有的类别走默认分支忽略并计数而不是直接抛异常这样新增行情条目不会把整个程序打挂。TradingPhaseCode是产品所处交易阶段代码。规范修订里第0位增加过“A盘后交易”后来加过“波动性中断V”。这个字段是量化风控的重要输入盘中看到V说明触发了波动性中断这时候不能按正常流动性模型下单。我把TradingPhaseCode变化当事件处理任何非连续交易状态的切换都强制触发一次策略状态复位。3.3 逐笔委托、逐笔成交UA201/UA202消息流与记录号对账逐笔委托消息的消息类型是UA201逐笔成交是UA202。记录号规则是核心消息记录号在一个频道内从1开始顺序递增出现跳变就说明消息丢失客户端通过UA002申请补传。具体判断是收到的记录号小于等于本频道已收到的最大记录号直接忽略收到的记录号大于最大记录号加1比如最大是10来了个12那11就丢了要补。每个频道的逐笔消息发送完毕后网关会发频道结束消息EndOfChannel标记一个周期结束。心跳消息没有记录号。逐笔委托消息还有联系人Contactortag10184和联系方式ContactInfotag10185这两个是1.00β之后加的比较冷门做券商内部系统对接时偶尔会用到。另外1.07版本开始逐笔行情支持两种不同的发送模式具体模式配置在网关侧客户端要能识别通道上实际是哪种模式再决定怎么组装逐笔委托和成交的关联关系。这块不要做假设以实际收到的消息流为准。3.4 证券实时状态消息SecuritySwitchType开关类别的用法证券实时状态消息走频道1带一组安全开关。修订历史里能看到开关类别的演进表决权、股票质押式回购、备兑开仓、做市商报价、港股通整手买、整手卖、零股买、零股卖、34回售撤销、35转融通出借、36债券回售转售中间还删除过18转股撤单、19回售撤单。开关的变化对策略影响很直接比如做期权备兑的策略看到备兑开仓开关变化就要重新校验持仓对应的标的券状态港股通策略要看整手零手买卖的开关。需要特别注意你不关心的开关类型发生变化系统要能自动忽略这也是规范兼容性要求的一部分。公告消息B走频道2每个公告文件有唯一ID。新公告由网关主动推给VSS登录前的丢失公告要通过重传消息先申请公告概要概要里是所有已发布公告ID再逐个补缺失的。规范建议登录完成后立即申请公告概要这个建议值得写死进默认流程因为公告里可能就藏着当天交易安排变化的关键信息。4. 接入实战从TCP建连到FAST解码的VSS客户端框架4.1 连接与登录socket、超时和Logon构造先把最基础的连接代码搭起来。import socket import struct def connect_mdgw(host: str, port: int 9129, timeout: float 10.0) - socket.socket: s socket.create_connection((host, port), timeouttimeout) s.settimeout(3.0) # 心跳3秒一次读超时低于3秒容易误判 return s代码逻辑说明create_connection负责TCP握手超时10秒给首次建连的宽限。连接建立后把读超时压到3秒是为了配合心跳节奏每3秒至少应该有应用层数据进来读超时触发后不一定是断线但说明链路异常要进重连流程。这里的关键参数是3秒网关负载高或链路抖动时我一般放宽到4秒再观察但绝不能放到30秒否则网关重启后你还在等数据。登录消息按轻量级STEP会话层规范构造核心是把DefaultApplVerID设为1.02。代码示意def build_logon(sender_comp_id: str, target_comp_id: str MDGW, heart_bt_int: int 30, appl_ver_id: str 1.02) - bytes: tags [ (35, A), # MsgTypeLogon (49, sender_comp_id), # SenderCompID (56, target_comp_id), # TargetCompID (108, str(heart_bt_int)), # HeartBtInt (DefaultApplVerID, appl_ver_id), # 通信版本号上线前替换为数字tag ] body .join(f{k}{v}\x01 for k, v in tags).encode() return struct.pack(H, len(body)) body逻辑说明这里用可读字段名代替数字tag真实实现要按STEP规范的标签表换算成数字。返回的包是“两字节长度消息体”的帧结构两字节长度是STEP分帧常见做法也可以按运行环境调成4字节但要在连接参数里和网关对好。HeartBtInt我习惯给30秒行情网关对会话层心跳的容错范围在轻量级STEP规范里有约定登录后的应用层心跳是UA001和会话层HeartBtInt不是一回事别重叠配置。4.2 收包循环长度分帧、消息分发、队列隔离收包循环做三件事拼帧、解STEP层、分发FAST解码。class MdgwClient: def __init__(self, sock: socket.socket): self.sock sock self.buf b self.queue queue.Queue(maxsize10000) def recv_loop(self): while True: chunk self.sock.recv(65536) if not chunk: raise ConnectionError(连接被对端关闭) self.buf chunk while len(self.buf) 2: msg_len struct.unpack(H, self.buf[:2])[0] if len(self.buf) 2 msg_len: break step_msg self.buf[2:2 msg_len] self.buf self.buf[2 msg_len:] channel_no, raw_data parse_step(step_msg) self.queue.put((channel_no, raw_data))逻辑说明外层循环把TCP流切成一帧帧STEP消息。内层while处理一个chunk里可能残留的多帧。parse_step返回ChannelNotag10201和RawDatatag96这样解码器能知道这条FAST数据属于哪个频道。queue容量设10000是给快照和逐笔做背压满了之后丢弃旧策略比无限堆积安全宁可丢弃触发重传也比内存撑爆被网关断线强。def parse_step(step_msg: bytes): # 简易解析顺序找tag10201和tag96 d parse_tags(step_msg) channel_no int(d.get(10201, 0)) raw_len int(d.get(95, 0)) raw_data d.get(96, ) if len(raw_data) ! raw_len: raise ValueError(RawDataLength与实际数据长度不符检查分帧逻辑) return channel_no, raw_data注意RawDataLength必须和实际长度一致这一行校验能拦截掉大部分分帧错位的问题。我遇到过一版代码把长度多算了2字节结果FAST解码器整天报错靠这条校验才定位到。4.3 FAST解码模板ID分发、前值字典重置、静默忽略未知字段FAST解码器推荐的落地方式是用XML模板驱动模板ID从999字段取。class FastDispatcher: TEMPLATES {3001: HeartBeat, 3002: ResendMsg, 3003: UserInfoReport} def __init__(self): self.decoder FastDecoder() # 加载协议模板文件后实例化 def decode_raw(self, channel_no: int, raw: bytes): # 一个RawData体里可能有多条FAST消息循环解 for fast_msg in self.decoder.decode_all(raw): template_id fast_msg.template_id handler self.TEMPLATES.get(template_id) if handler: handler(channel_no, fast_msg).run() else: # 未知模板统一跳过只计数 stats.record_unknown_template(template_id)这里的关键点是TEMPLATES字典只注册你关心的消息类型没注册的模板ID走else分支静默跳过。规范兼容性要求里明确说过新增消息类型对不改动的VSS要能自动忽略这里就是落到实处的位置。解码器每次处理一个新的RawData前要重置字典前值这个不能在handler里做必须放在decode_raw入口因为一个RawData字节块是一个完整的FAST消息流块与块之间的前值字典不允许串。4.4 参数与线程模型快照和逐笔的消费隔离参数项推荐值说明实时端口9129默认实时数据端口重传端口9130仅网络版提供现场版无此端口心跳间隔3秒频道心跳UA001断线判据6秒无应用层数据两倍心跳间隔读超时3-4秒配合心跳节奏队列上限10000条超限走丢弃重传线程模型上我强烈建议接收线程只做分帧和解STEP层FAST解码可以放解码线程业务消费再隔一层。快照频道和逐笔频道的处理要分开逐笔量级大解码慢一点就会堵心跳快照又不能重传一旦被逐笔堵住当天都别想拿到干净的快照数据。5. 避坑指南记录号跳变、重传失败与静默兼容的5个实战问题5.1 消息记录号跳变补传请求还回了个“部分完成”现象逐笔频道收到的记录号从10直接跳到12发起UA002请求补11结果网关返回ResendStatus2部分完成11还是没拿到。原因逐笔消息在网关内存里的保留窗口有限超出窗口的旧数据已经被清理。记录号跳变的实时位置离请求时间越久补回概率越低。解决检测到跳号立即发起重传请求不要攒批。同时把ResendStatus2当成常规事件处理记日志而不是直接告警。如果频繁补不回来回查自己的收包线程是不是在高峰时段被GC或日志拖慢。血泪经验是逐笔补传的黄金窗口很短请求越快补回来的希望越大。5.2 登录成功但收不到指定频道查权限而不是查网络现象TCP连接建好了Logon也回了成功但101x股票快照频道一直没有消息。原因行情网关按配置下发频道你的账号或网关这一侧没开这个频道的权限。规范写了“每个行情网关可以配置只接收某些频道的交易行情数据”指的就是这个。解决联系网关管理员确认频道权限配置。我见过不少团队在网络层、解码层折腾半天最后发现是频道没开。建议登录后加一个频道清单自检流程把期望频道的ChannelNo列成配置启动时和实际收到的心跳对一遍有心跳就有数据没心跳先查权限。5.3 心跳一直有但行情数据停更界面显示“已连接”很误导现象频道心跳3秒一次很规律但快照的W消息和逐笔的UA201/UA202都不动了监控面板还显示连接正常。原因心跳是每个频道空闲时独立发送的它只证明TCP链路和你订阅的频道还活着不证明业务消息在持续生产。网关侧某个业务模块卡住时心跳照样发。解决监控里把“最近一条业务消息时间”和“最近一条心跳时间”分开统计。我现在每个频道统计行情消息到达间隔超过该频道发布周期的3倍就报警。判断网关是否故障用两倍心跳间隔判断业务是否停摆用该频道的正常发布周期这两个指标不能混用。5.4 新增MDEntryType导致解码器崩溃违反兼容性要求现象某天行情正常突然某个快照包让解码器抛出未知字段异常处理线程退出后续数据全断。原因解码器用硬编码的字段枚举表遇到新增的行情条目类别比如加权平均价9这类新值直接抛错没有走忽略逻辑。解决规范第四条兼容性要求写得很清楚快照或逐笔新增MDEntryType不关注这些新条目的系统应能自动忽略。实现上把MDEntryType解析改成通用map结构键值对读出来不需要的类别直接丢弃。后来我为每个未知字段类型单独计数并定期输出统计这样既不丢数据又能感知交易所新增了什么类别。5.5 现场版网关没有重传端口补数方案完全失效现象接入环境从网络版换到现场版后UA002重传请求全部石沉大海日志里永远等不到重传应答。原因现场版使用单向卫星通信没有数据重传功能规范明确“只有网络版行情网关提供重传服务端口”所以9130端口根本不存在。解决接入前先确认环境是现场版还是网络版。现场版环境下逐笔缺失只能靠重建会话后的新数据流自然补齐快照本来就不能重传。我的对策是在策略层做容错逐笔成交丢失时降低高频策略的仓位上限用快照的五档挂单作为降级信号别让策略在数据残缺状态下裸跑。6. 进阶用法拿修订历史反推解码器兼容性清单预研策略不再怕新字段6.1 把修订历史变成解码器回归用例规范的修订历史其实是最实用的一张兼容性图纸。V1.00到V1.11每一次更新都对应一类新数据字段或新交易场景1.01加港股通开关和港股实时行情6301.02加加权平均价、昨收盘加权平均价1.04加国证指数行情1.06加盘后定价大宗交易1.07支持港股开市前时段新增xr/xs/xt/xu四类MDEntryType1.08加参考价1.09加债券现券行情和逐笔占位消息1.11再加竞买行情。把这条演进线整理成一张表就是解码器的兼容性验证清单。我的做法是每收到一份新版本规范先拉一次修订差异把新增的MDEntryType、SecuritySwitchType和频道代码追加到一个测试用例文件里然后用这些用例喂解码器验证它走的是“静默忽略”分支而不是异常分支。新版字段对应版本验证动作港股实时行情6301.01快照模板加MDStreamID分支加权平均价9/xj、昨收盘加权平均价xk1.02快照条目忽略测试港股开市前xr/xs/xt/xu1.07条目解析不做硬编码债券现券107x/207x频道1.09频道心跳覆盖测试竞买委托与成交1.11逐笔消息类型扩展测试这个技巧的价值不在于测试本身而在于让接入系统的每次升级都有据可依不用等上线了才被新行情类别打个措手不及。另外还有一个很实际的用法把修订历史当作策略研究的风向标。交易所新增哪类行情往往意味着这个方向在交易制度和市场服务上有变化。盘后定价交易、债券现券行情、港股开市前时段都是先出现在接口规范里再逐步成为策略圈讨论热点的。提前把解码器兼容性做扎实等这些数据真正成为策略标的时你的数据管道已经准备好了。现在每拿到新版规范我第一件事就是让解码器跑一遍新增字段用例跑通之后再谈业务逻辑。这套习惯帮我把上线前的深夜电话挡掉了一大半希望帮到你。本文还有配套的精品资源点击获取
返回列表