一次盘口数据误读复盘:从 bids/asks 展示到信号候选的边界
一、一次复盘那笔大单去哪了晚上复盘时同事指着监控日志里的一条记录问我昨天下午两点零三分系统标记了 600519 买一出现大单为什么后来什么都没发生日志是这样的[14:03:05] bid_qty_change: 1200 lots 1850.00 [14:03:05] signal_candidate: large_bid_detected [14:03:07] bid_qty_change: -1100 lots 1850.00 [14:03:07] signal_candidate: cleared两秒钟大单来了又走了。系统在这两秒内触发了一次“观察”然后自行清除。没有成交没有方向什么都没有。排查过程不复杂我们把那条记录的 raw_snapshot 调出来对比了前后三秒的完整盘口快照。结论是——那笔大单是挂单结构的正常变化。卖一在那一瞬间被成交掉了新的卖一上移了一个价位原来压在买一附近的挂单被重新归类看起来像是“买一突然多了一笔大单”。实际上供需关系没变只是报价结构变了。问题不在数据。数据如实地反映了那一刻的盘口状态。问题在于设计系统把“盘口上出现了一笔大单”等同于“出现了一个值得关注的信号”中间没有经过状态观察和可复盘验证。这次复盘之后我们把盘口数据的处理拆成了三层展示层只显示状态观察层做诊断信号候选层必须可回测、可复盘。每层之间有显式的升级条件不满足条件的数据不能进入下一层。二、展示、状态观察、信号候选是三层不同的东西从“看见盘口”到“研究信号”中间必须经过字段、时间和样本验证。第一层盘口展示这是最基础的层次。系统能收到 bids/asks 数据能把价格和数量渲染在屏幕上。屏幕上的数字在跳动颜色在变化信息在流动。展示层只做一件事把数据源返回的盘口快照原样呈现。不做判断不做比较不做标记。它回答的问题是“现在屏幕上有什么”不回答“这意味着什么”。但很多系统的第一版实现直接把展示层的数据接到了判断逻辑里。看到买一量大就觉得支撑强看到卖一量薄就觉得要突破。这是把静态截图当成决策输入和看着一张照片猜测下一秒会发生什么一样不可靠。第二层状态观察当你不再盯着单笔大单找方向而是系统性地用盘口数据观察市场状态时你进入了第二层。这一层可以观察四样东西价差变化bid/ask spread 在扩大还是缩小流动性厚薄买一侧和卖一侧的深度是否均衡有没有突变报价完整性某一侧突然变薄或消失——这是异常信号不是交易机会盘口异常价格跳空、挂单突变、刷新中断状态观察层只做诊断不做预测。它能告诉你市场现在是活跃还是冷清、稳定还是波动、正常还是异常。它给你的不是“应该交易”而是“现在适不适合观察”。设计取舍状态观察层的输出是结构化的状态标记不是信号。如果某一侧深度突然变薄观察层的输出是market_status: abnormal_thinning附带时间戳和当前快照。它不会输出action: sell。从“状态异常”到“交易决策”之间隔着信号候选层的一整套验证流程。第三层信号候选当你把观察内容量化为可回测的规则并且通过了字段、时间、机制和样本的验证之后盘口数据才进入“信号候选”的范畴。注意是“候选”不是“有效信号”。这一层有三道门槛字段关bids/asks 的价格和数量语义是否明确层级定义是否清晰——你看到的是一档还是五档还是十档历史样本是否完整可核对如果数据源只返回一档盘口但你的信号逻辑假设能看到五档以上的深度变化你的信号从一开始就少了一半输入维度。时间关时间戳是行情发生时间还是接收时间刷新是全量快照还是增量更新两次更新之间的间隔是否稳定买一卖一在几十毫秒内可能轮换多次如果你的时间戳是接收时间不同延迟的快照排在一起你看到的时序可能和真实发生的时序完全相反。机制关你的判断逻辑在不同市场机制下是否一致A 股涨停板上的买一挂几十万手是封板机制不是“买盘强劲”。美股同一只股票在不同交易所的盘口可能不一样。港股有竞价时段和持续交易时段的区别。同一个盘口现象在这些不同制度下含义完全不同。绝大多数人困在第二层。不是因为不够聪明是因为从“观察”到“候选”之间隔的不是一个指标而是一整套验证流程。三、真正要检查的 5 个问题在把盘口数据从“观察”升级为“信号候选”之前有五件事必须查清楚。① 你看到的是一档盘口还是多档深度一档盘口只有买一卖一多档深度能看到更多层挂单。如果你的信号逻辑假设能看到五档以上的深度变化但数据源只返回一档你的信号从一开始就少了一半输入维度。② bids/asks 里的 price、size/qty到底代表挂单还是成交盘口数据返回的是挂单价和挂单量不是成交价和成交量。这两个概念在数据字段里可能只差一个字段名但在判断逻辑里差一个世界。买一量突然增加真实原因可能是原来的卖一被成交掉了新的卖一价格更高原来的买一价位的挂单被重新归类——供需关系没变只是报价结构变了。③ timestamp 是行情发生时间、服务端处理时间还是你本地收到时间不同数据源的时间戳语义不同。如果你的时间戳是接收时间不同延迟的快照排在一起你看到的时序可能和真实发生的时序完全相反。④ 刷新是完整快照、增量更新还是展示层截取完整快照每次都发全量数据但两次快照之间发生的快速变化你不知道。增量更新只发变化的部分但如果推送有节流或丢包中间的关键变动可能被漏掉。你看到的刷新频率是前端 UI 的更新速率还是交易所原始数据的真实频率⑤ 你的判断能不能跨市场复用A 股、美股、港股的交易机制不同同一个盘口现象在不同市场含义完全不同。跨市场直接套用盘口判断框架等于在不懂规则的情况下做出推断。四、字段验证表与日志字段设计4.1 盘口字段验证表字段核对点不通过时处理symbol与请求一致含正确后缀阻断need_reviewbids为数组每项含 price 和 size/qty阻断need_reviewasks同上阻断need_reviewtimestamp整数且非 bool语义明确阻断标记 time_unknown层级数量确认是几档深度记录不阻断但标记 level_mismatchprice 字段可解析为有限 Decimal阻断fail closedsize/qty 字段可解析为有限 Decimal阻断fail closed4.2 日志字段设计每条盘口快照落日志时至少包含以下字段。这不是业务日志是可追溯的证据记录。字段来源说明symbol请求参数规范化后的品种代码source数据源标识区分多源接入timestamp_received客户端本地接收时间timestamp_source数据源返回原始时间戳market_status数据源返回或推导open/closed/pre_market/unknownbid_price_1数据源返回买一价bid_size_1数据源返回买一量ask_price_1数据源返回卖一价ask_size_1数据源返回卖一量spread计算ask_price_1 - bid_price_1depth_levels数据源返回实际返回的档位数status校验结果pass / need_review / fail_closedraw_snapshot数据源返回完整原始返回五、raw_snapshot 保存方案与状态机5.1 raw_snapshot 保存raw_snapshot是数据质量的最后一道防线。它不判断对错但让每一次冲突和异常变得可追溯。保存方案很简单但有几个约束必须遵守原始返回在落日志之前不做任何字段转换保留数据源返回的原始结构和类型。落库或写文件时raw_snapshot作为一个完整 JSON 字段存储不拆散成独立列——拆散可能改变嵌套层级影响后续回放。本地文件按日期和 symbol 分区raw_snapshots/2026-06-30/600519.SH/。5.2 fail closed / need_review 状态机校验结果分三种状态不是二元的“成功/失败”。状态含义触发条件下游行为pass所有字段校验通过必填字段齐全、类型正确、数值合法进入正常处理流程need_review部分字段异常但快照可保存层级不匹配、市场状态未知、可选字段缺失快照写入日志标记需人工复查fail_closed关键字段校验失败symbol 不匹配、bids/asks 为空、price 解析失败阻断不进入下游记录错误详情设计考量为什么需要need_review这个中间状态因为不是所有字段异常都应该阻断。市场状态未知时快照仍有参考价值。层级不匹配时数据仍可用于一档分析。但如果因为“不完全符合预期”就丢弃数据大量有效样本会白白浪费。need_review让数据先进日志让复查后决定不默认为失败也不默认为安全。六、TickDB 在这里的合理位置【适合谁】已经在看盘口、bids/asks、深度数据但想把观察过程结构化记录下来的人——量化行情观察员、金融研究员、需要验证行情字段的数据工程读者。【解决什么】TickDB 可以作为候选结构化行情入口帮助你把盘口和深度相关字段、时间信息、样本记录和异常排查放进同一套验证流程里。它不证明你的盘口信号有效不替你判断买卖不提供完整 Level 2、NBBO、暗池、全量订单簿或高频能力。【怎么验证】用自己的 symbol 和市场场景按五个问题逐项核对层级、字段语义、时间戳、刷新语义、跨市场适用性。保存请求参数、原始返回和检查时间。先证明数据能被解释再讨论它是否能进入信号研究。【不适合什么】不适合用来证明某个盘口信号一定有效不适合做荐股不适合承诺收益不替代策略回测、样本检验和风控系统。七、盘口能做什么不能做什么能做不能直接做观察 bid/ask spread预测涨跌判断流动性状态证明策略有效发现报价缺失或异常给买卖建议记录盘口变化样本重建完整订单流作为信号研究的候选输入承诺高频做市或套利能力验证字段结构和数据质量证明某个数据源永远更准