Files
quant-os/docs/runbooks/TUSHARE_QLIB_LOCAL_RUN.md

222 lines
9.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 本机 Tushare → 冻结 release → Qlib 回测
目标:只使用本机已有数据,冻结 `000905.SH` 的 2018—2025 输入,构建
managed Qlib provider,验证后运行一条可重放的周频动量 smoke。
## 1. 固定本机路径与 Python 环境
```bash
export QUANT_OS_ROOT=/path/to/quant-os
export TUSHARE_MIRROR_ROOT=/path/to/tushare-mirror
cd "$QUANT_OS_ROOT"
python3.12 -m venv .venv-qlib312
source .venv-qlib312/bin/activate
python -m pip install -r requirements/research-py312.txt
python -c "import qlib; assert qlib.__version__ == '0.9.7'"
export PYTHONPATH=src:.
```
当前正式项目目录内应新建自己的 `.venv-qlib312`。不要复用
`quant-os-migration-backup-20260730` 下的迁移遗留环境。
### 推荐的 Makefile 全链调用
```bash
export TUSHARE_MIRROR_ROOT=/path/to/tushare-mirror
export TUSHARE_PROVIDER=data/qlib/tushare-csi500-2018-2025-next
export TUSHARE_SNAPSHOT=artifacts/local-tushare/scoped-source-manifest.json
make tushare-snapshot
make tushare-build
make tushare-verify
make tushare-lineage
make tushare-backtest
make tushare-replay
```
每次 build 必须使用一个尚不存在的 `TUSHARE_PROVIDER` 目录,不能覆盖本页记录的
正式 provider。`make tushare-replay` 依赖 `tushare-backtest`,随后运行第二次
并用 `cmp` 要求两个 evidence JSON 逐 byte 相同。下面保留展开命令,便于审计
每一个参数。
## 2. 先盘点 live mirror
全镜像仍可能下载指数数据。先做快速 job-ledger 盘点:
```bash
python tools/tushare_qlib.py inventory \
--mirror-root "$TUSHARE_MIRROR_ROOT" \
--skip-file-verification \
--output-json artifacts/tushare/live-inventory.json
```
对几十万文件做全镜像 SHA 校验成本较高,也不能解决“扫描过程中镜像变化”的
问题。正式 run 应在下一步冻结范围后,只校验 release 选中的文件。
## 3. 冻结 2018—2025 scoped release
本次 Qlib 核心 release 包含:
```text
daily,adj_factor,trade_cal,stock_basic,index_daily,index_weight
index code = 000905.SH
date = 2018-01-01 ... 2025-12-31
```
独立入口为 `tools/tushare_snapshot.py`。先确认接口,再生成 manifest:
```bash
python tools/tushare_snapshot.py --help
python tools/tushare_snapshot.py \
--mirror-root "$TUSHARE_MIRROR_ROOT" \
--start 2018-01-01 \
--end 2025-12-31 \
--apis daily adj_factor trade_cal stock_basic index_daily index_weight \
--index-codes 000905.SH \
--output artifacts/local-tushare-20260731/scoped-source-manifest.json
```
默认开启文件验证,manifest 记录选择规则、目标 API/指数/日期、source job、
实际 Parquet 路径、size、row count、SHA-256 和 release hash。生成后不要
继续用 live ledger 代替这份 manifest。若下一条执行仿真需要
`daily_basic/stk_limit/suspend_d`,另建包含这些 API 的 release,不要静默
扩充本次 Qlib 输入。该 manifest 是 hash 锁定证据,不复制 raw Parquet
需要长期 raw 重建时还要保留对应内容寻址副本。
## 4. 构建并验证 v2 provider
先确认 v2 参数:
```bash
python tools/tushare_qlib.py build --help
```
当前已验证命令如下:
```bash
python tools/tushare_qlib.py build \
--mirror-root "$TUSHARE_MIRROR_ROOT" \
--output-dir data/qlib/tushare-csi500-2018-2025-v2 \
--start 2018-01-01 \
--end 2025-12-31 \
--market-name tushare_csi500 \
--benchmark-index-code 000905.SH \
--universe-index-code 000905.SH \
--minimum-observations 60 \
--ohlc-policy fail \
--output-json artifacts/tushare/csi500-2018-2025-build.json
python tools/tushare_qlib.py verify \
data/qlib/tushare-csi500-2018-2025-v2 \
--output-json artifacts/tushare/csi500-2018-2025-verify.json
python tools/tushare_lineage.py \
--source-manifest artifacts/local-tushare-20260731/scoped-source-manifest.json \
--provider-dir data/qlib/tushare-csi500-2018-2025-v2 \
--output-json artifacts/tushare/csi500-2018-2025-lineage.json
```
输出目录必须是新目录。失败时保留错误和 inventory/release 证据,不要删除门禁
或回退到 synthetic benchmark。v2 的复权是每个 symbol 以区间首个有效因子
为锚:`factor_t=adj_t/first_adj`OHLC 乘 factor、share volume 除 factor、
money 不变。不同 `start` 构建的 provider 具有不同归一化锚点,价格 level
不能直接拼接;需要以统一起点重建或显式重新归一化。
当前 `build` 会自行从 mirror 选择 source jobs,并把它们写进 provider
manifest;它不会直接读取上一步的 scoped manifest。验收时必须比较两份
manifest 的 source job/file hashes。snapshot 工具会为 `index_weight` 自动
包含 `start.year-1` 的 pre-start anchor。本次两边均为 221 个 jobs,全字段
diff 为 0scoped manifest SHA-256 为
`744a69828f527594e9f2787280095368c50deffa2f269c359ae65e2e0ac2d5f2`
selection SHA-256 为
`66e91fffecba5fa042922c49e339f24212c3febf5b874279866fdca660f54fb5`
provider 对 221 个 jobs 在消费前完成校验,消费后重新读取 SQLite 并再次校验
221 个文件;`selected_jobs_still_latest=true`
`post_read_file_verification=true`
live 下载器在整个验证窗口内继续写入无关任务,所以全 DB/WAL 的
`changed_through_file_verification=true`。scoped 证据仍成立:事务读取期间
`changed_while_reading=false`221 个 jobs 均 double verified、验证后仍为
latest,且与 build 的 221 个 source jobs 全字段零差异。不要把“全库有变化”
误写成“本次 scoped inputs 有变化”。
lineage 结果还必须满足:`source_job_count=221`,匹配字段严格为
`id/path/row_count/byte_count/sha256/request_sha256`
`mismatch_count=0`,并且 `converter_source_matches_current=true`
## 5. 运行本地 momentum smoke
provider 以 2025-12-31 结束;Qlib simulator 会访问回测结束日之后的下一个
provider session,因此回测结束日使用 2025-12-30
```bash
PYTHONHASHSEED=0 python -m platforms.qlib_runner \
--provider-uri data/qlib/tushare-csi500-2018-2025-v2 \
--market tushare_csi500 \
--benchmark SH000905 \
--start 2019-01-02 \
--end 2025-12-30 \
--feature-start 2018-01-02 \
--lookback 20 \
--topk 50 \
--n-drop 5 \
--rebalance weekly \
--output-json artifacts/tushare/csi500-momentum-run-1.json
```
原参数不变再运行一次,只改输出文件名为
`csi500-momentum-run-2.json`。比较输入/provider identity、signal hash、
portfolio report hash、表边界和重算指标;`created_at` 等非决定性元数据若
存在,应由 verifier 的语义比较处理,而不是只看终端是否打印成功。
本次两个 run JSON 实际逐 byte 相同,SHA-256 均为
`803035a95ee9cf6f90af20cdf7e7024149b97e0113b585209a80f80048be87d3`
## 6. 已获得的正式研究证据
| 项目 | 实测值 |
| --- | --- |
| data version | `5bf19d2da064357ad1802bca4bfa0c0505ed63fe2b24785ec6c56ecff1724963` |
| provider tree SHA-256 | `f173b8095fd9a62a63807324fa48bad83f0c282eea3abba871d0b0efd30b199f` |
| provider manifest SHA-256 | `27fb6fedb6a4b114eae2aba44505fb6c4d32c7a9ae81e58c724dfadb8dd10ed0` |
| lineage | 221 jobs × 6 fieldsmismatch 0converter current true |
| provider files / bytes | 10,011 / 71,707,194 |
| source stability | 221/221 jobs before/after stablepost-read file verification 通过 |
| instruments / sessions | 1,111 / 1,942 |
| selected rows | 1,975,455 |
| observed / effective snapshots | 97 / 96 |
| universe quality | 每个 snapshot 严格 500 constituentsweight sum ±0.5;最大间隔 36 天 |
| membership availability | 月末权重下一 provider 交易日生效;same-session use 为 false |
| signal / report rows | 176,615 / 1,698 |
| strategy cumulative return | 0.253151986388197 |
| benchmark cumulative return | 0.789559383530678 |
| max drawdown | -0.6041641365538415 |
| evidence flags | `production_ready=false``investment_value_claim=false``gate_credit=[]` |
## 7. 如何读结果
先回答四个工程问题:
1. frozen release 与 provider 是否通过 hash/语义验证;
2. benchmark 是否确为 `SH000905`,universe 是否来自历史权重并从下一交易日
生效;
3. 两次 run 的关键表和指标是否可重放;
4. 日期、股票数、缺失率、换手、成本和极端收益是否合理。
然后才看年化收益、最大回撤、信息比率等研究指标。该 momentum run 只是数据桥
和研究运行时 smoke,不是选股结论;它尚未完整表达历史 ST、逐股成交约束、
冲击成本、容量和券商执行,也不自动获得 60/80 标准中的任何门禁分数。
尤其注意,本次 Qlib run 的 `limit_threshold=0.095` 是统一 9.5% 近似,没有
使用镜像内 `stk_limit` 的逐股逐日价格,无法表达主板/创业板/科创板、规则变更
和 ST 股票的不同涨跌停限制;历史 `stock_st` 权限仍被拒。因此这些结果严格是
research-only,不能升级为 production-ready。
`index_weight` 源没有单独验证的 `published_at`,所以该 universe 只能称为
event-time / 保守次日生效 PIT 近似,不能称为严格 knowledge-time PIT。
更完整的数据边界与当前覆盖见
[`../TUSHARE_LOCAL_DATA.md`](../TUSHARE_LOCAL_DATA.md)。