qlib-sync 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/PKG-INFO +29 -15
  2. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/README.md +28 -14
  3. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/README.zh-CN.md +16 -10
  4. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/mcpb/manifest.json +1 -1
  5. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/mcpb/pyproject.toml +2 -2
  6. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/pyproject.toml +1 -1
  7. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/server.json +18 -4
  8. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/skills/qlib-sync/SKILL.md +11 -7
  9. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/src/qlib_sync/__init__.py +1 -1
  10. qlib_sync-0.2.0/src/qlib_sync/_upstream/LICENSE +201 -0
  11. qlib_sync-0.2.0/src/qlib_sync/_upstream/__init__.py +14 -0
  12. qlib_sync-0.2.0/src/qlib_sync/_upstream/validate_archive.py +288 -0
  13. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/src/qlib_sync/api.py +5 -1
  14. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/src/qlib_sync/cn/market.py +24 -7
  15. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/src/qlib_sync/cn/update.py +202 -149
  16. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/src/qlib_sync/mcp_server.py +5 -4
  17. qlib_sync-0.2.0/tests/release_fixture.py +131 -0
  18. qlib_sync-0.2.0/tests/test_api.py +22 -0
  19. qlib_sync-0.2.0/tests/test_cn_market.py +110 -0
  20. qlib_sync-0.2.0/tests/test_cn_update.py +592 -0
  21. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/tests/test_mcp_server.py +13 -6
  22. qlib_sync-0.2.0/tests/test_upstream_parity.py +148 -0
  23. qlib_sync-0.1.0/tests/test_api.py +0 -35
  24. qlib_sync-0.1.0/tests/test_cn_market.py +0 -544
  25. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/.gitignore +0 -0
  26. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/LICENSE +0 -0
  27. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/docs/scheduling.md +0 -0
  28. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/mcpb/.mcpbignore +0 -0
  29. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/mcpb/src/server.py +0 -0
  30. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/src/qlib_sync/cli.py +0 -0
  31. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/src/qlib_sync/cn/__init__.py +0 -0
  32. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/src/qlib_sync/txn.py +0 -0
  33. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/tests/test_cli.py +0 -0
  34. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/tests/test_release_metadata.py +0 -0
  35. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/tests/test_txn.py +0 -0
  36. {qlib_sync-0.1.0 → qlib_sync-0.2.0}/tools/demo/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: qlib-sync
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Safely install and update a local Qlib China A-share dataset, with an MCP server for AI assistants.
5
5
  Project-URL: Homepage, https://github.com/randyy179/qlib-sync
6
6
  Project-URL: Issues, https://github.com/randyy179/qlib-sync/issues
@@ -46,7 +46,9 @@ pip install qlib-sync
46
46
  qlib-sync cn update
47
47
  ```
48
48
 
49
- Or without installing anything first: `uvx qlib-sync cn update`.
49
+ Or without installing anything first: `uvx --python 3.12 qlib-sync cn update`.
50
+ (`--python 3.12` because pyqlib publishes wheels up to Python 3.12; uv fetches that Python
51
+ for you if needed. With pip, use Python 3.10 to 3.12.)
50
52
 
51
53
  ![qlib-sync updating the dataset: status, download with progress, checksum, summary, then an instant no-op](https://raw.githubusercontent.com/randyy179/qlib-sync/main/docs/assets/demo-update.gif)
52
54
 
@@ -70,9 +72,15 @@ That works until it doesn't:
70
72
  - **Every check downloads 550 MB**, even when nothing changed.
71
73
 
72
74
  The investment_data README itself describes a careful manual process: download the release
73
- manifest, check the archive with its `validate_archive.py`, then extract. qlib-sync runs the
74
- same checks automatically on every update and adds what a manual process cannot: it skips the
75
- download when nothing changed, extracts beside the live data and swaps it in with rollback
75
+ manifest, check the archive with `qlib/validate_archive.py --require-publishable`, then
76
+ extract. qlib-sync runs that validator itself, on every update: an unmodified copy (from
77
+ [commit 68fdc66](https://github.com/chenditc/investment_data/blob/68fdc66b109459e522050c94723684525aea23ab/qlib/validate_archive.py),
78
+ Apache-2.0) checks the manifest before the download and the archive after it. A test suite
79
+ feeds the same malformed releases to the upstream script and to qlib-sync and requires the same
80
+ verdict, and a weekly job flags any change upstream makes to the validator.
81
+
82
+ On top of that, qlib-sync skips the download when nothing changed, pins the manifest and the
83
+ archive to one release tag, extracts beside the live data and swaps it in with rollback
76
84
  (rather than extracting on top of the existing folder), and picks up republished releases.
77
85
 
78
86
  qlib-sync treats the dataset like a production system:
@@ -84,17 +92,22 @@ qlib-sync treats the dataset like a production system:
84
92
 
85
93
  | Step | What happens |
86
94
  |---|---|
87
- | Check | Reads the release's 500-byte manifest. Already current: done, nothing downloaded. |
95
+ | Check | Pins the latest release by tag and reads its 500-byte manifest. Already current: done, nothing downloaded. |
88
96
  | Download | Resumes interrupted downloads; shows progress; optional mirror for slow links to GitHub. |
89
- | Verify | Size and sha256 from the manifest; the same release checks as upstream's `validate_archive.py` (every index pool and the future calendar reach the manifest's dates, release marked publishable); archive paths and links are checked before extraction. |
97
+ | Verify | investment_data's own `validate_archive.py --require-publishable`, unmodified: manifest fields and formats, size and sha256, every required calendar and index pool, date formats, ordering, and the future calendar. Anything it rejects is refused. |
90
98
  | Stage | Extracts next to the active dataset; refuses downgrades. |
91
99
  | Swap | Renames the new dataset into place, then runs a real Qlib read. |
92
100
  | Roll back | Any failure after the swap moves the previous dataset back. |
93
101
  | Report | New trading days, newly listed and delisted symbols. |
94
102
 
95
- The checks catch broken, truncated, or tampered packages. They cannot catch wrong values inside a
96
- correctly published release (like the one in #163); for that, keep a rollback copy with
97
- `--keep-backup`.
103
+ **Verification is required by default.** If the manifest is missing, unreadable, or rejected by
104
+ the validator, the update stops and your data stays as it was. `--allow-unverified` installs
105
+ anyway when the manifest cannot be verified, and the result and install record are then marked
106
+ unverified; a package whose checksum disagrees with a valid manifest is always refused.
107
+
108
+ The checks catch broken, truncated, tampered, or malformed packages. They cannot catch wrong
109
+ values inside a correctly published release (like the one in #163); for that, keep a rollback
110
+ copy with `--keep-backup`.
98
111
 
99
112
  ## Commands
100
113
 
@@ -115,6 +128,7 @@ Useful options for `cn update`:
115
128
  | `--keep-backup` | Keep the previous dataset for one rollback |
116
129
  | `--protect FILE` | Carry a custom stock pool in `instruments/` across updates (repeatable) |
117
130
  | `--force` | Reinstall even when the dates match |
131
+ | `--allow-unverified` | Install when the manifest cannot be verified (marked unverified; checksum mismatches are still refused) |
118
132
 
119
133
  **The update replaces the whole `cn_data` folder.** Anything you added there is lost unless you
120
134
  pass it with `--protect`.
@@ -150,17 +164,17 @@ Every client needs the same two things: the command `uvx` and the arguments `qli
150
164
  | Client | How to add qlib-sync |
151
165
  |---|---|
152
166
  | Claude Desktop | Download `qlib-sync.mcpb` from the [latest release](https://github.com/randyy179/qlib-sync/releases/latest) and open it |
153
- | Claude Code | `claude mcp add qlib-sync -- uvx qlib-sync mcp` |
154
- | OpenAI Codex CLI | `codex mcp add qlib-sync -- uvx qlib-sync mcp` |
167
+ | Claude Code | `claude mcp add qlib-sync -- uvx --python 3.12 qlib-sync mcp` |
168
+ | OpenAI Codex CLI | `codex mcp add qlib-sync -- uvx --python 3.12 qlib-sync mcp` |
155
169
  | Cursor | The JSON below, in `~/.cursor/mcp.json` |
156
170
  | Gemini CLI | The JSON below, in `~/.gemini/settings.json` |
157
171
  | Qwen Code | The JSON below, in `~/.qwen/settings.json` |
158
172
  | Tencent CodeBuddy, ByteDance Trae, Alibaba Tongyi Lingma | MCP settings, add a server manually, paste the JSON below |
159
- | Cherry Studio | Settings, MCP servers, add: type STDIO, command `uvx`, arguments `qlib-sync` and `mcp` on separate lines |
160
- | VS Code (Copilot agent mode) | `.vscode/mcp.json`: `{ "servers": { "qlib-sync": { "type": "stdio", "command": "uvx", "args": ["qlib-sync", "mcp"] } } }` |
173
+ | Cherry Studio | Settings, MCP servers, add: type STDIO, command `uvx`, arguments `--python`, `3.12`, `qlib-sync`, `mcp` on separate lines |
174
+ | VS Code (Copilot agent mode) | `.vscode/mcp.json`: `{ "servers": { "qlib-sync": { "type": "stdio", "command": "uvx", "args": ["--python", "3.12", "qlib-sync", "mcp"] } } }` |
161
175
 
162
176
  ```json
163
- { "mcpServers": { "qlib-sync": { "command": "uvx", "args": ["qlib-sync", "mcp"] } } }
177
+ { "mcpServers": { "qlib-sync": { "command": "uvx", "args": ["--python", "3.12", "qlib-sync", "mcp"] } } }
164
178
  ```
165
179
 
166
180
  Menu names and file locations change between versions; check your client's MCP documentation
@@ -17,7 +17,9 @@ pip install qlib-sync
17
17
  qlib-sync cn update
18
18
  ```
19
19
 
20
- Or without installing anything first: `uvx qlib-sync cn update`.
20
+ Or without installing anything first: `uvx --python 3.12 qlib-sync cn update`.
21
+ (`--python 3.12` because pyqlib publishes wheels up to Python 3.12; uv fetches that Python
22
+ for you if needed. With pip, use Python 3.10 to 3.12.)
21
23
 
22
24
  ![qlib-sync updating the dataset: status, download with progress, checksum, summary, then an instant no-op](docs/assets/demo-update.gif)
23
25
 
@@ -41,9 +43,15 @@ That works until it doesn't:
41
43
  - **Every check downloads 550 MB**, even when nothing changed.
42
44
 
43
45
  The investment_data README itself describes a careful manual process: download the release
44
- manifest, check the archive with its `validate_archive.py`, then extract. qlib-sync runs the
45
- same checks automatically on every update and adds what a manual process cannot: it skips the
46
- download when nothing changed, extracts beside the live data and swaps it in with rollback
46
+ manifest, check the archive with `qlib/validate_archive.py --require-publishable`, then
47
+ extract. qlib-sync runs that validator itself, on every update: an unmodified copy (from
48
+ [commit 68fdc66](https://github.com/chenditc/investment_data/blob/68fdc66b109459e522050c94723684525aea23ab/qlib/validate_archive.py),
49
+ Apache-2.0) checks the manifest before the download and the archive after it. A test suite
50
+ feeds the same malformed releases to the upstream script and to qlib-sync and requires the same
51
+ verdict, and a weekly job flags any change upstream makes to the validator.
52
+
53
+ On top of that, qlib-sync skips the download when nothing changed, pins the manifest and the
54
+ archive to one release tag, extracts beside the live data and swaps it in with rollback
47
55
  (rather than extracting on top of the existing folder), and picks up republished releases.
48
56
 
49
57
  qlib-sync treats the dataset like a production system:
@@ -55,17 +63,22 @@ qlib-sync treats the dataset like a production system:
55
63
 
56
64
  | Step | What happens |
57
65
  |---|---|
58
- | Check | Reads the release's 500-byte manifest. Already current: done, nothing downloaded. |
66
+ | Check | Pins the latest release by tag and reads its 500-byte manifest. Already current: done, nothing downloaded. |
59
67
  | Download | Resumes interrupted downloads; shows progress; optional mirror for slow links to GitHub. |
60
- | Verify | Size and sha256 from the manifest; the same release checks as upstream's `validate_archive.py` (every index pool and the future calendar reach the manifest's dates, release marked publishable); archive paths and links are checked before extraction. |
68
+ | Verify | investment_data's own `validate_archive.py --require-publishable`, unmodified: manifest fields and formats, size and sha256, every required calendar and index pool, date formats, ordering, and the future calendar. Anything it rejects is refused. |
61
69
  | Stage | Extracts next to the active dataset; refuses downgrades. |
62
70
  | Swap | Renames the new dataset into place, then runs a real Qlib read. |
63
71
  | Roll back | Any failure after the swap moves the previous dataset back. |
64
72
  | Report | New trading days, newly listed and delisted symbols. |
65
73
 
66
- The checks catch broken, truncated, or tampered packages. They cannot catch wrong values inside a
67
- correctly published release (like the one in #163); for that, keep a rollback copy with
68
- `--keep-backup`.
74
+ **Verification is required by default.** If the manifest is missing, unreadable, or rejected by
75
+ the validator, the update stops and your data stays as it was. `--allow-unverified` installs
76
+ anyway when the manifest cannot be verified, and the result and install record are then marked
77
+ unverified; a package whose checksum disagrees with a valid manifest is always refused.
78
+
79
+ The checks catch broken, truncated, tampered, or malformed packages. They cannot catch wrong
80
+ values inside a correctly published release (like the one in #163); for that, keep a rollback
81
+ copy with `--keep-backup`.
69
82
 
70
83
  ## Commands
71
84
 
@@ -86,6 +99,7 @@ Useful options for `cn update`:
86
99
  | `--keep-backup` | Keep the previous dataset for one rollback |
87
100
  | `--protect FILE` | Carry a custom stock pool in `instruments/` across updates (repeatable) |
88
101
  | `--force` | Reinstall even when the dates match |
102
+ | `--allow-unverified` | Install when the manifest cannot be verified (marked unverified; checksum mismatches are still refused) |
89
103
 
90
104
  **The update replaces the whole `cn_data` folder.** Anything you added there is lost unless you
91
105
  pass it with `--protect`.
@@ -121,17 +135,17 @@ Every client needs the same two things: the command `uvx` and the arguments `qli
121
135
  | Client | How to add qlib-sync |
122
136
  |---|---|
123
137
  | Claude Desktop | Download `qlib-sync.mcpb` from the [latest release](https://github.com/randyy179/qlib-sync/releases/latest) and open it |
124
- | Claude Code | `claude mcp add qlib-sync -- uvx qlib-sync mcp` |
125
- | OpenAI Codex CLI | `codex mcp add qlib-sync -- uvx qlib-sync mcp` |
138
+ | Claude Code | `claude mcp add qlib-sync -- uvx --python 3.12 qlib-sync mcp` |
139
+ | OpenAI Codex CLI | `codex mcp add qlib-sync -- uvx --python 3.12 qlib-sync mcp` |
126
140
  | Cursor | The JSON below, in `~/.cursor/mcp.json` |
127
141
  | Gemini CLI | The JSON below, in `~/.gemini/settings.json` |
128
142
  | Qwen Code | The JSON below, in `~/.qwen/settings.json` |
129
143
  | Tencent CodeBuddy, ByteDance Trae, Alibaba Tongyi Lingma | MCP settings, add a server manually, paste the JSON below |
130
- | Cherry Studio | Settings, MCP servers, add: type STDIO, command `uvx`, arguments `qlib-sync` and `mcp` on separate lines |
131
- | VS Code (Copilot agent mode) | `.vscode/mcp.json`: `{ "servers": { "qlib-sync": { "type": "stdio", "command": "uvx", "args": ["qlib-sync", "mcp"] } } }` |
144
+ | Cherry Studio | Settings, MCP servers, add: type STDIO, command `uvx`, arguments `--python`, `3.12`, `qlib-sync`, `mcp` on separate lines |
145
+ | VS Code (Copilot agent mode) | `.vscode/mcp.json`: `{ "servers": { "qlib-sync": { "type": "stdio", "command": "uvx", "args": ["--python", "3.12", "qlib-sync", "mcp"] } } }` |
132
146
 
133
147
  ```json
134
- { "mcpServers": { "qlib-sync": { "command": "uvx", "args": ["qlib-sync", "mcp"] } } }
148
+ { "mcpServers": { "qlib-sync": { "command": "uvx", "args": ["--python", "3.12", "qlib-sync", "mcp"] } } }
135
149
  ```
136
150
 
137
151
  Menu names and file locations change between versions; check your client's MCP documentation
@@ -11,7 +11,8 @@ pip install qlib-sync
11
11
  qlib-sync cn update
12
12
  ```
13
13
 
14
- 也可以不预先安装,直接运行:`uvx qlib-sync cn update`。
14
+ 也可以不预先安装,直接运行:`uvx --python 3.12 qlib-sync cn update`。
15
+ (加 `--python 3.12` 是因为 pyqlib 只提供到 Python 3.12 的安装包,需要时 uv 会自动下载这个版本的 Python。用 pip 安装时请使用 Python 3.10 到 3.12。)
15
16
 
16
17
  ![qlib-sync 更新数据:查看状态、带进度下载、校验、汇报变化,再运行一次瞬间完成](docs/assets/demo-update.gif)
17
18
 
@@ -27,7 +28,9 @@ Qlib 的 README 推荐用 `wget` 下载社区数据包再用 `tar` 解压。平
27
28
  - **上游会重新发布**:2026 年 11 月,上游发现并修正了一处数据错误([investment_data#163](https://github.com/chenditc/investment_data/issues/163)),用同一个日期重新发布了数据包。只比较日期的话,永远拿不到修复后的数据。
28
29
  - **每次检查都要下载 550MB**,即使什么都没变。
29
30
 
30
- investment_data 自己的 README 也给出了一套严谨的手动流程:下载发布清单(manifest),用它的 `validate_archive.py` 校验数据包,再解压。qlib-sync 每次更新都自动执行同样的校验,并补上手动流程做不到的部分:没有新数据时不下载;在旧数据旁边解压,校验通过后整体切换、失败回滚(而不是直接解压覆盖到现有目录);能拿到上游重新发布的修复版。
31
+ investment_data 自己的 README 也给出了一套严谨的手动流程:下载发布清单(manifest),用 `qlib/validate_archive.py --require-publishable` 校验数据包,再解压。qlib-sync 每次更新都直接运行这个校验器:用的是未经修改的原文(来自 [commit 68fdc66](https://github.com/chenditc/investment_data/blob/68fdc66b109459e522050c94723684525aea23ab/qlib/validate_archive.py),Apache-2.0 协议),下载前校验 manifest,下载后校验数据包。我们有一组测试,把同样的坏数据分别交给上游脚本和 qlib-sync,要求两边结论完全一致;每周还有自动任务检查上游是否修改了校验器。
32
+
33
+ 在此之外,qlib-sync 补上了手动流程做不到的部分:没有新数据时不下载;把 manifest 和数据包锁定在同一个发布版本;在旧数据旁边解压,通过后整体切换、失败回滚(而不是直接解压覆盖到现有目录);能拿到上游重新发布的修复版。
31
34
 
32
35
  qlib-sync 把本地数据当生产系统来管理:
33
36
 
@@ -38,15 +41,17 @@ qlib-sync 把本地数据当生产系统来管理:
38
41
 
39
42
  | 步骤 | 做什么 |
40
43
  |---|---|
41
- | 检查 | 先读发布附带的 500 字节 manifest。已是最新就结束,不下载 |
44
+ | 检查 | 锁定最新发布的版本号,读取它附带的 500 字节 manifest。已是最新就结束,不下载 |
42
45
  | 下载 | 支持断点续传、显示进度;网络慢时可走镜像 |
43
- | 校验 | 核对大小和 sha256;执行与上游 `validate_archive.py` 相同的发布检查(各指数成分和未来交易日历都与 manifest 日期一致、发布标记为可发布);解压前检查路径和链接是否安全 |
46
+ | 校验 | 运行 investment_data 自己的 `validate_archive.py --require-publishable`(原文未改):manifest 字段和格式、大小和 sha256、所有必需的交易日历和指数成分、日期格式、排序、未来交易日历。它拒绝的一律不装 |
44
47
  | 准备 | 解压到旧数据旁边的临时目录;拒绝降级 |
45
48
  | 切换 | 把新数据换上去,再用 Qlib 实际读一次 |
46
49
  | 回滚 | 切换后任何一步失败,自动换回旧数据 |
47
50
  | 汇报 | 新增了哪些交易日、新上市和退市的股票 |
48
51
 
49
- 这些校验能挡住损坏、不完整或被篡改的数据包,**挡不住上游正常发布的数据里本身的数值错误**(比如 #163 那种)。需要防这类问题时,可以加 `--keep-backup` 保留一份可回滚的旧数据。
52
+ **默认必须通过校验。** manifest 缺失、读不了或被校验器拒绝时,更新会停止,原有数据保持不变。`--allow-unverified` 可以在 manifest 无法校验时仍然安装,此时结果和安装记录都会标明“未校验”;但如果 manifest 有效而数据包的校验值对不上,无论如何都会拒绝。
53
+
54
+ 这些校验能挡住损坏、不完整、被篡改或格式不对的数据包,**挡不住上游正常发布的数据里本身的数值错误**(比如 #163 那种)。需要防这类问题时,可以加 `--keep-backup` 保留一份可回滚的旧数据。
50
55
 
51
56
  ## 命令
52
57
 
@@ -67,6 +72,7 @@ qlib-sync cn restore --list # 查看备份;restore --yes 换回备份
67
72
  | `--keep-backup` | 保留一份旧数据用于回滚 |
68
73
  | `--protect 文件名` | 更新后保留 `instruments/` 里自定义的股票池文件(可重复使用) |
69
74
  | `--force` | 日期相同也重新安装 |
75
+ | `--allow-unverified` | manifest 无法校验时仍然安装(会标明未校验;校验值不符仍会拒绝) |
70
76
 
71
77
  **注意:更新会替换整个 `cn_data` 目录。** 你自己放进去的文件,除非用 `--protect` 指定,否则会丢失。
72
78
 
@@ -99,17 +105,17 @@ MCP server 在你本机运行,读取你电脑上的数据,不需要搭建任
99
105
  | 工具 | 接入方式 |
100
106
  |---|---|
101
107
  | Claude Desktop | 从[最新发布](https://github.com/randyy179/qlib-sync/releases/latest)下载 `qlib-sync.mcpb`,双击安装 |
102
- | Claude Code | `claude mcp add qlib-sync -- uvx qlib-sync mcp` |
103
- | OpenAI Codex CLI | `codex mcp add qlib-sync -- uvx qlib-sync mcp` |
108
+ | Claude Code | `claude mcp add qlib-sync -- uvx --python 3.12 qlib-sync mcp` |
109
+ | OpenAI Codex CLI | `codex mcp add qlib-sync -- uvx --python 3.12 qlib-sync mcp` |
104
110
  | 腾讯 CodeBuddy、字节 Trae、阿里通义灵码 | 在 MCP 设置里手动添加服务器,粘贴下面的 JSON |
105
- | Cherry Studio | 设置 → MCP 服务器 → 添加:类型选 STDIO,命令填 `uvx`,参数分两行填 `qlib-sync` 和 `mcp` |
111
+ | Cherry Studio | 设置 → MCP 服务器 → 添加:类型选 STDIO,命令填 `uvx`,参数分四行填 `--python`、`3.12`、`qlib-sync`、`mcp` |
106
112
  | 通义千问 Qwen Code | 把下面的 JSON 写进 `~/.qwen/settings.json` |
107
113
  | Cursor | 把下面的 JSON 写进 `~/.cursor/mcp.json` |
108
114
  | Gemini CLI | 把下面的 JSON 写进 `~/.gemini/settings.json` |
109
- | VS Code(Copilot Agent 模式) | `.vscode/mcp.json`:`{ "servers": { "qlib-sync": { "type": "stdio", "command": "uvx", "args": ["qlib-sync", "mcp"] } } }` |
115
+ | VS Code(Copilot Agent 模式) | `.vscode/mcp.json`:`{ "servers": { "qlib-sync": { "type": "stdio", "command": "uvx", "args": ["--python", "3.12", "qlib-sync", "mcp"] } } }` |
110
116
 
111
117
  ```json
112
- { "mcpServers": { "qlib-sync": { "command": "uvx", "args": ["qlib-sync", "mcp"] } } }
118
+ { "mcpServers": { "qlib-sync": { "command": "uvx", "args": ["--python", "3.12", "qlib-sync", "mcp"] } } }
113
119
  ```
114
120
 
115
121
  各工具的菜单名称和配置文件位置会随版本变化,找不到时以该工具的 MCP 官方文档为准。已实测:Claude Code、OpenAI Codex CLI。
@@ -2,7 +2,7 @@
2
2
  "manifest_version": "0.4",
3
3
  "name": "qlib-sync",
4
4
  "display_name": "qlib-sync: China A-share data",
5
- "version": "0.1.0",
5
+ "version": "0.2.0",
6
6
  "description": "Install, update, and read a local Qlib China A-share daily dataset.",
7
7
  "long_description": "Keeps ~/.qlib/qlib_data/cn_data current with the daily community release from chenditc/investment_data, with checksum verification and automatic rollback, and lets Claude read daily prices and index constituents from it. The first update downloads about 550 MB.",
8
8
  "author": {
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "qlib-sync-mcpb"
3
- version = "0.1.0"
3
+ version = "0.2.0"
4
4
  description = "Claude Desktop bundle for qlib-sync"
5
5
  requires-python = ">=3.10,<3.13"
6
- dependencies = ["qlib-sync==0.1.0"]
6
+ dependencies = ["qlib-sync==0.2.0"]
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "qlib-sync"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Safely install and update a local Qlib China A-share dataset, with an MCP server for AI assistants."
9
9
  dynamic = ["readme"]
10
10
  requires-python = ">=3.10,<3.13"
@@ -7,15 +7,22 @@
7
7
  "url": "https://github.com/randyy179/qlib-sync",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.1.0",
10
+ "version": "0.2.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "pypi",
14
14
  "identifier": "qlib-sync",
15
- "version": "0.1.0",
15
+ "version": "0.2.0",
16
16
  "runtimeHint": "uvx",
17
- "packageArguments": [{ "type": "positional", "value": "mcp" }],
18
- "transport": { "type": "stdio" },
17
+ "packageArguments": [
18
+ {
19
+ "type": "positional",
20
+ "value": "mcp"
21
+ }
22
+ ],
23
+ "transport": {
24
+ "type": "stdio"
25
+ },
19
26
  "environmentVariables": [
20
27
  {
21
28
  "name": "QLIB_SYNC_DATA_DIR",
@@ -27,6 +34,13 @@
27
34
  "description": "Optional prefix-style GitHub proxy for slow connections, e.g. https://ghfast.top",
28
35
  "isRequired": false
29
36
  }
37
+ ],
38
+ "runtimeArguments": [
39
+ {
40
+ "type": "named",
41
+ "name": "--python",
42
+ "value": "3.12"
43
+ }
30
44
  ]
31
45
  }
32
46
  ]
@@ -6,15 +6,16 @@ description: Install, update, check, and read the local Qlib China A-share daily
6
6
  # qlib-sync
7
7
 
8
8
  qlib-sync keeps `~/.qlib/qlib_data/cn_data` (Qlib's default A-share location) in step with the
9
- daily community release from chenditc/investment_data. Every update is transactional: the new
10
- package is verified (sha256 from the release manifest) and validated in a staging directory,
11
- swapped in, and checked with a real Qlib read. On any failure the previous data is restored,
12
- so running an update is always safe.
9
+ daily community release from chenditc/investment_data. Every update is transactional: the
10
+ release is pinned by tag, checked with the dataset maintainers' own `validate_archive.py
11
+ --require-publishable` (an unmodified copy ships with qlib-sync), staged, swapped in, and checked
12
+ with a real Qlib read. A release that cannot be verified is refused, and on any failure the
13
+ previous data is restored, so running an update is always safe.
13
14
 
14
15
  ## Pick the interface
15
16
 
16
17
  - **MCP tools available** (`cn_status`, `cn_update`, `cn_get_prices`, ...): use them directly.
17
- - **Otherwise** use the CLI. If `qlib-sync` is not installed, `uvx qlib-sync ...` runs it
18
+ - **Otherwise** use the CLI. If `qlib-sync` is not installed, `uvx --python 3.12 qlib-sync ...` runs it
18
19
  without installing; `pip install qlib-sync` installs it. Confirm before installing anything.
19
20
 
20
21
  ## Update the data
@@ -37,8 +38,9 @@ Read the `[ERROR]` line. The existing data is unchanged in every failure case.
37
38
  | Message contains | Meaning | What to do |
38
39
  |---|---|---|
39
40
  | `could not download` | Network problem reaching GitHub | Retry with a mirror: `qlib-sync cn update --mirror https://ghfast.top` (or set `QLIB_SYNC_MIRROR`). Common in mainland China. |
40
- | `sha256 does not match` | Corrupt or tampered download; it was discarded | Run the update again. If it repeats, the release itself may be broken; wait for the next one. |
41
- | `manifest says` | Package contents disagree with the release metadata | Upstream problem. Wait and retry later; do not use `--force`. |
41
+ | `refusing to install unverified data` | The release manifest could not be read or was rejected | Usually a network problem: retry, or add `--mirror`. If the message names `invalid-manifest`, upstream published something its own validator rejects; wait for the next release. |
42
+ | `upstream validator (archive-identity-mismatch)` | Corrupt or tampered download; it was discarded | Run the update again. If it repeats, the release itself may be broken; wait for the next one. |
43
+ | `upstream validator (` any other reason | The package fails the maintainers' own checks | Upstream problem. Wait and retry later; do not use `--force` or `--allow-unverified`. |
42
44
  | `refusing downgrade` | The release is older than the local data | Nothing to do; local data is newer. |
43
45
  | `already running` | Another update holds the lock | Wait for it. If none is running (a crash left the lock), remove `~/.qlib/qlib_data/.cn_data_update.lock` after confirming with the user. |
44
46
  | `not a Qlib daily dataset` | The target folder has other content | Ask the user; never delete it yourself. Use `--data-dir` to install elsewhere. |
@@ -50,6 +52,8 @@ After any repair, run `qlib-sync cn health-check`; it ends with `passed` or list
50
52
  - The update replaces the whole `cn_data` folder. Files the user added there are lost unless
51
53
  passed with `--protect FILE` (custom stock pools in `instruments/`). Mention this before the
52
54
  first update if they have customized the folder.
55
+ - Only suggest `--allow-unverified` if the user explicitly accepts installing data nobody has
56
+ checked; it never bypasses a checksum mismatch, and the result is marked unverified.
53
57
  - `--keep-backup` keeps the previous dataset for one rollback; `qlib-sync cn restore --list`
54
58
  shows backups and `restore --yes` switches back. Restoring replaces the active data, so
55
59
  confirm with the user first.
@@ -8,6 +8,6 @@ os.environ.setdefault("MLFLOW_DISABLE_AGENT_HINT", "1")
8
8
  from .api import health_check_cn, status_cn, update_cn
9
9
  from .cn.update import UpdateError, UpdateResult
10
10
 
11
- __version__ = "0.1.0"
11
+ __version__ = "0.2.0"
12
12
 
13
13
  __all__ = ["UpdateError", "UpdateResult", "health_check_cn", "status_cn", "update_cn", "__version__"]
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,14 @@
1
+ """Code from chenditc/investment_data, used unmodified.
2
+
3
+ `validate_archive.py` is the release validator from
4
+ https://github.com/chenditc/investment_data/blob/68fdc66b109459e522050c94723684525aea23ab/qlib/validate_archive.py
5
+ licensed under the Apache License 2.0 (see LICENSE in this folder). It is kept byte-for-byte
6
+ identical so qlib-sync applies exactly the checks the dataset's maintainers apply, and so a
7
+ change upstream can be detected by comparing git blob hashes.
8
+ """
9
+
10
+ SOURCE_REPOSITORY = "chenditc/investment_data"
11
+ SOURCE_PATH = "qlib/validate_archive.py"
12
+ SOURCE_COMMIT = "68fdc66b109459e522050c94723684525aea23ab"
13
+ # `git hash-object` of validate_archive.py; equal to the blob sha GitHub reports for SOURCE_PATH.
14
+ SOURCE_BLOB_SHA = "f7141a3969ff1fb9e448cf753ed3ffde017afaa3"