4、数据清洗:处理缺失值、异常值、时间戳对齐
数据清洗这步,说白了就是给原始订单簿数据「洗澡」。
我刚开始做量化回测时,拿到的数据看着挺漂亮,一跑策略就崩。后来才发现,里面藏着各种坑——缺失的报价、跳变的成交价、错位的时间戳。嗯,今天咱们就把这些坑一个一个填上。
4.1 缺失值处理:别让空数据毁了你的回测
订单簿数据里,缺失值很常见。比如某个时间点,买一价突然没了,或者卖一量显示为0。为什么会这样?可能是交易所数据推送延迟,也可能是网络抖动。
我个人习惯,先检查缺失值的分布情况:
# 检查每列的缺失比例
def check_missing(df):
missing = df.isnull().sum()
missing_pct = missing / len(df) * 100
return pd.DataFrame({
'缺失数量': missing,
'缺失比例(%)': missing_pct
})
拿到结果后,怎么补?我一般分三种情况:
- 连续缺失不超过3个:用线性插值。比如买一价在t1和t5都有值,t2到t4缺失,那就按时间比例补上。
- 缺失超过5个:直接删掉这段数据。为什么?因为插值补出来的价格,跟真实市场行为差太远,你想想看,谁会信?
- 关键字段缺失:比如买卖价差同时缺失,那这行数据基本废了,删。
我的小技巧:补缺失值时,别只盯着价格。成交量缺失也很要命。我曾经因为没补成交量,回测时策略疯狂下单,结果实盘一跑,流动性根本不够吃。
4.2 异常值检测:揪出那些「离谱」的数据
异常值比缺失值更隐蔽。你看着数据都有,但里面藏着「鬼」。
举个例子:某只股票平时买卖价差0.01元,突然某条记录显示价差0.5元。这可能是数据录入错误,也可能是瞬间的极端行情。怎么区分?
我常用的方法有三个:
- Z-score法:计算每个字段的Z值,超过3倍标准差就标记为异常。适合正态分布的数据。
- IQR四分位法:用Q1-1.5*IQR和Q3+1.5*IQR做边界。这个方法更稳健,不受极端值影响。
- 业务规则法:比如卖一价不能低于买一价,成交量不能为负数。这些是硬约束,违反了直接删。
def detect_outliers_iqr(df, column):
Q1 = df[column].quantile(0.25)
Q3 = df[column].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
return (df[column] < lower) | (df[column] > upper)
注意:别一发现异常值就删。我曾经在回测中删掉了一批「异常」数据,结果策略表现特别好。后来才发现,那些「异常」其实是真实的闪崩行情。删了它们,策略等于在温室里测试,实盘一上就完蛋。
所以我的原则是:先标记,再人工判断。如果是数据错误,删;如果是真实行情,保留,但要在策略里做风控。
4.3 时间戳对齐:让不同数据源「说同一种语言」
时间戳对齐,是数据清洗里最磨人的环节。
你从交易所A拿到的数据是毫秒级时间戳,从交易所B拿到的是微秒级。更麻烦的是,有些数据源用UTC,有些用本地时间。不对齐的话,回测时你会发现买卖单对不上,成交记录错位。
我一般按这几步来:
- 统一时区:全部转成UTC+0。别用本地时间,否则夏令时切换时你会疯掉。
- 统一精度:全部转成毫秒级。微秒级太细,实际交易中用不上;秒级太粗,会丢失细节。
- 重采样对齐:比如你想做1秒快照,就把所有数据按秒聚合。取每秒最后一笔报价作为该秒的快照。
# 时间戳对齐示例
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
df.set_index('timestamp', inplace=True)
df_aligned = df.resample('1S').last() # 每秒取最后一笔
避坑指南:我曾经用「平均价」做重采样,结果回测收益高得离谱。后来发现,平均价会抹平价格波动,让策略看起来更稳。实际上,你应该用「最后一笔」或「加权平均」,别用简单平均。
4.4 知识体系总览
下面这张图,把数据清洗的核心逻辑串起来了:
说白了,数据清洗就是「去伪存真」的过程。你花80%的时间做清洗,可能只换来20%的策略提升。但这20%,往往决定了你的策略是赚钱还是亏钱。
核心要点总结:
- 缺失值:连续缺失少就插值,多就删除
- 异常值:先标记后判断,别一刀切
- 时间戳:统一时区、统一精度、重采样对齐
- 清洗后的数据,一定要做可视化检查。看一眼图,比跑100行代码都管用
嗯,数据清洗就聊到这儿。记住一句话:脏数据进去,垃圾策略出来。别嫌麻烦,这一步值得你花时间。