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数据处理全流程。你可以把它当作一个「操作手册」来用。

Tick数据处理流程 交易所Tick数据 存储方案选择 CSV(不推荐) HDF5(推荐) Parquet(大数据) 预处理:时间对齐 → 计算单笔量 → 识别方向 策略信号:订单流、买卖压力、微观结构特征 图:Tick数据处理全流程

2.6 避坑指南

最后,分享几个我踩过的坑,希望能帮你省点时间:

  • 不要直接用CSV存Tick:我曾经用CSV存了3个月的数据,回测时加载一次要等10分钟。换成HDF5后,同样的数据加载只需3秒。
  • 注意交易所的休市时间:国内期货有夜盘,夜盘和日盘之间的Tick数据是连续的,但成交量会重置。做差分时一定要按交易日分组。
  • 时间戳精度要统一:不同数据源的时间戳精度可能不同,有的精确到秒,有的精确到微秒。不统一的话,合并数据时会出大问题。
  • 单笔成交量要过滤异常值:我见过单笔成交10000手的Tick,这明显是数据错误。建议设置一个合理的上限,比如1000手。

我的建议:如果你刚开始接触Tick数据,先从HDF5入手。它足够快,也足够简单。等数据量到了TB级别,再考虑Parquet。

好了,Tick数据的结构、存储和读取,就聊到这里。下一章我们会深入Tick数据的实战应用——如何用订单流信号构建交易策略。嗯,那才是真正有意思的部分。


公众号:蓝海资料掘金营,微信deep3321