Files
quant-os/docs/TUSHARE_LOCAL_DATA.md
T

16 KiB
Raw Blame History

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

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 月内多次刷新同一个分区,所以 dailyadj_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_daily000905.SH 37 年 job 5,239 2004-12-31—2026-07-29 真实中证 500 benchmark
index_weight000905.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。

另外已有 incomebalancesheetcashflowfina_indicatordisclosure_datemoneyflow 等数据。它们可以用于下一阶段的基本面、质量、 资金流特征,但财务报表必须按公告/披露可得日做 as-of join,不能直接按报告期 回填到历史日期。

仍然存在的硬缺口

  • 2026-07-26 的权限探测明确显示 stock_stdenied。在获得授权历史 ST 源之前,不能声称完整复现 ST 股票的逐日交易限制;
  • live ledger 在本次点时仍有 24 个 running19 个 931632CNY04.CSIindex_daily 年任务和 5 个 931632.CSIindex_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 横截面研究:

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

第二条是本地事件回测输入:

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 作为 锚点:

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 线。

固定单位仍为:

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,并按整个 研究区间的最少观测数筛选股票。它只能用于转换器/运行时技术验证:

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。实际构建命令为:

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 核心输入 可以这样冻结:

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

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=false221 个 selected jobs 均完成两次 size/SHA-256/Parquet row-count 校验,验证后仍是各自 partition 的 latest,并与 provider 的 221 个 source jobs 全字段零差异。这里的证据边界 是“scoped jobs 稳定”,不是“整个 live DB/WAL 静止”。

scoped manifest 与 provider 的正式 lineage 由独立工具核对:

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=0converter_source_matches_current=true。lineage artifact 同样明确 investment_value_claim=falsegate_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 fail0 个 envelope anomalies
benchmark 真实 index_daily(000905.SH) / SH000905
release flags production_ready=falseinvestment_value_claim=falsegate_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

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 缺口没有因为已有 namechangestk_limit 就自动消失;
  • index_weight 没有单独验证的 published_at,因此这里不是严格 knowledge-time PIT
  • 复权以每个 symbol 在 provider 区间内的首个有效因子为锚。不同 start 构建的 provider 归一化基准不同,不能直接拼接其价格 level;需要统一起点 重建或显式重新归一化;
  • 必须做时间切分、walk-forward/OOS、参数稳定性、成本压力和跨引擎差异;
  • 本文不据此声称 Quant OS 已达到 60 分或 80 分标准。

相关字段语义参见 Tushare 日线接口复权因子接口Qlib Data Layer