# 更新日志 本文件记录 easy-tdx 的版本变更。格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/)。 ## [1.32.1] — 2026-09-04 **行业/概念总览上线:WebUI 新增板块全景双栏目**——此前板块信息只有首页两张「热/冷」小榜单(各 ~10 条、仅当日涨跌幅),想看全部行业/概念的强弱全景、多周期走势与板块间轮动,只能逐个点开。本版在左侧导航新增「行业总览」(/industries,行业一级/二级可切)与「概念总览」(/concepts)两个栏目:全部板块一屏尽览(一级行业 128 / 概念 269 / 二级行业 345),热力图+表格双视图、板块广度统计与涨跌幅分布直方图、涨幅/跌幅/涨速异动三榜、翻红/翻绿轮动时间线;点击板块复用首页弹窗(左分时/日K、右成分股涨跌榜,可再叠个股弹窗)。 ### 新增 - **`GET /api/v1/board-mac/overview` 板块总览聚合端点**([board_mac.py](src/easy_tdx/web/routers/board_mac.py)):一次返回全部板块的当日涨跌幅(严格 `price/pre_close-1` 口径,规避 Issue #53 CHANGE_PCT 值槽恒 0)+ 领涨股(代码/名称/涨幅)+ 涨速/3日/5日/20日/YTD 多周期指标(按各排序键的 `sort_value` 归并)。服务端并发拉取后按板块代码归并、未请求的指标补 null(行结构稳定),结果缓存 15s(TTL)——前端 30s 轮询每次只打 1 个请求(否则需 6~12 次 `/board-mac/list`),休市时段前端自动停刷。 - **行业总览 / 概念总览页**([BoardOverviewView.vue](web-ui/src/views/BoardOverviewView.vue),双路由共用组件、路由 props 区分 HY/GN): - **顶部统计条**([BoardStatStrip.vue](web-ui/src/components/BoardStatStrip.vue)):板块广度(上涨/下跌/平盘家数)、板块涨幅中位数、全市场涨停/跌停家数(`/market/stat`)、板块涨跌幅分布直方图(±1% 一档、±3% 截断两端)。 - **热力图 / 表格双视图**([BoardTiles.vue](web-ui/src/components/BoardTiles.vue)):热力图等宽色块、红涨绿跌 5 档色阶、悬停看详情(价格/涨速/领涨股)、单击开板块弹窗;表格含最新价/涨跌幅/涨速/3日/5日/20日/YTD/领涨股列,表头点击排序(null 沉底);搜索框按名称/代码实时过滤(概念页 269 块全量渲染无压力)。 - **右栏榜单与轮动**([BoardRankRail.vue](web-ui/src/components/BoardRankRail.vue)):涨幅榜/跌幅榜/异动·涨速 Top10(按 |涨速| 降序,急拉急杀都算,|涨速|≥0.5% 橙色闪烁)+ **翻红/翻绿轮动时间线**(前端对相邻两次快照按涨跌幅符号 diff,负转正记翻红、正转负记翻绿,保留最近 30 条)。 - 行业页一级(HY)/二级(HY2)切换;**板块弹窗复用 BoardDialog**:左分时(`/minute`)/日K(`/bars`,MA/BOLL/MACD/KDJ/RSI),右成分股涨跌榜(可切升降序、点成分股叠开 StockDialog),头部 SSE 实时报价 + 加/移自选。 - 刷新与偏好:30s 轮询 + 交易时段门控(09:15~11:30 / 13:00~15:05,可关成全天候模式)+ 页面不可见暂停 + 手动刷新;视图模式/门控开关持久化 localStorage,与首页同款交互。 - 设计文档 [board-overview-design.md](docs/board-overview-design.md):现状盘点、功能/UI 设计、接口 schema、分期计划(P2 资金榜/轮动标签/加自选等后续迭代)。 ### 测试 - 后端新增 6 例([test_board_mac_overview.py](tests/unit/test_board_mac_overview.py)):多排序键归并与涨跌幅口径(`price/pre_close-1`)、未请求指标置 null、TTL 缓存命中(可注入时钟)、缓存键按 board_type 隔离、非法指标 400、空列表与零 pre_close 降级(涨跌幅为 null 而非除零)。 - 真机验证:一级行业 128 / 概念 269 / 二级行业 345 板块全量返回且指标齐全,缓存命中后响应 ~0.3s;Playwright 截图逐页验收热力图、表格、统计条、右栏榜单、板块弹窗与二级切换。 - 全量回归:pytest 1635 通过、ruff/ruff format/mypy 全绿、前端 `node --test` 通过、Playwright E2E 9/9。 ## [1.31.3] — 2026-09-04 **紧急修复盘中分时数据错乱(v1.31.2 回归),并包含 CLI 分析能力对齐**——v1.31.2 引入的"盘中走实时分时命令"路径存在解析错位:实时分时命令(0x0c1b)响应每条记录实际 2 个字段,解析器按 3 字段读取,从第二条起全部错位——个股与指数**盘中**分时出现天文价格(如科创50 出现 67008)与负成交量(如 -2296)。实测确认历史分时接口盘中查"当日"即返回已成交分钟(午休 12:33 时返回上午 120 条,尾价与实时行情现价吻合),实时命令本无必要。本版同时包含此前合入 main 的 CLI 分析能力对齐与寻优指标缓存修复。 ### 紧急修复(分时) - **分时全程改走历史分时接口**([client.py](src/easy_tdx/client.py) `get_minute_time_data`,同步+异步):先查今天——盘中=已成交部分、收盘后=全天 240 条;空(盘前/周末/节假日)回退最近交易日。实时分时命令从 client 移除,docstring 记录弃用原因(响应布局与解析器不匹配)。 - v1.31.2 的**盘前/休市回退**与**指数K线命令适配**(乱码日期检测自动换 `GetIndexBarsCmd`)经验证正确,保持不变。 ### 新增(CLI 对齐 WebUI/Python SDK 的分析能力) 补齐 CLI 此前缺失的三块:一键参数寻优、内置策略列表、组合级 WF/一条龙。引擎层全部复用现成实现(`ParamGridOptimizer`/`STRATEGY_PRESETS`/`evaluate_portfolio`/`PortfolioWalkForwardEngine`),CLI、Web API(`/backtest/optimize`、`/backtest/evaluate` 等)与 Python SDK 三条通路能力对等。 - **`easy-tdx optimize` 参数网格寻优命令**([backtest/cli.py](src/easy_tdx/backtest/cli.py)):单策略网格搜索(`--strategy ma_cross` 用该策略预设网格,或 `--param fast=5,10,15 --param slow=20,60` 自定义),以及 `--all` 一键寻优所有内置策略——逐策略按 `STRATEGY_PRESETS` 预设网格寻优后按总收益率全局排名(对齐 Web UI /optimize 页与 `/backtest/optimize-all/run/async`)。支持 `--workers N` 进程级并行、`--table` 排名表 / JSON 全量输出(含热力图矩阵)。`--strategy` 与 `--param` 在联网取数前前置校验(未知策略/未知参数/网格超限快速失败)。 - **`easy_tdx.backtest.optimizer.optimize_all_strategies` Python API**([optimizer.py](src/easy_tdx/backtest/optimizer.py)):一键全策略寻优的规范实现(`_optimize_strategy_best` 模块级 worker 可 pickle,主进程解析 label、跨策略 ProcessPool 并行),CLI 与后续 Web 端可共用;`presets` 参数支持注入子集网格(测试用)。 - **`easy-tdx strategies` 内置策略列表命令**:列出注册表全部策略(名称/中文标签/参数默认值/预设寻优网格点数/说明),`--output json` 输出与 Web API `GET /backtest/strategies` 同构的完整 schema。 - **`easy-tdx portfolio --evaluate / --wf`**:组合级一条龙评估(组合回测 + 组合 WF + 跨标的适配性体检 + 综合评分 + 组合评级 + 等权买入持有基准对比,调 `evaluate_portfolio`)与组合级 Walk-Forward(`PortfolioWalkForwardEngine`),输出与 Web UI /portfolio 页同构;同时补上 `--auto-fees` 品种感知费率旗标。 ### 修复 - **寻优指标缓存自 v1.25 起全程 0 命中(两段式加速静默失效)**([indicator_cache.py](src/easy_tdx/backtest/indicator_cache.py)):缓存键此前用数组**对象 id** 做签名,而引擎每次 run 会重建数组对象,id 逐点漂移导致跨网格点永不命中——`test_optimizer_cache_reuse_across_grid_points` 一直在失败(仅在带缓存统计的测试里暴露,正确性对拍不受影响,属纯性能回归)。改为**内容哈希**签名(blake2b 16 字节 + dtype/shape),同值不同对象视为同一数据;附带删除防 id 复用的 `_array_refs` 强引用表(内容寻址下不再需要,缓存生命周期内的数组引用也随之释放)。实测 16 点网格命中率 38%,新增「同内容不同对象必须命中」回归测试。 ### 测试 - **分时单测重写为 7 例**(同步/异步双口径):盘中只查今日历史分时(断言单次请求)、盘前回退最近交易日、指数锚点换指数K线命令、无日 K 兜底;盘中实测四类标的(个股 301008 / 上证指数 / 科创50 000688 / 880 板块指数)全部返回当日已成交分钟、尾价与实时现价吻合、零异常行(价格 ≤0 或成交量 <0 的行数为 0)。 - CLI 侧新增 13 例:`optimize` 命令互斥/未知策略/未知参数/畸形参数校验(联网前快速失败)、`strategies` 表格与 JSON 输出、`portfolio --help` 新旗标、`optimize_all_strategies` 排名序/label/skipped/JSON 原生类型、指标缓存「同内容不同对象必须命中」回归。 - 实测验证:`strategies` 列出 54 策略;`optimize --strategy`(3×3 网格)与 `optimize --all --workers 4`(54 策略 316 网格点)真实行情跑通;`portfolio --evaluate`(完整报告含评分/评级/WF/基准)与 `--wf` 真实跑通。 - 全量回归:pytest 1629 全部通过、ruff/ruff format/mypy 全绿(v1.25 既有失败 `test_optimizer_cache_reuse_across_grid_points` 随缓存键修复转绿)。 - CI 排除 `test_ai_llm.py`:LLM 异步任务轮询在 CI 无 API key 且 runner 卡顿时必然轮询超时抖挂,本地(有 mock)仍全量执行;排除后覆盖率 71%,远高于 60% 门槛。 ## [1.31.2] — 2026-09-04 **分时接口全场景修复:盘前/休市不再为空,指数从无到有**——`/api/v1/minute`(今日分时)此前固定用"今天的日期"走历史分时接口,而历史分时的当日数据要收盘后才生成:盘前、周末、节假日调用必然拿到 `{"data":[],"count":0}`,WebUI 分时图一片空白;指数(上证指数 000001、创业板指 399006、880 板块指数等)则**任何时候都为空**——旧实现从未适配指数。本版以「最新一根日 K 的日期」锚定最近交易日,个股与指数全场景有数。 ### 修复 - **盘前/周末/节假日分时为空**([client.py](src/easy_tdx/client.py) `get_minute_time_data`,同步+异步双口径):先取最新日 K 日期锚定最近交易日——等于今天(盘中/收盘后)走实时分时命令 `GetMinuteTimeDataCmd`(协议层早有此命令但 client 从未使用),早于今天(盘前/休市)自动回退查该日的历史分时。分时图任何时候打开都显示最近交易日的完整 240 条,9:30 开盘后自动切换为当日实时分时;无需维护节假日表(交易日由服务器日 K 事实决定)。 - **指数分时始终为空**:指数的 K 线响应每条比个股多 4 字节,锚点查询用个股 K 线命令解析指数数据会错位出乱码日期(实测解析出 116785687)。`_latest_trade_date` 现在先按个股命令查询并校验日期落位 `[19900101, 今天]`,不合法自动换指数 K 线命令 `GetIndexBarsCmd` 重查——不依赖代码前缀规则,个股/指数/880 板块指数全部自适应。 - **盘前占位脏数据防御**:实时分时接口在盘前会返回 240 条价格从 0 累加的占位数据,识别(首条价格 0)后不采用,避免脏数据进图。 ### 测试 - 分时单测重写+扩充为 8 例(同步/异步双口径):盘前回退最近交易日、盘中走实时命令、占位数据防御、无日 K 兜底(维持旧契约)、指数乱码日期→换指数 K 线命令锚定。 - 实测三类真实验证:个股 301008 / 指数 000001・399006・880958 盘前均返回最近交易日(2026-09-03)240 条,分时尾价与日 K 收盘价一致。 ## [1.31.1] — 2026-09-04 **策略库「重跑到今天」补齐组合分析**——v1.31.0 把组合分析链路补到了多标的组合页(/portfolio),但策略库(/strategies)里 `kind: 'multi'` 的**多策略组合**卡片点「↻ 重跑到今天」仍只跑主回测,没有 Walk-Forward、一条龙和 AI 解读;且结果区「绩效指标」表只透传 19 项老指标,v1.28 的深度 6 项(SQN/最大连胜连亏/Ulcer/VaR/CVaR)被丢弃。本版把分析链路延伸到多策略组合(N 策略 × 各自原标的),WebUI/REST 双端同步。 ### 新增(多策略组合级分析,对齐单标的) - **多策略组合级 Walk-Forward**(`MultiStrategyWalkForwardEngine`,[walkforward.py](src/easy_tdx/backtest/walkforward.py)):组合 WF 泛化为**槽位模型**(`_ComboSlot` + `_ComboWalkForwardBase` 基类),每个槽位是「一个策略 × 它自己的标的」(key 形如 `{label}@{symbol}`,与 `MultiStrategyEngine.individual_results` 一致),复用切窗语义(日期并集切窗、每窗独立开仓、上下文预热不污染信号)与 `WalkForwardResult` 输出结构;`PortfolioWalkForwardEngine`(一个策略 × 多标的)改为基类子类,行为不变。前端 WalkForwardPanel 零改动直接渲染。REST 端点 `POST /backtest/multi-strategy/wf/run/async`。 - **多策略组合级一条龙评估**(`evaluate_multi`,[benchmark.py](src/easy_tdx/backtest/benchmark.py)):`MultiStrategyEngine` 组合回测 + 多策略组合 WF + 逐槽位三段体检(各自策略 × 各自标的,8 项检查按「≥60% 标的通过」多数口径聚合)+ 综合评分(叠加 WF 一致性)+ 组合评级 + **各槽位标的的等权买入持有基准对比**(α/β/信息比率/跟踪误差),报告结构与单标的 `evaluate_strategy` 同构。REST 端点 `POST /backtest/multi-strategy/evaluate/run/async`。 - **策略库组合结果区新增分析入口**([StrategiesView.vue](web-ui/src/views/StrategiesView.vue)):「重跑到今天」跑完后,结果区标题栏新增 **🔬 WF 样本外验证** / **📋 一条龙评估** / **🤖 AI 解读** 按钮,按需触发(复用最近一次组合回测的 items/cash,区间保持不变——检验的是"这组配置"的稳定性);WF 面板与一条龙面板与单标的/多标的组合页同构。 - **组合 AI 解读 multi 模式**(`buildPortfolioAiPrompt`,[aiPrompt.ts](web-ui/src/aiPrompt.ts)):角色设定改为「N 个策略各跑各自的原标的,资金均分」语境,任务建议改为"换掉拖后腿的策略"等组合向表述;配置段列出策略明细(替代单一策略/参数行),各槽位表现榜按收益降序;解读随已跑完的 WF/一条龙数据一并打包。 - **多策略组合回测响应附带评级与评分**:`_run_multi_strategy_backtest` 与单标的/多标的组合响应同构,返回 `grade`(组合净值 5 维度口径)与 `score`。 ### 修复 - **策略库绩效指标表缺深度指标**:`comboPerf` 此前只映射 19 项,v1.28 的 SQN/最大连胜连亏/Ulcer/VaR/CVaR 6 项被丢弃——补齐全 25 项,与单标的 MetricTable 同口径。 ### 测试 - 后端新增 8 例:多策略组合 WF 引擎(窗口结构/与 Portfolio 版同构/空槽位容错)、`evaluate_multi`(报告结构/买入持有超额近零/JSON 兼容)、两个新端点的 Web 级端到端(mock 取数 + 真实引擎)。 - 前端新增 1 例:aiPrompt multi 模式(策略明细语境、槽位表现段标题、不再出现单一策略行)。 - 全量验证:pytest 1611 通过、ruff/ruff format/mypy 全绿、node --test 5/5、Playwright E2E 9/9。 ## [1.31.0] — 2026-09-03 **组合回测分析体系全面对齐单标的**——此前单标的回测已有 Walk-Forward 样本外验证、一条龙评估、SQN 系统质量/最大连胜连亏等 25 项绩效指标、S-D 评级与 AI 解读;而组合回测(一个策略 × 多只标的)只能看 4 个数字(总收益/假年化/标的数/总资金)加一条净值曲线,"组合跑得到底好不好、稳不稳"全靠猜。本版把整条分析链路在组合端补齐,WebUI/REST 双端同步。 ### 新增(组合级分析,对齐单标的) - **组合完整 25 项绩效指标**([portfolio_engine.py](src/easy_tdx/backtest/portfolio_engine.py)):合并净值曲线 + 各标的汇总成交喂 `PerformanceAnalyzer`,输出与单标的同口径的完整指标——含 **SQN 系统质量、最大连胜、最大连亏**,及夏普/索提诺/卡玛/Ulcer/VaR/CVaR/胜率/盈亏比/平均持仓天数等;另附 `total_stocks` / `total_cash` 组合字段。WebUI 组合页新增「组合绩效指标」表(复用 MetricTable)。 - **组合级 Walk-Forward 样本外验证**(`PortfolioWalkForwardEngine`,[walkforward.py](src/easy_tdx/backtest/walkforward.py)):按**全部标的日期并集**切窗(前 30% 预热区 + N 个连续测试窗),每窗各标的带上下文独立回测(`warmup_bars` 压制上下文信号、窗口起点空仓),合成组合窗内净值后算与单标的同构的逐窗指标;晚上市/停牌标的自动逐窗跳过。输出复用 `WalkForwardResult` 结构,前端 WalkForwardPanel 零改动直接渲染。REST 端点 `POST /backtest/portfolio/wf/run/async`。 - **组合级一条龙评估**(`evaluate_portfolio`,[benchmark.py](src/easy_tdx/backtest/benchmark.py)):组合回测 + 组合 WF + 适配性体检 + 综合评分(叠加 WF 一致性)+ 组合评级 + **等权买入持有组合基准**对比(α/β/信息比率/跟踪误差),报告结构与单标的 `evaluate_strategy` 同构,前端 EvaluatePanel 直接复用。其中适配性体检为**跨标的多数口径聚合**:逐标的跑三段体检,8 项检查按「≥60% 标的通过」合成,段指标取截面均值(收益/夏普/胜率均值、回撤取最深、交易数合计),诚实反映组合整体形态。REST 端点 `POST /backtest/portfolio/evaluate/run/async`。 - **组合交易明细**:`PortfolioResult` 新增组合层汇总成交表(各标的 concat + `symbol` 列),`PerformanceAnalyzer` 的 FIFO 持仓天数配对支持按 `symbol` 分组(避免 A 股的买入被 B 股的卖出错误配对);组合页新增带标的列的成交明细表(按时间倒序,最近 200 笔)。 - **组合回测响应附带评级与评分**:`_run_portfolio_backtest` 现与单标的 `_run_backtest` 同构,返回 `grade`(`grade_portfolio_equity` 净值 5 维度口径)与 `score`(`score_strategy`),前端/REST 直接消费。 ### 新增(AI 解读) - **组合版 AI 解读 Prompt**(`buildPortfolioAiPrompt`,[aiPrompt.ts](web-ui/src/aiPrompt.ts)):打包组合配置(标的清单/资金均分口径)、完整 25 项指标、净值概览、各标的表现榜(按收益降序)、可选的组合 WF/一条龙/评级段落与组合最近成交,角色设定明确「一篮子标的、资金均分」语境并要求关注标的集中度。弹窗交互(复制/下载/直接解读)抽为通用组件 `AiInterpretModal.vue`,单标的回测页与组合页共用(行为不变)。 - **AI 解读历史上下文**:组合解读落库时携带 `kind: "portfolio"` 与标的清单,供 AI 解读历史页「去回测」引导。 ### 修复 - **组合净值回撤口径错误**:`_build_combined_equity` 的 `drawdown` 此前为负值(`total - peak`)、`drawdown_pct` 以固定初始资金为分母——净值翻倍后回撤百分比会被放大数倍,且 EquityChart(取负显示)会把回撤画成正区域。统一为逐点峰值口径(`drawdown = peak - total`,`drawdown_pct = drawdown / peak`),与单标的 `PortfolioTracker`、`MultiStrategyEngine` 一致。 - **组合假年化**:`annual_return` 此前直接等于 `total_return`(代码自注"简化"),现由 `PerformanceAnalyzer` 按时间长度真实年化。 - **按标的取行情的日期列规范化**(`_normalize_bars_dt`,Web 回测路由):真实 TDX 日线返回 int `date` 列、分钟线返回 `datetime`,而 E2E mock 返回字符串 `date`——字符串直接喂引擎会在 `StrategyDataProxy` 的 float 强转处报错,遗留 `date` 冗余列亦然。统一改名 `date`→`datetime`、字符串 coerce 成 datetime64、删除冗余列,覆盖单标的/组合/多策略三条取数路径。该问题由新增的组合页 E2E 用例揭露。 ### 前端 - 组合页(PortfolioView)新增「附加分析」勾选区:组合级 Walk-Forward(窗口数可调)与一条龙评估,随「开始组合回测」并行运行(互不阻塞、独立错误提示);报告区新增组合绩效指标/WF 面板/一条龙面板/组合成交明细四个区块。 - `TradeTable` 支持可选 `showSymbol` 列;`EvaluatePanel` 支持可选 `gradeOverride`(组合口径评级);`PortfolioResult` 类型扩展完整绩效/成交/评级/评分字段(老结果缺省兼容)。 ### 测试 - 后端新增 17 例:组合完整指标(键全集/真年化/回撤口径/symbol 列/资金加权一致性)、组合 WF(窗口结构/聚合口径/数据不足/晚上市容错/JSON 兼容)、`evaluate_portfolio`(报告结构/买入持有超额近零/JSON 兼容)、三个新端点与组合响应附带的 grade/score/trades 的 Web 级端到端。 - 前端:aiPrompt 新增组合版 2 例(段落随可选数据增减、防御性数字转换);Playwright E2E 新增组合页 2 例(全流程出完整指标与成交明细、附加分析 + AI 组合 Prompt 打包断言)。 - 全量验证:pytest 1603 通过、ruff/ruff format/mypy 全绿、node --test 4/4、Playwright 9/9。 ## [1.30.3] — 2026-09-03 **修复「一键寻优所有策略」漏掉 35 个新策略**——v1.30.2 把内置策略从 19 个扩到 54 个,但「一键寻优」走的是另一份独立清单 `STRATEGY_PRESETS`(参数寻优预设网格):该清单未同步登记新策略,而未登记的策略会被**静默跳过**(仅记 warning)——于是 WebUI 寻优页策略下拉能看到 54 个策略,点「一键寻优所有策略」却仍只寻优旧的 19 个、合计 174 网格点,v1.30.2 的新策略全部缺席全局排名。 ### 修复 - **`STRATEGY_PRESETS` 补齐 35 个新策略的预设网格**([presets.py](src/easy_tdx/backtest/strategies/presets.py)):每个策略 1-2 个关键参数、3-9 个网格点,取值全部通过参数 schema 边界校验(跨参数约束如 `n161.8 震荡 / <38.2 趋势,策略侧稀缺的"行情状态开关")、`BBP` 布林 %B 位置、`BBW` 布林带宽(收窄=变盘预警)。 - **量能/资金类**:`AD` 累积/派发线(Marc Chaikin)、`CMF` 佳庆资金流量、`EFI` 艾尔德强力指数——补齐此前最薄弱的"资金流向"一块。 - 明确排除项维持 v1.30.1 的口径:ZigZag 家族、`FINDHIGH/FINDLOW/BACKSET` 等通达信未来函数、需右侧 K 线确认的 Bill Williams Fractals、需要 tick 级数据的 Volume Profile 均不引入。 ### 新增(策略,35 个,内置策略 19 → 54) - **存量指标补策略(19 个)**:`psy_reversal` / `mtm_cross` / `roc_zero` / `expma_cross` / `dfma_cross` / `cr_reversal` / `xsii_breakout` / `obv_cross` / `vr_reversal` / `mass_cross` / `mfi_reversal` / `brar_reversal` / `asi_cross` / `zhuoyao_trend`(多周期涨幅共振)/ `bias_signal_cross` / `sar_follow` / `vwap_cross` / `aroon_cross` / `fk_reversal`。 - **新指标首发策略(16 个)**:`supertrend`(方向翻转跟随)/ `kama_cross` / `hma_cross` / `chandelier`(通道突破进场+吊灯止损离场)/ `ichimoku_cross`(转换/基准线金叉+云层位置确认)/ `uos_reversal` / `cmo_reversal` / `tsi_cross` / `fisher_cross` / `squeeze_breakout`(挤压解除+动量方向)/ `chop_trend`(趋态开关+均线方向)/ `ad_cross` / `cmf_zero` / `efi_zero` / `bbp_reversal` / `bbw_squeeze`。全部实现 `entry_exit_masks`,走上 v1.28 的向量化快速路径。 ### 测试与防回归 - **无未来函数前缀一致性回归(`TestNoLookahead`)**:把 200 根 K 线截断到前 120 根,16 个新指标的输出在重叠段必须与"仅用前缀数据计算"逐位一致——未来函数的致命特征是后到数据改写历史输出,任何引用 t+1 之后数据的实现当场爆红。该测试与黄金基线、向量化对拍构成三重防线。 - 向量化对拍扩至 74 例全绿(54 策略逐 bar vs 向量化逐位一致且全部实际产生交易);黄金基线 `backtest_metrics.json` 重生成至 54 策略全量锁定,**19 个存量策略数字一位未动**(存量行为零漂移)。`test_mytt.py` 新增 47 例数值正确性测试(范围/预热/公式口径/一字板除零保护)。 - 文档同步:README 策略计数 19 → 54,《回测系统完全上手手册》陈旧的"18 个内置策略"修正。 ## [1.30.1] — 2026-09-03 **彻底移除 ZIG 之字转向指标与 `zig_breakout` 策略**——ZIG 是教科书级的**未来函数**:波峰/波谷拐点只有在**其后**的走势反向走满 X% 确认转向后才会**回溯标出**,也就是说序列里每个拐点的位置都用到了"当时不可能知道"的未来信息。把它当买卖信号回测,等于允许策略在波谷那一天精准买入、在见顶前一天精准卖出——收益必然严重虚高、参数寻优必然过拟合,回测结果与实盘表现脱节,**这正是本工具最要防的失真**。 ### 移除 - **为什么必须删,而不是加警示**——v1.29 引入时已尝试用「硬止损 + 右侧突破确认进场」两层保护对冲前视偏差,但那只能**缓解**而非**消除**:`zig_breakout` 的建仓信号"ZIG 向上启动(波谷确认)"本质上仍是偷看未来的产物,建立其上的任何回测数字、寻优选参、AI 解读都自带系统性偏差。铁证就在本项目自己的黄金基线里:`zig_breakout` 在固定样本上胜率 **100%**、总收益 **+88.2%**、夏普 **1.73**——一组完美得不像真话的数字,恰恰是前视偏差的签名(实盘信号天然滞后于回测拐点,实盘表现只会大幅劣于回测)。回测工具的生命线是"数字可信":与其留着一个数字必然失真的指标靠 docstring 警告自律(挡不住复制粘贴),不如删掉——**宁可少一个指标,不给用户一个实盘必然打脸的完美陷阱**。 - **移除范围(三端同步消失)**——① `MyTT.ZIG` 指标函数(`src/easy_tdx/MyTT.py`);② 内置策略 `zig_breakout`(`backtest/strategies/builtin.py` 注册表删除后,CLI 回测、Web API、WebUI 回测策略下拉与对比页自动不再出现);③ 参数寻优预设网格(`presets.py`)与「一键寻优所有策略」中的对应条目;④ 仓库根示例策略 `strategies/zig_breakout.py`(`--strategy-file` 入口);⑤ 单测 `test_mytt_zig.py` / `test_zig_strategy.py` 删除,向量化对拍测试的路径依赖白名单随之清空(其余全部 19 个内置策略均可向量化对拍),黄金基线 `backtest_metrics.json` 移除 zig_breakout 条目(其余策略零漂移);⑥ README / strategies/README / 架构文档中的相关说明。 - **兼容性影响(有意为之)**——外部脚本 `from easy_tdx.MyTT import ZIG` 将抛 `ImportError`,引用 `zig_breakout` 的回测命令 / API 调用将报"策略不存在":**快速失败好过静默失真**。需要之字转向类形态分析的用户,可自行基于已确认 K 线实现不含未来函数的右侧确认版本,或改用海龟突破(`turtle_breakout`)/ 唐奇安通道(`donchian`)等天然右侧的策略。 ## [1.29.2] — 2026-09-02 **可编辑安装失效时给出友好报错**([#58](https://github.com/handsomejustin/easy_tdx/discussions/58))——`pip install -e .` 会在 site-packages 写入 `_editable_impl_easy_tdx.pth`(内容为仓库 `src/` 绝对路径)。仓库目录被移动/重命名/重新 clone 或该文件丢失后,`easy-tdx` 只会抛出一句无法定位的 `No module named 'easy_tdx.cli'`:`easy_tdx` 本体因 site-packages 里的 `web/dist` 命名空间碎片仍可导入,报错极具误导性(实测复现,失效态 `easy_tdx.__path__` 只剩 site-packages 碎片路径)。 ### 新增 - **入口守卫模块 `easy_tdx._editable_guard`**——控制台脚本入口从 `easy_tdx.cli:cli` 改为经 `main()` 转发(正常态行为完全不变);`easy_tdx.cli` 不可导入时打印中文修复指引(失效原因说明 + `python -c "import easy_tdx; print(easy_tdx.__path__)"` 排查命令 + 免重建 venv 的修复命令 `pip uninstall easy-tdx -y && pip install -e . --no-deps` + issue 链接),退出码 1。守卫文件经 `force-include` 同时复制进 site-packages 的碎片目录——失效态下 `src/` 代码全部不可达,唯有该副本可导入,这正是守卫能"在坏掉时还活着"的关键(`__init__.py` 途径不可行:失效态解析到的是无 `__init__.py` 的命名空间碎片)。 - 单测 `tests/unit/test_editable_guard.py`(2 例):meta_path 阻断器模拟 `ModuleNotFoundError` → 断言提示内容与退出码;正常态断言转发调用 click 组。已本地实测三种安装形态:可编辑健康态 `--help` 正常、模拟失效态输出指引且退出码 1、wheel 安装态正常(hatchling 对包内文件 + force-include 同路径映射自动去重,guard 在 wheel 中恰好一份)。 ## [1.29.1] — 2026-09-02 **中金所成交持仓排名采集(ccpm,独立数据源)**——散户能免费看到的**最接近"主力动向"的公开数据**:每个交易日收盘后约 16:15,中金所官网公布各期货品种「成交量 / 持买单量(多单)/ 持卖单量(空单)」各前 20 名期货公司会员排名。新增 `easy_tdx.ccpm` 模块并三端接入(CLI / Web API / WebUI),零第三方依赖(标准库 urllib)。 ### 新增 - **核心模块 `easy_tdx.ccpm`**——抓取官网 `/sj/ccpm/{YYYYMM}/{DD}/{品种}.xml`(单文件含该品种全部合约 × 三类排名 × 各前 20 名会员)。协议逆向要点:官网 JS 的 `?id=` 仅为 0~99 随机防缓存参数可省略;非交易日返回 302→error_404,禁用 urllib 自动重定向并把 302/404 识别为「无数据」(`CcpmNoDataError`,区别于网络错误 `CcpmError`);仅 http 可用(https 握手失败);历史可回溯至 2012 年。`CcpmClient.get_rank()` 指定日期抓取、`latest_rank()` 自动回溯最近交易日(缺省最多回溯 15 天,覆盖春节长假);每个交易日数据发布后不可变 → 按日落盘缓存 `~/.easy_tdx/cache/ccpm/{YYYYMMDD}/{品种}.json`(随 `EASY_TDX_CONFIG_DIR`),历史二次查询零网络,`refresh=True` 强制重抓。 - **品种覆盖 8 个**:IF 沪深300 / IH 上证50 / IC 中证500 / IM 中证1000 股指期货 + TS/TF/T/TL 2/5/10/30 年期国债期货;品种元数据(标的 / 合约规模 / 一句话科普)集中在 `ccpm/models.py`,三端共用同一份文案。 - **CLI `easy-tdx ccpm`**——`easy-tdx ccpm IF [--date YYYY-MM-DD] [--table] [--refresh] [--no-cache]`,品种参数支持 `all` 一次抓全部 8 个品种(实测 460 行);`--table` 自动切换中文表头(JSON/CSV 保持英文机器友好列名)。 - **Web API**——`GET /api/v1/ccpm/products`(品种科普元数据)+ `GET /api/v1/ccpm/rank?product=IF&date=2026-09-02`(`date` 缺省自动回溯;404=该日期非交易日或数据未发布,文案说明 16:15 发布时间;`refresh` 参数强制重抓)。 - **WebUI「期货持仓排名」页**(行情组导航)——品种下拉(带中文名)+ 日期选择器 + 「自动取最近交易日」回溯开关 + 一键采集按钮;合约页签自动标注**主力**(=当日合计成交量最大的合约);前 20 名合计概览 chips(多单/空单/净持仓·多−空/当日成交,红涨绿跌);三组排名并排表格(与官网 CSV 同构),底部合计行;手动选非交易日给友好错误并可一键切回自动回溯。 - **三段新手科普折叠帮助**(面向小白用户):①「这是什么数据」——"(代客)"=期货公司经纪客户合计而非自营、只统计前 20 名(约占全市场六到八成)、「增减」=加仓/减仓语义;②「品种一览」——IF/IH/IC/IM 各跟踪哪个指数、国债期货=利率期货(价格与市场利率反向,期限越长越敏感);③「多单、空单、加减仓怎么看」——多单=看涨或锁成本、空单=看跌**或**套保对冲,重点强调**排名表看不出套保还是投机,空单多 ≠ 看空市场**(股指期货空单大头常是机构套保盘),净持仓只是情绪参考,期货是零和合约全市场多空永远相等;另附页面级风险提示(期货带杠杆,亏损可超本金)。 - **CLI/Web 测试 20 例**(`tests/unit/test_ccpm.py`,mock HTTP 零网络):XML 长表→宽表对齐、缺单元格容错、302→无数据翻译、按日缓存命中/强制刷新、latest_rank 回溯与耗尽、品种元数据完整性、路由 200/404/422、CliRunner 三例。 ## [1.29.0] — 2026-09-02 **借鉴社区 Fork([swimmingaaron/easy_tdx](https://github.com/swimmingaaron/easy_tdx))的六项实用特性**——该 Fork 自 v1.20.12 分叉后独立演化出一批好想法,本轮逐项甄别后移植其精华(剥离其单文件前端/平行后端层/硬编码个人路径等不可维护部分):ZIG 策略、交易时段感知刷新、120 分钟 K 线、逐 bar 衍生字段、159 只核心龙头池、多 Provider LLM 直连。 ### 新增 - **ZIG 右侧突破回补策略**(`zig_breakout`)——`MyTT` 新增 `ZIG` 之字转向指标(未来函数,拐点回溯标出;实现自 Fork 移植并补前视偏差警示文档)。策略逻辑:ZIG 波谷启动全仓买入(挂 `stop_loss_pct` 硬止损,OCO 由引擎逐 bar 监控)→ 见顶清仓并记录 HHV(N) 前高 → 收盘突破前高×(1+确认比例%) 右侧回补。ZIG 的前视偏差用「止损保护 + 右侧确认进场」两层对冲而非消除,策略 docstring 明示回测信号有前视性。因 `_breakout_level` 随持仓路径变化,不实现 `entry_exit_masks`(引擎自动走逐 bar 回放,向量化守护测试白名单放行)。同时登记寻优预设网格(zig_delta×confirm_pct=12 点)与独立策略文件 `strategies/zig_breakout.py`(供 `--strategy-file` 离线扫描)。 - **交易时段感知的仪表盘自动刷新**——新增共享模块 `realtime/session.py`:`is_trading_time()`(窗口 09:15~11:30:30 / 13:00~15:05,含集合竞价与收盘竞价缓冲,午休排除,周一至五)+ `GET /market/session`。WebUI 市场看板的 30/60/120s 三档轮询在休市时自动暂停(状态栏三态:交易中/休市已暂停/全天候模式),每分钟重估跨边界即时切换;「仅交易时段自动刷新」开关 localStorage 持久化,手动刷新按钮不受限。后端 SSE/WS 推送本就带时段过滤(feed `_DEFAULT_SESSIONS` / streamer 降频),本次不改其既有语义。 - **120 分钟 K 线**(`/bars?category=MIN_120`,别名 `120M`/`120MIN`)——协议无此枚举,路由层特判:优先 MAC 原生 `Period.MINS × times=120`;失败则取 2 倍 60M 相邻两根聚合(open=first / high=max / low=min / close=last / vol·amount=sum,时间取后一根,奇数根丢最旧保最新);标准 TdxClient 回退路径受单次 800 根限制最多合成 400 根。前端周期选择器(单标的回测 / 组合回测)新增 `MIN_120` 选项。 - **K 线逐 bar 衍生字段**——`/bars` 与 `/bars/index` 每根 bar 附带 `pre_close`(前收,首根退化为本根开盘)、`change`、`change_pct`、`amplitude_pct`(振幅%),前端无需重算;`pre_close ≤ 0.01` 按 0.01 兜底(QFQ 复权后早期价格可能为 0/负)。 - **159 只核心龙头池**(`screen/universe.py`,数据资产取自 Fork 按东方财富全行业龙头名单整理的 `CORE_UNIVERSE`,剥离其缓存/个人路径实现)——四组分层(全球第一/国内第一/科技细分/行业冠军),`universe="core"` 接入 `screen scan` CLI、`SignalScanner` 与 `StrengthRanker`(离线 .day 扫描按名单过滤,约 3 秒扫完龙头池),`/market/strength` API 同步支持;另暴露 `GET /market/core-leaders`。 - **全局风险提示与免责声明(Web UI)**——App 外壳底部新增常驻提示栏,覆盖全部页面(行情 / 回测 / 选股扫描 / AI 解读统一口径:"仅供量化研究与学习,不构成任何投资建议或个股推荐;历史表现不代表未来,股市有风险,据此操作风险自负")。龙头池页另加显著说明块:讲清名单含义(按东财公开资料整理的**扫描范围筛选清单**,仅描述行业地位的客观事实)与用途(`universe=core`),明确"不构成任何形式的个股推荐/买入建议/投资顾问服务,不对据此操作承担责任"。AI 解读正文(回测弹窗与历史页)均随附"AI 生成内容可能出错,仅供参考,不构成投资建议"提示。 - **AI 解读历史 + 龙头池页面(Web UI 导航新增「AI 解读历史」「龙头池」)**——每次成功的「直接解读」自动归档到 `~/.easy_tdx/llm_history.db`(SQLite,`llm_history_store`):提问 Prompt、解读正文、模型/耗时与当时的策略上下文(策略/参数/标的/周期/日期区间)。历史页按时间倒序展开查看,每条带「→ 去回测(带参数)」一键跳回回测页复现场景(复用寻优页的 query 预填链路)、查看提问 Prompt、删除/清空;API 为 `GET/DELETE /llm/history`。「龙头池」页展示 159 只核心龙头(搜索过滤 + 点击进个股详情,即 `universe=core` 同一名单)。另为前端路由表加兜底重定向:未注册路径(如把 API 路径当页面访问)回看板而非渲染空白。 - **多 Provider LLM 直连 + WebUI「AI 设置」页**——新增 `easy_tdx.ai` 模块与 `/llm/*` 路由。Provider 预设 9 家:DeepSeek / 通义千问 / 智谱 GLM(bigmodel.cn)/ Kimi / MiniMax / OpenAI / Claude(Anthropic 原生协议)/ Ollama(本地免 Key)/ 自定义(任意 OpenAI 兼容网关),base_url 与模型均可覆盖。配置落盘 `~/.easy_tdx/llm.json`(随 `EASY_TDX_CONFIG_DIR`),WebUI 表单与手工编辑同一份文件、双向兼容;字段级优先级 = 文件 > 环境变量(`LLM_PROVIDER`/`LLM_API_KEY`/`LLM_BASE_URL`/`LLM_MODEL`)> 预设默认。API:GET/PUT `/llm/config`(key 脱敏回显,回传脱敏串不覆盖真 key)、POST `/llm/test`(连通性+延迟)、POST `/llm/chat`。回测页「🤖 AI 解读」在模型已配置时新增「✨ 直接解读」——把组装好的报告 Prompt 提交为**后台任务**(接入与回测同一套 `task_runner`:4 线程池 + SQLite 持久化),前端短轮询 `GET /llm/chat/tasks/{task_id}` 取结果(`POST /llm/chat/async`,202),长耗时模型调用不占 HTTP 连接、断线重连后仍可查询,按钮实时显示已耗时;配置不完整在提交期即报 400,网络/鉴权/超时错误体现在任务态 `error`(读超时文案给出「调大超时」动作,默认超时 180s 可调至 600s)。未配置模型时保持导出 Prompt 手动路径。**思考型模型空白正文防御**(实测:GLM-5.x 的 `reasoning_content` 思考链计入 max_tokens,4000 预算被整份报告的思考耗尽后 `content` 为空白——truthy 但渲染为空,状态条报成功而正文空白):解析层对空白正文显式拦截——有思考链时报「调大 Max Tokens」的可操作错误(含当前值与 finish_reason),无思考链按格式错误上报,绝不返回空串;max_tokens 默认 4000→16000(上限即目标,按实际生成计费),前端再拦一道纯空白。零第三方依赖(标准库 urllib + `asyncio.to_thread`)。 ### 测试 - 新增 6 个单测文件共 51 例:`test_mytt_zig.py`(ZIG 边界/单调/V 型/锯齿/阈值双写法)、`test_zig_strategy.py`(注册/参数校验/引擎成交/独立文件加载/预设网格)、`test_realtime_session.py`(窗口边界/午休/周末/session_info)、`test_bars_min120_derived.py`(重采样聚合/裁剪/缺列、衍生字段/兜底)、`test_screen_universe_core.py`(名单 159 只唯一性/已知龙头/core 过滤准确性)、`test_ai_llm.py`(配置文件↔环境变量优先级/脱敏/双协议请求组装/HTTP 错误包装,HTTP 层 monkeypatch 零真实网络)。 - 黄金基线 `tests/golden/backtest_metrics.json` 重新生成:仅新增 zig_breakout 条目(5 笔交易),其余策略零漂移。 - 新增 `test_llm_history_store.py`(6 例:倒序/上下文 JSON 往返/坏数据容忍/删除清空/limit)与异步解读自动落历史 + 失败不落库的 API 级测试。 ## [1.28.2] — 2026-09-02 **修复指数/个股 K 线 vol 字段的三类协议语义错误**([#64](https://github.com/handsomejustin/easy_tdx/issues/64))——通达信服务端 K 线记录的第一个 4 字节字段(一直被当作成交量透传)的语义随周期/品种变化,此前原样返回错误数据。本轮通过逐字节拆包原始报文 + 新浪实时行情/东方财富分钟 K 三方交叉验证锁定规律后,在协议解析层(`GetIndexBarsCmd` / `GetSecurityBarsCmd` 的 `parse_response`,同步/异步客户端共用)统一修正。 ### 修复 - **指数分钟线(MIN_1/3/5/15/30/60,含 880xxx 板块指数)vol 实为成交额/100**——报文中两个字段分别是「成交额(百元)」与「成交额(元)」,恒差 100 倍(实测比值 0.9999996~0.9999999,剩余偏差仅为 4 字节自定义浮点的解码噪声),**真实的分钟成交量根本不在报文中**(对照:上证指数 2026-09-02 15:00 的 5min bar,东财真实成交量 13,954,814 手 / 成交额 208.75 亿元,协议 f1 返回的 208,748,512 ≈ amount/100,即 issue 反馈的现场)。修复后 vol 置 **NaN**(Web API 序列化为 `null`)而非拿成交额冒充成交量;`amount` 保持成交额(元)不变。 - **指数与个股的周/月/季/年线 vol 恰好少 100 倍**——服务端该字段为真实成交量/100(铁证:上证指数本周 8/31+9/1+9/2 三个日线 vol 合计 1,666,668,288 手,周线 f1 返回 16,666,683;浦发银行周线 2,693,885×100 = 269,388,500 vs 三日日线合计 269,388,528 股,均精确到解码噪声)。修复后 ×100 还原,与日线单位对齐(指数=手、个股=股)。日线(cat 4)与 cat 9("日线变体"——枚举名误标为 YEAR,实测返回日线粒度数据,真年线是 cat 11)不受影响。 - **`DataFrameResponse` 对 NaN 透传导致潜在 500**(`src/easy_tdx/web/schemas.py`)——Starlette `JSONResponse` 为 `allow_nan=False`,DataFrame 中任何 NaN(含本次指数分钟线 vol)直接抛异常返回 500。序列化统一 NaN → `null`。 - 语义与单位已在 `get_index_bars` / `get_security_bars`(含异步版)与 `/bars`、`/bars/index` 路由 docstring(OpenAPI 文档)写明。附带发现(本轮未改行为,仅记录):指数分时接口 `get_minute_time_data` 的 vol 列为成交额(万元)(全日合计 ≈ 日成交额/10000,个股分时则正常为股);`KlineCategory.YEAR=9` 实为日线变体、真年线是 `YEAR_ALT=11`,`/bars?category=YEAR` 目前实际返回日线数据。 ### 测试 - 新增 `tests/unit/test_bars_vol_semantics.py`(7 例):指数分钟线(6 个分钟周期 ×单条/多条对齐)vol=NaN 且 amount 不变、指数与个股周/月/季/年 ×100、日线与 cat 9 原样、`DataFrameResponse` NaN→null;报文用实抓原始字节构造(`0x4D4713FE`/`0x509B87A0` 为 2026-09-02 真实字段值)。 - 新增 `scripts/verify_issue64.py`:连真实服务器的验收脚本,输出各周期 vol/amount 及 amt/vol 比值、分钟线 f1 vs amount/100 偏差、指数分时 vol 全日合计对照,供回归复测。 ## [1.28.1] — 2026-09-02 **Web UI 新手友好化 + AI 解读导出**——回测报告的两个「看不懂」出口:名词解释折叠帮助(新手向)与 AI 解读 Prompt 一键导出(LLM 辅助解读),另修复 Walk-Forward 窗口数据被序列化成字符串的后端 bug。 ### 新增 - **名词解释折叠帮助**(Web UI)——Walk-Forward 样本外验证 / 一条龙评估 / 绩效指标三个报告框底部各内置「? 名词解释」按钮:默认收起、点击展开,共 33 个词条覆盖全部 25 项绩效指标与 WF / 评估术语(每窗独立开仓、盈利窗占比、连乘收益、Ulcer 指数、卡玛比率、α/β/信息比率、适配性体检等)。每条按「一句话定义 → 公式 → 细节 → 怎么看(阈值与经验法则)」组织,重点粗体亮色、阈值橙色粗体、细节细体暗色的字重层级;文案口径与后端实现逐项对齐(WF 预热区 30%、综合评分权重 50/15/10/5/20、体检 8 项 ≥75% 高适配、卡玛 = 年化收益 ÷ 最大回撤等)。新增通用组件 `HelpCollapse`(平滑展开动画;折叠态叠加 `visibility:hidden`,对无障碍树与自动化真正隐藏)与 `GlossaryList`(`**粗体**` 内联标记解析,词条数据集中 `web-ui/src/data/glossary.ts`)。 - **AI 解读 Prompt 一键导出**(Web UI)——回测报告工具栏新增「🤖 AI 解读」:把当前报告实时组装成结构化 markdown 提示词(角色设定 + 六步解读框架 + 回测配置 + 25 项指标 + 净值概览 + WF 逐窗明细 + 一条龙评估 + 评级 + 最近 8 笔成交 + 免责),一键复制 / 下载 .md,发给任意 LLM(ChatGPT / Claude / DeepSeek / 豆包…)即可获得针对性解读。文风指令经三轮实测迭代:要求「做了十几年量化的老手朋友聊天」口吻、禁八股句式(「事实是」「总的来说」等)、优点毛病都讲、婉转不下判决书、800 字内、只引用报告已有数字,并以 **0-10 信心分**收尾(统一行动刻度:0-3 放弃 / 4-6 继续改 / 7-8 小仓试错 / 9+ 逐步加仓)。可选段落(WF / 评估 / 评级)按数据有无自动拼接,未跑不出现。生成器为纯函数 `web-ui/src/aiPrompt.ts`(仅 type-only 本地导入,node:test 可直跑)。 - 单测 `web-ui/src/__tests__/aiPrompt.test.ts`(2 例):25 项指标行齐全、可选段按需拼接、WF 窗口字符串值防御性转换回归锁定。 ### 修复 - **`to_json_native` 有限 python float 被序列化成字符串**(`src/easy_tdx/backtest/types.py`)——有限 float 此前会落到末尾的 `str()` 兜底(np.float64 分支则正常转数字);Walk-Forward 窗口字段恰好都经 `float()` 包装全部中招,REST JSON 里逐窗 `total_return` / `sharpe` / `max_drawdown` / `win_rate` 变成字符串,前端严格判型处显示 `-`(AI 解读 Prompt 逐窗数据缺失、LLM 无法引用)。修复后有限 python float 与 np.float64 同口径保持数字、NaN → None。前端 Prompt 组装同步加 `Number()` 防御性转换,兼容仍在缓存的旧任务结果。 ### 测试 - E2E 新增 2 例:名词解释默认折叠 / 点击展开(词条与面板统计标签同名场景用类名作用域定位,规避 strict mode 冲突)、AI 弹窗内容打包断言(textarea 用 `toHaveValue` 而非 `toContainText`)。既有「盈利窗占比」「综合评分」「对比买入持有」等全局 `getByText` 断言收窄到对应容器(词条文案含同名词)。Playwright 本轮以 `PYTHONPATH=src` 对仓库后端运行,序列化修复被 E2E 真实覆盖。 ## [1.28.0] — 2026-09-02 **深度风险报告 + 移动止损 + 黄金测试**(借鉴 [akquant](https://github.com/akfamily/akquant))——把专业量化框架的「报告深度」与「测试 rigor」搬到散户工具上,三通道(CLI / Web API / Web UI)同步输出。同版本收录 Playwright E2E 前端测试基建与 WebSocket 实时推送联动(升级计划 P4-1 / P4-2)。 ### 性能 - **回测引擎信号管线提速 ×12.6(ma_cross 800 根全流程 93.6ms → 7.4ms,Windows/Py3.12 实测)**——升级计划 P4 遗留项。先写基准(`scripts/bench_engine.py`,perf_counter 中位数)再优化,profile 归因打破预期:v1.25 以为瓶颈是 `_generate_signals` 的逐 bar 循环,实测 **85% 墙钟在 `OrderSimulator._find_bar_index`**(每个信号都把整列 datetime `strftime("%Y%m%d")` 一遍,46 信号×800 根 ≈ 0.78s),另 ~10% 在 `_bind_data` 的 `_datetime_to_int`(同样 strftime 全列)。三处优化(全部保持行为逐位一致): 1. **向量化信号生成快速路径**:`Strategy` 新增 `entry_exit_masks()` 钩子(显式声明 entry/exit 布尔掩码,语义约定与约束写在 docstring),19 个内置策略全部实现;引擎按掩码 + 候选事件 bar 状态机一次产出信号(逐 bar 循环退化为逐事件循环),持仓估算逐行复刻 `_update_strategy_position`(含买不足 1 手的退化路径)。约束检测 `_vectorize_eligibility` 显式可测(未实现钩子 / 缠论注入 → 回退逐 bar);`signal_path="auto|vector|loop"` 可强制指定。信号层加速 ×1.39~1.72(800 根,四策略实测); 2. **OrderSimulator 日期查找表**:`_build_dt_lookup()` 每个 `simulate()` 只转一次 datetime→行号(O(信号数×bar 数) → O(bar 数)),重复日期取首个、未命中 None、object 列恒不匹配等语义与原全列扫描逐条对齐; 3. **`_datetime_to_int` 去 strftime**:datetime64 走 `year*10000+month*100+day` 整数算术(输出、NaT→NaN 行为与 strftime 一致),`_bind_data` 与查找表共用。 效果:单标的 800 根 ma_cross 全流程 **×12.6**(macd ×11.3、boll_breakout ×11.2、rsi_reversal ×11.1);32 点网格寻优 3.0s(按基线折算)→ 231ms。对拍单测 `test_backtest_engine_vector.py`(39 例):19 策略 × 默认/非默认参数 × warmup/极低资金/非默认费率/指标缓存,performance/trades/equity_curve/positions 逐位一致。 - 顺带发现(未改行为,仅记录):`wr_reversal` 的默认阈值 -80/-20 是通达信 -100~0 惯例,而 MyTT 的 WR 为 0~100 刻度——默认参数(及边界内任意合法参数)下 entry 恒 False,策略实际不产生交易;对拍用 `skip_bounds` 参数覆盖其掩码路径,语义修正另行排期。 ### 新增 - **绩效指标 19 → 25 项**(`backtest/performance.py`)——新增 Ulcer 指数(回撤深度×持续时间综合,与 S-D 评级「持有体验」定位同频)、95% 日 VaR / CVaR(历史分位数法,尾部风险)、SQN 系统质量数(√N×单笔收益均值/标准差,>2 可用 / >4 优秀 / >6 极佳)、最大连胜 / 最大连亏(散户心理最敏感的数字)。JSON / CSV 输出自动透传;`--table` 增加「深度风险」块;Web UI 绩效表「风险」组 +3 行、「交易」组 +3 行(老结果缺键显示 `-`)。 - **基准对比从 1 个数升级为 5 个数**(`backtest/benchmark.py`)——`evaluate_strategy` 的 `benchmark` 段在 `excess_return` 之外新增 `alpha`(年化 CAPM α,剔除基准影响后的真实超额)、`beta`(对基准敏感度,1=同涨同跌)、`information_ratio`(年化信息比率)、`tracking_error`(年化跟踪误差)。新公开函数 `compute_benchmark_comparison(strategy_curve, benchmark_curve)`。Web UI 一条龙评估卡新增 4 格对比行(α/信息比率按正负着色,β/跟踪误差中性);CLI `--evaluate` JSON 自动携带。 - **移动止损 + 百分比 bracket**(`backtest/engine.py` / `strategy.py`)——`buy()` 新增 akquant `place_bracket` 风格参数:`trail_stop`(自持仓期间最高收盘价回撤 N% 触发;水印在检查后更新 → 只可能次根起触发,与 next_open 语义一致、无前视)、`stop_loss_pct` / `take_profit_pct`(按信号根收盘自动换算绝对价)。止损/止盈/移动止损构成 OCO(任一触发全部失效),触发单 `source="stop"` 延迟下一根成交。 - **黄金测试(golden tests)**(`tests/unit/test_golden_backtest.py` + `tests/golden/backtest_metrics.json`)——借鉴 akquant 的 golden 机制:19 个内置策略在固定种子(seed=20260902,400 bar)合成数据上的 11 项指标 + 4 个规则场景(固定止损 / 止盈 / 移动止损 / 百分比 bracket)的成交价与时点 + 买入持有基准 + Alpha/Beta/IR/TE,全部锁定为 JSON 基线,容差 rel=abs=1e-6(紧到抓住费率/成交时点级别的逻辑漂移,松到容忍跨平台浮点尾数)。引擎任何撮合/费率/信号逻辑的静默改动都会在此爆出。更新基线:`EASY_TDX_REGEN_GOLDEN=1 python -m pytest tests/unit/test_golden_backtest.py`。**26 例新增**。 - **Playwright E2E 前端测试基建**(升级计划 P4-1)——web-ui 引入 `@playwright/test`(`e2e/` + `playwright.config.ts`,`npm run test:e2e`)。**mock 方案选后端合成数据而非 page.route 拦截**:`EASY_TDX_E2E_MOCK=1` 时 serve 的 lifespan 把 TDX/MAC 客户端替换为合成数据客户端(`web/e2e_mock.py`,按 (market, code) CRC32 播种的确定性随机游走,分页语义与真实 /bars 一致),回测/WF/一条龙评估/自选/策略库继续走**真实后端代码**(它们本就不依赖行情连接),SSE 由 QuoteStreamer 真轮询合成数据全链路覆盖(mock 模式下轮询降到 2s 一拍,不受交易时段限制)。用例覆盖:看板五大指数区块+SSE 价格渲染、自选增删、回测全流程(净值图/绩效表/成交记录)、「附加分析」开关(WF 逐窗柱状图+一条龙评估卡)、策略库保存;`EASY_TDX_CONFIG_DIR` 指向每轮独立临时目录(断言可写死、不污染真实 `~/.easy_tdx`)。CI frontend job 追加 E2E 步骤;`verify_ci.sh` 补 `--no-frontend` 与前端 typecheck+build+E2E 段。新增 `tests/unit/test_e2e_mock.py`(11 例)守护 mock 与真实客户端的契约。 - **WebSocket 实时推送联动 EventBus**(升级计划 P4-2)——`/ws/realtime/{symbol}` 从「不推送数据」变为真链路:新增 `web/realtime_hub.py`(RealtimeStreamHub),订阅集合变化时按需启停 `RealtimeDataFeed`(轮询 `get_stock_quotes` → `EventBus` → 每连接独立队列 fan-out,丢最旧保最新);**无人订阅完全停止轮询**(对齐 QuoteStreamer 节能语义);去重后标的上限 80;推送帧 `{type:"tick", symbol, market, code, price, volume, ts, open, high, low, pre_close, amount, name}`,30s 空闲 `ping` 心跳,客户端可 `subscribe`/`unsubscribe` 动态增删。端点重写为「单一写者泵」模型(全部出站帧经队列串行,杜绝并发 send 交错)。**前端接入选择只写文档不上组件**:看板/自选实时刷新已由 SSE `/stream/quotes`(全量快照、单连接共享)承担,WS 定位是按需单标的 tick(实时策略信号预留口),双通道同时拉同样行情属冗余——协议 + 自动重连/心跳容忍代码骨架落 `docs/api_reference.md` 与 README(「未联动」警示已撤)。新增 `scripts/ws_smoke.py` 手动冒烟(mock 模式随时可跑,实测可见 tick 帧与动态订阅确认)。环境变量 `EASY_TDX_WS_INTERVAL` 可调轮询间隔。 ### 修复 - `RealtimeDataFeed` stop-before-start 竞态:`run_async`/`run_sync` 首行会把 `_running` 重置为 True,若 `stop()` 在任务首次调度前调用,停止请求被覆盖、任务永不退出(RealtimeStreamHub 换标的重建 feed 时必现死锁)。引入独立 `_stop_requested` 标志,启动前已请求停止则直接返回;`tests/unit/test_realtime_feed.py` 补 2 个回归用例。 ## [1.27.2] — 2026-09-02 **市场异动(0x1237)类型解析补齐与修正**(Issue #62)——`_describe_unusual` 此前仅覆盖 15 种类型,0x15/0x16/0x1D/0x1E(占全天异动 23%)落入兜底分支,显示「异动类型0x16、数值为空」;0x13 方向语义亦有误。语义由 2026-09-01/09-02 两个交易日实测锚定(全天跟踪采样,收盘累计 17848 条)。 ### 新增 - **0x16 盘中强势/弱势**——09:25 撮合样本 v2 与当日开盘涨幅(open/pre_close-1)**49/49 精确一致**;v1 为带符号 ±1~3 级强弱等级(六组 v2 区间互不重叠且单调)。issue 作者猜的「大笔买入/卖出」「竞价试卖」据此排除。 - **0x15 竞价/尾盘异动(双时刻信号)**——开盘竞价 09:25(1191 条)与收盘 15:00:01~04(86 条)都触发;desc 按记录小时区分「竞价拉升/尾盘拉升」前缀;v1 为方向档(±0.5% 分档)、v2 为时段尾段价格变动、v3 为成交量(手)。同源实现 pytdx2 标的「尾盘」只对了一半。 - **0x1D/0x1E 急速拉升/急速下跌**——阈值下限恰 ±0.6%,与既有 0x04/0x05(加速拉升/下跌)构成不同短窗信号。 - **公开常量 `UNUSUAL_TYPE_NAMES`**(19 种类型码→名称),顶层 `easy_tdx` 与 `easy_tdx.mac.commands` 均可导入,配合 `df["unusual_type"].map(UNUSUAL_TYPE_NAMES)` 使用。 - **协议探索结论文档化**(`docs/protocol-unknown-fields.md` §3.4)——全天普查确认 PC 推送协议(0x40080cd1+ 体系)特有的「大笔买入/主力急入/急速上涨」等信号在 0x1237 拉取协议中**无对应类型码**;0x14 另有竞价试盘子族(09:15 撮合参考价触板即触发,现有解析器直接可用);请求监控参数(尾部 6×H)经单维扫描确认不是类型开关;北交所(Market.BJ)同样支持 0x1237。 ### 修复 - **0x13 竞价试盘方向修正**——v1=0x00 试买(申报价高于昨收)/ 0x01 试卖(低于昨收),552 条对照昨收 549 条一致;旧实现把约一半的试卖方向记录也显示成「竞价试买」,且数值无单位,现按方向显示「竞价试买/竞价试卖」并带 `申报价/竞价量手`。 - `examples/17_mac_monitor/unusual.py`:修正完全错误的类型码文档(旧注释「1=5分钟涨幅, 2=5分钟跌幅」实为杜撰)与示例输出;README「监控」小节补充类型映射用法。 ### 测试 - 新增 `tests/unit/test_unusual.py`(21 例,全部真实抓包字节 fixture:600551/600127 竞价异动、600123/600221 收盘「尾盘」前缀、603980/603900 试买/试卖方向);既有类型解析不回归;`UNUSUAL_TYPE_NAMES` 覆盖度与兜底分支互斥性校验;`test_public_api.py` 契约表登记新导出。异动相关测试全绿;全套 1278 通过、1 例 optimizer cache 既有失败与本次无关(干净 HEAD 同样失败)。 ## [1.27.1] — 2026-09-01 **v1.27.0 的维护版**——一项 UI 修复 + WebSocket 实时推送落地(随独立排期提交收录)。 ### 修复 - **回测页「附加分析」开关折行**——全局样式 ``input,select,textarea{width:100%}``(style.css)把勾选框撑满整行(实测 156px),文字被挤到下一行折行、窗口数控件块状堆叠;修复:复选框显式 ``width:auto`` 恢复原生 13px、勾选框+文字+窗口数同行单行布局(``white-space:nowrap``)、显式覆盖全局 ``label{display:block}``。Chrome DevTools 实机验证:两行均单行、区域零横向溢出。 ### 新增(随独立排期提交收录) - **`/ws/realtime/{symbol}` WebSocket 实时推送接通**(e58de78)——README 既有 TODO 落地:按需轮询 hub(订阅才拉取、无人订阅自动休眠)、多客户端并发订阅 fan-out、退订竞态处理,附冒烟脚本与 284 行单测。 ### 测试 - 时段门控用例的时间相关 flaky 修复(bec231e)——固定 23:00-23:59 模拟盘外改为动态构造未来 1 分钟时段,任何时刻运行都成立。 ## [1.27.0] — 2026-09-01 **公式与轮动版本**——升级计划 P3 + P4(部分)落地:通达信公式解析器让写惯公式的用户零 Python 进入筛选/回测,轮动组合引擎补齐「排名换仓」组合形态,附 Docker 部署与一键门禁脚本。 ### 新增 - **通达信公式解析器**(`formula.py`)——自建 tokenizer + 递归下降 AST + 白名单求值(**不走 Python eval**,无注入面):支持 `:=` 中间变量 / `名称:` 命名输出、`+ - * /`(除零→NaN)、比较、`AND OR NOT`(兼容 `&& || !`)、花括号注释、中文标识符;序列别名 C/O/H/L/V/AMOUNT;函数白名单 30+(MA/EMA/SMA/HHV/LLV/REF/CROSS/LONGCROSS/IF/MACD/KDJ/RSI/BOLL/ATR…,全部后视函数,**无未来数据**);命名布尔输出自动归类为**信号列**、数值输出归类为**排名列**;未知函数/变量报带位置的 `FormulaError`。 - **公式回测适配器**(`backtest/formula_strategy.py`)——信号列注入 K 线 + `ColumnSignalStrategy` 逐 bar 交易;买/卖列自动挑选(「买/卖」与 BUY/SELL 名称提示优先,其次声明顺序);信号下一根开盘成交;结果附 S-D 评级与综合评分。 - **公式三通道**——CLI `easy-tdx formula compute|screen|backtest`(`--formula` 或 `--file`,screen 支持逗号分隔/@文件标的列表);REST `POST /formula/validate`(语法+归类校验,无需数据)、`/formula/compute`(内联 ohlcv 或 symbol)、`/formula/backtest/run/async`、`/formula/screen/run/async`(后台任务);Python API `run_formula_backtest()`。 - **轮动组合引擎**(`backtest/rotation.py`)——排名定期换仓:打分函数只喂截至当日收盘的前缀数据(无未来泄漏);固定槽位**等额**(预算 = 净值/槽数,杜绝首买全仓单票);跌出前 `keep_rank` 名自动卖出、空槽自动补位;`daily/weekly/monthly` 刷新;可选槽内止盈止损(收盘触发、次开成交);复用主引擎 19 项绩效 + 组合评级。内置 `momentum_score(period)` 与 `formula_score(公式)` 打分(与公式模块联动)。REST `POST /backtest/rotation/run/async`。 - **回测页附加分析开关(Web UI)**——回测页新增「附加分析」区:勾选「Walk-Forward 样本外验证」随回测自动附加 WF 任务(窗口数可调 2~12,逐窗收益红涨绿跌柱状图 + 盈利窗占比/连乘收益/最差窗汇总卡);勾选「一条龙评估」附加评估任务(综合评分 0-100 分项条 + 高适配徽标 + 买入持有基准对比与「跑输买入持有」警示 + 8 项适配性检查清单 + 评级复用本地口径)。两任务与主回测共用同一份内联行情、并行互不阻塞、独立错误提示;新增 `WalkForwardPanel.vue` / `EvaluatePanel.vue` 组件与 store 的 `runWalkforward`/`runEvaluate` action(统一 `pollTask` 轮询助手);WF 端点支持 `?n_windows=` 查询参数。附带修复:WF/fitness/evaluate 报告的 numpy 标量在 REST 序列化时 400 的问题(`types.to_json_native` 源头清洗,各结果 `to_dict` 统一接入)。 - **Docker 部署**(`Dockerfile` + `docker-compose.yml`)——python:3.12-slim,装 `[web,warehouse]` 可选依赖,`/data` 卷持久化自选/策略库/任务库/K 线仓库,带健康检查。 - **一键门禁脚本**(`scripts/verify_ci.sh`)——ruff + ruff format + mypy strict + 全量 pytest 一条命令(`--fast` 跳过测试),可挂 git pre-push hook。 ### 修复 - **CI(UP038)**——CI 经 `requirements-dev.txt` 锁定 ruff 0.11.11(UP038 生效),本地 0.16.4 已移除该规则导致漏检;10 处 `isinstance(x, (A, B))` 统一改为 PEP 604 联合类型写法(两版规则集均合规,已用 CI 同版工具链复验)。 - **任务状态跃迁竞态**——`task_runner` 此前在锁内改内存状态后才在锁外落盘 SQLite,慢速环境(CI + coverage 插桩)读库方会命中「内存 done / 磁盘 running」窗口;改为 running/done/failed 三次跃迁均在同一把锁内**先落盘再对内存可见**(与 `submit` 的 pending 写法对齐),窗口从根上消除。 ### 文档 - README 介绍部分补充 v1.24~v1.27 能力:防过拟合验证链、通达信公式 + 轮动组合、本地 K 线数据仓库;CLI 参考新增 `--wf`/`--evaluate` 示例与 `formula`/`warehouse` 命令组。 ## [1.26.0] — 2026-09-01 **本地数据仓库版本**——把碎片化缓存升级为统一数据底座(升级计划 P2 阶段;P2-2 评级后端化已随 1.25.0 提前交付)。此前下游项目(indicator-lab 的 DuckDB 仓库、backtest-system 的 cache/ 目录)都在自建数据层,现在 easy-tdx 原生提供。 ### 新增 - **K 线仓库**(`warehouse/` 包,DuckDB 单文件)——默认 `~/.easy_tdx/warehouse.duckdb`(随 `EASY_TDX_CONFIG_DIR`),列存 + SQL 友好 + 主键去重 upsert。DuckDB 为**可选依赖**(`pip install easy-tdx[warehouse]`),惰性导入不影响核心三通道。 - **provisional / completed 状态机**(借鉴 indicator-lab)——15:05 前落盘的当日 bar **逐行**标记 `provisional`(盘中临时值),查询/回测默认忽略(杜绝拿盘中价当收盘价);`promote_provisional()` 把过期临时行转正,`include_provisional=True` 显式可见。 - **增量同步器**(`warehouse/sync.py`)——首同步全量(默认上限 8000 根),此后只拉尾部 15 根覆盖(收盘价修正/临时转正),不动更早历史;批次同步带进度回调、单标失败不中断批次,返回 added/updated/skipped/failed 汇总。默认 QFQ 口径(回测/筛选一致)。 - **仓库健康自检**(`health_check`)——三维度体检:①疑似缺口(相邻 bar 工作日差 > 5,含节假日误报提示);②异常跳变(复用 QFQ 对拍的板块感知跳空检测,多为除权数据需人工核查);③最新度(>7 天未更新的过期标的)+ provisional 行统计。 - **CLI 命令组** `easy-tdx warehouse`——`sync`(支持逗号分隔或 @文件 标的列表)、`query`(JSON 输出,`--include-provisional`)、`stats`(各标的行数/范围/临时行)、`check`(健康自检)。 ### 内部 - `pyproject.toml` 新增 `[warehouse]` 可选依赖组;`duckdb` 加入 dev 依赖(CI 跑仓库测试)。 - `cli/__init__.py` 注册 `warehouse` 命令组(37+1 个顶级命令)。 ## [1.25.0] — 2026-09-01 **防过拟合验证链版本**——补上两个下游项目(backtest-system / indicator-lab)都在自研的最大空白:样本外验证工具链。此后「回测好」可升级为「样本外也好」。升级计划第二阶段(P1),全量 1193 单测。 ### 新增 - **Walk-Forward 样本外验证引擎**(`backtest/walkforward.py`)——前 30% 预热区后均分 7 个连续测试窗,**每窗独立开仓**(窗口起点空仓、持仓不跨窗结转,杜绝跨窗重复计收益——backtest-system v1.2.1 踩过的坑直接采用正确语义);每窗前置 60 根上下文做指标预热,用引擎 `warmup_bars` 压制上下文区间信号(指标有历史、信号只属窗口内)。输出逐窗收益、盈利窗占比 `consistency`、连乘收益、最差/最好窗、平均夏普。接入 CLI `easy-tdx backtest --wf [--wf-windows N]` 与 REST `POST /backtest/wf/run/async`。 - **策略适配性评估**(`backtest/fitness.py`)——train/valid/test 三段切分(默认 60/20/20,段间独立回测)+ 8 项可解释检查(三段各自盈利/收益符号一致/测试段回撤有界/训练段样本充分/测试段未失效停摆/样本外加权夏普为正),通过率 ≥75% 且样本充分 →「高适配」标记;`evaluate_prefix` 只用截至某日之前的数据评估(滚动适配过滤原语,无未来数据泄漏),`rolling_fitness_scores` 输出时序适配分。 - **一条龙评估**(`backtest/benchmark.py` `evaluate_strategy()`)——回测 + WF + 适配性 + 综合评分 + S-D 评级 + **买入持有基准对比**(同区间同费率,`excess_return` 为跑不赢买入持有的一票否决级研发信号)一次调用出全报告。CLI `easy-tdx backtest --evaluate`;REST `POST /backtest/evaluate/run/async`。 - **策略综合评分**(`backtest/scoring.py`)——0-100 加权(收益 50% + 夏普 15% + 回撤 10% + Sortino 5% + WF 一致性 20%;无 WF 数据时权重自动归一化,不惩罚不加分),子项复用评级锚点插值,阈值口径单一真源。 - **评级后端化**(`backtest/grading.py`)——前端 `web-ui/src/grading/`(S-D 五档、六维加权、一票否决、组合净值指标重算)忠实移植 Python:`grade_performance` / `grade_grid_point` / `grade_portfolio_equity`;**评级刻意不看收益率**(与评分分工)。REST `/backtest/run` 与 `/backtest/run/async` 响应新增 `grade` + `score` 字段,CLI 通道同样可得。 - **多 seed 验证 + 晋级门槛**(`backtest/validation.py`)——股票池多 seed 随机抽样回测,跨样本稳定性指标(正收益比例、均值/中位数收益、平均夏普、各 seed 稳定性列 `per_seed_positive_ratio`)+ 四项可配置晋级门槛(正收益比例 ≥0.5 / 平均夏普 >0 / 平均交易数 ≥5 / 平均收益 >0),任一不达标即 `promoted=False`。REST `POST /backtest/multiseed/run/async`。 - **寻优两段式加速**——`IndicatorCache`(指标层跨网格点复用,`fast×slow` 网格中同参数指标只算一次,实测 36 点网格命中率 41.7%)+ `ParamGridOptimizer(workers=N)` 进程级并行(Windows spawn 安全的模块级 worker,实测 36 点×800 根 4 进程约 2 倍,网格越大收益越高);寻优结果附 `cache_stats`。诚实说明:本引擎逐 bar Python 循环占大头,指标缓存对廉价指标(MA/RSI)墙钟收益有限(~1.01x),其价值在昂贵指标(缠论类)与并行模式;REST 寻优请求新增 `workers` 字段。附带优化:`StrategyDataProxy` 数组绑定改零拷贝(`astype(copy=False)`)。 ### 内部 - `strategy.py` `I()` 支持引擎挂载指标缓存(不挂载时行为不变,向后兼容)。 - `backtest/__init__.py` 导出 WF/评分/评级/适配性/一条龙评估全套 API。 ## [1.24.0] — 2026-09-01 **信任与持久化版本**——修复下游反馈的 QFQ 复权可信度问题(引入双引擎对拍验证)、回测任务落盘 SQLite(重启不丢)、品种感知费率(ETF/可转债免印花税)。源自对两个下游项目(backtest-system / indicator-lab)的逆向调研,完整升级计划见 `docs/upgrade-plan-2026H2.md`。 ### 新增 - **QFQ 对拍验证体系**(`mac/qfq_check.py`)——公式法(NONE+XDXR)与跳空检测法(板块感知涨跌停阈值:主板 10%/双创 20%/北交所 30% + 0.5% 余量)双证据链交叉验证前复权结果,检出四类问题:`bad_price`(非法价格)、`residual_gap`(除权日仍残留跳空,疑似漏算/未生效)、`wrong_direction`(残差方向反,疑似复权过度/方向算反)、`unexplained_gap`(NONE 跳空但 XDXR 无对应记录)。已接入 `MacClient` / `AsyncMacClient` 的 QFQ 本地重算路径:不一致即打告警日志,最近一次报告存于 `client.last_qfq_crosscheck`。含「茅台式多重分红」「浦发式送转股方向」合成案例回归测试(13 个用例)。回应下游 backtest-system 对 QFQ 可靠性的反馈。 - **回测任务 SQLite 持久化**(`web/task_store.py`)——任务状态/结果双写内存 LRU + `~/.easy_tdx/tasks.db`(随 `EASY_TDX_CONFIG_DIR`,保留 500 条),serve 重启后对比页历史任务、已完成寻优排名均可继续查询;重启时遗留的 pending/running 任务自动标记为 failed(注明「服务重启中断」)。`EASY_TDX_NO_TASK_DB=1` 可关闭(测试默认关闭)。 - **任务结果导出端点**——`GET /backtest/tasks/{task_id}/export?format=json|csv`:JSON 导出完整 result;CSV 智能挑主表(trades → ranking → equity_curve,兜底 performance 键值对),带 `Content-Disposition` 附件头。 - **品种感知费率**(`backtest/fees.py`)——按代码前缀+市场推断品种(股票/ETF/LOF/可转债/B股/指数),自动解析佣金/最低佣金/印花税;核心法定差异:**ETF/可转债免印花税**(此前扁平默认对 ETF 轮动类策略长期错收印花税)。接入:`BacktestEngine(symbol=..., auto_fees=True)`、`PortfolioBacktestEngine(auto_fees=True)`(逐标的解析)、CLI `easy-tdx backtest --auto-fees`、REST 请求体 `auto_fees` 字段。显式非默认费率仍优先;结果 config 快照记录 symbol 与解析后费率。34 个测试用例。 ### 修复 - `performance.py` 中 `avg_holding_days` 的过时文档注释(实现早已是 FIFO 配对、按 size 加权的真实日历日口径,注释仍写「简化为固定值 5.0」,误导审计)。 ### 内部 - `tests/conftest.py` 全局默认 `EASY_TDX_NO_TASK_DB=1`,防止单测污染用户真实 `~/.easy_tdx/tasks.db`。 - `task_store` 初始化用独立 `_init_lock`(避免与写锁死锁);`task_runner` 的 pending 落盘先于 executor.submit(避免旧状态覆盖新状态的竞态)。 ## [1.23.3] — 2026-09-01 **serve 纯 API 模式 + 看板修复**。自 1.23.2 以来的增量: ### 新增 - **`easy-tdx serve --no-ui`**——纯 API 模式:不托管 Web UI 前端(根路径 404)、不自动打开浏览器,仅提供 `/api/v1/*` 全部 REST 端点 + SSE + Swagger 文档(`/docs`)。给 AI Agent / 程序化调用省去前端资源;`create_app(enable_ui=False)` 可程序化使用,默认行为不变。 ### 修复 - **看板概念板块冷榜全为正值板块**——概念板块约 269 个,降序拉取 120 个时第 113-120 名仍在 +0.7% 附近,尾部截断致"冷榜"展示的是涨幅中游板块;现拉全量 500(MAC 分页 2 页请求,实测尾部 -2.35%~-3.83% 恢复真跌幅榜)。行业板块 86 个本就全量,不受影响。 ### 文档 - README 简介区补充行情终端看板截图(web-ui-page-4)、评级徽章截图(web-ui-page-5)、CLI 三通道输出截图(cli-page-1)与 Web 使用示意(web-ui-page-6)。 ## [1.23.2] — 2026-09-01 **1.23.1 的质量门禁补丁**——1.23.1 的 PyPI 包与 EXE 功能完整(1078 单测全过、本地全功能冒烟),但其 tag commit 未通过 CI 的 `ruff check` / `ruff format --check` / `mypy --strict` 三道门禁(发布前漏在本地预演)。本版本补齐: - mypy strict:`watchlist_store` / `routers/watchlist` 裸 `dict` 补泛型参数;`routers/stream` 的 `event_gen` 补 `AsyncGenerator[str, None]` 注解;`app` 的 `_watch_symbols` 补返回类型、teardown 变量改名消除类型冲突。 - ruff:5 处超长行拆行、3 处导入排序、3 个文件 `ruff format` 重排(含历史遗留的 `test_ex_tick_chart_date.py`)。 - FastAPI 0.141+ `_IncludedRouter` 的测试适配(`app.routes` 不再平铺子路由,改用 OpenAPI schema 验证)随 1.23.1 已入库,此处一并回归确认。 CI 全矩阵(3 OS × 3 Python + frontend job)绿。**建议直接使用本版本**;1.23.1 功能等价,仅代码整洁度差异。 ## [1.23.1] — 2026-09-01 **行情终端 Web UI 重大升级**——Web UI 从「回测工作台」升级为「行情终端 + 回测工作台」双模块。展示层设计对标 tick-stock-panel 等专业看盘终端(暗色主题、红涨绿跌、高信息密度侧边栏布局),数据全部来自通达信协议直连,零新增后端依赖(SSE 用标准 StreamingResponse 手写,未引 sse-starlette)。 ### 新增:行情终端 - **市场看板(`/`)**——五大指数实时行情条(内嵌当日迷你分时 + 成交额,SSE 推送)、全市场涨跌统计(涨/跌/平/停 + 涨停跌停家数堆叠条)、**四维情绪雷达**(赚钱效应/量能/动量/趋势,附综合分与判词)、**全市场涨跌分布直方图**(DESC+ASC 各拉 3000 去重合并约 5500 只、22 桶、鼠标跟随浮窗显示区间家数与占比)、**涨停雷达**(≥9.8% 名单)、行业/概念板块热冷双榜(一次拉 120 个板块切两端,可点击下钻)、**四联排行榜**(涨幅/跌幅/成交额/换手 tab 切换)、两市异动雷达(60 类异动事件流)。所有榜单/板块行点击直达个股或板块弹窗。 - **自选行情(`/watchlist`)**——输入 6 位代码一键加自选(市场按代码段自动识别 + MAC symbol-info 自动取中文名,历史无名称记录自动补全),全表 SSE 实时刷新,行内 SVG 迷你分时(60 秒重拉),点击行打开详情弹窗。 - **个股详情弹窗**——五档盘口(量条 + 按昨收着色)+ 分时图(渐变面积/均价线/昨收基准/红绿量柱,支持 **1/3/5 日多日分时**,历史日走 `/minute/history`)+ 日 K(**技术指标可切换**:主图 MA/BOLL/EMA,副图 MACD/KDJ/RSI,前端本地计算与 MyTT 同口径)+ 一键加/移除自选 + **一键寻优**(跳转参数寻优页自动跑全策略预设网格)。 - **板块详情弹窗**——行业/概念板块的分时/日 K(含指标)+ 成分股涨跌榜(升降序切换,点击叠开个股弹窗),支持板块加自选。 - **实时推送架构**——后端 `QuoteStreamer` 单条共享轮询循环 fan-out 到所有 SSE 连接(每连接独立队列 + 背压丢旧、无人订阅自动休眠、盘中 8 秒/盘外 60 秒自动降频、自选增删下周期自动纳入);前端 pinia 全局单连接 + 指数退避重连,侧边栏底部实时连接徽标。 - **自选持久化**——`~/.easy_tdx/watchlist.db`(SQLite,`(market, code)` 唯一幂等,分组字段预留)。 ### 修复 - **大盘指数与板块指数报价缩小 10 倍**(解码层)——`_price_decimal_digits` 曾把 SH `000` 系列(上证指数/沪深300/科创50 等)与 `881`/`885` 板块指数按 3 位小数(厘)解析,而这些指数的协议原始单位是「分」(实测 2026-09-01:科创50 1647.53 显示成 164.753、沪深300 4611.44 显示成 461.144、种植业板块 1039.93 显示成 103.993)。现统一改为 2 位;`880` 统计指数保持 3 位(market_stat 家数还原依赖该语义)。ETF/基金/债券 3 位(Issue #8)不受影响。CLI `quote`、REST、SSE 三出口同时修正。 - 批量五档 REST 路径笔误(前端 `/security/quotes` → `/quotes`,SPA fallback 吞掉 404 导致自选添加与部分弹窗失败)。 - SSE 五档字段名白名单笔误(`bid1_vol` → `bid_vol1`,导致推送缺失盘口)。 - MAC 排行榜列名适配(价格列为 `close` 无 `change_pct`,前端归一化计算涨跌幅 + market 数字码转字符串)。 - 日 K tab 切换不撑满(`v-show` 零宽容器初始化 ECharts → 改 `v-if`);分布图浮窗超出卡片上界(改鼠标跟随 + 边界钳制)。 - 寻优「查看」等四处跳转指向旧 `/` 路由(路由改造后 `/` 已是看板)→ 改 `/backtest`。 - FastAPI 0.141+ `_IncludedRouter` 导致 `app.routes` 不再平铺子路由,两个既有测试改用 OpenAPI schema 验证。 ### 测试 - 新增 `tests/unit/test_watchlist_and_streamer.py`(7 例:自选 CRUD/幂等/排序、streamer fan-out/白名单/背压丢旧/交易时段判定)与 `tests/unit/test_quote_decimal_digits.py`(20 组参数化用例锁定指数/ETF/股票/统计指数/跨市场同码不同义的小数位语义);更新 Issue #8 时代两处用构造数据自证的旧断言为真实值口径。全套 1078 个单测通过。 ## [1.21.0] — 2026-08-31 **扩展日线(vipdoc/ds `*.day`)解析槽位错误**(Issue #57,含破坏性 API 变更)——`read_ex_daily_bars` 把第 7 槽(成交量)同时赋给 `amount` 与 `vol`(`amount=vol`),真正的成交额藏在第 6 槽 float32 重解释值里、以误导性字段名 `hk_stock_amount` 暴露。实测铁证:扩展市场 `47#IF300`(沪深300)2023-09-11 第 6 槽 float32 = 186,871,758,848、第 7 槽 uint32 = 105,358,016,与标准市场 `sh000300.day` 同日 amount(元)/ vol(手)**完全一致**,证明第 6 槽是 float32 成交额、第 7 槽是 uint32 成交量。`_EX_DAILY_FMT` 旧声明 ` 4295 万股的股票(招商银行 2026-08-31 单日 1.14 亿股等)编码后超出 uint32 上限,`struct.error` 直接失败;低成交量股票不触发,故长期未被发现。 ### 修复 - **`cli/cmd_offline.py` `_sync_one_daily`**——对 `vol_coeff == 0.01` 的证券类型(A/B 股、深市基金等)写入前换算 股→手(`vol /= 100`),与读取端 `read_daily_bars` 的 `vol × 0.01` 方向对称。作者用服务器原生 .day 文件(0x06B9 下载)实测验证:浦发银行 2026-08-31 原始 vol 字段 99,682,464(股)与协议 API 完全一致。 - 已知边界(未处理):单日成交 > 42.9 亿股的极端天量换算后仍超上限;实测通达信官方 .day 对该 bar 亦降级存储,如需对齐另行讨论。 ## [1.20.12] — 2026-08-28 **`ex tick --date` 传 YYYYMMDD 整数直接崩溃**(PR #56,社区贡献者 @Harveyliu007)——CLI 的 `--date` 选项传入 `YYYYMMDD` 整数,而 `MacExClient.goods_tick_chart()` 只接受 `datetime.date`(内部直接 `query_date.year` 编码),实跑必抛 `AttributeError: 'int' object has no attribute 'year'`;`cmd_ex.py` 的调用点长期带 `# type: ignore[arg-type]`,类型系统没能拦住。A 股侧 `MacClient.get_tick_chart()` 早已支持 int 日期(内部转换),ex 侧漏了同类处理。 ### 修复 - **`ex/mac_client.py` 新增 `_coerce_query_date()`**——int(YYYYMMDD)/ date / None 统一归一为 date 对象;`MacExClient` / `AsyncMacExClient` 的 `goods_tick_chart`、`goods_transaction`(共 4 个方法)签名放宽为 `int | date | None`,含港股 ex 历史逐笔协议路由分支,与 A 股侧 YYYYMMDD 整数语义对齐。 - **`cli/cmd_ex.py`**——移除掩盖问题的 `# type: ignore[arg-type]`;`ex tick --help` 补充 `--date` 用法示例。 - 顺带:`.gitignore` 增加沙箱环境的 `.npm-cache/` / `.uv-cache/` 本地缓存目录。 ### 测试 - 新增 `tests/unit/test_ex_tick_chart_date.py`(13 例,全部离线 mock 连接层):`_coerce_query_date` 纯函数(int/date/None/月份前导零)、同步/异步客户端三种输入、美股(74)与港股(31)协议路由、CLI 端到端(`--date` 传参 / 缺省 None 两天路径)。 - 维护者侧复核:ruff / mypy strict 通过;实测修复前崩溃的 `easy-tdx ex tick US_STOCK TSLA --date 20260827` 正常返回当日分时(21:30 开盘起全部分时点)。全套 1050 个单测通过(`test_web_api.py` 2 个失败为基线已存在的环境问题,与本变更无关)。 - 遗留(未改动):`ex tick` 的 `--days` 选项仍未接线(扩展市场单日分时协议无多日查询),另行跟进。 ## [1.20.11] — 2026-08-28 **资金流口径限制文档标注**(Issue #55,纯文档、无行为变更)——用户实测反馈:同日同股,本库资金流与东财"主力净额"(数据中心 `RPT_DMSK_TS_STOCKNEW` 的 `PRIME_INFLOW`,该字段已验证恒等于超大单+大单净额)差异极大且方向不一(工业富联 601138 偏大 36 倍、洛阳钼业 603993 偏小 2.4 倍,不可系数校正)。用户独立用 `get_history_transaction_data` 复算八个分档与库返回**逐分吻合(diff 0.00 元)**,证实库实现无误;根因在数据源口径——0x0fb5 的"逐笔"是交易所真实逐笔**聚合**后的记录(000001.SZ 单日实测 76,411 笔 → 仅 4,485 条,约 17:1),按聚合后单笔成交额分档把几乎全部成交推入主力档(实测 601138/603993 主力档占成交额 99.4%+、小单档仅 0.02%),故 `main_net_inflow` 实质是"当日主动买卖总失衡"(另有约 2–4% 方向未定的成交被排除);而东财基于 L2 逐笔委托按**挂单额**分档、四档净额严格归零。实证两口径在选股层面几乎不相干(856 个共同信号日仅 13.9% 选中同一只股票;同规则策略 2020–2026 单笔均值 −0.12% vs 东财口径 +1.56%),不能互相替代。 ### 文档 - **`get_fund_flow` / `get_history_fund_flow` docstring(sync/async 共 4 处,`client.py`)**——标注三点口径限制:① 分档基于 0x0fb5 聚合后的"单笔成交额",不是挂单额;② 聚合导致高价股小单档可不足成交额 1%、主力档常占 95%+,值更接近"主动买卖总失衡";③ 与东财/同花顺"主力净流入"不可比,勿混用于同一张表或同一个因子。 - **`FundFlow` / `HistoricalFundFlow` 类 docstring**(`models/stats.py`)——同步口径说明(原"基于 Tick 数据加权计算"的描述不准确,实为逐笔重算)。 - **README**——"标准协议"章节 `get_fund_flow` 示例后新增"资金流口径注意"引注块;TdxClient API 表两行加"口径注意见上文"指引。 - **`docs/api_reference.md`**——"资金流向"章节新增完整"口径注意"段(含 17:1 聚合实测、2–4% 方向未定排除、东财四档归零等细节),`get_fund_flow` 小节加指引。 - **`examples/08_fund_flow/`** 两个示例的模块 docstring 补口径注意;**Web 端点 `/fund-flow`、`/fund-flow/history`** 的 Swagger 描述各补一行口径提示。 全套 1035 个单测通过(`test_web_api.py` 2 个失败为基线已存在的环境问题,与本变更无关)。 ## [1.20.10] — 2026-08-26 **`get_board_list` 板块涨速列恒为 0**(Issue #53)——用户反馈板块列表的涨速列存在但全是 0。逆向核实(0x1231 抓包 + 与 `SymbolQuotesCmd` 字段逐一对值锚定)发现根因:响应中 price 与 pre_close 之间的那个 float **不是固定的"涨速",而是"当前排序列的值"**(板块与领涨股各一份)——请求里的 sort_column 此前硬编码为 0(涨跌幅),而涨跌幅列仅作排序键、值槽恒 0(客户端可由 price/pre_close 计算),所以永远拿到 0。实测锚定排序列映射:**0=涨跌幅(值槽恒 0)、1=涨速%、2=3日涨幅、3=20日涨幅、4=60日涨幅、5=年初至今、6=5日涨幅、7=10日涨幅**。 ### 修复 - **`get_board_list` 暴露 `sort_column` 参数**(`MacClient` / `AsyncMacClient`)—— 新增 `BoardSortColumn` 枚举(公开导出),取涨速传 `BoardSortColumn.SPEED`,此时按涨速降序返回、`sort_value` 列即涨速%;默认仍按涨跌幅降序(行为不变)。分页请求全程透传同一排序键。 - **字段更名(破坏性)**:`BoardInfo.rise_speed → sort_value`、`symbol_rise_speed → symbol_sort_value`(`src/easy_tdx/mac/models.py`、`commands/board_list.py`)—— 旧名在语义上是错的(该值槽只有按涨速排序时才是涨速),且从未返回过正确数据(恒 0),更名比留着一个撒谎的列名更安全。 - **Web 端点 `/board-mac/list` 新增 `sort_column` 查询参数**(`web/convert.py` 新增 `board_sort_from_str`)—— 如 `?sort_column=SPEED`;CLI `easy-tdx board-list` 新增 `--sort` 选项(`CHANGE_PCT/SPEED/CHANGE_3D/CHANGE_5D/CHANGE_10D/CHANGE_20D/CHANGE_60D/YTD`)。 - README 板块示例补 `sort_column=BoardSortColumn.SPEED` 用法。 ### 测试 - 新增 `tests/unit/test_board_list.py`(9 例):sort_column 请求字节打包位置断言(帧偏移 16);排序列枚举值锚定;合成 160 字节记录解析(sort_value/symbol_sort_value);sync/async 客户端透传;`board_sort_from_str` 转换器;Web 端点 `?sort_column=SPEED` 端到端透传;记录长度 160 字节不变式;`_EXPECTED_KIND` 公共 API 契约补 `BoardSortColumn`。实测:涨速降序 top10(近期复牌 0.234%、教育培训 0.138%…)、3日/60日/年初至今等排序键数值与 `SymbolQuotesCmd` 同名字段逐一相等。全套 1035 个单测通过(`test_web_api.py` 2 个失败为基线已存在的环境问题)。 ## [1.20.9] — 2026-08-26 **`get_history_fund_flow` 取不到历史主力净额**(Issue #52)——用户反馈拿不到历史主力净额数据。排查发现三层根因(全部经 52 台已知服务器实测核实):其一,文档声称的"Category 22 直连资金流接口"是**虚构协议**——46 台可达服务器对该请求全部仅回 2 字节空包(0 条或 ret_count 撒谎),从未成功返回过数据,所谓"9 字节头 + 36 字节/条"响应格式系臆造(单测里的格式是 mock);其二,实际数据一直来自"日 K 线取日期 + 历史逐笔成交重算",但历史逐笔接口**当日数据要收盘清算后才有**,而日 K 盘中已包含当日 bar,导致 `start=0` 的最新一行(今天)恒为全 0;其三,`main_net_inflow`(主力净额)此前仅为 dataclass property,`_to_df` 的 `asdict()` 静默丢弃,返回 DataFrame 里根本没有主力净额列。 ### 修复 - **移除虚构的 Category 22 死代码**(删除 `src/easy_tdx/commands/fund_flow.py`)—— `GetHistoryFundFlowCmd` 的请求复用 K 线格式(category=22),实测所有服务器均回空包;响应解析格式(9 字节头 + 36 字节/条)无真实样本支撑。`_fetch_fund_flow_records`(sync/async)不再先试注定失败的直连,直接走"日 K + 逐笔重算",每次调用省一次无效往返。`docs/protocol-unknown-fields.md` 中"fund_flow 9 字节头部(已确认)"的错误结论改为"已证伪并移除"的实测记录(46 台全空,2026-08-26)。 - **当日行盘中改走当日实时逐笔**(`src/easy_tdx/client.py`,Issue #52)—— bar 日期(上海时区)等于今天时用 `GetTransactionDataCmd`(当日实时逐笔),其余日期仍走 `GetHistoryTransactionDataCmd`(历史逐笔)。盘中调用 `get_history_fund_flow(..., 0, N)` 最新一行即为当日实时主力净额(实测茅台 13:30 盘中 +3.87 亿元),收盘清算后自动切回历史逐笔,无需调用方感知。空数据故障转移(v1.20.5)逻辑不变,撒谎服务器换台实测依然生效。 - **物化 `main_net_inflow` 主力净额列**(`_fund_flow_df_with_net`)—— `get_history_fund_flow` 返回列紧随 `date` 之后、`get_fund_flow`(当日快照)放首列;单位元,正=净流入,=(超大单+大单)流入 − 流出,无需用户手工计算。`get_fund_flow`/`get_history_fund_flow`(sync/async 共 4 处)统一接入。 - **文档同步**(`docs/api_reference.md`、`docs/field_mapping.md`、`examples/08_fund_flow/history_fund_flow.py`)—— 更新返回类型与列说明,移除"优先走 Category 22 直连"的误导描述,补充口径说明(按单笔成交金额分级:>100 万超大 / 20~100 万大 / 4~20 万中 / ≤4 万小,与第三方平台划分标准可能略有差异)。 ### 测试 - 重写 `test_get_history_fund_flow_fallback`(去掉虚构直连分支,补 `main_net_inflow` 列存在性与数值断言);新增 `test_get_history_fund_flow_today_uses_realtime_ticks`(当日 bar 走实时逐笔、历史日期走历史逐笔的路径回归);`test_get_fund_flow_logic` 补当日主力净额断言;删除 2 个针对已移除命令的虚构协议测试(`test_protocol_fixes.py`)。全套 1026 个单测通过(`test_web_api.py` 2 个失败为基线已存在的环境问题,与本变更无关)。 ## [1.20.8] — 2026-08-21 **新增「信号雷达」页:一键扫描全部已保存策略的最近买卖信号**——用户希望能每天一键把策略库里保存的单策略与组合策略都算一遍,列出哪些有买入/卖出信号,方便跟踪。本次新增顶部导航页 `/signals`,一次点击即扫描策略库全部策略(single/portfolio/multi 三种 kind 统一展开成"策略×标的"子任务),汇总列出最近 N 根 K 线(窗口可选 1/3/5/10,默认 5)内出现信号的策略。实测 26 条策略展开 36 个子任务,取行情 + 计算共约 7 秒。 ### 新增 - **信号扫描核心**(`src/easy_tdx/web/signal_scan.py`,新文件)—— `expand_targets` 把已保存策略统一展开(single→1 条、portfolio→每只标的一条、multi→每个子策略一条,数据损坏的条目展开为 error 行不中断整批);`fetch_scan_bars` 按 (symbol, category) 去重取最近 800 根 K 线(同标的多个策略只取一次,单标的失败记 None 不中断);`evaluate_signals` 单遍跑策略 bar-by-bar 信号流程(复用 `combo._update_position` 跟踪仓位,与回测引擎同口径),返回窗口内信号序列、结束仓位(持仓/空仓)与最新收盘;`normalize_symbol` 按代码段重判市场前缀,纠正历史保存的错标 symbol(如 SZ:515080→SH:515080,规则与前端 `detectMarket` 一致)。 - **信号扫描端点**(`src/easy_tdx/web/routers/backtest.py`)—— `POST /api/v1/backtest/signal-scan/run/async`:读策略库 → 展开 → 去重取行情(async 上下文内完成)→ 后台线程逐条算信号,结果走现有任务轮询机制(`GET /backtest/tasks/{id}`)。只扫信号、不重跑完整回测、不改写策略库保存的业绩快照。策略库为空返回 400。请求/响应模型 `SignalScanRequest/Row/Result` 见 `backtest_schemas.py`(`window_bars` 1~30)。 - **信号雷达页**(`web-ui/src/views/SignalRadarView.vue`,新文件 + 路由 `/signals` + 导航入口)—— 「⚡ 一键扫描」按钮 + 窗口选择;汇总卡片(子任务数/买入/卖出/失败);筛选 tab(有信号/买入/卖出/失败/全部,默认只看有信号);明细表含策略名、类型徽章、子策略+参数、标的、买入红/卖出绿信号徽章(A股配色习惯)、窗口内信号序列(如 `S 08-20 · B 08-21`)、最新收盘、策略当前持仓/空仓、「载入」跳回测页回填。上次扫描结果缓存 localStorage,重进页面直接展示(标注扫描时间与耗时)。盘中提示:最后一根 K 线未收盘,信号为盘中即时值。 - **前端 API 封装**(`web-ui/src/api.ts`、`types.ts`)—— `submitSignalScanTask`/`runSignalScanWithPolling`(取行情在提交请求内完成,默认 300s 超时)/`asSignalScanResult`;`SignalScanResult` 加入 `TaskState.result` 联合类型。 ### 测试 - 新增 20 个测试(`tests/unit/test_signal_scan.py`):`normalize_symbol` 参数化纠错(7 例);三种 kind 展开 + 数据损坏容错;取数去重/失败容错/date→datetime 列归一化(fake async client);`evaluate_signals` 金叉买入、死叉卖出、窗口过滤与仓位跟踪(金叉位置用 MyTT 独立计算互验)、与真实回测引擎成交方向序列一致性对照;`run_scan` 汇总计数 + 三类失败行;端到端(TestClient + fake store/取数:提交→轮询 done→结果结构、空库 400、窗口越界 422)。全套 1030 个单测通过。 ## [1.20.7] — 2026-08-19 两项用户反馈修复 + 前端依赖安全升级:**Web `/bars` 的 MIN_1 时间被归一化为 00:00:00**(Issue #49)与**参数寻优选出快慢倒挂的"最优参数"**(Issue #39)。 ### 修复 - **`/bars` MIN_1 误判为日线**(`src/easy_tdx/web/routers/bars.py`,Issue #49)—— `KlineCategory` 枚举值不按周期长短排序(`MIN_1=7`、`MIN_3=8` 均大于 `DAY=4`),MAC 路径用 `int(cat) >= int(KlineCategory.DAY)` 判定"日线及以上"会把 1 分钟线误判为日线,`_normalize_mac_df` 因此将 `datetime` 截断为 `00:00:00` 并把列名改为 `date`。改为 `_is_daily_plus()` 查表判定(复用 `_df._CATEGORY_MINUTES`,与回退 TdxClient 路径同一判定源),保证两条路径 `date`/`datetime` 语义一致。新增表驱动单测 + 端点级回归测试(假 MAC 客户端,无网络依赖)。 - **策略参数寻优选出语义倒挂组合**(`src/easy_tdx/backtest/strategies/`、`optimizer.py`,Issue #39)—— 寻优网格的笛卡尔积包含 `{"fast":30,"slow":20}` 这类倒挂组合,倒挂的双均线交叉本质是反向策略,回测成绩可能反而突出从而被选为"最优参数"展示。`ParametrizedStrategy` 新增 `param_constraints` 跨参数语义约束(要求 a0` 但 body 完全为空(pos=2 剩余 0 字节),v1.18.3 的容错有 `if bars:` 条件——bars 为空时走 `raise` → 500,老人看到"取行情失败"。(2) 用户在 `/optimize`、`/portfolio` 等前端路由页面刷新时,后端 StaticFiles 找不到文件返回 404(SPA fallback 缺失)。 ### 修复 - **K 线空 body 不再 500**(`src/easy_tdx/commands/security_bars.py`)—— 移除 `if bars:` 条件,无论已解析条数多少,`TdxDecodeError` 都 `return bars`(空列表让前端分页重试比直接 500 友好)。日志证据:`偏移 2,实际剩余 0 字节` = body 只有 ret_count 头、第 1 条 datetime 就崩。`GetIndexBarsCmd`(指数 K 线)同改。更新 `test_security_bars_truncated_first_record_still_raises`(原断言 raise,现断言返回空列表)+ 新增 `test_security_bars_ret_count_lies_body_completely_empty` 回归守卫。 - **SPA fallback**(`src/easy_tdx/web/app.py`)—— 子类化 `StaticFiles` 为 `SPAStaticFiles`,404 时返回 `index.html` 让 Vue Router 接管。修复 `/optimize`、`/portfolio`、`/compare`、`/strategies` 等前端路由刷新 404。API 路径(`/api/v1/*`)已在路由表注册,不受影响。 ## [1.19.2] — 2026-07-07 **修复干净 Windows 上 EXE 双击后页面纯黑** —— v1.19.1 在没装开发工具的 Windows(如老人电脑)上双击 EXE,浏览器打开 `localhost:8000` 后页面纯黑、`/docs` 却能正常打开。根因:干净 Windows 的注册表里没有 `.js` 文件的 `Content Type` 映射,Python 的 `mimetypes.guess_type('.js')` 返回 `None`,FastAPI/Starlette 的 `StaticFiles` 回退到 `text/plain`。但 `index.html` 里的 `