数据源与数据清洗:Tick级数据、Level 1/Level 2数据、数据质量检查与存储方案

做高频交易,数据就是你的石油。但原油不能直接烧,对吧?

我见过太多团队,策略逻辑写得天花乱坠,结果一跑实盘就崩。查到最后,问题全出在数据上。时间戳歪了、价格跳空了、买卖档对不上……这些坑,我几乎都踩过一遍。

今天咱们就把数据这块地基打牢。从数据源分类,到清洗检查,再到存储方案,一条线捋清楚。

4.1 数据源的层级:Tick、Level 1、Level 2

先搞清楚我们面对的是什么数据。交易所给的数据,按颗粒度分三层。

数据层级 包含内容 典型频率 用途
Tick级 每一笔成交的逐笔数据(价格、数量、时间、成交标识) 微秒级 订单流分析、微观结构建模
Level 1 最优买卖价(Best Bid/Offer)、最新成交价、成交量 毫秒级或秒级快照 常规策略信号、盘口监控
Level 2 多档买卖盘口(通常5-10档)、逐笔委托、逐笔成交 微秒级快照+逐笔流 订单簿重建、流动性分析、高频做市

我个人习惯,做高频策略至少要用Level 2数据。Level 1太粗糙了,你看不到订单簿的深度变化。举个例子,有一次我发现某只股票Level 1的买一价没变,但Level 2显示买二到买五全被撤单了——这明显是有人在挂假单诱多。如果你只看Level 1,就被骗进去了。

核心原则:策略的时间尺度越短,需要的数据颗粒度就越细。做秒级以上的策略,Level 1勉强够用;做毫秒级甚至微秒级的策略,必须上Tick级或Level 2。

4.2 数据质量检查:缺失值、异常值、时间戳对齐

数据拿到手,别急着用。先做三件事:查缺、查异、对时间。

4.2.1 缺失值处理

高频数据里缺失值很常见。网络抖动、交易所撮合引擎短暂卡顿、数据供应商丢包,都可能导致某一段数据空白。

怎么查?我一般用两个方法:

  • 时间间隔检查:计算相邻两条数据的时间差。如果间隔超过正常频率的3倍以上,就标记为缺失区间。
  • 成交量/笔数检查:如果某段时间内成交量为0,但前后都有活跃交易,那大概率是数据丢了。

处理方式分情况:

  • 缺失区间很短(比如几毫秒),可以用前值填充或线性插值。
  • 缺失区间较长(比如几秒以上),我建议直接丢弃这段数据。高频策略对数据真实性要求极高,插值出来的假数据比缺失更危险。

注意:千万不要在Tick级数据上做均值插值。Tick数据是离散事件,不是连续信号。你插一个中间价格出来,这个价格在真实市场中从未存在过,策略会学歪。

4.2.2 异常值检测

异常值在高频数据里很要命。一个错误的价格,可能让你的策略瞬间爆仓。

我遇到过最离谱的一次,某数据源把一笔成交价多打了一个零,100块变成1000块。我的策略以为价格暴涨,直接下了空单……还好风控拦住了。

常用的检测方法:

  • 价格突变检测:计算相邻Tick的价格变化率。如果变化率超过阈值(比如0.5%),标记为可疑。
  • 买卖价差检测:如果某时刻的买价高于卖价(价差为负),这肯定是数据错误。
  • 成交量异常:单笔成交量超过该股票日均成交量的某个倍数(比如10倍),需要人工复核。

我的经验:异常值不要直接删除,先标记出来。有时候多个异常值同时出现,反而能发现数据源的系统性bug。我曾经通过分析异常值的分布规律,发现某个数据供应商在特定时间段会重复发送旧数据。

4.2.3 时间戳对齐

这是高频数据清洗里最头疼的一环。不同数据源的时间戳精度不一样,有的精确到毫秒,有的精确到微秒,有的甚至用的是本地时间而非交易所时间。

对齐的核心思路:

  1. 统一时区:全部转为交易所本地时间(比如美股用美东时间,A股用北京时间)。
  2. 统一精度:以精度最低的数据源为准,或者全部截断到毫秒级。
  3. 对齐算法:使用最近邻匹配。比如你有Tick级成交数据和Level 2快照数据,把每条成交数据匹配到离它最近的快照上。
# 伪代码:时间戳对齐示例
def align_timestamps(trade_data, snapshot_data, tolerance=1e-3):
    """
    trade_data: 成交数据,时间戳精度微秒
    snapshot_data: 快照数据,时间戳精度毫秒
    tolerance: 最大允许时间差(秒)
    """
    aligned = []
    for trade in trade_data:
        # 找到时间差最小的快照
        nearest = find_nearest(snapshot_data, trade.timestamp)
        if abs(nearest.timestamp - trade.timestamp) < tolerance:
            aligned.append((trade, nearest))
    return aligned

避坑指南:我曾经遇到过一个数据源,它的时间戳是数据到达服务器的时间,而不是交易所的撮合时间。这两者可能差几十毫秒。做高频策略时,几十毫秒的偏差足以让信号完全失效。一定要确认时间戳的基准。

4.3 数据存储方案:Parquet vs HDF5

清洗好的数据,得找个地方存起来。高频数据量巨大,一天可能几个GB,选错存储格式,读写速度会让你崩溃。

特性 Parquet HDF5
压缩率 高(列式存储,按列压缩) 中(支持压缩,但不如Parquet)
读取速度 快(列裁剪,只读需要的列) 快(但读取整表时较慢)
写入速度 中(需要先缓存再写入) 快(支持追加写入)
跨语言支持 好(Python、R、Java、C++都支持) 好(但Python的h5py和R的rhdf5接口略有差异)
适用场景 分析查询、机器学习特征工程 实时写入、大规模数据归档

我个人偏好Parquet。原因很简单:做量化研究时,我经常只需要读取某几列数据(比如价格和成交量),Parquet的列式存储让我不用加载整张表,速度快很多。

但如果你需要实时写入大量数据,HDF5的追加写入能力更友好。我有个朋友做高频回测系统,每天写入几十GB的Tick数据,他用HDF5,因为Parquet的写入需要先攒一批再写,延迟较高。

我的建议:研究阶段用Parquet,生产环境的数据管道用HDF5。或者两者都用——Parquet存分析用的宽表,HDF5存原始数据流。

4.4 知识体系总览

下面这张图,把本章的核心逻辑串起来了。从数据源到清洗,再到存储,每一步都有坑,每一步都有对策。

高频交易数据流水线 数据源 Tick级 | Level 1 | Level 2 数据清洗 缺失值 | 异常值 | 时间戳对齐 数据存储 Parquet | HDF5 缺失值:时间间隔检查 异常值:价格突变检测 时间戳:最近邻匹配 Parquet:列式存储,分析快 HDF5:追加写入,适合管道 核心原则 策略时间尺度越短,数据颗粒度越细 清洗比建模更花时间,但值得

你看,整个流程其实不复杂。但每一步都有细节。数据源选不对,后面全白干;清洗不干净,策略就是垃圾进垃圾出;存储选不好,回测跑得比乌龟还慢。

我刚开始做高频的时候,觉得策略才是核心,数据只是工具。后来被市场教育了几次,才明白:数据质量决定了策略的上限,策略代码只是把这个上限兑现出来而已。