Metadata-Version: 2.4
Name: quotekit
Version: 0.6.1
Summary: A股/北交所/场内ETF统一行情工具箱: mootdx(默认,多线程) + easyquotation(新浪/腾讯回退), 统一DataFrame返回, 实时行情/K线(8频度)/股票列表
Author-email: haifeng <chenhaifeng@touguyun.cn>
License: MIT
Project-URL: Homepage, https://github.com/fenglex/qdata/tree/master/qdata-labs/quotekit
Project-URL: Repository, https://github.com/fenglex/qdata/tree/master/qdata-labs/quotekit
Keywords: quant,finance,a-share,stock,etf,index,quotes,kline,mootdx,easyquotation,tongdaxin
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pandas
Requires-Dist: mootdx
Requires-Dist: easyquotation
Requires-Dist: pypinyin
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Dynamic: license-file

# quotekit — A股/北交所/场内ETF 统一行情工具箱

一个出口拉取 A 股个股（含北交所）、**场内 ETF** 与指数的实时行情和 K 线，统一返回 pandas DataFrame。

- **标的覆盖**：沪深 A 股 + 北交所（43/83/87/92 开头）+ 场内 ETF（沪 50/51/52/56/58、深 15/16 开头）+ 股指指数
- **默认源 mootdx**（通达信协议，32 线程并发，快、带五档盘口）
- **自动回退**：mootdx 失败/缺失的代码（含北交所、休市场景）自动用 easyquotation 新浪源补，仍缺失再用腾讯源
- **休市可用**：休市时返回最后交易日收盘快照（自动落到 eq 源，属设计行为）

## 安装

```bash
pip install quotekit                      # PyPI
pip install -e .                          # 开发安装(源码)
```

依赖：`pandas`、`mootdx`、`easyquotation`、`pypinyin`（见 pyproject.toml）。

## 快速上手

```python
from quotekit import get_quotes, get_index_quotes, configure

# 个股: 三种代码写法混用均可
df = get_quotes(['600519', '000001.SZ', 'sh600000', '920099'])
print(df[['symbol', 'price', 'open', 'high', 'low', 'volume', 'amount', 'time']])

# 指数: 000001上证 399001深成 399006创业板指 000300沪深300
idx = get_index_quotes(['000001', '399001', '000300'])

# 场内ETF: 与个股同一接口, 支持实时快照(含五档)和K线
etf = get_quotes(['510300', '510050', '588000', '159915', '511990'])
etf_bars = get_bars('510300', freq='day', sd='2026-01-01')          # ETF日K

# 交易所 A 股列表(symbol 格式 000001.SZ): 'all' | 'SH' | 'SZ' | 'BJ'
lst = get_stock_list()            # 全市场 5400+ 只, 含名称
lst = get_stock_list('BJ')        # 仅北交所
lst = get_stock_list(refresh=True)  # 强制绕过缓存重拉

# K线: 单只或多只, 频度 1m/5m/15m/30m/60m/day/week/month, 支持复权与日期区间
bars = get_bars('600519', freq='day', limit=120)                    # 最近120根
bars = get_bars(['600519', '000001.SZ'], freq='30m', limit=100)     # 多只30分钟K
bars = get_bars('600519', freq='day', limit=800, adjust='qfq')      # 前复权
bars = get_bars('600519', freq='day', sd='2024-01-01')              # 日期区间(优先于limit)
bars = get_bars('600519', freq='day', sd='2026-01-01', ed='2026-03-31')
bars = get_bars('000001.SZ', freq='day', ed='2025-12-31')           # ed之前全部历史
bars = get_bars('920099', freq='week', limit=52)                    # 北交所周K

# 搜索: 股票/ETF/指数, 支持代码/中文名/拼音首字母/全拼
search('茅台')            # 中文名 -> 600519
search('mt')              # 拼音首字母
search('maotai')          # 全拼
search('000001')          # 同时命中 上证指数 + 平安银行
search('hs300', kind='etf')   # kind: 'all'|'stock'|'etf'|'index'

# 强制指定源 / 临时改线程数
df = get_quotes(['600519'], source='tencent')
df = get_quotes(all_codes, workers=16)

# 全局配置(线程数/超时/批量, 批量上限60)
configure(workers=32, timeout=10, batch=60)
```

## 返回样例（真实数据）

所有数据接口统一返回 **pandas DataFrame**：`get_quotes` / `get_index_quotes` / `get_bars` /
`get_stock_list` / `search`（`configure` 为全局配置函数，无返回值）。
以下样例为真实拉取数据（2026-10-02 国庆休市期间采集，快照与K线均为最后交易日 2026-09-30 收盘口径）。

### get_quotes — 个股/ETF 实时快照（29 列，主列示例如下）

```text
   symbol    price     open     high      low    volume       amount                time
600519.SH 1258.620 1239.530 1268.000 1236.050   3833098 4.797247e+09 2026-09-30 15:34:59
000001.SZ   11.570   11.360   11.650   11.330 104535745 1.205815e+09 2026-09-30 16:30:00
920099.BJ   16.340   16.170   16.670   16.050    828203 1.348952e+07 2026-09-30 15:30:00
510300.SH    4.432    4.421    4.444    4.415 495854381 2.196229e+09 2026-09-30 15:34:59
```

单只个股完整 29 列（转置视图，`df[df['code']=='600519'].T`）：

```text
                            0
code                   600519
symbol              600519.SH
price                 1258.62
open                  1239.53
high                   1268.0
low                  1236.05
volume                3833098
amount           4797246636.0
time      2026-09-30 15:34:59
bid1                  1258.62
bid2                  1258.44
bid3                  1258.16
bid4                  1258.05
bid5                   1258.0
ask1                  1258.65
ask2                  1258.66
ask3                  1258.68
ask4                  1258.69
ask5                  1258.75
bid_vol1                 1445
bid_vol2                  100
bid_vol3                  100
bid_vol4                  200
bid_vol5                 4100
ask_vol1                  200
ask_vol2                  300
ask_vol3                  100
ask_vol4                  200
ask_vol5                 8000
```

### get_index_quotes — 指数实时快照（9 列）

```text
  code    symbol      price       open       high        low      volume       amount                time
000001 000001.SH  3842.1946  3839.2527  3851.2169  3833.0863 41456024700 6.793990e+11 2026-09-30 16:19:58
399001 399001.SZ 12887.6200 12958.0850 12966.4500 12854.7770 49438619293 7.585904e+11 2026-09-30 15:00:03
399006 399006.SZ  3135.2770  3164.7020  3173.3610  3125.4910 13366668354 3.662387e+11 2026-09-30 15:00:03
000300 000300.SH  4357.6155  4356.7955  4368.6139  4341.8791 16294962600 3.564915e+11 2026-09-30 16:19:40
```

### get_bars — K线（9 列）

日K（`get_bars('600519', freq='day', limit=5)`）：

```text
  code    symbol   datetime    open    high     low   close  volume  amount
600519 600519.SH 2026-09-23 1255.03 1271.50 1250.89 1251.24 3098100     NaN
600519 600519.SH 2026-09-24 1250.01 1256.13 1231.05 1237.00 3123900     NaN
600519 600519.SH 2026-09-28 1236.00 1244.01 1228.10 1243.88 2821800     NaN
600519 600519.SH 2026-09-29 1244.60 1245.87 1230.88 1235.58 2636600     NaN
600519 600519.SH 2026-09-30 1239.53 1268.00 1236.05 1258.62 3833100     NaN
```

30分钟K（`get_bars('600519', freq='30m', limit=5)`）：

```text
  code    symbol            datetime    open    high     low   close  volume  amount
600519 600519.SH 2026-09-30 11:30:00 1237.67 1241.50 1237.66 1240.99  192800     NaN
600519 600519.SH 2026-09-30 13:30:00 1240.70 1252.39 1240.70 1248.26  619900     NaN
600519 600519.SH 2026-09-30 14:00:00 1248.26 1267.60 1248.10 1263.11  895400     NaN
600519 600519.SH 2026-09-30 14:30:00 1263.58 1265.50 1258.57 1259.70  592400     NaN
600519 600519.SH 2026-09-30 15:00:00 1259.42 1259.98 1255.86 1258.62  494700     NaN
```

> 注：休市期间 mootdx 返回空、自动回退腾讯源，腾讯 K 线无成交额字段，`amount` 为 NaN（交易日走
> mootdx 主源时有值）。多只代码传入时各只数据纵向拼接，`code`/`symbol` 列可区分。

### get_stock_list — 交易所 A 股列表（4 列）

全市场共 5491 只（`get_stock_list()`，2026-09 底口径），前 5 行：

```text
  code name market    symbol
600000 浦发银行     SH 600000.SH
600004 白云机场     SH 600004.SH
600006 东风股份     SH 600006.SH
600007 中国国贸     SH 600007.SH
600008 首创环保     SH 600008.SH
```

北交所（`get_stock_list('BJ')`）前 3 行：

```text
  code  name market    symbol
430017  星昊医药     BJ 430017.BJ
430047  诺思兰德     BJ 430047.BJ
430090 *ST同辉     BJ 430090.BJ
```

### search — 证券搜索（5 列）

```text
search('茅台')
  code    symbol name market  type
600519 600519.SH 贵州茅台     SH stock

search('hs300', kind='etf')
  code    symbol     name market type
515390 515390.SH    HS300     SH  etf
515130 515130.SH   HS300E     SH  etf
510310 510310.SH HS300ETF     SH  etf
517030 517030.SH   HGS300     SH  etf
159919 159919.SZ    沪深300     SZ  etf

search('mt')   # 拼音首字母
  code    symbol  name market  type
161032 161032.SZ    煤炭     SZ   etf
880301 880301.SH    煤炭     SH index
168204 168204.SZ 煤炭LOF     SZ   etf
515220 515220.SH   煤炭ETF     SH  etf
880394 880394.SH   摩托车     SH index
```

## 返回 schema

个股/ETF 29 列 / 指数 9 列，`symbol` 格式 `600519.SH` / `000001.SZ` / `920099.BJ` / `510300.SH`。

| 列 | 类型 | 说明 |
|---|---|---|
| code | str | 6 位纯代码 |
| symbol | str | 带市场后缀 |
| price | float | 现价 |
| open / high / low | float | 今开 / 最高 / 最低 |
| volume | int64 | 成交量（股票=股、ETF=份；mootdx 源手×100） |
| amount | float | 成交额（元） |
| time | str | `YYYY-MM-DD HH:MM:SS` |
| bid1..5 / ask1..5 | float | 五档价（个股与 ETF） |
| bid_vol1..5 / ask_vol1..5 | int64 | 五档量（个股与 ETF） |

场内 ETF 实测说明（2026-09-30 快照验证）：实时与 K 线与个股完全同接口；成交量口径为**份**，
经"金额÷量≈现价"自检通过（510300/510050/588000/159915/511990 五只全部吻合）；日K收盘与量
和实时快照精确一致；mootdx 主路径交易日生效（TDX 协议 ETF 与股票同接口）。

`get_stock_list(market)` 返回 4 列：`code`（6 位）、`symbol`（`000001.SZ` 格式）、`name`、`market`（SH/SZ/BJ）。
SH/SZ 来自 mootdx 实时列表（含名称，休市可用），A 股口径前缀：SH 60/68、SZ 00/30；BJ 来自 easyquotation
内置代码表（静态文件，名称经 eq 补齐，新上市股票可能滞后）。进程内缓存，`refresh=True` 重拉。
**注意**：列表仅含 A 股，不含 ETF/指数（`get_quotes`/`get_bars` 支持场内 ETF，代码需自行传入）。

`search(keyword, kind, limit)` 返回 5 列：`code / symbol / name / market / type`（type 为
`stock`/`etf`/`index`，etf 含 LOF 等场内基金）。证券池覆盖全部 A 股 + 北交所 + 场内基金 + 沪深指数，
来自 mootdx 全列表（与 `get_stock_list` 共享缓存）+ 北交所清单。

匹配排序两级：先按**匹配层级**（代码精确 > 代码前缀 > 名称前缀 > 名称包含 > 拼音首字母前缀 >
首字母包含 > 全拼包含 > 模糊兜底），同层内按**相似度降序**（difflib SequenceMatcher 的字母重合度，
如 `mt` 会把"煤炭"排在"煤炭指数"前）；拼写有误差时（如 `maotia`）由模糊层兜底召回（阈值 0.55，
结合最长公共子串比率）。

全量列表获取：**8 线程**分页并行拉取（每页 1000 条，绕过 mootdx 自带 stocks() 的串行翻页，
直接调底层 tdxpy 协议接口），**三级缓存**——进程内 → 磁盘（TTL 8 小时，`~/.quotekit/market_list.csv`，
环境变量 `QUOTEKIT_CACHE_DIR` 可改路径，跨进程复用）→ 网络。`refresh=True` 强制重拉并刷新磁盘缓存。

`get_bars(codes, freq, limit, adjust, sd, ed)` 返回 9 列：`code / symbol / datetime / open / high / low / close /
volume(股) / amount(元)`。datetime：分钟线 `YYYY-MM-DD HH:MM:SS`，日线及以上 `YYYY-MM-DD`。

取数方式两种（给了 `sd`/`ed` 时优先，忽略 `limit`）：
- **limit 模式**：最近 N 根。mootdx 逐只自动翻页凑足；tencent/sina 按源上限截断（日K 800、分钟 320/1970）。
- **日期区间模式**：`sd`/`ed`（`'YYYY-MM-DD'`，分钟线可带时分秒；均可单独给，ed 缺省=至今）。
  tencent 日线原生支持日期段并按年分段回拉（可取全部历史，实测 1991 年起）；mootdx 回翻过滤；
  分钟线的日期区间受源保留窗口限制（mootdx 近期、tencent 最近 320 根、sina 最近 1970 根）。
  日期模式下区间无数据返回空 DataFrame（不报错）。

频度与源能力：

| 频度 | mootdx（主源） | tencent（回退） | sina（回退，仅分钟） |
|---|---|---|---|
| 1m/5m/15m/30m/60m | 近期滚动窗口，复权支持 | 320 根，无复权 | 1970 根，无复权 |
| day/week/month | 回溯到上市，复权支持 | 800 根/段（自动分段翻全部历史），复权支持 | ✗ |

分钟频率的复权仅 mootdx 支持；腾讯源无成交额字段（amount 为 NaN）；休市时 mootdx 返回空自动回退。

## 数据源与实现

| 数据 | 主路径 | 回退路径 | 说明 |
|---|---|---|---|
| 个股/ETF 实时快照 | mootdx → tdxpy（通达信 TCP 7709，32 线程 × 60 只/批） | 新浪 → 腾讯（easyquotation） | 休市时 mootdx 空，自动回退拿最后快照 |
| 指数实时快照 | mootdx 底层 tdxpy（显式 market+code，绕过 mootdx 的市场误判） | 新浪 → 腾讯 | sina 沪市指数量单位为手已 ×100 归一 |
| K线 分钟 | mootdx（近期窗口，支持复权） | 腾讯 mkline（320 根）→ 新浪（1970 根） | 回退源分钟无复权 |
| K线 日/周/月 | mootdx（回溯到上市，支持复权） | 腾讯 fqkline（800 根/段，自动按年分段回拉全部历史，支持复权） | — |
| 股票列表 | mootdx → tdxpy `get_security_list`（8 线程分页） | — | 北交所来自 easyquotation 静态代码表，名称经腾讯补齐 |
| 搜索池 | 同上（与股票列表共享缓存）+ pypinyin 拼音 | — | 沪深指数取自 mootdx 全列表 |

注：腾讯 K 线接口为 `ifzq.gtimg.cn` 的公开端点（fqkline/mkline），直接 HTTP 调用，不经 easyquotation。

## 性能实测（2026-10，休市环境）

| 操作 | 耗时 |
|---|---|
| 全市场列表冷拉取（51861 条，8 线程） | **1.8~5.7s**（受 mootdx 服务器波动，慢轮曾 20s） |
| 线程数对比（2/4/8/16 各 3 轮均值） | 4.8s / 2.4s / **1.9s** / 5.9s（负优化）——8 为甜点 |
| 新进程命中磁盘缓存建池 | ~0.8s |
| 同进程搜索（缓存后） | ~50ms |
| 全市场实时快照（mootdx 32 线程，交易日） | ~0.3s（休市计时为协议往返上界） |

## 口径与已知行为（实测）

- 单位统一：量=股（ETF 为份）、额=元；已修正各源字段错位（sina `volume` 实为成交额、tencent `成交额(万)` 实为元、sina 沪市指数量单位为手需 ×100）。
- mootdx 单连接独占、批量上限 60 只/次；并发靠"每线程一条连接"。
- 北交所（43/83/87/92 开头）mootdx 不支持，auto 模式自动由 eq 补齐；老北交码（83/43）sina 数据残缺（有价无开高低），需要完整数据请强制 `source='tencent'`。
- mootdx 的 `servertime` 不含日期，按调用当日拼时间；交易日调用即正确。
- 全部源失败抛 `QuoteSourceError`（含各源失败原因）。
- 休市期间：mootdx 快照为空 → 自动回退 eq 拿最后快照。

## 运行测试

```bash
python -m unittest discover -s tests -v     # 离线单元测试
python tests/test_live_smoke.py             # 在线冒烟(需网络, 交易日效果最佳)
```

## 目录结构

```
quotekit/
├── pyproject.toml
├── src/quotekit/
│   ├── __init__.py     # 出口: get_quotes / get_index_quotes / get_bars / get_stock_list / configure
│   ├── api.py          # 实时行情编排与回退链
│   ├── bars.py         # K线接口(8频度, 复权, 多源回退)
│   ├── lists.py        # 交易所 A 股列表(8线程分页+三级缓存)
│   ├── search.py       # 证券搜索(拼音/相似度/模糊兜底)
│   ├── sources.py      # mootdx / easyquotation 实时行情适配器
│   ├── codes.py        # 代码归一(sh600519 / 600519.SH / 600519)
│   ├── schema.py       # 统一列定义
│   └── config.py       # 全局配置
├── tests/              # 离线单测 + 在线冒烟
└── examples/basic_usage.py
```
