4、数据清洗与对齐:去重、时间戳对齐、异常值处理、订单簿重建逻辑
数据清洗,说白了就是给原始数据「洗澡」。
我刚开始做订单簿回溯时,拿到交易所的原始数据直接往里灌,结果回测曲线惨不忍睹。后来才发现,数据里藏着各种「脏东西」——重复记录、时间错位、价格异常……嗯,从那以后,我养成了一个习惯:数据不洗三遍,绝不进策略。
4.1 去重:别让同一笔订单算两次
交易所的API有时候会重复推送同一条数据。尤其是WebSocket断线重连后,补发的数据里经常混着已经处理过的记录。
我的做法很简单:
- 用
(交易对, 时间戳, 订单ID)作为唯一键 - 维护一个哈希集合,来一条查一条
- 重复的直接丢弃
核心原则:去重必须在数据进入管道的第一时间做。等数据进了数据库再查重,成本高十倍。
def deduplicate(records):
seen = set()
clean = []
for r in records:
key = (r['symbol'], r['timestamp'], r['order_id'])
if key not in seen:
seen.add(key)
clean.append(r)
return clean
避坑指南:我曾经遇到过交易所的订单ID是自增整数,但不同交易对之间会重复。所以一定要把交易对也加进唯一键里。
4.2 时间戳对齐:把不同时钟拉到同一张表上
不同交易所的时间戳格式五花八门。有的用毫秒,有的用微秒,还有的用字符串。更头疼的是,有些交易所的服务器时钟本身就有偏移。
我建议统一做三件事:
- 全部转成纳秒时间戳(Python的
pd.Timestamp默认支持纳秒) - 对齐到整秒或整毫秒,根据你的策略精度需求来
- 做一次时间偏移校正,用交易所的公开时间戳API做参考
def align_timestamp(ts, freq='1s'):
# 将时间戳对齐到整秒
return pd.Timestamp(ts).floor(freq)
注意:对齐时间戳会丢失部分精度。如果你的策略依赖微秒级订单簿变化,建议保留原始时间戳,只在聚合分析时做对齐。
4.3 异常值处理:那些离谱的价格是怎么来的?
你想想看,一个正常交易对的价格突然跳了100倍,这合理吗?
我在项目中遇到过好几次,原因是交易所的撮合引擎在某个瞬间产生了「脏数据」——比如价格字段被错误地填成了订单ID,或者数量字段出现了负数。
我的过滤规则:
- 价格不能为0或负数
- 数量不能为0或负数
- 价格不能偏离前N笔的均值超过3个标准差
- 时间戳不能是未来的(超过当前时间5秒以上)
个人经验:我曾经用3倍标准差过滤掉了一批「异常」数据,结果发现那其实是某次大行情前的试单。后来我改成了「5倍标准差 + 人工复核」的策略。
def filter_outliers(records, window=20, z_thresh=5):
prices = [r['price'] for r in records]
mean = np.mean(prices[-window:])
std = np.std(prices[-window:])
return [r for r in records
if abs(r['price'] - mean) < z_thresh * std]
4.4 订单簿重建逻辑:从零拼出一张完整的挂单表
订单簿重建,说白了就是「用增量数据还原全量快照」。
交易所通常提供两种数据:
- 全量快照:某一时刻的完整买卖盘
- 增量更新:每次订单变化时的变动记录
重建逻辑其实不复杂:
- 拿到一个全量快照作为基准
- 后续每来一条增量,更新对应的价格档位
- 如果增量中的价格不在当前订单簿中,新增一个档位
- 如果增量中的数量为0,删除该档位
避坑指南:我曾经遇到增量数据丢失的情况,导致订单簿越拼越错。后来我加了一个「校验机制」——每1000条增量后,请求一次全量快照做对比。如果偏差超过阈值,就重新从快照开始重建。
class OrderBook:
def __init__(self, snapshot):
self.bids = {p: q for p, q in snapshot['bids']}
self.asks = {p: q for p, q in snapshot['asks']}
def apply_update(self, update):
side = self.bids if update['side'] == 'bid' else self.asks
if update['quantity'] == 0:
side.pop(update['price'], None)
else:
side[update['price']] = update['quantity']
4.5 整体流程:一张图看懂
我把整个数据清洗与对齐的流程画成了下面这张图。你跟着箭头走一遍,基本就清楚了。
总结一下:数据清洗不是一次性工作,而是贯穿整个回溯过程的持续任务。每当你发现回测结果异常,第一个怀疑的应该是数据质量,而不是策略逻辑。
嗯,这一章的内容就到这里。数据清洗虽然枯燥,但它是所有量化策略的基石。地基不稳,楼盖得再高也得塌。