第1章:高频数据预处理——Tick级数据清洗、对齐与重采样
各位同学,咱们直接进入正题。
高频因子计算,说白了就是跟Tick数据打交道。Tick数据有多脏?我刚开始做量化那会儿,接过一次交易所的原始数据,打开一看差点没把咖啡喷屏幕上——空值、乱序、重复时间戳,什么妖魔鬼怪都有。所以这一章,咱们先把数据洗干净。
1.1 Tick级数据清洗
先说说清洗。Tick数据最常见的几个问题:
- 重复记录:同一毫秒内收到多条相同报价
- 时间戳错乱:后一条数据的时间戳比前一条还早
- 价格异常:比如出现0元成交价,或者涨停价外数据
- 字段缺失:某些字段是NaN,比如卖一价、卖一量
我个人习惯,清洗分三步走:
- 去重:按时间戳+价格+成交量联合去重
- 排序:按时间戳升序排列,确保时序正确
- 过滤:剔除价格<=0、成交量<=0的脏数据
核心原则:宁可少一条数据,也不要一条脏数据。高频因子对异常值极其敏感,一条错误数据可能让整个因子失效。
代码示例:
import pandas as pd
import numpy as np
def clean_tick_data(df):
# 1. 去重
df = df.drop_duplicates(subset=['timestamp', 'price', 'volume'])
# 2. 排序
df = df.sort_values('timestamp').reset_index(drop=True)
# 3. 过滤异常
df = df[(df['price'] > 0) & (df['volume'] > 0)]
return df
避坑指南:我曾经遇到过一个坑——某交易所的Tick数据里,偶尔会出现时间戳为0的记录。如果你不做过滤,这些数据会直接打乱你的重采样结果。所以,记得加一条 df = df[df['timestamp'] > 0]。
1.2 数据对齐与重采样
清洗完了,接下来是对齐。为什么需要对齐?
你想想看,不同股票的交易频率不一样。茅台可能一秒成交几十笔,但某些冷门股可能一分钟才一笔。如果你直接拿原始Tick数据算因子,时间窗口根本对不上。
对齐的核心思路:把不规则Tick数据,映射到统一的时间网格上。
常用的重采样方法:
- 等间隔重采样:比如每1秒、每100ms取一个快照
- 基于成交量重采样:每成交一定股数取一个样本
- 基于成交额重采样:每成交一定金额取一个样本
我个人最常用的是等间隔重采样,因为它最直观,也最容易跟其他因子对齐。
代码示例:
def resample_tick(df, freq='1s'):
# 设置时间戳为索引
df = df.set_index('timestamp')
# 重采样:取最后一笔价格作为该秒的收盘价
resampled = df.resample(freq).agg({
'price': 'last',
'volume': 'sum',
'ask_price1': 'last',
'bid_price1': 'last'
})
return resampled.dropna()
注意:重采样时,如果某段时间内没有交易数据,你会得到NaN。这时候就需要处理缺失值了。千万别直接dropna,否则你的时间序列会变得不连续。
1.3 缺失值处理
缺失值怎么处理?嗯,这里要分情况讨论。
高频数据里的缺失值,通常有两种:
- 短暂缺失:比如某几秒没有成交,但前后都有数据
- 长时间缺失:比如停牌、熔断,或者数据源断流
对于短暂缺失,我建议用前向填充(forward fill)。说白了,就是用上一笔的价格填充当前空缺。为什么?因为高频交易中,价格变化是连续的,前一笔价格是最合理的估计。
对于长时间缺失,比如超过10秒没有数据,那就别填充了。直接标记为缺失,或者剔除该时间段。强行填充反而会引入噪声。
代码示例:
def handle_missing(df, max_gap=10):
# 前向填充,但限制最大填充间隔
df = df.fillna(method='ffill', limit=max_gap)
# 超过最大间隔的缺失值,直接设为NaN
df[df.isna().any(axis=1)] = np.nan
return df
我的经验:曾经有个策略,回测时表现很好,实盘却一直亏。查了半天,发现是缺失值处理方式不对——我用的是线性插值,结果在停牌期间插出了一堆不存在的价格。后来改成前向填充,问题就解决了。
1.4 异常值检测
最后一步,异常值检测。高频数据里的异常值,通常表现为:
- 价格跳变:比如前一秒还是100元,下一秒突然变成1000元
- 成交量暴增:正常每秒成交100股,突然变成100万股
- 买卖价差异常:比如卖一价比买一价高出10%
检测方法有很多,我推荐两种:
- Z-score方法:计算价格或成交量的Z-score,超过3倍标准差视为异常
- IQR方法:用四分位距(IQR)检测,超出1.5倍IQR范围的视为异常
我个人更倾向于IQR方法,因为它对极端值不那么敏感,更适合高频数据。
代码示例:
def detect_outliers_iqr(df, column='price'):
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.index.tolist()
注意:异常值不一定要直接删除。有时候,异常值本身可能就是信号。比如某只股票突然出现大单成交,可能意味着有机构在操作。所以,检测出异常值后,先标记,再根据业务逻辑决定是否剔除。
知识体系总览
下面这张图,概括了本章的核心逻辑:
这张图把整个预处理流程串起来了。从原始Tick数据开始,经过清洗、对齐、缺失值处理、异常值检测,最终得到干净、规整的高频数据。每一步都不可或缺。
总结一下:高频数据预处理,说白了就是「去脏、对齐、补缺、排异」八个字。把这四步做好,你的因子计算就成功了一半。