feat: operationalize local Tushare Qlib research
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
# 本机 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 为 0;scoped 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 fields;mismatch 0;converter current true |
|
||||
| provider files / bytes | 10,011 / 71,707,194 |
|
||||
| source stability | 221/221 jobs before/after stable;post-read file verification 通过 |
|
||||
| instruments / sessions | 1,111 / 1,942 |
|
||||
| selected rows | 1,975,455 |
|
||||
| observed / effective snapshots | 97 / 96 |
|
||||
| universe quality | 每个 snapshot 严格 500 constituents;weight 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)。
|
||||
Reference in New Issue
Block a user