3. Tick数据结构:Tick数据字段详解与预处理

Tick数据是金融市场最细粒度的交易记录,每一笔成交或每一次报价变动都会生成一条Tick记录。理解Tick数据的字段含义、掌握清洗与预处理方法,是构建高频回测系统的基石。本节将逐一拆解Tick数据的核心字段,并给出工业级的数据清洗流程。

3.1 Tick数据核心字段详解

一条完整的Tick记录通常包含以下字段,按功能可分为时间标识、成交信息、报价信息和辅助信息四类。

字段类别 字段名称 数据类型 说明 典型示例
时间标识 Timestamp datetime / int64 交易所撮合时间,精度通常为毫秒或微秒 2025-03-21 09:30:00.123
Exchange string 交易所代码(如SSE、SZSE、CME) SSE
成交信息 LastPrice float64 最新成交价 15.28
Volume int64 当前Tick的成交量(非累计) 200
Turnover float64 当前Tick的成交金额 3056.00
买卖盘口 BidPrice1 ~ BidPrice5 float64 买一到买五档位价格 15.27, 15.26, ...
BidVolume1 ~ BidVolume5 int64 买一到买五档位挂单量 500, 800, ...
AskPrice1 ~ AskPrice5 float64 卖一到卖五档位价格 15.29, 15.30, ...
AskVolume1 ~ AskVolume5 int64 卖一到卖五档位挂单量 300, 600, ...
辅助信息 Symbol / InstrumentID string 合约代码或股票代码 600519.SH
关键概念: LastPrice 与 Bid/Ask 的关系决定了市场微观结构。当 LastPrice 等于 AskPrice1 时,表示主动买入(吃单);当 LastPrice 等于 BidPrice1 时,表示主动卖出。这一关系是构建订单流不平衡指标的基础。

3.2 Tick数据清洗与预处理

原始Tick数据通常存在缺失值、异常值、重复记录和时间错乱等问题。以下是一套标准化的预处理流程。

3.2.1 清洗流程总览

  1. 时间排序与去重:按 Timestamp 升序排列,删除完全重复的行(所有字段相同)。
  2. 缺失值处理:对价格字段(LastPrice、BidPrice1、AskPrice1)的缺失值,使用前向填充(ffill);对成交量缺失值,直接填充为0。
  3. 异常价格过滤:剔除价格超出 [1, 100000] 区间的记录,以及价格变动超过阈值(如 ±20%)的跳变点。
  4. 盘口逻辑校验:确保 BidPrice1 < AskPrice1,且各档位价格单调(BidPrice1 > BidPrice2 > ...)。
  5. 非交易时段过滤:根据交易所规则,剔除集合竞价、午休、收盘后的无效Tick。
实战经验: 在回测中,不要对 Tick 数据做平滑或插值处理,这会引入未来信息。前向填充是唯一安全的缺失值填充方式。

3.2.2 代码实现示例

import pandas as pd
import numpy as np

def clean_tick_data(df: pd.DataFrame) -> pd.DataFrame:
    """
    标准Tick数据清洗函数
    输入df必须包含列:Timestamp, LastPrice, Volume,
    BidPrice1, AskPrice1, BidVolume1, AskVolume1
    """
    # 1. 时间排序
    df = df.sort_values('Timestamp').reset_index(drop=True)

    # 2. 去重
    df = df.drop_duplicates()

    # 3. 缺失值前向填充(价格类)
    price_cols = ['LastPrice', 'BidPrice1', 'AskPrice1']
    df[price_cols] = df[price_cols].ffill()

    # 4. 成交量缺失补0
    df['Volume'] = df['Volume'].fillna(0)

    # 5. 异常价格过滤
    df = df[(df['LastPrice'] > 1) & (df['LastPrice'] < 100000)]

    # 6. 盘口逻辑校验
    df = df[df['BidPrice1'] < df['AskPrice1']]

    # 7. 价格跳变过滤(单笔变动超过20%视为异常)
    df['price_change_pct'] = df['LastPrice'].pct_change().abs()
    df = df[df['price_change_pct'] < 0.20]
    df = df.drop(columns=['price_change_pct'])

    return df

3.3 Tick数据字段关系与处理流程

下面的SVG图展示了Tick数据从原始记录到清洗后数据的完整处理链路,以及各字段之间的逻辑关系。

Tick数据字段关系与清洗流程 原始Tick记录 Timestamp, LastPrice Volume, Bid/Ask 5档 时间排序与去重 按Timestamp升序 drop_duplicates() 缺失值填充 价格: ffill 成交量: fillna(0) 异常价格过滤 价格区间 [1, 100000] 跳变 < 20% 盘口逻辑校验 BidPrice1 < AskPrice1 档位价格单调 非交易时段过滤 剔除集合竞价 剔除午休/收盘 清洗后Tick数据 可用于回测的干净、有序、逻辑自洽的数据 ⚠ 注意事项: 清洗顺序不可颠倒:先排序去重 → 再填充缺失 → 最后过滤异常。若先过滤再填充,可能将异常值前向传播。

3.4 常见数据质量问题与处理策略

问题类型 典型表现 影响 处理策略
时间戳重复 同一毫秒出现多条相同价格记录 回测中重复计算收益 保留第一条,或按Volume合并
价格为零或负数 LastPrice = 0 或 -1 导致收益率计算崩溃 直接删除该行
盘口倒挂 BidPrice1 > AskPrice1 价差为负,逻辑错误 删除该Tick或交换Bid/Ask
成交量突增 Volume 超过前100个Tick的99%分位数 可能是数据合并错误 用中位数替换或删除
非交易时段数据 午休11:30-13:00出现Tick 引入无效信号 按交易所日历过滤
重要提醒: 在回测中,绝对不要对Tick数据进行未来数据填充(如使用后向填充或插值)。任何基于未来信息的填充都会导致回测结果严重失真,产生虚假的高收益。

3.5 数据预处理后的字段衍生

清洗完成后,通常需要衍生以下关键字段用于回测策略:

  • Spread = AskPrice1 - BidPrice1(买卖价差)
  • MidPrice = (BidPrice1 + AskPrice1) / 2(中间价)
  • OrderFlowImbalance = (AskVolume1 - BidVolume1) / (AskVolume1 + BidVolume1)(订单流不平衡)
  • LogReturn = log(LastPrice / LastPrice.shift(1))(对数收益率)
  • VolumeWeightedPrice = Turnover / Volume(加权均价,用于大单拆分)
# 字段衍生示例
df['Spread'] = df['AskPrice1'] - df['BidPrice1']
df['MidPrice'] = (df['BidPrice1'] + df['AskPrice1']) / 2
df['OrderFlowImbalance'] = (df['AskVolume1'] - df['BidVolume1']) / (df['AskVolume1'] + df['BidVolume1'] + 1e-8)
df['LogReturn'] = np.log(df['LastPrice'] / df['LastPrice'].shift(1))