第三章:Tick数据解析——Tick数据的结构、采集与清洗方法
各位同学,欢迎来到第三章。
前两章我们聊了套利交易的基本逻辑和订单流的核心概念。从这章开始,我们要真正动手了。今天要啃的这块骨头,叫Tick数据。
说白了,Tick数据就是市场最原始的脉搏。每一笔成交、每一次报价变动,都被记录成一条Tick。我做了这么多年量化,可以负责任地告诉你:没有干净的Tick数据,一切高阶策略都是空中楼阁。
3.1 Tick数据长什么样?
先看一个最直观的例子。这是我从某交易所拉下来的一条真实Tick记录:
timestamp, price, volume, side, symbol
2024-01-15 09:30:00.123456, 100.25, 200, B, AAPL
嗯,就这么一行。但你别小看它。每个字段都有讲究:
- timestamp:精确到微秒的时间戳。我见过不少新手直接用秒级数据做套利,结果回测曲线漂亮得不行,实盘一跑就崩。为什么?因为套利拼的就是毫秒甚至微秒级的价差变化。
- price:成交价格。注意,有些交易所给的是成交价,有些给的是买卖报价。这个坑我后面会讲。
- volume:成交量。这里有个细节——是单笔成交量还是累计成交量?不同数据源定义不同。
- side:买卖方向。B代表买方主动吃单,S代表卖方主动砸盘。这是订单流分析的核心。
- symbol:合约代码。跨品种套利时,你得同时盯着好几个symbol。
重要提醒:Tick数据的时间戳精度,直接决定了你的套利策略能否捕捉到真实的价差机会。我个人习惯,低于毫秒级的数据直接弃用。
3.2 数据采集——从哪来,怎么拿?
数据采集这块,我踩过的坑比你们走过的路还多。先说说常见的几个来源:
| 数据源 | 优点 | 缺点 | 我的建议 |
|---|---|---|---|
| 交易所API直连 | 实时、原始、无延迟 | 需要自己维护连接,容易断 | 实盘首选,但要做好重连机制 |
| 第三方数据商 | 稳定、清洗过、有历史数据 | 贵,而且可能有延迟 | 回测用这个,省心 |
| 开源数据(如Polygon) | 免费或低价 | 质量参差不齐,字段不全 | 练手可以,实盘别用 |
我曾经在一个项目中,为了省点钱用了某免费数据源。结果回测时发现价差套利策略年化收益30%,开心得不行。实盘跑了三天,亏了5%。后来一查,原来是数据源的时间戳精度不够,导致我计算的价差全是错的。嗯,从那以后,我再也不敢在数据源上省钱。
采集代码其实不复杂。我给你一个简单的WebSocket订阅示例:
import websocket
import json
def on_message(ws, message):
tick = json.loads(message)
# 这里你可以把tick写入数据库或文件
print(f"收到Tick: {tick['symbol']} @ {tick['price']}")
def on_error(ws, error):
print(f"连接出错: {error}")
# 自动重连逻辑要写在这里
def on_close(ws, close_status_code, close_msg):
print("连接关闭,准备重连...")
def on_open(ws):
# 订阅你需要的合约
subscribe_msg = {
"type": "subscribe",
"symbol": ["BTCUSDT", "ETHUSDT"]
}
ws.send(json.dumps(subscribe_msg))
ws = websocket.WebSocketApp(
"wss://你的交易所地址",
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close
)
ws.run_forever()
小技巧:采集Tick数据时,建议同时记录本地时间戳和交易所时间戳。为什么?因为网络延迟会导致你收到数据的时间比实际发生时间晚几毫秒。做套利时,这个差异足以让你做出错误决策。
3.3 数据清洗——脏数据是量化交易的头号杀手
数据采集回来,你以为就能直接用了吗?太天真了。我见过太多人,数据都没洗干净就开始跑策略,结果亏得底裤都不剩。
常见的脏数据问题,我列了个清单:
- 重复数据:同一笔成交被记录了两次。你想想看,如果你基于成交量做分析,重复数据会让你误判市场深度。
- 缺失数据:某段时间内完全没有Tick。可能是网络断了,也可能是交易所宕机。套利策略遇到这种情况,很容易产生虚假信号。
- 异常价格:比如突然出现一个1000美元的成交,而前后价格都是100美元。这明显是错误数据。
- 时间戳错乱:后发生的交易时间戳反而更早。这在分布式系统中很常见。
怎么清洗?我给你一个实战中验证过的流程:
import pandas as pd
import numpy as np
def clean_ticks(df):
"""
清洗Tick数据
df: DataFrame,包含timestamp, price, volume, side, symbol
"""
# 1. 去重
df = df.drop_duplicates(subset=['timestamp', 'price', 'volume', 'symbol'])
# 2. 按时间排序
df = df.sort_values('timestamp')
# 3. 检查时间戳连续性
time_diff = df['timestamp'].diff()
# 如果时间间隔超过5秒,标记为缺失
df['gap_flag'] = (time_diff > pd.Timedelta(seconds=5)).astype(int)
# 4. 价格异常检测(3倍标准差法)
price_mean = df['price'].rolling(window=100).mean()
price_std = df['price'].rolling(window=100).std()
df['price_anomaly'] = (np.abs(df['price'] - price_mean) > 3 * price_std)
# 5. 剔除异常数据
df_clean = df[~df['price_anomaly']].copy()
# 6. 填充缺失的时间戳(用前一个有效值填充)
df_clean = df_clean.set_index('timestamp').resample('1ms').ffill().reset_index()
return df_clean
# 使用示例
raw_data = pd.read_csv('ticks_raw.csv')
clean_data = clean_ticks(raw_data)
print(f"清洗前: {len(raw_data)} 条, 清洗后: {len(clean_data)} 条")
警告:清洗数据时,千万不要用全局均值填充异常值。我曾经犯过这个错,结果把一次真实的闪崩事件给洗掉了,导致策略完全没捕捉到那次套利机会。正确的做法是用局部滚动窗口的统计量来判断。
3.4 实战中的几个坑
最后,分享几个我在实战中遇到的坑,希望能帮你们少走弯路:
- 不同交易所的时间同步问题:做跨交易所套利时,两个交易所的时间戳可能相差几十毫秒。我建议你用NTP协议同步本地时间,然后以本地时间戳为准。
- Tick数据的存储:一天的数据量可能上亿条。用CSV存?别闹了。我推荐用Parquet格式或者时序数据库(如InfluxDB)。
- 回测时的数据偏差:用历史Tick数据回测时,要注意数据中可能包含了未来信息。比如,你用的数据源可能已经对数据做了后处理,导致某些信息被提前泄露。
好了,这一章的内容就到这里。Tick数据是套利交易的地基,地基不稳,房子再漂亮也得塌。下一章,我们会基于这些干净的Tick数据,构建真正的订单流指标。
记住:数据质量决定策略上限。别急着跑策略,先把数据搞干净。
公众号:蓝海资料掘金营,微信deep3321