港交所行情协议MMDP/OMP解析:从二进制流到低延迟订单簿实战

发布时间:2026/7/31 5:26:28
港交所行情协议MMDP/OMP解析:从二进制流到低延迟订单簿实战 1. 行情数据金融市场的脉搏与神经在金融交易的世界里行情数据就是市场的脉搏和神经。无论是股票、期货还是外汇每一笔交易、每一次报价都通过行情数据这个载体实时地传递到全球的投资者面前。对于交易所而言如何高效、准确、可靠地将海量的市场数据分发给成千上万的参与者是一项核心的基础设施工程。香港交易所作为亚洲最重要的金融市场枢纽之一其行情数据协议的设计与实现直接关系到全球投资者能否公平、及时地获取市场信息进而做出投资决策。我接触过不少交易所的数据协议从国内的Level-2到欧美的ITCH/FIX/FAST再到港交所的OGOpen Gateway协议。每次对接一个新的行情源都像是一次深入其技术腹地的探险。今天我想和大家深入聊聊港交所的行情协议体系。这不仅仅是一份技术文档的解读更是结合了我在实际对接、性能调优和故障排查中的一系列经验。你会发现理解协议背后的设计哲学远比记住几个字段定义重要得多。港交所的行情协议官方称之为“证券市场行情数据服务”它是一套完整的、从交易所核心交易系统到外部用户终端的数据分发解决方案。这套协议不仅仅定义了数据的格式更规定了连接方式、会话管理、重传机制等一系列确保数据完整性与实时性的规则。对于程序化交易者、量化研究员、券商系统开发者乃至金融IT运维人员来说透彻理解这套协议是构建稳定、低延迟数据通道的基石。2. 港交所行情协议生态全景从OG到二进制流很多人一提到港交所行情可能首先想到的是各种数据供应商提供的封装好的API比如某些商业软件的插件。但如果我们想追求极致的延迟控制、完全自主的数据处理逻辑或者需要定制化的数据清洗流程那么直接对接港交所官方的原始协议就成为了必由之路。港交所的行情分发体系主要基于其OGOpen Gateway架构。2.1 核心协议栈MMDP与OMP港交所的行情数据主要通过两种主流通路分发它们面向不同需求和规模的用户MMDPMarket Data Multicast Distribution Platform这是港交所低延迟行情数据的核心。它采用组播Multicast技术进行数据分发。你可以把组播想象成一个电台广播交易所作为广播塔发送一份数据流所有订阅了这个“频道”的接收者都能同时收到而不是为每个接收者单独发送一份拷贝。这极大地节省了交易所出口带宽和核心系统的处理压力是实现低延迟、高吞吐量分发的关键技术。协议载体MMDP数据流基于UDPUser Datagram Protocol传输。UDP是无连接的没有TCP那样的握手、确认、重传机制因此开销极小延迟极低。但这也意味着网络丢包需要由应用层协议来处理。数据格式MMDP传输的是高度优化的二进制流。每个数据包Packet都经过精心设计字段紧凑没有冗余字符以最大限度地减少传输数据量提升解析速度。OMPOpen Market Data Platform可以理解为MMDP的“友好访问版”或“补充通道”。它主要面向那些无法直接接入组播网络的环境例如某些云服务器或网络架构不支持组播或者对延迟要求不是极端苛刻的用户。协议载体OMP通常基于TCPTransmission Control Protocol。TCP提供可靠的、有序的、错误校正的数据流确保了数据的完整无误但代价是比UDP有更高的延迟和开销。访问方式用户可以通过标准的TCP Socket连接到港交所指定的服务器地址和端口来获取数据。注意对于绝大多数追求性能的机构用户如券商、高频交易公司MMDP是首选。而OMP则常用于备份链路、测试环境或某些特定的应用场景。我们接下来的讨论将主要围绕MMDP展开因为它是协议设计的精髓所在。2.2 会话生命周期从登录到心跳对接行情协议不是简单的“打开一个端口收数据”。它是一系列有状态的交互我们称之为一个“会话Session”。一个完整的会话通常包括以下几个阶段登录Logon客户端首先需要向行情网关发送一个登录请求报文。这个报文中包含了你的用户账号、密码或令牌、请求的行情频道等信息。网关验证通过后会回复一个登录响应标志着会话正式建立。数据流传输登录成功后网关开始向你推送连续的行情数据流。这个流是混合的里面包含了不同证券的买卖盘、成交、状态等信息。心跳Heartbeat与序号检查为了监测连接的健康状态客户端和服务器会定期互相发送心跳报文。更重要的是每个数据报文都带有一个序列号Sequence Number。客户端需要持续检查这个序列号是否连续。如果发现跳号就意味着中间有数据包丢失了。重传请求Retransmission Request当检测到丢包序列号不连续时客户端必须立即向网关发送重传请求指明丢失的序列号范围。网关会从缓存中重新发送这些丢失的数据包。这是MMDP/UDP方案下保证数据完整性的关键机制。登出Logout当客户端需要断开连接时应发送登出请求进行优雅的会话终止。这个生命周期管理确保了即使在不可靠的UDP传输上也能构建出一个可靠的数据服务。理解每个阶段的状态和可能发生的问题是后续进行故障排查的基础。3. 二进制报文拆解读懂市场的语言MMDP传输的二进制流是效率的体现但也对解析程序提出了高要求。我们收到的不是一个一个的“消息”而是一个个的“数据包Packet”。每个Packet有一个固定的包头Packet Header后面跟着一个或多个行情消息Message。3.1 数据包结构信封与信件让我们类比一下整个数据包就像一个快递信封信封上有收件人需要的信息包头信封里装着的是一封或多封具体的信件行情消息。Packet Header包头固定长度通常是20字节左右。它包含以下关键信息Packet Sequence Number包序列号这是整个数据包的全局唯一递增序号。用于检测丢包。Message Count消息数量指明这个Packet里面封装了多少条独立的行情消息。Send Time发送时间戳交易所发出这个Packet的精确时间通常是纳秒级。这是计算网络延迟和交易所内部处理延迟的黄金指标。Packet Length包长度整个Packet的总字节数。Message(s)消息体在包头之后紧跟着的就是一条或多条行情消息。每条消息也有自己的小头Message Header和消息体Message Body。Message Header消息头包含消息类型Message Type、消息长度、对应证券的代码通常是一个数字形式的Instrument ID等。Message Body消息体这是核心数据所在其结构完全由Message Type决定。3.2 核心消息类型解读港交所定义了数十种消息类型但最核心、出现频率最高的是以下几类Incremental Packet增量更新包这不是一个具体的消息类型而是一种数据组织方式。一个Packet里可以包含多条不同类型的增量更新消息用于实时刷新市场状态。交易状态消息Trading Session Status告诉你市场目前处于什么阶段例如“开市前时段”、“持续交易时段”、“午间休市”、“收市”等。你的系统必须根据这个状态来决定如何处理后续的行情比如在非交易时段收到的订单簿更新可能只是指示性报价。证券静态信息消息Security Definition通常在每个交易日开始时发送。它定义了今天所有可交易证券的基本信息包括数字ID与交易代码如00005.HK的映射、买卖单位Lot Size、价格变动单位Tick Size、货币等。这是你建立本地代码映射表的依据没有它你收到一堆数字ID将无法识别是哪只股票。订单簿增量更新消息Order Book Update这是流量最大的一类消息。它通常以“价格档次Price Level”为单位进行更新。例如买一价的数量发生了变化或者卖五价被撤销了。一条消息里会包含UpdateAction是新增New、修改Change还是删除Delete这个价格档位Side是买盘Bid还是卖盘OfferPrice价格。Size在这个价格上的合计订单数量。Order Count有时有在这个价格上的订单数量冰山订单下有用。客户端需要根据这些增量消息在内存中维护一个本地订单簿的镜像。成交消息Trade报告一笔成交的发生。包含成交价格、成交量、成交时间、买卖方向通常是主动成交的方向等。注意成交发生后订单簿的相应数量会被扣除这通常由后续的订单簿更新消息来反映。快照消息Snapshot在某些情况下如每日开盘前或响应重传请求后网关会发送完整的订单簿快照。它包含了当前所有价格档位的买卖盘信息。用于初始化或重建本地订单簿。解析二进制流的过程就是循环读取Packet Header根据其中的Message Count循环读取每条Message Header再根据Message Type调用对应的解析函数来解读Message Body。这个过程必须高效且准确通常会用C、Rust或高性能的Java/C#来编写。4. 实战对接从零构建一个稳定的行情客户端理解了协议原理我们来谈谈实战。构建一个生产级别的港交所MMDP行情客户端远不止写个解析器那么简单。它涉及网络、系统、内存、逻辑等方方面面。4.1 环境准备与网络配置这是第一步也是坑最多的一步。组播订阅你的服务器必须接入支持组播的网络并且正确配置了路由。你需要从港交所或你的线路供应商那里获取组播地址Multicast Group IP和端口Port不同的行情频道如证券、衍生品对应不同的组播地址。源地址Source IP有时为了安全会要求指定只接收来自特定源IP的组播流。在你的接收程序或操作系统中你需要加入Join这个组播组。在Linux下这通常通过socket选项IP_ADD_MEMBERSHIP来完成。网络适配器与缓冲区高速数据流对网卡和操作系统网络栈是巨大考验。使用高性能网卡考虑支持RSS接收侧缩放的万兆甚至更高速率网卡。调整Socket缓冲区大小默认的UDP接收缓冲区SO_RCVBUF通常太小在行情爆发时极易丢包。你需要将其设置为一个很大的值例如64MB或更大。并且注意在Linux上你不仅要在代码中设置还需要提高系统的net.core.rmem_max等内核参数上限否则设置不生效。# 示例临时提高系统参数 sysctl -w net.core.rmem_max134217728 # 128MB sysctl -w net.core.rmem_default1342177284.2 核心处理逻辑设计你的客户端程序需要处理多条并行的逻辑线网络接收线程这个线程的唯一任务就是以最高优先级从Socket读取数据包放入一个无锁环形队列Ring Buffer。它的工作必须尽可能快避免任何阻塞操作如日志打印、业务处理。协议解析与订单簿维护线程从环形队列中取出原始Packet进行解析。根据消息类型如果是静态信息更新本地代码表。如果是订单簿更新更新内存中的订单簿数据结构。这里的数据结构设计至关重要。通常使用std::map或std::unordered_map以证券ID为Key其Value是一个包含买盘和卖盘std::map以价格为Key排序的结构。更新时需要处理好线程安全。如果是成交更新本地成交记录并可能触发策略逻辑。持续检查包序列号发现丢包立即构造并发送重传请求。心跳与会话管理线程定时如每秒发送心跳报文并检查接收心跳响应是否超时。管理登录、登出等会话状态。数据分发线程将处理好的、结构化的行情数据如订单簿快照、成交事件分发给内部的其他策略或风控模块。4.3 性能优化关键点内存管理避免在高速处理路径上动态分配内存new/delete,malloc/free。应使用内存池预分配所有需要的缓冲区。CPU亲和性与NUMA将关键的接收线程和解析线程绑定到特定的CPU核心上减少上下文切换和缓存失效。如果使用多路CPUNUMA架构确保线程和其使用的内存位于同一个NUMA节点。时间戳在Packet进入网卡驱动层甚至使用支持硬件时间戳的网卡、进入用户程序、开始解析等关键节点打上时间戳。这能帮你精确衡量每个环节的延迟定位瓶颈。解析优化二进制解析避免使用高层抽象。直接使用memcpy到结构体注意字节序对齐和转换或者使用指针偏移直接读取。对于频繁调用的价格、数量转换函数如将整数表示的“价格*10000”转换为浮点数确保其被内联或高度优化。5. 避坑指南那些年我踩过的雷对接港交所行情光看文档是远远不够的。下面分享几个典型的“坑”希望能帮你少走弯路。5.1 序列号不连续不等于丢包这是最容易让人紧张的问题。你发现Packet Sequence Number从 1000 直接跳到了 1003第一反应就是“丢了2个包快重传”。但等等先检查一下Message Type。港交所的协议中有一种特殊的“心跳包”或“空数据包”。它可能只包含包头和极少的心跳信息其序列号是递增的但Message Count为 0。也就是说序列号1001和1002的包可能是这种不包含实际行情数据的“空包”。协议规范里会明确说明这种包的序列号行为。正确的做法实现一个“有效数据包序列号”的检查逻辑只对那些Message Count 0的包进行连续性校验。对于空包记录其序列号用于监控连接活性即可不触发重传。5.2 订单簿重建的时机与一致性你的本地订单簿是基于持续的增量更新维护的。但在以下情况它可能和交易所的权威状态不一致程序刚启动。网络中断后重连。重传逻辑出现异常。此时你需要一个快照Snapshot来重建一个正确的起点。港交所通常在每日开盘前、以及响应某些特定重传请求时会发送完整快照。关键经验不要假设任何时候都能收到快照。你的程序应该设计成两种模式增量模式正常运行时信任并应用每一条增量更新。快照模式在检测到状态异常如序列号缺口太大且无法通过重传弥补或收到快照消息时清空本地订单簿用快照数据完全重建然后切换回增量模式。更棘手的是“增量与快照的交叉”你可能正在处理增量流突然插进来一个快照包。如果处理顺序不当会导致订单簿混乱。必须严格按包序列号顺序处理所有Packet并在处理快照消息时原子性地切换订单簿状态。5.3 价格与数量的表示与计算行情数据中的价格和数量通常不是浮点数或整数而是经过缩放Scaled的整数。价格可能是Price * 10000或Price * 100000000取决于产品后的整数值。解析后必须除以相应的缩放因子。务必查阅规格书确认每只证券的缩放因子因为不同产品如股票、涡轮、牛熊证可能不同。静态信息消息Security Definition里通常会包含这个PriceDisplayFormat或MinPriceIncrement信息。数量通常是股份数量股数对于期货则是合约张数。但要注意买卖盘更新中的Size它代表的是该价格档位的总订单数量单位是“手Lot”。你需要用静态信息中的Lot Size每手股数乘以这个Size才能得到总股数。例如买一价Size 50该股票的Lot Size 500则买一价的总订单股数为50 * 500 25,000股。忽略缩放因子和单位转换会导致计算出的金额、市值等全部错误这是非常低级的致命错误。5.4 网络抖动与重传风暴在UDP环境下网络轻微抖动可能导致瞬间的连续丢包。如果你的重传策略过于激进可能会发生发现丢失包1001-1005。立刻发送对1001-1005的重传请求。由于网络问题这个重传请求也可能丢失或延迟。你等不及又发送了一次重传请求。最终网络恢复网关收到了两个重复的重传请求于是发送了两份1001-1005的数据。你的客户端收到双份数据订单簿被重复更新导致数量虚高。应对策略实现重传请求的退避Backoff机制第一次重传等待时间短如10ms如果还没收到第二次等待时间长一些如50ms以此类推。记录已请求重传的序列号范围避免在收到数据前重复请求同一段数据。设置合理的重传超时和放弃阈值对于太久远的数据交易所的缓存可能已经清除重传也无意义。此时应记录错误并尝试寻找机会如等待下一个快照来恢复状态而不是无限重试。对接港交所行情协议是一个将严谨的协议规范、高性能的系统编程和细致的金融业务知识相结合的过程。它没有太多黑科技更多的是对细节的掌控和对异常情况的周全考虑。从网络报文的抓取解析到内存订单簿的毫秒级更新再到面对网络波动时的从容恢复每一个环节都考验着开发者的功底。当你亲手构建的系统能够稳定地吐出精准的市场数据并支撑起交易决策时那种成就感是巨大的。希望这篇结合了原理与实战的分享能为你深入金融市场数据底层架构提供一张有用的地图。