Metadata-Version: 2.5
Name: mootdx2
Version: 1.4.2
Summary: 通达信数据读取接口.
Project-URL: Homepage, https://www.mootdx.com
Project-URL: Repository, https://github.com/mootdx/mootdx
Author-email: bopo <ibopo@126.com>
License-File: AUTHORS.rst
License-File: LICENSE
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Natural Language :: English
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Requires-Python: >=3.8
Requires-Dist: click>=8.1.3
Requires-Dist: filelock>=3.12.0
Requires-Dist: httpx>=0.25.0
Requires-Dist: mini-racer>=0.12.0
Requires-Dist: prettytable>=3.5.0
Requires-Dist: pyyaml>=6.0.3
Requires-Dist: tdxpy>=0.2.5
Requires-Dist: tenacity>=8.1.0
Requires-Dist: tqdm>=4.66.0
Requires-Dist: typing-extensions>=4.5.0
Description-Content-Type: text/markdown

[English](./README_EN.md)

# Mootdx2

[![PyPI version](https://img.shields.io/pypi/v/mootdx2.svg)](https://pypi.org/project/mootdx2/)
[![Python Version](https://img.shields.io/pypi/pyversions/mootdx2.svg)](https://pypi.org/project/mootdx2/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Language](https://img.shields.io/badge/Language-Python%203-blue.svg)](https://www.python.org/)

通达信行情与历史数据读取工具包，支持标准股票与扩展市场在线实时行情、离线历史文件（日线/分钟线/分时线/板块）极速解析、财务数据下载解析、90天长效缓存除权因子管理、跨进程文件锁保护及股票与 ETF 统一前复权/后复权精准计算。

---

## 核心特性

- **在线行情获取**：基于 TDX 原生协议，支持股票、指数、ETF、期权、期货等多品种实时五档行情、历史 K 线、分时线、逐笔成交与财务摘要查询。
- **高可用双轨驱动与自动容灾 (Failover)**：内置纯 Python 原生协议驱动（`driver='native'`）与经典 `tdxpy` 双轨制；配合最优服务器测速（`bestip`）、线程安全连接池（`RLock` 保护）与 300 秒自适应冷却黑名单，异常时自动触发跨驱动平滑降级与节点重试，重试耗尽时返回语义化空对象降级，杜绝程序崩溃。
- **现代资源管理协议**：全面支持上下文管理器协议（`with Quotes.factory(...) as client:`），任务结束或异常时自动释放底层连接与套接字，防止长效运行中的句柄泄漏（FD exhaustion）。
- **本地离线数据解析与反向回写**：高效解析本地通达信数据目录文件（`.day` / `.lc1` / `.lc5` / 板块 `.dat`），支持 Windows、macOS、Linux 默认路径识别，内置 ETF 与指数价格系数自动推断，并支持将 DataFrame 重新序列化打包回写为标准通达信 `.day` 二进制文件。
- **原子落盘与长效跨进程缓存**：除权除息数据（XDXR）与本地缓存采用纳秒级临时文件与 `os.replace` 原子落盘，90 天长效 `.plk` 缓存集成 `filelock` 跨进程互斥锁，杜绝高并发多进程环境下的文件损坏与重复网络拉取。
- **统一精准复权算法**：原生支持股票现金分红/送转股（`category=1`）及 ETF 份额折算（`category=11`），复权失败严格抛出 `ReversionError`，杜绝未复权脏数据流入量化系统。
- **巨潮官方公告与财报下载**：内置基于标准库零三方依赖的 `CninfoClient`，支持按 6 位代码检索上市公司公告与一键下载官方 PDF 财报原件。
- **按需批量预同步**：提供带请求节流（Rate Limiting）的多线程除权数据批量预下载接口与 CLI 命令，提供进度跟踪并记录 `.plk` 文件路径。
- **多格式导出与 CLI 支持**：支持命令行一键导出 CSV、Excel、HDF5、JSON 等格式。

---

## 安装说明

### 使用 pip 安装

```bash
pip install mootdx2
```

安装完整命令行增强依赖：

```bash
pip install "mootdx2[all]"
```

### 开发环境安装

```bash
# 克隆仓库
git clone https://github.com/mootdx/mootdx.git
cd mootdx

# 使用 uv 同步依赖
uv sync --all-groups

# 执行单元测试
uv run pytest
```

---

## 快速上手与 API 说明

### 1. 在线行情读取 (`mootdx2.quotes.Quotes`)

`Quotes` 客户端支持标准市场（`market='std'`）与扩展市场（`market='ext'`），推荐使用 **上下文管理器 (`with`)**，任务结束或异常时会自动释放底层 TCP Socket 连接，杜绝长效量化任务中的连接句柄泄漏：

```python
from mootdx2.quotes import Quotes

# 推荐：使用上下文管理器安全释放资源 (支持心跳维护、多线程连接与自动最优 IP)
with Quotes.factory(market='std', multithread=True, heartbeat=True, bestip=True) as client:
    # 1. 获取 K 线数据 (frequency: 9=日线, 8=1分钟, 0=5分钟; 支持前复权 adjust='qfq')
    df_bars = client.bars(symbol='600036', frequency=9, offset=500, adjust='qfq')
    print(df_bars.tail())

    # 2. 获取指数 K 线
    df_index = client.index(symbol='000001', frequency=9, offset=100)

    # 3. 获取实时多股五档报价
    df_quotes = client.quotes(symbols=['600000', '000001', '600519'])

    # 4. 获取分时数据与历史分时
    df_minute = client.minute(symbol='600036')
    df_hist_minute = client.minutes(symbol='600036', date='2024-01-15')

    # 5. 获取分笔成交明细
    df_trans = client.transaction(symbol='600036', start=0, offset=800)
    df_hist_trans = client.transactions(symbol='600036', start=0, offset=800, date='2024-01-15')

    # 6. 获取除权除息原始数据 (XDXR)
    df_xdxr = client.xdxr(symbol='600036')

    # 7. 获取板块分类与股票数量
    df_block = client.block()
    stock_count = client.stock_count(market=1)  # 0: 深圳, 1: 上海, 2: 北京

    # 8. 批量安全获取日 K 线 (防封节流 + 离线优先 + 自动落盘缓存)
    # 针对几百只股票批量下载场景，默认 80ms 节流、每 50 只批次缓冲休眠、网络抖动指数退避，优先读本地通达信 vipdoc
    batch_results = client.batch_bars(
        symbols=['600000', '000001', '600519'],
        frequency=9,
        offset=800,
        delay=0.08,          # 单次在线请求间隔 (秒)
        batch_size=50,       # 批次大小
        batch_delay=1.0,     # 批次间休息 (秒)
        offline_first=True,  # 优先检查并读取本地通达信 vipdoc (0 网络开销)
        cache=True,          # 在线获取成功后自动缓存至本地磁盘 (24h 有效)
        show_progress=True,  # 显示 tqdm 进度条
    )
```

---

### 2. 本地离线文件读取 (`mootdx2.reader.Reader`)

自动识别本地通达信安装目录（亦可显式指定 `tdxdir`），快速读取本地二进制数据文件：

```python
from mootdx2.reader import Reader

# 初始化 Reader (默认匹配系统路径: Windows C:/new_tdx, macOS ~/new_tdx, Linux ~/.local/share/new_tdx)
reader = Reader.factory(market='std', tdxdir=None)

# 1. 读取日线数据 (支持直接返回前复权/后复权)
df_daily_raw = reader.daily(symbol='600036')
df_daily_qfq = reader.daily(symbol='600036', adjust='qfq')
df_daily_hfq = reader.daily(symbol='600036', adjust='hfq')

# 2. 读取 1 分钟 / 5 分钟线数据
df_min1 = reader.minute(symbol='600036', suffix='1')
df_min5 = reader.minute(symbol='600036', suffix='5')

# 3. 读取分时线数据
df_fzline = reader.fzline(symbol='600036')

# 4. 读取板块数据
df_blocks = reader.block(name='block_gn.dat') # 概念板块
df_custom_blocks = reader.block_new()         # 自定义板块
```

---

### 3. 除权除息、复权因子与长效缓存工具 (`mootdx2.sync` / `mootdx2.utils.factor` / `mootdx2.cache`)

针对量化高频调用、防封禁与多进程安全设计的除权与复权管理体系：

```python
from mootdx2 import get_xdxr, get_xdxr_cache_path, read_xdxr_cache, sync_xdxr
from mootdx2.cache import refresh_all_plk_cache
from mootdx2.exceptions import ReversionError
from mootdx2.tools.reversion import reversion
from mootdx2.utils.factor import fq_factor

# 1. 获取除权除息数据 (自动走 90 天长效缓存与跨进程锁，refresh=True 可强制刷新覆盖本地 .plk)
df_xdxr = get_xdxr('600036', refresh=False)

# 2. 查询与读取本地通达信权息 .plk 文件路径
cache_path = get_xdxr_cache_path('600036')
print(f'Cache Path: {cache_path}')
df_cached = read_xdxr_cache('600036')

# 3. 统一复权计算 (通达信权息优先 -> 新浪因子兜底/涵盖 ETF; 失败严格抛出 ReversionError)
try:
    df_qfq = reversion(symbol='600036', stock_data=raw_df, xdxr=df_xdxr, type_='qfq')
except ReversionError as err:
    print(f'复权计算失败: {err}')

# 4. 新浪复权因子获取与单标的强制刷新
df_factor = fq_factor(symbol='510050', method='qfq', refresh=True)

# 5. 本地 .plk 缓存全量强制刷新
# 扫描本地所有已存在的 .plk 缓存文件，并发向远程请求最新数据并强制重写覆盖本地磁盘
refresh_stat = refresh_all_plk_cache(workers=4, delay=0.05, show_progress=True)
print(f"刷新成功: {refresh_stat['success']}, 刷新失败: {refresh_stat['failed']}")

# 6. 批量预同步除权数据 (支持并发、请求间隔延时节流防封、进度显示与 .plk 汇总)
sync_result = sync_xdxr(
    symbols=['600000', '000001', '600519'], # 或指定 file='stocks.txt'
    force=False,                             # 跳过有效缓存
    workers=4,                               # 线程数
    delay=0.05,                              # 单请求间隔秒数
    show_progress=True,
)
print(f"总计: {sync_result['total']}, 成功: {sync_result['success']}, 跳过: {sync_result['skipped']}")
print(f"缓存文件路径列表: {sync_result['files']}")

# 7. 批量预同步日K线数据 (落盘本地离线缓存)
sync_kline_res = sync_daily(
    symbols=['600000', '000001', '600519'],
    offset=800,
    show_progress=True,
)
print(f"日K同步总计: {sync_kline_res['total']}, 成功: {sync_kline_res['success']}")

# 8. 统一获取复权 K 线 (离线 .day 优先 + 在线增量补齐 + 自动前复权)
client = Quotes.factory('std')
df_kline = client.kline(
    symbol='600036',
    start='2020-01-01',
    end='2024-01-01',
    adjust='qfq',
    offline_first=True,
)
```

---

### 4. 量化库集成与前复权（QFQ）获取方式对比

#### 在线获取 vs 本地文件获取对比

| 对比维度 | 在线行情 (`Quotes.bars` / `Quotes.k`) | 本地文件 (`Reader.daily`) |
| :--- | :--- | :--- |
| **使用前置条件** | **零前置配置**：无需安装通达信软件，开箱即用 | **需有本地文件**：需本地安装通达信并完成“盘后数据下载”（或使用 `mootdx2 bundle` 批量下载） |
| **底层数据来源** | 通达信在线行情服务器 (TCP Socket 协议) | 本地通达信客户端 `vipdoc` 二进制文件 (`.day`) |
| **网络依赖** | 需要网络连接，受行情服务器可用性与频控影响 | **完全离线，0 网络开销**，不受服务器频控限制 |
| **数据实时性** | **实时/最新**：包含盘中最新分时与最近交易日数据 | **静态历史**：取决于本地通达信最后一次盘后下载的时间 |
| **复权计算机制** | 拉取未复权 K 线 + 自动获取/匹配本地 90 天除权缓存进行内存动态前复权 | 解析本地二进制 `.day` 原始记录 + 匹配除权缓存进行内存动态前复权 |
| **吞吐量与性能** | 适合单股/小批量实时查询、监控与日内更新 | **极速吞吐** (内存映射/二进制解析)，适合全市场历史大规模回测 |
| **典型调用** | `client.bars(symbol='600036', adjust='qfq')`<br>`client.k(symbol='600036', adjust='qfq')` | `reader.daily(symbol='600036', adjust='qfq')` |

#### 前置准备与选型指南

1. **在线获取（推荐场景：实盘监控、日常单股查询、轻量化服务器环境）**
   - **前置条件**：仅需能够访问外网 TCP 端口，无需在机器上部署通达信客户端。
   - **执行流程**：直接调用 `Quotes.factory('std').bars('600036', adjust='qfq')`，底层自动拉取原始 K 线并在内存中结合权息信息动态折算前复权数据。

2. **本地文件读取（推荐场景：全市场 5000+ 标的数十次回测、历史特征工程）**
   - **前置条件**：
     - **方式一（客户端同步）**：打开本地通达信客户端 -> 点击菜单 **系统 -> 盘后数据下载** -> 勾选沪深日线并完成下载。
     - **方式二（命令行下载）**：在无图形界面的服务器上使用 `mootdx2 bundle -s 600000,000001 -a daily -o ./data -e csv` 批量获取。
   - **执行流程**：调用 `Reader.factory('std').daily('600036', adjust='qfq')`，直接通过本地二进制文件解析与本地缓存因子动态复权，达到每秒数万条记录的极速吞吐。

---

### 5. 量化系统增量前复权最佳实践

外部量化回测或数据系统（如 `kdata-quant` 等）在本地持久化行情并需要**增量更新**和**前复权（QFQ）**时，推荐采用**“持久化未复权原始行情 + 独立维护除权信息 + 查询时内存动态复权”**的标准架构。

#### 为什么不能直接追加（Append）前复权数据？
前复权以最新价格为基准，一旦标的除权除息（送转/分红），**历史所有前复权价格均会联动重算**。直接在本地追加前复权数据会导致历史基准未调整，产生虚假的跳空暴跌缺口。

#### 快速集成代码示例

```python
import pandas as pd
from mootdx2.quotes import Quotes
from mootdx2.utils.adjust import get_xdxr, to_adjust

client = Quotes.factory("std")

# 1. 增量获取未复权原始日线 (例如从本地最新日期的下一天拉取)
raw_df = client.k(symbol="600036", begin="2024-01-01")

# 2. 刷新或读取本地除权除息缓存 (内置 90 天文件锁长效缓存)
xdxr_df = get_xdxr(symbol="600036", refresh=False)

# 3. 数据规整并设置以 DatetimeIndex 为索引
raw_df["date"] = pd.to_datetime(raw_df.get("date", raw_df.get("datetime", raw_df.index)))
raw_df = raw_df.set_index("date").sort_index()

# 4. 内存动态执行前复权 (必须基于包含最新价格的完整未复权序列计算)
df_qfq = to_adjust(temp_df=raw_df, symbol="600036", adjust="qfq")

# 5. 按照策略实际需要的时间区间切片
df_target = df_qfq.loc["2024-01-01":"2024-06-01"]
```

---

### 6. 巨潮资讯公告检索与 PDF 财报下载 (`mootdx2.data.CninfoClient`)

基于 Python 标准库零额外依赖实现的官方公告与财报下载接口：

```python
from mootdx2 import CninfoClient

client = CninfoClient()

# 1. 检索上市公司最新公告明细
df_announcements = client.get_announcements(code='600519', count=20)
print(df_announcements[['date', 'title', 'type', 'pdf_url']].head())

# 2. 一键下载官方 PDF 财报原件到指定目录
if not df_announcements.empty and df_announcements.iloc[0]['pdf_url']:
    pdf_path = client.download_pdf(df_announcements.iloc[0], dest_dir='./reports')
    print(f'财报原件已保存至: {pdf_path}')
```

---

### 6. 财务数据下载与解析 (`mootdx2.affair.Affair`)

```python
from mootdx2.affair import Affair

# 获取远程财务文件列表
file_list = Affair.files()

# 下载指定季度的财务压缩包
Affair.fetch(downdir='download_dir', filename='gpcw19960630.zip')

# 解析已下载的财务文件为 DataFrame
df_financial = Affair.parse(downdir='download_dir', filename='gpcw19960630.zip')
```

---

## 命令行工具 (CLI)

安装后可直接在终端使用 `mootdx2` 命令：

```bash
# 查看帮助与版本
mootdx2 --help
mootdx2 -V

# 1. 测速并更新最优行情服务器
mootdx2 bestip -l 5 -v

# 2. 批量同步除权因子 (90天长效缓存 + 并发节流防封)
mootdx2 sync -s "600000,000001,600519" -w 4 -d 0.05
mootdx2 sync -f stocks.txt --force

# 3. 获取实时行情并导出
mootdx2 quotes -s 600036 -a daily -o output.csv

# 4. 读取本地通达信数据文件
mootdx2 reader -s 600036 -a daily -o daily_600036.xlsx

# 5. 批量下载历史数据
mootdx2 bundle -s 600000,000001 -a daily -o bundle_dir -e csv

# 6. 下载并列出财务文件
mootdx2 affair -l
mootdx2 affair -f gpcw20230930.zip -d ./data
```

---

## 许可证

本项目基于 [MIT License](./LICENSE) 开源发布。
