Metadata-Version: 2.4
Name: qdc
Version: 0.2.3
Summary: QDC online data client (tushare-compatible) for the QDC cloud data bridge.
Author: QDC Data
License: Proprietary
Project-URL: Homepage, https://data.qdc-data.com
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.25
Requires-Dist: pandas>=1.1

# qdc

QDC「云端数据桥」的 Python 连接器(`https://data.qdc-data.com`)。
返回 pandas DataFrame,并带一层 **tushare 兼容壳**,重叠接口零迁移。

## 安装

```bash
pip install qdc
```

升级:

```bash
pip install -U qdc
```

内网或离线环境连不上公共源时,可以按完整地址装(版本号要自己跟着换):

```bash
pip install https://api.qdc-data.com:8443/release/qdc-0.2.3-py3-none-any.whl
```

> 2026-09-06 起 qdc 已发布在 PyPI(https://pypi.org/project/qdc/)。
> **在此之前 `pip install qdc` 是假的** —— v0.1.0 的说明文件就这么写着,而包从没发出去过,
> 客户照着敲只会得到 "No matching distribution found"。

需要 Python ≥ 3.8、`requests`、`pandas`(会自动装)。

## 三行上手

```python
import qdc
qdc.connect("你的API密钥", cache_dir="~/.qdc")
df = qdc.daily("600519.SH", "20240101", "20240105")   # -> pandas DataFrame
```

`df` 的列:`ts_code, trade_date, open, high, low, close, vol, amount`(字段已对齐 tushare)。

## tushare 兼容用法

```python
import qdc
pro = qdc.pro_api("你的API密钥")
df = pro.daily(ts_code="000001.SZ", start_date="20240101", end_date="20241231")
df = pro.moneyflow(ts_code="600519.SH", start_date="20240101", end_date="20240131")
df = pro.top_list(trade_date="20240105")     # 龙虎榜
df = pro.daily(ts_code="600519.SH", trade_date="20240105")   # 只要某一天
df = pro.income(ts_code="600519.SH", period="20231231")      # 只要某个报告期
df = pro.daily(ts_code="600519.SH", start_date="20240101", end_date="20240131",
               fields="ts_code,trade_date,close")            # 只要几列
# ★不认识的参数会当场报错,不会被静默忽略。
```

## 原生接口

```python
c = qdc.connect(key, cache_dir="~/.qdc")     # 或 qdc.Client(key)

qdc.query(sku, code=, start=, end=, table=, limit=, fields=, date_field=, key=)
qdc.daily(code, start, end, adjust=None)     # adjust: None / "hfq" 后复权 / "qfq" 前复权
qdc.moneyflow(code, start, end)
qdc.lhb(code=None, date=None)                # 龙虎榜
qdc.financials(code, statement="income")     # income 利润表 / balance 资产负债表 / cashflow 现金流量表
qdc.index_daily(code, start, end)            # 指数日线
qdc.adj_factor(code, start, end)             # 复权因子(稀疏表,见下)
qdc.adj_factor_table()                       # 全市场复权因子整表(自动翻页 + 缓存)

qdc.universe(st=False, only_st=False, list_days_gt=None, amount_gt=None, index=None, industry=None)
#   st=True 是【包含】ST(默认 False=剔除);要只看 ST 用 only_st=True
#   返回 CodeList:用起来就是 list,额外带 .index_as_of(用的哪一份成分快照)/ .as_of / .meta
qdc.lookup("茅台")                            # 名称/代码模糊查
qdc.describe("cn_daily_full")                # 字段/口径/覆盖范围
```

## 三件必须知道的事

### 1. 你拿到的是不是全部 —— 看 `df.attrs["qdc"]`

服务端单次最多返回 10,000 行(你这一档的 `max_rows`)。**本连接器会自动翻页取全**,
并把过程记在 `df.attrs["qdc"]` 里:

```python
df = qdc.query("stocks_1m", code="002027.SZ", start="20240101", end="20241231")
print(len(df))                      # 58322 —— 全年真实行数
print(df.attrs["qdc"]["pages"])     # 6 —— 翻了 6 页
print(df.attrs["qdc"]["complete"])  # True —— 取完了
```

`complete` 为 `False` 只会在撞到 `max_pages` 上限时出现,那时同时会抛一条警告。

> ★ v0.1.0 **不翻页**:上面这个查询它只交给你第一页 10,000 行,HTTP 200、没有任何提示 ——
> 少 82.9% 的数据,而你的回测不会告诉你。这是升级到 0.2.0 最重要的理由。

### 2. 复权是在本地算的

原「A股日线(后复权)」商品已于 2026-08-09 退役(它是纯派生数据)。现在:

- `adjust="hfq"` = 取不复权日线 + 复权因子,本地按 `不复权价 × 复权因子` 还原;
- `adjust="qfq"` = 后复权再除以该票最新因子(以最新价为基准);
- 成交量与成交额**不参与复权**(退役登记实测:后复权的 vol/amount 与不复权完全相同)。

因子表是**稀疏**的(只在除权日有记录),按代码前向填充;整表未收录的股票表示从未除权,按因子 = 1。
如果某票在你这一档深度内取不到「该交易日之前最近一条」因子,对应行的价格会置为 `NaN` 并告警 ——
**不会**按因子 = 1 蒙混过去给你一个错价。

因子表整表约 5.4 万行,`connect(cache_dir=...)` 之后会缓存到本地,不必每次重取。

### 3. 限额:同一只票每台设备每天只能取一次

去重键是 `(密钥, 设备, 代码, 日期)`,**不含数据集也不含表名**。所以:

- 先 `daily("600519.SH")` 再 `adj_factor("600519.SH")` → 第二次必然 429。
  (正因为如此,`adjust="hfq"` 才用**不带代码的整表**去取因子 —— 那条路不计次。)
- `financials(code, "income")` 之后当天再取同一只票的 `balance` 也会 429。
  三张表要分三天取,或者走整包下载。
- **翻页不重复计次**:第 2 页起算同一次取数。

`device` 必须是**固定不变**的串。每次现生成新串会把设备槽位一个个占满;
不传时本连接器会按主机名 + 网卡地址生成一个稳定值。

## 约定

- 代码格式:`600519.SH` / `000001.SZ`(内部 `.XSHG/.XSHE/.XBSE` 会自动归一,大小写不敏感)。
- 日期:`YYYYMMDD` 字符串。

## 错误

HTTP `401`(没有密钥)、`403`(未授权的数据集 / 设备数超限)、`429`(限额)等一律抛 `qdc.QDCError`,
带服务端给的 `message` 和 `.status_code`。

```python
try:
    qdc.daily("600519.SH", "20240101", "20240105")
except qdc.QDCError as e:
    print(e.status_code, e.message)
```

## 版本

### 0.2.3(2026-09-07)

**默认超时从 30 秒放宽到 180 秒。** 服务端高档位单条查询就允许 60 秒,前面还可能排队等并发槽 ——
30 秒会让客户端在服务端还在干活时先断开,表现成「网络错误」,而那次取数机会已经扣掉了。
断开时服务端会立刻停止查询,所以放宽它不会让服务器白干。

同批服务端修复(不用升级连接器也生效):

- **失败的请求不再扣掉当天的取数机会。** 以前字段名写错先 422、改正后重试就 429,
  一次数据没拿到、机会没了。现在任何失败都会把那次记账撤销。
- **`start > end` 直接报错**,不再返回 200 + 0 行 —— 0 行和「真的没数据」长得一模一样。
- **完整代码能搜到了。** 以前 `lookup("600519")` 有结果而 `lookup("600519.SH")` 没有 ——
  本工具自己吐出来的代码自己不认。同时返回里标明**只检索 A 股**,查美股为空时会说清是范围问题。
- **字段字典的日期类型订正为字符串。** 库内是毫秒时间戳,返回前已格式化成 `"20260803"`,
  而字段字典此前照抄原始列类型报的是整数,客户照着解析会崩。
- **逐笔成交取「最新一笔」不再取错。** 排序键在笔内不唯一,拿到的是开盘集合竞价那笔;
  现已补上笔序号排序。

### 0.2.2(2026-09-07)

三处,前两处都属于「不报错、只给错答案」:

1. **tushare 兼容层会静默丢掉参数。** `pro.daily(trade_date=...)`(只要某一天)、
   `pro.income(period=...)`(只要某个报告期)、`fields=`(只要几列)——这些参数以前被
   `**kwargs` 整个吞掉,不传也不报错,于是「只要一天」变成了整段历史。
   现在认识的参数真的传下去,**不认识的当场报错**,不再有兜底。
2. **复权因子表没取全时仍会给出「完整」的结果。** 复权还原有一条推断:
   「因子表里查不到这只票 = 它从未除权 = 因子按 1 算」——这条只有在因子表完整时才成立。
   表缺了一截,除过权的票就会被按因子 1 处理,价格是错的而结果还标着已取完。
   现在:因子表没取全直接报错,**残表绝不写进缓存**;缓存要带完整性标记才会被采信。
3. **`st` 参数的语义与说明相反。** 说明写的是「是否包含 ST 股」,实现却是「只选 ST」,
   于是沪深300 加上 `st=True` 反而返回 0 只。现在 `st=True` 是**包含**、
   `st=False`(默认)是剔除,要只看 ST 用新增的 `only_st=True`。

### 0.2.1(2026-09-07)

**按指数选股返回的是历史成分的并集,不是当前成分。** 服务端匹配成分表时没限定快照日期,
而那张表存着多份月度快照 —— 沪深300 返回 319 只(其中 19 只早已调出指数),
中证500 是 550 而不是 500,上证50 是 55 而不是 50。
客户拿它当选股池跑回测,池子里混着已调出的票,结果偏乐观而且无从发现。
现在钉在**该指数最新的那一份快照**上,并把成分日期带回来:

```python
codes = qdc.universe(index="000300")
len(codes)          # 300
codes.index_as_of   # '2026-08-31' —— 这批成分是哪一天的
```

指数名或代码匹配不上时服务端会明确报错并说清楚,不会静默返回全市场。

### 0.2.0(2026-09-06)

三处都属于「不报错、只给错答案」,升级前请留意你既有代码的结论可能变:

1. **自动翻页**。之前只取第一页,一只票一年分钟线交给你 10,000 行(真实 58,322 行),零提示。
2. **复权**。之前 `adjust="hfq"` 打到已退役的 `cn_daily_hfq` 直接 422 报错;
   `adjust="qfq"` 更糟 —— 它静默返回**不复权**价。现在两者都在本地正确还原。
3. **财报表名**。之前 `statement="balance"` 打到不存在的表名 `balancesheet`,
   服务端静默回退到默认表,于是你要资产负债表、拿到的是**利润表**,HTTP 200 无提示。
   现在表名在本地校验,拼错当场报错。

另外:`index_daily` 之前指向 A 股日线表,查指数稳定返回 0 行,现已改指「指数日线」;
查询已退役的数据集会在本地给出继任者和迁移办法,而不是一句 `unknown sku`。

### 0.1.0

首个版本(从未对外分发)。
