Metadata-Version: 2.4
Name: pytdxdata
Version: 0.1.0
Summary: 通达信(TongDaXin)行情数据客户端：动态连接池 + 双层TTL缓存 + 3连接并发分页，纯asyncio零运行时依赖，支持A股/港股/美股/期货/期权
Keywords: tdx,trading,stock,kline,quote,通达信,A股,港股,美股,期货,asyncio,quant
Author: vex1023
Author-email: vex1023 <vex1023@qq.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: OS Independent
Classifier: Framework :: AsyncIO
Classifier: Typing :: Typed
Requires-Dist: pytest>=8.0 ; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24 ; extra == 'dev'
Requires-Dist: ruff>=0.6 ; extra == 'dev'
Requires-Dist: uvloop>=0.21 ; extra == 'uvloop'
Requires-Python: >=3.13
Project-URL: Homepage, https://github.com/vex1023/pytdxdata
Project-URL: Repository, https://github.com/vex1023/pytdxdata
Project-URL: Documentation, https://github.com/vex1023/pytdxdata#readme
Provides-Extra: dev
Provides-Extra: uvloop
Description-Content-Type: text/markdown

# pytdxdata

[![PyPI version](https://img.shields.io/pypi/v/pytdxdata)](https://pypi.org/project/pytdxdata/)
[![Python](https://img.shields.io/pypi/pyversions/pytdxdata)](https://pypi.org/project/pytdxdata/)
[![License](https://img.shields.io/pypi/l/pytdxdata)](https://github.com/vex1023/pytdxdata/blob/main/LICENSE)
[![PyPI downloads](https://img.shields.io/pypi/dm/pytdxdata)](https://pypi.org/project/pytdxdata/)

通达信(TongDaXin)行情数据客户端：**动态连接池 + 双层TTL缓存 + 3连接并发分页**（纯 asyncio，零运行时依赖）。

**A股(沪/深/北) + 港股 + 美股 + 期货 + 期权**，一套接口全市场。

参照 [easy-tdx](https://github.com/handsomejustin/easy_tdx) 重新实现，解决其三大短板（无连接池、串行分页、单连接单锁），协议层独立实现（不依赖 easy-tdx 包，仅参照其字节级格式）。

## 特性

| 特性 | 说明 |
|------|------|
| 🧩 动态连接池 | 启动时对服务器**真实K线探测**选优（剔除握手正常但数据返回空的坏节点），按等待队列/空闲时长动态扩缩容，健康分（失败×0.5/成功+0.2/冷却120s）+EWMA延迟选路，跨服务器故障转移 |
| 🗄️ 双层TTL缓存 | 内存LRU + 磁盘SQLite（WAL），TTL分级：报价5s / 分钟K线60s / 日线当天 / 复权K线1天 / **历史逐笔永久**；single-flight 并发同键去重（只回源一次）；缓存"已解析记录"（重复请求0网络0解析） |
| ⚡ 3连接并发分页 | 单请求拆N页（每页显式start偏移→无状态可并行），3连接并行拉取、按序拼接、失败换连接重试 |
| 🔀 双通道整合 | 标准通道（经典行情）+ MAC通道（逐笔/复权），按命令自动选路 |
| 📦 零重型依赖 | 纯 asyncio + stdlib（socket/zlib/struct/sqlite3），返回 `list[dataclass]`，pandas 由调用方按需转换 |

## 安装

```bash
pip install pytdxdata          # 或 uv add pytdxdata
```

从源码开发：

```bash
git clone <repo-url>
cd pytdxdata
uv sync                          # 创建虚拟环境 + 安装CLI
uv run python -c "import pytdxdata"
```

可选加速（uvloop）：

```bash
uv add --optional uvloop uvloop
```

> 注：数据来自通达信公开行情服务器（无需账号），服务器清单由 `server_probe.py` 导入时自动探测可用性与延时，存于 `servers.json`。

## CLI 命令行工具

```bash
pip install -e .   # 或 uv sync，注册 tdx 命令

tdx quotes sz000001                    # 行情报价(五档) / hk00700 / usAAPL
tdx kline sz000001 --period day --count 30 --adjust qfq
tdx kline cffex:IFL0 --count 30        # 期货K线(hk/us/zz/dl/shf/cffex/gz)
tdx transactions sz000001 --count 50   # 逐笔(含成交笔数)
tdx minute sz000001                    # 分时
tdx index 000001 --count 30            # 指数K线
tdx board list / members 881001 / ranking / change --days 5
tdx info sz000001                      # 个股快照
tdx list --market sz --count 100       # 证券列表
tdx auction sz000001 / unusual / xdxr / finance / flow
tdx server-info / market-stat
```

## 快速开始

```python
import asyncio
from pytdxdata import TdxData
from pytdxdata.models import KlinePeriod, Market, Adjust

async def main():
    async with TdxData() as td:   # 自动建标准池(52台)+MAC池(3台)+缓存
        # 日K线（count>800 自动3连接并发分页）
        bars = await td.get_kline(1, "600000", KlinePeriod.DAY, count=2400)
        # 分钟K线（复权走MAC通道）
        m1 = await td.get_kline(0, "300308", KlinePeriod.MIN_1, count=240)
        # 五档报价（TTL 5s 缓存，重复请求0网络）
        q = await td.get_quotes([(1, "600000"), (0, "000001")])
        # 逐笔成交（MAC通道，含 trade_count=成交笔数，支持历史回溯）
        t = await td.get_transactions(0, "000001", date=20260811)
        total_trades = sum(r.trade_count for r in t)   # 当天总成交笔数
        # 证券列表/数量
        n = await td.get_security_count(0)
        lst = await td.get_security_list(1, start=0, count=1000)
        # 分时/除权除息/财务
        minute = await td.get_minute(1, "600000")
        xdxr = await td.get_xdxr(1, "600000")
        fin = await td.get_finance(1, "600000")

asyncio.run(main())
```

## API 参考

统一入口 `TdxData`（`async with TdxData() as td:` 自动 start/close）。

### 连接管理

| 方法 | 说明 |
|------|------|
| `start()` / `close()` | 启动（服务器探测+预建连接+缓存）/ 关闭 |
| `__aenter__` / `__aexit__` | 异步上下文管理器 |

### 行情接口

| 方法 | 返回 | 说明 |
|------|------|------|
| `get_kline(market, code, period, *, start, count, adjust)` | `list[SecurityBar]` | K线（count>800自动并发分页；adjust≠NONE走MAC复权；88/000/399指数走指数命令） |
| `get_index_kline(market, code, period, *, start, count)` | `list[SecurityBar]` | 指数K线（多涨跌家数字段已处理） |
| `get_quotes(stocks)` | `list[SecurityQuote]` | 五档报价（>80只自动切批，缓存键=规范化股票集） |
| `get_transactions(market, code, *, date, start, count)` | `list[TransactionRecord]` | **逐笔成交（MAC，含 trade_count 成交笔数）**，支持历史回溯 |
| `get_minute(market, code, *, date)` | `list[MinuteBar]` | 分时（今日/历史） |
| `get_security_count(market)` | `int` | 市场证券数量 |
| `get_security_list(market, *, start, count)` | `list[SecurityInfo]` | 证券列表（count=None=全部，先查总数分页） |
| `get_security_list_all(market)` | `list[SecurityInfo]` | 全市场列表（缓存1天） |
| `get_xdxr(market, code)` | `list[XdxrRecord]` | 除权除息历史（标准） |
| `get_finance(market, code)` | `FinanceRecord \| None` | 财务快照（标准） |

### 板块/竞价/异动（MAC）

| 方法 | 返回 | 说明 |
|------|------|------|
| `get_board_list(board_type, count)` | `list[BoardInfo]` | 板块列表（0=全部 1=行业 2=概念） |
| `get_board_members(board_symbol, count, sort_type)` | `list[MemberQuote]` | 板块成分股报价（自定义字段） |
| `get_board_summary(board_symbol)` | `dict` | 板块汇总（成分数/成交额/主力净流入/涨跌家数） |
| `get_board_ranking(board_type, top_n)` | `list[dict]` | 板块涨跌幅排行 |
| `get_board_change_ranking(board_type, days, top_n)` | `list[dict]` | 板块N日涨跌幅排行（板块指数K线） |
| `get_belong_board(market, code)` | `list[BelongBoard]` | 个股所属板块 |
| `get_auction(market, code)` | `list[AuctionItem]` | 集合竞价 |
| `get_unusual(market, start, count)` | `list[UnusualItem]` | 市场异动 |
| `get_capital_flow(market, code)` | `CapitalFlow` | 资金流向 |

### 快照/服务器/扩展（MAC）

| 方法 | 返回 | 说明 |
|------|------|------|
| `get_symbol_info(market, code)` | `SymbolSnapshot` | 个股特征快照 |
| `get_stock_quotes(stocks, bits)` | `list[MemberQuote]` | MAC自定义字段报价 |
| `get_stock_quotes_list(category, count)` | `list[MemberQuote]` | 市场分类报价列表（0沪A/6全A/8科创/14创业） |
| `get_stock_kline_with_indicators(market, code, indicators, ...)` | `dict` | K线+指标（MACD/KDJ/RSI/BOLL/MA/EMA） |
| `get_goods_list(market, start, count)` | `list[GoodsItem]` | 扩展市场商品（期货/期权） |
| `get_server_info()` | `ServerInfo` | 服务器交易时段 |
| `get_kline_offset(offset, count)` | `tuple[int,int]` | K线偏移 |
| `get_chart_sampling(market, code)` | `list[float]` | 分时采样（扩展市场命令，A股不适用） |

### 文件/信息（标准+MAC）

| 方法 | 返回 | 说明 |
|------|------|------|
| `get_company_info_category/content(...)` | `list` / `str` | F10公司信息（标准） |
| `get_block_info(filename)` | `bytes` | 板块文件下载（标准） |
| `get_report_file(filename)` | `bytes` | 报告/财务文件下载（标准） |
| `get_financial_file_list()` | `list[str]` | 财务文件列表（tdxfin/gpcw.txt） |
| `get_financial_records(filename)` | `bytes` | 财务文件内容（gpcw*.zip） |
| `get_file_meta(filename)` / `download_file(filename)` | `FileMeta` / `bytes` | 远程文件（MAC） |
| `get_price_limits(market, code, name, pre_close, listed_days)` | `tuple` | 涨跌停价（本地计算） |
| `get_market_stat()` | `MarketStat` | 全市场统计（涨跌家数/总额/市值/涨跌停数） |

### 数据模型（dataclass）

| 模型 | 关键字段 |
|------|---------|
| `SecurityBar` | market/code/open/high/low/close/vol/amount/year/month/day/hour/minute + `datetime`属性 |
| `SecurityQuote` | price/pre_close/OHLC/vol/amount/s_vol/b_vol/`bid[5]`/`ask[5]`/server_time/trading_status |
| `TransactionRecord` | time/price/vol/`trade_count`/bs_flag |
| `SecurityInfo` | code/name(GBK)/volunit/decimal_point/pre_close |
| `XdxrRecord` | category/分红送转/缩股/行权等（除权除息） |
| `MinuteBar` | price/vol |
| `FinanceRecord` | 股本/资产/营收/利润（万元/万股） |

### 枚举

- `Market`：SZ=0 / SH=1 / **BJ=2**（北交所）
- `KlinePeriod`：MIN_5=0 / MIN_15=1 / MIN_30=2 / MIN_60=3 / **DAY=4** / WEEK=5 / MONTH=6 / MIN_1=7 / MIN_3=8 / YEAR=9 / SEASON=10（**与通达信协议字节一致**）
- `Adjust`：NONE / QFQ（前复权）/ HFQ（后复权）

## 通道路由

| 命令 | 默认通道 | 说明 |
|------|---------|------|
| K线（复权） | **MAC** | 支持QFQ/HFQ |
| K线（不复权）/指数 | 标准 | 价格小数位规则已验证 |
| 报价 | 标准 | 股票2位/指数ETF转债3位 |
| **逐笔成交** | **MAC** | 含trade_count，支持历史日期 |
| 列表/数量/分时/除权/财务 | 标准 | 仅标准通道提供 |

## 架构

```
src/pytdxdata/
├── api.py              # 统一接口 TdxData（对外唯一入口）
├── router.py           # 标准/MAC 通道选路
├── config.py           # 服务器清单加载（servers.json + 环境变量覆盖）
├── protocol/           # 独立协议层（帧/变长整数/差分还原/自定义浮点/命令）
│   └── commands/       # kline/quotes/list/transaction/minute/xdxr/finance/mac
├── pool/               # 动态连接池（connection/health/pool）
├── cache/              # 双层TTL缓存（key/ttl/memory/disk/manager）
├── scheduler/          # 并发分页调度器（Paginator，3连接并行）
├── models/             # dataclass 模型 + 枚举
└── codec/              # 缓存载荷序列化（pickle+zlib）
```

## 扩展市场（EX通道：港股/美股/期货/期权）

`get_kline` 的 market 参数接受 `ExMarket` 枚举（港股/美股/期货/期权等52个市场）：

```python
from pytdxdata.models import ExMarket, KlinePeriod

# 港股（HK_MAIN_BOARD=31，代码5位）
bars = await td.get_kline(ExMarket.HK_MAIN_BOARD, '00700', KlinePeriod.DAY, count=700)
# 美股（US_STOCK=74）
bars = await td.get_kline(ExMarket.US_STOCK, 'AAPL', KlinePeriod.DAY, count=700)
# 期货（CFFEX=47 / 郑28 / 大29 / 上30，合约如 IFL0/rb2610）
bars = await td.get_kline(ExMarket.CFFEX_FUTURES, 'IFL0', KlinePeriod.DAY, count=700)
# 股票期权（沪8/深9，合约代码8位）
bars = await td.get_kline(ExMarket.SH_STOCK_OPTION, '10010971', KlinePeriod.DAY, count=700)
```

- 端口 7727，统一走 **pytdx EX 协议**（0x23FF K线 / 0x23FA 报价 / 0x23F4 市场列表）
- 4台EX服务器：MAC EX 2台（116.205.135.205 / 121.37.232.167，0x2454 login）+ pytdx EX 2台（通达信扩展市场 116.205.143.214 / 国泰君安 103.221.142.82，0x2454 setup）
- EX 辅助接口：get_goods_list / get_goods_count / get_goods_quotes / get_goods_tick_chart / get_goods_transaction 等
- 实测：腾讯00700=466.6 / AAPL=332.8 / IFL0=4683.2 / 沪期权10010971=0.192
- 美股/港股期权：通达信协议无数据（美股期权可用 CBOE 公开接口，见 quantdata/scripts/import/cboe_options.py）

## 与 easy-tdx 对比

| 维度 | easy-tdx | pytdxdata |
|------|----------|----------|
| 并发分页 | ❌ 串行 while 循环 | ✅ 3连接并行 |
| 连接池 | ❌ 单连接单锁 | ✅ 动态池+故障转移 |
| 缓存 | 仅证券列表(1天) | ✅ 双层TTL+single-flight |
| 数据正确性 | 基准 | ✅ K线6字段/报价7字段逐字段一致（实测） |
| 2400根日K耗时 | 0.31s | **0.16s（1.97x）** |
| 缓存命中重取 | — | 0.016s（0网络） |
| 接口覆盖 | 基准 | **36/36 全覆盖** |
| 依赖 | pandas等 | 零运行时依赖 |

实测对比报告：`examples/compare_report.md`

## 已知限制

- 标准通道K线 `count<100` 部分服务器返回空 → 分页器已强制 `min_page=100` 规避
- 标准当日逐笔的笔数字段协议中有但 easy-tdx 模型丢弃（pytdxdata 已显式解析）；历史逐笔无笔数字段
- 报价价格小数位按品种推断（股票2位/指数ETF转债3位），同代码不同市场含义不同（SH 000001=指数 vs SZ 000001=股票）
- 行情数据来自通达信公开服务器，**15分钟延时**；需实时数据请使用券商行情
- 港股/美股/期货/期权数据走通达信 EX 通道（7727），部分市场（美股/港股期权）协议内无数据
- 服务器可用性随时间变化，可用 `python -m pytdxdata.server_probe` 重新探测

## 免责声明

本包仅用于**技术研究与教育目的**。行情数据来自通达信公开接口，不保证实时性与准确性；作者不对任何投资决策负责。使用前请遵守通达信服务条款及相关法律法规。

## 测试

```bash
uv run pytest tests/        # 48个测试：协议/池/缓存/分页/API
```

## 目录

- `examples/quickstart.py` — 快速示例
- `examples/tdx_update_minute.py` — 全市场分钟K线更新（含北交所，45s拉完5,539只）
- `examples/compare_easy_tdx.py` — 与 easy-tdx 对比脚本
