第1章:数据源与数据清洗——Tick级数据的“原石”打磨术

各位同学,欢迎来到高频交易的第一道关卡。

说实话,很多新手一上来就研究策略、回测、机器学习,结果呢?数据质量不过关,策略跑得再漂亮也是空中楼阁。我见过太多团队,花了几百万买策略,最后发现是数据对齐出了问题——嗯,那叫一个惨。

今天我们就来聊聊:Tick级数据怎么拿、Level 2/Level 3怎么解析、数据怎么洗白白、最后怎么存。说白了,就是教你如何把市场的“原油”炼成“汽油”。

核心观点:高频交易中,数据质量决定策略上限。80%的坑都出在数据清洗环节。

1.1 Tick级数据获取:每秒几千笔的“心跳”

Tick数据是什么?就是每一笔成交的原始记录。比如某只股票在14:30:01.123秒成交了100股,价格10.01元——这就是一条Tick。

我个人习惯把Tick数据比作市场的“心电图”。你想想看,如果心电图有毛刺,医生能做出正确诊断吗?

获取Tick数据的常见渠道:

  • 交易所直连(如CTP、XTP):延迟最低,但门槛高,适合机构
  • 数据供应商(如Wind、聚宽、Tushare):方便,但可能有延迟
  • 开源方案(如ccxt对接加密货币):免费,但质量参差不齐

我的经验:刚开始做量化时,我图便宜用了某免费数据源,结果回测年化50%,实盘直接亏成狗。后来发现数据里少了10%的Tick——那些都是大单成交啊!

下面是一个简单的Python代码,演示如何从CSV读取Tick数据并做初步检查:

import pandas as pd

# 读取Tick数据
df = pd.read_csv('tick_data.csv', parse_dates=['time'])
print(f"总行数: {len(df)}")
print(f"时间范围: {df['time'].min()} ~ {df['time'].max()}")

# 检查是否有缺失值
print(f"缺失值统计:\n{df.isnull().sum()}")

1.2 Level 2/Level 3数据解析:看透市场的“底牌”

普通行情只能看到五档买卖盘,Level 2能看到十档,Level 3甚至能看到逐笔委托。说白了,Level 3就是市场的“底牌”——谁在挂单、谁在撤单、谁在成交,一清二楚。

Level 2 vs Level 3 对比:

维度 Level 2 Level 3
数据量 约50-100条/秒 约500-2000条/秒
内容 十档买卖盘、逐笔成交 逐笔委托、撤单、成交
解析难度 中等 高(需处理委托链)
策略价值 较高 极高(可做订单流分析)

解析Level 3数据时,我遇到过一个大坑:委托编号的连续性。有些交易所的委托编号会跳号,如果你按顺序处理,就会漏掉撤单信息。我曾经因为这个bug,整整排查了两天。

# 解析Level 2快照数据示例(伪代码)
def parse_level2_snapshot(raw_bytes):
    """解析Level 2快照"""
    # 假设raw_bytes是二进制数据
    bid_prices = []  # 买盘价格
    bid_volumes = [] # 买盘数量
    
    for i in range(10):  # 十档
        price = struct.unpack('<d', raw_bytes[i*8:(i+1)*8])[0]
        volume = struct.unpack('<I', raw_bytes[80+i*4:84+i*4])[0]
        bid_prices.append(price)
        bid_volumes.append(volume)
    
    return bid_prices, bid_volumes

注意:Level 3数据解析时,一定要处理“委托链”的完整性。一个委托可能被部分成交、部分撤单,你需要维护一个委托状态机。

1.3 数据清洗与对齐:把“脏数据”变成“黄金”

数据清洗,说白了就是去伪存真。Tick数据常见的“脏”问题:

  • 时间戳错乱:比如14:30:01.123后面跟着14:30:01.122
  • 价格异常:比如突然出现0.01元或99999元
  • 重复数据:同一笔成交被记录两次
  • 缺失数据:某几秒内完全没有Tick

我的清洗流程(三步走):

  1. 去重:按时间戳+价格+数量去重
  2. 排序:按时间戳升序排列
  3. 插值:对缺失的Tick做线性插值(注意:只适用于小间隔缺失)
def clean_tick_data(df):
    """清洗Tick数据"""
    # 1. 去重
    df = df.drop_duplicates(subset=['time', 'price', 'volume'])
    
    # 2. 排序
    df = df.sort_values('time')
    
    # 3. 检查时间戳是否递增
    time_diff = df['time'].diff()
    if (time_diff < pd.Timedelta(0)).any():
        print("警告:存在时间戳倒挂!")
        # 这里可以进一步处理
    
    # 4. 过滤异常价格(比如超出[1, 1000]范围)
    df = df[(df['price'] >= 1) & (df['price'] <= 1000)]
    
    return df

避坑指南:我曾经处理过一批加密货币的Tick数据,发现凌晨3点到4点之间数据全部缺失。后来才知道,那是交易所的维护窗口。如果你不做对齐,回测时就会以为那一个小时没有交易——策略会“凭空”盈利。

1.4 数据存储方案:Parquet vs Arrow

数据清洗完了,怎么存?我见过有人用CSV存Tick数据,结果一个月的行情文件就几十个GB,加载一次要半小时——这还怎么玩高频?

推荐方案:Parquet + Arrow

特性 Parquet Arrow
存储格式 列式存储,压缩率高 内存列式格式,零拷贝
读取速度 较快(需解压) 极快(内存映射)
适用场景 长期存储、历史回测 实时计算、高频策略
压缩比 约5:1 ~ 10:1 约2:1(无压缩)

我的实践:用Parquet存历史数据,用Arrow做实时数据管道。比如每天收盘后,把当天的Tick数据转成Parquet归档;盘中则用Arrow格式在内存中流转,延迟可以控制在微秒级。

import pyarrow as pa
import pyarrow.parquet as pq

# 将DataFrame保存为Parquet
df = pd.DataFrame({
    'time': pd.date_range('2024-01-01', periods=1000000, freq='1ms'),
    'price': np.random.uniform(10, 11, 1000000),
    'volume': np.random.randint(1, 1000, 1000000)
})

# 保存
table = pa.Table.from_pandas(df)
pq.write_table(table, 'tick_data.parquet', compression='snappy')

# 读取
table = pq.read_table('tick_data.parquet')
df = table.to_pandas()
print(f"读取完成,共{len(df)}行")

性能对比:同样100万条Tick数据,CSV加载需要3.2秒,Parquet只需要0.4秒——快了8倍。而且Parquet文件只有CSV的1/5大小。

知识体系总览

下面这张图,是我自己总结的Tick数据全流程。你可以把它当作一张“藏宝图”:

Tick级数据全流程架构 数据源 交易所 / 供应商 / 开源 解析层 Level 2 / Level 3 解析 清洗层 去重 / 排序 / 插值 存储 Parquet/Arrow 关键说明: 1. 数据源:优先选择交易所直连,延迟最低;开源方案适合学习和回测 2. 解析层:Level 3需要维护委托状态机,注意委托编号的连续性 3. 清洗层:时间戳对齐是核心,建议使用微秒级精度 4. 存储层:Parquet用于归档,Arrow用于实时计算 ⚠ 常见错误:跳过清洗直接存储,导致后续策略回测失真

好了,这一章的内容就到这里。数据源和数据清洗是高频交易的“地基”,地基不稳,楼盖得再高也得塌。下一章我们会聊到订单簿重建——那是把Level 2数据变成实时买卖压力的核心技术。


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