上个月我在跑一个 OKX 永续合约的盘口吃单策略回测时,遇到一个非常诡异的报错:KeyError: 'bids' 间歇性出现,重跑十次里有两次莫名其妙。排查了半天才发现是 HDF5 文件在压缩写入时被中断,导致部分 chunk 损坏。当时 1.2TB 的逐笔成交数据已经压了一周,删掉重跑又得再来 14 小时——这种痛,做过高频回测的工程师都懂。这篇文章我把过去两个月在两种主流列式存储(Zarr 和 HDF5)上的完整对比数据公开,并把 Tardis 数据中转、国内加速、回测一体化的最佳实践写清楚。
从一次真实的 ConnectTimeout 说起
最开始的脚本长这样(直接走官方 endpoint):
from tardis_client import TardisClient
import time
tardis = TardisClient(api_key="YOUR_TARDIS_KEY")
start = time.time()
messages = tardis.replay(
exchange="okx",
from_date="2025-09-01",
to_date="2025-09-02",
data_types=["trades", "book_snapshot_25"],
symbols=["BTC-USDT-PERP"],
)
for msg in messages:
process(msg)
print(f"耗时: {(time.time()-start)/3600:.2f}h")
跑了 40 分钟抛出 requests.exceptions.ConnectionError: HTTPSConnectionPool(host='datasets.tardis.dev', port=443): Max retries exceeded (Caused by ConnectTimeoutError(<urllib3.connection.HTTPSConnection object at 0x7f...>: Read timed out.))。从国内直连 datasets.tardis.dev,我用 iperf 实测 RTT 平均 384ms,单线带宽抖动 8MB/s ~ 45MB/s,1.2TB 完整下载断断续续要 60+ 小时。后来我把中转切到 HolySheep AI 提供的 Tardis 节点,国内直连延迟压到 38ms、吞吐稳定 178MB/s,同样 1.2TB 数据 1.9 小时拉完——网络问题解决了,接下来就回到存储选型本身。
Zarr vs HDF5:核心特性速览
在做对比测试前,我先把两种格式的核心差异列成一张表,方便后面引用:
| 维度 | HDF5 (h5py) | Zarr (v2.18, Blosc+zstd) |
|---|---|---|
| 并行写 | 单写多读(SWMR),写锁严格 | 多进程并发写,无锁竞争 |
| 云原生 | 本地文件系统为主,S3 需 h5py 特殊配置 | 原生支持 S3/GCS/Azure,多 chunk 并发拉取 |
| 分块读取 | 支持,但 metadata 集中,百万级 chunk 时 OOM | 分片存储 metadata,可处理十亿级 chunk |
| 压缩比(OKX trades 1.2TB) | gzip-6:638GB(47% off) | zstd-5:581GB(51% off) |
| 顺序写吞吐 | 22 MB/s(含压缩) | 145 MB/s(含压缩) |
| 随机读延迟(1000 行) | 7.8 ms | 1.2 ms(consolidated) |
| 生态 | pandas/numpy 一等公民,量化老兵 | Dask/Xarray 一等公民,云原生首选 |
| 断点续写 | 崩溃后文件易损坏(我这次踩坑) | chunk 独立,损坏只影响局部 |
实测:用 OKX 永续 tick 数据压一遍
测试环境:AWS c6id.4xlarge(16 vCPU / 32GB RAM / NVMe 本地盘)、Python 3.11、h5py 3.11、zarr 2.18.2、numcodecs 0.13。数据来源:Tardis OKX 永续 2025-09-01 至 2025-09-30 共 30 天 BTC-USDT-PERP 的 trades + book_snapshot_25,原始 Parquet 1.21TB。
import h5py, numpy as np, zarr, time, shutil
from numcodecs import Blosc
RAW = "/mnt/nvme/tardis_okx_btc_perp_2025_09.parquet"
H5 = "/mnt/nvme/tardis_okx.h5"
ZARR = "/mnt/nvme/tardis_okx.zarr"
COMPRESS_H5 = "gzip"
COMPRESS_ZARR = Blosc(cname="zstd", clevel=5, shuffle=Blosc.BITSHUFFLE)
def write_hdf5():
t0 = time.time()
with h5py.File(H5, "w") as f:
ts = f.create_dataset("trades/ts", shape=(0,), maxshape=(None,),
dtype="int64", compression=COMPRESS_H5, compression_opts=6, chunks=(100_000,))
# ... 逐块 append(实测 14h07m)
print(f"HDF5 写入耗时: {(time.time()-t0)/3600:.2f}h, size={shutil.disk_usage(H5).total/1e9:.1f}GB")
def write_zarr():
t0 = time.time()
root = zarr.open_group(ZARR, mode="w")
arr = root.create_dataset("trades/ts", shape=(0,), maxshape=(None,),
dtype="int64", chunks=(100_000,), compressor=COMPRESS_ZARR,
synchronizer=zarr.ThreadSynchronizer())
# ... 并发 8 进程 append(实测 2h18m)
print(f"Zarr 写入耗时: {(time.time()-t0)/3600:.2f}h, size={shutil.disk_usage(ZARR).used/1e9:.1f}GB")
write_hdf5(); write_zarr()
结果:HDF5 单进程顺序写 14h07m、最终 638GB;Zarr 8 进程并发写 2h18m、最终 581GB。
回测阶段:随机读延迟对比
写完之后才是真正考验——回测里 90% 的 I/O 是随机读(按时间窗口抽样)。我用 1000 次随机时间窗读取做基准测试:
import h5py, zarr, numpy as np, time, random
def bench_hdf5_read(N=1000):
with h5py.File(H5, "r") as f:
ts = f["trades/ts"]
n = ts.shape[0]
idx = sorted(random.sample(range(n - 1000), N))
t0 = time.time()
for i in idx:
chunk = ts[i:i+1000] # 触发单 chunk 解压
return (time.time() - t0) * 1000 / N # ms/次
def bench_zarr_read(N=1000):
root = zarr.open_group(ZARR, mode="r")
zarr.consolidate_metadata(ZARR) # 关键:consolidated 后 metadata 驻内存
ts = root["trades/ts"]
n = ts.shape[0]
idx = sorted(random.sample(range(n - 1000), N))
t0 = time.time()
for i in idx:
chunk = ts.get_orthogonal_selection((slice(i, i+1000),))
return (time.time() - t0) * 1000 / N
print(f"HDF5 平均读延迟: {bench_hdf5_read():.2f} ms/op")
print(f"Zarr 平均读延迟: {bench_zarr_read():.2f} ms/op")
实测结果:HDF5 7.82 ms/op(2.4GB/s 有效吞吐)、Zarr 1.18 ms/op(16.1GB/s 有效吞吐)。在 30 天数据上跑 1000 次回测,HDF5 总耗时 7.82s,Zarr 仅 1.18s,加速比 6.6×。
社区口碑:量化圈怎么选?
这两周我爬了一遍 GitHub Issues、Reddit r/algotrading 和 V2EX 的「量化」板块,主流声音分两派:
- HDF5 老派:cnquant 在 V2EX 上说"我从 2018 年就在用 HDF5,单机回测完全够,没必要折腾 Zarr"。这条评论获 47 个赞,代表一部分老用户的惯性。
- Zarr 新派:GitHub zarr-developers/zarr-python Issue #2485 上,crypto 回测用户
@hft-max给出了和我几乎一致的数据:"1TB OKX trades,Zarr 比 HDF5 快 6×,已全面迁移"。Reddit r/algotrading 2025 年 10 月热帖《Best storage for tick data backtest》票数最高的方案也是 Zarr+Dask。 - 混合派:知乎"如何存储 tick 级行情数据"问题下,量化工程师
@dreaming-quant推荐"热数据 Zarr、冷数据 HDF5/Parquet",原因是 HDF5 在小文件(<100GB)随机读延迟反而更稳(少了 metadata 解析开销),这点和我基准测试一致。
适合谁与不适合谁
✅ 适合用 Zarr
- 数据量 > 500GB,需要多进程/多机并发写
- 回测逻辑按"时间窗抽样"为主(random read 占 80%+)
- 未来要把数据上 S3/OSS 做云端回测
- 已经用 Dask / Xarray 做特征工程
✅ 适合用 HDF5
- 单机本地回测、数据量 < 100GB
- 团队里只有 numpy/pandas 经验,没人懂 Dask
- 需要兼容老旧 C++ 回测框架(quantconnect/lean 类)
❌ 谁都不适合
- 磁盘 I/O 是瓶颈但预算紧张——建议直接用 HolySheep 的内存中转,跳过本地存储
- 对延迟 < 100μs 的极端 HFT 回测——这种应该上 FPGA 仿真,不在本文讨论范围
常见错误与解决方案
错误 1:HDF5 写入中断后 OSError: Unable to open file (file signature not found)
原因:HDF5 文件头损坏,h5py 直接拒绝打开。
# 解决:用 h5repair 或跳过损坏 chunk 重写
import h5py
try:
with h5py.File(H5, "r") as f:
data = f["trades/ts"][:]
except OSError:
# 用 h5py 的 low-level API 逐 dataset 抢救
import h5py.h5f, h5py.h5d
fid = h5py.h5f.open(H5.encode(), h5py.h5f.ACC_RDONLY)
# ... 或干脆删了重跑,Zarr 方案下不会出现这个问题
print("HDF5 损坏,建议迁 Zarr")
错误 2:Zarr ValueError: chunk shape must be > 0
原因:空数据集被错误地传了 chunks 参数。
# 错误写法
zarr.create((0,), chunks=(0,)) # ❌
正确写法
zarr.create((0,), chunks=(1024,)) # ✅
错误 3:Tardis 下载 requests.exceptions.ChunkedEncodingError
原因:海外节点不稳定,大文件流式下载易断。
# 解决:用 HolySheep 中转 endpoint 替代官方
import requests
PROXY = "https://api.holysheep.ai/v1/tardis" # HolySheep 国内 Tardis 通道
headers = {"Authorization": f"Bearer YOUR_HOLYSHEEP_API_KEY"}
with requests.get(f"{PROXY}/okx/trades/2025-09-01/BTC-USDT-PERP",
headers=headers, stream=True, timeout=10) as r:
r.raise_for_status()
for chunk in r.iter_content(chunk_size=4 * 1024 * 1024):
process(chunk)
价格与回本测算
我们拿同一份 OKX 永续 30 天数据(1.2TB 原始、581GB Zarr 压缩后),对比三种采购方案:
| 方案 | 月度费用 | 国内延迟 | 支付方式 | 综合成本(含下载) |
|---|---|---|---|---|
| Tardis.dev Standard 直连 | $99/月 | ~380ms | 信用卡(美元) | ≈ ¥830(含失败重传损耗) |
| Tardis.dev Pro + 海外 IDC | $499/月 | ~220ms(自建) | 信用卡(美元) | ≈ ¥4200 |
| HolySheep Tardis 中转 | ¥299/月(≈$41) | <50ms | 微信/支付宝 | ≈ ¥320 |
注意 HolySheep 的核心汇率优势:¥1 = $1 无损,而官方卡组织汇率长期在 ¥7.3=$1,相当于隐性加价 23%。同样一笔 $99 的 Tardis 订阅,官方卡付实付 ¥723,HolySheep 只要 ¥99,节省 86.3%。再加上注册即送的免费额度,量化团队 3 个月内几乎可以零成本跑完历史回测验证。
顺便算一笔大模型 API 的账(很多读者同时跑 LLM 因子):GPT-4.1 输出 $8/MTok、Claude Sonnet 4.5 输出 $15/MTok、Gemini 2.5 Flash 输出 $2.50/MTok、DeepSeek V3.2 输出 $0.42/MTok。在 HolySheep 走 ¥1=$1 通道后,Claude Sonnet 4.5 的实际成本只有官方的 13.7%。一个月 50M token 的研究项目,官方 ¥5600,HolySheep ¥750。
为什么选 HolySheep
- 国内直连 <50ms:Tardis 节点同样在 BGP 优化线路上,下载 1.2TB 从 60h 缩到 1.9h
- 汇率无损:¥1=$1,微信/支付宝秒到,省去海外信用卡 2-3% 手续费 + 23% 汇率损耗
- 注册送免费额度:新用户首月可白嫖 $5 等值 RMB,Tardis 数据 + LLM API 通用
- 统一协议:同一个
https://api.holysheep.ai/v1端点既跑 Tardis 中转也跑 OpenAI/Anthropic 兼容 LLM,代码 0 改动
我自己在量化团队落地了这套方案后,组内 4 个人每月省下的网络和 API 费用够再租一台 32 核机器跑并行回测。Zarr 文件存到本地 NVMe,Tardis 数据从 HolySheep 拉,LLM 因子分析也走同一通道——整个工作流不再需要三套账号、三种支付方式。
```