2. 交易数据基础

在滑动窗口交易聚合与实时评分系统中,数据是模型的燃料,而数据的组织方式决定了窗口能否被正确切分、特征能否被准确计算。很多实时风控或量化策略的偏差,并非来自算法本身,而是源于对交易数据形态和时间语义的理解不足。本节从交易流水、订单簿、逐笔成交三类核心数据出发,厘清时间戳、事件时间与处理时间的关系,为后续窗口聚合打下基础。

一、三类核心交易数据及其特征

交易流水是账户维度的资金变动记录,通常由核心账务系统产生,包含交易号、账号、金额、币种、交易类型、状态等字段。它的特点是强一致性、低频、批量落库,一条流水往往代表一次已确认的资金转移。交易流水适合做账户级累计指标,例如“近5分钟同一账户转出总额”,但其延迟通常较高,不适合作为毫秒级实时评分的唯一数据源。

订单簿是市场维度的买卖意愿快照,记录每个价位上的委托数量。它分为增量更新(order add/cancel/modify)和周期快照(snapshot)两种形态。订单簿数据量大、更新频繁,包含价格、方向、数量、订单ID等。其核心特征是状态性强:任意时刻的订单簿是此前所有增量的累积结果,因此处理时必须维护状态,不能只看单条消息。

逐笔成交是市场维度的实际成交记录,包含成交价、成交量、买卖方向、成交时间、成交编号等。它是最接近“事件流”的数据,天然适合滑动窗口聚合,例如“近1分钟大单买入占比”“近30秒成交笔数”。逐笔成交的乱序和重复问题也最突出,是事件时间处理的主要战场。

数据类别 典型来源 更新频率 是否含状态 窗口聚合适用性
交易流水 账务核心 低(秒~分钟) 否 账户级累计、低频窗口
订单簿 交易所行情 高(毫秒~微秒) 是(需维护快照) 盘口特征、深度变化
逐笔成交 交易所成交流 高(毫秒~微秒) 否(事件独立) 高频窗口聚合首选

二、时间戳、事件时间与处理时间

时间语义是实时系统的第一性问题。同一条逐笔成交记录,至少涉及三个时间:

  • 事件时间(Event Time):交易在交易所实际发生的时间,由交易所时钟打标,是业务上真正有意义的时间。
  • 处理时间(Processing Time):消息到达计算引擎、被算子处理的本地时间,取决于网络、队列和调度。
  • 摄入时间(Ingestion Time):消息进入流处理系统的时间,介于两者之间,常被用作折中。

事件时间与处理时间之间通常存在乱序和延迟。例如交易所10:00:00.100成交的一笔单,可能因网络抖动在10:00:00.350才到达,处理时间比事件时间晚250毫秒。如果按处理时间开窗,这笔成交会被错误地归入后一个窗口,导致“近1秒成交量”在边界处出现尖刺或空洞。

核心原则:滑动窗口的边界应由事件时间决定,处理时间只用于监控延迟和触发补偿。窗口聚合必须基于事件时间语义,才能保证结果可重放、可对齐。

三、方法流程:从原始消息到可聚合事件

要让逐笔成交进入滑动窗口,通常需要一条标准化的处理链路:

  1. 接入与解析:从行情网关或消息队列消费原始报文,解析出成交价、量、方向、事件时间戳。
  2. 去重与排序:按成交编号或交易所序列号去重,并在允许延迟范围内按事件时间排序。
  3. 水位线推进:用 watermark 表示“事件时间已推进到某点,早于该点的事件基本到齐”,用于触发窗口计算。
  4. 窗口分配:将每条成交按事件时间分配到对应的滑动窗口区间。
  5. 聚合与评分:在窗口内计算成交量、笔数、大单占比等特征,送入实时评分模型。
// 以 Flink 风格伪代码示意事件时间滑动窗口
stream
  .assignTimestampsAndWatermarks(
      WatermarkStrategy
        .<Trade>forBoundedOutOfOrderness(Duration.ofMillis(200))
        .withTimestampAssigner((trade, ts) -> trade.eventTime)
  )
  .keyBy(Trade::getSymbol)
  .window(SlidingEventTimeWindows.of(Time.seconds(1), Time.millis(200)))
  .aggregate(new TradeAggregator());

上述配置中,forBoundedOutOfOrderness(200ms) 表示允许事件时间最多乱序200毫秒,滑动步长200毫秒、窗口长度1秒,意味着每个成交会同时落入最多5个窗口。这正是滑动窗口计算量放大的原因,也是后续章节要优化的重点。

四、案例与场景:一次边界错位引发的评分抖动

某实时风控系统用“近1秒买入金额”作为评分特征。初期实现按处理时间开窗,上线后发现同一账户在行情剧烈波动时评分频繁跳变。复盘发现:交易所撮合高峰时,成交消息在网关排队,处理时间比事件时间平均晚300毫秒,且延迟分布不稳定。结果同一批成交被切到不同窗口,1秒特征值忽高忽低,模型误判为异常交易。

改为事件时间窗口并设置200毫秒水位线后,特征值与交易所侧统计基本对齐,评分抖动消失。这个案例说明:时间语义选错,再好的模型也会被数据切分方式拖垮。

实践建议:在接入层就保留原始事件时间字段,不要用接收时间覆盖它;同时单独记录处理时间,用于计算端到端延迟指标,两者分工明确。

五、常见误区与复盘清单

  • 误区一:用处理时间代替事件时间。 在低延迟环境下看似可用,一旦出现网络抖动或消费积压,窗口结果立即失真。
  • 误区二:忽略乱序,假设消息有序到达。 多分区、多网关场景下乱序几乎必然发生,必须显式设置水位线或允许延迟。
  • 误区三:把订单簿快照当事件流直接聚合。 快照是状态,不是增量事件,直接求和会重复计算。
  • 误区四:时间戳单位混用。 秒、毫秒、微秒混用会导致窗口错位几个数量级,务必在解析层统一。
复盘检查点:窗口边界是否由事件时间决定?水位线延迟是否覆盖了P99乱序?去重键是否唯一且稳定?时间戳单位是否统一?这四点任一缺失,都可能导致聚合特征不可信。

理解交易流水的低频一致性、订单簿的状态性、逐笔成交的事件性,并严格区分事件时间与处理时间,是滑动窗口交易聚合的第一道工程防线。只有数据形态和时间语义都对齐,后续的特征计算与实时评分才有可靠的基础。