第3章:Tick数据获取与清洗

做微观结构研究,第一步就是搞数据。这步要是没做好,后面所有模型都是空中楼阁。我见过太多人花大把时间调模型,结果发现数据本身就有问题——白忙一场。

今天咱们就聊聊Tick数据的获取和清洗。说白了,就是怎么把原始数据弄到手,再把它收拾干净。

3.1 数据源:从哪搞Tick数据?

Tick数据不是随便哪个平台都能拿到的。我把它分成两类:交易所直连和第三方数据商。

3.1.1 交易所API

交易所是数据的源头。国内几家主流交易所都提供API接口,但门槛不低。

交易所 API类型 Tick频率 费用
上交所 MDGW(行情网关) 3秒快照 按席位收费
深交所 V5行情接口 3秒快照 按流量计费
中金所 CTP行情 逐笔成交 按年收费

我个人习惯用CTP接口拿期货Tick数据。为什么?因为它提供的是逐笔成交,不是快照。你想想看,3秒快照里可能错过了几十笔交易,这对微观结构分析来说,信息损失太大了。

小提示: 如果你做股票Tick研究,建议用Level-2数据。普通Level-1行情只有3秒快照,Level-2才有逐笔委托和逐笔成交。

3.1.2 第三方数据商

交易所API虽然权威,但维护成本高。你得自己搭服务器、写接收程序、处理断线重连。说白了,不是每个团队都有这个精力。

这时候第三方数据商就派上用场了。我常用的几家:

  • Wind:覆盖面最广,但Tick数据更新有延迟
  • 聚宽:回测友好,Python接口直接调用
  • RiceQuant:历史Tick数据质量不错
  • Tushare:开源免费,适合个人研究

我在项目中遇到过一个问题:某第三方数据商的Tick数据,在开盘前5分钟经常出现时间戳错乱。后来排查发现,是他们服务器时钟同步没做好。嗯,这里要注意——用第三方数据,一定要做交叉验证。

3.2 数据清洗流程

拿到原始Tick数据后,别急着分析。先过一遍清洗流程。我总结了一套标准流程,大概分四步:

核心流程: 格式统一 → 时间对齐 → 缺失处理 → 异常过滤

咱们用Python代码走一遍。假设你从CTP拿到了期货Tick数据:

import pandas as pd
import numpy as np

# 原始数据示例
raw_data = pd.DataFrame({
    'time': ['09:30:01.123', '09:30:01.456', '09:30:02.001'],
    'price': [5000.0, 5001.5, 4998.0],
    'volume': [10, 5, 20],
    'bid_price': [4999.5, 5000.0, 4997.5],
    'ask_price': [5000.5, 5002.0, 4998.5]
})

# 第一步:格式统一
raw_data['time'] = pd.to_datetime(raw_data['time'])
raw_data = raw_data.sort_values('time')

# 第二步:时间对齐(按毫秒对齐)
raw_data['time_ms'] = raw_data['time'].dt.floor('ms')
raw_data = raw_data.drop_duplicates(subset=['time_ms'])

# 第三步:缺失处理
# 用前向填充处理缺失的买卖价
raw_data['bid_price'] = raw_data['bid_price'].ffill()
raw_data['ask_price'] = raw_data['ask_price'].ffill()

# 第四步:异常过滤
# 过滤掉价格超过3个标准差的异常点
mean_price = raw_data['price'].mean()
std_price = raw_data['price'].std()
raw_data = raw_data[
    (raw_data['price'] > mean_price - 3*std_price) & 
    (raw_data['price'] < mean_price + 3*std_price)
]

这段代码看着简单,但每一步都有坑。我一个个说。

3.2.1 格式统一

不同数据源的时间格式五花八门。有的是字符串,有的是时间戳,有的带时区。我建议统一转成datetime格式,方便后续处理。

曾经有一次,我拿到的数据里时间戳是Unix毫秒数,但文档里写的是秒。结果差了1000倍,回测出来的策略收益高得离谱——后来才发现是数据问题。

3.2.2 时间对齐

Tick数据不是均匀间隔的。有时候一秒来10笔,有时候10秒没动静。做分析前,最好按固定时间间隔对齐。

我个人习惯按毫秒对齐。为什么?因为高频交易中,毫秒级别的差异就可能影响结论。你想想看,如果两个订单在1毫秒内先后到达,谁先谁后?这直接决定了成交顺序。

3.2.3 缺失处理

Tick数据缺失是常态。常见原因:

  • 网络丢包
  • 交易所行情中断
  • 数据商采集故障

处理方式要看场景。如果是做流动性分析,我建议用前向填充——用最近的有效值填补。如果是做波动率建模,可以考虑插值法。

注意: 千万别用均值填充Tick数据!这会把市场微观结构完全破坏掉。我曾经见过有人这么干,结果算出来的买卖价差全是错的。

3.3 异常值处理

异常值在Tick数据里很常见。我把它分成三类:

类型 特征 处理方法
价格跳变 价格瞬间变化超过阈值 3σ法则或IQR过滤
负价格 价格小于0 直接删除
时间戳重复 多条记录时间相同 保留第一条或聚合

这里重点说说价格跳变。在正常市场中,价格是连续变化的。如果突然出现一个离谱的价格,多半是数据错误。

举个例子:某股票当前价格100元,突然出现一笔成交价是1000元。这明显不合理。我一般用3σ法则来检测:

def detect_price_anomaly(df, window=20, threshold=3):
    """检测价格异常点"""
    df['rolling_mean'] = df['price'].rolling(window=window).mean()
    df['rolling_std'] = df['price'].rolling(window=window).std()
    df['z_score'] = (df['price'] - df['rolling_mean']) / df['rolling_std']
    return df[df['z_score'].abs() > threshold]

这段代码用滚动窗口计算Z-score。如果某个价格点偏离均值超过3个标准差,就标记为异常。

我曾经在国债期货数据上跑这个检测,发现每天下午2点半左右都会出现一批异常值。后来一查,原来是交易所在那段时间切换数据源,导致时间戳错位。

3.4 知识体系总览

说了这么多,咱们用一张图把整个流程串起来:

Tick数据获取与清洗流程 数据源 交易所API / 第三方数据商 数据清洗 格式统一 → 时间对齐 异常值处理 3σ法则 / IQR过滤 数据源对比 • 交易所API:实时、权威、成本高 • 第三方数据商:便捷、历史全 • 推荐:CTP逐笔成交数据 • 注意:交叉验证数据质量 清洗关键步骤 • 格式统一:转datetime • 时间对齐:毫秒级对齐 • 缺失处理:前向填充 • 去重:保留有效记录 异常类型与处理 • 价格跳变:3σ过滤 • 负价格:直接删除 • 时间戳重复:聚合处理 • 滚动窗口检测异常 核心原则 数据质量决定模型上限 宁可少数据,不要坏数据

这张图把整个流程串起来了。从数据源开始,经过清洗、异常处理,最后得到干净可用的Tick数据。每一步都有讲究,不能跳过。

我的经验: 数据清洗花的时间,通常占整个项目时间的60%以上。别嫌麻烦,这一步做扎实了,后面建模会顺畅很多。

好了,关于Tick数据获取与清洗,今天就聊到这儿。记住一句话:数据质量决定模型上限。你模型再牛,数据是垃圾,结果就是垃圾。

专注资料整理