缺失值处理:时间戳缺失检测、快照缺失时的重同步策略、价格档位空缺填充
做订单簿数据清洗,说白了就是跟「不完美」的数据打交道。我刚开始做高频回测那会儿,天真地以为交易所给的数据都是规规矩矩的。结果呢?第一次跑回测就发现收益率曲线像心电图一样乱跳——后来一查,原来是某段时间的快照丢了,策略在「真空」里瞎交易。
嗯,今天咱们就来聊聊缺失值处理。这活儿看着不起眼,但做不好,后面的特征工程全是垃圾。
时间戳缺失检测
订单簿数据是按时间戳排列的。正常情况下,快照应该以固定间隔到达。比如交易所每100ms推送一次。但现实是——网络抖动、系统过载、甚至交易所自己的bug,都会导致某些时间戳凭空消失。
怎么检测?我个人的习惯是两步走:
- 计算时间差序列:把相邻时间戳相减,得到一个间隔序列
- 标记异常间隔:如果某个间隔远大于正常值(比如超过1.5倍标准间隔),就认为中间有缺失
核心思路:不是看「有没有缺失」,而是看「间隔是否合理」。
举个例子,假设正常间隔是100ms,你发现某两个时间戳差了500ms。那中间大概率丢了4个快照。这时候不能简单跳过,得标记出来。
# 伪代码:时间戳缺失检测
def detect_missing_timestamps(timestamps, expected_interval_ms=100, tolerance=0.5):
gaps = []
for i in range(1, len(timestamps)):
diff = (timestamps[i] - timestamps[i-1]) / 1e6 # 转成毫秒
if diff > expected_interval_ms * (1 + tolerance):
missing_count = round(diff / expected_interval_ms) - 1
gaps.append({
'start': timestamps[i-1],
'end': timestamps[i],
'missing_count': missing_count
})
return gaps
这里有个坑——精度问题。时间戳单位是纳秒,但很多交易所实际精度只有微秒甚至毫秒。我遇到过一种情况:两个时间戳看起来只差了99ms,但实际是因为精度截断导致的「假缺失」。所以检测时最好留个缓冲区间。
快照缺失时的重同步策略
检测到缺失之后怎么办?直接扔掉?那回测的连续性就断了。强行补一个?补错了更麻烦。
我一般用三种策略,看场景选:
| 策略 | 适用场景 | 风险 |
|---|---|---|
| 丢弃+重对齐 | 缺失比例很小(<1%) | 可能丢失关键行情 |
| 前向填充 | 市场波动不大 | 延迟反应价格变化 |
| 重同步 | 缺失时间较长 | 需要额外数据源 |
重同步是我最推荐的做法。说白了就是:用下一个有效快照来「校准」当前状态。但要注意——不能简单地把下一个快照复制过来,因为中间可能发生了成交。
我的经验:重同步时,最好同时检查成交记录。如果缺失期间有成交,那快照里的累计成交量、最后一笔价格都得跟着调整。否则回测时你会发现「咦,怎么价格没动但成交了?」
具体做法是这样的:
- 找到缺失区间后的第一个有效快照
- 从成交记录中提取缺失期间的所有成交
- 用成交数据「回退」快照状态,得到缺失开始时的合理状态
- 用线性插值填充中间的快照
听起来复杂?其实核心就一句话:别凭空造数据,用成交记录来辅助重建。
价格档位空缺填充
这个更常见。订单簿的买一到买十,卖一到卖十,经常有空位。比如买三的价格是10.00,买五直接跳到10.02,中间10.01没人挂单。
这种空缺要不要填?看你的策略需求。
- 做市策略:必须填。因为你要知道每个价位的深度
- 趋势策略:可以不填。空缺本身也是一种信息(流动性不足)
如果决定要填,我常用两种方法:
前向填充(Forward Fill)
最简单。空缺的价格用上一个有效价格代替,数量填0。比如:
原始:买一 10.00(100), 买二 10.01(0), 买三 10.02(200)
前向填充后:买一 10.00(100), 买二 10.01(0), 买三 10.02(200)
注意,数量填0而不是填上一个数量。因为空缺意味着「没人挂单」,不是「有人挂了但没记录」。
线性插值(Linear Interpolation)
更平滑,但风险也大。比如买一和买三之间空缺了买二,那就取价格的平均值:
买二价格 = (10.00 + 10.02) / 2 = 10.01
买二数量 = (100 + 200) / 2 = 150
注意:线性插值会「创造」出原本不存在的挂单。如果市场本身流动性很差,插值出来的数据会误导策略。我曾经因为这个在回测里赚得盆满钵满,实盘一跑就亏——因为插值出来的「假深度」根本没法成交。
异常空值识别
最后聊一个容易被忽略的点:空值不等于缺失。
有些交易所会在某些档位上显式返回空值(null),表示「这个价位没有挂单」。这和「缺失」是两码事。缺失是数据没到,空值是数据到了但内容是空的。
怎么区分?我总结了一个小规则:
- 时间戳连续,但某个档位是null → 正常空值,保留
- 时间戳不连续,且档位全是null → 可能是快照缺失,需要重同步
- 时间戳连续,但所有档位都是null → 异常!可能是交易所系统故障
最后一种情况我遇到过两次。一次是交易所的推送服务挂了,返回的全是空快照。另一次是网络中间层把数据包截断了,只传了时间戳没传内容。这两种情况都得直接丢弃,不能填充。
避坑指南:我建议在清洗流程里加一个「全空快照检测」。如果连续3个以上的快照全是空值,直接标记为异常区间,后续特征计算时跳过。别想着用插值去补——你补出来的是一段「假行情」,策略会死得很惨。
知识体系总览
下面这张图把整个缺失值处理的逻辑串起来了。你可以把它当作清洗流程的「检查清单」:
你看,整个流程其实是个漏斗——先检测时间戳,再处理快照,最后填充档位。每一步都有对应的判断条件。别想着一步到位,数据清洗这事儿,急不得。
好了,关于缺失值处理就聊到这儿。记住一句话:宁可少一个快照,也别多一个假数据。回测里多赚的那几个点,实盘里都会还回去的。