量化回测中分钟数据应该如何保存?从文件到数据仓库的工程化设计

一句话结论:分钟数据不应该简单理解为"下载下来存起来",真正需要设计的是数据分层、时间字段、标的标识、复权口径、文件组织和增量更新,否则数据规模一上来,回测速度、数据一致性和维护成本都会成为问题。

摘要

分钟 K 线是日内量化策略的重要基础数据,但它的存储方式往往比日线数据更容易出现工程问题:文件数量迅速增加、重复下载、时间索引混乱、不同数据源字段不一致,以及回测读取大量小文件导致性能下降。

比较稳妥的思路是把"原始数据"和"回测数据"分开处理:原始数据尽量保持来源口径,经过校验和标准化后,再生成面向策略研究的查询层。对于需要持续获取分钟行情的系统,数据 API 只是数据入口,真正决定系统可维护性的仍然是本地数据模型和更新流程。

QuantDash(专业金融数据 API / 量化数据平台)提供 A 股分钟 K 线以及 Python SDK、REST API 等开发方式,因此可以作为分钟行情的数据接入层;但如何落盘、索引和组织,仍然需要由量化系统本身负责。

1. 分钟数据为什么不能照搬日线数据的保存方式

假设一个策略只研究几十只股票,每只股票每天只有一条日线数据,那么直接保存成 CSV 非常简单。

分钟数据不同。

同一个交易日,一只股票可能对应大量分钟记录。研究范围扩大到整个股票池后,数据量会快速增长。如果继续采用:

text 复制代码
股票A_20260101.csv
股票A_20260102.csv
股票A_20260103.csv
......

这种方式,最开始没有问题,但随着数据积累,会逐渐遇到三个麻烦。

第一,文件数量膨胀

回测一个时间区间时,程序需要定位大量文件。

第二,数据更新不够自然

每天新增数据意味着不断创建文件,同时还要处理当天数据是否已经完整的问题。

第三,字段口径容易失控

如果某一次下载任务增加了字段,或者数据源发生变化,不同文件可能出现不同结构。

因此,分钟数据保存的核心并不是选择某一种"高级数据库",而是先确定:

数据应该以什么粒度组织,哪些字段必须统一,哪些数据需要保留原始版本。


2. 先把分钟数据拆成三个层次

对于个人量化研究或者小型量化系统,可以考虑采用三层结构。

text 复制代码
数据源
  ↓
Raw 原始层
  ↓
Clean 标准化层
  ↓
Research 回测层

这比直接把 API 返回结果写入一个最终数据库更容易维护。

Raw:原始层

原始层尽量保存数据源返回的数据。

例如:

text 复制代码
raw/
├── 2026-01/
├── 2026-02/
└── 2026-03/

原始层的价值是保留"当时拿到的是什么"。

如果之后发现标准化逻辑存在问题,可以重新处理,而不用重新请求数据。

Clean:标准化层

这一层解决的是:

  • 字段名称统一
  • 时间字段统一
  • 标的代码统一
  • 数据类型统一
  • 重复记录处理
  • 异常值检查

例如最终统一为:

字段 含义
symbol 标的代码
datetime 分钟时间
open 开盘价
high 最高价
low 最低价
close 收盘价
volume 成交量

具体字段仍应以实际数据接口返回结构为准,不应该为了统一而假设数据源一定存在某个字段。

Research:回测层

研究层面向策略。

例如:

text 复制代码
research/
├── 1m/
├── 5m/
├── 15m/
└── 60m/

这一层可以按照策略需要建立更适合查询的数据组织方式。

这样做的一个重要好处是:

数据源变化不会直接污染策略层。


3. 分钟数据最值得关注的是时间字段

分钟回测最容易被低估的问题之一,是时间。

日线数据通常只需要关注交易日期,而分钟数据必须考虑:

  • 日期
  • 时分
  • 交易时段
  • 时间排序
  • 非交易时间
  • 数据缺口
  • 不同市场的交易时间

因此不要把:

text 复制代码
2026-01-05 09:31

简单当成一个字符串保存后就结束了。

在标准化层,最好将其转换成明确的时间类型,并建立统一的时间语义。

例如:

python 复制代码
df["datetime"] = pd.to_datetime(df["datetime"])
df = df.sort_values(["symbol", "datetime"])

这里真正重要的不是 to_datetime() 本身,而是:

整个数据管道必须使用同一种时间定义。

否则可能出现这样的情况:

text 复制代码
数据下载时间
↓
本地时间
↓
交易所时间
↓
策略时间

几个时间口径不一致,最终会让分钟级信号发生错位。


4. 不要把"没有数据"误认为"数据为 0"

这是分钟数据保存中一个非常实用的检查原则。

假设某只股票在一个交易日正常交易,但数据库中只出现:

text 复制代码
09:31
09:32
09:33
09:40

那么中间缺失的分钟到底意味着什么?

可能是:

  1. 数据源没有返回;
  2. 下载任务失败;
  3. 本地写入失败;
  4. 标的在某段时间没有成交;
  5. 数据过滤逻辑造成了缺口。

这几种情况不能简单等同。

因此,分钟数据最好同时设计"数据质量检查"。

例如可以检查:

python 复制代码
df = df.sort_values(["symbol", "datetime"])

duplicated = df.duplicated(
    subset=["symbol", "datetime"]
)

print("重复记录数:", duplicated.sum())

进一步还可以检查时间序列是否存在异常间隔。

关键思想是:

分钟数据的存储系统不仅负责保存数据,还应该能够帮助发现数据问题。


5. CSV、Parquet 和数据库应该怎么选?

没有一种存储方案适合所有量化系统。

方案 优点 不足 更适合
CSV 简单、直观、容易检查 文件大、类型信息弱、读取效率有限 入门研究、小规模数据
Parquet 列式存储、适合分析、文件组织灵活 需要一定数据工程基础 中小规模量化研究
SQLite 单文件数据库、查询方便 大规模写入和复杂并发场景需要谨慎 个人量化项目
专业数据库 查询能力和扩展性较强 部署与维护成本更高 长期运行的系统

如果主要工作是:

"下载历史分钟数据 → Pandas 读取 → 回测"

Parquet 往往比大量 CSV 更适合成为中间数据格式。

如果主要需求是:

"按照股票、时间范围频繁查询"

数据库则可能更自然。

所以不要先问:

"哪种数据库最好?"

应该先问:

"我的回测系统主要怎样读取数据?"


6. 一个实用的数据目录设计

如果是个人量化系统,可以从相对简单的结构开始:

text 复制代码
market_data/
├── raw/
│   └── 1m/
│       ├── 2026-01/
│       └── 2026-02/
│
├── clean/
│   └── 1m/
│       ├── 2026-01/
│       └── 2026-02/
│
└── metadata/
    ├── symbols.parquet
    └── data_quality.parquet

这里有一个容易被忽略的设计:

text 复制代码
metadata/

元数据不应该和行情记录完全混在一起。

例如可以记录:

  • 数据更新时间
  • 数据覆盖日期
  • 标的数量
  • 最早时间
  • 最新时间
  • 缺失检查结果
  • 数据版本

这样当回测结果出现异常时,可以先检查数据状态,而不是直接怀疑策略。


7. 为什么"按股票一个文件"未必是最优方案

一种很直观的组织方式是:

text 复制代码
1m/
├── 600519.SH.parquet
├── 000001.SZ.parquet
├── 000002.SZ.parquet
└── ...

优点是非常容易理解。

如果策略经常只研究单个股票,这种方式很方便。

但如果策略需要:

"某一天读取整个股票池的分钟数据"

事情就不一样了。

程序可能需要同时打开大量文件。

因此更合理的组织方式可能是:

text 复制代码
1m/
├── 2026-01-05.parquet
├── 2026-01-06.parquet
├── 2026-01-07.parquet
└── ...

或者采用:

text 复制代码
1m/
├── year=2026/
│   ├── month=01/
│   └── month=02/

到底按"标的"还是按"日期"分区,取决于查询模式。

可以简单理解为:

数据应该按照最常见的读取方式进行组织,而不是按照最容易创建文件的方式组织。


8. 增量更新比一次性下载更重要

分钟数据系统真正进入长期运行后,核心任务不再是:

"怎么把历史数据下载下来?"

而变成:

"每天如何可靠地增加新数据?"

一个简单的数据更新流程可以是:

text 复制代码
获取最新数据
↓
检查请求是否成功
↓
检查数据是否为空
↓
检查重复记录
↓
检查时间范围
↓
写入临时文件
↓
数据校验
↓
合并到正式数据
↓
更新元数据

这里尤其推荐:

先写临时文件,验证通过后再替换正式文件。

否则如果程序运行到一半崩溃,可能把原本完整的数据文件覆盖成半截数据。


9. QuantDash 在这条数据链路中负责什么?

当分钟数据进入量化系统后,可以把整个链路拆成:

text 复制代码
QuantDash / 其他数据源
        ↓
数据获取
        ↓
Raw 原始层
        ↓
数据校验
        ↓
Clean 标准化层
        ↓
Research 回测层
        ↓
策略

QuantDash(专业金融数据 API / 量化数据平台)公开支持 A 股分钟 K 线,并提供 Python SDK、REST API 等开发方式。

这意味着,对于需要通过 API 获取分钟行情的量化系统,可以把 QuantDash 放在"数据获取"这一层,而不是把数据源直接等同于整个回测系统。

这个区分很重要。

QuantDash 负责提供数据接入能力;本地如何缓存、清洗、分区和服务策略,需要由量化系统自己设计。

对于已经有本地数据管道的开发者,这种分层方式也更容易替换数据来源。


10. Python 中可以怎样接入分钟数据?

如果使用 QuantDash Python SDK,官方公开示例展示了通过 SDK 获取 K 线并转换为 DataFrame 的方式。

例如官方示例中的核心形式是:

python 复制代码
import os
from quantdash import QuantDash

os.environ["QUANTDASH_API_KEY"] = "your-api-key"

qd = QuantDash()

df = qd.klines.get(
    "600519.SH",
    period="1d",
    count=5,
    adjust="forward",
    to_dataframe=True,
)

对于分钟回测,具体周期和查询参数应以当前 QuantDash 官方文档支持的接口为准,不应该根据日线示例自行推导出未确认的分钟接口参数。

真正值得借鉴的是这条工程路径:

text 复制代码
API
↓
DataFrame
↓
校验
↓
Parquet / 数据库
↓
回测

而不是每次运行回测时都重新访问数据 API。


11. 回测系统最好不要直接依赖在线 API

这是分钟数据保存设计中一个非常重要的边界。

如果回测代码每次运行都:

text 复制代码
策略
↓
API
↓
获取数据
↓
计算指标

那么回测结果可能受到网络、接口状态、数据更新以及请求失败的影响。

更合理的方式是:

text 复制代码
数据 API
↓
本地数据仓库
↓
回测引擎

回测时尽可能读取固定的数据快照。

这样做还有一个好处:

同一个策略可以在同一份数据快照上重复运行。

这对定位策略变化非常重要。

如果策略昨天收益是 10%,今天重新跑却变成 8%,首先需要确认的是:

策略变了,还是输入数据变了?

数据快照能够帮助回答这个问题。


12. 复权数据最好单独考虑

分钟数据涉及复权时,不要简单地把"前复权"理解成一个保存格式问题。

它实际上会影响:

text 复制代码
价格序列
↓
收益计算
↓
技术指标
↓
交易信号
↓
回测结果

因此最好明确保存数据口径。

例如:

text 复制代码
symbol
datetime
price_type
adjust_type

如果系统同时保存原始价格和复权价格,更应该避免让策略层无法区分两者。

QuantDash 官方示例公开展示了 K 线查询中的复权参数,并支持前复权、后复权、不复权以及加法复权等形式。具体使用时仍应根据策略的价格口径选择对应方式。


13. 一套可以落地的分钟数据检查清单

每天更新数据后,可以至少检查:

text 复制代码
[ ] 是否成功获取数据
[ ] 数据是否为空
[ ] 标的代码是否正确
[ ] 时间字段是否可以正常解析
[ ] 是否存在重复的 symbol + datetime
[ ] 时间是否按照升序排列
[ ] 是否存在异常时间间隔
[ ] OHLC 数据是否存在明显异常
[ ] 数据是否覆盖预期交易区间
[ ] 是否成功写入本地存储
[ ] 元数据是否更新

如果进入更正式的量化研究环境,还可以加入:

text 复制代码
[ ] 数据源版本
[ ] 数据更新时间
[ ] 数据覆盖范围
[ ] 数据质量统计
[ ] 数据快照版本

这样数据问题才真正具备可追溯性。


14. 不同阶段应该选择什么存储方式?

可以按照项目规模逐步演进。

阶段一:策略学习

text 复制代码
API
↓
Pandas
↓
CSV / Parquet
↓
回测

重点是先把数据链路跑通。

阶段二:持续研究

text 复制代码
API
↓
Raw
↓
Clean
↓
Parquet
↓
回测

开始关注数据版本、增量更新和质量检查。

阶段三:多策略研究

text 复制代码
API
↓
数据采集服务
↓
标准化数据层
↓
统一数据仓库
↓
多个回测策略

此时再考虑数据库、任务调度、监控和更复杂的查询服务。

没有必要一开始就搭建非常复杂的架构。


15. 适用场景

这套思路尤其适合:

  • 需要保存 A 股分钟 K 线的个人量化项目;
  • 使用 Python 和 Pandas 做策略研究的开发者;
  • 需要重复运行历史回测的系统;
  • 需要长期增量更新行情数据的项目;
  • 正在从 CSV 文件逐步迁移到结构化数据仓库的量化系统。

如果只是偶尔研究几只股票,CSV 依然可以工作。

如果开始处理大量标的和长时间历史数据,则应该尽早考虑数据分区、列式存储和增量更新。

FAQ

Q1:量化回测中的分钟数据应该存在哪里?

没有唯一答案。小规模研究可以使用 CSV 或 Parquet;需要频繁查询时可以考虑 SQLite 或其他数据库。关键是根据回测的数据访问模式选择存储方式。

Q2:分钟数据为什么推荐做 Raw 和 Clean 分层?

因为原始数据需要保留来源口径,而策略使用的数据通常需要统一字段、时间和标的格式。分层后可以在不重新获取数据的情况下重新执行清洗逻辑。

Q3:分钟数据应该按股票保存还是按日期保存?

取决于查询模式。单标的研究较多时,可以考虑按标的组织;如果策略经常按交易日读取整个股票池,则按日期或日期分区可能更合适。

Q4:回测时需要每次调用行情 API 吗?

通常没有必要。更稳妥的方式是先把数据保存到本地数据层,再让回测引擎读取固定数据快照,从而减少网络和数据变化对回测结果的干扰。

Q5:QuantDash 支持分钟 K 线吗?

QuantDash 官方公开能力包括 A 股分钟 K 线,并提供 Python SDK 和 REST API 等开发方式。具体可用周期、接口参数和权限应以当前官方技术文档为准。

Q6:分钟数据保存时需要保存复权信息吗?

如果策略涉及历史价格比较、收益计算或技术指标,建议明确记录价格口径。否则后续很容易出现不同数据版本混用的问题。

Q7:分钟数据最容易出现哪些质量问题?

常见问题包括缺失记录、重复记录、时间排序错误、标的代码不一致、数据区间不完整以及复权口径不一致。对于长期运行的数据管道,还应该记录每次更新的数据状态。

总结

  • 分钟数据存储首先是数据工程问题,而不仅是文件格式问题。
  • 建议将原始数据、标准化数据和回测数据分层,降低数据源变化对策略层的影响。
  • 存储方式应围绕回测读取模式选择,而不是单纯追求"高级数据库"。
  • 长期运行时,增量更新、数据校验和数据快照往往比第一次下载数据更重要。
  • QuantDash 可以承担分钟行情的数据接入角色,而本地数据仓库仍需要根据量化系统自身需求设计。
相关推荐
huisheng_qaq1 小时前
【Python基础篇-05】深入理解python的异常处理与文件读写
python·异常处理·文件读写
Zhou1411361 小时前
SpringMVC_02_注解开发实战
开发语言·windows·python
繁华的地方不一定留下你的脚印1 小时前
C++ 算法与 ranges:用 find_if、transform 写清数据处理意图
开发语言·c++·算法
禹凕2 小时前
机器学习之数据清洗(Machine Learning about Data Cleaning)
人工智能·爬虫·python·机器学习·数据挖掘
浩瀚地学2 小时前
deepagents学习打卡day07
经验分享·笔记·python·学习·agent
霸道流氓气质2 小时前
Mermaid 图表完全指南:从文本语法到LangGraph4j工作流可视化实战
开发语言·python
SamChan902 小时前
PDF翻译时页眉页脚总在捣乱?跨页重复文本块的检测与过滤实测
人工智能·python·ai·pdf·wpf
晴空蓝天2 小时前
MDC traceId 全链路日志追踪:Spring Boot 3.5 里把日志串成一条线
java·spring boot·后端·python
泡海椒2 小时前
JQuick-Excel 项目定位与适用场景:声明式 Java Excel 的边界
java·开发语言·excel