3、日志数据清洗:缺失值处理、异常值检测与剔除、时间戳对齐与标准化

做量化交易的朋友都知道,日志数据这东西,看着规整,实际上脏得很。我入行头两年,就吃过不少亏。有一次策略回测跑出来收益曲线漂亮得不像话,结果一查,原来是某天的成交数据全丢了,系统自动补了个零。嗯,从那以后,我对数据清洗这件事就格外上心。

说白了,日志清洗就是给数据「洗澡」。把脏的、乱的、不对齐的,统统处理干净。今天咱们就聊聊这个环节里的三个核心动作:缺失值、异常值、时间戳。

3.1 缺失值处理:别让空值坑了你

日志里出现缺失值,太常见了。网络断了、行情源挂了、系统重启了,都可能导致某条记录没写进去。我个人习惯,拿到日志第一件事就是跑个 isnull() 看看。

核心原则:不要盲目填充,先搞清楚缺失的原因。

常见的处理方式有三种:

  • 直接删除:如果缺失比例很小(比如低于5%),而且缺失是随机的,直接删掉最省事。
  • 向前填充:用上一个有效值补上。适合价格、持仓这类变化缓慢的数据。
  • 插值法:用前后值取平均,或者线性插值。适合成交量、波动率这类有趋势的数据。

我在项目中遇到过一种情况:某只股票的分钟线日志,连续10分钟都是空值。当时我用了向前填充,结果策略以为价格没变,直接下了单。后来我加了个阈值判断——连续缺失超过3笔,直接标记为「数据异常」,不参与计算。

# 我个人常用的缺失值处理逻辑
import pandas as pd

def clean_missing(df, max_consecutive=3):
    # 先检查连续缺失
    missing_mask = df['price'].isna()
    consecutive = missing_mask.groupby((~missing_mask).cumsum()).transform('sum')
    
    # 连续缺失超过阈值,标记为异常
    df.loc[consecutive > max_consecutive, 'data_quality'] = 'bad'
    
    # 普通缺失用向前填充
    df['price'].fillna(method='ffill', inplace=True)
    
    return df

小技巧:我习惯在清洗后保留一列「数据质量」标记。这样回测时能清楚知道哪些数据是原始值,哪些是补出来的。

3.2 异常值检测与剔除:别让极端值带偏你

异常值这东西,有时候是行情真的暴涨暴跌,有时候就是数据写错了。怎么区分?我总结了一套「三步走」的方法。

第一步:肉眼扫一遍

别笑,这步真有用。把价格、成交量画成折线图,一眼就能看出哪些点不对劲。比如某只股票平时成交量几百万,突然某天显示几十亿,那八成是数据录入时多打了个零。

第二步:用统计方法筛

我个人偏爱「3σ原则」和「IQR方法」。3σ适合正态分布的数据,IQR更稳健,不受极端值影响。

# IQR方法检测异常值
def detect_outliers_iqr(df, column):
    Q1 = df[column].quantile(0.25)
    Q3 = df[column].quantile(0.75)
    IQR = Q3 - Q1
    
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    
    outliers = df[(df[column] < lower_bound) | (df[column] > upper_bound)]
    return outliers

第三步:结合业务逻辑判断

这一步最容易被忽略。你想想看,某只股票涨停了,价格突然跳升10%,这在统计上可能是异常值,但在业务上完全合理。我曾经犯过这个错——把某次「乌龙指」事件的数据当异常剔除了,结果回测少算了一笔大额亏损。

注意:异常值不一定要剔除。如果是真实的极端行情,保留它反而能让策略更鲁棒。我建议先标记,再决定是剔除还是保留。

3.3 时间戳对齐与标准化:统一时钟,统一节奏

做量化最头疼的事之一,就是不同数据源的时间戳对不上。交易所的时间、行情商的时间、自己服务器的时间,各有各的偏差。我记得有一次,策略在模拟盘跑得好好的,一上实盘就亏钱。查了两天才发现,是行情时间戳比实际慢了200毫秒。

时间戳对齐,核心就两件事:

  • 统一时区:全部转成UTC,或者全部用北京时间。千万别混着用。
  • 统一精度:有的数据精确到秒,有的精确到毫秒。对齐到同一个精度,我一般选毫秒。
# 时间戳标准化示例
def standardize_timestamp(df, time_col='timestamp'):
    # 转成datetime格式
    df[time_col] = pd.to_datetime(df[time_col])
    
    # 统一时区到UTC
    df[time_col] = df[time_col].dt.tz_localize('Asia/Shanghai', ambiguous='infer')
    df[time_col] = df[time_col].dt.tz_convert('UTC')
    
    # 对齐到整秒(或整毫秒)
    df[time_col] = df[time_col].dt.floor('1s')
    
    return df.sort_values(time_col)

还有一个坑:不同交易所的休市时间不一样。A股中午休市,美股没有。如果你把A股和美股的数据直接拼在一起,中间那段空白时间就会造成对齐错误。我的做法是,先按交易所分开清洗,最后再合并。

对齐后的数据,一定要做一次「时间连续性检查」。看看相邻两条记录的时间差是不是均匀的。如果突然出现一个大跳变,那说明中间有数据丢了。

3.4 本章知识体系总览

下面这张图,是我自己画的数据清洗流程。每次做新项目,我都会先按这个框架走一遍,基本不会漏掉什么。

日志数据清洗流程 原始日志数据 缺失值处理 异常值检测与剔除 时间戳对齐与标准化 清洗完成 删除 / 填充 / 插值 3σ / IQR / 业务判断 时区统一 / 精度对齐

这张图看着简单,但每一步都有讲究。缺失值处理不是无脑填充,异常值检测不是一刀切,时间戳对齐更不是转个格式就完事。我做了这么多年,每次清洗还是会遇到新问题。嗯,这就是量化交易的魅力——你永远不知道数据里藏着什么惊喜。

我的习惯:每次清洗完数据,我都会写一份「数据清洗报告」,记录发现了多少缺失值、多少异常值、做了哪些处理。这样以后复盘时,能清楚知道数据是怎么来的。


公众号:蓝海资料掘金营,微信deep3321