个人量化研究数据库选型指南:从文件到时序数据库的实战对比

📅 发布时间:2026/8/20 22:42:15
个人量化研究数据库选型指南:从文件到时序数据库的实战对比 1. 先想清楚你的数据到底要存什么、怎么用个人量化研究最怕的不是模型不灵而是数据没管好。模型可以换策略可以调但数据一旦乱了或者查询慢到跑一次回测要等半天整个研究流程就卡住了。所以选数据库不是看哪个技术最火而是先想清楚你的数据长什么样、你打算怎么用它。我见过很多新手一上来就问“用 MySQL 还是 PostgreSQL”这其实问错了。对于个人研究者你的数据场景通常很明确时间序列数据是绝对核心。你的股票K线、期货tick、因子值、信号都是带时间戳的。除此之外你还需要存一些维度数据比如股票代码列表、行业分类、财务指标快照。最后你的研究中间结果比如每次回测的净值曲线、绩效指标也需要有个地方放。所以选择的核心矛盾是时间序列的写入和查询效率与维度数据的关联查询便利性之间的权衡。一个只存时间序列的专用数据库查询速度可能飞快但你想把行情数据和财务数据关联起来做分析可能就得自己写一堆代码拼接很麻烦。一个通用的关系型数据库关联查询很方便但面对每天几百万条的tick数据性能可能成为瓶颈。我的建议是先别急着定方案花十分钟把你的数据需求列清楚数据量级是日频、分钟级还是tick级未来一年大概有多少条记录查询模式是经常按股票代码查一段时间的历史行情还是需要做复杂的多表关联比如找出市盈率低于行业平均且最近有放量上涨的股票分析工具你主要用 Python 的 Pandas 做分析还是用 SQL 直接跑你的回测框架对数据接口有什么偏好硬件环境数据是放在你自己的电脑上还是云服务器你的机器内存、磁盘特别是SSD有多大想清楚这些我们再来看看有哪些选项以及它们分别适合什么样的“个人研究者”。2. 从最简单到最专业四种主流方案的实战对比市面上方案很多但个人研究者没必要全都折腾一遍。我根据复杂度和适用场景把它们归为四类。你可以直接对号入座。2.1 方案一文件 Pandas (HDF5/Parquet/Feather)适合谁刚刚入门数据量不大比如只研究A股日频数据几年下来也就几十万条追求极简启动不想维护任何数据库服务的研究者。核心能力这不是数据库而是序列化文件格式。你的整个数据库就是一个或几个文件。用 Pandas 读写在内存里操作思路最直接。HDF5(.h5): 通过pandas.HDFStore使用。可以存储带类型的 DataFrame支持分块读取查询速度不错。但文件内部结构复杂一旦损坏可能难以修复且跨语言支持一般。Parquet(.parquet): Apache 生态的列式存储格式。最大优点是压缩比高节省磁盘空间而且被 Spark、DuckDB 等很多新工具原生支持。用pandas.read_parquet读取很方便。Feather(.feather): 设计目标就是快速读写在 Pandas 和 R 之间交换数据。它几乎就是内存数据的直接镜像所以读写速度极快但压缩率不如 Parquet。怎么用import pandas as pd import numpy as np # 假设你有一个日频行情 DataFrame: df_daily # 保存 df_daily.to_parquet(stock_daily.parquet) # 节省空间 # 或 df_daily.to_feather(stock_daily.feather) # 追求读写速度 # 读取可以只读部分列 df pd.read_parquet(stock_daily.parquet, columns[code, date, close]) # 按日期和代码筛选 (在内存中) filtered df[(df[date] 2023-01-01) (df[code] 000001.SZ)]避坑点全部数据加载到内存这是最大限制。如果你的数据量超过内存就会卡死。Parquet 虽支持分块读取但用 Pandas 处理大文件依然不便。并发访问差文件一般不支持多进程同时写入回测时如果想并行计算并写结果需要小心处理锁或拆分文件。复杂查询靠手动所有关联、聚合、筛选逻辑都需要你用 Pandas 代码写出来不如 SQL 直观。结论入门首选快速验证想法。当你的数据量在几个GB以内且分析模式固定时用 Parquet 或 Feather 能让你最快跑起来。一旦数据增长或查询变复杂就要考虑升级。2.2 方案二SQLite适合谁需要关系型数据库的便利性比如用 SQL 做复杂关联查询但又不想安装和配置 MySQL/PostgreSQL 这类独立服务的研究者。数据量在几十GB级别以下通常都能胜任。核心能力SQLite 是一个库不是一个服务器。你的整个数据库就是一个.db文件。它支持标准的 SQL具备事务、索引等核心功能但无需管理服务进程零配置。怎么用import sqlite3 import pandas as pd # 连接数据库文件不存在会自动创建 conn sqlite3.connect(quant_research.db) # 将Pandas DataFrame写入表 df_daily.to_sql(stock_daily, conn, if_existsreplace, indexFalse) # 用SQL直接查询 sql SELECT a.date, a.close, b.industry FROM stock_daily a JOIN stock_info b ON a.code b.code WHERE a.date BETWEEN 2023-01-01 AND 2023-03-31 AND b.industry 银行 df_result pd.read_sql_query(sql, conn) conn.close()性能关键一定要建索引对于时间序列查询在(code, date)上建立复合索引速度提升是数量级的。CREATE INDEX idx_daily_code_date ON stock_daily (code, date);避坑点写入并发弱SQLite 在某一时刻只允许一个写入操作。如果你的回测是并行任务同时写入结果可能会遇到“database is locked”错误。解决方案是让每个子进程写入独立的临时文件最后合并。内存模式你可以用:memory:创建纯内存数据库速度极快适合中间计算但程序关闭数据就消失记得持久化。数据类型宽松SQLite 数据类型比较灵活有时可能导致类型推断错误在创建表时最好显式定义字段类型。结论个人研究的“瑞士军刀”。在数据量未达到TB级且你需要频繁使用SQL做关联分析时SQLite 是平衡便利与能力的绝佳选择。它的.db文件也方便备份和迁移。2.3 方案三DuckDB适合谁处理的数据量超过了 Pandas 内存限制比如几十GB需要进行复杂的交互式分析或连接多个大型数据集但又觉得部署传统数仓如ClickHouse太重的个人研究者。核心能力这是一个进程内分析型数据库。它像 SQLite 一样以库的形式嵌入你的应用但引擎是为分析型查询OLAP设计的擅长处理海量数据的聚合、连接操作。它可以直接读写 Parquet/CSV 文件无需先“导入”数据。怎么用import duckdb # 连接内存或文件 conn duckdb.connect(quant.duckdb) # 或 :memory: # 1. 直接查询Parquet文件无需导入 query SELECT code, date, volume FROM stock_daily.parquet WHERE date 2023-01-01 AND volume 10000000 df conn.execute(query).df() # 直接返回DataFrame # 2. 也可以创建表并持久化 conn.execute(CREATE TABLE daily AS SELECT * FROM stock_daily.parquet) # 3. 执行复杂关联查询即使数据在多个Parquet文件里 complex_sql SELECT d.code, d.date, d.close, f.pe_ratio FROM daily/*.parquet d JOIN financials.parquet f ON d.code f.code AND d.date f.report_date WHERE f.pe_ratio 15 result conn.execute(complex_sql).df()性能关键DuckDB 会自动并行化查询以利用多核CPU。对于超大数据集确保你的机器有足够内存或者使用它的溢出到磁盘的功能。避坑点不是事务型数据库虽然支持事务但它的强项是分析不适合高频率、小事务的写入场景比如实时交易记录。更适合存储清洗后的历史数据和回测结果。社区和工具生态相比 MySQL/PostgreSQL其管理工具和客户端支持较少但作为嵌入式库这通常不是问题。仍在快速发展虽然核心很稳定但一些高级功能可能还在演进中。结论个人量化分析的“性能加速器”。当你受限于 Pandas 内存又厌倦了 SQLite 处理大数据连接时的缓慢DuckDB 几乎是无缝升级的最佳选择。特别是它能直接查询 Parquet 文件让数据管理流程变得极其简洁。2.4 方案四时序数据库 (InfluxDB, TimescaleDB)适合谁数据源是超高频率的时序数据如每秒数千条的tick数据、分钟级传感器数据并且查询模式几乎全是基于时间范围的聚合和筛选的专业个人研究者或小型团队。核心能力为时间序列数据优化。写入速度极快压缩效率高专门针对“按时间范围查询某指标”这类操作做了索引和存储优化。InfluxDB专门的时序数据库数据模型围绕“指标(measurement)、标签(tags)、字段(fields)、时间戳”设计。它的查询语言是 Flux 或 InfluxQL和 SQL 思路不同需要学习。TimescaleDB基于 PostgreSQL 的插件。这意味着你可以在享受 PostgreSQL 全部功能复杂的SQL、事务、GIS等的同时获得针对时序数据的超表hypertable和自动分区管理。这对需要关联时序数据和其他关系数据的场景特别友好。怎么用 (以 TimescaleDB 为例) 首先你需要一个运行的 PostgreSQL并安装 TimescaleDB 扩展。-- 创建超表自动按时间分区 CREATE TABLE stock_ticks ( time TIMESTAMPTZ NOT NULL, code TEXT NOT NULL, price DECIMAL, volume BIGINT ); SELECT create_hypertable(stock_ticks, time); -- 插入数据和PostgreSQL完全一样 INSERT INTO stock_ticks VALUES (NOW(), 000001.SZ, 14.25, 10000); -- 查询最近10分钟某只股票的平均价格底层会自动扫描相关分区效率高 SELECT code, AVG(price) FROM stock_ticks WHERE time NOW() - INTERVAL 10 minutes AND code 000001.SZ GROUP BY code;避坑点复杂度高需要安装和维护一个数据库服务比前面三种方案都重。适用场景专一如果你的数据不是典型的高频时序数据或者你需要大量非时序的复杂关联查询那时序数据库的优势可能不明显反而引入了不必要的复杂度。学习成本尤其是 InfluxDB需要学习其特定的数据模型和查询语言。结论高频数据专家的选择。对于绝大多数个人研究者日频、分钟级数据用前三种方案足以应对。只有当你真的被 tick 数据淹没并且查询都是时间窗口聚合时才值得引入专门的时序数据库。TimescaleDB 因为兼容 SQL是更平滑的入门选择。3. 从选择到落地我的配置与操作清单光知道方案不够还得知道怎么把它用起来。下面是我根据常见场景整理的配置和操作顺序。3.1 环境准备与依赖安装无论选哪个Python 环境是基础。建议使用conda或venv创建独立环境。通用基础# 创建环境 conda create -n quant_db python3.10 conda activate quant_db # 核心数据分析库 pip install pandas numpy按方案安装文件方案pip install pyarrow fastparquet(用于 Parquet) 或pip install pyarrow feather-format(用于 Feather)。SQLitePython 标准库自带sqlite3无需额外安装。DuckDBpip install duckdbTimescaleDB需要先安装 PostgreSQL 服务器并加载 TimescaleDB 扩展。本地开发可以用 Docker 快速启动docker run -d --name timescaledb -p 5432:5432 -e POSTGRES_PASSWORDpassword timescale/timescaledb:latest-pg16然后安装 Python 驱动pip install psycopg2-binary或asyncpg。3.2 数据入库标准流程以 SQLite/DuckDB 为例不要一次性把所有数据塞进去。遵循这个流程可以避免后期混乱。设计表结构时间序列表至少包含timestamp/date(主键或索引的一部分)、symbol/code、以及各种价格/因子字段。明确时间字段的精度和时区。维度表如股票信息表 (code,name,industry,list_date)。code设为主键。结果表回测结果表 (strategy_id,run_date,nav,sharpe,max_drawdown)。编写数据清洗与入库脚本import pandas as pd import sqlite3 from pathlib import Path def init_database(db_pathquant.db): conn sqlite3.connect(db_path) # 创建日频行情表 conn.execute( CREATE TABLE IF NOT EXISTS daily_bar ( code TEXT, date DATE, open REAL, high REAL, low REAL, close REAL, volume INTEGER, PRIMARY KEY (code, date) ) ) # 创建股票信息表 conn.execute( CREATE TABLE IF NOT EXISTS stock_info ( code TEXT PRIMARY KEY, name TEXT, industry TEXT ) ) conn.commit() return conn def update_daily_data(conn, csv_file_path): 从CSV文件更新日线数据 df pd.read_csv(csv_file_path) # 确保列名和数据类型匹配 df[date] pd.to_datetime(df[date]).dt.date # 使用 pandas 的 to_sql 方法if_existsappend df.to_sql(daily_bar, conn, if_existsappend, indexFalse) print(fUpdated {len(df)} records.) if __name__ __main__: conn init_database() # 假设你的数据文件在 data/ 目录下 for csv_file in Path(./data).glob(*.csv): update_daily_data(conn, csv_file) # 创建索引应在数据插入后创建效率更高 conn.execute(CREATE INDEX IF NOT EXISTS idx_daily_code_date ON daily_bar (code, date)) conn.commit() conn.close()建立索引这是影响查询速度最关键的一步。对于时间序列表必须在(code, date)上建立复合索引。如果经常按日期范围查全市场可以单独为date建索引。3.3 查询模式与性能优化不同的研究问题对应不同的查询写法。场景A获取单只股票历史行情-- 高效因为命中 (code, date) 索引 SELECT * FROM daily_bar WHERE code 000001.SZ ORDER BY date;场景B获取特定日期全市场数据-- 为 date 单独建索引会更快 SELECT * FROM daily_bar WHERE date 2023-12-01;场景C关联查询股票行情行业信息SELECT d.*, s.industry FROM daily_bar d JOIN stock_info s ON d.code s.code WHERE d.date BETWEEN 2023-01-01 AND 2023-03-31 AND s.industry 电子确保stock_info.code有主键索引。场景D复杂聚合计算计算行业日度平均收益率SELECT s.industry, d.date, AVG((d.close - d.open) / d.open) AS avg_daily_return FROM daily_bar d JOIN stock_info s ON d.code s.code WHERE d.date 2023-01-01 GROUP BY s.industry, d.date ORDER BY s.industry, d.date;对于 DuckDB这种涉及大表关联和聚合的查询优势明显。性能排查如果查询变慢第一反应不是换数据库而是检查索引用EXPLAIN QUERY PLAN(SQLite) 或EXPLAIN(DuckDB/PostgreSQL) 查看查询计划确认是否利用了索引。检查数据量是否已经增长到超出预期考虑按年份分表或分区。检查查询语句是否无意中导致了全表扫描比如对索引列使用了函数WHERE YEAR(date)2023。4. 长期维护与升级路径研究不是一锤子买卖数据库方案也需要能跟着你的需求成长。4.1 日常维护清单定期备份尤其是 SQLite 的.db文件或 DuckDB 的数据库文件直接复制即可。对于文件方案整个数据目录也要备份。可以考虑用脚本自动备份到网盘或其他硬盘。日志记录在数据更新脚本中加入日志记录每次更新的时间、数据源、行数便于出错时追溯。版本控制你的数据库 Schema 定义脚本CREATE TABLE语句、数据清洗脚本都应该用 Git 管理起来。存储监控留意磁盘空间。特别是高频数据增长很快。设置警报或定期清理过期数据如只保留最近3年的tick数据。4.2 何时需要考虑升级用着用着觉得难受了可能就是升级的信号查询慢到无法忍受在正确使用索引后简单查询仍需要数秒且数据量仍在快速增长。内存不足使用文件或 SQLite 时Pandas 经常因内存不足崩溃。需要更复杂的分析需要频繁进行窗口函数、递归查询等高级 SQL 操作而当前数据库支持不好。并发需求需要多个回测任务同时写入结果当前方案锁冲突严重。平滑升级路径从 文件 升级到 SQLite/DuckDB这是最自然的路径。写一个迁移脚本将 Parquet 文件读入然后用.to_sql()或 DuckDB 的CREATE TABLE ... AS SELECT * FROM file.parquet导入。从 SQLite 升级到 DuckDBDuckDB 可以直接连接并查询 SQLite 数据库文件迁移成本极低。从 SQLite/DuckDB 升级到 TimescaleDB/PostgreSQL这一步稍重需要使用pgloader或自定义 ETL 脚本进行数据迁移。但换来的是更强大的功能和更好的并发支持。4.3 最后的建议从简单开始逐步演进不要一开始就追求最“专业”最“强大”的方案。过度设计是个人项目最大的杀手。我的建议始终是第零步用 CSV 或 Parquet 文件把数据整理好用 Pandas 跑通你的第一个策略回测。这是最快的验证。第一步当数据多了查询复杂了马上切换到SQLite。享受 SQL 的便利它能支撑你很长一段时间。第二步当 SQLite 在处理大数据关联查询时开始力不从心无缝切换到DuckDB。几乎不用改代码性能立竿见影。第三步只有当你的研究确实深入到高频领域或者需要构建一个多用户、高并发的回测服务时才去考虑TimescaleDB或更专业的方案。记住工具是为你服务的。最合适的方案是那个能让你最少分心在数据管理上最多精力集中在策略研究上的方案。从今天起选一个最简单的先把数据规整地存起来让研究流程跑起来这才是最重要的一步。