feat: add Tushare Qlib data bridge

This commit is contained in:
2026-07-26 13:41:24 +08:00
parent 48c5f64bbd
commit e5911ca454
15 changed files with 1804 additions and 8 deletions
+12 -1
View File
@@ -32,6 +32,7 @@ synthetic PIT/features -> purged walk-forward -> Ridge challenger
(not deployment-frozen)
Qlib 0.9.7:
Tushare completed/checksummed Parquet -> immutable managed provider
native portable-momentum signal bridge
Alpha158 + LightGBM + Recorder research workflow
(no L3 target / L4 order-intent parity)
@@ -62,6 +63,14 @@ XtTrader:
## Data plane and point-in-time semantics
本地 Tushare 镜像是独立只读输入边界。adapter 只信任 SQLite 中 completed
job,并重新核对文件大小、SHA-256、Parquet 行数/footerQlib provider
发布到新目录,manifest 绑定 source job、build parameter、converter source、
完整 provider tree 和可重算 `data_version`。当前镜像未完成且缺复权、
真实 benchmark、历史成分/ST/停牌/涨跌停,因此只能进入 Qlib 技术 smoke
不能直接进入 canonical production decision。完整合同与实跑证据见
[`TUSHARE_LOCAL_DATA.md`](TUSHARE_LOCAL_DATA.md)。
JQData adapter 的输入合同是逐交易日历史指数成员和 `get_bars` 日线字段:
- 未复权 `open/high/low/close``volume``money`
@@ -173,6 +182,8 @@ Qlib 路径固定为 `0.9.7`。CPython 3.12 实际 fixture smoke 已得到:
重算的有限值指标,明确 `investment_value_claim=false`
- local MLflow file store 使用时,runner 以
`os.environ.setdefault("MLFLOW_ALLOW_FILE_STORE", "true")` 显式确认。
- 本地 Tushare completed 分区已生成 managed provider;早期未复权动量
smoke 在 `PYTHONHASHSEED=0` 下连续两次得到逐 byte 相同的 evidence JSON。
该 fixture 只有两只股票和一个 benchmark,性能没有投资意义。Qlib 的单一
`limit_threshold` 也不能表达逐日板块/ST 规则,所以它只参与 L1/L2 和诊断
@@ -217,7 +228,7 @@ saved/current source aggregate、saved/current schema aggregate 和 payload
schema。`verify-snapshot-decision` 与 data snapshot semantic verifier 承担
各自边界的同类职责。
这些合同已有自动测试;最近一次标准库套件为 211 tests、OK、3 个可选路径
这些合同已有自动测试;最近一次标准库套件为 216 tests、OK、6 个可选路径
skip。测试结果不能替代 schema 迁移演练、真实平台导出或券商回调认证。
## Clocks and reconciliation
+3 -2
View File
@@ -25,7 +25,7 @@ A 股 Baseline 内核。它的新增得分必须在授权数据、真实平台
截至 2026-07-26 的实现事实快照:
- 截至 2026-07-26 的最新标准库全量测试为 `Ran 211 tests``OK (skipped=3)`
- 截至 2026-07-26 的最新标准库全量测试为 `Ran 216 tests``OK (skipped=6)`
- JQData raw/factor/pre-close/limits/paused + 每日 PIT `is_st`/指数成员
已连接到 semantic snapshot verifier、`snapshot-backtest`
`snapshot-decision`,并拒绝把抓取当日未到 24:00 的日线标成完整收盘,
@@ -152,7 +152,8 @@ A 股 Baseline 内核。它的新增得分必须在授权数据、真实平台
T 日 Signal 与 T+1 broker fact 绑定、完整 artifact verifier
- Colab 默认无账号流程,以及显式可选的 JQData/Qlib cell。
最近一次 211-test suite两条 Qlib fixture smoke 证明上述代码路径能执行,
最近一次 216-test suite两条 Qlib fixture smoke 和 Tushare
managed-provider 技术 smoke 证明上述代码路径能执行,
但仍明确不证明:
- synthetic 数据或两股票 Qlib fixture 的收益具有投资价值;
+2 -1
View File
@@ -5,7 +5,7 @@
| Path | Intended use | Runtime / prerequisite | Current verified fact | Missing release evidence |
| --- | --- | --- | --- | --- |
| Local synthetic execution | 确定性事件回测、ledger/replay | Python ≥3.10,无账号 | 标准库套件最近记录为 211 tests、OK、3 skipsmoke + exact manifest verifier 可运行 | synthetic 不代表真实市场/收益 |
| Local synthetic execution | 确定性事件回测、ledger/replay | Python ≥3.10,无账号 | 标准库套件最近记录为 216 tests、OK、6 skipsmoke + exact manifest verifier 可运行 | synthetic 不代表真实市场/收益 |
| Local synthetic research | PIT/features/walk-forward/Ridge/risk/cost/target | Python ≥3.10,无账号 | research smoke + manifest verifier 可运行 | Ridge 未冻结部署,未做授权长样本 OOS |
| JQData ingestion | 授权数据到 canonical immutable snapshot | `jqdatasdk==1.9.8` + 授权账号 | fake-provider 测试覆盖 raw/factor/pre-close/limits/paused/PIT `is_st`/membership、24:00 完整收盘约束和语义 verifier | 尚未登录真实账号保存快照、许可与 lineage |
| Snapshot local backtest | PIT membership 的 provider-data 本地回测 | semantic verifier 通过的 snapshot | `snapshot-backtest` 复用 production decision path,输出普通 run manifest | 尚无授权真实 snapshot;仍是本地 fill model |
@@ -19,6 +19,7 @@
| XtTrader read-only shadow | 账户绑定 target-diff 与 broker observation | MiniQMT/QMT、账户查询权限、既有本地 HMAC key | HMAC 账户绑定 + authenticated evidence envelope、decision/observation semantic replay、资产/持仓/委托/成交恒等式、fail-closed exact verifier 与 fake broker 合同 | HMAC 不是 broker attestation;仍缺官方 query 失败/空结果合同、callback/restart、20 日真实 shadownot live-ready |
| Qlib native momentum | signal/model research | CPython 3.12 + exactly `pyqlib==0.9.7` | 真实本地 fixture 成功,24 signal;保存 signal/report hash、表边界和重算 portfolio 指标 | synthetic 两股票;无真实数据/OOS,无 L3/L4 parity |
| Qlib Alpha158/LightGBM | model fit + Recorder workflow | 同上 + LightGBM/MLflow stack | 真实 fixture 完成 model/Recorder;读取 portfolio report,保存表 hash、边界、重算指标与 artifact path | synthetic 两股票性能无意义;真实 provider/OOS 和冻结模型缺失 |
| Tushare local → Qlib | completed Parquet 的只读盘点、不可变 provider 与本地研究 smoke | 同上 + `pyarrow==24.0.0`;本地镜像 | 93 个 completed 文件全部通过 size/SHA/row-countv4 provider verifier 成功;同一动量命令连续两次 evidence JSON 逐 byte 相同 | `daily` 仅完成约 8.2%,缺复权、真实 benchmark、PIT 成分/ST/停牌/涨跌停;`production_ready=false` |
| Mock parity exporter | JoinQuant/QMT wrapper 的 L2—L4 合同回归 | Python ≥3.10 | `mock_contract_only``real_platform_pass=false``gate_credit=[]` | 不计 G9;必须换成真实平台导出 |
G1—G10 当前都未通过。JoinQuant hosted smoke 与 Qlib fixture smoke 是真实
+225
View File
@@ -0,0 +1,225 @@
# Tushare 本地数据盘点、Qlib 接入与回测手册
截至 2026-07-26Quant OS 已能只读盘点另一个任务生成的 Tushare
Parquet 镜像,校验已完成分区,并转换为不可变的 Qlib 0.9.7 provider。
这条路径已经完成两次 byte-identical 的本地动量回测。
当前证据的边界是:
```text
completed_partitions_only_not_full_mirror
production_ready = false
investment_value_claim = false
gate_credit = []
```
它证明“本地 Tushare 数据可以进入 Qlib 并可重放”,不证明镜像已经下载完整,
也不证明策略收益有效或 Quant OS 已达到 `BASELINE_60`
## 1. 2026-07-26 点时盘点
镜像中共有 93 个状态为 `completed` 的 Parquet,均通过 SQLite 记录的文件大小、
SHA-256 和 Parquet row count 校验;另有 1 个遗留 `running` job。
| 表 | 文件数 | 行数 | 当前范围/说明 |
| --- | ---: | ---: | --- |
| `daily` | 35 | 28,731 | 1990-12-19—1993-10-29138 个代码、731 个交易日 |
| `trade_cal` | 37 | 13,004 | SSE 1990—2026 日历,8,689 个开市日 |
| `stock_basic` | 4 | 5,869 | 当前上市/退市/暂停上市等基础快照 |
| `stock_company` | 3 | 6,294 | SSE/SZSE/BSE 公司资料 |
| `index_basic` | 7 | 9,643 | CSI/SW/SSE/SZSE 等指数基础资料 |
| `index_classify` | 6 | 870 | 申万 2014/2021 分类 |
| `bse_mapping` | 1 | 248 | 北交所新旧代码映射 |
| **合计** | **93** | **64,659** | **5,734,035 bytes Parquet** |
`daily` 原计划覆盖 1990-12—2026-07,共 428 个月;当前完成 35 个月,约
8.2%。下载进程已停止,SQLite 仍遗留
`daily/month=1993-11``running` 状态。日志显示首先发生 DNS 解析失败,
错误处理阶段又发生 `sqlite3.OperationalError: unable to open database
file`。因此不能把这个状态解释为仍在后台下载,也不要在 Quant OS
转换过程中修改或自动恢复原下载器。
尚未落盘的生产关键表包括:
- `adj_factor``index_daily`
- `index_member_all``index_weight`
- `stk_limit``suspend_d``stock_st`
- `daily_basic`
其中历史 `stock_st` 权限探测被拒绝。即使其他下载完成,历史 ST 仍需要新的
授权数据源或明确的降级门禁。
## 2. 已确认的数据质量
- 已完成 Parquet 自然键无重复,核心日线字段无空值;
- 无负成交量或负成交额,日线日期与同期 SSE 开市日完全对应;
- 17 条早期 OHLC 包络异常;默认拒绝,只有显式
`--ohlc-policy expand-range` 才按原始 `open/high/low/close` 四价的
min/max 修复,并记录数量与异常键 hash;
- 2 条 `pct_chg` 与保存精度下重算结果的偏差超过 0.02 个百分点;
- 日线代码 `000022.SZ` 不在当前 `stock_basic` 快照;
- `stock_basic` 存在遗留非标准代码 `T600018.SH`,转换器不会把它当成
A 股代码;
- `index_basic.base_point` 存在分区类型漂移,后续 canonical 合并时必须
显式 cast。
Tushare 的 `daily` 是未复权行情,停牌期间不返回记录;`vol` 单位为手,
`amount` 单位为千元。转换器固定执行:
```text
600000.SH -> SH600000
000001.SZ -> SZ000001
430047.BJ -> BJ430047
volume = vol * 100
money = amount * 1000
factor = 1
```
未上市、退市后和停牌缺口在 Qlib feature 中保留为 `NaN`。字段语义来源见
[Tushare 日线接口](https://tushare.pro/document/2?doc_id=27)与
[复权因子接口](https://tushare.pro/document/2?doc_id=28)。
## 3. Quant OS 如何使用这些数据
当前已经实现的链路是研究/引擎验证路径:
```text
Tushare mirror
-> 只读取 SQLite completed jobs
-> 文件大小 + SHA-256 + Parquet row count/footer 校验
-> 新目录原子发布 Qlib binary provider
-> provider tree hash + data_version + manifest verifier
-> Qlib momentum / Alpha158 research runner
```
正式 Quant OS 还需要第二条生产数据路径:
```text
完整 Tushare/JQData PIT 数据
-> canonical immutable snapshot
-> Quant OS 本地事件回测 / decision
-> 聚宽、QMT、Qlib 的同输入分层比较
```
Qlib 适合因子、模型和组合研究,不拥有原始数据,也不替代 Quant OS 的
Signal/Target/Order/Broker 合同。当前 Tushare adapter 只实现第一条链路;
在复权、真实 benchmark、历史成分、停牌、涨跌停和 ST 数据补齐前,不会把它
接入生产 decision。
## 4. 可运行命令
从 Quant OS 根目录执行。镜像路径只放在当前 shell 环境变量中,不写入 Git:
```bash
export QUANT_OS_ROOT=/path/to/quants-strategies/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
export PYTHONPATH=src:.
```
盘点并验证全部 completed 文件:
```bash
python tools/tushare_qlib.py inventory \
--mirror-root "$TUSHARE_MIRROR_ROOT" \
--output-json artifacts/tushare-inventory.json
```
当前未复权数据只能显式构建技术验证 provider;每次必须使用新目录,工具拒绝
覆盖已有 provider
```bash
python tools/tushare_qlib.py build \
--mirror-root "$TUSHARE_MIRROR_ROOT" \
--output-dir data/qlib/tushare-early-v1 \
--start 1990-12-19 \
--end 1993-10-29 \
--minimum-observations 60 \
--allow-unadjusted \
--ohlc-policy expand-range \
--output-json artifacts/tushare-qlib-build.json
python tools/tushare_qlib.py verify \
data/qlib/tushare-early-v1 \
--output-json artifacts/tushare-qlib-verify.json
```
运行 Qlib 0.9.7 本地回测:
```bash
PYTHONHASHSEED=0 python -m platforms.qlib_runner \
--provider-uri data/qlib/tushare-early-v1 \
--market tushare_a \
--benchmark SH999999 \
--start 1992-01-02 \
--end 1993-10-28 \
--feature-start 1991-01-02 \
--lookback 20 \
--topk 10 \
--n-drop 2 \
--rebalance weekly \
--output-json artifacts/tushare-qlib-momentum.json
```
Qlib simulator 会消费结束日后的下一 provider session,所以 backtest `end`
必须早于 provider 最后一个交易日。Quant OS 对受管 provider 会在 Qlib
初始化前检查 market、benchmark、起始边界和这个终点条件。
`artifacts/``data/qlib/` 均被 Git 忽略。原始 Parquet、token、绝对镜像
路径和回测临时产物不得提交到仓库。
## 5. 本次真实本地运行证据
最终 v4 provider
| 证据 | 值 |
| --- | --- |
| adapter source SHA-256 | `2228c23af176a0f2fcf311e88dca9bdc5d63fd36fa5a3c2d3e42808a405e7243` |
| data version | `3378aa75a5601bde476ad07bea90418966a66a037ca59195dec93d77b41cc3f8` |
| provider tree SHA-256 | `dc85b8daf692a66641afef64398700264c6054f1dac84b1211cb258a06b62948` |
| manifest SHA-256 | `862e7435bb0be7c8a3c66180e49761c110056c1a3cc02c1618e70d9eb566d945` |
| provider 文件/大小 | 975 / 1,085,886 bytes |
| calendar | 1990-12-19—1993-10-29731 sessions |
| market | 107 个至少有 60 条记录的 A 股代码 |
| selected rows | 27,998 |
回测参数为 1992-01-02—1993-10-28、20 日动量、周频、Top 10、每期 drop 2
初始资金 1,000 万,Qlib 0.9.7、Python 3.12.13、`PYTHONHASHSEED=0`
相同命令连续执行两次,结果 JSON 逐 byte 相同:
| 证据/指标 | 值 |
| --- | ---: |
| result JSON SHA-256 | `92ff9b8cea746e9c89ddf62fcfe3feb21248ca9112d9e10d24e0639058a86020` |
| runner source SHA-256 | `fa8118819252c55e99e67de356cc961d8a4a22f82256b4d62579a45426d96167` |
| signal | 4,700 行;hash `cfad4087d68b7f71e33f0f46fc5bb97db985e02ac2515f01857e2ab6813a5909` |
| portfolio report | 466 行;hash `5f8606fb64963d3e2618f66503e6ae94048c24b62c5d8e1806ff44ec57558b04` |
| strategy cumulative return | -52.5251% |
| max drawdown | -87.7604% |
| synthetic benchmark cumulative return | 196,302.0822% |
| total cost / turnover | 0.016013 / 29.567521 |
这些收益数字没有投资解释。原因包括未复权早期行情、没有真实指数 benchmark、
没有历史成分/ST/停牌/涨跌停、按全区间至少 60 条观测筛选带来的非 PIT
偏差,以及 1992—1993 特殊市场阶段。极端 synthetic benchmark 恰好说明
为什么“程序跑完”不能等价为“回测有效”。
## 6. 何时可以升级为正式研究数据
至少完成以下步骤后,才能新建 production-eligible provider
1. 修复下载器错误状态并完成目标日期的 `daily`
2. 补齐 `adj_factor`,冻结复权基准日和公式;
3. 使用 `index_daily` 替换合成 benchmark
4. 指数策略补齐 `index_member_all/index_weight`
5. 补齐 `suspend_d/stk_limit`,为历史 `stock_st` 找到授权来源或保持硬阻断;
6. 冻结下载 job、请求 hash、Parquet hash、转换器 hash 和新 `data_version`
7. 运行真实长样本 walk-forward/OOS,而不是复用本次早期 smoke;
8. 同一冻结输入进入 Quant OS 本地回测、聚宽和可用 QMT,生成 L1—L4
差异报告。
Qlib provider 的目录格式和缺失值/复权约定参见
[Qlib Data Layer 文档](https://qlib.readthedocs.io/en/latest/component/data.html)。