2. Tick级数据:Tick数据的结构、存储与读取
做量化交易的朋友都知道,K线数据是「压缩过的故事」,而Tick数据才是「原始的现场录音」。我个人一直认为,如果你没碰过Tick级数据,你对市场的理解就永远隔着一层纱。
这一章,我们就来拆解Tick数据的里里外外。说白了,就是搞清楚三件事:Tick长什么样、怎么存、怎么读。
2.1 Tick数据的核心结构
先问一个问题:一笔Tick数据到底包含哪些字段?
我刚开始做高频策略时,以为Tick就是「时间+价格+成交量」三个字段。后来被实盘数据狠狠教育了一回——你想想看,如果只有这三个字段,你怎么区分「主动买入」和「主动卖出」?
标准的Tick数据结构,至少包含以下字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| Timestamp | datetime | 精确到毫秒或微秒的时间戳 |
| Symbol | string | 合约代码,如 'rb2401' |
| LastPrice | float | 最新成交价 |
| Volume | int | 当前成交量(累计值) |
| Turnover | float | 当前成交额(累计值) |
| BidPrice1 ~ BidPrice5 | float | 买一到买五价格 |
| BidVolume1 ~ BidVolume5 | int | 买一到买五挂单量 |
| AskPrice1 ~ AskPrice5 | float | 卖一到卖五价格 |
| AskVolume1 ~ AskVolume5 | int | 卖一到卖五挂单量 |
关键点:Volume和Turnover是累计值,不是单笔成交量。要算单笔成交量,得用当前值减去上一笔值。这个坑我踩过,后面会细说。
2.2 为什么Tick数据这么「重」?
你可能已经猜到了——Tick数据量巨大。以国内商品期货为例,一个活跃合约一天能产生几十万笔Tick。如果是股指期货,百万级也是常事。
为什么会这么多?
- 行情推送频率高:交易所每笔成交都会推送一次Tick
- 快照+逐笔:有些交易所还会推送逐笔委托数据,数据量翻倍
- 多合约同时订阅:你不可能只盯一个合约,同时订阅10个合约,一天就是几百万行
我记得第一次处理全市场Tick数据时,一个月的原始数据就占了200多GB。当时我的笔记本直接卡死,嗯,从那以后我就再也不敢用CSV存Tick了。
2.3 Tick数据的存储方案
存储Tick数据,核心就三个字:快、省、稳。
我个人习惯把存储方案分成三个梯队:
2.3.1 CSV(不推荐用于生产)
CSV的好处是「所见即所得」,用Excel就能打开。但缺点也很明显:
- 没有压缩,一个月的Tick数据轻松上10GB
- 读写速度慢,尤其是按时间范围查询时
- 不支持并行写入,多进程写入会乱
我曾经用CSV存了半年的数据,结果回测时加载一次要等5分钟。后来我果断换了方案。
2.3.2 HDF5(推荐用于中小团队)
HDF5是科学计算领域的「老大哥」。它的优势在于:
- 支持列式压缩,存储空间能省60%-80%
- 读取速度快,按时间切片几乎是秒级
- 支持Python的pandas直接读写
下面是我常用的存储代码:
import pandas as pd
import h5py
def save_tick_to_h5(df, filepath, key='tick_data'):
"""
将Tick数据存储为HDF5格式
df: 包含Timestamp、LastPrice等字段的DataFrame
"""
# 设置时间索引
df = df.set_index('Timestamp')
# 使用固定格式存储,压缩率更高
df.to_hdf(
filepath,
key=key,
mode='w',
complevel=5, # 压缩级别,0-9
complib='blosc' # 使用blosc压缩算法
)
print(f'数据已保存至 {filepath}')
def load_tick_from_h5(filepath, start_time=None, end_time=None):
"""
从HDF5读取Tick数据,支持按时间范围筛选
"""
df = pd.read_hdf(filepath, 'tick_data')
if start_time:
df = df[df.index >= start_time]
if end_time:
df = df[df.index <= end_time]
return df
小技巧:使用blosc压缩时,我建议把complevel设为5。太低压缩率不够,太高写入速度会明显变慢。5是一个不错的平衡点。
2.3.3 Parquet(推荐用于大数据场景)
如果你要处理TB级别的Tick数据,Parquet是更好的选择。它是列式存储格式,配合Spark或Dask使用效果极佳。
import pyarrow.parquet as pq
import pyarrow as pa
def save_tick_to_parquet(df, filepath):
"""
将Tick数据存储为Parquet格式
"""
table = pa.Table.from_pandas(df)
pq.write_table(
table,
filepath,
compression='snappy', # 快速压缩
row_group_size=100000 # 每10万行一个行组
)
def load_tick_from_parquet(filepath):
"""
读取Parquet格式的Tick数据
"""
table = pq.read_table(filepath)
df = table.to_pandas()
return df
2.4 Tick数据的读取与预处理
数据存好了,怎么读出来用?这里有几个常见的坑,我一个个说。
2.4.1 时间戳对齐
不同交易所的时间戳精度不一样。上期所精确到毫秒,中金所精确到微秒。如果你把两个交易所的数据混在一起,时间对齐就是个麻烦事。
我的做法是:统一转成纳秒级时间戳,然后按纳秒对齐。
def align_timestamp(df):
"""
统一时间戳为纳秒级
"""
# 如果已经是datetime类型,直接转
if pd.api.types.is_datetime64_any_dtype(df.index):
df.index = df.index.astype('datetime64[ns]')
else:
# 如果是字符串,先解析
df.index = pd.to_datetime(df.index).astype('datetime64[ns]')
return df
2.4.2 计算单笔成交量
前面说过,交易所推送的Volume是累计值。要算单笔成交量,必须做差分。
注意:差分时要注意「换月」和「休市」的情况。如果两笔Tick之间隔了一个夜盘休市,差分出来的值会异常大。一定要先按交易日分组,再在组内做差分。
def calc_tick_volume(df):
"""
计算单笔成交量
df: 已按时间排序的Tick数据
"""
# 先按日期分组
df['date'] = df.index.date
df['tick_volume'] = df.groupby('date')['Volume'].diff()
# 第一笔的tick_volume设为原始Volume
df['tick_volume'] = df['tick_volume'].fillna(df['Volume'])
# 过滤异常值(比如超过1000手的单笔成交)
df = df[df['tick_volume'] <= 1000]
return df
2.4.3 识别买卖方向
这是Tick数据最有价值的地方。通过比较成交价和买卖盘口,可以判断一笔成交是主动买还是主动卖。
def identify_trade_direction(df):
"""
识别每笔Tick的买卖方向
规则:
- 成交价 >= 卖一价:主动买入
- 成交价 <= 买一价:主动卖出
- 其他情况:中性(盘口内成交)
"""
conditions = [
(df['LastPrice'] >= df['AskPrice1']),
(df['LastPrice'] <= df['BidPrice1'])
]
choices = ['Buy', 'Sell']
df['direction'] = np.select(conditions, choices, default='Neutral')
return df
实战经验:我曾在实盘策略中只用「主动买入量 - 主动卖出量」这个指标,就捕捉到了不少短线机会。说白了,Tick数据里的买卖方向,就是市场最真实的资金流向。
2.5 知识体系总览
下面这张图,是我梳理的Tick数据处理全流程。你可以把它当作一个「操作手册」来用。
2.6 避坑指南
最后,分享几个我踩过的坑,希望能帮你省点时间:
- 不要直接用CSV存Tick:我曾经用CSV存了3个月的数据,回测时加载一次要等10分钟。换成HDF5后,同样的数据加载只需3秒。
- 注意交易所的休市时间:国内期货有夜盘,夜盘和日盘之间的Tick数据是连续的,但成交量会重置。做差分时一定要按交易日分组。
- 时间戳精度要统一:不同数据源的时间戳精度可能不同,有的精确到秒,有的精确到微秒。不统一的话,合并数据时会出大问题。
- 单笔成交量要过滤异常值:我见过单笔成交10000手的Tick,这明显是数据错误。建议设置一个合理的上限,比如1000手。
我的建议:如果你刚开始接触Tick数据,先从HDF5入手。它足够快,也足够简单。等数据量到了TB级别,再考虑Parquet。
好了,Tick数据的结构、存储和读取,就聊到这里。下一章我们会深入Tick数据的实战应用——如何用订单流信号构建交易策略。嗯,那才是真正有意思的部分。
公众号:蓝海资料掘金营,微信deep3321