2. 金融数据基础

在构建实时回放与补数链路之前,必须先厘清“数据到底长什么样”。金融数据并非单一表格,而是由行情、交易、订单、成交等多条数据流交织而成。它们各自携带不同的时间戳、序列号,并以快照或增量的形式到达。理解这些基础概念,是后续设计回放引擎、补数策略和一致性校验的前提。

一、核心数据类型:四条并行的数据流

金融实时链路中,最常处理的数据类型可以归纳为四类,它们描述的市场视角完全不同:

  • 行情数据(Market Data):描述市场报价状态,包括买一卖一、盘口深度、最新价、成交量、涨跌幅等。行情又分为 Level-1(最优一档)和 Level-2(多档盘口、逐笔委托/成交)。
  • 订单数据(Order):描述“委托意图”,即某账户在某个时刻提交的买卖指令,包含订单号、方向、价格、数量、订单类型、状态(已报、部成、全成、撤单)。
  • 成交数据(Trade):描述“实际发生的撮合结果”,包含成交编号、成交价、成交量、买卖双方订单号、成交时间。成交是订单状态迁移的触发事件。
  • 交易数据(Transaction/Account):描述账户维度的资金与持仓变动,如出入金、冻结、解冻、持仓更新,通常由成交和结算驱动。

这四类数据在回放时往往需要按时间轴对齐。例如,一笔成交会同时改变订单状态、账户持仓和行情最新价。若只回放行情而忽略订单与成交,就无法还原真实的撮合过程。

二、时间戳:三种时间语义不可混用

时间戳是金融数据最容易被低估的字段。同一条数据通常携带多个时间戳,含义截然不同:

时间戳类型 含义 典型用途
事件时间(Event Time) 交易所或撮合引擎产生该事件的时间 回放排序、行情还原、延迟分析
接收时间(Ingest Time) 本地网关或采集程序收到数据的时间 链路延迟监控、补数边界判断
处理时间(Process Time) 下游计算引擎实际处理该数据的时间 流式计算窗口、状态更新

回放链路必须明确以哪个时间戳作为排序基准。通常以事件时间为主,接收时间用于判断数据是否迟到或乱序。若混用,会导致回放出的行情与真实市场顺序不一致。

常见误区:直接用本地接收时间排序回放。网络抖动或批量补数时,接收时间会严重偏离事件时间,导致成交先于订单出现、行情价格跳变等逻辑错误。

三、序列号:判断连续性与丢包的标尺

序列号是数据源为每条消息分配的单调递增编号。它的核心价值在于:通过序列号是否连续,可以判断是否丢包、重复或乱序。

不同数据源的序列号粒度不同:

  • 交易所逐笔行情:通常按频道(Channel)或合约维护独立序列号。
  • 订单/成交流:按账户或按市场维护序列号。
  • 快照流:每个快照携带一个版本号或序列号区间。
# 序列号连续性检查示例
last_seq = 1001
for msg in stream:
    if msg.seq == last_seq + 1:
        process(msg)
    elif msg.seq <= last_seq:
        log.warn("duplicate or out-of-order", seq=msg.seq)
    else:
        log.error("gap detected", expect=last_seq+1, got=msg.seq)
        trigger_backfill(last_seq+1, msg.seq-1)
    last_seq = msg.seq

补数链路的核心触发条件之一,就是序列号出现空洞。发现 gap 后,需要向数据源或历史库请求缺失区间,再按事件时间插入回放队列。

四、快照与增量:两种数据到达形态

快照(Snapshot)是某一时刻的完整状态切片,增量(Increment/Delta)是相对上一状态的变化量。二者在回放与补数中的处理方式完全不同。

快照:包含全量字段,可直接覆盖本地状态,适合初始化、断点恢复和定期校准。
增量:只包含变化字段,体积小、频率高,必须依赖前序状态才能正确应用。

典型场景中,行情系统会先推送一次快照,随后持续推送增量。回放引擎需要维护一个“当前状态”,按序列号顺序应用增量。若中间丢失增量,状态就会漂移,必须通过下一次快照或补数来修复。

快照 + 增量:状态演进与序列号空洞 时间 快照 seq=1000 增量 1001 增量 1002 丢失 1003 增量 1004 快照 seq=1005 状态S0 状态S1(应用1001-1002) 状态漂移(缺1003) 状态S2(快照校准) 触发补数 快照 增量 序列号空洞

五、案例:一笔成交如何贯穿四类数据

假设账户 A 在 10:00:00.123 提交限价买单,订单号 O1001,价格 10.50,数量 1000。交易所撮合后产生成交 T2001,成交价 10.50,数量 500。这条链路会同时影响多个数据流:

  1. 订单流:O1001 状态从“已报”变为“部成”,剩余 500。
  2. 成交流:新增 T2001,携带买卖双方订单号、成交价量、事件时间。
  3. 行情流:最新价更新为 10.50,成交量增加 500,盘口买一数量减少。
  4. 交易流:账户 A 持仓增加 500,冻结资金相应扣减。

回放时,若只按行情流回放,会看到价格变化但无法解释原因;若只按订单流回放,又缺少市场背景。正确做法是以事件时间为主轴,将四类数据合并排序,按序列号校验连续性,再逐条应用到状态机。

实践建议:在回放引擎中为每类数据维护独立的序列号水位线(watermark),并记录最近一次快照的版本号。当增量序列号与水位线不连续时,优先用快照重置状态,再补缺失增量,避免状态漂移累积。

六、误区与复盘

  • 误区一:把接收时间当事件时间。补数批量导入时,接收时间集中且乱序,回放结果与真实市场顺序不符。复盘时应统一以事件时间排序,接收时间仅用于延迟统计。
  • 误区二:忽略序列号粒度。不同频道、不同合约的序列号独立递增,若用全局序列号判断连续性,会误报大量空洞。应按数据源和频道分别维护水位线。
  • 误区三:增量丢失后继续应用后续增量。状态会持续漂移,且无法通过后续增量自愈。正确做法是立即触发补数或等待下一次快照校准。
  • 误区四:快照与增量混用同一套解析逻辑。快照可直接覆盖状态,增量必须依赖前序状态。解析层应明确区分消息类型,避免将快照当增量应用导致字段缺失。

掌握这些基础概念后,后续设计回放链路时就能明确:以事件时间排序、以序列号检测空洞、以快照校准状态、以增量驱动更新。这四条原则贯穿实时回放与补数的全过程,也是判断链路是否可靠的核心标尺。