ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

STEP V1.11 Level2行情接入:FAST解码、重传与避坑指南

STEP V1.11 Level2行情接入:FAST解码、重传与避坑指南

简介:这份《深交所Level2行情数据接口规范V1.11》面向高频交易与量化策略开发者、券商行情接入人员及关注涨停板博弈的进阶交易者,系统给出深市Level2与STEP格式行情的数据获取与处理规则,解决行情消息解析、字段取值与系统兼容性等实际问题。压缩包共1个PDF文件,约682KB,为深圳证券交易所2021年8月发布的技术标准正式文档,包含修订历史、名词释义、会话机制及各业务消息定义,方便按章节查阅。该资源已有1995人学习下载,说明其在行情接入与量化研究群体中具有一定参考价值。文档完整记录从2013年0.10版到1.11版的历次修订脉络,逐项说明快照行情频道统计、证券实时状态开关类别、逐笔委托与成交、盘后定价交易、债券现券交易及港股开市前时段等消息的增补与调整,并明确新增行情条目与开关类别应被自动忽略的兼容性要求,可作为接口开发、联调测试与版本升级对照的规范依据。

1. 先看清这份规范能解决什么,再谈接入

Level2 行情接进策略之前,最先卡住人的往往不是模型,而是那份接口规范。某交易所发布的 STEP 行情数据接口规范 V1.11 就是这类文档:它不讲策略,只规定证券交易行情怎么编码、怎么分频道、丢了怎么补。对做量化的团队来说,这份文档是把行情落成可用 tick 的唯一依据;对刚接手接入的新人来说,它更像一份需要逐条对照的验收清单。从 2013 年的 0.10 版一路改到 2021 年 8 月,文档版本已经到 1.11,通信版本却始终停在 1.02,中间塞进了债券现券、盘后定价、港股开市前时段、逐笔两种发送模式等一堆增量。它能不能用?能,而且必须按它来;但它不是开箱即用的 SDK,很多关键约定藏在字段级的注释里,读完文档和跑通链路之间还隔着一层工程活。

2. 会话与端口:9129 / 9130 两个连接撑起整条链路

接入的第一步不是解包,是把连接建对。规范里对会话的描述很克制,只有几行字,但每一行都对应一个上线后才会暴露的故障点。用户行情系统(VSS)和行情网关(MDGW)之间是标准 TCP/IP 点对点连接,实时数据端口默认 9129,重传服务端口默认 9130,每个端口只允许建立一条连接。这句话的含义是:你不可能靠多开几条 TCP 来提高吞吐,网关侧对连接数是有硬约束的,想扩容只能在单连接内做消费能力的优化。

还有一个容易忽略的前提:只有网络版行情网关提供重传服务端口。现场版走单向卫星,没有数据重传功能。如果接入方案里设计了一套依赖重传的补数逻辑,却部署在现场版网关上,那套逻辑永远不会被触发,丢包就是永久丢失。选型阶段先把网关版本确认清楚,比后面写多少补偿代码都管用。

2.1 Logon 里的 DefaultApplVerID:填 1.02,不是 1.11

文档封面写着 V1.11,很多人第一反应是把 1.11 填进登录消息,结果登录直接被拒。规范 2.1.1 节说得很明确:本版接口规范对应的通信版本号是 1.02,在 Logon 消息的 DefaultApplVerID 字段填写该通信版本号。文档版本和通信版本是两条独立的版本线,文档改到 1.11 是因为业务字段在增加,通信层协议并没有跟着跳版本。

# 建立实时数据会话时的登录消息构造(字段编号参照规范会话层定义) logon = { "MsgType": "A", # Logon "DefaultApplVerID": "1.02", # 通信版本号,注意不是文档版本 1.11 "HeartBtInt": 30, # 会话层心跳间隔(秒),与频道心跳不是一回事 "ResetSeqNumFlag": "Y", # 首次登录建议重置序列号 } def build_logon(host: str, port: int = 9129) -> dict: """实时会话与重传会话分别建连,端口不同但登录参数一致""" return { "endpoint": (host, port), "logon": logon, "session_type": "realtime" if port == 9129 else "resend", }

这段代码里有两个参数值得展开。HeartBtInt 是会话层心跳,和后面 3 秒一次的频道心跳完全是两个层级的东西:会话层心跳用来保活 TCP 会话,频道心跳用来判断某个频道是否还在推数据。ResetSeqNumFlag 在首次登录时置 Y,重连时是否重置要看你的恢复策略——如果依赖应用层重传补数,会话层序列号重置不会影响补数正确性,因为补数走的是记录号而不是会话序列号。

提示:会话层自带的恢复机制只是为了和标准会话协议兼容,规范明确说它不能当作真正的消息恢复机制使用。丢数据的补全必须走应用层的重传消息,这一点在 design review 阶段就要定死。

2.2 频道代码不是随便订阅的:101x 与 201x 是两套东西

规范第三章给了一张频道表,这张表是整个接入工作的索引。快照行情和逐笔行情走的是完全不同的频道段,业务类别靠频道代码的第 4 位区分。表中 x 表示 0 到 9 的数字,所以 101x 实际展开是 1010 到 1019 十个频道,对应股票快照;201x 展开是 2010 到 2019,对应股票逐笔。

频道代码频道内容消息类型可否重传
1市场实时状态、证券实时状态f / h否
2公告B是(按公告 ID)
10 / 11交易所指数、国证指数快照W否
101x~105x股票/基金/可转债/权证/期权集中竞价快照W否
201x~205x对应品种的逐笔委托与逐笔成交UA201 / UA202是
106x / 206x债券通用质押式回购快照 / 匹配成交逐笔W / UA201、UA202快照否、逐笔是
107x / 207x债券现券交易快照 / 匹配成交逐笔W / UA201、UA202同上
300x / 301x盘后定价大宗交易快照 / 盘后定价交易快照W否
3021债券分销快照W否
400x / 401x综合金融服务逐笔 / 债券报价及大额逐笔UA201、UA202是
5000用户信息报告UA003否
5001港股实时行情快照W否

这张表落到代码里就是一个频道过滤器。网关可能推送你并不关心的频道,规范 4.1 节的兼容性要求第一条就说得很清楚:新增行情频道时,VSS 如果不使用这些数据,应当能不作变更而自动忽略。所以过滤逻辑要按「白名单匹配 + 未知频道丢弃」来写,而不是「遇到不认识的频道就抛异常」。

# 频道白名单:按业务需要订阅,未命中的频道直接丢弃,不报错 SNAPSHOT_PREFIX = ("10", "11", "101", "102", "103", "104", "105", "106", "107", "300", "301") TICK_PREFIX = ("201", "202", "203", "204", "205", "206", "207", "400", "401") def classify_channel(channel_no: int) -> str: ch = str(channel_no) if ch == "1": return "status" # 市场/证券实时状态 if ch == "2": return "news" # 公告 if ch == "5000": return "user_report" # 仅信息商需要发送 if ch == "5001" or ch.startswith(SNAPSHOT_PREFIX): return "snapshot" # 定时发布,不可重传 if ch.startswith(TICK_PREFIX): return "tick" # 带记录号,可重传 return "unknown" # 未知频道静默丢弃

参数上唯一需要小心的是前缀匹配的歧义:频道 10 和 101x 都以「10」开头,如果用 startswith("10") 会把股票快照和指数快照混在一起。实际工程里我一般把频道号先转成整数再按区间判断,或者像上面这样把精确值放在前缀判断之前,用顺序保证优先级。这种小坑在单元测试里加两条用例就能锁死。

2.3 快照靠定时、逐笔靠记录号:两个接收循环不能写成一套

快照行情和逐笔行情在接收语义上是两种东西,硬套同一套循环逻辑必然出问题。快照行情定时发布,规范明确写了不能重传;每个快照频道可能包含多个类别的行情,每类有自己的发布频率。这意味着快照侧的正确性判断标准是「有没有收到最新一版」,而不是「有没有漏掉某一条」。消费端用覆盖写就行,不需要维护序号。

逐笔行情不一样。每条逐笔消息都带频道代码和消息记录号,记录号在频道内从 1 开始顺序递增。判断逻辑只有三条:收到的记录号小于等于已收到的最大记录号,说明是重复消息,忽略;等于最大值加一,正常;大于最大值加一,说明中间丢了,触发重传。每个频道的逐笔消息发完之后,网关会发一笔频道结束消息。频道空闲时有独立心跳,心跳消息没有记录号。

class TickChannelState: def __init__(self, channel_no: int): self.channel_no = channel_no self.max_seq = 0 # 本频道已收到的最大记录号 self.gap_buffer = {} # 记录号 -> 消息,等待补数期间先缓存 def on_message(self, seq_num: int, payload: dict): if seq_num <= self.max_seq: return "duplicate" # 重复消息,直接丢 if seq_num == self.max_seq + 1: self.max_seq = seq_num self._drain_gap_buffer() return "ok" # 出现跳变:先缓存,再发起重传,避免乱序到达时二次触发 self.gap_buffer[seq_num] = payload return "gap" def _drain_gap_buffer(self): while self.max_seq + 1 in self.gap_buffer: self.max_seq += 1 self.gap_buffer.pop(self.max_seq)

这段状态机的关键在于跳变时先缓存再请求补数。如果收到 12 就直接把 max_seq 推到 12,那么 11 补回来之后会被判成重复消息丢掉,数据就永久缺了一条。血泪经验是:补数期间到达的新消息不能直接推进水位线,必须等缺口填平。

心跳超时的判断也有具体数字。规范写的是频道心跳间隔 3 秒,如果超过两倍心跳间隔没有收到数据包,就可能是网关故障,应当断开重连。也就是 6 秒。这个阈值不要自己拍脑袋改成 30 秒,改大了故障发现慢,改小了网络抖动就误判断线。

3. FAST 解码链路:RawData 里装的不只是一条消息

消息结构的定义在 4.2 节,看起来只有五行表格,但它是整个解码器的地基。所有应用层消息都由 STEP 消息层和 FAST 消息层构成:Standard Header、频道代码 ChannelNo(标记 10201)、RawDataLength(标记 95)、RawData(标记 96)、Standard Trailer。RawData 是 FAST 编码的消息体,而消息体里可以包含本频道的多条 FAST 编码消息。这句话是整个解码器设计的核心约束——你不能假设一个 TCP 包对应一条业务消息。

3.1 四个必备域的读取顺序决定了解码器能不能复用

正确顺序是:先读 Standard Header 拿到消息类型,再读 ChannelNo 决定这条消息交给哪个业务处理器,然后按 RawDataLength 从字节流里切出定长的 RawData 块,最后把这块交给 FAST 解码器。RawDataLength 是字节长度不是字符长度,如果实现语言里字符串和字节数组混用,中文公告内容一定会切错位置。

import struct def parse_step_frame(buf: bytes, offset: int): """从字节流中解析一个 STEP 消息帧,返回 (channel_no, raw_data, next_offset)""" # Standard Header 长度依会话层规范而定,此处以占位长度示意 header_len = read_header_length(buf, offset) pos = offset + header_len # 标记 10201 ChannelNo,按会话层域定义的长度读取 channel_no = struct.unpack_from(">I", buf, pos)[0] pos += 4 # 标记 95 RawDataLength raw_len = struct.unpack_from(">I", buf, pos)[0] pos += 4 # 标记 96 RawData raw_data = buf[pos:pos + raw_len] pos += raw_len # Standard Trailer 略过 return channel_no, raw_data, pos + trailer_len

这里的参数说明比代码本身重要。header_len 和 trailer_len 必须从会话层规范里取实际值,不能照抄示例。raw_len 读出来之后要做一个上限校验,防止脏数据导致超大内存分配,常见做法是设一个比如 1 MB 的硬上限,超过就断开重连并记录日志。这个校验不是防御性编程的洁癖,是真实存在的翻车场景:字节流错位之后读出来的长度字段可能是任意值。

3.2 模板 ID 的两个区间,以及每次解码前必须重置字典

FAST 模板 ID 的编码区间是硬约定:3000 到 3999 是公共消息,4000 到 15999 是实时行情数据。公共消息里心跳是 3001,重传是 3002,用户信息报告是 3003,对应的 STEP 层消息类型分别是 UA001、UA002、UA003。

比模板 ID 更容易出错的是 FAST 字典前值的处理。规范在消息结构表下面专门加了一句注释:解码 FAST 消息体前应该重置解码器的 FAST 字典前值。FAST 协议靠前值复制和增量编码压缩数据,解码器内部维护了一份「上一次的值」,如果跨 RawData 块复用这份状态,上一块里的价格会被带到下一块,解出来的行情价格就全错了。这种错误不会报异常,只会让价格看起来「有点像但又不对」,排查起来极其痛苦。

def decode_raw_data(channel_no: int, raw_data: bytes, decoder): """每个 RawData 块独立解码,块内多条 FAST 消息共享同一份字典""" decoder.reset_previous_values() # 关键:跨块必须重置 messages = [] pos = 0 while pos < len(raw_data): msg, consumed = decoder.decode_one(raw_data, pos) messages.append(msg) pos += consumed # 块内多条消息之间不重置,保持前值连续性 return messages

块内不重置、块间重置,这个边界要写进注释,否则下一个人重构时很容易把 reset 提到循环外面,性能看起来更好了,数据却悄悄错了。上线前的验证方法也简单:拿一段真实行情,把解码结果和快照里的最新价做交叉比对,价格对不上就是字典状态串了。

3.3 重传请求怎么构造:ApplEndSeqNum 填 0 是个开关

重传走的是 9130 端口那条独立会话。请求消息的 STEP 层类型是 UA002,FAST 层模板 ID 3002,关键字段有四个:ResendType(标记 10077)取 1 表示逐笔行情数据、取 2 表示公告信息;ChannelNo 指定频道;ApplBegSeqNum(1182)和 ApplEndSeqNum(1183)指定记录号范围。

ApplEndSeqNum 有一个特殊语义:填 0 时,网关会把它设置成收到重传请求时该频道数据在内存中的最大值。也就是说填 0 等价于「把从起始序号到当前最新的全部补给我」。这个用法在首次建连或者断线时间较长时非常实用,但要注意它只补到请求那一刻为止,补完之后如果实时流还在推,得靠实时会话继续追。

def build_resend_request(channel_no: int, begin_seq: int, end_seq: int = 0, resend_type: int = 1, news_id: str = ""): req = { "MsgType": "UA002", "TemplateID": 3002, "ResendType": resend_type, # 1=逐笔行情 2=公告 "ChannelNo": channel_no, "ApplBegSeqNum": begin_seq, "ApplEndSeqNum": end_seq, # 0 表示补到网关内存中的最大值 } if resend_type == 2: req["NewsID"] = news_id # 为空表示申请公告概要 return req

应答侧的处理同样要看字段。网关返回的重传消息里,ResendStatus(10076)有四种取值:1 完成、2 部分完成、3 无权限、4 数据不可用。只有 1 才能认为补数成功;2 意味着还有缺口,得再发一次请求;3 和 4 属于配置或权限问题,重试没有意义,应该直接告警。Text(58)字段在请求被拒时带回错误代码,日志里一定要打出来,否则线上排查只能靠猜。

公告的处理路径不太一样。每个公告文件有唯一 ID,登录完成后建议立即向网关申请重传公告文件。申请时 NewsID 留空表示要公告概要,概要里包含所有已发布公告文件的 ID,拿到之后逐个比对本地已有文件,缺哪个补哪个。规范给的建议是「完成登录动作之后立即申请」,这条别省,否则连接建立之前的公告就永久缺失了。

4. 数据字典落地:开关类别和行情条目是两套扩展位

数据字典那一章看起来最枯燥,实际是兼容性工作的主战场。规范第五章把域分成会话层和业务层两组,业务层里有两类字段会随着业务演进而不断增加取值:证券实时状态的开关类别(SecuritySwitchType)和快照行情的行情条目类别(MDEntryType)。这两类字段的处理方式,直接决定了你每次交易所升级规范时要不要改代码。

4.1 开关类别:34 回售撤销、35 转融通出借、36 债券回售转售

开关类别的取值在历史上被反复增删。2020 年 1 月的 1.05 版增加了「34-回售撤销」,同时删掉了「18-转股撤单」「19-回售撤单」「24-实时分拆」「25-实时合并」;2020 年 4 月的 1.06 版增加了「35-转融通出借」;2021 年 7 月的 1.10 版增加了「36-债券回售转售」。这种「加新的、删旧的」的演进节奏说明一件事:开关类别不能硬编码成枚举校验。

# 错误示范:遇到未知开关类别直接抛异常 def apply_switch_bad(switch_type: int, value: str): if switch_type not in (1, 2, 3, 4, 5, 34, 35, 36): raise ValueError(f"unknown switch type {switch_type}") # 正确做法:已知类别落库,未知类别记一条统计后忽略 KNOWN_SWITCHES = {1: "申购", 2: "赎回", 34: "回售撤销", 35: "转融通出借", 36: "债券回售转售"} def apply_switch(switch_type: int, value: str, stats: dict): name = KNOWN_SWITCHES.get(switch_type) if name is None: stats["unknown_switch"] = stats.get("unknown_switch", 0) + 1 return None # 静默忽略,不中断消息处理 return {"switch": name, "value": value}

参数说明上,stats 这个计数器不是可选项。未知开关类别虽然要忽略,但必须能观测到——如果某天计数突然从 0 涨到几百,说明交易所在推新业务了,该去查规范更新了。这类「静默忽略但埋点上报」的模式,是应对规范里所有可扩展枚举的通用解法。

4.2 MDEntryType 的增量:从加权平均价到港股买卖盘上下限

行情条目类别的扩充更频繁。2017 年 4 月的 1.02 版增加了「9 加权平均价」「xj 加权平均价涨跌 BP」「xk 昨收盘加权平均价」;2016 年增加了「xh 按盘价」「xi 参考价」;2020 年 8 月的 1.07 版为支持港股开市前时段,增加了 xr 买盘上限价、xs 买盘下限价、xt 卖盘上限价、xu 卖盘下限价四个条目。注意这些取值是字符串而不是数字,解析时别用整型转换。

快照消息的解析要按「条目类别 → 字段」的映射来写,遇到不认识的类别跳过该条目继续解析后面的条目,而不是整条消息丢弃。这个区别很关键:一条快照里同时有最新价、成交量、买卖五档,如果因为一个不认识的条目把整条消息扔了,损失的是整条行情。

条目类别含义引入版本解析注意
9加权平均价1.02数值型,注意精度
xh按盘价1.01字符串枚举
xi参考价1.01 / 1.08集中竞价快照也使用
xj加权平均价涨跌 BP1.02单位是基点,不是百分比
xk昨收盘加权平均价1.02与昨收价不是同一字段
xr/xs/xt/xu港股买卖盘上下限价1.07仅开市前时段有效

4.3 逐笔的两种发送模式与占位消息

2020 年 8 月的 1.07 版引入了一个结构级变化:逐笔行情支持两种不同的发送模式。一种是把逐笔委托(UA201)和逐笔成交(UA202)分成两条独立消息流;另一种是逐笔委托及成交合并成一条消息。这两种模式对下游的撮合还原逻辑影响很大——分开模式下你可以先建委托簿再匹配成交,合并模式下必须按消息内自带的类型标记分流。

选型上我一般倾向分开模式,原因是委托和成交的字段差异大,合并消息里有一半字段永远是空的,解析时的分支判断反而更重。但如果是带宽敏感的场景,合并模式的消息条数少一半,压缩收益明显。这个取舍要在接入方案阶段和网关配置对齐,不能单方面决定。

2021 年 4 月的 1.09 版还增加了逐笔占位消息。债券现券交易业务的逐笔行情在某些时段没有实际成交,但记录号必须连续,占位消息就是用来占住记录号位置的。解析器遇到占位消息要正常推进 max_seq,但不要往委托簿里写任何东西。这一点如果漏了,记录号水位线不前进,后面每一条真实消息都会被判成跳变,重传请求会像雪崩一样发出去。

注意:兼容性要求一共五条,分别针对新增频道、新增应用层消息类型、新增行情类别、新增行情条目类别、新增开关类别。五条的共同指向是「不使用就自动忽略,不作变更」。把这条原则写进解析器的默认分支,比事后补丁可靠得多。

5. 避坑排查:上线后最容易翻车的五个点

规范读得再细,链路跑起来还是会遇到问题。下面这几条是按「现象 → 原因 → 解决」整理的,基本覆盖了接入初期八成以上的故障。

5.1 连接与重传类故障

现象一:跑着跑着连接就断了,没有任何错误日志。原因是流量控制。规范 2.1.5 节写得很直接:如果用户行情系统不能及时处理网关发送的数据,导致网关累积的待发送消息超过设定阈值,网关会立即断开连接。这不是网络问题,是消费速度跟不上。解决办法是把解码和业务处理解耦,收包线程只做拆帧和入队,业务逻辑放到独立线程池;队列长度要监控,堆积超过阈值就告警而不是等断线。另外要确认重连逻辑是带退避的,别写成断线后立即狂重连,那样只会被反复踢。

现象二:重传请求发出去了,返回 ResendStatus=3。原因是权限。3 表示无权限,通常是重传会话没建在 9130 端口,或者接入账号没有开通重传服务。现场版网关根本不提供重传端口,如果部署在现场版环境里发重传请求,只能拿到失败应答。先确认网关版本和端口,再去查账号权限,顺序别反。

现象三:补数补完之后数据还是有缺口,而且缺口位置飘忽。原因是补数期间水位线被错误推进。前面代码里提过,跳变时如果直接把 max_seq 更新到新收到的记录号,中间缺失的那几条补回来就会被判成重复消息丢弃。解决方式是缺口期间用缓冲区暂存,等补数应答把缺口填平再统一推进水位线。还有一种可能是 ApplEndSeqNum 填了具体值而不是 0,导致补数范围不够,这种情况把结束序号改成 0 让它补到最新即可。

5.2 解码与字段类故障

现象四:价格字段偶发出现明显偏离,重放同一段数据又复现不了。十有八九是 FAST 字典前值没重置。跨 RawData 块复用了上一块的解码状态,增量编码的基准值错了,解出来的价格就会带上前一条消息的残留。排查方法是把解码器改成每块强制 reset 之后重放,如果问题消失就确认了。这类问题最坑的地方在于它不报错,只出错数,而且错得「看起来合理」,肉眼看 K 线不一定能发现。

现象五:新增业务上线当天,程序开始大量抛异常。原因通常是解析器对未知枚举做了严格校验。规范每次版本升级都会加新的开关类别或行情条目类别,比如 34 回售撤销、35 转融通出借、36 债券回售转售,或者 xr 到 xu 这四个港股买卖盘限价条目。如果代码里写的是「不在白名单就抛异常」,新业务一来整条链路就挂了。改法是所有可扩展枚举统一走「已知处理、未知忽略并计数」的路径,同时在监控里给未知类别单独建一个指标,涨了就去查规范。

6. 联调自检:用频道统计和心跳间隔做最后一公里验证

规范读通了、代码写完了,还差一步才能真正上线:拿什么证明这条链路是对的。我一般会在联调阶段做三件事,顺序不能乱。

先用频道心跳做链路存活验证。心跳模板 ID 3001,三个关键字段:ChannelNo、ApplLastSeqNum(标记 1350,最后一条行情消息的记录号)、EndOfChannel(标记 10205,频道结束标志)。心跳间隔 3 秒。写一个守夜脚本,把每个已订阅频道的心跳时间戳打出来,超过 6 秒没有心跳就标红。这一步能把「订阅了但网关没推」和「推了但你没收到」区分开,省掉大量扯皮。

再用快照行情频道统计消息做数据完整性校验。每个快照频道都会发统计消息,里面带行情类别和该频道的消息计数。拿统计值和本地入库条数做对账,差值稳定为 0 才算通。这一步的价值在于快照不可重传,丢了就是丢了,只能靠统计对账发现问题。对账周期我一般设成每分钟一次,差值超过阈值就告警。

最后用重传链路做一次人为破坏测试。手动丢掉若干条逐笔消息,看补数请求能不能在预期时间内把缺口填平,填平之后委托簿的最终状态和没丢消息的那一路是否一致。这个测试一定要在仿真环境做,别在生产上试。

检查项判定标准失败时的第一动作
频道心跳任意频道间隔不超过 6 秒查该频道订阅配置与网关推送日志
快照对账统计消息计数与本地入库差值稳定为 0查解析分支是否丢弃了未知条目
逐笔水位线记录号连续,无长期挂起的缺口查重传应答的 ResendStatus
公告完整性登录后公告概要中的 ID 全部落地查 NewsID 为空时是否正确申请概要
未知枚举计数长期为 0 或稳定不变查规范是否有新版发布

有一件事我印象很深:早期接入时觉得快照不能重传是件小事,反正定时发,丢了等下一版就行。直到某次盘后复盘发现某个时点的盘口状态对不上,才知道快照丢一条,那一秒的盘口就是错的,而策略恰好在那秒做了判断。从那以后我每次接入新频道,都强制把「统计对账」和「心跳守夜」两个脚本先跑通再谈业务逻辑,宁可多花半天,也不事后翻日志找那一秒。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表