第3章:Tick数据获取与清洗
做微观结构研究,第一步就是搞数据。这步要是没做好,后面所有模型都是空中楼阁。我见过太多人花大把时间调模型,结果发现数据本身就有问题——白忙一场。
今天咱们就聊聊Tick数据的获取和清洗。说白了,就是怎么把原始数据弄到手,再把它收拾干净。
3.1 数据源:从哪搞Tick数据?
Tick数据不是随便哪个平台都能拿到的。我把它分成两类:交易所直连和第三方数据商。
3.1.1 交易所API
交易所是数据的源头。国内几家主流交易所都提供API接口,但门槛不低。
| 交易所 | API类型 | Tick频率 | 费用 |
|---|---|---|---|
| 上交所 | MDGW(行情网关) | 3秒快照 | 按席位收费 |
| 深交所 | V5行情接口 | 3秒快照 | 按流量计费 |
| 中金所 | CTP行情 | 逐笔成交 | 按年收费 |
我个人习惯用CTP接口拿期货Tick数据。为什么?因为它提供的是逐笔成交,不是快照。你想想看,3秒快照里可能错过了几十笔交易,这对微观结构分析来说,信息损失太大了。
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数据缺失是常态。常见原因:
- 网络丢包
- 交易所行情中断
- 数据商采集故障
处理方式要看场景。如果是做流动性分析,我建议用前向填充——用最近的有效值填补。如果是做波动率建模,可以考虑插值法。
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数据。每一步都有讲究,不能跳过。
好了,关于Tick数据获取与清洗,今天就聊到这儿。记住一句话:数据质量决定模型上限。你模型再牛,数据是垃圾,结果就是垃圾。