qdc 0.2.2__tar.gz → 0.2.4__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.
@@ -1,201 +1,242 @@
1
- Metadata-Version: 2.4
2
- Name: qdc
3
- Version: 0.2.2
4
- Summary: QDC online data client (tushare-compatible) for the QDC cloud data bridge.
5
- Author: QDC Data
6
- License: Proprietary
7
- Project-URL: Homepage, https://data.qdc-data.com
8
- Requires-Python: >=3.8
9
- Description-Content-Type: text/markdown
10
- Requires-Dist: requests>=2.25
11
- Requires-Dist: pandas>=1.1
12
-
13
- # qdc
14
-
15
- QDC「云端数据桥」的 Python 连接器(`https://data.qdc-data.com`)。
16
- 返回 pandas DataFrame,并带一层 **tushare 兼容壳**,重叠接口零迁移。
17
-
18
- ## 安装
19
-
20
- ```bash
21
- pip install qdc
22
- ```
23
-
24
- 升级:
25
-
26
- ```bash
27
- pip install -U qdc
28
- ```
29
-
30
- 内网或离线环境连不上公共源时,可以按完整地址装(版本号要自己跟着换):
31
-
32
- ```bash
33
- pip install https://api.qdc-data.com:8443/release/qdc-0.2.2-py3-none-any.whl
34
- ```
35
-
36
- > 2026-09-06 起 qdc 已发布在 PyPI(https://pypi.org/project/qdc/)。
37
- > **在此之前 `pip install qdc` 是假的** —— v0.1.0 的说明文件就这么写着,而包从没发出去过,
38
- > 客户照着敲只会得到 "No matching distribution found"。
39
-
40
- 需要 Python ≥ 3.8、`requests`、`pandas`(会自动装)。
41
-
42
- ## 三行上手
43
-
44
- ```python
45
- import qdc
46
- qdc.connect("你的API密钥", cache_dir="~/.qdc")
47
- df = qdc.daily("600519.SH", "20240101", "20240105") # -> pandas DataFrame
48
- ```
49
-
50
- `df` 的列:`ts_code, trade_date, open, high, low, close, vol, amount`(字段已对齐 tushare)。
51
-
52
- ## tushare 兼容用法
53
-
54
- ```python
55
- import qdc
56
- pro = qdc.pro_api("你的API密钥")
57
- df = pro.daily(ts_code="000001.SZ", start_date="20240101", end_date="20241231")
58
- df = pro.moneyflow(ts_code="600519.SH", start_date="20240101", end_date="20240131")
59
- df = pro.top_list(trade_date="20240105") # 龙虎榜
60
- df = pro.daily(ts_code="600519.SH", trade_date="20240105") # 只要某一天
61
- df = pro.income(ts_code="600519.SH", period="20231231") # 只要某个报告期
62
- df = pro.daily(ts_code="600519.SH", start_date="20240101", end_date="20240131",
63
- fields="ts_code,trade_date,close") # 只要几列
64
- # ★不认识的参数会当场报错,不会被静默忽略。
65
- ```
66
-
67
- ## 原生接口
68
-
69
- ```python
70
- c = qdc.connect(key, cache_dir="~/.qdc") # 或 qdc.Client(key)
71
-
72
- qdc.query(sku, code=, start=, end=, table=, limit=, fields=, date_field=, key=)
73
- qdc.daily(code, start, end, adjust=None) # adjust: None / "hfq" 后复权 / "qfq" 前复权
74
- qdc.moneyflow(code, start, end)
75
- qdc.lhb(code=None, date=None) # 龙虎榜
76
- qdc.financials(code, statement="income") # income 利润表 / balance 资产负债表 / cashflow 现金流量表
77
- qdc.index_daily(code, start, end) # 指数日线
78
- qdc.adj_factor(code, start, end) # 复权因子(稀疏表,见下)
79
- qdc.adj_factor_table() # 全市场复权因子整表(自动翻页 + 缓存)
80
-
81
- qdc.universe(st=False, only_st=False, list_days_gt=None, amount_gt=None, index=None, industry=None)
82
- # st=True 是【包含】ST(默认 False=剔除);要只看 ST 用 only_st=True
83
- # 返回 CodeList:用起来就是 list,额外带 .index_as_of(用的哪一份成分快照)/ .as_of / .meta
84
- qdc.lookup("茅台") # 名称/代码模糊查
85
- qdc.describe("cn_daily_full") # 字段/口径/覆盖范围
86
- ```
87
-
88
- ## 三件必须知道的事
89
-
90
- ### 1. 你拿到的是不是全部 —— 看 `df.attrs["qdc"]`
91
-
92
- 服务端单次最多返回 10,000 行(你这一档的 `max_rows`)。**本连接器会自动翻页取全**,
93
- 并把过程记在 `df.attrs["qdc"]` 里:
94
-
95
- ```python
96
- df = qdc.query("stocks_1m", code="002027.SZ", start="20240101", end="20241231")
97
- print(len(df)) # 58322 —— 全年真实行数
98
- print(df.attrs["qdc"]["pages"]) # 6 —— 翻了 6 页
99
- print(df.attrs["qdc"]["complete"]) # True —— 取完了
100
- ```
101
-
102
- `complete` 为 `False` 只会在撞到 `max_pages` 上限时出现,那时同时会抛一条警告。
103
-
104
- > ★ v0.1.0 **不翻页**:上面这个查询它只交给你第一页 10,000 行,HTTP 200、没有任何提示 ——
105
- > 少 82.9% 的数据,而你的回测不会告诉你。这是升级到 0.2.0 最重要的理由。
106
-
107
- ### 2. 复权是在本地算的
108
-
109
- 原「A股日线(后复权)」商品已于 2026-08-09 退役(它是纯派生数据)。现在:
110
-
111
- - `adjust="hfq"` = 取不复权日线 + 复权因子,本地按 `不复权价 × 复权因子` 还原;
112
- - `adjust="qfq"` = 后复权再除以该票最新因子(以最新价为基准);
113
- - 成交量与成交额**不参与复权**(退役登记实测:后复权的 vol/amount 与不复权完全相同)。
114
-
115
- 因子表是**稀疏**的(只在除权日有记录),按代码前向填充;整表未收录的股票表示从未除权,按因子 = 1。
116
- 如果某票在你这一档深度内取不到「该交易日之前最近一条」因子,对应行的价格会置为 `NaN` 并告警 ——
117
- **不会**按因子 = 1 蒙混过去给你一个错价。
118
-
119
- 因子表整表约 5.4 万行,`connect(cache_dir=...)` 之后会缓存到本地,不必每次重取。
120
-
121
- ### 3. 限额:同一只票每台设备每天只能取一次
122
-
123
- 去重键是 `(密钥, 设备, 代码, 日期)`,**不含数据集也不含表名**。所以:
124
-
125
- - 先 `daily("600519.SH")` 再 `adj_factor("600519.SH")` → 第二次必然 429。
126
- (正因为如此,`adjust="hfq"` 才用**不带代码的整表**去取因子 —— 那条路不计次。)
127
- - `financials(code, "income")` 之后当天再取同一只票的 `balance` 也会 429。
128
- 三张表要分三天取,或者走整包下载。
129
- - **翻页不重复计次**:第 2 页起算同一次取数。
130
-
131
- `device` 必须是**固定不变**的串。每次现生成新串会把设备槽位一个个占满;
132
- 不传时本连接器会按主机名 + 网卡地址生成一个稳定值。
133
-
134
- ## 约定
135
-
136
- - 代码格式:`600519.SH` / `000001.SZ`(内部 `.XSHG/.XSHE/.XBSE` 会自动归一,大小写不敏感)。
137
- - 日期:`YYYYMMDD` 字符串。
138
-
139
- ## 错误
140
-
141
- HTTP `401`(没有密钥)、`403`(未授权的数据集 / 设备数超限)、`429`(限额)等一律抛 `qdc.QDCError`,
142
- 带服务端给的 `message` 和 `.status_code`。
143
-
144
- ```python
145
- try:
146
- qdc.daily("600519.SH", "20240101", "20240105")
147
- except qdc.QDCError as e:
148
- print(e.status_code, e.message)
149
- ```
150
-
151
- ## 版本
152
-
153
- ### 0.2.2(2026-09-07)
154
-
155
- 三处,前两处都属于「不报错、只给错答案」:
156
-
157
- 1. **tushare 兼容层会静默丢掉参数。** `pro.daily(trade_date=...)`(只要某一天)、
158
- `pro.income(period=...)`(只要某个报告期)、`fields=`(只要几列)——这些参数以前被
159
- `**kwargs` 整个吞掉,不传也不报错,于是「只要一天」变成了整段历史。
160
- 现在认识的参数真的传下去,**不认识的当场报错**,不再有兜底。
161
- 2. **复权因子表没取全时仍会给出「完整」的结果。** 复权还原有一条推断:
162
- 「因子表里查不到这只票 = 它从未除权 = 因子按 1 算」——这条只有在因子表完整时才成立。
163
- 表缺了一截,除过权的票就会被按因子 1 处理,价格是错的而结果还标着已取完。
164
- 现在:因子表没取全直接报错,**残表绝不写进缓存**;缓存要带完整性标记才会被采信。
165
- 3. **`st` 参数的语义与说明相反。** 说明写的是「是否包含 ST 股」,实现却是「只选 ST」,
166
- 于是沪深300 加上 `st=True` 反而返回 0 只。现在 `st=True` 是**包含**、
167
- `st=False`(默认)是剔除,要只看 ST 用新增的 `only_st=True`。
168
-
169
- ### 0.2.1(2026-09-07)
170
-
171
- **按指数选股返回的是历史成分的并集,不是当前成分。** 服务端匹配成分表时没限定快照日期,
172
- 而那张表存着多份月度快照 —— 沪深300 返回 319 只(其中 19 只早已调出指数),
173
- 中证500 是 550 而不是 500,上证50 是 55 而不是 50。
174
- 客户拿它当选股池跑回测,池子里混着已调出的票,结果偏乐观而且无从发现。
175
- 现在钉在**该指数最新的那一份快照**上,并把成分日期带回来:
176
-
177
- ```python
178
- codes = qdc.universe(index="000300")
179
- len(codes) # 300
180
- codes.index_as_of # '2026-08-31' —— 这批成分是哪一天的
181
- ```
182
-
183
- 指数名或代码匹配不上时服务端会明确报错并说清楚,不会静默返回全市场。
184
-
185
- ### 0.2.0(2026-09-06)
186
-
187
- 三处都属于「不报错、只给错答案」,升级前请留意你既有代码的结论可能变:
188
-
189
- 1. **自动翻页**。之前只取第一页,一只票一年分钟线交给你 10,000 行(真实 58,322 行),零提示。
190
- 2. **复权**。之前 `adjust="hfq"` 打到已退役的 `cn_daily_hfq` 直接 422 报错;
191
- `adjust="qfq"` 更糟 —— 它静默返回**不复权**价。现在两者都在本地正确还原。
192
- 3. **财报表名**。之前 `statement="balance"` 打到不存在的表名 `balancesheet`,
193
- 服务端静默回退到默认表,于是你要资产负债表、拿到的是**利润表**,HTTP 200 无提示。
194
- 现在表名在本地校验,拼错当场报错。
195
-
196
- 另外:`index_daily` 之前指向 A 股日线表,查指数稳定返回 0 行,现已改指「指数日线」;
197
- 查询已退役的数据集会在本地给出继任者和迁移办法,而不是一句 `unknown sku`。
198
-
199
- ### 0.1.0
200
-
201
- 首个版本(从未对外分发)。
1
+ Metadata-Version: 2.4
2
+ Name: qdc
3
+ Version: 0.2.4
4
+ Summary: QDC online data client (tushare-compatible) for the QDC cloud data bridge.
5
+ Author: QDC Data
6
+ License: Proprietary
7
+ Project-URL: Homepage, https://data.qdc-data.com
8
+ Requires-Python: >=3.8
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: requests>=2.25
11
+ Requires-Dist: pandas>=1.1
12
+
13
+ # qdc
14
+
15
+ QDC「云端数据桥」的 Python 连接器(`https://data.qdc-data.com`)。
16
+ 返回 pandas DataFrame,并带一层 **tushare 兼容壳**,重叠接口零迁移。
17
+
18
+ ## 安装
19
+
20
+ ```bash
21
+ pip install qdc
22
+ ```
23
+
24
+ 升级:
25
+
26
+ ```bash
27
+ pip install -U qdc
28
+ ```
29
+
30
+ 内网或离线环境连不上公共源时,可以按完整地址装(版本号要自己跟着换):
31
+
32
+ ```bash
33
+ pip install https://api.qdc-data.com:8443/release/qdc-0.2.3-py3-none-any.whl
34
+ ```
35
+
36
+ > 2026-09-06 起 qdc 已发布在 PyPI(https://pypi.org/project/qdc/)。
37
+ > **在此之前 `pip install qdc` 是假的** —— v0.1.0 的说明文件就这么写着,而包从没发出去过,
38
+ > 客户照着敲只会得到 "No matching distribution found"。
39
+
40
+ 需要 Python ≥ 3.8、`requests`、`pandas`(会自动装)。
41
+
42
+ ## 三行上手
43
+
44
+ ```python
45
+ import qdc
46
+ qdc.connect("你的API密钥", cache_dir="~/.qdc")
47
+ df = qdc.daily("600519.SH", "20240101", "20240105") # -> pandas DataFrame
48
+ ```
49
+
50
+ `df` 的列:`ts_code, trade_date, open, high, low, close, vol, amount`(字段已对齐 tushare)。
51
+
52
+ ## tushare 兼容用法
53
+
54
+ ```python
55
+ import qdc
56
+ pro = qdc.pro_api("你的API密钥")
57
+ df = pro.daily(ts_code="000001.SZ", start_date="20240101", end_date="20241231")
58
+ df = pro.moneyflow(ts_code="600519.SH", start_date="20240101", end_date="20240131")
59
+ df = pro.top_list(trade_date="20240105") # 龙虎榜
60
+ df = pro.daily(ts_code="600519.SH", trade_date="20240105") # 只要某一天
61
+ df = pro.income(ts_code="600519.SH", period="20231231") # 只要某个报告期
62
+ df = pro.daily(ts_code="600519.SH", start_date="20240101", end_date="20240131",
63
+ fields="ts_code,trade_date,close") # 只要几列
64
+ # ★不认识的参数会当场报错,不会被静默忽略。
65
+ ```
66
+
67
+ ## 原生接口
68
+
69
+ ```python
70
+ c = qdc.connect(key, cache_dir="~/.qdc") # 或 qdc.Client(key)
71
+
72
+ qdc.query(sku, code=, start=, end=, table=, limit=, fields=, date_field=, key=)
73
+ qdc.daily(code, start, end, adjust=None) # adjust: None / "hfq" 后复权 / "qfq" 前复权
74
+ qdc.moneyflow(code, start, end)
75
+ qdc.lhb(code=None, date=None) # 龙虎榜
76
+ qdc.financials(code, statement="income") # income 利润表 / balance 资产负债表 / cashflow 现金流量表
77
+ qdc.index_daily(code, start, end) # 指数日线
78
+ qdc.adj_factor(code, start, end) # 复权因子(稀疏表,见下)
79
+ qdc.adj_factor_table() # 全市场复权因子整表(自动翻页 + 缓存)
80
+
81
+ qdc.universe(st=False, only_st=False, list_days_gt=None, amount_gt=None, index=None, industry=None)
82
+ # st=True 是【包含】ST(默认 False=剔除);要只看 ST 用 only_st=True
83
+ # 返回 CodeList:用起来就是 list,额外带 .index_as_of(用的哪一份成分快照)/ .as_of / .meta
84
+ qdc.lookup("茅台") # 名称/代码模糊查
85
+ qdc.describe("cn_daily_full") # 字段/口径/覆盖范围
86
+ ```
87
+
88
+ ## 在线计算(0.2.4 起):服务端算好,只把结果传回来
89
+
90
+ 不用把几百万行逐笔搬回本地再自己加总 —— 服务端现算:
91
+
92
+ ```python
93
+ # 合成K线:逐笔 → 1m/5m/15m/30m/60m/1d/1w/1M;1分钟线 → 5m~1M;日线 → 1w/1M
94
+ qdc.bars("cn_tick", "600519", "20240603", "20240607", freq="5m")
95
+ qdc.bars("stocks_1m", ["600519", "000001"], "20240101", "20241231", freq="60m")
96
+
97
+ # 区间统计:开高低收、成交量(股)、成交额(元)、笔数、均价、涨跌幅%、振幅%
98
+ qdc.stats("cn_daily_full", "600519", "20240101", "20241231", metrics=["close", "pct_chg", "amplitude"])
99
+ qdc.stats("cn_tick", "600519", "20240603", "20240630", group="day") # 每天一行
100
+
101
+ # 逐笔条件统计(高级版):10 点前、主动买入、单笔 ≥500 手 的成交量和笔数
102
+ qdc.stats("cn_tick", "600519", "20240603", "20240603", metrics=["volume", "count"],
103
+ side="buy", min_lots=500, time_to="10:00")
104
+ ```
105
+
106
+ - 计费:按**扫描的原始行数**计入当天条数额度(`df.attrs["qdc"]["scanned_rows"]`),与 `query` 共用。
107
+ - 一次能算多大:基础版逐笔「只数×天数」≤ 92(约 3 个月)、最多 3 只、不含条件统计;
108
+ 高级版 ≤ 731(约 2 年)、最多 10 只。分钟线一次 ≤ 1 年(高级版 2 年),日线不限。超了会 422 并告诉你怎么分段。
109
+ - 服务端为保护机器,同一时刻只并行算 2 条;忙时返回 429「在线计算此刻正忙」,几秒后重试即可。
110
+
111
+ ## 三件必须知道的事
112
+
113
+ ### 1. 你拿到的是不是全部 —— 看 `df.attrs["qdc"]`
114
+
115
+ 服务端单次最多返回 10,000 行(你这一档的 `max_rows`)。**本连接器会自动翻页取全**,
116
+ 并把过程记在 `df.attrs["qdc"]` 里:
117
+
118
+ ```python
119
+ df = qdc.query("stocks_1m", code="002027.SZ", start="20240101", end="20241231")
120
+ print(len(df)) # 58322 —— 全年真实行数
121
+ print(df.attrs["qdc"]["pages"]) # 6 —— 翻了 6 页
122
+ print(df.attrs["qdc"]["complete"]) # True —— 取完了
123
+ ```
124
+
125
+ `complete` 为 `False` 只会在撞到 `max_pages` 上限时出现,那时同时会抛一条警告。
126
+
127
+ > ★ v0.1.0 **不翻页**:上面这个查询它只交给你第一页 10,000 行,HTTP 200、没有任何提示 ——
128
+ > 少 82.9% 的数据,而你的回测不会告诉你。这是升级到 0.2.0 最重要的理由。
129
+
130
+ ### 2. 复权是在本地算的
131
+
132
+ 原「A股日线(后复权)」商品已于 2026-08-09 退役(它是纯派生数据)。现在:
133
+
134
+ - `adjust="hfq"` = 取不复权日线 + 复权因子,本地按 `不复权价 × 复权因子` 还原;
135
+ - `adjust="qfq"` = 后复权再除以该票最新因子(以最新价为基准);
136
+ - 成交量与成交额**不参与复权**(退役登记实测:后复权的 vol/amount 与不复权完全相同)。
137
+
138
+ 因子表是**稀疏**的(只在除权日有记录),按代码前向填充;整表未收录的股票表示从未除权,按因子 = 1。
139
+ 如果某票在你这一档深度内取不到「该交易日之前最近一条」因子,对应行的价格会置为 `NaN` 并告警 ——
140
+ **不会**按因子 = 1 蒙混过去给你一个错价。
141
+
142
+ 因子表整表约 5.4 万行,`connect(cache_dir=...)` 之后会缓存到本地,不必每次重取。
143
+
144
+ ### 3. 限额:按【每天累计取数条数】计,不是按次数
145
+
146
+ 同一只票可以反复查、可以先试探再重取;翻页取回的行照常计入当天条数。
147
+ 每次返回的 `meta` 里有 `daily_rows_used / daily_rows_limit`,照着它安排节奏,不要等撞上 429。
148
+ (旧版说明里的「同一只票每台设备每天只能取一次」已退役。)
149
+
150
+ `device` 必须是**固定不变**的串。每次现生成新串会把设备槽位一个个占满;
151
+ 不传时本连接器会按主机名 + 网卡地址生成一个稳定值。
152
+
153
+ ## 约定
154
+
155
+ - 代码格式:`600519.SH` / `000001.SZ`(内部 `.XSHG/.XSHE/.XBSE` 会自动归一,大小写不敏感)。
156
+ - 日期:`YYYYMMDD` 字符串。
157
+
158
+ ## 错误
159
+
160
+ HTTP `401`(没有密钥)、`403`(未授权的数据集 / 设备数超限)、`429`(限额)等一律抛 `qdc.QDCError`,
161
+ 带服务端给的 `message` 和 `.status_code`。
162
+
163
+ ```python
164
+ try:
165
+ qdc.daily("600519.SH", "20240101", "20240105")
166
+ except qdc.QDCError as e:
167
+ print(e.status_code, e.message)
168
+ ```
169
+
170
+ ## 版本
171
+
172
+ ### 0.2.4(2026-09-30)
173
+
174
+ 新增 `bars()` / `stats()` 在线计算(服务端现算,只回结果),见上文「在线计算」。
175
+
176
+ ### 0.2.3(2026-09-07)
177
+
178
+ **默认超时从 30 秒放宽到 180 秒。** 服务端高档位单条查询就允许 60 秒,前面还可能排队等并发槽 ——
179
+ 30 秒会让客户端在服务端还在干活时先断开,表现成「网络错误」,而那次取数机会已经扣掉了。
180
+ 断开时服务端会立刻停止查询,所以放宽它不会让服务器白干。
181
+
182
+ 同批服务端修复(不用升级连接器也生效):
183
+
184
+ - **失败的请求不再扣掉当天的取数机会。** 以前字段名写错先 422、改正后重试就 429,
185
+ 一次数据没拿到、机会没了。现在任何失败都会把那次记账撤销。
186
+ - **`start > end` 直接报错**,不再返回 200 + 0 行 —— 0 行和「真的没数据」长得一模一样。
187
+ - **完整代码能搜到了。** 以前 `lookup("600519")` 有结果而 `lookup("600519.SH")` 没有 ——
188
+ 本工具自己吐出来的代码自己不认。同时返回里标明**只检索 A 股**,查美股为空时会说清是范围问题。
189
+ - **字段字典的日期类型订正为字符串。** 库内是毫秒时间戳,返回前已格式化成 `"20260803"`,
190
+ 而字段字典此前照抄原始列类型报的是整数,客户照着解析会崩。
191
+ - **逐笔成交取「最新一笔」不再取错。** 排序键在笔内不唯一,拿到的是开盘集合竞价那笔;
192
+ 现已补上笔序号排序。
193
+
194
+ ### 0.2.2(2026-09-07)
195
+
196
+ 三处,前两处都属于「不报错、只给错答案」:
197
+
198
+ 1. **tushare 兼容层会静默丢掉参数。** `pro.daily(trade_date=...)`(只要某一天)、
199
+ `pro.income(period=...)`(只要某个报告期)、`fields=`(只要几列)——这些参数以前被
200
+ `**kwargs` 整个吞掉,不传也不报错,于是「只要一天」变成了整段历史。
201
+ 现在认识的参数真的传下去,**不认识的当场报错**,不再有兜底。
202
+ 2. **复权因子表没取全时仍会给出「完整」的结果。** 复权还原有一条推断:
203
+ 「因子表里查不到这只票 = 它从未除权 = 因子按 1 算」——这条只有在因子表完整时才成立。
204
+ 表缺了一截,除过权的票就会被按因子 1 处理,价格是错的而结果还标着已取完。
205
+ 现在:因子表没取全直接报错,**残表绝不写进缓存**;缓存要带完整性标记才会被采信。
206
+ 3. **`st` 参数的语义与说明相反。** 说明写的是「是否包含 ST 股」,实现却是「只选 ST」,
207
+ 于是沪深300 加上 `st=True` 反而返回 0 只。现在 `st=True` 是**包含**、
208
+ `st=False`(默认)是剔除,要只看 ST 用新增的 `only_st=True`。
209
+
210
+ ### 0.2.1(2026-09-07)
211
+
212
+ **按指数选股返回的是历史成分的并集,不是当前成分。** 服务端匹配成分表时没限定快照日期,
213
+ 而那张表存着多份月度快照 —— 沪深300 返回 319 只(其中 19 只早已调出指数),
214
+ 中证500 是 550 而不是 500,上证50 是 55 而不是 50。
215
+ 客户拿它当选股池跑回测,池子里混着已调出的票,结果偏乐观而且无从发现。
216
+ 现在钉在**该指数最新的那一份快照**上,并把成分日期带回来:
217
+
218
+ ```python
219
+ codes = qdc.universe(index="000300")
220
+ len(codes) # 300
221
+ codes.index_as_of # '2026-08-31' —— 这批成分是哪一天的
222
+ ```
223
+
224
+ 指数名或代码匹配不上时服务端会明确报错并说清楚,不会静默返回全市场。
225
+
226
+ ### 0.2.0(2026-09-06)
227
+
228
+ 三处都属于「不报错、只给错答案」,升级前请留意你既有代码的结论可能变:
229
+
230
+ 1. **自动翻页**。之前只取第一页,一只票一年分钟线交给你 10,000 行(真实 58,322 行),零提示。
231
+ 2. **复权**。之前 `adjust="hfq"` 打到已退役的 `cn_daily_hfq` 直接 422 报错;
232
+ `adjust="qfq"` 更糟 —— 它静默返回**不复权**价。现在两者都在本地正确还原。
233
+ 3. **财报表名**。之前 `statement="balance"` 打到不存在的表名 `balancesheet`,
234
+ 服务端静默回退到默认表,于是你要资产负债表、拿到的是**利润表**,HTTP 200 无提示。
235
+ 现在表名在本地校验,拼错当场报错。
236
+
237
+ 另外:`index_daily` 之前指向 A 股日线表,查指数稳定返回 0 行,现已改指「指数日线」;
238
+ 查询已退役的数据集会在本地给出继任者和迁移办法,而不是一句 `unknown sku`。
239
+
240
+ ### 0.1.0
241
+
242
+ 首个版本(从未对外分发)。
@@ -1,15 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: qdc
3
- Version: 0.2.2
4
- Summary: QDC online data client (tushare-compatible) for the QDC cloud data bridge.
5
- Author: QDC Data
6
- License: Proprietary
7
- Project-URL: Homepage, https://data.qdc-data.com
8
- Requires-Python: >=3.8
9
- Description-Content-Type: text/markdown
10
- Requires-Dist: requests>=2.25
11
- Requires-Dist: pandas>=1.1
12
-
13
1
  # qdc
14
2
 
15
3
  QDC「云端数据桥」的 Python 连接器(`https://data.qdc-data.com`)。
@@ -30,7 +18,7 @@ pip install -U qdc
30
18
  内网或离线环境连不上公共源时,可以按完整地址装(版本号要自己跟着换):
31
19
 
32
20
  ```bash
33
- pip install https://api.qdc-data.com:8443/release/qdc-0.2.2-py3-none-any.whl
21
+ pip install https://api.qdc-data.com:8443/release/qdc-0.2.3-py3-none-any.whl
34
22
  ```
35
23
 
36
24
  > 2026-09-06 起 qdc 已发布在 PyPI(https://pypi.org/project/qdc/)。
@@ -85,6 +73,29 @@ qdc.lookup("茅台") # 名称/代码模糊查
85
73
  qdc.describe("cn_daily_full") # 字段/口径/覆盖范围
86
74
  ```
87
75
 
76
+ ## 在线计算(0.2.4 起):服务端算好,只把结果传回来
77
+
78
+ 不用把几百万行逐笔搬回本地再自己加总 —— 服务端现算:
79
+
80
+ ```python
81
+ # 合成K线:逐笔 → 1m/5m/15m/30m/60m/1d/1w/1M;1分钟线 → 5m~1M;日线 → 1w/1M
82
+ qdc.bars("cn_tick", "600519", "20240603", "20240607", freq="5m")
83
+ qdc.bars("stocks_1m", ["600519", "000001"], "20240101", "20241231", freq="60m")
84
+
85
+ # 区间统计:开高低收、成交量(股)、成交额(元)、笔数、均价、涨跌幅%、振幅%
86
+ qdc.stats("cn_daily_full", "600519", "20240101", "20241231", metrics=["close", "pct_chg", "amplitude"])
87
+ qdc.stats("cn_tick", "600519", "20240603", "20240630", group="day") # 每天一行
88
+
89
+ # 逐笔条件统计(高级版):10 点前、主动买入、单笔 ≥500 手 的成交量和笔数
90
+ qdc.stats("cn_tick", "600519", "20240603", "20240603", metrics=["volume", "count"],
91
+ side="buy", min_lots=500, time_to="10:00")
92
+ ```
93
+
94
+ - 计费:按**扫描的原始行数**计入当天条数额度(`df.attrs["qdc"]["scanned_rows"]`),与 `query` 共用。
95
+ - 一次能算多大:基础版逐笔「只数×天数」≤ 92(约 3 个月)、最多 3 只、不含条件统计;
96
+ 高级版 ≤ 731(约 2 年)、最多 10 只。分钟线一次 ≤ 1 年(高级版 2 年),日线不限。超了会 422 并告诉你怎么分段。
97
+ - 服务端为保护机器,同一时刻只并行算 2 条;忙时返回 429「在线计算此刻正忙」,几秒后重试即可。
98
+
88
99
  ## 三件必须知道的事
89
100
 
90
101
  ### 1. 你拿到的是不是全部 —— 看 `df.attrs["qdc"]`
@@ -118,15 +129,11 @@ print(df.attrs["qdc"]["complete"]) # True —— 取完了
118
129
 
119
130
  因子表整表约 5.4 万行,`connect(cache_dir=...)` 之后会缓存到本地,不必每次重取。
120
131
 
121
- ### 3. 限额:同一只票每台设备每天只能取一次
122
-
123
- 去重键是 `(密钥, 设备, 代码, 日期)`,**不含数据集也不含表名**。所以:
132
+ ### 3. 限额:按【每天累计取数条数】计,不是按次数
124
133
 
125
- - 先 `daily("600519.SH")` 再 `adj_factor("600519.SH")` → 第二次必然 429。
126
- (正因为如此,`adjust="hfq"` 才用**不带代码的整表**去取因子 —— 那条路不计次。)
127
- - `financials(code, "income")` 之后当天再取同一只票的 `balance` 也会 429。
128
- 三张表要分三天取,或者走整包下载。
129
- - **翻页不重复计次**:第 2 页起算同一次取数。
134
+ 同一只票可以反复查、可以先试探再重取;翻页取回的行照常计入当天条数。
135
+ 每次返回的 `meta` 里有 `daily_rows_used / daily_rows_limit`,照着它安排节奏,不要等撞上 429。
136
+ (旧版说明里的「同一只票每台设备每天只能取一次」已退役。)
130
137
 
131
138
  `device` 必须是**固定不变**的串。每次现生成新串会把设备槽位一个个占满;
132
139
  不传时本连接器会按主机名 + 网卡地址生成一个稳定值。
@@ -150,6 +157,28 @@ except qdc.QDCError as e:
150
157
 
151
158
  ## 版本
152
159
 
160
+ ### 0.2.4(2026-09-30)
161
+
162
+ 新增 `bars()` / `stats()` 在线计算(服务端现算,只回结果),见上文「在线计算」。
163
+
164
+ ### 0.2.3(2026-09-07)
165
+
166
+ **默认超时从 30 秒放宽到 180 秒。** 服务端高档位单条查询就允许 60 秒,前面还可能排队等并发槽 ——
167
+ 30 秒会让客户端在服务端还在干活时先断开,表现成「网络错误」,而那次取数机会已经扣掉了。
168
+ 断开时服务端会立刻停止查询,所以放宽它不会让服务器白干。
169
+
170
+ 同批服务端修复(不用升级连接器也生效):
171
+
172
+ - **失败的请求不再扣掉当天的取数机会。** 以前字段名写错先 422、改正后重试就 429,
173
+ 一次数据没拿到、机会没了。现在任何失败都会把那次记账撤销。
174
+ - **`start > end` 直接报错**,不再返回 200 + 0 行 —— 0 行和「真的没数据」长得一模一样。
175
+ - **完整代码能搜到了。** 以前 `lookup("600519")` 有结果而 `lookup("600519.SH")` 没有 ——
176
+ 本工具自己吐出来的代码自己不认。同时返回里标明**只检索 A 股**,查美股为空时会说清是范围问题。
177
+ - **字段字典的日期类型订正为字符串。** 库内是毫秒时间戳,返回前已格式化成 `"20260803"`,
178
+ 而字段字典此前照抄原始列类型报的是整数,客户照着解析会崩。
179
+ - **逐笔成交取「最新一笔」不再取错。** 排序键在笔内不唯一,拿到的是开盘集合竞价那笔;
180
+ 现已补上笔序号排序。
181
+
153
182
  ### 0.2.2(2026-09-07)
154
183
 
155
184
  三处,前两处都属于「不报错、只给错答案」:
File without changes
@@ -28,11 +28,13 @@ from .client import (
28
28
  universe,
29
29
  lookup,
30
30
  describe,
31
+ bars,
32
+ stats,
31
33
  pro_api,
32
34
  ProApi,
33
35
  )
34
36
 
35
- __version__ = "0.2.2"
37
+ __version__ = "0.2.4"
36
38
 
37
39
  __all__ = [
38
40
  "Client",
@@ -50,6 +52,8 @@ __all__ = [
50
52
  "universe",
51
53
  "lookup",
52
54
  "describe",
55
+ "bars",
56
+ "stats",
53
57
  "pro_api",
54
58
  "ProApi",
55
59
  "__version__",
@@ -39,12 +39,21 @@ __all__ = [
39
39
  "describe",
40
40
  "pro_api",
41
41
  "ProApi",
42
+ "bars",
43
+ "stats",
42
44
  ]
43
45
 
44
46
  DEFAULT_BASE = "https://data.qdc-data.com"
45
- _VERSION = "0.2.2"
47
+ _VERSION = "0.2.4"
46
48
  _USER_AGENT = "qdc-python/" + _VERSION
47
49
 
50
+ # 默认超时。★[2026-09-07] 原来是 30 秒,而服务端高档位单条查询就允许 60 秒
51
+ # (limits.timeout_s),前面还可能排队等并发槽 —— 于是客户端会在服务端还在干活时先断,
52
+ # 表现成「网络错误」,而那次取数机会已经扣掉了。宁可等,也不要制造假故障。
53
+ # ★这个值只是「等多久才放弃」,不影响服务端算多久;真正的上限由服务端 limits 定。
54
+ # ★断连服务端会立刻停止查询(W-410),所以放宽它不会让服务器白干。
55
+ DEFAULT_TIMEOUT = 180
56
+
48
57
  # 翻页安全上限。到顶时【显式告警并标记 meta["complete"]=False】,绝不静默截断。
49
58
  _MAX_PAGES = 200
50
59
 
@@ -147,7 +156,7 @@ class Client:
147
156
  这是「我拿到的是不是全部」唯一可信的判据。
148
157
  """
149
158
 
150
- def __init__(self, key, base=DEFAULT_BASE, device=None, timeout=30,
159
+ def __init__(self, key, base=DEFAULT_BASE, device=None, timeout=DEFAULT_TIMEOUT,
151
160
  cache_dir=None, max_pages=_MAX_PAGES):
152
161
  if not key:
153
162
  raise QDCError("API key is required")
@@ -502,6 +511,40 @@ class Client:
502
511
  """Describe a sku/table: returns a dict {sku, table, fields, date_col, coverage}."""
503
512
  return self._get("/v1/describe", {"sku": sku, "table": table})
504
513
 
514
+ # ---- 在线计算(W-638,服务端现算,只回结果)-------------------------------
515
+ @staticmethod
516
+ def _codes(code):
517
+ if isinstance(code, (list, tuple, set)):
518
+ return ",".join(str(c) for c in code)
519
+ return code
520
+
521
+ def bars(self, sku, code, start, end, freq="1d"):
522
+ """在线合成K线。sku: cn_tick / stocks_1m / cn_daily_full;freq: 1m/5m/15m/30m/60m/1d/1w/1M。
523
+
524
+ 成交量=股、成交额=元、价格不复权。按扫描的原始行数计入当天条数额度
525
+ (df.attrs["qdc"]["scanned_rows"])。
526
+ """
527
+ body = self._get("/v1/bars", {"sku": sku, "code": self._codes(code), "start": start,
528
+ "end": end, "freq": freq})
529
+ return self._frame(body.get("rows") or [], dict(body.get("meta") or {}))
530
+
531
+ def stats(self, sku, code, start, end, metrics=None, group="total", min_lots=None,
532
+ max_lots=None, side=None, price_min=None, price_max=None, time_from=None,
533
+ time_to=None):
534
+ """在线区间统计。metrics 可选 open,high,low,close,volume,amount,count,vwap,pct_chg,amplitude。
535
+
536
+ group="day" 按天各一行。条件参数(min_lots/max_lots/side=buy|sell/price_min/price_max/
537
+ time_from/time_to "HH:MM")只对 cn_tick 且仅高级版开放。
538
+ """
539
+ if isinstance(metrics, (list, tuple)):
540
+ metrics = ",".join(metrics)
541
+ body = self._get("/v1/stats", {
542
+ "sku": sku, "code": self._codes(code), "start": start, "end": end,
543
+ "metrics": metrics, "group": group, "min_lots": min_lots, "max_lots": max_lots,
544
+ "side": side, "price_min": price_min, "price_max": price_max,
545
+ "time_from": time_from, "time_to": time_to})
546
+ return self._frame(body.get("rows") or [], dict(body.get("meta") or {}))
547
+
505
548
  # ---- tushare shim ----------------------------------------------------
506
549
  def pro_api(self):
507
550
  """Return a tushare-compatible facade bound to this client."""
@@ -641,7 +684,7 @@ def _require():
641
684
  return _default
642
685
 
643
686
 
644
- def connect(key, base=DEFAULT_BASE, device=None, timeout=30, cache_dir=None, max_pages=_MAX_PAGES):
687
+ def connect(key, base=DEFAULT_BASE, device=None, timeout=DEFAULT_TIMEOUT, cache_dir=None, max_pages=_MAX_PAGES):
645
688
  """Create and register a default Client. Returns the Client.
646
689
 
647
690
  cache_dir: 给复权因子表这类整表数据做本地缓存的目录(如 "~/.qdc")。不传则只在进程内缓存。
@@ -703,7 +746,14 @@ def describe(sku, table=None):
703
746
  return _require().describe(sku, table=table)
704
747
 
705
748
 
706
- def pro_api(key=None, base=DEFAULT_BASE, device=None, timeout=30, cache_dir=None):
749
+ def bars(sku, code, start, end, freq="1d"):
750
+ return _require().bars(sku, code, start, end, freq=freq)
751
+
752
+
753
+ def stats(sku, code, start, end, metrics=None, group="total", **cond):
754
+ return _require().stats(sku, code, start, end, metrics=metrics, group=group, **cond)
755
+
756
+ def pro_api(key=None, base=DEFAULT_BASE, device=None, timeout=DEFAULT_TIMEOUT, cache_dir=None):
707
757
  """Return a tushare-compatible facade.
708
758
 
709
759
  If `key` is given, a fresh Client is created (and registered as default);
@@ -1,189 +1,242 @@
1
- # qdc
2
-
3
- QDC「云端数据桥」的 Python 连接器(`https://data.qdc-data.com`)。
4
- 返回 pandas DataFrame,并带一层 **tushare 兼容壳**,重叠接口零迁移。
5
-
6
- ## 安装
7
-
8
- ```bash
9
- pip install qdc
10
- ```
11
-
12
- 升级:
13
-
14
- ```bash
15
- pip install -U qdc
16
- ```
17
-
18
- 内网或离线环境连不上公共源时,可以按完整地址装(版本号要自己跟着换):
19
-
20
- ```bash
21
- pip install https://api.qdc-data.com:8443/release/qdc-0.2.2-py3-none-any.whl
22
- ```
23
-
24
- > 2026-09-06 起 qdc 已发布在 PyPI(https://pypi.org/project/qdc/)。
25
- > **在此之前 `pip install qdc` 是假的** —— v0.1.0 的说明文件就这么写着,而包从没发出去过,
26
- > 客户照着敲只会得到 "No matching distribution found"。
27
-
28
- 需要 Python ≥ 3.8、`requests`、`pandas`(会自动装)。
29
-
30
- ## 三行上手
31
-
32
- ```python
33
- import qdc
34
- qdc.connect("你的API密钥", cache_dir="~/.qdc")
35
- df = qdc.daily("600519.SH", "20240101", "20240105") # -> pandas DataFrame
36
- ```
37
-
38
- `df` 的列:`ts_code, trade_date, open, high, low, close, vol, amount`(字段已对齐 tushare)。
39
-
40
- ## tushare 兼容用法
41
-
42
- ```python
43
- import qdc
44
- pro = qdc.pro_api("你的API密钥")
45
- df = pro.daily(ts_code="000001.SZ", start_date="20240101", end_date="20241231")
46
- df = pro.moneyflow(ts_code="600519.SH", start_date="20240101", end_date="20240131")
47
- df = pro.top_list(trade_date="20240105") # 龙虎榜
48
- df = pro.daily(ts_code="600519.SH", trade_date="20240105") # 只要某一天
49
- df = pro.income(ts_code="600519.SH", period="20231231") # 只要某个报告期
50
- df = pro.daily(ts_code="600519.SH", start_date="20240101", end_date="20240131",
51
- fields="ts_code,trade_date,close") # 只要几列
52
- # ★不认识的参数会当场报错,不会被静默忽略。
53
- ```
54
-
55
- ## 原生接口
56
-
57
- ```python
58
- c = qdc.connect(key, cache_dir="~/.qdc") # 或 qdc.Client(key)
59
-
60
- qdc.query(sku, code=, start=, end=, table=, limit=, fields=, date_field=, key=)
61
- qdc.daily(code, start, end, adjust=None) # adjust: None / "hfq" 后复权 / "qfq" 前复权
62
- qdc.moneyflow(code, start, end)
63
- qdc.lhb(code=None, date=None) # 龙虎榜
64
- qdc.financials(code, statement="income") # income 利润表 / balance 资产负债表 / cashflow 现金流量表
65
- qdc.index_daily(code, start, end) # 指数日线
66
- qdc.adj_factor(code, start, end) # 复权因子(稀疏表,见下)
67
- qdc.adj_factor_table() # 全市场复权因子整表(自动翻页 + 缓存)
68
-
69
- qdc.universe(st=False, only_st=False, list_days_gt=None, amount_gt=None, index=None, industry=None)
70
- # st=True 是【包含】ST(默认 False=剔除);要只看 ST 用 only_st=True
71
- # 返回 CodeList:用起来就是 list,额外带 .index_as_of(用的哪一份成分快照)/ .as_of / .meta
72
- qdc.lookup("茅台") # 名称/代码模糊查
73
- qdc.describe("cn_daily_full") # 字段/口径/覆盖范围
74
- ```
75
-
76
- ## 三件必须知道的事
77
-
78
- ### 1. 你拿到的是不是全部 —— 看 `df.attrs["qdc"]`
79
-
80
- 服务端单次最多返回 10,000 行(你这一档的 `max_rows`)。**本连接器会自动翻页取全**,
81
- 并把过程记在 `df.attrs["qdc"]` 里:
82
-
83
- ```python
84
- df = qdc.query("stocks_1m", code="002027.SZ", start="20240101", end="20241231")
85
- print(len(df)) # 58322 —— 全年真实行数
86
- print(df.attrs["qdc"]["pages"]) # 6 —— 翻了 6 页
87
- print(df.attrs["qdc"]["complete"]) # True —— 取完了
88
- ```
89
-
90
- `complete` 为 `False` 只会在撞到 `max_pages` 上限时出现,那时同时会抛一条警告。
91
-
92
- > ★ v0.1.0 **不翻页**:上面这个查询它只交给你第一页 10,000 行,HTTP 200、没有任何提示 ——
93
- > 少 82.9% 的数据,而你的回测不会告诉你。这是升级到 0.2.0 最重要的理由。
94
-
95
- ### 2. 复权是在本地算的
96
-
97
- 原「A股日线(后复权)」商品已于 2026-08-09 退役(它是纯派生数据)。现在:
98
-
99
- - `adjust="hfq"` = 取不复权日线 + 复权因子,本地按 `不复权价 × 复权因子` 还原;
100
- - `adjust="qfq"` = 后复权再除以该票最新因子(以最新价为基准);
101
- - 成交量与成交额**不参与复权**(退役登记实测:后复权的 vol/amount 与不复权完全相同)。
102
-
103
- 因子表是**稀疏**的(只在除权日有记录),按代码前向填充;整表未收录的股票表示从未除权,按因子 = 1。
104
- 如果某票在你这一档深度内取不到「该交易日之前最近一条」因子,对应行的价格会置为 `NaN` 并告警 ——
105
- **不会**按因子 = 1 蒙混过去给你一个错价。
106
-
107
- 因子表整表约 5.4 万行,`connect(cache_dir=...)` 之后会缓存到本地,不必每次重取。
108
-
109
- ### 3. 限额:同一只票每台设备每天只能取一次
110
-
111
- 去重键是 `(密钥, 设备, 代码, 日期)`,**不含数据集也不含表名**。所以:
112
-
113
- - 先 `daily("600519.SH")` 再 `adj_factor("600519.SH")` → 第二次必然 429。
114
- (正因为如此,`adjust="hfq"` 才用**不带代码的整表**去取因子 —— 那条路不计次。)
115
- - `financials(code, "income")` 之后当天再取同一只票的 `balance` 也会 429。
116
- 三张表要分三天取,或者走整包下载。
117
- - **翻页不重复计次**:第 2 页起算同一次取数。
118
-
119
- `device` 必须是**固定不变**的串。每次现生成新串会把设备槽位一个个占满;
120
- 不传时本连接器会按主机名 + 网卡地址生成一个稳定值。
121
-
122
- ## 约定
123
-
124
- - 代码格式:`600519.SH` / `000001.SZ`(内部 `.XSHG/.XSHE/.XBSE` 会自动归一,大小写不敏感)。
125
- - 日期:`YYYYMMDD` 字符串。
126
-
127
- ## 错误
128
-
129
- HTTP `401`(没有密钥)、`403`(未授权的数据集 / 设备数超限)、`429`(限额)等一律抛 `qdc.QDCError`,
130
- 带服务端给的 `message` 和 `.status_code`。
131
-
132
- ```python
133
- try:
134
- qdc.daily("600519.SH", "20240101", "20240105")
135
- except qdc.QDCError as e:
136
- print(e.status_code, e.message)
137
- ```
138
-
139
- ## 版本
140
-
141
- ### 0.2.2(2026-09-07)
142
-
143
- 三处,前两处都属于「不报错、只给错答案」:
144
-
145
- 1. **tushare 兼容层会静默丢掉参数。** `pro.daily(trade_date=...)`(只要某一天)、
146
- `pro.income(period=...)`(只要某个报告期)、`fields=`(只要几列)——这些参数以前被
147
- `**kwargs` 整个吞掉,不传也不报错,于是「只要一天」变成了整段历史。
148
- 现在认识的参数真的传下去,**不认识的当场报错**,不再有兜底。
149
- 2. **复权因子表没取全时仍会给出「完整」的结果。** 复权还原有一条推断:
150
- 「因子表里查不到这只票 = 它从未除权 = 因子按 1 算」——这条只有在因子表完整时才成立。
151
- 表缺了一截,除过权的票就会被按因子 1 处理,价格是错的而结果还标着已取完。
152
- 现在:因子表没取全直接报错,**残表绝不写进缓存**;缓存要带完整性标记才会被采信。
153
- 3. **`st` 参数的语义与说明相反。** 说明写的是「是否包含 ST 股」,实现却是「只选 ST」,
154
- 于是沪深300 加上 `st=True` 反而返回 0 只。现在 `st=True` 是**包含**、
155
- `st=False`(默认)是剔除,要只看 ST 用新增的 `only_st=True`。
156
-
157
- ### 0.2.1(2026-09-07)
158
-
159
- **按指数选股返回的是历史成分的并集,不是当前成分。** 服务端匹配成分表时没限定快照日期,
160
- 而那张表存着多份月度快照 —— 沪深300 返回 319 只(其中 19 只早已调出指数),
161
- 中证500 是 550 而不是 500,上证50 是 55 而不是 50。
162
- 客户拿它当选股池跑回测,池子里混着已调出的票,结果偏乐观而且无从发现。
163
- 现在钉在**该指数最新的那一份快照**上,并把成分日期带回来:
164
-
165
- ```python
166
- codes = qdc.universe(index="000300")
167
- len(codes) # 300
168
- codes.index_as_of # '2026-08-31' —— 这批成分是哪一天的
169
- ```
170
-
171
- 指数名或代码匹配不上时服务端会明确报错并说清楚,不会静默返回全市场。
172
-
173
- ### 0.2.0(2026-09-06)
174
-
175
- 三处都属于「不报错、只给错答案」,升级前请留意你既有代码的结论可能变:
176
-
177
- 1. **自动翻页**。之前只取第一页,一只票一年分钟线交给你 10,000 行(真实 58,322 行),零提示。
178
- 2. **复权**。之前 `adjust="hfq"` 打到已退役的 `cn_daily_hfq` 直接 422 报错;
179
- `adjust="qfq"` 更糟 —— 它静默返回**不复权**价。现在两者都在本地正确还原。
180
- 3. **财报表名**。之前 `statement="balance"` 打到不存在的表名 `balancesheet`,
181
- 服务端静默回退到默认表,于是你要资产负债表、拿到的是**利润表**,HTTP 200 无提示。
182
- 现在表名在本地校验,拼错当场报错。
183
-
184
- 另外:`index_daily` 之前指向 A 股日线表,查指数稳定返回 0 行,现已改指「指数日线」;
185
- 查询已退役的数据集会在本地给出继任者和迁移办法,而不是一句 `unknown sku`。
186
-
187
- ### 0.1.0
188
-
189
- 首个版本(从未对外分发)。
1
+ Metadata-Version: 2.4
2
+ Name: qdc
3
+ Version: 0.2.4
4
+ Summary: QDC online data client (tushare-compatible) for the QDC cloud data bridge.
5
+ Author: QDC Data
6
+ License: Proprietary
7
+ Project-URL: Homepage, https://data.qdc-data.com
8
+ Requires-Python: >=3.8
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: requests>=2.25
11
+ Requires-Dist: pandas>=1.1
12
+
13
+ # qdc
14
+
15
+ QDC「云端数据桥」的 Python 连接器(`https://data.qdc-data.com`)。
16
+ 返回 pandas DataFrame,并带一层 **tushare 兼容壳**,重叠接口零迁移。
17
+
18
+ ## 安装
19
+
20
+ ```bash
21
+ pip install qdc
22
+ ```
23
+
24
+ 升级:
25
+
26
+ ```bash
27
+ pip install -U qdc
28
+ ```
29
+
30
+ 内网或离线环境连不上公共源时,可以按完整地址装(版本号要自己跟着换):
31
+
32
+ ```bash
33
+ pip install https://api.qdc-data.com:8443/release/qdc-0.2.3-py3-none-any.whl
34
+ ```
35
+
36
+ > 2026-09-06 起 qdc 已发布在 PyPI(https://pypi.org/project/qdc/)。
37
+ > **在此之前 `pip install qdc` 是假的** —— v0.1.0 的说明文件就这么写着,而包从没发出去过,
38
+ > 客户照着敲只会得到 "No matching distribution found"。
39
+
40
+ 需要 Python ≥ 3.8、`requests`、`pandas`(会自动装)。
41
+
42
+ ## 三行上手
43
+
44
+ ```python
45
+ import qdc
46
+ qdc.connect("你的API密钥", cache_dir="~/.qdc")
47
+ df = qdc.daily("600519.SH", "20240101", "20240105") # -> pandas DataFrame
48
+ ```
49
+
50
+ `df` 的列:`ts_code, trade_date, open, high, low, close, vol, amount`(字段已对齐 tushare)。
51
+
52
+ ## tushare 兼容用法
53
+
54
+ ```python
55
+ import qdc
56
+ pro = qdc.pro_api("你的API密钥")
57
+ df = pro.daily(ts_code="000001.SZ", start_date="20240101", end_date="20241231")
58
+ df = pro.moneyflow(ts_code="600519.SH", start_date="20240101", end_date="20240131")
59
+ df = pro.top_list(trade_date="20240105") # 龙虎榜
60
+ df = pro.daily(ts_code="600519.SH", trade_date="20240105") # 只要某一天
61
+ df = pro.income(ts_code="600519.SH", period="20231231") # 只要某个报告期
62
+ df = pro.daily(ts_code="600519.SH", start_date="20240101", end_date="20240131",
63
+ fields="ts_code,trade_date,close") # 只要几列
64
+ # ★不认识的参数会当场报错,不会被静默忽略。
65
+ ```
66
+
67
+ ## 原生接口
68
+
69
+ ```python
70
+ c = qdc.connect(key, cache_dir="~/.qdc") # 或 qdc.Client(key)
71
+
72
+ qdc.query(sku, code=, start=, end=, table=, limit=, fields=, date_field=, key=)
73
+ qdc.daily(code, start, end, adjust=None) # adjust: None / "hfq" 后复权 / "qfq" 前复权
74
+ qdc.moneyflow(code, start, end)
75
+ qdc.lhb(code=None, date=None) # 龙虎榜
76
+ qdc.financials(code, statement="income") # income 利润表 / balance 资产负债表 / cashflow 现金流量表
77
+ qdc.index_daily(code, start, end) # 指数日线
78
+ qdc.adj_factor(code, start, end) # 复权因子(稀疏表,见下)
79
+ qdc.adj_factor_table() # 全市场复权因子整表(自动翻页 + 缓存)
80
+
81
+ qdc.universe(st=False, only_st=False, list_days_gt=None, amount_gt=None, index=None, industry=None)
82
+ # st=True 是【包含】ST(默认 False=剔除);要只看 ST 用 only_st=True
83
+ # 返回 CodeList:用起来就是 list,额外带 .index_as_of(用的哪一份成分快照)/ .as_of / .meta
84
+ qdc.lookup("茅台") # 名称/代码模糊查
85
+ qdc.describe("cn_daily_full") # 字段/口径/覆盖范围
86
+ ```
87
+
88
+ ## 在线计算(0.2.4 起):服务端算好,只把结果传回来
89
+
90
+ 不用把几百万行逐笔搬回本地再自己加总 —— 服务端现算:
91
+
92
+ ```python
93
+ # 合成K线:逐笔 → 1m/5m/15m/30m/60m/1d/1w/1M;1分钟线 → 5m~1M;日线 → 1w/1M
94
+ qdc.bars("cn_tick", "600519", "20240603", "20240607", freq="5m")
95
+ qdc.bars("stocks_1m", ["600519", "000001"], "20240101", "20241231", freq="60m")
96
+
97
+ # 区间统计:开高低收、成交量(股)、成交额(元)、笔数、均价、涨跌幅%、振幅%
98
+ qdc.stats("cn_daily_full", "600519", "20240101", "20241231", metrics=["close", "pct_chg", "amplitude"])
99
+ qdc.stats("cn_tick", "600519", "20240603", "20240630", group="day") # 每天一行
100
+
101
+ # 逐笔条件统计(高级版):10 点前、主动买入、单笔 ≥500 手 的成交量和笔数
102
+ qdc.stats("cn_tick", "600519", "20240603", "20240603", metrics=["volume", "count"],
103
+ side="buy", min_lots=500, time_to="10:00")
104
+ ```
105
+
106
+ - 计费:按**扫描的原始行数**计入当天条数额度(`df.attrs["qdc"]["scanned_rows"]`),与 `query` 共用。
107
+ - 一次能算多大:基础版逐笔「只数×天数」≤ 92(约 3 个月)、最多 3 只、不含条件统计;
108
+ 高级版 ≤ 731(约 2 年)、最多 10 只。分钟线一次 ≤ 1 年(高级版 2 年),日线不限。超了会 422 并告诉你怎么分段。
109
+ - 服务端为保护机器,同一时刻只并行算 2 条;忙时返回 429「在线计算此刻正忙」,几秒后重试即可。
110
+
111
+ ## 三件必须知道的事
112
+
113
+ ### 1. 你拿到的是不是全部 —— 看 `df.attrs["qdc"]`
114
+
115
+ 服务端单次最多返回 10,000 行(你这一档的 `max_rows`)。**本连接器会自动翻页取全**,
116
+ 并把过程记在 `df.attrs["qdc"]` 里:
117
+
118
+ ```python
119
+ df = qdc.query("stocks_1m", code="002027.SZ", start="20240101", end="20241231")
120
+ print(len(df)) # 58322 —— 全年真实行数
121
+ print(df.attrs["qdc"]["pages"]) # 6 —— 翻了 6 页
122
+ print(df.attrs["qdc"]["complete"]) # True —— 取完了
123
+ ```
124
+
125
+ `complete` 为 `False` 只会在撞到 `max_pages` 上限时出现,那时同时会抛一条警告。
126
+
127
+ > ★ v0.1.0 **不翻页**:上面这个查询它只交给你第一页 10,000 行,HTTP 200、没有任何提示 ——
128
+ > 少 82.9% 的数据,而你的回测不会告诉你。这是升级到 0.2.0 最重要的理由。
129
+
130
+ ### 2. 复权是在本地算的
131
+
132
+ 原「A股日线(后复权)」商品已于 2026-08-09 退役(它是纯派生数据)。现在:
133
+
134
+ - `adjust="hfq"` = 取不复权日线 + 复权因子,本地按 `不复权价 × 复权因子` 还原;
135
+ - `adjust="qfq"` = 后复权再除以该票最新因子(以最新价为基准);
136
+ - 成交量与成交额**不参与复权**(退役登记实测:后复权的 vol/amount 与不复权完全相同)。
137
+
138
+ 因子表是**稀疏**的(只在除权日有记录),按代码前向填充;整表未收录的股票表示从未除权,按因子 = 1。
139
+ 如果某票在你这一档深度内取不到「该交易日之前最近一条」因子,对应行的价格会置为 `NaN` 并告警 ——
140
+ **不会**按因子 = 1 蒙混过去给你一个错价。
141
+
142
+ 因子表整表约 5.4 万行,`connect(cache_dir=...)` 之后会缓存到本地,不必每次重取。
143
+
144
+ ### 3. 限额:按【每天累计取数条数】计,不是按次数
145
+
146
+ 同一只票可以反复查、可以先试探再重取;翻页取回的行照常计入当天条数。
147
+ 每次返回的 `meta` 里有 `daily_rows_used / daily_rows_limit`,照着它安排节奏,不要等撞上 429。
148
+ (旧版说明里的「同一只票每台设备每天只能取一次」已退役。)
149
+
150
+ `device` 必须是**固定不变**的串。每次现生成新串会把设备槽位一个个占满;
151
+ 不传时本连接器会按主机名 + 网卡地址生成一个稳定值。
152
+
153
+ ## 约定
154
+
155
+ - 代码格式:`600519.SH` / `000001.SZ`(内部 `.XSHG/.XSHE/.XBSE` 会自动归一,大小写不敏感)。
156
+ - 日期:`YYYYMMDD` 字符串。
157
+
158
+ ## 错误
159
+
160
+ HTTP `401`(没有密钥)、`403`(未授权的数据集 / 设备数超限)、`429`(限额)等一律抛 `qdc.QDCError`,
161
+ 带服务端给的 `message` 和 `.status_code`。
162
+
163
+ ```python
164
+ try:
165
+ qdc.daily("600519.SH", "20240101", "20240105")
166
+ except qdc.QDCError as e:
167
+ print(e.status_code, e.message)
168
+ ```
169
+
170
+ ## 版本
171
+
172
+ ### 0.2.4(2026-09-30)
173
+
174
+ 新增 `bars()` / `stats()` 在线计算(服务端现算,只回结果),见上文「在线计算」。
175
+
176
+ ### 0.2.3(2026-09-07)
177
+
178
+ **默认超时从 30 秒放宽到 180 秒。** 服务端高档位单条查询就允许 60 秒,前面还可能排队等并发槽 ——
179
+ 30 秒会让客户端在服务端还在干活时先断开,表现成「网络错误」,而那次取数机会已经扣掉了。
180
+ 断开时服务端会立刻停止查询,所以放宽它不会让服务器白干。
181
+
182
+ 同批服务端修复(不用升级连接器也生效):
183
+
184
+ - **失败的请求不再扣掉当天的取数机会。** 以前字段名写错先 422、改正后重试就 429,
185
+ 一次数据没拿到、机会没了。现在任何失败都会把那次记账撤销。
186
+ - **`start > end` 直接报错**,不再返回 200 + 0 行 —— 0 行和「真的没数据」长得一模一样。
187
+ - **完整代码能搜到了。** 以前 `lookup("600519")` 有结果而 `lookup("600519.SH")` 没有 ——
188
+ 本工具自己吐出来的代码自己不认。同时返回里标明**只检索 A 股**,查美股为空时会说清是范围问题。
189
+ - **字段字典的日期类型订正为字符串。** 库内是毫秒时间戳,返回前已格式化成 `"20260803"`,
190
+ 而字段字典此前照抄原始列类型报的是整数,客户照着解析会崩。
191
+ - **逐笔成交取「最新一笔」不再取错。** 排序键在笔内不唯一,拿到的是开盘集合竞价那笔;
192
+ 现已补上笔序号排序。
193
+
194
+ ### 0.2.2(2026-09-07)
195
+
196
+ 三处,前两处都属于「不报错、只给错答案」:
197
+
198
+ 1. **tushare 兼容层会静默丢掉参数。** `pro.daily(trade_date=...)`(只要某一天)、
199
+ `pro.income(period=...)`(只要某个报告期)、`fields=`(只要几列)——这些参数以前被
200
+ `**kwargs` 整个吞掉,不传也不报错,于是「只要一天」变成了整段历史。
201
+ 现在认识的参数真的传下去,**不认识的当场报错**,不再有兜底。
202
+ 2. **复权因子表没取全时仍会给出「完整」的结果。** 复权还原有一条推断:
203
+ 「因子表里查不到这只票 = 它从未除权 = 因子按 1 算」——这条只有在因子表完整时才成立。
204
+ 表缺了一截,除过权的票就会被按因子 1 处理,价格是错的而结果还标着已取完。
205
+ 现在:因子表没取全直接报错,**残表绝不写进缓存**;缓存要带完整性标记才会被采信。
206
+ 3. **`st` 参数的语义与说明相反。** 说明写的是「是否包含 ST 股」,实现却是「只选 ST」,
207
+ 于是沪深300 加上 `st=True` 反而返回 0 只。现在 `st=True` 是**包含**、
208
+ `st=False`(默认)是剔除,要只看 ST 用新增的 `only_st=True`。
209
+
210
+ ### 0.2.1(2026-09-07)
211
+
212
+ **按指数选股返回的是历史成分的并集,不是当前成分。** 服务端匹配成分表时没限定快照日期,
213
+ 而那张表存着多份月度快照 —— 沪深300 返回 319 只(其中 19 只早已调出指数),
214
+ 中证500 是 550 而不是 500,上证50 是 55 而不是 50。
215
+ 客户拿它当选股池跑回测,池子里混着已调出的票,结果偏乐观而且无从发现。
216
+ 现在钉在**该指数最新的那一份快照**上,并把成分日期带回来:
217
+
218
+ ```python
219
+ codes = qdc.universe(index="000300")
220
+ len(codes) # 300
221
+ codes.index_as_of # '2026-08-31' —— 这批成分是哪一天的
222
+ ```
223
+
224
+ 指数名或代码匹配不上时服务端会明确报错并说清楚,不会静默返回全市场。
225
+
226
+ ### 0.2.0(2026-09-06)
227
+
228
+ 三处都属于「不报错、只给错答案」,升级前请留意你既有代码的结论可能变:
229
+
230
+ 1. **自动翻页**。之前只取第一页,一只票一年分钟线交给你 10,000 行(真实 58,322 行),零提示。
231
+ 2. **复权**。之前 `adjust="hfq"` 打到已退役的 `cn_daily_hfq` 直接 422 报错;
232
+ `adjust="qfq"` 更糟 —— 它静默返回**不复权**价。现在两者都在本地正确还原。
233
+ 3. **财报表名**。之前 `statement="balance"` 打到不存在的表名 `balancesheet`,
234
+ 服务端静默回退到默认表,于是你要资产负债表、拿到的是**利润表**,HTTP 200 无提示。
235
+ 现在表名在本地校验,拼错当场报错。
236
+
237
+ 另外:`index_daily` 之前指向 A 股日线表,查指数稳定返回 0 行,现已改指「指数日线」;
238
+ 查询已退役的数据集会在本地给出继任者和迁移办法,而不是一句 `unknown sku`。
239
+
240
+ ### 0.1.0
241
+
242
+ 首个版本(从未对外分发)。
@@ -1,4 +1,4 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
File without changes
File without changes