ashare-data-lite 0.1.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 (39) hide show
  1. ashare_data_lite-0.1.0/LICENSE +22 -0
  2. ashare_data_lite-0.1.0/MANIFEST.in +4 -0
  3. ashare_data_lite-0.1.0/PKG-INFO +134 -0
  4. ashare_data_lite-0.1.0/README.md +107 -0
  5. ashare_data_lite-0.1.0/docs/api.md +228 -0
  6. ashare_data_lite-0.1.0/pyproject.toml +44 -0
  7. ashare_data_lite-0.1.0/scripts/release.py +109 -0
  8. ashare_data_lite-0.1.0/scripts/validate_release.py +168 -0
  9. ashare_data_lite-0.1.0/setup.cfg +4 -0
  10. ashare_data_lite-0.1.0/src/ashare_data/__init__.py +163 -0
  11. ashare_data_lite-0.1.0/src/ashare_data/_convert.py +152 -0
  12. ashare_data_lite-0.1.0/src/ashare_data/_default.py +36 -0
  13. ashare_data_lite-0.1.0/src/ashare_data/_http.py +86 -0
  14. ashare_data_lite-0.1.0/src/ashare_data/_utils.py +87 -0
  15. ashare_data_lite-0.1.0/src/ashare_data/async_client.py +260 -0
  16. ashare_data_lite-0.1.0/src/ashare_data/client.py +303 -0
  17. ashare_data_lite-0.1.0/src/ashare_data/etf.py +61 -0
  18. ashare_data_lite-0.1.0/src/ashare_data/exceptions.py +38 -0
  19. ashare_data_lite-0.1.0/src/ashare_data/fallback.py +13 -0
  20. ashare_data_lite-0.1.0/src/ashare_data/index.py +40 -0
  21. ashare_data_lite-0.1.0/src/ashare_data/models.py +127 -0
  22. ashare_data_lite-0.1.0/src/ashare_data/py.typed +0 -0
  23. ashare_data_lite-0.1.0/src/ashare_data/sources/__init__.py +1 -0
  24. ashare_data_lite-0.1.0/src/ashare_data/sources/eastmoney.py +160 -0
  25. ashare_data_lite-0.1.0/src/ashare_data/sources/exchanges.py +45 -0
  26. ashare_data_lite-0.1.0/src/ashare_data/sources/fred.py +73 -0
  27. ashare_data_lite-0.1.0/src/ashare_data/sources/sina.py +87 -0
  28. ashare_data_lite-0.1.0/src/ashare_data/sources/tencent.py +234 -0
  29. ashare_data_lite-0.1.0/src/ashare_data/sources/twelvedata.py +113 -0
  30. ashare_data_lite-0.1.0/src/ashare_data/stock.py +40 -0
  31. ashare_data_lite-0.1.0/src/ashare_data_lite.egg-info/PKG-INFO +134 -0
  32. ashare_data_lite-0.1.0/src/ashare_data_lite.egg-info/SOURCES.txt +37 -0
  33. ashare_data_lite-0.1.0/src/ashare_data_lite.egg-info/dependency_links.txt +1 -0
  34. ashare_data_lite-0.1.0/src/ashare_data_lite.egg-info/requires.txt +5 -0
  35. ashare_data_lite-0.1.0/src/ashare_data_lite.egg-info/top_level.txt +1 -0
  36. ashare_data_lite-0.1.0/tests/test_api.py +181 -0
  37. ashare_data_lite-0.1.0/tests/test_http.py +105 -0
  38. ashare_data_lite-0.1.0/tests/test_readme.py +65 -0
  39. ashare_data_lite-0.1.0/tests/test_sources.py +475 -0
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ashare-data-lite contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,4 @@
1
+ include README.md LICENSE
2
+ include docs/api.md
3
+ recursive-include tests *.py
4
+ recursive-include scripts *.py
@@ -0,0 +1,134 @@
1
+ Metadata-Version: 2.4
2
+ Name: ashare-data-lite
3
+ Version: 0.1.0
4
+ Summary: 使用 Python 标准库直接获取证券行情、基金净值与宏观序列
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/zhangjw777/ashare-data-lite
7
+ Project-URL: Repository, https://github.com/zhangjw777/ashare-data-lite
8
+ Project-URL: Documentation, https://github.com/zhangjw777/ashare-data-lite/blob/main/docs/api.md
9
+ Project-URL: Issues, https://github.com/zhangjw777/ashare-data-lite/issues
10
+ Keywords: ashare,stock,etf,market-data,finance
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Operating System :: OS Independent
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Provides-Extra: dev
23
+ Requires-Dist: build>=1.2; extra == "dev"
24
+ Requires-Dist: twine>=6; extra == "dev"
25
+ Requires-Dist: PyYAML>=6; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ # ashare-data-lite
29
+
30
+ 轻量、免费的 Python 行情工具,提供沪深股票、ETF、指数、基金净值及海外市场数据。Python 3.10+,运行代码使用标准库,采用 MIT 许可证。
31
+
32
+ ## 安装与使用
33
+
34
+ 从 PyPI 安装:
35
+
36
+ ```shell
37
+ pip install ashare-data-lite
38
+ ```
39
+
40
+ ```python
41
+ from ashare_data import get_etf_quote, get_etf_history, get_fund_nav_history
42
+
43
+ quote = get_etf_quote("159941")
44
+ print(quote.name, quote.last, quote.iopv)
45
+
46
+ bars = get_etf_history("159941", "2026-09-01", "2026-10-23")
47
+ print(bars[-1].timestamp, bars[-1].close, bars[-1].volume)
48
+
49
+ nav = get_fund_nav_history("159941", "2026-09-01", "2026-10-23")
50
+ print(nav[-1].date, nav[-1].nav)
51
+ ```
52
+
53
+ ETF 指交易所交易基金,IOPV 指基金份额参考净值。查询结果采用 dataclass,支持属性访问和 `to_dict()`。股票数量以股计,ETF 数量以份计;百分率 `2.5` 表示 `2.5%`。
54
+
55
+ ## 支持的数据
56
+
57
+ | 数据 | 入口 |
58
+ | --- | --- |
59
+ | 股票、ETF、指数实时行情及五档买卖报价 | `get_stock_quote`、`get_etf_quote`、`get_index_quote`,批量方法以 `_quotes` 结尾 |
60
+ | 日线与 5 / 15 / 30 分钟 K 线 | `get_stock_history`、`get_etf_history`、`get_index_history` |
61
+ | 基金历史单位净值 | `get_fund_nav_history` |
62
+ | ETF 目录、上交所 ETF 份额 | `get_etf_catalog`、`get_sse_etf_shares` |
63
+ | FRED 经济序列及 VIX | `get_fred_series`、`get_vix_history` |
64
+ | Twelve Data 海外 K 线 | `get_twelve_bars`、`get_twelve_daily_history` |
65
+
66
+ 沪深行情、基金净值、目录和份额可直接查询。FRED 和 Twelve Data 使用服务商账户密钥,支持环境变量 `FRED_API_KEY`、`TWELVE_DATA_API_KEY`。
67
+
68
+ ## 自动降级
69
+
70
+ 日线默认原始价格,依次尝试腾讯、新浪;基金单位净值依次尝试东方财富移动端、网页。使用默认配置即可查询,也可设置来源优先级:
71
+
72
+ ```python
73
+ bars = get_etf_history(
74
+ "159941", "2026-10-01", "2026-10-23",
75
+ sources=["sina", "tencent"],
76
+ )
77
+ ```
78
+
79
+ 降级过程保持周期与复权方式一致。分钟线由新浪提供;前复权、后复权日线由腾讯提供。来源的覆盖日期与账户权限决定实际可用数据范围。
80
+
81
+ ## 批量、序列化与异步
82
+
83
+ ```python
84
+ import json
85
+ from ashare_data import get_etf_quotes
86
+
87
+ quotes = get_etf_quotes(["159941", "513100"])
88
+ for quote in quotes:
89
+ print(quote.symbol, quote.last, quote.error)
90
+
91
+ print(json.dumps(quotes[0].to_dict(), ensure_ascii=False))
92
+ ```
93
+
94
+ ```python
95
+ import asyncio
96
+ from ashare_data import AsyncMarketClient
97
+
98
+ async def main():
99
+ client = AsyncMarketClient(timeout=10)
100
+ quotes = await client.get_etf_quotes(["159941", "513100"])
101
+ print(quotes[0].last)
102
+
103
+ asyncio.run(main())
104
+ ```
105
+
106
+ 同步客户端 `MarketClient` 与异步客户端 `AsyncMarketClient` 提供相同的方法和返回类型。完整参数、返回字段、单位及异常见 [API 文档](https://github.com/zhangjw777/ashare-data-lite/blob/main/docs/api.md)。
107
+
108
+ ## 数据来源与使用范围
109
+
110
+ 实时行情来自腾讯;历史 K 线来自腾讯、新浪;基金净值来自东方财富;基金份额来自上交所;经济序列与海外行情来自 FRED、Twelve Data。数据源的覆盖范围、延迟、接口格式及访问额度可能变化,使用方应做好超时处理并校验关键数据。账户接口遵循对应服务商的套餐和调用限制。
111
+
112
+ 本项目的 MIT 许可证适用于程序代码。行情数据的使用、存储和再分发遵循各来源的使用条款;交易分析需要结合其他信息校验,并自行承担决策风险。
113
+
114
+ ## 开发
115
+
116
+ ```shell
117
+ python -m pip install -e ".[dev]"
118
+ python -m unittest discover -s tests -v
119
+ ```
120
+
121
+ 使用案例位于 `examples/`。在线样本检查运行 `python examples/smoke_check.py`,记录保存在 `docs/live-check.json`。GitHub Actions 在 Python 3.10~3.13 执行自动测试。
122
+
123
+ ## 发布新版本
124
+
125
+ 在 `main` 分支审阅并提交功能改动后执行:
126
+
127
+ ```shell
128
+ python scripts/release.py 0.1.1
129
+ ```
130
+
131
+ 脚本检查 PyPI 与 Git 标签、更新版本、构建并安装测试,然后推送提交与 `v0.1.1` 标签。GitHub Actions 使用 `pypi` Environment 和 OIDC(开放身份连接)可信发布到 PyPI。工作流运行记录见 [Publish to PyPI](https://github.com/zhangjw777/ashare-data-lite/actions/workflows/publish.yml)。
132
+
133
+ 仅执行发布前验证:`python scripts/release.py 0.1.1 --check-only`。项目内的发布指导位于 [.agents/skills/publish-pypi/SKILL.md](https://github.com/zhangjw777/ashare-data-lite/blob/main/.agents/skills/publish-pypi/SKILL.md)。
134
+
@@ -0,0 +1,107 @@
1
+ # ashare-data-lite
2
+
3
+ 轻量、免费的 Python 行情工具,提供沪深股票、ETF、指数、基金净值及海外市场数据。Python 3.10+,运行代码使用标准库,采用 MIT 许可证。
4
+
5
+ ## 安装与使用
6
+
7
+ 从 PyPI 安装:
8
+
9
+ ```shell
10
+ pip install ashare-data-lite
11
+ ```
12
+
13
+ ```python
14
+ from ashare_data import get_etf_quote, get_etf_history, get_fund_nav_history
15
+
16
+ quote = get_etf_quote("159941")
17
+ print(quote.name, quote.last, quote.iopv)
18
+
19
+ bars = get_etf_history("159941", "2026-09-01", "2026-10-23")
20
+ print(bars[-1].timestamp, bars[-1].close, bars[-1].volume)
21
+
22
+ nav = get_fund_nav_history("159941", "2026-09-01", "2026-10-23")
23
+ print(nav[-1].date, nav[-1].nav)
24
+ ```
25
+
26
+ ETF 指交易所交易基金,IOPV 指基金份额参考净值。查询结果采用 dataclass,支持属性访问和 `to_dict()`。股票数量以股计,ETF 数量以份计;百分率 `2.5` 表示 `2.5%`。
27
+
28
+ ## 支持的数据
29
+
30
+ | 数据 | 入口 |
31
+ | --- | --- |
32
+ | 股票、ETF、指数实时行情及五档买卖报价 | `get_stock_quote`、`get_etf_quote`、`get_index_quote`,批量方法以 `_quotes` 结尾 |
33
+ | 日线与 5 / 15 / 30 分钟 K 线 | `get_stock_history`、`get_etf_history`、`get_index_history` |
34
+ | 基金历史单位净值 | `get_fund_nav_history` |
35
+ | ETF 目录、上交所 ETF 份额 | `get_etf_catalog`、`get_sse_etf_shares` |
36
+ | FRED 经济序列及 VIX | `get_fred_series`、`get_vix_history` |
37
+ | Twelve Data 海外 K 线 | `get_twelve_bars`、`get_twelve_daily_history` |
38
+
39
+ 沪深行情、基金净值、目录和份额可直接查询。FRED 和 Twelve Data 使用服务商账户密钥,支持环境变量 `FRED_API_KEY`、`TWELVE_DATA_API_KEY`。
40
+
41
+ ## 自动降级
42
+
43
+ 日线默认原始价格,依次尝试腾讯、新浪;基金单位净值依次尝试东方财富移动端、网页。使用默认配置即可查询,也可设置来源优先级:
44
+
45
+ ```python
46
+ bars = get_etf_history(
47
+ "159941", "2026-10-01", "2026-10-23",
48
+ sources=["sina", "tencent"],
49
+ )
50
+ ```
51
+
52
+ 降级过程保持周期与复权方式一致。分钟线由新浪提供;前复权、后复权日线由腾讯提供。来源的覆盖日期与账户权限决定实际可用数据范围。
53
+
54
+ ## 批量、序列化与异步
55
+
56
+ ```python
57
+ import json
58
+ from ashare_data import get_etf_quotes
59
+
60
+ quotes = get_etf_quotes(["159941", "513100"])
61
+ for quote in quotes:
62
+ print(quote.symbol, quote.last, quote.error)
63
+
64
+ print(json.dumps(quotes[0].to_dict(), ensure_ascii=False))
65
+ ```
66
+
67
+ ```python
68
+ import asyncio
69
+ from ashare_data import AsyncMarketClient
70
+
71
+ async def main():
72
+ client = AsyncMarketClient(timeout=10)
73
+ quotes = await client.get_etf_quotes(["159941", "513100"])
74
+ print(quotes[0].last)
75
+
76
+ asyncio.run(main())
77
+ ```
78
+
79
+ 同步客户端 `MarketClient` 与异步客户端 `AsyncMarketClient` 提供相同的方法和返回类型。完整参数、返回字段、单位及异常见 [API 文档](https://github.com/zhangjw777/ashare-data-lite/blob/main/docs/api.md)。
80
+
81
+ ## 数据来源与使用范围
82
+
83
+ 实时行情来自腾讯;历史 K 线来自腾讯、新浪;基金净值来自东方财富;基金份额来自上交所;经济序列与海外行情来自 FRED、Twelve Data。数据源的覆盖范围、延迟、接口格式及访问额度可能变化,使用方应做好超时处理并校验关键数据。账户接口遵循对应服务商的套餐和调用限制。
84
+
85
+ 本项目的 MIT 许可证适用于程序代码。行情数据的使用、存储和再分发遵循各来源的使用条款;交易分析需要结合其他信息校验,并自行承担决策风险。
86
+
87
+ ## 开发
88
+
89
+ ```shell
90
+ python -m pip install -e ".[dev]"
91
+ python -m unittest discover -s tests -v
92
+ ```
93
+
94
+ 使用案例位于 `examples/`。在线样本检查运行 `python examples/smoke_check.py`,记录保存在 `docs/live-check.json`。GitHub Actions 在 Python 3.10~3.13 执行自动测试。
95
+
96
+ ## 发布新版本
97
+
98
+ 在 `main` 分支审阅并提交功能改动后执行:
99
+
100
+ ```shell
101
+ python scripts/release.py 0.1.1
102
+ ```
103
+
104
+ 脚本检查 PyPI 与 Git 标签、更新版本、构建并安装测试,然后推送提交与 `v0.1.1` 标签。GitHub Actions 使用 `pypi` Environment 和 OIDC(开放身份连接)可信发布到 PyPI。工作流运行记录见 [Publish to PyPI](https://github.com/zhangjw777/ashare-data-lite/actions/workflows/publish.yml)。
105
+
106
+ 仅执行发布前验证:`python scripts/release.py 0.1.1 --check-only`。项目内的发布指导位于 [.agents/skills/publish-pypi/SKILL.md](https://github.com/zhangjw777/ashare-data-lite/blob/main/.agents/skills/publish-pypi/SKILL.md)。
107
+
@@ -0,0 +1,228 @@
1
+ # API 参考
2
+
3
+ 所有公开入口均可从 `ashare_data` 导入。调用返回 dataclass 对象或其列表,使用 `obj.field` 读取字段、`obj.to_dict()` 转为适合 JSON 的字典。
4
+
5
+ ## 1. 共同约定
6
+
7
+ | 项目 | 约定 |
8
+ | --- | --- |
9
+ | 代码 | 字符串,支持 `159941`、`sz159941`、`159941.SZ` |
10
+ | 品种 | `instrument="auto" / "stock" / "etf" / "index"` |
11
+ | 日期参数 | `YYYY-MM-DD` 字符串或 `datetime.date`,起止日均包含;默认从 `1970-01-01` 到中国时区当天 |
12
+ | 代码歧义 | 股票入口 `"000001"` 为平安银行,指数入口为上证指数;混合入口中的指数使用 `"sh000001"`、`"sz399001"` |
13
+ | 时间 | `timestamp` 为带时区的 `datetime`;沪深行情为 UTC+08:00,海外 K 线为 UTC |
14
+ | 日期字段 | `date` 为 `datetime.date`,表示净值所属日、统计日或观测日 |
15
+ | 数量 | 沪深股票按股、ETF 按份;不同来源的上游取整精度可能有差异 |
16
+ | 价格与金额 | 按 `currency` 标价币种;市值与成交额使用基本货币单位,如人民币元 |
17
+ | 百分率 | `*_pct` 为百分数,`2.5` 表示 `2.5%` |
18
+ | 缺失值 | 可选字段为 `None`,成功查询的空区间返回 `[]`;FRED 缺失观测跳过 |
19
+ | 列表顺序 | 历史按时间去重升序;批量行情按首次输入顺序去重 |
20
+ | 对象 | dataclass 为只读对象;`to_dict()` 递归将日期、时间转为 ISO 8601 字符串,将元组转为列表 |
21
+
22
+ ## 2. 实时行情
23
+
24
+ | 函数 | 参数 | 返回 |
25
+ | --- | --- | --- |
26
+ | `get_stock_quote` / `get_etf_quote` / `get_index_quote` | `code: str` | `Quote` |
27
+ | `get_stock_quotes` / `get_etf_quotes` / `get_index_quotes` | `codes: Sequence[str]` | `list[Quote]` |
28
+ | `get_quote` | `code, *, instrument="auto"` | `Quote` |
29
+ | `get_quotes` | `codes, *, instrument="auto"` | `list[Quote]` |
30
+
31
+ ```python
32
+ from ashare_data import get_etf_quote, get_quotes
33
+
34
+ quote = get_etf_quote("159941")
35
+ print(quote.last, quote.iopv, quote.premium_rate_pct)
36
+ print(quote.bids[0].price, quote.bids[0].volume)
37
+
38
+ quotes = get_quotes(["sz159941", "sh600519", "sh000001"])
39
+ ```
40
+
41
+ 实时行情采用腾讯。单只查询缺失时抛出 `QuoteNotFoundError`;批量查询的缺失项为 `Quote`,其 `error` 包含原因,其余可选字段为 `None`。整次网络或响应解析失败时抛出对应异常。
42
+
43
+ ### Quote
44
+
45
+ | 字段 | 类型 | 含义 |
46
+ | --- | --- | --- |
47
+ | `symbol` / `code` | `str` | 带市场前缀的证券标识 / 六位代码 |
48
+ | `instrument` | `str` | `stock`、`etf`、`index`;混合查询缺失项可为 `auto` |
49
+ | `source` | `str` | 本次实际数据来源 |
50
+ | `name` | `str \| None` | 证券名称 |
51
+ | `timestamp` | `datetime \| None` | 行情时间 |
52
+ | `currency` | `str \| None` | 标价币种,例如 `CNY` |
53
+ | `last` / `prev_close` | `float \| None` | 最新价 / 昨收价 |
54
+ | `open` / `high` / `low` | `float \| None` | 当日开盘价 / 最高价 / 最低价 |
55
+ | `change` / `change_pct` | `float \| None` | 相对昨收的涨跌额 / 涨跌幅 |
56
+ | `volume` / `amount` | `float \| None` | 当日累计成交数量 / 成交金额 |
57
+ | `outer_volume` / `inner_volume` | `float \| None` | 外盘 / 内盘成交数量 |
58
+ | `turnover_rate_pct` / `amplitude_pct` | `float \| None` | 换手率 / 振幅 |
59
+ | `market_cap` / `circulating_market_cap` | `float \| None` | 总市值 / 流通市值 |
60
+ | `pe` / `pb` | `float \| None` | 股票市盈率 / 市净率 |
61
+ | `limit_up` / `limit_down` | `float \| None` | 涨停价 / 跌停价 |
62
+ | `volume_ratio` | `float \| None` | 量比 |
63
+ | `order_imbalance` | `float \| None` | 买五档数量合计减卖五档数量合计 |
64
+ | `average_price` | `float \| None` | 当日成交均价 |
65
+ | `is_suspended` | `bool` | 源端停牌标记 |
66
+ | `bids` / `asks` | `tuple[OrderLevel, ...]` | 买 / 卖报价,按一档到五档排列;缺失行情时为空元组 |
67
+ | `iopv` | `float \| None` | ETF 份额参考净值 |
68
+ | `premium_rate_pct` | `float \| None` | ETF 折溢价率;正数为溢价,负数为折价 |
69
+ | `circulating_shares` / `total_shares` | `float \| None` | 流通 / 总股票股数或基金份数 |
70
+ | `error` | `str \| None` | 批量缺失项的错误信息;成功时为 `None` |
71
+
72
+ `OrderLevel` 表示一档买卖报价。
73
+
74
+ | 字段 | 类型 | 含义 |
75
+ | --- | --- | --- |
76
+ | `price` | `float \| None` | 该档报价 |
77
+ | `volume` | `float \| None` | 该档委托数量,股或份 |
78
+
79
+ ETF 专属字段在其他品种上为 `None`;其余可选字段由品种与数据可用性决定。
80
+
81
+ ## 3. 历史 K 线
82
+
83
+ `get_stock_history`、`get_etf_history`、`get_index_history` 共用以下签名,返回 `list[Bar]`:
84
+
85
+ ```python
86
+ get_etf_history(
87
+ code, start=None, end=None, *,
88
+ sources=None, adjust="none", scale=240, datalen=5049, max_pages=100,
89
+ )
90
+ ```
91
+
92
+ `get_history` 使用相同参数,并增加 `instrument="auto"`。
93
+
94
+ | 参数 | 含义 |
95
+ | --- | --- |
96
+ | `sources` | 按优先级排列的列表;日线默认 `["tencent", "sina"]`,分钟线默认 `["sina"]` |
97
+ | `adjust` | `"none"` 原始价格、`"qfq"` 前复权、`"hfq"` 后复权;指数使用 `"none"` |
98
+ | `scale` | `240` 日线;`5`、`15`、`30` 分钟线 |
99
+ | `datalen` | 新浪最近 K 线数量,1~5049,默认 5049 |
100
+ | `max_pages` | 腾讯历史分页上限,正整数,默认 100 |
101
+
102
+ 来源出错后按顺序尝试下一个兼容来源,成功后返回该来源的结果。选择兼容来源时保持周期和复权方式一致:
103
+
104
+ | 周期、价格方式 | 可用来源 |
105
+ | --- | --- |
106
+ | 日线,原始价格 | `tencent`、`sina` |
107
+ | 日线,前复权或后复权 | `tencent` |
108
+ | 5 / 15 / 30 分钟线,原始价格 | `sina` |
109
+
110
+ `sources` 支持调整顺序或仅保留所需来源。新浪提供最近记录;条数上限截断所请求的起始日期时报告来源失败。所有兼容来源失败时抛出 `AllSourcesFailedError`。请求的周期与复权方式缺少兼容来源时抛出 `ConfigurationError`。
111
+
112
+ ```python
113
+ from ashare_data import get_etf_history
114
+
115
+ daily = get_etf_history("159941", "2026-10-01", "2026-10-23")
116
+ minute = get_etf_history("159941", "2026-10-23", "2026-10-23", scale=5)
117
+ adjusted = get_etf_history("159941", "2026-01-01", "2026-10-23", adjust="qfq")
118
+ preferred = get_etf_history(
119
+ "159941", "2026-10-01", "2026-10-23", sources=["sina", "tencent"],
120
+ )
121
+ print(daily[-1].close)
122
+ ```
123
+
124
+ ### Bar
125
+
126
+ 沪深历史与海外历史共用 `Bar`。
127
+
128
+ | 字段 | 类型 | 含义 |
129
+ | --- | --- | --- |
130
+ | `symbol` | `str` | 沪深为市场前缀代码,海外为服务商证券代码 |
131
+ | `timestamp` | `datetime` | K 线记录的日期或时间;日线为所属交易日零点 |
132
+ | `interval` | `str` | 周期,如 `1day`、`5min` |
133
+ | `source` | `str` | 本次实际来源 |
134
+ | `adjust` | `str` | 实际价格方式:`none`、`qfq`、`hfq`、`all`、`splits`、`dividends` |
135
+ | `open` / `high` / `low` / `close` | `float \| None` | 开盘 / 最高 / 最低 / 收盘价格;成功记录的收盘价有值 |
136
+ | `volume` | `float \| None` | 该周期成交数量 |
137
+ | `volume_unit` | `str` | 沪深为 `share`:股或份;海外为 `provider`:服务商对该品种定义的成交量单位 |
138
+ | `currency` | `str \| None` | 标价币种;沪深为 `CNY`,海外随品种确定 |
139
+
140
+ ## 4. 基金净值、目录与份额
141
+
142
+ | 函数 | 签名 | 返回 |
143
+ | --- | --- | --- |
144
+ | `get_fund_nav_history` | `(code, start=None, end=None, *, sources=None)` | `list[FundNav]` |
145
+ | `get_etf_catalog` | `()` | `dict[str, str]`,六位 ETF 代码到名称的映射 |
146
+ | `get_sse_etf_shares` | `(day)`,统计日期 | `list[FundShares]`,上交所 ETF 份额 |
147
+
148
+ 净值的默认来源顺序为 `["eastmoney_mobile", "eastmoney_web"]`,可用 `sources` 调整优先级。移动端失败后,网页接口重新获取完整区间;同一客户端自动管理请求间隔和冷却状态。
149
+
150
+ ```python
151
+ from ashare_data import get_fund_nav_history, get_etf_catalog, get_sse_etf_shares
152
+
153
+ nav = get_fund_nav_history("159941", "2026-10-01", "2026-10-23")
154
+ print(nav[-1].date, nav[-1].nav)
155
+ catalog = get_etf_catalog()
156
+ shares = get_sse_etf_shares("2026-10-23")
157
+ ```
158
+
159
+ `FundNav` 与 `FundShares` 的字段如下。
160
+
161
+ | 字段 | 类型 | 所属类型与含义 |
162
+ | --- | --- | --- |
163
+ | `code` | `str` | 两者共用,六位基金代码 |
164
+ | `date` | `date` | 净值所属日 / 份额统计日 |
165
+ | `source` | `str` | 本次实际来源 |
166
+ | `nav` | `float` | FundNav 单位净值,币种随基金份额类别确定 |
167
+ | `shares` | `float \| None` | FundShares 基金份额,单位份 |
168
+
169
+ ## 5. FRED 与海外行情
170
+
171
+ | 函数 | 签名 | 返回 |
172
+ | --- | --- | --- |
173
+ | `get_fred_series` | `(series_id, start=None, end=None, *, api_key=None)` | `list[Observation]` |
174
+ | `get_vix_history` | `(start=None, end=None, *, api_key=None)` | `list[Observation]`,VIX 日度收盘序列 `VIXCLS` |
175
+ | `get_twelve_bars` | `(symbol, *, interval="1day", outputsize=5000, start=None, end=None, adjust="all", api_key=None)` | 一页 `list[Bar]` |
176
+ | `get_twelve_daily_history` | `(symbol, start=None, end=None, *, adjust="all", api_key=None, max_pages=100)` | 自动分页的 `list[Bar]` |
177
+
178
+ FRED 密钥环境变量为 `FRED_API_KEY`,Twelve Data 为 `TWELVE_DATA_API_KEY`。密钥优先级:单次 `api_key` → 客户端配置 → 环境变量。
179
+
180
+ Twelve Data 的 `outputsize` 为 1~5000;`interval` 按账户支持的周期传入;`adjust` 可为 `none`、`splits`、`dividends`、`all`。`get_twelve_bars` 的起止参数可使用日期或日期时间字符串;完整日线的起止参数使用日期。可用市场、历史长度和调用额度由账户权限决定。
181
+
182
+ ```python
183
+ from ashare_data import MarketClient
184
+
185
+ client = MarketClient(fred_api_key="YOUR_FRED_KEY", twelve_api_key="YOUR_TWELVE_KEY")
186
+ vix = client.get_vix_history("2026-10-01", "2026-10-23")
187
+ spy = client.get_twelve_daily_history("SPY", "2026-01-01", "2026-10-23")
188
+ ```
189
+
190
+ `Observation` 表示序列的单次有效观测。
191
+
192
+ | 字段 | 类型 | 含义 |
193
+ | --- | --- | --- |
194
+ | `series_id` | `str` | FRED 序列标识 |
195
+ | `date` | `date` | 观测日期 |
196
+ | `value` | `float` | 有效观测值,单位由该 FRED 序列定义 |
197
+ | `source` | `str` | 本次实际来源 |
198
+
199
+ ## 6. 配置、客户端与异常
200
+
201
+ `configure` 签名为 `(*, timeout=10, retries=1, fred_api_key=None, twelve_api_key=None) -> MarketClient`,设置顶层函数使用的默认客户端。`timeout` 为每次网络请求超时秒数,须大于 0;`retries` 为网络异常及临时网关错误的重试次数,须为非负整数。
202
+
203
+ `MarketClient` 使用相同配置参数,实例方法与顶层函数同名、同参数、同返回类型。`AsyncMarketClient` 提供对应的 `await` 方法。实例间配置独立;同一客户端复用净值限速和冷却状态。异步取消后,已开始的网络请求由超时约束。
204
+
205
+ ```python
206
+ import asyncio
207
+ from ashare_data import AsyncMarketClient, configure
208
+
209
+ configure(timeout=10, retries=1)
210
+
211
+ async def main():
212
+ client = AsyncMarketClient(timeout=10)
213
+ quote = await client.get_etf_quote("159941")
214
+ print(quote.last)
215
+
216
+ asyncio.run(main())
217
+ ```
218
+
219
+ | 异常 | 含义 |
220
+ | --- | --- |
221
+ | `MarketDataError` | 全部公开行情异常的父类 |
222
+ | `InvalidCodeError` | 代码格式错误 |
223
+ | `ConfigurationError` | 参数、来源能力或密钥配置错误 |
224
+ | `RequestError` | 网络或 HTTP 错误;`status_code` 为 HTTP 状态码,`retry_after` 为建议等待秒数 |
225
+ | `DataFormatError` | 来源响应或分页结果异常 |
226
+ | `QuoteNotFoundError` | 单只证券行情缺失 |
227
+ | `AllSourcesFailedError` | 所有候选来源失败;`errors` 为按尝试顺序排列的 `(来源名, 异常)` 列表 |
228
+
@@ -0,0 +1,44 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ashare-data-lite"
7
+ dynamic = ["version"]
8
+ description = "使用 Python 标准库直接获取证券行情、基金净值与宏观序列"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ dependencies = []
14
+ keywords = ["ashare", "stock", "etf", "market-data", "finance"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Operating System :: OS Independent",
24
+ ]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/zhangjw777/ashare-data-lite"
28
+ Repository = "https://github.com/zhangjw777/ashare-data-lite"
29
+ Documentation = "https://github.com/zhangjw777/ashare-data-lite/blob/main/docs/api.md"
30
+ Issues = "https://github.com/zhangjw777/ashare-data-lite/issues"
31
+
32
+ [project.optional-dependencies]
33
+ dev = ["build>=1.2", "twine>=6", "PyYAML>=6"]
34
+
35
+ [tool.setuptools.dynamic]
36
+ version = {attr = "ashare_data.__version__"}
37
+
38
+ [tool.setuptools.packages.find]
39
+ where = ["src"]
40
+ include = ["ashare_data*"]
41
+
42
+ [tool.setuptools.package-data]
43
+ ashare_data = ["py.typed"]
44
+
@@ -0,0 +1,109 @@
1
+ """验证发行包并创建不可变版本标签;推送后由 GitHub Actions 发布到 PyPI。"""
2
+
3
+ import argparse
4
+ import json
5
+ from pathlib import Path
6
+ import re
7
+ import subprocess
8
+ import sys
9
+ import tempfile
10
+ from urllib.error import HTTPError
11
+ from urllib.request import urlopen
12
+
13
+ from validate_release import ROOT, project_version, validate
14
+
15
+
16
+ def git(*args):
17
+ result = subprocess.run(
18
+ ["git", *args], cwd=ROOT, text=True, capture_output=True, check=True
19
+ )
20
+ return result.stdout.strip()
21
+
22
+
23
+ def release(version, check_only=False):
24
+ if not re.fullmatch(r"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)", version):
25
+ raise RuntimeError("版本号使用 X.Y.Z 格式")
26
+ if git("status", "--porcelain"):
27
+ raise RuntimeError("请先审阅并提交代码改动,再执行发布命令")
28
+ branch = git("branch", "--show-current")
29
+ if branch != "main":
30
+ raise RuntimeError("发布命令在 main 分支执行")
31
+ git("fetch", "origin", "--tags")
32
+ if (
33
+ not subprocess.run(
34
+ ["git", "merge-base", "--is-ancestor", "origin/main", "HEAD"], cwd=ROOT
35
+ ).returncode
36
+ == 0
37
+ ):
38
+ raise RuntimeError("请先整合 origin/main 的最新提交")
39
+ tag = f"v{version}"
40
+ if tag in git("tag", "--list").splitlines():
41
+ raise RuntimeError(f"版本标签已存在:{tag},请选择新版本")
42
+ try:
43
+ with urlopen(
44
+ "https://pypi.org/pypi/ashare-data-lite/json", timeout=15
45
+ ) as response:
46
+ versions = json.load(response)["releases"]
47
+ except HTTPError as exc:
48
+ if exc.code != 404:
49
+ raise
50
+ versions = {}
51
+ if version in versions:
52
+ raise RuntimeError(f"PyPI 版本已存在:{version}")
53
+ stable = [
54
+ tuple(map(int, item.split(".")))
55
+ for item in versions
56
+ if re.fullmatch(r"\d+\.\d+\.\d+", item)
57
+ ]
58
+ if stable and tuple(map(int, version.split("."))) <= max(stable):
59
+ raise RuntimeError("新版本应高于当前 PyPI 稳定版本")
60
+ file = ROOT / "src" / "ashare_data" / "__init__.py"
61
+ previous = file.read_text(encoding="utf-8")
62
+ changed = version != project_version()
63
+ if changed:
64
+ file.write_text(
65
+ re.sub(
66
+ r'^__version__ = "[^"]+"',
67
+ f'__version__ = "{version}"',
68
+ previous,
69
+ count=1,
70
+ flags=re.MULTILINE,
71
+ ),
72
+ encoding="utf-8",
73
+ )
74
+ try:
75
+ with tempfile.TemporaryDirectory(prefix="ashare-release-") as directory:
76
+ validate(Path(directory) / "dist")
77
+ except BaseException:
78
+ if changed:
79
+ file.write_text(previous, encoding="utf-8")
80
+ raise
81
+ if check_only:
82
+ if changed:
83
+ file.write_text(previous, encoding="utf-8")
84
+ print("PASS release preflight")
85
+ return
86
+ if changed:
87
+ git("add", "src/ashare_data/__init__.py")
88
+ git("commit", "-m", f"Release {version}")
89
+ git("push", "origin", "HEAD:main")
90
+ git("tag", "-a", tag, "-m", f"ashare-data-lite {version}")
91
+ git("push", "origin", f"refs/tags/{tag}")
92
+ print(
93
+ "已触发发布:https://github.com/zhangjw777/ashare-data-lite/actions/workflows/publish.yml"
94
+ )
95
+ print(
96
+ f"发布后验证:python -m pip install --index-url https://pypi.org/simple ashare-data-lite=={version}"
97
+ )
98
+
99
+
100
+ if __name__ == "__main__":
101
+ parser = argparse.ArgumentParser(description=__doc__)
102
+ parser.add_argument("version")
103
+ parser.add_argument("--check-only", action="store_true")
104
+ args = parser.parse_args()
105
+ try:
106
+ release(args.version, args.check_only)
107
+ except (RuntimeError, subprocess.CalledProcessError) as exc:
108
+ print(f"发布中止:{exc}", file=sys.stderr)
109
+ raise SystemExit(1)