上个月我在跑一个 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 ms1.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 的「量化」板块,主流声音分两派:

适合谁与不适合谁

✅ 适合用 Zarr

✅ 适合用 HDF5

❌ 谁都不适合

常见错误与解决方案

错误 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

我自己在量化团队落地了这套方案后,组内 4 个人每月省下的网络和 API 费用够再租一台 32 核机器跑并行回测。Zarr 文件存到本地 NVMe,Tardis 数据从 HolySheep 拉,LLM 因子分析也走同一通道——整个工作流不再需要三套账号、三种支付方式。

👉 免费注册 HolySheep AI,获取首月赠额度

```