3、逐笔数据存储方案:内存存储、磁盘存储、数据库存储

做量化交易的朋友都知道,逐笔成交数据有多「吃」存储。我刚开始接触这个领域时,以为几天的数据顶多几百兆。结果呢?一天的高频数据就能撑爆我的笔记本内存。嗯,存储方案选不对,后面全白费。

今天咱们就聊聊,逐笔数据到底该怎么存。我把它分成三大类:内存存储磁盘存储数据库存储。每种方案都有它的脾气,咱们一个一个拆解。

3.1 内存存储:Pandas DataFrame

说白了,就是把数据直接塞进内存里。Pandas DataFrame 是咱们最熟悉的工具,没有之一。

适用场景:回测、策略验证、小批量数据分析。

我个人习惯在回测时用 DataFrame。原因很简单——快。读取一次,后面所有操作都在内存里跑,速度没得说。

import pandas as pd

# 读取逐笔成交数据
df = pd.read_csv('tick_data.csv')
print(f'数据量: {len(df)} 行')
print(f'内存占用: {df.memory_usage(deep=True).sum() / 1024**2:.2f} MB')

但这里有个坑。我曾经在项目中一次性加载了 3 天的逐笔数据,结果内存直接飙到 8GB。你想想看,如果做全市场回测,内存根本扛不住。

避坑指南:我曾经因为内存溢出导致回测跑了 6 小时白跑。后来学乖了,用 dtype 参数压缩数据类型,内存能省 40% 以上。

# 压缩数据类型,节省内存
df = pd.read_csv('tick_data.csv',
                 dtype={
                     'price': 'float32',
                     'volume': 'int32',
                     'timestamp': 'int64'
                 })

3.2 磁盘存储:HDF5 与 Parquet

内存不够用怎么办?那就存磁盘。但别用 CSV,那玩意儿又慢又占空间。我推荐两个格式:HDF5Parquet

3.2.1 HDF5 格式

HDF5 是科学计算领域的老牌格式。它的特点是:支持分层存储、支持压缩、读取速度快。

import h5py

# 写入 HDF5
with h5py.File('tick_data.h5', 'w') as f:
    f.create_dataset('tick_data', data=df.values)
    f.create_dataset('columns', data=df.columns.values.astype('S'))

# 读取 HDF5
with h5py.File('tick_data.h5', 'r') as f:
    data = f['tick_data'][:]
    cols = [c.decode() for c in f['columns'][:]]
    df_h5 = pd.DataFrame(data, columns=cols)

我记得有一次做全市场逐笔数据回放,HDF5 的读取速度比 CSV 快了将近 10 倍。而且压缩后文件大小只有原来的 1/3。

3.2.2 Parquet 格式

Parquet 是大数据生态的宠儿。它的列式存储结构,特别适合逐笔数据这种「按列查询」的场景。

import pyarrow.parquet as pq

# 写入 Parquet
df.to_parquet('tick_data.parquet', compression='snappy')

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

我的建议:如果你做的是单机分析,HDF5 够用。如果未来要对接大数据平台,直接上 Parquet。别问我怎么知道的——我踩过换格式的坑。

3.3 数据库存储:InfluxDB 与 ClickHouse

当数据量达到 TB 级别,磁盘文件也扛不住了。这时候需要专业的时序数据库。

3.3.1 InfluxDB

InfluxDB 是专为时序数据设计的。它的查询语法简单,写入速度快,适合实时行情存储。

from influxdb import InfluxDBClient

client = InfluxDBClient(host='localhost', port=8086)
client.switch_database('tick_db')

# 写入数据
json_body = [
    {
        "measurement": "tick_data",
        "tags": {"symbol": "000001"},
        "time": "2024-01-01T09:30:00Z",
        "fields": {
            "price": 10.25,
            "volume": 1000
        }
    }
]
client.write_points(json_body)

但 InfluxDB 有个缺点:不支持复杂的 JOIN 操作。如果你需要关联多张表,它就不太合适了。

3.3.2 ClickHouse

ClickHouse 是我目前的主力存储方案。它的列式存储、向量化执行引擎,让逐笔数据的聚合查询快到飞起。

-- 创建表
CREATE TABLE tick_data (
    timestamp DateTime,
    symbol String,
    price Float64,
    volume UInt64
) ENGINE = MergeTree()
ORDER BY (symbol, timestamp);

-- 查询某只股票每分钟的平均价格
SELECT 
    toStartOfMinute(timestamp) AS minute,
    avg(price) AS avg_price
FROM tick_data
WHERE symbol = '000001'
GROUP BY minute
ORDER BY minute;

性能对比:我做过测试,在 10 亿行逐笔数据上,ClickHouse 的聚合查询比 MySQL 快了 100 倍以上。这不是夸张,是实测数据。

3.4 存储性能对比与选型建议

咱们直接上对比表,一目了然:

存储方案 读取速度 写入速度 压缩比 并发支持 适用场景
Pandas DataFrame 极快 单线程 回测、小批量分析
HDF5 中等 单线程 单机大数据分析
Parquet 中等 多线程 大数据生态、Spark
InfluxDB 中等 极快 中等 高并发 实时行情、监控
ClickHouse 极快 高并发 大规模时序分析

选型建议其实很简单:

  • 个人研究、回测:Pandas DataFrame + HDF5 就够用
  • 团队协作、中等规模:Parquet + ClickHouse 是黄金组合
  • 实时行情、高频交易:InfluxDB 写入快,适合做缓存层

注意:别一上来就上 ClickHouse。我见过有人为了存 100 万行数据搭了个 ClickHouse 集群,完全是杀鸡用牛刀。先评估数据量,再选方案。

3.5 知识体系总览

下面这张图,是我梳理的逐笔数据存储方案选型逻辑。你跟着这个思路走,基本不会出错。

逐笔数据存储方案选型逻辑 逐笔成交数据 数据量评估:< 100GB / 100GB-1TB / > 1TB 小规模 (< 100GB) 中等规模 (100GB-1TB) 大规模 (> 1TB) 推荐方案 Pandas + HDF5 单机分析,快速回测 推荐方案 Parquet + ClickHouse 团队协作,高效查询 推荐方案 ClickHouse 集群 分布式存储,高并发 注意事项 注意内存溢出 压缩数据类型 注意事项 评估数据增长 预留扩展空间 注意事项 运维成本较高 需要专业DBA

这张图的核心逻辑就一句话:先看数据量,再选存储方案。别盲目追求高大上的技术栈,适合你的才是最好的。

好了,关于逐笔数据的存储方案,咱们就聊到这儿。下一章我会讲讲数据清洗的那些坑,保证让你少走弯路。


公众号:蓝海数据掘金营,微信deep3321