高频数据清洗与预处理:Tick数据去重、异常值处理、时间戳对齐、交易量聚合

做高频交易的人都知道一句话:垃圾进,垃圾出。这话听着糙,但理不糙。我刚开始接触Tick数据时,以为直接从交易所拿到的数据就是干净的,结果回测时发现策略收益高得离谱——后来一查,原来是数据里混了重复的成交记录,把交易量算了两遍。

嗯,从那以后,我再也不敢跳过数据清洗这一步了。今天咱们就来聊聊,高频数据清洗到底该怎么做。

为什么Tick数据这么脏?

说白了,Tick数据就是交易所每笔成交的原始记录。它不像分钟K线那样已经被人处理过,而是带着各种“毛刺”的原始信号。你想想看,交易所的撮合引擎每秒要处理几万笔订单,网络延迟、系统缓存、数据重传……任何一个环节出点小问题,你的数据就会出问题。

我个人习惯把Tick数据的“脏”归纳为四类:

  • 重复数据:同一笔成交被记录了两次
  • 异常值:价格或交易量明显偏离正常范围
  • 时间戳错位:成交时间与系统时间不一致
  • 数据稀疏:某些时间段没有成交记录

这四类问题,每一个都能让你的策略分析结果跑偏。咱们一个一个来解决。

第一步:Tick数据去重

重复数据是最常见的问题。我在项目中遇到过,某次从数据商拿到的BTC永续合约数据,居然有3%的重复记录。这些重复数据会让你的交易量统计翻倍,流动性分析完全失真。

去重的核心逻辑其实很简单:找到能唯一标识一笔成交的字段组合。通常我们用“交易对+时间戳+价格+交易量”这四个字段来判断。

去重策略:

  • 精确去重:四个字段完全一致,直接删除重复行
  • 模糊去重:时间戳相差1毫秒以内,价格和交易量相同,视为重复
  • 顺序去重:保留第一次出现的记录,删除后续重复

来看一段Python代码,这是我常用的去重方法:

import pandas as pd

def dedup_tick_data(df):
    """
    Tick数据去重函数
    df: DataFrame,包含列 ['symbol', 'timestamp', 'price', 'volume']
    """
    # 先按时间排序
    df = df.sort_values('timestamp')
    
    # 精确去重
    df_clean = df.drop_duplicates(
        subset=['symbol', 'timestamp', 'price', 'volume'],
        keep='first'
    )
    
    # 模糊去重:时间戳相差1ms内的重复
    df_clean['time_group'] = (df_clean['timestamp'] * 1000).astype(int) // 2
    df_clean = df_clean.drop_duplicates(
        subset=['symbol', 'time_group', 'price', 'volume'],
        keep='first'
    )
    
    return df_clean.drop('time_group', axis=1)

小技巧:去重前一定要先排序。我曾经因为没排序,结果把正常数据当重复删了,回测时发现策略信号全乱了。

第二步:异常值处理

异常值分两种:价格异常交易量异常。价格异常可能是交易所的“胖手指”错误,交易量异常可能是数据采集时的截断错误。

我常用的方法是“三西格玛法则”加上“百分位截断”。说白了,就是先算均值和标准差,把超出3个标准差的数据标记为异常。但高频数据有个特点——价格波动本身就很大,直接用标准差可能会误杀正常数据。

所以我的做法是:

  1. 先计算滚动窗口(比如100笔)的均值和标准差
  2. 标记超出均值±5倍标准差的数据为异常
  3. 用前一笔正常数据填充异常值
def remove_outliers(df, window=100, z_thresh=5):
    """
    异常值处理
    """
    df['price_ma'] = df['price'].rolling(window).mean()
    df['price_std'] = df['price'].rolling(window).std()
    
    # 标记异常
    df['is_outlier'] = (
        (df['price'] - df['price_ma']).abs() > z_thresh * df['price_std']
    )
    
    # 用前一个正常值填充
    df.loc[df['is_outlier'], 'price'] = None
    df['price'].fillna(method='ffill', inplace=True)
    
    return df

注意:千万不要直接删除异常值!高频数据的时间序列是连续的,删除会导致时间戳不连续,后续的对齐和聚合都会出问题。用前值填充是最稳妥的做法。

第三步:时间戳对齐

时间戳对齐是个大坑。不同交易所的时间精度不一样,有的精确到毫秒,有的精确到微秒,还有的干脆只到秒。你想想看,如果两个交易所的数据时间戳精度不同,你怎么做套利分析?

我的做法是:统一到毫秒级,然后做最近邻对齐

具体来说:

  • 把所有时间戳统一为毫秒级整数
  • 设定一个对齐窗口(比如5毫秒)
  • 在窗口内找到最接近的时间戳进行对齐
def align_timestamps(df, base_timestamps, window_ms=5):
    """
    时间戳对齐
    df: 待对齐的数据
    base_timestamps: 基准时间戳序列
    window_ms: 对齐窗口(毫秒)
    """
    aligned = []
    for ts in base_timestamps:
        # 找到窗口内的数据
        mask = (df['timestamp'] >= ts - window_ms) & \
               (df['timestamp'] <= ts + window_ms)
        window_data = df[mask]
        
        if len(window_data) > 0:
            # 取最接近的一条
            idx = (window_data['timestamp'] - ts).abs().idxmin()
            aligned.append(window_data.loc[idx])
    
    return pd.DataFrame(aligned)

避坑指南:我曾经在回测时发现策略收益异常稳定,后来发现是对齐窗口设得太大了(50毫秒),导致把不同时间点的数据强行对齐了。窗口大小要根据交易对的流动性来定,流动性越好的品种,窗口可以越小。

第四步:交易量聚合

交易量聚合,说白了就是把高频的逐笔成交数据,聚合成固定时间间隔的“快照”。为什么要做聚合?因为逐笔数据太密了,直接分析会带来巨大的计算量,而且很多策略并不需要那么高的精度。

我常用的聚合方式有两种:

聚合方式 说明 适用场景
时间聚合 固定时间间隔(如1秒、100毫秒) 流动性好的品种
交易量聚合 固定交易量(如每100BTC聚合一次) 流动性差的品种
事件聚合 按价格变动事件聚合 波动率分析
def aggregate_volume(df, interval='1s'):
    """
    交易量聚合
    """
    # 设置时间索引
    df = df.set_index('timestamp')
    
    # 按时间间隔聚合
    agg_df = df.resample(interval).agg({
        'price': 'ohlc',  # 开盘、最高、最低、收盘
        'volume': 'sum',  # 交易量求和
        'side': lambda x: x.mode()[0] if len(x) > 0 else None  # 买卖方向
    })
    
    return agg_df

知识体系总览

说了这么多,咱们来画个图,把整个清洗流程串起来。这张图我画了很多次,每次带新人时都会拿出来讲一遍:

高频数据清洗与预处理流程 原始Tick数据 第一步:数据去重 精确去重 + 模糊去重(1ms窗口) 第二步:异常值处理 滚动窗口 + 三西格玛法则 第三步:时间戳对齐 统一毫秒级 + 最近邻对齐 第四步:交易量聚合 时间聚合 / 交易量聚合 / 事件聚合 常见问题 • 重复数据占3%-5% • 价格异常波动 • 时间戳精度不一致 • 交易量截断错误 • 数据稀疏问题 • 买卖方向缺失 • 交易所数据延迟 • 网络重传导致重复

写在最后

数据清洗这件事,看着简单,做起来全是坑。我见过太多人把80%的时间花在策略开发上,结果数据没洗干净,回测结果全是假的。说白了,数据清洗不是“要不要做”的问题,而是“怎么做才到位”的问题

我个人习惯把清洗流程做成自动化管道,每次拿到新数据就跑一遍。虽然前期搭建管道花点时间,但后面省下的时间绝对值得。你想想看,如果每次都要手动检查数据,那还怎么做高频交易?

好了,这一章的内容就到这里。记住一句话:数据干净了,策略才有意义


专注资料整理