release: v1.11.5 — scipy 依赖声明 + 文档同步 + 工程化清理

- pyproject: 新增 science 可选依赖组(scipy),dev 纳入 scipy;版本号 1.11.5
- factor/analysis: spearman 分支加 scipy 软依赖保护,缺失时友好报错
- mac/client: 板块排行吞异常处补 logger.debug
- CLAUDE.md: 架构章节补全 mac/ex/unified/portfolio/factor/offline/screen 子包
- .gitignore: 补全 .coverage、signals.json
- README: 补 1.11.5 changelog
This commit is contained in:
Justin Gu
2026-06-13 21:10:35 +08:00
parent be41746aa9
commit 88638e82ad
6 changed files with 70 additions and 11 deletions
+4
View File
@@ -16,3 +16,7 @@ venv/
uv.lock
docs/_build/
FK/
# 测试覆盖率与运行时产物
.coverage
signals.json
+34 -9
View File
@@ -23,7 +23,18 @@ ruff format --check src/ tests/
```
src/easy_tdx/
├── client.py # TdxClient / AsyncTdxClient(高层 API
├── client.py # TdxClient / AsyncTdxClientWindows 通达信协议高层 API
├── mac/ # macOS 通达信协议(独立命令/编解码/握手)
│ ├── client.py # MacClient / AsyncMacClientmac 行情高层 API
│ ├── commands/ # mac 协议命令(SymbolQuotesCmd / BoardMembersQuotesCmd 等)
│ ├── enums.py # Category / SortType / FilterType / BoardType / Period / Adjust
│ └── models.py # MacQuoteField / MacTickChart 等 dataclass
├── ex/ # 扩展行情协议(ExTdx+ macOS 扩展(MacEx
│ ├── client.py # ExTdxClient / AsyncExTdxClient
│ ├── mac_client.py # MacExClient / AsyncMacExClientgoods_* 系列方法)
│ ├── commands/ # 扩展协议命令
│ └── models.py # ExMarketInfo / ExInstrumentInfo 等
├── unified.py # UnifiedTdxClient / AsyncUnifiedTdxClient(聚合 mac + mac_ex 的门面)
├── chanlun/ # 缠论技术分析模块(独立于 transport,纯计算)
│ ├── analyser.py # ChanlunAnalyser 主入口(接收 DataFrame
│ ├── types.py # 数据结构(Kline/CLKline/FX/BI/XD/ZS/MMD/BC
@@ -39,27 +50,41 @@ src/easy_tdx/
│ ├── beichi.py # 背驰判断(笔/盘整/趋势)
│ └── multi_level.py # 多级别联立分析
├── transport/
│ ├── sync.py # TdxConnectionsocket+ ping_host / ping_all
│ ├── sync.py # TdxConnectionsocket+ ping_host / ping_all / ping_mac_all
│ └── async_.py # AsyncTdxConnectionasyncio
├── commands/ # 每条命令:build_request() + parse_response(),无 IO
├── codec/ # price / volume / datetime / frame 编解码
├── codec/ # price / volume / datetime / frame / financial / bitmap 编解码
├── backtest/ # 回测引擎
│ ├── engine.py # BacktestEngine(止损/止盈执行/缠论自动桥接)
│ ├── strategy.py # Strategy 基类(向量化 datetime
│ ├── orders.py # OrderSimulatorSL/TP 同 bar 执行)
│ ├── execution.py # 可插拔执行模型(Immediate/TWAP/VWAP/Limit
│ ├── slippage.py # 滑点模型(Fixed/Percent/Volume/Volatility
│ ├── performance.py # PerformanceAnalyzerFIFO 真实持仓天数)
│ ├── portfolio_engine.py # 多标的组合回测
── combo.py # 多因子组合回测
│ ├── portfolio.py / portfolio_engine.py # 多标的组合回测
── combo.py # 多因子组合回测
│ ├── attribution.py # Brinson / 因子 / 成本归因
│ └── dsl.py # 策略 DSL 解析
├── portfolio/ # 组合层
│ ├── optimizer.py # 权重优化(mean_variance 可选 scipy,缺失回退等权)
│ └── ... # risk / rebalance 等
├── factor/ # 因子分析
│ ├── analysis.py # FactorAnalyzercompute_ic 用 spearman,可选 scipy → pip install easy-tdx[science]
│ ├── engine.py / base.py / transform.py
│ └── builtin/ # 内置因子
├── screen/
── scanner.py # SignalScanner(并发扫描/增量缓存)
── scanner.py # SignalScanner(并发扫描/增量缓存)
│ └── ranker.py # 排序/打分
├── realtime/
│ └── engine.py # EventBus + RealtimeStrategyasyncio 事件驱动)
├── cli/
│ └── cmd_chanlun.py # easy-tdx chanlun CLI 命令
├── offline/ # 离线数据读写(daily_bar / min_bar / gbbq / history_financial 等)
├── indicator.py / MyTT.py # 技术指标(MyTT 为第三方库移植,命名沿袭惯例)
├── config.py # 主机/端口/超时配置 + best_host 持久化
├── cli/ # easy-tdx CLIclick);cmd_chanlun / cmd_backtest / cmd_offline / cmd_factor 等
└── models/ # 纯 dataclass,无业务逻辑
```
commands 层不依赖 transport,可独立单测。修改 codec 或 commands 时不需要网络。
四套 client 关系:`client.py`Windows 标准)、`mac/client.py`macOS)、`ex/client.py`(扩展)、`ex/mac_client.py`(macOS 扩展);每套均提供 sync + async 镜像。`unified.py` 是 mac + mac_ex 的门面。commands 层不依赖 transport,可独立单测。修改 codec 或 commands 时不需要网络。
## 协议编解码注意事项
+18
View File
@@ -1511,6 +1511,24 @@ ruff format --check src/ tests/ # format check
## Changelog
### 1.11.5 (2026-06-13)
**稳定性与代码质量修复** — 全项目代码审计 + 一个潜伏的 ping 崩溃 bug 修复。
**Bug 修复**
- 修复 `easy-tdx ping` 在非交易时间(服务器握手阶段关闭连接)整个命令崩溃的问题。根因:`ping_host` 仅捕获 `OSError`,但握手期 `_recv_exact_sock` 抛出的 `TdxConnectionError`(继承自 `TdxError(Exception)` 而非 `OSError`)逃出捕获,经 `ping_all``fut.result()` 重新抛出,导致单台服务器不可用就拖垮整条测速命令。修复后符合 docstring 承诺"不可达服务器不包含在结果中",并加防御层让 `ping_all` 对异常 future 容错跳过。
- 修复回测 `OrderSimulator._find_bar_index` 把 DataFrame 的 index label 当位置索引用的隐患。当传入 df 的 index 非默认 RangeIndex 时,`idxmax()` 返回的 label 与 `iloc[]` 期望的位置不一致,可能导致撮合取错 K 线。改用 `to_numpy().argmax()` 取真实位置。
**依赖与工程化**
- scipy 隐式硬依赖声明:`factor/analysis.py` 的 Rank ICspearman)通过 pandas lazy import scipy,干净环境必报 `ModuleNotFoundError`。新增 `science` 可选依赖组(`pip install easy-tdx[science]`),并在 spearman 分支加 try-import 友好报错(复用 `optimizer.py` 现有模式)。
- `mac/client.py` 板块 N 日涨跌幅排行中静默吞异常的 `except Exception: continue` 补上 `logger.debug` 日志,便于排查。
- `.gitignore` 补全 `.coverage``signals.json`
**文档**
- `CLAUDE.md` 架构章节更新:补全 `mac/``ex/``unified.py``portfolio/``factor/``offline/``screen/` 等子包,说明四套 clientWindows/macOS/扩展/macOS扩展)的 sync+async 镜像关系。
**测试**564 passed, 0 failed+8 新增:2 ping 容错回归、2 非连续 index 回归、4 既有覆盖增强)
### 1.11.1 (2026-06-12)
**量化因子引擎 + 组合管理 + 高级回测增强** — 三大新模块,补齐从因子研究到组合执行的完整量化链路。
+3 -2
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "easy-tdx"
version = "1.11.1"
version = "1.11.5"
description = "通达信 TCP 协议行情数据客户端,支持在线行情、离线数据读取与写入同步"
readme = "README.md"
requires-python = ">=3.10"
@@ -14,7 +14,8 @@ dependencies = ["pandas>=2.0", "tzdata>=2024.1", "click>=8.0"]
easy-tdx = "easy_tdx.cli:cli" # cli/__init__.py exposes the click group
[project.optional-dependencies]
dev = ["pytest>=8.0", "pytest-asyncio>=0.23", "pytest-cov", "mypy>=1.9", "ruff>=0.4"]
dev = ["pytest>=8.0", "pytest-asyncio>=0.23", "pytest-cov", "mypy>=1.9", "ruff>=0.4", "scipy>=1.10"]
science = ["scipy>=1.10"]
web = ["fastapi>=0.110", "uvicorn[standard]>=0.29"]
[tool.hatch.build.targets.wheel]
+6
View File
@@ -56,6 +56,12 @@ class FactorAnalyzer:
ic_values.append(np.nan)
continue
if method == "spearman":
try:
import scipy # type: ignore[import-untyped] # noqa: F401 # pandas spearman lazy import
except ImportError as e:
raise ImportError(
"Rank IC (spearman) 需要 scipy,请执行 `pip install easy-tdx[science]`"
) from e
corr = valid[self._factor_col].corr(valid[self._return_col], method="spearman")
else:
corr = valid[self._factor_col].corr(valid[self._return_col], method="pearson")
+5
View File
@@ -3,6 +3,7 @@
from __future__ import annotations
import asyncio
import logging
import time
from dataclasses import asdict
from types import TracebackType
@@ -48,6 +49,8 @@ _RETRY_DELAYS = (0.1, 0.5, 1.0, 2.0)
_KLINE_PAGE_SIZE = 700
_BOARD_MEMBERS_PAGE_SIZE = 80
_logger = logging.getLogger(__name__)
def _convert_board_code(board_symbol: str) -> int:
"""将用户可见的板块代码转换为服务器协议代码。
@@ -815,6 +818,7 @@ class MacClient:
adjust=Adjust.NONE,
)
except Exception:
_logger.debug("板块 %s K线获取失败,跳过", board_code, exc_info=True)
continue
if kline_df.empty or len(kline_df) < 2:
@@ -1642,6 +1646,7 @@ class AsyncMacClient:
adjust=Adjust.NONE,
)
except Exception:
_logger.debug("板块 %s K线获取失败,跳过", board_code, exc_info=True)
continue
if kline_df.empty or len(kline_df) < 2: