feat: operationalize local Tushare Qlib research
This commit is contained in:
+21
-8
@@ -66,12 +66,13 @@ XtTrader:
|
||||
|
||||
## Data plane and point-in-time semantics
|
||||
|
||||
本地 Tushare 镜像是独立只读输入边界。adapter 只信任 SQLite 中 completed
|
||||
job,并重新核对文件大小、SHA-256、Parquet 行数/footer;Qlib provider
|
||||
发布到新目录,manifest 绑定 source job、build parameter、converter source、
|
||||
完整 provider tree 和可重算 `data_version`。当前镜像未完成且缺复权、
|
||||
真实 benchmark、历史成分/ST/停牌/涨跌停,因此只能进入 Qlib 技术 smoke,
|
||||
不能直接进入 canonical production decision。完整合同与实跑证据见
|
||||
本地 Tushare live mirror 是独立只读输入边界。scoped snapshot 在一次 SQLite
|
||||
读事务中选择目标日期/API/指数的 canonical completed job,并重新核对文件
|
||||
大小、SHA-256、Parquet 行数/footer;Qlib provider 发布到新目录,manifest
|
||||
绑定 source job、build parameter、converter source、完整 provider tree 和
|
||||
可重算 `data_version`。当前已有复权因子、真实 `000905.SH` benchmark/历史
|
||||
权重、停牌和涨跌停数据,但 live mirror 仍可变化,且 `stock_st` 权限被拒;
|
||||
因此必须先冻结、再验证,不能直接进入完整 canonical production decision。完整合同见
|
||||
[`TUSHARE_LOCAL_DATA.md`](TUSHARE_LOCAL_DATA.md)。
|
||||
|
||||
JQData adapter 的输入合同是逐交易日历史指数成员和 `get_bars` 日线字段:
|
||||
@@ -190,8 +191,20 @@ 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。
|
||||
- 旧 Tushare 早期未复权 managed provider/momentum smoke 在
|
||||
`PYTHONHASHSEED=0` 下连续两次得到逐 byte 相同的 evidence JSON;它只证明
|
||||
旧转换器和运行时可重放;
|
||||
- 2018—2025 v2 managed provider 已使用真实复权、`SH000905` benchmark 和
|
||||
PIT index-weight universe,通过 verifier;同一 momentum run 两次 JSON
|
||||
byte-identical。月末权重保守地从下一 provider 交易日生效,
|
||||
`same_session_membership_use=false`;97 个 observed snapshots 形成 96 个
|
||||
effective rosters,每个严格 500 成分、weight sum 100±0.5,最大间隔 36 天。
|
||||
live DB/WAL 可被无关下载任务修改,但 221 个 scoped jobs double verified、
|
||||
验证后仍为 latest;独立 lineage 工具对 221 jobs 的 6 个身份字段得到
|
||||
mismatch 0,并确认 converter source 为 current。它仍是
|
||||
event-time/保守次日生效 PIT 近似,不是严格 knowledge-time PIT;不同
|
||||
`start` 的首观测锚 provider 不可直接拼接。它仍是 research-only、
|
||||
`gate_credit=[]`,不表达历史 ST 和逐股逐日涨跌停。
|
||||
|
||||
该 fixture 只有两只股票和一个 benchmark,性能没有投资意义。Qlib 的单一
|
||||
`limit_threshold` 也不能表达逐日板块/ST 规则,所以它只参与 L1/L2 和诊断
|
||||
|
||||
@@ -19,7 +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;Baseline 60 至少 20 个交易日,Production 80 当前缺口为同一冻结候选至少 60 个交易日;not 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-count;v4 provider verifier 成功;同一动量命令连续两次 evidence JSON 逐 byte 相同 | `daily` 仅完成约 8.2%,缺复权、真实 benchmark、PIT 成分/ST/停牌/涨跌停;`production_ready=false` |
|
||||
| Tushare local → Qlib | live mirror 只读盘点、scoped release、不可变 provider 与本地研究 | 同上 + `pyarrow==24.0.0`;本地镜像 | selected jobs double verified、仍 latest;独立 lineage 验证 221 jobs×6 fields、mismatch 0、converter current true;v2 verifier 通过:1,111 instruments、1,942 sessions、1,975,455 rows;97 observed/96 effective snapshots 严格 500 成分、次日生效;双次 JSON byte-identical | event-time/保守次日生效 PIT 近似,不是严格 knowledge-time PIT;不同 start 的首观测锚 provider 不可直接拼接;research-only、`gate_credit=[]`;ST/统一 9.5% 仍缺 |
|
||||
| Mock parity exporter | JoinQuant/QMT wrapper 的 L2—L4 合同回归 | Python ≥3.10 | `mock_contract_only`、`real_platform_pass=false`、`gate_credit=[]` | 不计 G9;必须换成真实平台导出 |
|
||||
|
||||
G1—G10 当前都未通过。JoinQuant TargetPackage run 支持且只支持
|
||||
|
||||
@@ -8,3 +8,5 @@
|
||||
- 当前实现架构:[`../docs/ARCHITECTURE.md`](ARCHITECTURE.md)
|
||||
- 平台矩阵:[`PLATFORM_MATRIX.md`](PLATFORM_MATRIX.md)
|
||||
- Tushare 数据:[`TUSHARE_LOCAL_DATA.md`](TUSHARE_LOCAL_DATA.md)
|
||||
- Tushare → Qlib 本机运行:
|
||||
[`runbooks/TUSHARE_QLIB_LOCAL_RUN.md`](runbooks/TUSHARE_QLIB_LOCAL_RUN.md)
|
||||
|
||||
+276
-172
@@ -1,225 +1,329 @@
|
||||
# Tushare 本地数据盘点、Qlib 接入与回测手册
|
||||
# Tushare 本地数据盘点、冻结边界与 Qlib 接入
|
||||
|
||||
截至 2026-07-26,Quant OS 已能只读盘点另一个任务生成的 Tushare
|
||||
Parquet 镜像,校验已完成分区,并转换为不可变的 Qlib 0.9.7 provider。
|
||||
这条路径已经完成两次 byte-identical 的本地动量回测。
|
||||
|
||||
当前证据的边界是:
|
||||
点时证据:2026-07-31 00:40 CST。原先“93 个 completed 文件、日线只到
|
||||
1993-10”的结论已经失效,不得再用于判断当前镜像。当前镜像约 7.7 GB,
|
||||
SQLite job ledger 在该点时已有 378,707 条 `completed` 版本记录、约
|
||||
1.80 亿行;同时仍有与本次中证 500 研究无关的指数任务处于 `running`。
|
||||
|
||||
```text
|
||||
completed_partitions_only_not_full_mirror
|
||||
tushare_daily_completed_partitions = 428
|
||||
tushare_daily_planned_partitions = 428
|
||||
tushare_daily_completed_rows = 18,041,386
|
||||
tushare_daily_market_data_from = 1990-12-19
|
||||
tushare_daily_market_data_through = 2026-07-28
|
||||
production_ready = false
|
||||
investment_value_claim = false
|
||||
gate_credit = []
|
||||
```
|
||||
|
||||
它证明“本地 Tushare 数据可以进入 Qlib 并可重放”,不证明镜像已经下载完整,
|
||||
也不证明策略收益有效或 Quant OS 已达到 `BASELINE_60`。
|
||||
这意味着数据已经足够做一条真实的 2018—2025 本地 Qlib 研究链,但不能把
|
||||
“live raw mirror 很大”直接等同于“已经有可复现、可用于发布结论的数据集”。
|
||||
正确边界是三层:
|
||||
|
||||
## 1. 2026-07-26 点时盘点
|
||||
| 层 | 本机位置/产物 | 用途 | 是否可变 |
|
||||
| --- | --- | --- | --- |
|
||||
| live raw mirror | `$TUSHARE_MIRROR_ROOT` | 下载器持续落盘的 Parquet、权限探测和 job ledger | 是;不能直接作为可重放 run 的唯一引用 |
|
||||
| scoped frozen release | `tools/tushare_snapshot.py` 生成的范围化 manifest | 固定日期、API、指数代码、所选 job/file hash 和选择规则 | manifest 不变;引用的 raw bytes 仍需留存 |
|
||||
| Qlib provider | `data/qlib/...` + provider manifest | Qlib 0.9.7 可直接读取的 calendar/instruments/features | 否;是 frozen release 的派生物,不是原始数据备份 |
|
||||
|
||||
镜像中共有 93 个状态为 `completed` 的 Parquet,均通过 SQLite 记录的文件大小、
|
||||
SHA-256 和 Parquet row count 校验;另有 1 个遗留 `running` job。
|
||||
`tools/tushare_qlib.py` 和冻结工具都只读镜像,不调用 Tushare,也不读取或保存
|
||||
token。snapshot 工具不复制 Parquet;manifest 能在源文件被覆盖时检测不一致,
|
||||
但若要长期从 raw bytes 重建,还需要后续接入 content-addressed archive。
|
||||
原始 Parquet、绝对路径、账号信息和本地运行 artifacts 不进入 Git。
|
||||
|
||||
| 表 | 文件数 | 行数 | 当前范围/说明 |
|
||||
| --- | ---: | ---: | --- |
|
||||
| `daily` | 35 | 28,731 | 1990-12-19—1993-10-29,138 个代码、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** |
|
||||
## 1. 当前真实覆盖
|
||||
|
||||
`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
|
||||
转换过程中修改或自动恢复原下载器。
|
||||
下面同时区分“自然分区文件”和 SQLite 的“completed 版本记录”。下载器在
|
||||
2026-07 月内多次刷新同一个分区,所以 `daily` 和 `adj_factor` 都是 428 个
|
||||
自然月文件,却各有 430 条 completed 版本记录。job 行数求和会重复计算旧版
|
||||
2026-07,不能当作当前文件行数。
|
||||
|
||||
尚未落盘的生产关键表包括:
|
||||
| 数据 | 当前自然文件 | 当前行数 | 实际可见范围 | 可直接支持什么 |
|
||||
| --- | ---: | ---: | --- | --- |
|
||||
| `daily` | 428 | 18,041,386 | 1990-12-19—2026-07-28 | OHLC、前收、成交量额、收益与本地撮合输入 |
|
||||
| `adj_factor` | 428 | 18,868,457 | 1990-12—2026-07;最大 `trade_date=20260729` | 区间内累计复权 |
|
||||
| `trade_cal` | 39 completed 版本 | 13,423 | SSE 1990—2026 | 交易日历与完整性检查 |
|
||||
| `stock_basic` | 4 | 5,869 | 当前上市、退市、暂停上市等快照 | 代码、上市/退市日期;不是 PIT 成分表 |
|
||||
| `daily_basic` | 331 | 17,335,584 | 1999-01-04—2026-07-28 | 市值、换手、估值与横截面特征 |
|
||||
| `stk_limit` | 235 | 17,878,521 | 2007-01-04—2026-07-29 | 逐股逐日真实涨跌停价格 |
|
||||
| `suspend_d` | 37 | 641,379 | 非空记录 1999-05-04—2026-07-29 | 停复牌与可交易性 |
|
||||
| `index_daily`,`000905.SH` | 37 年 job | 5,239 | 2004-12-31—2026-07-29 | 真实中证 500 benchmark |
|
||||
| `index_weight`,`000905.SH` | 37 年 job | 129,000 | 2005-01-31—2026-06-30 | 中证 500 的历史定期成分/权重快照 |
|
||||
| `index_member_all` | 1 | 3,000 | 申万行业层级及进出日期 | 行业分类;不能冒充中证 500 历史成分 |
|
||||
|
||||
- `adj_factor`、`index_daily`;
|
||||
- `index_member_all`、`index_weight`;
|
||||
- `stk_limit`、`suspend_d`、`stock_st`;
|
||||
- `daily_basic`。
|
||||
`daily` 原计划覆盖 1990-12—2026-07,共 428 个月;当前完成
|
||||
428 个月,约 100.0%。这里的
|
||||
“完成”只指自然月文件覆盖,不代表 live mirror 已冻结,也不代表
|
||||
production-ready。
|
||||
|
||||
其中历史 `stock_st` 权限探测被拒绝。即使其他下载完成,历史 ST 仍需要新的
|
||||
授权数据源或明确的降级门禁。
|
||||
另外已有 `income`、`balancesheet`、`cashflow`、`fina_indicator`、
|
||||
`disclosure_date`、`moneyflow` 等数据。它们可以用于下一阶段的基本面、质量、
|
||||
资金流特征,但财务报表必须按公告/披露可得日做 as-of join,不能直接按报告期
|
||||
回填到历史日期。
|
||||
|
||||
## 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。
|
||||
- 2026-07-26 的权限探测明确显示 `stock_st` 为 `denied`。在获得授权历史
|
||||
ST 源之前,不能声称完整复现 ST 股票的逐日交易限制;
|
||||
- live ledger 在本次点时仍有 24 个 `running`:19 个
|
||||
`931632CNY04.CSI` 的 `index_daily` 年任务和 5 个 `931632.CSI` 的
|
||||
`index_weight` 年任务。它们与 `000905.SH` 的 2018—2025 release 无关,
|
||||
但说明全镜像仍是移动目标;
|
||||
- `daily` 的最新 completed 请求元数据包含 `20260729`,但当前 Parquet
|
||||
的最大实际日期是 `20260728`;任何冻结工具都必须验证 Parquet 内容,而不能
|
||||
只相信请求参数或 job 状态;
|
||||
- 多个 completed 版本可指向同一个最终路径。冻结时必须按明确规则选择当前
|
||||
canonical 版本并复核 size/SHA-256/Parquet row count,不能把所有
|
||||
`completed` 盲目拼接;
|
||||
- `stock_basic` 是当前快照,不得用它反向构造历史指数 universe。
|
||||
|
||||
Tushare 的 `daily` 是未复权行情,停牌期间不返回记录;`vol` 单位为手,
|
||||
`amount` 单位为千元。转换器固定执行:
|
||||
## 2. 数据如何进入 Quant OS
|
||||
|
||||
第一条可用研究路径是中证 500 横截面研究:
|
||||
|
||||
```text
|
||||
daily + adj_factor + trade_cal
|
||||
+ index_daily(000905.SH)
|
||||
+ index_weight(000905.SH)
|
||||
-> scoped frozen release (2018-01-01—2025-12-31)
|
||||
-> managed Qlib provider
|
||||
-> momentum / Alpha158 / LightGBM
|
||||
-> evidence JSON + provider verifier
|
||||
```
|
||||
|
||||
第二条是本地事件回测输入:
|
||||
|
||||
```text
|
||||
raw daily/pre_close + stk_limit + suspend_d
|
||||
+ PIT universe + signal/target
|
||||
-> Quant OS reference execution
|
||||
-> ledger / replay / platform parity
|
||||
```
|
||||
|
||||
两条路径不能混为一谈。Qlib 负责因子、模型和组合研究;Quant OS 的
|
||||
Signal/Target/Order/Broker 合同、涨跌停、停牌、T+1、成交和对账仍由本地
|
||||
事件链负责。历史 ST 未补齐时,本地执行链必须保留显式缺口或 fail-closed,
|
||||
不能静默按普通股票处理后再把结果标为完整市场仿真。
|
||||
|
||||
### 复权口径
|
||||
|
||||
新 provider 对每个 symbol 使用 release 区间内第一个有效 `adj_factor` 作为
|
||||
锚点:
|
||||
|
||||
```text
|
||||
factor_t = adj_factor_t / first_valid_adj_factor
|
||||
adjusted_OHLC_t = raw_OHLC_t * factor_t
|
||||
adjusted_volume_t = raw_volume_in_shares_t / factor_t
|
||||
money_t = raw_amount_in_CNY_t
|
||||
```
|
||||
|
||||
这是“首个有效因子锚定的区间累计复权”,文档和证据中不要把它简写成
|
||||
`qfq`。不用区间末因子做归一化,是为了避免研究区间末端信息参与早期价格的
|
||||
缩放。停牌缺口保留为 `NaN`,不会伪造 K 线。
|
||||
|
||||
固定单位仍为:
|
||||
|
||||
```text
|
||||
600000.SH -> SH600000
|
||||
000001.SZ -> SZ000001
|
||||
430047.BJ -> BJ430047
|
||||
volume = vol * 100
|
||||
money = amount * 1000
|
||||
factor = 1
|
||||
raw_volume_in_shares = Tushare vol * 100
|
||||
raw_amount_in_CNY = Tushare amount * 1000
|
||||
```
|
||||
|
||||
未上市、退市后和停牌缺口在 Qlib feature 中保留为 `NaN`。字段语义来源见
|
||||
[Tushare 日线接口](https://tushare.pro/document/2?doc_id=27)与
|
||||
[复权因子接口](https://tushare.pro/document/2?doc_id=28)。
|
||||
### universe 与 benchmark 口径
|
||||
|
||||
## 3. Quant OS 如何使用这些数据
|
||||
- benchmark 使用 `index_daily(000905.SH)`,Qlib symbol 为 `SH000905`;
|
||||
- universe 使用 `index_weight(000905.SH)` 的历史快照,并按“只向未来
|
||||
carry-forward、绝不从未来 backfill、月末权重下一交易日生效”生成时变
|
||||
成分区间。这是 event-time / 保守次日生效 PIT 近似;源数据没有单独验证的
|
||||
`published_at`,不能声称严格 knowledge-time PIT;
|
||||
- `index_member_all` 是申万行业数据,不参与中证 500 universe;
|
||||
- 研究报告必须分别披露 benchmark、universe、价格复权和可交易性过滤来源。
|
||||
|
||||
当前已经实现的链路是研究/引擎验证路径:
|
||||
## 3. 现有代码基线与 v2 接口
|
||||
|
||||
```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:
|
||||
仓库当前基线的 `build` 只消费 `daily/trade_cal/stock_basic`,必须显式传
|
||||
`--allow-unadjusted`,会生成 synthetic equal-weight benchmark,并按整个
|
||||
研究区间的最少观测数筛选股票。它只能用于转换器/运行时技术验证:
|
||||
|
||||
```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
|
||||
export PYTHONPATH=src:.
|
||||
```
|
||||
|
||||
盘点并验证全部 completed 文件:
|
||||
|
||||
```bash
|
||||
python tools/tushare_qlib.py inventory \
|
||||
PYTHONPATH=src:. python tools/tushare_qlib.py build \
|
||||
--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 \
|
||||
--output-dir data/qlib/tushare-legacy-2018-2025 \
|
||||
--start 2018-01-01 \
|
||||
--end 2025-12-31 \
|
||||
--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
|
||||
--ohlc-policy fail \
|
||||
--output-json artifacts/tushare/legacy-build.json
|
||||
```
|
||||
|
||||
运行 Qlib 0.9.7 本地回测:
|
||||
不要用这个结果回答“中证 500 策略是否有效”,也不要给它任何发布门禁分数。
|
||||
|
||||
### 已验证的 v2 接口
|
||||
|
||||
v2 已消费 `adj_factor`、真实指数行情和历史指数权重,并完成 provider
|
||||
构建、验证和两次本地 run。实际构建命令为:
|
||||
|
||||
```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
|
||||
PYTHONPATH=src:. 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
|
||||
```
|
||||
|
||||
Qlib simulator 会消费结束日后的下一 provider session,所以 backtest `end`
|
||||
必须早于 provider 最后一个交易日。Quant OS 对受管 provider 会在 Qlib
|
||||
初始化前检查 market、benchmark、起始边界和这个终点条件。
|
||||
这条命令不再传 `--allow-unadjusted`。provider manifest 必须记录复权口径、
|
||||
真实 benchmark、时变 universe、所有 source job/file hash、转换器 hash、
|
||||
质量统计和可重算 `data_version`。
|
||||
|
||||
`artifacts/` 与 `data/qlib/` 均被 Git 忽略。原始 Parquet、token、绝对镜像
|
||||
路径和回测临时产物不得提交到仓库。
|
||||
`tools/tushare_snapshot.py` 是独立的 raw scoped freeze 入口。Qlib 核心输入
|
||||
可以这样冻结:
|
||||
|
||||
## 5. 本次真实本地运行证据
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
最终 v4 provider:
|
||||
默认会验证 size、SHA-256 和 Parquet row count。需要研究
|
||||
`daily_basic/stk_limit/suspend_d` 时应再冻结一个明确包含它们的扩展
|
||||
release;不要把未列入 manifest 的表暗中加入既有 run。当前 `build` 仍从
|
||||
mirror 重新选择 source jobs,并不接受这份 manifest 作为参数,因此 run
|
||||
验收还必须比较 provider manifest 与 scoped manifest 的 source
|
||||
job/file hashes。
|
||||
|
||||
| 证据 | 值 |
|
||||
snapshot 工具在请求 `index_weight` 时会自动包含 `start.year-1` 的 pre-start
|
||||
PIT anchor。本次 scoped manifest 与 provider 均为 221 个 source jobs,
|
||||
全字段比对零差异,已经覆盖有效 roster 日期 `2017-12-29`:
|
||||
|
||||
```text
|
||||
scoped_manifest_sha256 = 744a69828f527594e9f2787280095368c50deffa2f269c359ae65e2e0ac2d5f2
|
||||
scoped_selection_sha256 = 66e91fffecba5fa042922c49e339f24212c3febf5b874279866fdca660f54fb5
|
||||
scoped_source_jobs = 221
|
||||
provider_source_jobs = 221
|
||||
source_job_full_field_diff = 0
|
||||
provider_source_jobs_before_read = 221
|
||||
provider_source_jobs_after_read = 221
|
||||
provider_selected_jobs_still_latest = true
|
||||
provider_post_read_file_verification = true
|
||||
```
|
||||
|
||||
冻结期间 SQLite 主文件/WAL 仍被无关下载任务更新:
|
||||
`changed_through_file_verification=true`。这不是 selected scope 未冻结:
|
||||
SQLite 事务读取期间 `changed_while_reading=false`,221 个 selected jobs
|
||||
均完成两次 size/SHA-256/Parquet row-count 校验,验证后仍是各自 partition
|
||||
的 latest,并与 provider 的 221 个 source jobs 全字段零差异。这里的证据边界
|
||||
是“scoped jobs 稳定”,不是“整个 live DB/WAL 静止”。
|
||||
|
||||
scoped manifest 与 provider 的正式 lineage 由独立工具核对:
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
结果为 221 个 jobs、6 个逐 job 字段
|
||||
`id/path/row_count/byte_count/sha256/request_sha256` 全部一致,
|
||||
`mismatch_count=0`、`converter_source_matches_current=true`。lineage artifact
|
||||
同样明确 `investment_value_claim=false`、`gate_credit=[]`。
|
||||
|
||||
## 4. 2018—2025 provider 与双次回测实证
|
||||
|
||||
managed provider 已构建在新目录,并由独立 verifier 通过:
|
||||
|
||||
| 证据 | 实测值 |
|
||||
| --- | --- |
|
||||
| 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-29,731 sessions |
|
||||
| market | 107 个至少有 60 条记录的 A 股代码 |
|
||||
| selected rows | 27,998 |
|
||||
| data version | `5bf19d2da064357ad1802bca4bfa0c0505ed63fe2b24785ec6c56ecff1724963` |
|
||||
| provider tree SHA-256 | `f173b8095fd9a62a63807324fa48bad83f0c282eea3abba871d0b0efd30b199f` |
|
||||
| provider manifest SHA-256 | `27fb6fedb6a4b114eae2aba44505fb6c4d32c7a9ae81e58c724dfadb8dd10ed0` |
|
||||
| provider files / bytes | 10,011 / 71,707,194 |
|
||||
| source stability | 221/221 jobs 在消费前后稳定;读取后重新校验文件且仍为 latest |
|
||||
| calendar | 2018-01-02—2025-12-31,1,942 sessions |
|
||||
| event-time PIT 近似 market | 1,111 个曾进入 `000905.SH` roster 的 instruments |
|
||||
| snapshot coverage | 97 observed / 96 effective;最大日历间隔 36 天 |
|
||||
| roster quality | 每个 observed snapshot 严格 500 constituents;weight sum 容差 ±0.5 |
|
||||
| availability | 月末权重从下一 provider 交易日生效;`same_session_membership_use=false` |
|
||||
| selected daily rows | 1,975,455 |
|
||||
| price adjustment | 完整;0 个缺失 daily/adj keys;首个有效因子锚定 |
|
||||
| OHLC quality | `--ohlc-policy fail`;0 个 envelope anomalies |
|
||||
| benchmark | 真实 `index_daily(000905.SH)` / `SH000905` |
|
||||
| release flags | `production_ready=false`、`investment_value_claim=false`、`gate_credit=[]` |
|
||||
|
||||
回测参数为 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 相同:
|
||||
周频 20 日动量参数为 2019-01-02—2025-12-30、Top 50、每期 drop 5、
|
||||
初始资金 1,000 万、开仓费 0.03%、平仓费 0.08%、最低费用 5 元、
|
||||
`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 |
|
||||
| run JSON SHA-256 | `803035a95ee9cf6f90af20cdf7e7024149b97e0113b585209a80f80048be87d3` |
|
||||
| signal rows | 176,615 |
|
||||
| portfolio report rows | 1,698 |
|
||||
| strategy cumulative return | 0.253151986388197(25.3152%) |
|
||||
| benchmark cumulative return | 0.789559383530678(78.9559%) |
|
||||
| max drawdown | -0.6041641365538415(-60.4164%) |
|
||||
| total cost / turnover | 0.03579348112198462 / 65.51393782463258 |
|
||||
|
||||
这些收益数字没有投资解释。原因包括未复权早期行情、没有真实指数 benchmark、
|
||||
没有历史成分/ST/停牌/涨跌停、按全区间至少 60 条观测筛选带来的非 PIT
|
||||
偏差,以及 1992—1993 特殊市场阶段。极端 synthetic benchmark 恰好说明
|
||||
为什么“程序跑完”不能等价为“回测有效”。
|
||||
这组数字证明真实 Tushare → event-time/保守次日生效 PIT 近似 universe →
|
||||
复权 Qlib provider →
|
||||
momentum runner 的链路可重放,不证明策略可投资。策略显著跑输 benchmark,
|
||||
且最大回撤超过 60%,不能把正累计收益单独拿出来宣传。
|
||||
|
||||
## 6. 何时可以升级为正式研究数据
|
||||
最终身份变化来自 verifier/转换器源码绑定;provider tree 与回测数值未变。
|
||||
verifier 已确认 recorded/current converter SHA-256 一致。由于 run evidence
|
||||
绑定 provider identity,两个 run JSON 的 SHA-256 随之更新。
|
||||
|
||||
至少完成以下步骤后,才能新建 production-eligible provider:
|
||||
## 5. 验证顺序
|
||||
|
||||
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
|
||||
差异报告。
|
||||
1. 对 live mirror 做快速元数据盘点,确认目标 API 和 `000905.SH` 范围;
|
||||
2. 生成 2018—2025 scoped frozen release,并验证所选文件的 size、
|
||||
SHA-256、Parquet footer/row count、自然键和日期边界;
|
||||
3. 在冻结后从同一 source state 构建新 provider,绝不覆盖已有目录,并核对
|
||||
provider/scoped manifest 的 source hashes;
|
||||
4. 运行 `tools/tushare_qlib.py verify`,核对 manifest、tree hash、
|
||||
calendar、instrument 和 feature 文件;
|
||||
5. 运行同一条 Qlib momentum 命令两次,比较 evidence JSON 的关键输入、
|
||||
signal/report hash 和重算指标;
|
||||
6. 再进入 walk-forward、Alpha158/LightGBM、成本/风险扰动和本地事件回测。
|
||||
|
||||
Qlib provider 的目录格式和缺失值/复权约定参见
|
||||
[Qlib Data Layer 文档](https://qlib.readthedocs.io/en/latest/component/data.html)。
|
||||
完整可复制命令见
|
||||
[`runbooks/TUSHARE_QLIB_LOCAL_RUN.md`](runbooks/TUSHARE_QLIB_LOCAL_RUN.md)。
|
||||
|
||||
## 6. 结果边界
|
||||
|
||||
2018—2025 的真实行情、复权、指数行情和历史权重会让回测比旧的
|
||||
1990—1993 technical smoke 有意义得多,但仍只是一条研究证据:
|
||||
|
||||
- momentum smoke 的收益不能证明投资价值;
|
||||
- Qlib 的成交模型不能替代 A 股逐股涨跌停、停牌、T+1 和真实容量仿真;
|
||||
- 本次 Qlib run 使用统一 `limit_threshold=0.095`,只是 9.5% 市场级近似;
|
||||
它没有消费已有 `stk_limit` 的逐股逐日价格,也不能表达不同板块、日期和
|
||||
ST 股票的真实限制;
|
||||
- `stock_st` 缺口没有因为已有 `namechange` 或 `stk_limit` 就自动消失;
|
||||
- `index_weight` 没有单独验证的 `published_at`,因此这里不是严格
|
||||
knowledge-time PIT;
|
||||
- 复权以每个 symbol 在 provider 区间内的首个有效因子为锚。不同 `start`
|
||||
构建的 provider 归一化基准不同,不能直接拼接其价格 level;需要统一起点
|
||||
重建或显式重新归一化;
|
||||
- 必须做时间切分、walk-forward/OOS、参数稳定性、成本压力和跨引擎差异;
|
||||
- 本文不据此声称 Quant OS 已达到 60 分或 80 分标准。
|
||||
|
||||
相关字段语义参见
|
||||
[Tushare 日线接口](https://tushare.pro/document/2?doc_id=27)、
|
||||
[复权因子接口](https://tushare.pro/document/2?doc_id=28)和
|
||||
[Qlib Data Layer](https://qlib.readthedocs.io/en/latest/component/data.html)。
|
||||
|
||||
@@ -19,13 +19,14 @@ Investment value claim: false
|
||||
独立仓:
|
||||
|
||||
```text
|
||||
~/boat-workspace/Code/quant-os
|
||||
$QUANT_OS_ROOT
|
||||
```
|
||||
|
||||
推荐 Python 3.12:
|
||||
|
||||
```bash
|
||||
cd ~/boat-workspace/Code/quant-os
|
||||
export QUANT_OS_ROOT=/path/to/quant-os
|
||||
cd "$QUANT_OS_ROOT"
|
||||
python3.12 -m venv .venv-core
|
||||
source .venv-core/bin/activate
|
||||
python -m pip install -e .
|
||||
@@ -36,7 +37,7 @@ python -m pip install -e .
|
||||
`quant-os` 应在 checkout 内运行;从其他目录调用新命令时显式写:
|
||||
|
||||
```bash
|
||||
quant-os --project-root ~/boat-workspace/Code/quant-os doctor
|
||||
quant-os --project-root "$QUANT_OS_ROOT" doctor
|
||||
```
|
||||
|
||||
wheel 不是独立的数据/runtime 分发包。
|
||||
@@ -117,16 +118,29 @@ quant-os verify-research-manifest artifacts/research-smoke/manifest.json
|
||||
|
||||
## 5. Tushare 数据与 Qlib
|
||||
|
||||
状态:`[已实现到技术 smoke|真实镜像仍不完整]`
|
||||
状态:`[v2 provider/run 已验证|research-only]`
|
||||
|
||||
先阅读 [`../TUSHARE_LOCAL_DATA.md`](../TUSHARE_LOCAL_DATA.md)。当前盘点表明
|
||||
completed Parquet 能校验并转换为 managed Qlib provider,但缺复权、真实
|
||||
benchmark、历史成分、ST、停牌和涨跌停,不能进入生产 decision。
|
||||
先阅读 [`../TUSHARE_LOCAL_DATA.md`](../TUSHARE_LOCAL_DATA.md) 和
|
||||
[`../runbooks/TUSHARE_QLIB_LOCAL_RUN.md`](../runbooks/TUSHARE_QLIB_LOCAL_RUN.md)。
|
||||
当前镜像已有复权因子、真实中证 500 benchmark/历史权重、停牌和涨跌停;
|
||||
raw mirror 仍可变化,历史 `stock_st` 权限被拒,所以必须先冻结 scoped
|
||||
release,再构建、验证 provider。2018—2025 provider 和双次 momentum run
|
||||
已经完成;月末指数权重保守地从下一 provider 交易日生效,不做 same-session
|
||||
使用,97 个 observed snapshots 形成 96 个 effective rosters,均严格 500
|
||||
成分、weight sum 在 100±0.5 内、最大间隔 36 天。但统一 9.5% 涨跌停近似和
|
||||
ST 缺口仍使其不能进入完整 production decision,`gate_credit=[]`。
|
||||
冻结期间全 DB/WAL 可能因无关下载任务变化;验收看的是 221 个 scoped jobs
|
||||
double verified、验证后仍为 latest;再用 `tools/tushare_lineage.py` 核对
|
||||
221 jobs 的 6 个身份字段,要求 mismatch 0 且 converter current true。
|
||||
历史权重没有单独验证的 `published_at`,所以 universe 是 event-time/保守
|
||||
次日生效 PIT 近似,不是严格 knowledge-time PIT。复权使用首观测锚;不同
|
||||
`start` 构建的 provider 不能直接拼接价格 level。
|
||||
|
||||
典型流程:
|
||||
|
||||
```bash
|
||||
PYTHONPATH=src:. python tools/tushare_qlib.py inventory --help
|
||||
python tools/tushare_snapshot.py --help
|
||||
PYTHONPATH=src:. python tools/tushare_qlib.py build --help
|
||||
PYTHONPATH=src:. python tools/tushare_qlib.py verify --help
|
||||
PYTHONPATH=src:. python -m platforms.qlib_runner --help
|
||||
|
||||
@@ -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