2. 逐笔数据流与特征计算对象剖析

逐笔特征增量更新的第一步,不是写计算逻辑,而是把“数据长什么样、按什么顺序到达、特征在哪个窗口上定义”这三件事彻底厘清。逐笔行情本质上是事件流:每一笔成交、每一次委托的挂撤,都是一个带时间戳和序列号的离散事件。特征计算的对象不是某一张快照表,而是这条事件流上按规则切分出的窗口。理解数据结构、对齐机制与窗口定义,才能保证增量更新算出的值与全量重算一致,同时把延迟压在可控范围内。

逐笔成交与逐笔委托的数据结构

以沪深Level-2逐笔数据为例,逐笔成交通常包含:证券代码、成交时间(精确到毫秒或更细)、成交价格、成交数量、成交方向(买方主动/卖方主动,部分市场用买卖盘序号标识)、买方委托序号、卖方委托序号、以及交易所侧的序列号。逐笔委托则包含:委托时间、委托价格、委托数量、买卖方向、委托类型(限价/市价/本方最优等)、委托序号。关键点在于,成交记录通过买方委托序号和卖方委托序号与委托记录建立关联,这使得“某笔委托被成交了多少、还剩多少”可以在流上增量维护,而不必回查历史。

工程上要特别注意字段的语义差异:成交方向不等于买卖方向,它描述的是主动方;委托序号在同一交易日内单调递增但不一定连续;部分市场的逐笔委托与逐笔成交分两条通道推送,到达顺序不保证与交易所撮合顺序一致。因此,特征计算前必须先做序列对齐,否则窗口内的“先委托后成交”因果关系会被打乱。

时间戳与序列对齐

对齐有两个维度:时间维度和序列维度。时间戳来自交易所,但不同通道的时钟可能略有偏差,且网络传输会引入乱序与抖动。序列号则是交易所为每条记录分配的单调标识,是判断真实先后顺序的权威依据。实践中通常以序列号为主键做排序与去重,时间戳仅用于窗口切分和延迟度量。

一个可落地的对齐策略是维护一个小的重排缓冲区:记录到达后先入缓冲区,按序列号排序,当缓冲区中出现连续序列段或等待超过设定阈值(例如50毫秒)时,将已确认有序的记录放出。这样既处理了轻微乱序,又不会因等待缺失序列而无限阻塞。放出后的记录进入特征计算管线,同时更新“已处理最大序列号”水位线,水位线用于判断窗口是否可以闭合。

# 重排缓冲区的伪代码示意
buffer = {}
max_seq_seen = 0
def on_record(rec):
    buffer[rec.seq] = rec
    max_seq_seen = max(max_seq_seen, rec.seq)
    # 放出连续段
    while (next_seq in buffer):
        emit(buffer.pop(next_seq))
        next_seq += 1
    # 超时强制放出,避免阻塞
    if now() - oldest_arrival_time(buffer) > 50ms:
        emit(buffer.pop_min_seq())

需要强调的是,序列对齐的目标不是追求全局完美排序,而是在低延迟约束下做到“局部有序、因果不乱”。对于特征计算而言,只要同一窗口内的成交与委托相对顺序正确,跨窗口的轻微延迟可以通过水位线机制容忍。

特征计算窗口的定义

逐笔特征常用的窗口有三类:固定时间窗口(如最近1秒、最近5秒)、固定事件数窗口(如最近100笔成交)、以及基于序列号的逻辑窗口(如自上次快照以来的全部增量)。时间窗口适合刻画“近期活跃度”,事件数窗口适合刻画“近期形态”,逻辑窗口适合与快照对齐做增量修正。

窗口定义必须明确三个要素:窗口边界如何确定(事件时间还是处理时间)、窗口是否滑动、以及窗口闭合条件。以“最近1秒主动买入成交量占比”为例,窗口边界应使用事件时间,窗口随每条新成交滑动,闭合条件是水位线超过窗口右边界。若用处理时间,网络抖动会导致同一批数据在不同时刻算出不同结果,破坏可复现性。

建议在特征配置中显式声明:窗口类型、窗口长度、时间基准(事件时间/处理时间)、以及乱序容忍阈值。这三项决定了增量结果与全量重算能否对齐。

低延迟约束下的数据流拓扑

把上述对象串起来,就得到一条典型的数据流拓扑:行情接入 → 解码与字段校验 → 重排缓冲与序列对齐 → 事件分发 → 窗口状态更新 → 特征输出。低延迟设计的关键在于把“重”和“轻”分开:解码与校验可以并行多线程,重排缓冲保持单线程以保证顺序,窗口状态更新采用增量累加而非全量扫描。

下图展示了这一拓扑中各环节的职责与数据形态变化,以及水位线在窗口闭合中的作用。

行情接入 逐笔成交/委托 解码与校验 字段解析·去重 重排缓冲 按序列号排序 事件分发 成交/委托分流 窗口状态更新 增量累加 特征输出 占比·强度·斜率 水位线(已处理最大序列号) 判断窗口能否闭合 数据形态变化: 原始字节 → 结构化事件(含序列号/时间戳/委托序号) → 有序事件流 → 窗口内增量状态 → 特征值 低延迟要点:解码并行、重排单线程保序、窗口增量累加、水位线驱动闭合,避免全量重扫。

在这条拓扑中,窗口状态更新是延迟最敏感的一环。以“最近1秒主动买入成交量占比”为例,增量维护只需保存一个滑动队列:新成交入队时,若方向为买方主动则累加主动量,同时累加总量;出队时做减法。占比 = 主动量 / 总量。整个过程是O(1)摊销,不需要每次重扫窗口内全部成交。对于“最近100笔成交的平均单笔量”,同样用队列加运行和即可。

最后要建立一致性校验习惯:在离线环境用同一批逐笔数据分别跑全量重算与增量更新,逐窗口比对特征值。若出现偏差,优先检查三处——重排缓冲是否漏放或重复放出记录、窗口边界用的是事件时间还是处理时间、以及委托与成交的关联序号是否在乱序时被错误匹配。把这三处对齐,增量特征才能在低延迟下稳定复现全量结果。