Quant OS:Tushare 数据资产、Qlib 研究链与本地回测实录(2026-07-31)
[!success] 先说结论 本机 Tushare 镜像已经不再是“只有早期几十个文件”的样例数据。它现在约 7.7 GB,A 股日线自然月分区已经覆盖 1990-12-19—2026-07-28;我已经从中 冻结出 2018—2025 的中证 500 研究输入,构建了 Qlib 0.9.7 Provider,并用 正式项目自己的 Python 环境完成了三次逐 byte 一致的本地回测。
[!warning] 这证明了什么,又没有证明什么 它证明“本地真实数据 → 可校验数据集 → Qlib 信号 → 组合回测 → 可重放证据” 已经跑通;不证明当前动量策略有投资价值,也不证明 Quant OS 已经达到 60/80 分生产标准。公开状态仍明确为
production_ready=false、investment_value_claim=false、gate_credit=[]。
代码只在 Quant OS 独立项目中。本文统一记为:
$QUANT_OS_ROOT
原始镜像统一记为:
$TUSHARE_MIRROR_ROOT
- 公开状态页:Quant OS Status
- 代码仓库:boat/quant-os
- 本次提交:6275370
- 权威技术手册:TUSHARE_LOCAL_DATA.md
- 本地运行手册:TUSHARE_QLIB_LOCAL_RUN.md
OB 只保存本文这种知识文档,不保存 Python 源码、Parquet、Provider、回测 artifact、账号、密码或 token。
一、应该怎样理解这批数据
最容易犯的错误,是看到 7.7 GB 和一亿多行,就把它当成“已经可用于回测的 数据集”。其实现在有三个不同对象:
flowchart LR
A["Live Tushare mirror<br/>持续变化的原始 Parquet"] --> B["Scoped snapshot<br/>冻结选择与文件 hash"]
B --> C["Qlib Provider<br/>calendar / instruments / features"]
C --> D["Signal & portfolio backtest"]
D --> E["Evidence record<br/>指标、hash、边界"]
- Live mirror 是原材料仓。下载器可能继续增加或刷新分区,不能直接作为 一次实验的唯一版本号。
- Scoped snapshot 固定本次究竟选择了哪些 API、日期、指数、job 和 Parquet 文件,并记录 size、row count、query hash、SHA-256。
- Qlib Provider 是面向 Qlib 的派生格式。它可快速读取,但不是原始数据 备份,也不能替代 frozen source manifest。
这次新增的关键能力,不只是“会读 Parquet”,而是把这三层的身份连接起来: snapshot 和 Provider 各自可验证,再用 lineage verifier 对 221 个 source jobs 的六个字段逐一比对。最终差异为 0。
二、数据到底有什么
截至 2026-07-31 的点时盘点:
| 数据 | 当前自然文件/分区 | 当前行数 | 可利用方向 |
|---|---|---|---|
daily |
428 个月 | 18,041,386 | OHLC、收益、成交量额、回测行情 |
adj_factor |
428 个月 | 18,868,457 | 累计复权 |
daily_basic |
331 个月 | 17,335,584 | 市值、换手、估值、横截面特征 |
stk_limit |
235 个月 | 17,878,521 | 逐股逐日真实涨跌停价格 |
suspend_d |
37 年 | 641,379 | 停复牌、可交易性 |
stock_basic |
4 个版本 | 5,869 | 代码、上市/退市信息 |
000905.SH index_daily |
37 年 | 5,239 | 真实中证 500 benchmark |
000905.SH index_weight |
37 年 | 129,000 | 历史月度成分和权重 |
全镜像的 SQLite job ledger 有约 37.8 万条 completed 版本记录、约 1.80 亿行。 这里“版本记录”会包含同一自然分区的历史刷新,所以不能把 ledger 中所有历史 版本的行数直接当作当前去重数据量。
本次真正进入 Qlib 的范围是:
index 000905.SH
date 2018-01-01 ... 2025-12-31
core APIs daily, adj_factor, trade_cal, stock_basic,
index_daily, index_weight
在这一区间内,96/96 个日线月分区完整,原始 daily 共 8,813,885 行;上海、
深圳可用日线 8,544,077 行,1,942 个交易日。自然键重复、核心空值、OHLC
包络异常和交易日历缺口均为 0;可用日线的复权因子缺失为 0。
daily_basic 在同一范围只缺 13 个可用键,但它尚未进入本次 Provider。这样做
是有意把“数据链跑通”和“因子集合扩张”分开,避免在一次 release 中偷偷改变
输入定义。
三、Quant OS 已经新增了什么
1. 范围冻结器
tools/tushare_snapshot.py 会在一个 SQLite 读事务中,按
(api, partition) 选择最新 completed 版本;它拒绝范围内 running、deferred、
缺分区和文件校验失败。对选中的每个文件做两次 size、SHA、Parquet row count
验证,并在结束时确认 source job 仍是 latest。
它还会自动纳入研究起点前一年的 index_weight 锚点,否则 2018 年第一天无法
知道当时已经生效的成分集合。
2. Tushare → Qlib v2 构建器
tools/tushare_qlib.py 和 adapters/tushare_local.py 已支持:
- SSE 交易日历;
- 真实
000905.SH指数行情; - 历史
index_weight中证 500 成分; - 严格每期 500 只、权重和约 100、快照最大间隔检查;
- 复权 OHLC、反向调整 share volume、保持 money 不变;
- 构建前后再次读取 ledger 和源文件,降低 TOCTOU 风险;
- Provider tree、manifest、converter source 和
data_version验证; - 拒绝 PIT universe 与 synthetic benchmark 混用。
由于 Tushare 的 index_weight 数据没有单独验证过的 published_at,本次采用
保守规则:月末快照从下一个 Provider 交易日才生效。因此它只能称为
event-time / conservative next-session PIT 近似,不能冒充严格的
knowledge-time PIT。
复权使用每只股票在构建区间首个有效复权因子作常数锚:
multiplier = adj_factor_t / first_adj_factor
adjusted OHLC = raw OHLC × multiplier
adjusted volume = raw share volume / multiplier
money = raw money
这不会用未来公司行为重新缩放更早价格,但也意味着以不同 start 构建的两个
Provider 价格 level 可能不同,不能直接拼接。要延长历史,应统一起点重建。
3. 独立血缘验证器
tools/tushare_lineage.py 不相信“文件名看起来一样”,而是比较 snapshot 与
Provider 的每一个 source job:
id
path
row_count
byte_count
sha256
request_sha256
本次结果为 221 jobs × 6 fields,mismatch_count=0,
converter_source_matches_current=true。
4. 一键研究链
Makefile 已提供:
make tushare-snapshot
make tushare-build
make tushare-verify
make tushare-lineage
make tushare-backtest
make tushare-replay
tushare-replay 会再跑一次相同回测,并用 cmp 要求两个结果 JSON 逐 byte
一致。每次 build 必须换一个不存在的新 Provider 目录,工具拒绝覆盖已冻结版本。
四、已经跑出来的 Provider
正式本地位置:
$QUANT_OS_ROOT/data/qlib/tushare-csi500-2018-2025-v2/
核心身份:
| 证据 | 值 |
|---|---|
| data version | 5bf19d2da064357ad1802bca4bfa0c0505ed63fe2b24785ec6c56ecff1724963 |
| Provider tree | f173b8095fd9a62a63807324fa48bad83f0c282eea3abba871d0b0efd30b199f |
| Provider manifest | 27fb6fedb6a4b114eae2aba44505fb6c4d32c7a9ae81e58c724dfadb8dd10ed0 |
| 文件/字节 | 10,011 / 71,707,194 |
| 日历 | 1,942 sessions,2018-01-02—2025-12-31 |
| 历史有效股票 | 1,111 |
| 被选日线 | 1,975,455 |
| 成分快照 | 97 observed / 96 effective |
| 每期成分 | 严格 500 |
| 最大快照间隔 | 36 天 |
为什么 97 observed 只有 96 effective?因为 2025-12-31 的月末 roster 要到下一 个 Provider 交易日生效,但当前 Provider 正好在 2025-12-31 结束,所以它被 看见、校验,却不会提前污染区间内的持仓集合。
五、已经跑过的真实本地回测
这不是 Alpha 模型定稿,而是一条研究 smoke:
Qlib 0.9.7
Python 3.12.13
period 2019-01-02 ... 2025-12-30
feature start 2018-01-02
signal 20 日动量
portfolio Top 50 / drop 5
rebalance 周频
benchmark SH000905(真实指数日线)
在迁移环境中先连续跑了两次;随后又在正式
$QUANT_OS_ROOT/.venv-qlib312 环境中重跑一次。三次输出逐 byte 一致:
| 证据/指标 | 结果 |
|---|---|
| run JSON SHA-256 | 803035a95ee9cf6f90af20cdf7e7024149b97e0113b585209a80f80048be87d3 |
| signal | 176,615 行 |
| portfolio report | 1,698 个交易日 |
| 策略累计收益 | 25.3152% |
| 中证 500 累计收益 | 78.9560% |
| 最大回撤 | -60.4164% |
| 总成本 | 0.035793 |
| 总换手 | 65.513938 |
这组数字首先告诉我们的不是“策略赚了 25%”,而是:
- 数据、日历、历史成分、真实 benchmark 和 Qlib simulator 能完成长区间运行;
- 同输入、同代码、同环境契约可以重放;
- 简单 20 日动量明显跑输中证 500,且最大回撤约 60%,不能进入候选策略;
- 工程链路通过与策略有效性是两件完全不同的事。
Qlib 会打印 $open contains nan 和 Mean of empty slice。这主要来自停牌、
新进入成分但没有当日可用开盘价等稀疏情况;本次运行退出码为 0、结果可重放,
所以它是需要继续分类治理的数据告警,不是一次运行失败。
六、你现在怎样自己验证
正式项目已拥有独立研究环境:
export QUANT_OS_ROOT=/path/to/quant-os
export TUSHARE_MIRROR_ROOT=/path/to/tushare-mirror
cd "$QUANT_OS_ROOT"
source .venv-qlib312/bin/activate
export PYTHONPATH=src:.
先验证已经冻结的 Provider 与血缘,不需要重建 76 MB 数据:
python tools/tushare_qlib.py verify \
data/qlib/tushare-csi500-2018-2025-v2
python tools/tushare_lineage.py \
--source-manifest artifacts/local-tushare-20260731/scoped-source-manifest.json \
--provider-dir data/qlib/tushare-csi500-2018-2025-v2
预期看到:
provider ok = true
source_job_count = 221
mismatch_count = 0
converter_source_matches_current = true
重放已有研究:
make tushare-replay \
PYTHON=.venv-qlib312/bin/python \
TUSHARE_MIRROR_ROOT="$TUSHARE_MIRROR_ROOT"
它大约会输出较多 Qlib warning;最终应得到两个相同 SHA 的 JSON,并通过
cmp。如果要重新构建,请先把 TUSHARE_PROVIDER 指向一个尚不存在的新
目录,不要覆盖本页记录的 v2 Provider。
整库的 325 项测试已经通过,另有 1 项可选 runtime skip:
make test PYTHON=.venv-qlib312/bin/python
七、这些数据接下来能怎样利用
现在就能做
- Qlib 因子研究:在当前中证 500 PIT 近似 universe 上增加价格量能、
daily_basic市值/换手/估值特征。 - Alpha158 + LightGBM:做严格 train/valid/test 和 walk-forward,输出 IC、Rank IC、分组收益、换手、成本敏感性,而不是只看累计收益。
- 研究回归测试:每次 adapter、特征或策略变更后,用冻结
data_version重放,判断差异来自代码还是数据。 - 本地事件回测输入:将同一 snapshot 映射到 Quant OS canonical market state,加入逐股涨跌停、停复牌、T+1 和费用模型。
- 模型/目标包出口:本地训练后输出带
signal_as_of、next_session、 model/data hash 的 TargetPackage,供聚宽或 QMT adapter 消费。
聚宽怎样利用
聚宽托管回测不能直接读取这台 Mac 的 7.7 GB 本地目录。正确做法不是硬上传 全部 Parquet,而是:
- 在本地用冻结数据训练、验证和生成 TargetPackage;
- 在聚宽用同一策略契约和平台原生行情做 hosted parity;
- 对 symbol、时钟、复权、成分、订单和结果 evidence 做差异报告。
聚宽登录后才能形成真实平台 evidence;当前本地 Tushare/Qlib 成功不能替代 聚宽账号内的真实 hosted backtest。
QMT 怎样利用
QMT 适配层最终只应接收:
- 用户在本机安全配置的账号/终端信息;
- 已验签或 hash 固定的 TargetPackage;
- QMT 行情、资产、持仓、订单和回报。
账号和密码不能写进 OB、Git、Provider 或 evidence。拿到账号之前,可以完成 adapter、mock、dry-run 和 fail-closed 门禁;拿到账号后还必须实测连接、 只读查询、最小下单、撤单、回报、重启恢复和 kill switch。
八、为什么它仍不等于 60/80 分
还缺的不是“再跑一个收益曲线”,而是生产语义:
stock_st历史权限被拒,不能把空值当作非 ST;- 本次 Qlib simulator 使用统一 9.5% 涨跌停近似,尚未消费逐股逐日
stk_limit; - 成分快照没有独立
published_at,不是严格 knowledge-time PIT; - 还没有把成员区间每日覆盖与
suspend_d原因完整区分; - snapshot 是 hash manifest,不是 raw byte archive;源文件若被删除,仅靠 manifest 不能重建;
- 尚无聚宽真实托管回测证据;
- 尚无 QMT 真实券商连接、成交回报、恢复和风险演练证据;
- 尚无漂移监控、容量评估、影子运行和持续 paper/live 观察期。
所以这次工作的正确定位是:数据与研究底座从“样例可运行”升级为“真实、 可冻结、可追溯、可重放的本地研究链”。它是走向 60/80 分的必要基础,但 不会自动获得生产门禁分数。
九、下一轮最值得做的实验
按信息增益排序:
- 把
daily_basic接入新的 frozen release,建立 price/volume + size/value/ liquidity 的特征数据集; - 跑 Alpha158 + LightGBM 的滚动训练与 purged walk-forward;
- 建立 baseline:中证 500、等权、简单动量、LightGBM 四组同口径比较;
- 做交易成本从 5/10/20/50 bps、换手约束、TopK 和持有期的敏感性分析;
- 把
stk_limit、suspend_d映射进 Quant OS 事件回测; - 登录聚宽后跑同窗口 hosted parity;
- QMT 账号开通后先做只读和 shadow,再做最小真实订单演练。
真正进入“模型研究”的起点,应该是第 2 项,而不是继续扩大原始数据文件数量。 现在数据量已经够用;下一步的瓶颈是时间语义、实验设计、交易约束和跨平台 证据,而不是缺一条能画出曲线的脚本。