从一份文件,到批量下载、检查、查询的数据仓库
没有 QuantSpace:AI 自己现场决定往哪存
用了 QuantSpace:AI 照约定存
复权不是"美化"价格,而是消除非交易原因造成的机械价格跳变——但"非交易原因"在不同资产上不是一回事。
PandaDataClient.get_adj_factor() 取复权因子表 →
skills.compute.adjust.forward_adjust() 计算 → 存入 1d_adj/是否复权会直接改变收益率和回测结果,必须像 1d、5m 一样成为数据身份的一部分——
QuantSpace 用目录而不是文件名后缀来承载这个身份。
| 目录 | 含义 |
|---|---|
market/1d/ | 原始日线(不复权) |
market/1d_adj/ | 复权后的标准日线,不覆盖原始行情 |
market/5m/ / 5m_adj/ | 5 分钟线的原始 / 复权版本 |
DataManager().save_symbol(
symbol, df, frequency="1d")
DataManager().read_symbol(
symbol, frequency="1d")
路径 = market/<frequency>/<symbol>.parquet
frequency 是显式参数,不是
从数据里猜出来的——调用方必须自己说清楚"这是哪个频率",读取时也要显式传
frequency="1d_adj" 这样的口径,不能让程序或 Agent 猜;②
目录 = frequency、文件名 = symbol,所以 list_symbols(frequency=...) 只需要列一个
目录就行——不再是开场那种"每个脚本自己拼路径",统一交给 DataManager 一个入口管。AGENTS.md,再读 skills/ingest、
skills/store 的 SKILL.md,确定用哪些接口、目录契约是什么list_symbols()/read_symbol() 查本地是否已有;
缺失才下载、save_symbol() 写入;逐文件 validate_ohlcv() 质检read_symbols() 合并成 (symbol, eob) 面板下面每一步怎么走的,拖一拖、点一点自己看;组块 B 接下来放大讲第 ③④⑤ 步, 第 ⑥ 步留到综合应用环节对照真实验收表。
以 CFFEX.IF99 · 1d 这一个任务为例,八个任务都是同一套逻辑:
dm.list_symbols(frequency="1d") 查本地已有哪些 symbol,
判断 "CFFEX.IF99" 在不在里面
→
缺失才下载:fetch_market_data(..., type="future")
→
dm.save_symbol("CFFEX.IF99", df, frequency="1d") 写入 Parquet
→
validate_ohlcv(df) 检查 NaN/Inf/负值/
high<low/时间是否单调递增
validate_ohlcv() 只负责报告,不会替你
改数据——返回一份 DataQualityReport,.passed 是 True 才算这个
任务真的完成;不通过就要回头看取数或转换环节,不能假装没看见。dm.read_symbols(
["CFFEX.IF99", "SHFE.RB99", "DCE.I99", "CZCE.MA99"],
frequency="1d")
read_symbol(),加一列 symbol,把索引变成
(symbol, eob) 两级,concat 到一起再按索引排序——这就是本课反复
出现的 (symbol, eob) panel 格式FileNotFoundError 列出全部缺的文件,不是修一个报一个1d 全部验证通过后,
拼出的日线 panel 形状是 (968, 5)——下一页真实验收表会看到同一个数字。来自一次真实 sub-agent 测试(在当前 QuantSpace 项目中实际执行,非虚构):
| symbol | 1d 行数 | 5m 行数 | 日期覆盖 | 质量 |
|---|---|---|---|---|
CFFEX.IF99 | 242 | 11,616 | 2023-01-03 → 12-29 | PASS |
SHFE.RB99 | 242 | 16,554 | 2023-01-03 → 12-29 | PASS |
DCE.I99 | 242 | 16,554 | 2023-01-03 → 12-29 | PASS |
CZCE.MA99 | 242 | 16,554 | 2023-01-03 → 12-29 | PASS |
真实数据:CFFEX.IF99 1 分钟线(58,080 行)——用 pd.read_parquet()
读比 pd.read_csv() 快约 16 倍。
整份文件(9 列)合起来:Parquet 1.3MB vs CSV 4.2MB,压缩比约 3.3 倍—— 这是每一列压缩效果天差地别(285B ~ 523KB 不等)之后的平均结果。
打个比方:把数据仓库想象成一座图书馆,两种查法差在哪,一眼就能看出来。
SELECT/GROUP BY
用不到的区不进,范围对不上的层不看save_symbol() 写入
不替代 validate_ohlcv() 质检
不维护常驻 .duckdb 文件,每次临时连接
选择口诀:读一个 symbol、继续做 Python 计算 →
Pandas/DataManager;跨很多 Parquet 做筛选、盘点、聚合 → DuckDB。
| 组件 | 解决什么 |
|---|---|
store skill | Agent 该用哪些入口和契约 |
| Parquet | 行情怎样高效、稳定地保存 |
DataManager | 路径、读写、panel、查询统一管理 |
| DuckDB | 跨多份 Parquet 做筛选和聚合 |
第 3 课:把今天这份 (symbol, eob) panel 里的 OHLCV,转换成因子。
检查-补齐-验证、拿证据而不是听宣称——这套心智模型不会变, 变的只是从"管理数据"到"计算因子"。