3. 缺失值处理策略:让数据不再“断片”
做市数据里,缺失值就像心电图上的空白段——看着就让人心慌。我刚开始做量化那会儿,遇到缺失值第一反应就是删掉。后来吃过亏才明白,做市数据里的缺失,往往藏着重要的市场信息。
说白了,缺失值处理不是简单的“填坑”。你得先问自己:为什么会有缺失?是流动性不足?是数据采集故障?还是盘前盘后的正常现象?搞清楚原因,才能选对策略。
核心原则:做市数据的缺失值处理,必须在“保留信息”和“避免引入偏差”之间找到平衡。我个人习惯,先做缺失模式分析,再选填充方法。
3.1 向前填充(ffill)—— 最常用的“懒人”方法
向前填充,就是用上一个有效值填补后面的空缺。在逐笔成交数据里,这招特别好使。为什么?因为做市报价在短时间内通常不会剧烈变化。
举个例子,某只股票在10:00:01的买一价是10.02,10:00:02缺失了,用ffill填充后,10:00:02的买一价也是10.02。这在流动性好的标的上,基本合理。
# 我个人最常用的ffill写法
import pandas as pd
# 假设df是1秒频的做市报价数据
df['bid_price'].fillna(method='ffill', inplace=True)
# 注意:限制最大填充间隔
# 如果连续缺失超过5秒,我建议不要填充
max_gap = pd.Timedelta('5s')
df['bid_price'] = df['bid_price'].fillna(method='ffill', limit=5)
我的经验:在盘口深度数据里,ffill配合limit参数使用效果最好。我曾经遇到过一个极端情况,某只冷门股连续30秒没有报价,ffill直接填了30个相同值,结果策略模型以为市场静止了,差点触发风控。
3.2 向后填充(bfill)—— 反向思维,也有妙用
向后填充和ffill正好相反,用后面的值填前面的空缺。你可能会问:这合理吗?嗯,在某些场景下,bfill比ffill更合适。
比如处理盘后数据。交易所收盘后,有些数据源会延迟推送最后一笔成交。这时候用bfill,相当于用收盘后的确认数据,回填到收盘那一刻。
# bfill的典型用法
df['last_price'].fillna(method='bfill', inplace=True)
# 我建议:在盘后数据清洗时,先bfill再ffill
# 这样可以同时处理开盘前和收盘后的缺失
df['volume'].fillna(method='bfill', inplace=True)
df['volume'].fillna(method='ffill', inplace=True)
避坑指南:我曾经在回测时用了bfill处理盘中数据,结果把未来的价格信息泄露到了过去。回测曲线漂亮得不像话,实盘直接打脸。记住:盘中数据绝对不能用bfill,除非你想作弊。
3.3 线性插值 —— 让填充更“平滑”
线性插值,说白了就是在两个已知点之间画一条直线,用直线上的值填补中间的空缺。对于价格这类连续变化的变量,线性插值比ffill更合理。
你想想看,价格在1秒内从10.00跳到10.02,中间缺失的那一秒,用10.01填充是不是比10.00更合理?线性插值做的就是这件事。
# pandas的线性插值
df['bid_price'].interpolate(method='linear', inplace=True)
# 对于时间序列,我推荐使用time插值
# 它会考虑时间间隔的不均匀性
df['bid_price'].interpolate(method='time', inplace=True)
| 插值方法 | 适用场景 | 我的评分 |
|---|---|---|
| linear | 等间隔时间序列 | ⭐⭐⭐⭐ |
| time | 不等间隔时间序列 | ⭐⭐⭐⭐⭐ |
| quadratic | 价格曲线较平滑 | ⭐⭐⭐ |
| spline | 高阶平滑需求 | ⭐⭐ |
小技巧:线性插值对异常值很敏感。我一般会先做一次异常值检测,把明显的错误值标记为NaN,然后再做插值。这样插出来的结果更干净。
3.4 基于时间权重的填充 —— 做市数据的“高级玩法”
这个方法听起来高大上,其实原理很简单:距离越近的数据点,权重越大。在逐笔成交数据里,时间戳往往不均匀,用时间权重填充比简单线性插值更精确。
我记得有一次处理期权做市数据,买卖价差在开盘后前5分钟特别大。如果用普通插值,会把开盘后的高波动“平滑”掉。改用时间权重填充后,保留了开盘初期的波动特征,策略表现明显提升。
# 自定义时间权重填充
import numpy as np
from scipy import interpolate
def time_weighted_fill(series, timestamps):
"""
基于时间权重的填充
timestamps: 对应的时间戳(datetime类型)
"""
# 只对缺失值进行处理
mask = series.isna()
if mask.sum() == 0:
return series
# 获取有效数据点
valid_idx = ~mask
valid_times = timestamps[valid_idx].astype(np.int64) // 10**9
valid_values = series[valid_idx].values
# 对每个缺失点,计算时间加权值
result = series.copy()
for idx in series[mask].index:
current_time = timestamps[idx].astype(np.int64) // 10**9
# 找到前后各3个有效点
before = valid_times[valid_times < current_time][-3:]
after = valid_times[valid_times > current_time][:3]
if len(before) == 0 or len(after) == 0:
continue
# 计算时间权重(距离越近权重越大)
all_times = np.concatenate([before, after])
all_values = np.concatenate([
valid_values[valid_times < current_time][-3:],
valid_values[valid_times > current_time][:3]
])
weights = 1 / np.abs(all_times - current_time)
weights = weights / weights.sum()
result[idx] = np.dot(weights, all_values)
return result
3.5 高频数据中的特殊处理 —— 盘前盘后数据
做市数据里,盘前盘后是最容易出问题的时段。为什么?因为这段时间流动性差,报价稀疏,甚至没有报价。
我个人处理盘前盘后数据的经验是:不要强行填充。盘前集合竞价阶段,价格发现机制还没完全启动,你填进去的值大概率是错的。
我的处理原则:
- 盘前(9:15-9:25):只保留集合竞价的撮合数据,其他缺失不填充
- 盘中(9:30-11:30, 13:00-15:00):使用ffill + 线性插值组合
- 盘后(15:00-15:30):用bfill处理,因为盘后数据通常有延迟确认
# 分时段处理缺失值
def handle_market_hours(df):
"""
根据交易时段处理缺失值
"""
df = df.copy()
# 定义时段
pre_open = (df.index.time >= pd.Timestamp('09:15').time()) & \
(df.index.time < pd.Timestamp('09:25').time())
trading = (df.index.time >= pd.Timestamp('09:30').time()) & \
(df.index.time < pd.Timestamp('11:30').time()) | \
(df.index.time >= pd.Timestamp('13:00').time()) & \
(df.index.time < pd.Timestamp('15:00').time())
after_close = (df.index.time >= pd.Timestamp('15:00').time()) & \
(df.index.time < pd.Timestamp('15:30').time())
# 盘前:不填充,保留NaN
# 盘中:ffill + 线性插值
df.loc[trading] = df.loc[trading].fillna(method='ffill', limit=3)
df.loc[trading] = df.loc[trading].interpolate(method='time')
# 盘后:bfill
df.loc[after_close] = df.loc[after_close].fillna(method='bfill')
return df
我曾经踩过的坑:有一次处理股指期货的做市数据,盘前15分钟有大量缺失。我用ffill填充后,策略在开盘瞬间产生了巨大的持仓偏差。后来复盘才发现,盘前的“缺失”其实是正常的——那时候根本没有交易。从那以后,我处理盘前数据时,会先判断缺失是否属于“正常缺失”,如果是,直接保留NaN。
知识体系总览
下面这张图,是我自己总结的缺失值处理决策流程。每次处理新数据前,我都会过一遍这个流程。
这张图的核心逻辑是:先判断时段,再判断缺失长度,最后根据数据类型选择方法。我建议你把这张图打印出来贴在工位上,处理数据时照着走一遍,基本不会出错。
最后说一句:没有万能的方法。我见过有人把所有缺失都用机器学习预测填充,结果过拟合得一塌糊涂。做市数据的缺失值处理,关键是理解你的数据、理解你的策略、理解市场。工具只是手段,判断力才是核心。
公众号:蓝海资料掘金营,微信deep3321