news 2026/9/2 17:25:21

【量化系统从0到1】存储架构:不选择什么,比选择什么更重要

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【量化系统从0到1】存储架构:不选择什么,比选择什么更重要

这套系统是个人量化研究系统:单用户,日线级别,盘后批处理——每天收盘后拉数据、算因子、跑策略、出报告,只产出信号和分析报告,不做实盘下单。部署在一台 2 核、1GiB 内存的云主机上。

存储要装的东西按形态分是五类:行情(日线 bar)、因子(bar 的派生特征)、财务(三大报表和指标)、任务与配置(运行状态、股票池、分组)、元数据与评估结果(数据版本、IC 结果)。选型最忌讳先定方案再补理由——结论一旦立住,理由会自动补齐,哪怕在场景里根本不成立。正确顺序是先刻画每类数据的本质特征,特征指向什么,就用什么。

行情:追加、不可变、按列扫描

行情是最大的一类数据,特征最鲜明:

  • 追加式:每天盘后新增一天,历史不删除
  • 不可变:已产生的数据不再修改(复权因子偶尔被数据源修正,属低频覆盖)
  • 按列扫描:回测与因子计算只消费 open、close、adj_factor 等少数几列,其余列不参与计算
  • 规模:全市场 5695 只股票,1992 年至今约 8300 个交易日,千万行级

前三个特征指向列式、压缩、以文件为单位的不可变存储。parquet(列式存储格式)恰好是这种形态:列存让"只读几列"只加载那几列,行存要整行读入;文件不可变让"追加一天"变成"旧文件 + 新段合并",与读操作天然隔离。

第四个特征(千万行级)决定了一件事:数据不能整表进内存。机器只有 1GiB 内存,消费端全部按批流式处理,扫描时只读需要的列——这同样由列式结构支撑。存储容量从来不是问题(压缩后单文件数百 MB 级),内存才是约束。

因子:bar 的派生视图

因子是 bar 经计算得到的特征序列(动量、波动率、量价关系等),18 个因子各落一个文件。特征是可重建:bar 不变时重跑计算函数得到相同结果;缓存带数据版本,bar 更新后旧因子自动失效。

可重建意味着不需要数据库级别的管理:没有事务、没有并发写,只有"算出来、写文件、按数据版本引用"。所以它和行情一样落 parquet,每个因子一个文件,按列读取。评估结果(IC、分层多空)落 JSON,供界面直接读。

财务:主键查询 + 缓存

财务和行情是两个极端:

  • 数据源要求逐股拉取:Tushare 财务接口必填股票代码,没有全市场批量接口
  • 查询形态是主键点查:给定股票和报告期,取最近 N 期报表
  • 需要缓存和增量:首次查询拉最近 8 期,之后直查缓存;全市场刷新按"已缓存期数是否足够"跳过

主键查询 + 存在则更新(upsert)+ 断点续拉,这是典型的行存事务场景。SQLite 是最低成本的实现:Python 标准库自带,零依赖、零独立进程、单文件,目前缓存 38MB。

任务与配置:事务型小数据

任务状态(进行中/成功/失败)、股票池、分组、设置、告警——频繁小写、状态需要事务一致性。调度器、Web 服务、CLI 三个入口同时操作任务表,同一时刻只允许一个回测在跑(防双跑),依赖数据库的锁和事务。

它们与财务落在同一个 SQLite 文件里(不同表)。设计稿曾设想给 SQLite 开 WAL 模式应对读写并发,落地时发现不需要:单用户、写入量极小、事务短,默认模式即可。

元数据与评估结果:一次写入,人读

meta.json 记录数据版本、拉取时间、股票池、行数,报告头引用它作为可复现锚点;ic.json、factor_eval.json 存因子评估结果,界面直接读。体量 KB 级、一次写入、主要给人读。JSON 不需要 schema、不需要查询引擎,编辑器打开就能看。

反向排除:特征不匹配的候选

正向选型回答"特征指向什么",反向排除回答"特征否定什么"。DuckDB 和 PostgreSQL 都进入过候选,最终未引入:

  • DuckDB解决即席 SQL 查询——用 SQL 直接查 parquet,多查询并发毫秒级。我的查询全部是确定性 pipeline(拉数→因子→策略→回测→报告),没有"临时写一句 SQL 探索数据"的需求;数据已是千万行级,但读数据按列流式、按批处理,存储不是瓶颈——拉数的瓶颈在数据源限流(网络),不在存储。查询慢的痛点没有出现。
  • PostgreSQL / MySQL解决多用户并发、在线事务、网络访问。我的场景单用户、批处理写、本地文件即可。引入它们意味着常驻服务进程、账号权限、备份恢复、连接池——在一台 2 核机器上全是纯开销。SQLite 的文件级单写覆盖全部真实需求。

设计稿曾给存储设过"千万行"阈值,实际全历史数据已经在千万行级——阈值本身不是判决依据,真正触发优化的条件是痛点:查询变慢、或单文件读写成为瓶颈。届时再谈 DuckDB 与分区;界面要对多用户公开,再谈 PostgreSQL。触发条件存在的意义,是未来做决定时对照,而不是重新争论。

落地:让选型不翻车的机制

选型只是第一步,更新与并发才容易出问题:

  • 增量更新:对比已有数据只拉缺失的交易日,避免每天全量重拉
  • 分片 + 断点续拉:按 60 个交易日切分片,每片独立拉取、原子落盘(先写临时文件再改名),失败重跑只补缺失分片
  • 跨进程互斥:拉数用文件锁(flock)保证任意时刻全局只有一个拉取任务,调度器、Web、CLI 并发也不超数据源限流
  • 备份:每次拉数后按日期存一份,保留最近 14 份,数据损坏可回滚

小结

选型两步:先刻画数据特征,特征指向什么用什么;再对照排除,特征不匹配的不引入。五类数据最终落在三种存储上——parquet、SQLite、JSON——不是这三种技术最好,是每类数据的特征恰好指向它们。

不选择什么比选择什么更重要,因为每个被排除的方案都标记了一个需求边界:DuckDB 对应"没有查询慢的痛点",PostgreSQL 对应"没有多用户并发"。边界写清楚,该换什么、什么时候换,都是现成的答案。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 17:19:19

芯片测试入门:ATE 台架到车规验证 6 步流程

从需求拆解、ATE 台架搭建,到 AEC-Q100 与 ISO 26262 证据链,一次讲清汽车电子芯片测试的完整闭环。 文章目录一、为什么汽车芯片测试和普通芯片测试不是一回事?二、6 步流程总览:一条从“测得到”到“能放行”的链路三、S1 需求拆…

作者头像 李华
网站建设 2026/9/2 17:18:17

ASP友情链接网源码部署与二次开发实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/2 17:17:52

从游戏解说到高效表达:信息处理与结构化输出的能力拆解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/2 17:17:12

Live2D Unity 2.1 SDK 压缩包完整导入指南与排错实战

简介:面向Unity3D开发者提供的Live2DUnity2.1SDK,是一套专门用于在三维游戏引擎中制作二维动态角色动画的完整工具链。该版本针对日本市场优化,整合了模型编辑、资源导出、运行时控制与交互反馈等核心模块,适合独立开发者及中小型…

作者头像 李华
网站建设 2026/9/2 17:11:56

从二维码囤积到链接管理:构建个人数字凭证生命周期系统

最近在整理一些本地文件时,发现了一个挺有意思的现象:我电脑里某个不起眼的文件夹,不知不觉间,已经塞满了上百个二维码图片。这些二维码,有的是某个会议的签到入口,有的是某个文档的临时分享链接&#xff0…

作者头像 李华