3. 数据存储选型:CSV、Parquet、HDF5、ClickHouse在高频场景下的优劣对比
做高频做市,数据存储是个绕不开的坎儿。
我见过不少团队,策略写得挺漂亮,结果倒在了数据读写上。你想想看,每秒几千笔 tick 数据,一天下来就是几千万行。如果存储选错了,回测跑一次要等半天,实盘更是想都别想。
今天咱们就来掰扯掰扯,CSV、Parquet、HDF5、ClickHouse 这四种方案,在高频场景下到底谁行谁不行。我会结合我踩过的坑,给你讲清楚。
核心结论先放这:高频场景下,CSV 只适合临时调试,Parquet 是离线分析的首选,HDF5 适合中等规模的结构化数据,ClickHouse 则是实盘级高频查询的王者。
3.1 CSV:最熟悉的陌生人
CSV 太常见了,几乎每个做量化的都用过。但说实话,在高频场景下,它就是个坑。
优点:
- 人类可读,用记事本就能打开
- 生态兼容性最好,Pandas、Excel 都能直接读
- 不需要额外安装任何数据库
缺点(高频场景下致命):
- 没有 schema 约束,字段类型全靠猜。我遇到过 CSV 里时间戳被读成字符串,回测结果全错,排查了一下午
- 压缩率极差。同样一份 tick 数据,CSV 占 10GB,Parquet 可能只要 1GB
- 读取速度慢。CSV 是纯文本解析,每行都要做字符串分割和类型转换。1000 万行数据,CSV 读一遍要 30 秒,Parquet 只要 3 秒
- 不支持列式裁剪。你只想读 price 这一列,CSV 也得把整行全读进来
避坑指南:我曾经把 3 个月的期货 tick 数据全存成 CSV,结果回测时光读数据就花了 40 分钟。后来换成 Parquet,同样的数据 4 分钟读完。嗯,从那以后我再也没用 CSV 存过正式数据。
3.2 Parquet:离线分析的利器
Parquet 是我个人最推荐的离线存储格式。它本质上是列式存储,专为分析型负载设计。
为什么 Parquet 适合高频数据?
- 列式存储:你只需要读需要的列,IO 量大幅减少
- 内置压缩:支持 Snappy、Zstd 等算法,压缩比通常在 5:1 到 10:1
- 谓词下推:可以按时间范围过滤,只读取符合条件的行组
- 与 Spark、Dask、Polars 等大数据框架无缝集成
举个例子,假设你有 1 亿条 tick 数据,字段包括 timestamp、symbol、bid、ask、last_price、volume。如果你只想分析 last_price,Parquet 只需要读取这一列的数据块,而 CSV 必须把全部 6 列都读进来。
我的经验:做特征工程时,我习惯把原始 tick 数据存成 Parquet,然后按日期分区。这样回测时只需要加载特定日期的数据,速度飞快。而且 Parquet 的 schema 是自描述的,换个人来接手也能看懂字段类型。
但 Parquet 也有短板:
- 不适合频繁更新。Parquet 文件是不可变的,你要修改数据只能重写整个文件
- 小文件太多时性能下降。每个 Parquet 文件都有元数据开销,如果每个文件只有几 MB,反而比 CSV 还慢
- 实时写入不友好。Parquet 是批处理格式,不适合逐条写入
3.3 HDF5:科学计算的老将
HDF5 在科学计算领域用得很多,量化圈子里也有一批忠实用户。它本质上是一个分层数据容器,支持多维数组存储。
HDF5 的优势:
- 支持多维数据。比如你可以把 (日期, 合约, 时间) 组织成一个三维数组
- 支持 chunked storage,可以按块压缩和读取
- 支持并行 IO,多进程可以同时读写同一个文件的不同部分
- Python 生态支持好,h5py 和 PyTables 都很成熟
但高频场景下要注意:
- 单文件模式容易损坏。HDF5 把所有数据塞进一个文件,一旦文件损坏,数据全丢。我有个朋友就遇到过,回测跑了三天,结果 HDF5 文件打不开了……
- 并发写入有坑。虽然支持并行 IO,但多进程同时写入同一个 HDF5 文件时,锁机制会导致性能急剧下降
- 不适合超大规模数据。当数据量超过几十 GB 时,HDF5 的元数据管理会变慢
我的建议:如果你做的是中低频策略,数据量在 10GB 以内,HDF5 是个不错的选择。但高频做市的数据量通常以 TB 计,这时候 HDF5 就有点力不从心了。
3.4 ClickHouse:实盘高频的王者
ClickHouse 是俄罗斯 Yandex 开源的列式数据库,专为 OLAP 场景设计。我接触它是在 2020 年,当时被它的查询速度震惊了——10 亿行数据,聚合查询只要几百毫秒。
ClickHouse 的核心优势:
- 真正的列式存储,按列压缩,压缩比极高
- 向量化执行引擎,充分利用 CPU 的 SIMD 指令集
- 支持实时写入,每秒可以写入几十万行
- 分布式架构,可以横向扩展
- SQL 接口,学习成本低
高频场景下的典型用法:
- 用 MergeTree 引擎按时间分区,每天一个分区
- 设置 ORDER BY (symbol, timestamp),加速按合约和时间的查询
- 使用物化视图预计算常用指标,比如每分钟的 VWAP、波动率
-- 创建高频 tick 表
CREATE TABLE ticks (
symbol String,
timestamp DateTime64(3),
bid Float64,
ask Float64,
last_price Float64,
volume UInt64
) ENGINE = MergeTree()
ORDER BY (symbol, timestamp)
PARTITION BY toYYYYMMDD(timestamp)
TTL timestamp + INTERVAL 90 DAY;
-- 查询某合约最近 1 分钟的 tick 数
SELECT count() FROM ticks
WHERE symbol = 'rb2401'
AND timestamp >= now() - INTERVAL 1 MINUTE;
避坑指南:ClickHouse 不适合频繁的更新和删除操作。它的 UPDATE 和 DELETE 是异步的,性能很差。如果你需要频繁修改数据,建议用 Kafka + ClickHouse 的流式写入方案,只追加不修改。
ClickHouse 的缺点:
- 需要部署和维护,不像 CSV 那样开箱即用
- 内存消耗大,复杂查询容易 OOM
- 单机性能有限,数据量超过 10TB 建议上集群
- 不适合小数据量场景,几百万行数据用 ClickHouse 有点杀鸡用牛刀
3.5 四种方案对比总表
| 维度 | CSV | Parquet | HDF5 | ClickHouse |
|---|---|---|---|---|
| 读取速度(1亿行) | 慢(~300秒) | 快(~30秒) | 中等(~60秒) | 极快(~5秒) |
| 压缩比 | 差(1:1) | 优秀(5:1~10:1) | 良好(3:1~5:1) | 优秀(5:1~8:1) |
| 实时写入 | 支持(但慢) | 不支持 | 有限支持 | 支持(高吞吐) |
| 列式裁剪 | 不支持 | 支持 | 支持 | 支持 |
| 并发读写 | 差 | 良好 | 中等 | 优秀 |
| 部署复杂度 | 无 | 无 | 低 | 高 |
| 适用场景 | 临时调试 | 离线分析 | 中等规模 | 实盘高频 |
3.6 我的选型建议
说了这么多,到底怎么选?我给出一个实际可操作的方案:
- 原始数据存储:用 ClickHouse。实盘产生的 tick 数据直接写入 ClickHouse,按天分区,保留 90 天。查询快,写入也快。
- 离线特征工程:用 Parquet。从 ClickHouse 导出数据到 Parquet 文件,按日期和合约分区。然后用 Polars 或 Dask 做特征计算,速度快,内存友好。
- 临时分析:用 CSV。调试代码、快速查看数据分布时,CSV 最方便。但用完就删,别留着占地方。
- HDF5:我个人现在用得少了。除非是处理多维数组数据(比如期权隐含波动率曲面),否则不太推荐。
一句话总结:高频做市的数据链路,我建议走「ClickHouse 存原始数据 → Parquet 做特征工程 → 内存 DataFrame 跑策略」这条路线。CSV 只做临时查看,HDF5 留给特定场景。
数据存储选型这事,没有银弹。关键是根据你的数据量、查询模式、实时性要求来权衡。你想想看,如果每天只有几万条数据,CSV 完全够用。但如果你跟我一样,每天处理几亿条 tick,那 ClickHouse + Parquet 的组合会让你省心很多。
最后说一句,别在存储选型上纠结太久。先用 Parquet 跑起来,等数据量上来了再迁移到 ClickHouse。我见过太多人一开始就想搞个大而全的架构,结果项目还没跑起来就夭折了。先跑通,再优化,这才是做高频的务实之道。