Metadata-Version: 2.4
Name: quotekit
Version: 0.6.0
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 -e .                          # 开发安装
pip install build && python -m build      # 打包: dist/quotekit-0.6.0-py3-none-any.whl
pip install dist/quotekit-0.6.0-py3-none-any.whl
```

依赖：`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)
```

## 返回 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
```
