4. 数据采集与预处理图解

竞价买卖申报不平衡分析的质量,首先取决于数据底座的可靠性。本节围绕一条完整的数据处理链路展开:从交易接口拉取原始申报快照,到清洗异常值、对齐时间戳,最终构建可直接用于不平衡度计算的分析数据集。这条链路上的每一步都会影响后续指标的可信度,因此需要明确每个环节的输入、处理规则与输出形态。

一、从交易接口获取申报数据

集中竞价场景下,申报数据通常通过交易所或行情商的接口以快照(Snapshot)或逐笔(Tick)形式下发。以常见的 Level-2 行情快照为例,每个快照包含时间戳、证券代码、最新价、成交量、委托买入总量、委托卖出总量、买一至买十档价格与数量等字段。对于申报不平衡分析,我们最关心的是委买总量、委卖总量以及各档位的挂单分布。

实际采集时需要注意三个工程细节。第一,接口通常有频率限制,例如每 3 秒一个快照,采集程序应按接口约定节流,避免被限流或封禁。第二,快照是"截面数据",同一时间点只反映当前挂单状态,若要做申报增量分析,需要保存连续快照并做差分。第三,字段命名在不同数据源间不一致,例如委买总量可能叫 total_bid_vol、bid_volume 或 BP1 累加值,采集层应统一映射到标准字段名。

下面是一段示意性的采集伪代码,展示按标准字段落库的基本结构(参数为示例假设):

# 示例:快照采集与标准字段映射(伪代码)
SNAPSHOT_INTERVAL = 3  # 秒,示例假设
FIELDS_MAP = {
    "total_bid_vol": "委买总量",
    "total_ask_vol": "委卖总量",
    "bid_px_1": "买一价",
    "ask_px_1": "卖一价",
}

def fetch_snapshot(symbol):
    raw = api.get_snapshot(symbol)      # 调用交易接口
    record = {std: raw.get(src) for src, std in FIELDS_MAP.items()}
    record["symbol"] = symbol
    record["ts_raw"] = raw["timestamp"]  # 原始时间戳,待对齐
    return record

while trading:
    db.insert(fetch_snapshot("600000"))
    sleep(SNAPSHOT_INTERVAL)

采集完成后,原始表通常保留全部字段与原始时间戳,作为"不可变原始层",后续清洗与对齐都在其副本上进行,保证可追溯。

二、清洗异常值

原始申报数据中常见的异常包括:委买或委卖总量为负、为零但价格非零、单笔快照量级突变(如突然放大百倍)、时间戳重复或倒序、字段缺失。清洗的目标不是"删掉不好看的数据",而是识别并标记这些记录,避免它们污染不平衡度指标。

推荐采用"规则过滤 + 统计标记"两层策略。规则层处理确定性错误:总量小于零直接置空;买卖总量同时为零视为无申报状态,保留但标记 no_quote;时间戳重复保留首条。统计层处理量级异常:对委买总量序列计算滚动中位数与绝对偏差(MAD),当某点偏离超过阈值(例如 5 倍 MAD)时标记为疑似异常,交由人工复核或按前后值插补。

清洗原则:原始层不改动,清洗结果写入独立字段或独立表,并保留 clean_flag 与 clean_reason,便于回溯每一条被处理记录的原因。

举例来说,若某快照委买总量为 120 万手,而前后 10 个快照均在 1.2 万手附近,则该点偏离约 100 倍,明显超出正常波动,应标记为异常而非直接参与计算。若确认是接口字段错位(如把成交量写入了委买量),则整段数据需重新采集。

三、对齐时间戳

不同数据源的时间戳精度和时区可能不同:交易所快照可能是毫秒级本地时间,而另一些接口给的是秒级 UTC 时间。若直接按字符串拼接,会出现同一时刻的数据被错位对齐,导致不平衡度在时间轴上"抖动"。

对齐分两步。第一步统一时区与精度:全部转换为同一时区(如北京时间),并统一到毫秒或秒。第二步按分析粒度重采样:若分析目标是分钟级不平衡度,则将快照按分钟分桶,取每桶最后一个快照作为该分钟的代表值(前向填充),或对桶内快照求均值。选择哪种取决于分析目的——研究挂单存量用末值,研究申报活跃度用均值。

对齐后应做一致性校验:检查时间序列是否严格递增、是否存在长时间空洞(如超过 30 秒无快照,可能为停牌或采集中断),并对空洞区间做显式标记,而不是让插值悄悄填平。

四、构建分析数据集

完成清洗与对齐后,构建面向分析的数据集。核心字段包括:证券代码、对齐后时间戳、委买总量、委卖总量,以及派生指标申报不平衡度。常用定义为:

imbalance = (bid_vol - ask_vol) / (bid_vol + ask_vol)

该指标取值在 [-1, 1] 之间,正值表示买方挂单占优,负值表示卖方占优,接近 0 表示相对均衡。分母为零时(双方均无申报)应置为空值而非 0,避免把"无数据"误读为"均衡"。

数据集还应包含质量字段:clean_flag、gap_flag、snapshot_count(该时间桶内快照数),使分析时可按质量分层过滤。最终输出一张宽表,一行对应一个"证券 × 时间桶",供后续统计与可视化直接使用。

申报数据采集与预处理链路 ① 接口采集 交易接口快照 / 逐笔 委买总量 · 委卖总量 原始时间戳 · 档位价格 原始层:不可变留存 ② 异常值清洗 规则层:负值 / 零值 / 重复 统计层:MAD 偏离标记 clean_flag · clean_reason 异常标记而非直接删除 ③ 时间戳对齐 统一时区与精度 按分钟分桶重采样 空洞检测 · gap_flag 末值 / 均值按目的选择 ④ 分析数据集 证券 × 时间桶 宽表 委买 / 委卖总量 imbalance 派生指标 质量字段随行携带 数据形态变化示意(示例假设) 原始快照(3 秒一条) 10:00:00 买 12000 卖 11800 10:00:03 买 12100 卖 11750 10:00:06 买 -500 卖 11900 ← 异常 10:00:09 买 12200 卖 11850 清洗后(标记异常) 10:00:00 12000 / 11800 ok 10:00:03 12100 / 11750 ok 10:00:06 null / 11900 flag=neg 10:00:09 12200 / 11850 ok 分钟对齐 + 派生指标 10:00 买 12200 卖 11850 imbalance = +0.0146 snapshot_count = 4 gap_flag = 0 关键校验:时间序列严格递增 · 分母为零置空 · 空洞显式标记 · 每条清洗记录保留原因字段 imbalance = (委买总量 − 委卖总量) / (委买总量 + 委卖总量),取值 [-1, 1],正值买方占优

五、质量校验与常见问题

数据集构建完成后,建议做三项快速校验。其一,抽样核对:随机抽取若干时间桶,回到原始快照手工核对委买委卖总量与不平衡度,确认映射与计算无误。其二,分布检查:绘制不平衡度直方图,正常交易日应集中在 0 附近并呈近似对称分布;若出现大量 ±1 的极端值,往往说明分母过小或清洗不彻底。其三,空洞统计:统计 gap_flag=1 的比例,若超过 5%,需排查采集中断或接口限流问题。

常见误区:把清洗后的空值直接填 0 参与计算。空值代表"该时刻数据不可用",填 0 会人为制造"买卖均衡"的假象,扭曲不平衡度序列的统计特征。正确做法是保留空值并在分析时按质量字段过滤或单独说明。

至此,一条从接口到分析数据集的链路闭合:采集保证字段完整与可追溯,清洗保证异常被识别而非被掩盖,对齐保证时间轴一致,数据集则把质量信息与派生指标一并交付。后续所有不平衡度的统计、分组与可视化,都应基于这张带有质量标记的分析宽表展开,而不是回到原始快照临时计算。