数据存储方案:从文件到数据库的进化之路

做量化投资,数据就是你的弹药库。弹药怎么存、怎么取,直接决定了你的策略能不能跑起来。

我刚开始做量化那会儿,觉得数据存哪儿都一样。后来踩了不少坑,才明白存储方案选错了,后面全是泪。今天咱们就把几种主流方案掰开揉碎了聊。

本地文件存储:最朴素的方案

说白了,就是把数据往文件里一扔。CSV 和 Parquet 是两种最常见的格式。

CSV:人人都能看懂

CSV 的好处是简单。任何编辑器都能打开,Excel 也能直接读。我早期做回测时,就用 CSV 存日线数据。

# 一个典型的 CSV 格式
date,open,high,low,close,volume
2024-01-01,100.5,102.3,99.8,101.2,1234567
2024-01-02,101.3,103.1,100.2,102.8,1345678

但 CSV 有个致命问题——慢。你想想看,每次读取都要把整个文件解析一遍。几万行数据还行,几百万行?等着吧。

注意:CSV 没有数据类型。你存的价格是字符串还是浮点数?全靠猜。我曾经因为 CSV 把价格读成字符串,导致回测结果差了十万八千里。

Parquet:列式存储的王者

Parquet 是列式存储格式。什么意思?就是按列存数据,而不是按行。

举个例子,你要算某只股票的平均收盘价。CSV 得把整行数据都读出来,Parquet 只读收盘价那一列。速度能差 10 倍以上。

# 用 Pandas 读写 Parquet
import pandas as pd

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

# 读取
df = pd.read_parquet('stock_data.parquet')

我个人习惯用 Parquet 存历史数据。压缩率高,读取快,还自带 schema。唯一的缺点是不能直接用文本编辑器看,调试时不太方便。

小技巧:Parquet 支持分区存储。按日期分区,查询某段时间的数据时,只读相关分区,速度更快。

数据库存储:结构化数据的归宿

文件存多了,管理就成了噩梦。这时候就该数据库上场了。

SQLite:轻量级的选择

SQLite 是个嵌入式数据库。不需要安装,一个文件就是一个数据库。适合单机使用。

import sqlite3

# 创建连接
conn = sqlite3.connect('trading.db')
cursor = conn.cursor()

# 建表
cursor.execute('''
    CREATE TABLE IF NOT EXISTS daily_data (
        date TEXT,
        symbol TEXT,
        open REAL,
        high REAL,
        low REAL,
        close REAL,
        volume INTEGER
    )
''')

# 插入数据
cursor.execute('''
    INSERT INTO daily_data VALUES (?, ?, ?, ?, ?, ?, ?)
''', ('2024-01-01', 'AAPL', 100.5, 102.3, 99.8, 101.2, 1234567))

conn.commit()

SQLite 的好处是零配置。我经常用它做本地回测的数据缓存。但并发写入时性能很差,多进程同时写可能会锁死。

MySQL:正经的生产环境方案

MySQL 是客户端-服务器架构。支持多用户并发,性能稳定。

import mysql.connector

# 连接数据库
conn = mysql.connector.connect(
    host='localhost',
    user='trader',
    password='your_password',
    database='trading'
)

cursor = conn.cursor()

# 查询某只股票最近10天的数据
cursor.execute('''
    SELECT date, close 
    FROM daily_data 
    WHERE symbol = 'AAPL' 
    ORDER BY date DESC 
    LIMIT 10
''')

for row in cursor.fetchall():
    print(row)

MySQL 适合做团队协作的数据平台。但要注意索引设计。我曾经因为没加索引,查询几千万条数据时,一次查询跑了 30 秒。

核心对比:
  • SQLite:单机、轻量、零配置
  • MySQL:多用户、高性能、需运维

时序数据库:为时间序列而生

量化数据本质上就是时间序列。每一条数据都带时间戳。传统数据库处理这种数据,效率其实不高。

InfluxDB 是专门为时序数据设计的数据库。它有几个特点:

  • 时间戳自动索引
  • 数据自动过期
  • 连续查询(Continuous Query)
  • 高压缩率
from influxdb import InfluxDBClient

# 连接 InfluxDB
client = InfluxDBClient(host='localhost', port=8086)
client.switch_database('trading')

# 写入数据
json_body = [
    {
        "measurement": "tick_data",
        "tags": {
            "symbol": "AAPL"
        },
        "time": "2024-01-01T09:30:00Z",
        "fields": {
            "price": 100.5,
            "volume": 1000
        }
    }
]

client.write_points(json_body)

# 查询
results = client.query('''
    SELECT mean(price) 
    FROM tick_data 
    WHERE symbol = 'AAPL' 
    AND time > now() - 1h 
    GROUP BY time(5m)
''')

InfluxDB 的查询语法是类 SQL 的,但专门为时间聚合做了优化。比如上面那个查询,按 5 分钟窗口算平均价,传统数据库写起来很麻烦,InfluxDB 一行搞定。

避坑指南:InfluxDB 的 tag 字段是索引的,但 tag 值不能太多。我曾经把订单号当 tag 存,结果 tag 基数太大,查询性能直线下降。后来改成 field 才解决。

数据版本管理:别让历史数据丢了

做量化最怕什么?数据改了,回测结果对不上。

我建议用 DVC(Data Version Control)来管理数据版本。它跟 Git 类似,但专门管大文件。

# 初始化 DVC
dvc init

# 添加数据文件
dvc add stock_data.parquet

# 提交版本
git add stock_data.parquet.dvc .gitignore
git commit -m "add stock data v1.0"

# 切换版本
git checkout v1.0
dvc checkout

DVC 的好处是,数据文件存在本地或云存储,Git 只存指针。这样既保留了版本历史,又不会让 Git 仓库变得臃肿。

我的习惯:每次修改数据源或清洗逻辑后,都打一个版本标签。这样回测时能精确复现当时的数据状态。

怎么选?一张表说清楚

方案 适用场景 优点 缺点
CSV 小数据量、临时分析 简单、通用 慢、无类型
Parquet 历史数据存储 快、压缩率高 不可读
SQLite 单机缓存 零配置 并发差
MySQL 团队协作 稳定、功能全 需运维
InfluxDB 高频行情 时序优化 学习成本

我个人建议:

  • 个人回测:Parquet + SQLite 组合
  • 团队协作:MySQL + Parquet 组合
  • 高频交易:InfluxDB 是首选

数据版本管理一定要做。别问我为什么知道——我曾经因为数据版本没管好,浪费了整整一周去排查回测结果不一致的问题。

数据存储方案选择决策树 数据存储方案 本地文件存储 数据库存储 时序数据库 CSV Parquet SQLite MySQL InfluxDB 简单通用 速度慢 列式存储 读取快 零配置 并发差 多用户 需运维 时序优化 高压缩 数据版本管理(DVC)

数据存储没有银弹。关键是根据你的场景选对工具。文件存储适合小规模、单机使用;数据库适合结构化、多用户场景;时序数据库则是高频数据的利器。再加上版本管理,你的数据基础设施就稳了。

专注资料整理