3、数据获取与清洗:A股Level-2行情数据获取、逐笔成交数据清洗、订单簿数据重构

好,咱们进入实战环节。前面聊了那么多噪声交易者的行为特征,现在得动手拿数据了。说实话,A股的Level-2行情,是我见过最「实在」的数据之一。为什么?因为它把每一笔挂单、每一笔成交都摆在你面前。你想想看,这相当于你站在交易所的服务器旁边,看着所有订单在眼前流动。

我个人习惯把Level-2数据称为「市场的X光片」。普通行情只能看到收盘价、成交量这些表面指标,而Level-2能让你看到订单簿的深度、逐笔成交的细节。噪声交易者的那些小动作——比如频繁撤单、小单试探、大单拆单——在Level-2数据面前,基本是透明的。

3.1 数据获取:从哪里拿,拿什么

先说说数据源。目前国内主流的Level-2数据提供商有几家:万得、聚宽、通联、米筐。我个人比较推荐用聚宽或者通联的API,因为它们的接口设计比较Pythonic,文档也相对清晰。

获取数据时,你主要关注三个维度:

  • 逐笔成交数据:每一笔成交的时间、价格、数量、买卖方向
  • 十档行情数据:买卖各10档的挂单价格和数量
  • 逐笔委托数据:每一笔挂单的详细信息(包括撤单)

这里有个坑,我一开始没注意。很多数据提供商把「逐笔成交」和「逐笔委托」混在一起给。但实际上,成交是成交,委托是委托。成交是已经发生的交易,委托是还没成交的挂单。这两个数据要分开处理。

核心要点:噪声交易者的识别,主要依赖逐笔成交数据中的「订单拆分」和「频繁撤单」特征。而订单簿重构,则依赖十档行情数据。

3.2 逐笔成交数据清洗:别让脏数据坑了你

拿到原始数据后,第一件事不是分析,是清洗。我在项目中遇到过好几次,因为数据没洗干净,导致回测结果异常漂亮——后来发现是数据里混入了「虚拟成交」记录。嗯,有些数据商为了补全缺失的tick,会插入一些插值数据。这些数据在统计上看起来合理,但实际交易中根本不存在。

清洗逐笔成交数据,我一般按以下步骤来:

  1. 去重:同一笔成交可能被重复推送,按「时间+价格+数量+方向」去重
  2. 过滤异常价格:比如价格超过当日涨跌停板,或者价格跳变超过3%的(除非是开盘集合竞价)
  3. 检查买卖方向:有些数据商把「B」和「S」标反了,可以用「价格是否在买一卖一之间」做交叉验证
  4. 时间对齐:把毫秒级的时间戳对齐到交易所的时钟(有些数据商的时间戳有偏移)

举个例子,下面是我常用的清洗函数:

def clean_tick_data(df):
    # 去重
    df = df.drop_duplicates(subset=['time', 'price', 'volume', 'direction'])
    
    # 过滤异常价格
    df = df[(df['price'] > 0) & (df['price'] < df['upper_limit'])]
    
    # 检查买卖方向
    df['direction_valid'] = df.apply(
        lambda x: check_direction(x['price'], x['bid1'], x['ask1']), 
        axis=1
    )
    df = df[df['direction_valid']]
    
    return df

小技巧:清洗时别急着删数据。我习惯把「被过滤掉的数据」单独存一份,回头看看是不是自己的过滤条件太严格了。有时候,那些「异常数据」恰恰是噪声交易者的痕迹。

3.3 订单簿数据重构:从快照到连续流

订单簿重构,说白了就是把每隔几秒推送一次的「快照数据」,还原成连续的订单流。为什么要做这个?因为噪声交易者的撤单行为,在快照数据里是看不到的。你只能看到「当前挂单是多少」,但看不到「这3秒内撤了多少单又挂了多少单」。

重构的方法其实不复杂:

  • 用第一个快照作为初始状态
  • 然后逐笔应用「逐笔委托数据」中的挂单和撤单
  • 每来一笔成交,就更新对应的挂单数量
  • 定期用新的快照做「校准」,防止累积误差

这里有个关键点:校准。我刚开始做的时候,以为只要逐笔更新就行。结果跑了半天,订单簿里的挂单数量和实际快照差了十万八千里。为什么?因为有些委托数据丢失了,或者数据商推送的顺序有问题。

所以我现在都这么做:每收到10笔委托更新,就用最新的快照做一次「硬同步」。如果偏差超过1%,就全部用快照覆盖,重新开始。

def rebuild_orderbook(snapshot, trades, orders):
    orderbook = snapshot.copy()
    sync_counter = 0
    
    for order in orders:
        # 应用委托更新
        orderbook = apply_order(orderbook, order)
        sync_counter += 1
        
        # 每10笔校准一次
        if sync_counter % 10 == 0:
            latest_snapshot = get_latest_snapshot()
            if check_deviation(orderbook, latest_snapshot) > 0.01:
                orderbook = latest_snapshot
                sync_counter = 0
    
    return orderbook

注意:订单簿重构非常消耗内存。如果你同时处理5000只股票,每只股票每秒更新几十次,内存很快就爆了。我建议只对「目标股票池」做实时重构,其他股票用快照数据就够了。

3.4 知识体系总览

说了这么多,咱们用一张图把整个流程串起来。这张图是我自己画的数据处理流水线,从原始数据到最终可用于分析的订单簿,每一步都有对应的处理逻辑。

Level-2 数据获取与清洗流程 数据提供商API 逐笔成交数据 十档行情数据 逐笔委托数据 清洗步骤 去重 → 过滤异常 → 方向校验 清洗步骤 去重 → 价格校验 → 时间对齐 清洗步骤 去重 → 撤单标记 → 顺序校验 订单簿重构(含校准) 可用于分析的订单簿

这张图里,我特意把「校准」环节标红了。为什么?因为这是最容易出问题的地方。我见过太多人,订单簿重构出来之后,买卖价差是负的——这明显是数据没校准好。记住,订单簿重构不是一次性的工作,而是一个持续校准的过程。

好了,数据获取和清洗这块就聊到这儿。下一节咱们会基于这些干净的数据,开始真正识别噪声交易者的行为模式。到时候你会发现,数据清洗花的时间,绝对值得。

专注资料整理