数据存储方案:从文件到数据库的进化之路
做量化投资,数据就是你的弹药库。弹药怎么存、怎么取,直接决定了你的策略能不能跑起来。
我刚开始做量化那会儿,觉得数据存哪儿都一样。后来踩了不少坑,才明白存储方案选错了,后面全是泪。今天咱们就把几种主流方案掰开揉碎了聊。
本地文件存储:最朴素的方案
说白了,就是把数据往文件里一扔。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 有个致命问题——慢。你想想看,每次读取都要把整个文件解析一遍。几万行数据还行,几百万行?等着吧。
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。唯一的缺点是不能直接用文本编辑器看,调试时不太方便。
数据库存储:结构化数据的归宿
文件存多了,管理就成了噩梦。这时候就该数据库上场了。
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 一行搞定。
数据版本管理:别让历史数据丢了
做量化最怕什么?数据改了,回测结果对不上。
我建议用 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 是首选
数据版本管理一定要做。别问我为什么知道——我曾经因为数据版本没管好,浪费了整整一周去排查回测结果不一致的问题。
数据存储没有银弹。关键是根据你的场景选对工具。文件存储适合小规模、单机使用;数据库适合结构化、多用户场景;时序数据库则是高频数据的利器。再加上版本管理,你的数据基础设施就稳了。