Files
quant-os/docs/TUSHARE_LOCAL_DATA.md

330 lines
16 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 本地数据盘点、冻结边界与 Qlib 接入
点时证据:2026-07-31 00:40 CST。原先“93 个 completed 文件、日线只到
1993-10”的结论已经失效,不得再用于判断当前镜像。当前镜像约 7.7 GB,
SQLite job ledger 在该点时已有 378,707 条 `completed` 版本记录、约
1.80 亿行;同时仍有与本次中证 500 研究无关的指数任务处于 `running`
```text
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 = []
```
这意味着数据已经足够做一条真实的 2018—2025 本地 Qlib 研究链,但不能把
“live raw mirror 很大”直接等同于“已经有可复现、可用于发布结论的数据集”。
正确边界是三层:
| 层 | 本机位置/产物 | 用途 | 是否可变 |
| --- | --- | --- | --- |
| 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 的派生物,不是原始数据备份 |
`tools/tushare_qlib.py` 和冻结工具都只读镜像,不调用 Tushare,也不读取或保存
token。snapshot 工具不复制 Parquetmanifest 能在源文件被覆盖时检测不一致,
但若要长期从 raw bytes 重建,还需要后续接入 content-addressed archive。
原始 Parquet、绝对路径、账号信息和本地运行 artifacts 不进入 Git。
## 1. 当前真实覆盖
下面同时区分“自然分区文件”和 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 历史成分 |
`daily` 原计划覆盖 1990-12—2026-07,共 428 个月;当前完成
428 个月,约 100.0%。这里的
“完成”只指自然月文件覆盖,不代表 live mirror 已冻结,也不代表
production-ready。
另外已有 `income``balancesheet``cashflow``fina_indicator`
`disclosure_date``moneyflow` 等数据。它们可以用于下一阶段的基本面、质量、
资金流特征,但财务报表必须按公告/披露可得日做 as-of join,不能直接按报告期
回填到历史日期。
### 仍然存在的硬缺口
- 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。
## 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
raw_volume_in_shares = Tushare vol * 100
raw_amount_in_CNY = Tushare amount * 1000
```
### universe 与 benchmark 口径
- 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 接口
### 现有基线
仓库当前基线的 `build` 只消费 `daily/trade_cal/stock_basic`,必须显式传
`--allow-unadjusted`,会生成 synthetic equal-weight benchmark,并按整个
研究区间的最少观测数筛选股票。它只能用于转换器/运行时技术验证:
```bash
PYTHONPATH=src:. python tools/tushare_qlib.py build \
--mirror-root "$TUSHARE_MIRROR_ROOT" \
--output-dir data/qlib/tushare-legacy-2018-2025 \
--start 2018-01-01 \
--end 2025-12-31 \
--minimum-observations 60 \
--allow-unadjusted \
--ohlc-policy fail \
--output-json artifacts/tushare/legacy-build.json
```
不要用这个结果回答“中证 500 策略是否有效”,也不要给它任何发布门禁分数。
### 已验证的 v2 接口
v2 已消费 `adj_factor`、真实指数行情和历史指数权重,并完成 provider
构建、验证和两次本地 run。实际构建命令为:
```bash
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
```
这条命令不再传 `--allow-unadjusted`。provider manifest 必须记录复权口径、
真实 benchmark、时变 universe、所有 source job/file hash、转换器 hash、
质量统计和可重算 `data_version`
`tools/tushare_snapshot.py` 是独立的 raw scoped freeze 入口。Qlib 核心输入
可以这样冻结:
```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
```
默认会验证 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 通过:
| 证据 | 实测值 |
| --- | --- |
| 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-311,942 sessions |
| event-time PIT 近似 market | 1,111 个曾进入 `000905.SH` roster 的 instruments |
| snapshot coverage | 97 observed / 96 effective;最大日历间隔 36 天 |
| roster quality | 每个 observed snapshot 严格 500 constituentsweight 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=[]` |
周频 20 日动量参数为 2019-01-02—2025-12-30、Top 50、每期 drop 5、
初始资金 1,000 万、开仓费 0.03%、平仓费 0.08%、最低费用 5 元、
`PYTHONHASHSEED=0`。相同命令连续执行两次,两个 JSON 逐 byte 相同:
| 证据/指标 | 实测值 |
| --- | ---: |
| run JSON SHA-256 | `803035a95ee9cf6f90af20cdf7e7024149b97e0113b585209a80f80048be87d3` |
| signal rows | 176,615 |
| portfolio report rows | 1,698 |
| strategy cumulative return | 0.25315198638819725.3152% |
| benchmark cumulative return | 0.78955938353067878.9559% |
| max drawdown | -0.6041641365538415-60.4164% |
| total cost / turnover | 0.03579348112198462 / 65.51393782463258 |
这组数字证明真实 Tushare → event-time/保守次日生效 PIT 近似 universe →
复权 Qlib provider →
momentum runner 的链路可重放,不证明策略可投资。策略显著跑输 benchmark,
且最大回撤超过 60%,不能把正累计收益单独拿出来宣传。
最终身份变化来自 verifier/转换器源码绑定;provider tree 与回测数值未变。
verifier 已确认 recorded/current converter SHA-256 一致。由于 run evidence
绑定 provider identity,两个 run JSON 的 SHA-256 随之更新。
## 5. 验证顺序
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、成本/风险扰动和本地事件回测。
完整可复制命令见
[`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)。