3. 数据质量初探:缺失值检测、异常值识别与时间戳对齐

做市商的数据,说白了就是高频交易的“血液”。血液不干净,策略跑得再快也是白搭。我见过太多团队,模型建得花里胡哨,结果一上实盘就崩,查到最后——数据质量不过关。

这一节,咱们就聊聊怎么给盘口数据“体检”。我会把我在实战中踩过的坑、总结的经验,一股脑倒出来。你跟着走一遍,以后拿到任何行情数据,心里都有底。

3.1 缺失值检测:别让“空”坑了你

盘口数据里,缺失值很常见。网络抖动、交易所推送延迟、甚至撮合引擎的bug,都会导致某一条记录“消失”。

我个人的习惯是:先检测,再处理,绝不盲目填充。

检测方法其实很简单。用Pandas的isnull()配合sum(),一眼就能看出哪些字段有问题。

import pandas as pd

# 假设df是已经加载的盘口DataFrame
# 检查每列的缺失值数量
missing_counts = df.isnull().sum()
print(missing_counts[missing_counts > 0])

输出结果大概长这样:

字段缺失数量
bid_price_112
ask_price_18
bid_volume_115

嗯,这里要注意:缺失值不一定都要删掉。比如盘口深度数据,偶尔缺一条,用前向填充(ffill)往往比删掉更合理。但如果是连续缺失超过3个时间戳,我建议直接丢弃那一段——因为市场状态可能已经变了。

我的小技巧: 对于做市商策略,我通常保留缺失率低于5%的字段。超过这个阈值,我会考虑重新采集数据,或者用插值法补全。但记住,插值只适用于价格类字段,成交量千万别插值——那会扭曲真实的市场行为。

3.2 异常值识别:价格跳变与量比异常

异常值,是数据清洗里的“硬骨头”。盘口数据里,最常见的两类异常是:价格跳变量比异常

3.2.1 价格跳变:是行情突变还是数据错误?

价格跳变,指的是相邻两个时间戳的价格变化幅度,远超正常波动范围。比如,上一笔买一价是100.01,下一笔突然变成100.50。这可能是交易所的乌龙指,也可能是数据采集的bug。

怎么识别?我一般用差分法

# 计算买一价的差分(相邻差值)
df['bid_price_diff'] = df['bid_price_1'].diff().abs()

# 设定阈值:比如超过3个标准差就算异常
threshold = df['bid_price_diff'].mean() + 3 * df['bid_price_diff'].std()
anomalies = df[df['bid_price_diff'] > threshold]

print(f"检测到 {len(anomalies)} 个价格跳变异常")

我曾经在回测一个高频策略时,发现收益曲线突然出现一个巨大的尖峰。排查了半天,原来是某一天的数据里混进了一个错误的价格——买一价被记录成了100000。你想想看,这种数据如果不清理,策略回测结果能信吗?

避坑指南: 阈值设定不能太死板。不同品种、不同时间段的波动率差异很大。我建议用滚动窗口的动态阈值,比如过去5分钟的3倍标准差。这样能适应市场的变化。

3.2.2 量比异常:挂单量突然暴增或归零

量比异常,说白了就是挂单量突然变得离谱。比如,某个档位的买量突然从100手变成10000手,或者直接变成0。这可能是大单进场,也可能是数据推送的bug。

识别方法类似,但阈值要更谨慎:

# 计算买一量的变化率
df['bid_vol_ratio'] = df['bid_volume_1'] / df['bid_volume_1'].shift(1)

# 量比超过10倍或低于0.1倍,视为异常
vol_anomalies = df[(df['bid_vol_ratio'] > 10) | (df['bid_vol_ratio'] < 0.1)]
print(f"检测到 {len(vol_anomalies)} 个量比异常")

这里有个坑:量比异常不一定是错误。比如,某个大机构突然撤单再挂单,就会导致量比剧烈变化。所以,我通常会把量比异常和价格跳变结合起来判断。如果价格没变,只是量变了,那大概率是真实的市场行为,保留即可。

3.3 时间戳对齐与重采样:让数据“同步”

做市商的数据,往往来自多个交易所或多个数据源。每个源的时间戳精度可能不一样——有的精确到毫秒,有的精确到微秒。如果不做对齐,策略就会“看错”市场。

时间戳对齐,核心就一句话:统一精度,按需重采样。

3.3.1 统一时间戳精度

我习惯把所有时间戳统一到毫秒级。为什么?因为大多数做市商策略的决策频率在毫秒级别,微秒级的精度反而会增加计算负担。

# 将时间戳转换为毫秒级,并设为索引
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
df.set_index('timestamp', inplace=True)

如果数据源的时间戳是字符串格式,记得先解析:

df['timestamp'] = pd.to_datetime(df['timestamp'], format='%Y-%m-%d %H:%M:%S.%f')

3.3.2 重采样:从高频到低频

重采样,就是把高频数据降采样到低频。比如,把毫秒级的盘口数据,重采样到秒级或分钟级。这样做的好处是:减少数据量、平滑噪声、方便与低频策略对接。

我常用的重采样方法是:

# 重采样到1秒,取最后一笔的价格和平均成交量
resampled = df.resample('1S').agg({
    'bid_price_1': 'last',
    'ask_price_1': 'last',
    'bid_volume_1': 'mean',
    'ask_volume_1': 'mean'
})

这里有个细节:价格用'last',成交量用'mean'。为什么?因为价格是状态量,取最后一笔最接近真实市场;成交量是流量,取平均值能反映这段时间的整体情况。

核心原则: 重采样时,一定要根据字段的业务含义选择聚合函数。别一股脑全用'mean',那会丢失很多信息。

3.4 知识体系总览

说了这么多,咱们用一张图把这一节的核心逻辑串起来。你一看就明白:数据质量初探,到底在做什么。

数据质量初探:核心流程 原始盘口数据 缺失值检测 isnull() / ffill 异常值识别 价格跳变 / 量比异常 时间戳对齐 统一精度 / 重采样 清洗后数据 关键检查点 • 缺失率 < 5% • 价格跳变 < 3σ • 量比异常结合判断 • 时间戳精度统一 • 重采样函数匹配

这张图把咱们这一节的内容串起来了。从原始数据出发,经过缺失值检测、异常值识别、时间戳对齐,最后得到干净可用的数据。每一步都有对应的技术和阈值,你照着做,基本不会出大问题。

好了,数据质量初探就聊到这儿。记住一句话:数据清洗不是一次性工作,而是贯穿整个策略生命周期的习惯。每次拿到新数据,都走一遍这个流程,你会发现——很多策略问题,其实都是数据问题。

专注资料整理