3、数据读取与初步探索:读取CSV/Parquet文件、DataFrame基本信息查看、描述性统计与缺失值概览

做高频做市,第一步就是跟数据打交道。说实话,这一步要是没做好,后面所有策略都是空中楼阁。我见过太多人一上来就急着写策略,结果数据读进来全是乱码,或者时间戳对不上,白白浪费好几天。

今天我们就来聊聊,怎么把原始数据请进门,然后快速摸清它的底细。

3.1 读取CSV文件——最基础也最坑

CSV文件是期权数据最常见的格式。交易所、数据商给的数据,十有八九都是CSV。但你别小看它,读取的时候稍不注意就会翻车。

我个人习惯用pandas.read_csv(),但有几个参数必须盯紧:

import pandas as pd

# 我建议这样读,避免踩坑
df = pd.read_csv(
    'option_tick_20240101.csv',
    parse_dates=['timestamp'],      # 时间列自动解析
    dtype={'symbol': 'str'},        # 合约代码强制字符串
    low_memory=False                # 防止混合类型警告
)

嗯,这里要注意。很多CSV文件里的时间戳是字符串,比如"2024-01-01 09:30:00.123456"。如果不加parse_dates,它就是个普通字符串,你后面做时间序列分析就全乱了。

我曾经踩过的坑:有一次读取美股期权数据,合约代码列里有数字开头的代码(比如"SPXW240105C04500000"),pandas默认把它当成了浮点数,结果精度丢失,后面匹配合约全对不上。从那以后,我所有合约代码列都强制指定为字符串类型。

3.2 读取Parquet文件——高频数据的首选

做高频做市,数据量动不动就是几十GB。CSV文件读起来慢,占空间也大。这时候Parquet格式就派上用场了。

Parquet是列式存储格式,压缩率高,读取速度快。我自己的回测系统,现在全部用Parquet存储。

# 读取Parquet文件,简单到没朋友
df = pd.read_parquet('option_tick_20240101.parquet')

# 如果你只想读某几列,可以这样
df = pd.read_parquet(
    'option_tick_20240101.parquet',
    columns=['timestamp', 'symbol', 'bid_price', 'ask_price']
)

你想想看,一个10GB的CSV文件,转成Parquet可能只有2-3GB,读取速度还能快5-10倍。说白了,这就是高频交易的标配。

我的建议:如果你每天处理的数据量超过1GB,强烈建议转成Parquet格式。不仅省硬盘,还能让你的策略迭代速度快一个数量级。

3.3 DataFrame基本信息查看——快速摸清数据底细

数据读进来了,第一件事不是写策略,而是看看它长什么样。我一般会按顺序做这几步:

# 1. 看前几行,确认数据格式
print(df.head())

# 2. 看数据总量和列名
print(f"数据量: {df.shape[0]} 行, {df.shape[1]} 列")
print(f"列名: {df.columns.tolist()}")

# 3. 看每列的数据类型
print(df.dtypes)

# 4. 看内存占用
print(f"内存占用: {df.memory_usage(deep=True).sum() / 1024**2:.2f} MB")

为什么要看内存占用?因为高频数据量太大了。我曾经有一次加载了3天的逐笔成交数据,直接占了我32GB内存,电脑卡死。后来我学会了用dtype参数压缩数据类型,内存直接降到8GB。

3.4 描述性统计——数据质量的第一道防线

描述性统计能帮你快速发现异常。比如价格出现负数、买卖价差大得离谱,这些一眼就能看出来。

# 对所有数值列做描述性统计
print(df.describe())

# 对特定列做更细致的统计
print(df[['bid_price', 'ask_price', 'bid_size', 'ask_size']].describe())

我一般重点关注这几个指标:

  • count:非空值的数量,如果比总行数少,说明有缺失值
  • min / max:价格范围是否合理,有没有出现0或负数
  • mean / std:均值是否在合理区间,标准差是否异常大
  • 25% / 75%:数据分布是否集中,有没有极端值
实战经验:有一次我发现某只期权的最优卖价(ask_price)最小值是0.01,但其他合约都是0.05以上。查了半天,原来是数据商在某个时间点把价格单位搞错了。如果没有做描述性统计,这种错误根本发现不了。

3.5 缺失值概览——数据清洗的起点

高频数据里,缺失值是个大问题。交易所偶尔会断流,网络偶尔会抖动,这些都会导致数据缺失。

# 查看每列的缺失值数量
print(df.isnull().sum())

# 查看缺失值占比
print((df.isnull().sum() / len(df) * 100).round(2))

我一般会按这个标准处理:

缺失比例 处理方式
< 1% 直接删除或向前填充
1% - 5% 用插值法填充,比如线性插值
> 5% 需要检查数据源,可能整个时间段都有问题

为什么会这样?因为高频数据对时间连续性要求极高。如果缺失超过5%,你填充出来的数据基本就是假的,还不如直接丢弃那个时间段。

我曾经犯过的错:有一次做回测,数据缺失了大概3%,我用均值填充了。结果回测结果特别好,实盘却亏得一塌糊涂。后来才发现,缺失值都集中在市场剧烈波动的时候,均值填充完全扭曲了真实的市场行为。记住:缺失值不是随机出现的,它往往伴随着市场异常。

3.6 本章知识体系

下面这张图,是我自己总结的数据读取与初步探索的完整流程。你可以把它当成一个检查清单:

数据读取与初步探索 · 知识体系 📂 数据读取 CSV文件:parse_dates / dtype / low_memory Parquet文件:列式存储 / 压缩 / 快速读取 🔍 基本信息查看 head() / shape / columns / dtypes 内存占用检查 / 数据类型优化 📊 描述性统计 describe() / count / min / max 均值 / 标准差 / 四分位数 ⚠️ 缺失值概览 isnull().sum() / 缺失比例计算 填充策略:删除 / 插值 / 均值填充 ✅ 数据质量报告 为后续清洗与策略开发奠定基础

这张图把整个流程串起来了。从数据读取开始,到基本信息查看,再到描述性统计和缺失值分析,最后输出一份数据质量报告。每一步都是下一步的基础,缺一不可。

好了,数据已经读进来了,我们也摸清了它的底细。接下来就可以进入真正的数据清洗环节了。记住:数据清洗不是苦力活,它是你策略成功的基石。花在这个上面的时间,每一分钟都值得。

专注资料整理