4. 数据完整性校验:检查数据连续性、时间戳单调性、缺失值检测
好,咱们进入第四讲。数据完整性,说白了就是回答三个问题:数据齐不齐?时间对不对?有没有坑?
我在做高频交易系统的时候,遇到过最头疼的事——不是策略亏钱,而是数据本身有问题。你辛辛苦苦跑了一周的回测,最后发现是因为某一天的 tick 数据少了一秒。嗯,那种感觉,就像你爬到了山顶,发现爬错了山。
4.1 数据连续性检查:Sequence Number 的秘密
先说说 Sequence Number。这东西是交易所给的,每个 tick 一个编号。正常情况下,它是严格递增的,中间不能跳号。
为什么这么重要?
因为跳号意味着丢数据。我见过最夸张的一次,某只股票在 10:00:00 到 10:00:01 之间,Sequence Number 从 1001 直接跳到了 2001。你想想看,这一秒里丢了 1000 个 tick!
核心检查逻辑:
- 检查 Sequence Number 是否严格递增
- 检查是否有重复的 Sequence Number
- 检查跳号是否超过阈值(比如连续跳号超过 10 个)
我个人的习惯是,先做一次全局扫描,看看最大跳号是多少。如果最大跳号超过 100,那基本可以判定这批数据有问题。
# 伪代码:检查 Sequence Number 连续性
def check_sequence_continuity(df):
# 计算相邻 tick 的差值
df['seq_diff'] = df['sequence_number'].diff()
# 找出跳号的位置
gaps = df[df['seq_diff'] > 1]
# 统计跳号情况
total_gaps = len(gaps)
max_gap = gaps['seq_diff'].max()
print(f"总跳号次数: {total_gaps}")
print(f"最大跳号: {max_gap}")
# 如果跳号太多,直接报警
if total_gaps > 100:
raise ValueError("数据连续性严重异常!")
避坑指南:
我曾经遇到过一种情况:Sequence Number 是连续的,但时间戳是乱的。这说明什么?说明数据被重新排序过,但 Sequence Number 没跟着改。所以,光检查 Sequence Number 是不够的,还得看时间戳。
4.2 时间戳单调性:别让时间倒流
时间戳单调性,说白了就是时间只能往前走,不能往回走。这个检查看起来简单,但坑特别多。
常见问题:
- 时间倒流:后一个 tick 的时间比前一个还早。这通常是因为数据源的时间同步出了问题。
- 时间静止:连续多个 tick 的时间戳一模一样。这在高频数据里几乎不可能,除非是撮合引擎卡住了。
- 时间跳跃:两个 tick 之间差了十几秒。这在正常交易中很少见,除非是休市或者数据源断连。
我记得有一次,某个数据供应商给的数据,每天下午 3:00 之后的时间戳全是乱的。后来发现是他们服务器的时间同步脚本在收盘后跑,导致时间回退了 1 秒。你想想看,就这 1 秒,让我的策略在回测里多赚了 5%。
# 检查时间戳单调性
def check_timestamp_monotonicity(df):
# 计算时间差
df['time_diff'] = df['timestamp'].diff().dt.total_seconds()
# 找出异常点
negative_time = df[df['time_diff'] < 0]
zero_time = df[df['time_diff'] == 0]
large_gap = df[df['time_diff'] > 5] # 超过5秒算大跳跃
print(f"时间倒流次数: {len(negative_time)}")
print(f"时间静止次数: {len(zero_time)}")
print(f"大时间跳跃次数: {len(large_gap)}")
# 如果时间倒流超过 10 次,直接报警
if len(negative_time) > 10:
raise ValueError("时间戳严重异常!存在时间倒流!")
注意:
有些交易所会在收盘后发送一些特殊 tick(比如盘后交易),这些 tick 的时间戳可能比最后一笔正常交易还晚。所以,检查时间戳单调性时,最好先过滤掉非交易时段的数据。
4.3 缺失值检测:看不见的敌人
缺失值,是数据质量里最隐蔽的问题。它不像跳号那么明显,但影响一样大。
常见的缺失值类型:
| 字段 | 缺失含义 | 处理方式 |
|---|---|---|
| 价格 | 该 tick 没有成交价 | 用前一个 tick 的价格填充,或直接删除 |
| 成交量 | 该 tick 没有成交量 | 通常设为 0,但要注意是否合理 |
| 买卖盘口 | 缺少买一/卖一价格 | 用上一档价格填充,或标记为异常 |
| Sequence Number | 严重!说明数据源有问题 | 直接丢弃该 tick |
我个人的经验是,价格和成交量的缺失值,容忍度可以高一点。比如,如果缺失率低于 0.1%,直接用前值填充就行。但如果是 Sequence Number 缺失,那基本可以判定这个数据源有问题,建议直接换数据源。
# 缺失值检测
def detect_missing_values(df):
# 计算每个字段的缺失率
missing_rates = df.isnull().mean() * 100
print("各字段缺失率:")
for col, rate in missing_rates.items():
print(f" {col}: {rate:.4f}%")
# 如果关键字段缺失率超过 1%,报警
critical_fields = ['price', 'volume', 'sequence_number']
for field in critical_fields:
if missing_rates.get(field, 0) > 1:
raise ValueError(f"{field} 缺失率过高!")
return missing_rates
一个小技巧:
我曾经处理过一批数据,价格字段的缺失率只有 0.01%,看起来没问题。但仔细一看,缺失的价格全集中在某一天的 10:00-10:01 这一分钟里。这说明什么?说明那分钟的数据源出了问题。所以,缺失值不仅要看整体比例,还要看分布。
4.4 知识体系总览
下面这张图,是我自己总结的数据完整性校验流程。你照着这个做,基本不会漏掉关键问题。
这张图的核心逻辑很简单:先检查 Sequence Number,再检查时间戳,最后检查缺失值。顺序不能乱,因为 Sequence Number 的异常往往会导致时间戳检查的结果不可信。
4.5 实战中的坑与经验
最后,分享几个我踩过的坑:
- 坑一: 有些数据源会在收盘后补发 tick,这些 tick 的 Sequence Number 是连续的,但时间戳是乱的。所以,收盘后的数据要单独处理。
- 坑二: 缺失值检测不能只看整体比例。我曾经遇到过一个数据集,整体缺失率只有 0.5%,但所有缺失都集中在某一天的 10 秒内。这说明那 10 秒的数据源出了问题。
- 坑三: 时间戳单调性检查要考虑到交易所的特殊情况。比如,有些交易所会在开盘前发送一些测试 tick,这些 tick 的时间戳可能比正常交易还早。
总结一下:
数据完整性校验,说白了就是三个步骤:查连续性、查时间、查缺失。每一步都有坑,但只要你按照流程走,基本能过滤掉 99% 的问题。剩下的 1%,嗯,那就要靠经验了。