2. 数据获取与预处理:从源头到可用数据

做量化交易,尤其是高频策略,最头疼的往往不是策略本身,而是数据。我常说一句话:「策略决定上限,数据决定下限」。你想想看,如果拿到的逐笔数据都是脏的、乱的、时间戳对不齐的,再牛的模型也白搭。

这一章,我们就来聊聊怎么把原始数据变成能用的「干净数据」。我会结合我这些年踩过的坑,给你拆解每一步。

2.1 数据来源:交易所与数据商

逐笔数据,也就是 Level-2 行情里的核心内容。它记录了每一笔成交的细节:时间、价格、成交量、买卖方向等等。

获取渠道主要有两个:

  • 交易所直连:比如上交所的 L2 行情、深交所的 L2 行情。延迟最低,但门槛高,需要券商资质或机构席位。
  • 第三方数据商:比如万得、聚宽、通联数据、RiceQuant 等。方便,但数据质量参差不齐。

我个人习惯用第三方数据商,因为省事。但要注意一点:不同数据商对同一笔成交的解析可能不同。我曾经遇到过,同一个时间点的同一笔交易,A 数据商标记为「主动买」,B 数据商标记为「主动卖」。嗯,这里要留个心眼。

核心原则: 数据源要稳定、可追溯。建议固定使用一家数据商,并定期做交叉验证。

2.2 数据格式解析:CSV vs Parquet

拿到数据后,第一件事就是看格式。最常见的两种:

格式 优点 缺点 我的建议
CSV 通用、可读性强、Excel 能打开 文件大、读写慢、不支持压缩 小数据量(<1GB)可以用
Parquet 列式存储、压缩率高、读写快 需要特定库(如 pyarrow) 大数据量(>1GB)首选

举个例子,我手头有一份某天的逐笔数据,CSV 格式 2.3GB,换成 Parquet 后只有 400MB。读取速度从 30 秒降到了 3 秒。你说香不香?

代码示例:

import pandas as pd

# 读取 CSV
df_csv = pd.read_csv('tick_data.csv')

# 读取 Parquet
df_parquet = pd.read_parquet('tick_data.parquet')

# 保存为 Parquet
df_csv.to_parquet('tick_data.parquet', compression='snappy')
小技巧:snappy 压缩,速度最快,压缩比也不错。别用 gzip,太慢了。

2.3 数据清洗:去重、缺失值、异常值

数据拿到手,别急着分析。先做三件事:去重、补缺失、过滤异常。

2.3.1 去重

数据商有时候会重复推送同一笔成交。怎么判断?看时间戳 + 成交编号 + 价格 + 数量,四个字段完全一致就是重复。

# 去重
df = df.drop_duplicates(subset=['time', 'trade_id', 'price', 'volume'])

我曾经遇到过一个极端案例:某数据商在某个时间段内,把同一笔成交重复推送了 5 次。如果不做去重,你的成交量统计会直接翻 5 倍,策略信号全乱套。

2.3.2 缺失值处理

逐笔数据里,缺失值通常出现在价格或成交量字段。原因可能是数据商采集时丢包了。

处理方法:

  • 直接删除:如果缺失比例很小(<0.1%),直接删掉这行。
  • 向前填充:用上一笔成交的价格填充。适合价格连续的场景。
  • 插值:用前后两笔的平均值。适合成交量。
# 删除缺失值
df = df.dropna(subset=['price', 'volume'])

# 向前填充
df['price'] = df['price'].ffill()
注意: 不要用均值填充!逐笔数据的价格波动很大,均值会引入偏差。

2.3.3 异常值过滤

什么叫异常值?比如价格突然跳到 0 元,或者成交量突然变成 99999999 手。这些明显是数据错误。

我常用的方法:

  • 价格范围过滤:比如股票价格不可能低于 0.01 元,也不可能高于 1000 元(除非特殊情况)。
  • 成交量过滤:单笔成交量超过该股票日均成交量的 10%,基本就是异常。
  • 涨跌幅过滤:单笔成交价格相比上一笔涨跌幅超过 10%,大概率是错误数据。
# 价格过滤
df = df[(df['price'] >= 0.01) & (df['price'] <= 1000)]

# 成交量过滤(假设日均成交量是 100000 手)
df = df[df['volume'] <= 100000 * 0.1]

2.4 时间对齐与标准化

这是最容易被忽视的一步,也是坑最多的一步。

不同数据商的时间戳格式可能不一样:有的用毫秒,有的用微秒,有的直接用字符串。而且,交易所的时钟和数据商的时钟可能存在偏差。

我的做法:

  1. 统一时间格式:全部转为 datetime64[ns] 类型。
  2. 对齐到毫秒:逐笔数据的时间精度通常是毫秒级,微秒级的数据可以四舍五入到毫秒。
  3. 检查时间连续性:如果两笔成交之间间隔超过 1 秒,说明可能有数据缺失。
# 时间标准化
df['time'] = pd.to_datetime(df['time'], unit='ms')

# 对齐到毫秒
df['time'] = df['time'].dt.floor('ms')

# 检查时间间隔
df['time_diff'] = df['time'].diff().dt.total_seconds()
print(df['time_diff'].describe())
避坑指南: 我曾经因为时间戳没对齐,导致回测结果和实盘差了 0.5 秒。0.5 秒在逐笔交易里,可能就是好几笔成交的差距。后来我加了一个步骤:用交易所的行情快照时间戳做校准。

2.5 知识体系总览

下面这张图,概括了本章的核心逻辑。你可以把它当作一个检查清单:

逐笔数据获取与预处理流程 数据获取 交易所 / 数据商 格式解析 CSV / Parquet 数据清洗 去重 / 缺失 / 异常 时间对齐 标准化 Level-2 行情订阅 列式存储 vs 行式存储 drop_duplicates / ffill datetime64[ns] 最终产出:干净、对齐、可用的逐笔成交数据 可用于回测、因子计算、策略信号生成

这张图把整个流程串起来了。从数据获取到时间对齐,每一步都不能跳过。我见过不少新手,拿到数据直接跑策略,结果回测曲线漂亮得不行,实盘一塌糊涂。为什么?数据没洗干净。

好了,这一章的内容就到这里。数据预处理是基本功,也是最能体现一个量化工程师水平的地方。下一章,我们会深入逐笔数据的核心字段,看看每一列到底代表什么。


蓝海数据掘金营,专注资料整理