# 更新日志 本文件记录 easy-tdx 的版本变更。格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/)。 ## [1.18.2] — 2026-07-06 **回退拼音声母搜索功能,回到稳定的 6 位代码输入** —— v1.19.0 引入的拼音声母搜索(输 `zjxc` 命中中际旭创)因底层依赖过重被移除。该功能首次使用时需从通达信服务器爬取沪深 A 股约 5000 条完整名单(几十次协议往返,慢机器耗时几十秒到超时),且与共享的 TDX 连接耦合——爬名单期间会阻塞行情请求。虽经多轮优化(按需加载 / 全站遮罩 / 单飞去重 / 后台预热),均无法兼顾"不阻塞核心行情"与"首次可用"。本次回到 v1.18.1 的干净基线,代码输入框恢复为纯 6 位代码输入(市场自动识别)。 ### 变更 - **移除拼音声母搜索** —— 删除 `StockSearchInput` / `AppInitOverlay` / `useStockSearch` 三个组件与 composable,移除 `pypinyin` 依赖、`/security/search-index` 端点、lifespan 启动预热。`SymbolPicker` / `StocksPicker` 恢复为纯 6 位代码输入框。 ### 已知约束(非 bug) - **代码输入只支持 6 位数字** —— 不支持拼音/名字搜索。这是有意为之的取舍:避免引入需实时爬全名单的重依赖。如未来重做,应将股票名单做成独立本地数据库(用离线命令定期更新),搜索只查本地,不依赖实时爬 TDX。 ## [1.18.1] — 2026-07-06 **Web UI 一键寻优多进程并发 + 策略库组合评级 + 市场前缀纠正** —— 两个独立主题合并发布。(1) 「一键寻优所有策略」此前串行跑 17 个策略的预设网格(共约 182 个网格点),在中大型机器上动辄几十秒到几分钟。本次引入 `ProcessPoolExecutor` 多进程并发,配置区新增并发数选择器(串行 / 4 / 8 / 16 进程,自动检测 CPU 核数并标注推荐档),实测 8 进程可提速 4-6×。**关键认知**:回测是 numpy/pandas 的 CPU 密集计算并持有 GIL,多线程无加速,必须用多进程;照搬项目里已跑通的 `screen/scanner.py` 进程池模板。(2) 策略库「组合回测」结果区补上组合评级徽章(与单标的回测/组合页同口径的 5 维度评分),同时修复历史保存策略的市场前缀错配(5 开头的沪市基金/ETF 曾被误判为深市)。 ### 新增 - **一键寻优多进程并发**(`src/easy_tdx/web/routers/backtest.py` + `backtest_schemas.py`)—— `OptimizeAllBacktestRequest` 新增 `workers` 字段(默认 1=串行,范围 0-32)。抽出模块顶层函数 `_optimize_one_strategy`(可被 `ProcessPoolExecutor` pickle),`_run_optimize_all` 在 `workers >= 2` 时用进程池并行寻优,`workers` 为 0 或 1 时走原串行逻辑(向后兼容)。进程池在函数内 `with` 创建/销毁,对前端轮询与 `task_runner` 透明。 - **并发数选择器**(`web-ui/src/views/OptimizeView.vue`)—— 配置区新增「一键寻优并发」区:自动检测 CPU 核数(`navigator.hardwareConcurrency`)+ 串行/4/8/16 进程下拉,默认串行,标注推荐档(`min(CPU, 8)`)。默认串行的考量:Windows spawn 启动开销大,小机器上多进程可能反而更慢,让用户先实测再开并发。 - **策略库组合评级**(`web-ui/src/views/StrategiesView.vue`)—— 组合回测结果区新增「组合评级」详情块,从 `combined_equity` 重算夏普/卡玛/回撤/波动率等 5 维度评分,复用 `gradePortfolio`(与 `/portfolio` 页和单标的回测页同口径),让用户一眼判断这个组合该不该经常参与。 ### 变更 - **市场前缀判断统一**(`web-ui/src/views/BacktestView.vue` + `StrategiesView.vue`)—— `BacktestView.fullSymbol` 此前硬编码规则(6/9 开头 SH,8/4 开头 BJ,其余 SZ),漏判 5 开头的沪市基金/ETF(如 `515030` 被误判为 SZ)。改为复用 `market.ts` 的 `detectMarket`(与 `SymbolPicker` / `StocksPicker` 同一套规则)。`StrategiesView` 新增 `normalizeSymbol`,在发请求前重算历史保存策略的市场前缀,纠正历史数据 + 兜底未来。 - **`backtest_schemas.py` 代码风格** —— 修复 `SavedStrategyCreate.kind` 字段描述行超过 ruff 100 字符上限(E501)的历史遗留。 ### 已知约束(非 bug) - **并发默认串行** —— 多进程在 Windows 上 spawn 子进程有启动开销,CPU 核数少的机器开并发可能反而更慢。因此 UI 默认选串行,让用户先用同一标的、串行 vs 并行各跑一次对比耗时,再决定是否开并发。 - **并发仅对「一键寻优所有策略」生效** —— 单策略寻优(`/backtest/optimize/run/async`)内部网格点也可并行,但收益小、复杂度高,本次未做。 ## [1.18.0] — 2026-07-05 **Web UI 策略库新增「策略组合」保存能力 + 分类 Tab** —— 此前用户在策略库勾选多个单标的策略做组合回测,跑出满意结果后却**无法保存**这个组合,下次要重新勾选重新跑。更关键的是,用户真正的诉求是「下次打开就知道哪些该买、哪些该卖」——这本质是要**截至今天的策略信号**,而非静态存档。本次落地:在策略库融入 `kind: 'multi'` 类型,组合回测结果区加「💾 保存为组合」按钮,组合卡片「↻ 重跑到今天」一键用今天作为结束日重跑,跑出来的"当前持仓"就是截至今天的策略信号(持仓/空仓/浮盈/浮亏)。同时把策略库拆成「单标的」/「组合」两个 Tab,避免数量多了之后混排难找。 ### 新增 - **策略组合保存**(`web-ui/src/views/StrategiesView.vue` + 后端 schema 扩展)—— 组合回测结果区右上「💾 保存为组合」橙色按钮,弹窗输入名称 + 备注。存为 `kind: 'multi'`,`context.items` 存完整 `MultiStrategyItem[]`(各策略的 strategy+params+symbol+日期),`snapshot` 存组合级绩效(总收益/年化/策略数/资金)。复用现有 SQLite 单表(`kind` 字段本就是 TEXT),**零数据库迁移,零后端逻辑改动**。 - **载入即重跑到今天** —— multi 卡片按钮不是普通「载入」而是「↻ 重跑到今天」:confirm 后自动把 `end_date` 全部覆盖为今天,触发组合回测,跑完滚动到结果区。这样"当前持仓"表 = 截至今天的策略信号,直接回答用户「哪些该买该卖」。 - **持仓三态高亮** —— 当前持仓表的状态徽章从两态(持仓/空仓)升级为三态:🟢 **持有**(浮盈,绿底)/ 🟠 **持有·浮亏**(橙底,提示注意止损)/ ⚪ **空仓·等买点**(灰底,行半透明)。让用户一眼分辨"该继续拿"还是"该警惕"。 - **策略库 Tab 分类** —— 顶部新增「单标的 [N]」/「组合 [N]」两个 Tab(带计数徽章 + 蓝色下划线 active 指示),切换时清空勾选避免跨 Tab 残留。「组合回测」按钮仅在单标的 Tab 显示(组合策略无法再被组合);空态文案按 Tab 分别引导。 - **过拟合警示 + 模型仓位免责** —— 组合回测结果区顶部橙色警示条明确告知「历史回测优秀 ≠ 未来一定有效,收益可能来自特定时段市场环境」;持仓表上方水印标注「表中是**模型仓位**,不是你真实账户的持仓,过夜后可能因新 K 线触发买卖而变化」。载入组合的 confirm 弹窗也提示策略信号非投资建议。 - **保存组合弹窗 A11y** —— `role="dialog"` + `aria-modal="true"` + `aria-labelledby`,ESC 关闭,打开时自动 focus 到名称输入框。 ### 变更 - **后端 schema kind 扩展**(`src/easy_tdx/web/backtest_schemas.py`)—— `SavedStrategyCreate.kind` / `SavedStrategy.kind` 的 `Literal` 由 `["single", "portfolio"]` 扩展为 `["single", "portfolio", "multi"]`。前端 `web-ui/src/types.ts` 镜像同步。 - **卡片 Badge 细化** —— multi 显示「多策略」橙色徽章 + 🗂 图标 + 橙色卡片边框;portfolio 显示「多标的」紫色徽章。两类组合在「组合」Tab 内可一眼区分。 - **代码审计修复(10 项)** —— 修复 `multi-icon` 误用未引入的 Material Icons 字体导致显示英文文本;补齐 `onComboBacktest` 漏写的 `lastComboCash`;持仓三态计算收敛为 `holdingViews` computed(避免模板每行 3 次函数调用);`.overfit-warn` 与 `.signal-disclaimer` 合并为 `.warn-box` 基础类 + modifier;`ctx.items` 加运行时校验;`document.querySelector` 改用 ref;删除 `selectedIds` 静默替换的副作用。 ### 已知约束(非 bug) - **策略信号 ≠ 投资建议,也非真实账户持仓** —— 系统显示的"持仓"是**模型仓位**(策略说该持仓),不是用户真实账户的持仓。UI 已在多处(过拟合警示条 + 持仓表水印 + 载入 confirm)明确标注。用户应把它当作参考信号,而非"未来必涨"的保证。 - **信号会漂移** —— 今天重跑说"持仓",明天大跌触发止损可能变"空仓"。需要定期打开重跑。水印标注的是计算当天的收盘价信号。 - **不做真实账户追踪** —— 不存用户实际买卖了多少股、真实成本。那是投资日记/MRP 级别功能,工程量 5x,本次未做。 ## [1.17.15] — 2026-07-05 **Web UI 参数寻优体验升级:一键寻优按钮改橙色 + 等待期间投资大师名言轮播** —— 参数寻优是一个后台 Task,跑一遍全策略预设网格往往要 30 秒到几分钟。此前点击「一键寻优所有策略」后,按钮变灰、右侧整片纯色空白,用户根本不知道系统在干什么,容易以为卡住。本次给两段体验都做了升级:按钮改为暖橙渐变(`#f59e0b → #ea580c`)在深色金融主题下比 primary 蓝更醒目;寻优进行中右侧展示 100 条全球投资大师名言(巴菲特/芒格/格雷厄姆/林奇/索罗斯/利弗莫尔/塔勒布/达利欧等),3 秒随机轮播一条,Fisher-Yates 洗牌避免短期重复,让等待不枯燥、还能学到东西。 ### 新增 - **100 条投资大师名言数据**(`web-ui/src/data/investment-quotes.ts`)—— 覆盖价值投资(巴菲特/芒格/格雷厄姆/林奇/费雪/博格尔/邓普顿)、宏观对冲(索罗斯/罗杰斯/达利欧/塔勒布)、技术趋势(利弗莫尔/江恩)、行为金融(霍华德·马克斯)等流派,纯中文面向中文用户。每条 `{ text, author }` 结构。 - **名言轮播组件**(`web-ui/src/components/QuoteCarousel.vue`)—— Props 驱动 `interval`(默认 3000ms),mount 时 Fisher-Yates 洗牌取第 0 条,每 `interval` ms 推进,一轮播完重新洗牌;`onUnmounted` 清 `setInterval` 防泄漏。视觉:顶部 3 秒线性进度条(橙色填充暗示"还在跑")+ 装饰大引号 + fade/slide 过渡(350ms)+ 底部脉动橙点「后台寻优进行中,大师智慧伴你等待…」。CSS 变量 `--quote-interval` 把 props 透传给动画时长,保证进度条与切换同步。 - **一键寻优按钮橙色样式**(`web-ui/src/views/OptimizeView.vue`)—— 新增 `.run-all-btn` 暖橙渐变 + 橙色光晕;hover 加亮上移 1px;运行中保留暗橙识别度(不像普通按钮变纯灰)。 ### 已知约束(非 bug) - **轮播组件目前仅接入「一键寻优」** —— 设计成 Props 驱动、零业务耦合,未来可复用到「开始寻优」「组合回测」等同样有后台 Task 等待的页面,本次未做。 ## [1.17.14] — 2026-07-05 **Web UI 新增「数据评级」系统(S/A/B/C/D 五档)** —— 此前回测结果只有冷冰冰的 19 项指标,普通用户看到「总收益 126%」会觉得不错,却看不出胜率仅 35%、最大回撤 41% 背后的「套牢拿不住」风险。本次给单标的回测、组合回测、参数寻优三个入口都加上一个一眼可读的评级徽章,让普通人 1 秒判断「这个品种适不适合经常参与」。评级**不看收益率**(避免被近期大涨误导),只看风险调整后的持有体验:卡玛比率(套牢回本难度)、最大回撤、胜率、利润因子、夏普、波动率六个维度加权评分,再叠加一票否决(系统亏损/深回撤/低胜率)。京东方那种「收益好看但风险高」的案例会评 **D 档**,明确告诉用户「别碰」。长线低频策略(如 6 年 6 笔交易)不会被一刀切否决——交易笔数少时只把胜率/利润因子降权,不影响基于净值的评级。 ### 新增 - **评级核心模块**(`web-ui/src/grading/`)—— 纯前端 TypeScript 实现,零后端改动。`engine.ts`(线性插值 + 加权 + 一票否决)、`thresholds.ts`(8 维度阈值锚点表,集中可调)、`combinedMetrics.ts`(从组合净值曲线重算夏普/卡玛/波动率)、`index.ts`(三个场景入口)。 - **三个场景评级** —— 单标的回测用 6 维度(卡玛 18% + 最大回撤 17% + 胜率 17% + 利润因子 18% + 夏普 15% + 波动率 15%);组合回测从 `combined_equity` 重算 5 维度(卡玛 25% + 最大回撤 22% + 夏普 22% + 索提诺 15% + 波动率 16%,因净值算不出胜率/利润因子);参数寻优用 4 维度降级版(夏普 30% + 最大回撤 28% + 胜率 22% + 利润因子 20%,因 GridPointResult 只有 6 字段)。 - **一票否决规则** —— 系统亏损(`profit_factor < 1` → D)、深回撤(`max_drawdown > 60%` → D)、高回撤(> 50% 最高 B)、低胜率(< 30% 且样本充足最高 C)、微利(利润因子 < 1.2 最高 B)。 - **样本不足降权(不否决)** —— 交易笔数 < 10 时,把依赖逐笔成交的维度(胜率/利润因子)权重降到 0,重分配给净值类维度;评级照常给出,旁边标「⚠ 交易样本有限」。修复了「长线策略 6 年 6 笔被打到 D」的过度惩罚。 - **评级 UI 组件**(`GradeBadge.vue` / `GradeDetails.vue`)—— 圆形徽章(S 金/A 绿/B 蓝/C 橙/D 红,遵循 A 股颜色惯例)+ 展开式详情(维度得分条 + 否决原因 + 样本提示)。接入 `BacktestView` / `PortfolioView` / `OptimizeView`,寻优排名表新增「评级」列。 - **评级自检测试**(`web-ui/src/grading/__tests__/grade.test.ts`)—— 15 个测试用 Node 内置 `node:test` + rolldown 打包跑,覆盖核心场景:京东方 = D(核心断言)、长线策略 = B、否决规则、组合评级、插值边界。 ### 变更 - **`tsconfig.app.json` 排除测试目录** —— `src/**/__tests__/**` 和 `scripts/**` 不进 app bundle(测试用 rolldown 独立打包跑,不经 vue-tsc)。 ### 已知约束(非 bug) - **评级阈值需在真实数据上观察后微调** —— 所有阈值集中在 `thresholds.ts`,当前用金融惯例值校准。如果某批真实回测的评级不符合直觉,可在该文件单点调整,无需动评分引擎。 - **寻优排名表全量算评级** —— 大表(200 行)未做虚拟化,目前性能可接受。若未来卡顿再优化。 ## [1.17.13] — 2026-07-04 **修复多策略组合回测「最大回撤」严重虚高** —— 用户反馈:3 个策略各自最大回撤仅 45.53%/40.16%/16.89%,组合在一起却显示 **83.76%**。根因是 `MultiStrategyEngine._build_combined_equity` 计算 `drawdown_pct` 时**分母误用初始资金(`initial`)而非逐点峰值(`peak`)**:净值大涨后峰值是初始值的好几倍(本例总收益 545%,峰值≈6.45×初始),同样的绝对回撤额除以小的初始值,百分比被等比放大。正确公式应为 `drawdown / peak`(相对当时峰值的回撤,0~1),与单标的 `PortfolioTracker.equity_curve` 的 `drawdown_pct` 定义一致。修复后最大回撤回到合理区间(≤ 各策略最大回撤的加权,不可能超过 100%)。**连带修复**:卡玛比率(`年化收益 / 最大回撤`)此前因 max_drawdown 虚高而被压低,修复后恢复正常。其余指标(总收益/年化/夏普/索提诺/波动率/交易数/胜率/盈亏比)经逐一核对**均正确**,不受此 bug 影响。 ### 修复 - **`drawdown_pct` 分母改用逐点峰值**(`src/easy_tdx/backtest/multi_strategy_engine.py` `_build_combined_equity`)—— `drawdown / initial` → `drawdown / peak`(`peak_safe = peak.where(peak != 0, 1.0)` 防除零)。这同时修复 `EquityChart` 回撤曲线显示(前端读 `drawdown_pct` 取负向下画)。 - **回归守卫**(`tests/unit/test_multi_strategy.py::test_max_drawdown_relative_to_peak_not_initial`)—— 构造「净值 1→6→4」的大涨后回撤场景,断言 `max_drawdown ≈ 33%`(旧逻辑会算成 200%,必 >1,断言 `≤1.0` 抓住回归)。 ## [1.17.12] — 2026-07-04 **修复 CI 在新版 FastAPI 上路由注册失败** —— v1.17.11 的 `DELETE /api/v1/strategies/{id}` 用 `status_code=204`,较新 FastAPI/Starlette 在路由注册阶段(`add_api_route`)就抛 `AssertionError: Status code 204 must not have a response body`,导致 CI 的 ubuntu 矩阵(py3.10/3.12/3.13)整片 ERROR(21 个 web 测试因 fixture 导入 router 而连带失败)。改为返回 `200 + {"deleted": id}` 确认体,既消除注册期断言又给前端明确反馈。 ### 修复 - **DELETE 路由不再用 204**(`src/easy_tdx/web/routers/strategies.py`)—— `status_code=204` 改为默认 200,返回 `{"deleted": strategy_id}`;同步更新测试断言(`tests/unit/test_strategy_store.py`)。 ## [1.17.11] — 2026-07-04 **Web UI 新增「策略库」与「多策略组合回测」** —— 此前回测结果存在进程内存,重启即丢,用户无法留存自己反复验证过的好策略。本次落地两层能力:(1) **策略库**——在单标的/组合回测结果区点「保存策略」,把策略 + 标的上下文 + 成绩快照(总收益/夏普/回撤/胜率)一起存进本地 SQLite 单文件(`~/.easy_tdx/strategies.db`),策略库页可载入回填、一键重跑、删除;(2) **多策略组合回测**——策略库勾选 N 个单标的策略,各拿 1/N 资金、各跑原标的(取最新行情),净值曲线按日期并集对齐求和,组合结果复用单标的的 19 项完整绩效指标(基于合并净值曲线 + 汇总成交用 `PerformanceAnalyzer` 算出),并展示各策略当前持仓。**895 单测全绿**(+24 新增),ruff format/check / mypy strict / 前端 vue-tsc 全通过。 ### 新增 - **策略库后端**(`src/easy_tdx/web/strategy_store.py`、`routers/strategies.py`)—— SQLite 单文件 CRUD(加入/列出/查看/删除),落库路径随 `EASY_TDX_CONFIG_DIR` 环境变量走(与 `config.py` 同约定),线程安全(写操作串行锁 + `check_same_thread=False`)。5 个接口:`GET/POST /api/v1/strategies`、`GET/DELETE /strategies/{id}`。保存记录含 strategy + params + context(symbol 或 stocks + 日期 + 周期)+ trade_config + snapshot(成绩快照)+ tags + notes。 - **策略库前端**(`web-ui/src/views/StrategiesView.vue` + 路由 `/strategies` + 导航)—— 卡片网格列表,展示策略名/标的/收益/夏普/回撤/标签/备注/创建时间。「载入」跳转对应回测页并自动回填(单标的剥掉市场前缀只传 6 位代码;组合新增 URL query 回填);「删除」二次确认。空态提示去回测页保存。 - **保存策略按钮**(`BacktestView.vue` / `PortfolioView.vue` 结果区)—— 弹窗填名称/标签/备注,其余(策略参数、标的上下文、成绩快照)自动从当前请求 + 结果填入。 - **多策略组合回测引擎**(`src/easy_tdx/backtest/multi_strategy_engine.py`)—— `MultiStrategyEngine`:N 个策略各拿 1/N 资金、各跑原标的,曲线按日期并集 ffill 对齐求和。输出结构同 `PortfolioResult`(`individual_results` key 形如 `"双均线交叉@SH:601088"`),前端复用组合页图表零改动。 - **多策略组合回测接口**(`web/routers/backtest.py` `POST /backtest/multi-strategy/run/async`)—— 勾选 N 个策略,逐个在 async 上下文取行情 + 构造策略实例(失败跳过),后台线程跑引擎。组合整体绩效基于合并净值曲线 + 汇总成交喂 `PerformanceAnalyzer`,得到与单标的同口径的 19 项指标。 - **策略库组合回测 UI**(`StrategiesView.vue`)—— 每张卡片加复选框(组合策略无单一 symbol 自动 disabled),顶部「组合回测(N)」按钮,结果区复用 `EquityChart` + `MetricTable`(19 项绩效)+ `PortfolioSummaryTable` + `PortfolioCompareChart` + 当前持仓表(各策略回测结束持仓快照)。 ### 变更 - **`PortfolioView.vue` 新增 URL query 回填** —— 此前组合页不读 query,策略库「载入组合策略」无法回填;新增 `onMounted` 读取 `strategy/params/stocks/startDate/endDate/category`,与单标的页回填风格一致。 - **修正多策略合并净值曲线回撤符号** —— `_build_combined_equity` 原用 `drawdown = total - peak`(负值),改为 `peak - total`(正值),与单标的 `PortfolioTracker`、`PerformanceAnalyzer`、`EquityChart`(前端取负向下画)的正值约定一致;否则最大回撤算成 0、夏普/卡玛比率失真。 ### 已知约束(非 bug) - **多策略组合回测仅支持资金分仓(并行制)** —— 每个策略各拿 1/N 资金独立回测后曲线相加;不支持信号共振(投票制,`combo.py` 已有但未暴露 Web API)。资金/成本统一一组均分,不支持每策略单独配置。 - **组合回测结果暂不回存策略库** —— 当前可保存的是单次回测的策略;多策略组合的结果暂未支持存为"策略的组合"。 ## [1.17.10] — 2026-07-04 **Web UI 一键寻优「查看」按钮跳转携带完整行情上下文** —— `/optimize` 页策略排名表的两个「查看」按钮此前跳转只带 `strategy` + `params`,丢失了股票代码、周期、起止日期,导致跳到回测页后用户得手动重选标的与日期才能复现寻优行情。本次让跳转 URL 额外携带 `symbol/startDate/endDate/category`,回测页 `onMounted` 自动回填到 `SymbolPicker` 表单(股票代码/周期/起止日期全部就位),用户只需点「开始回测」即可完整复现。**向后兼容**:老书签(只有 `strategy/params`)仍正常工作,缺失字段保持默认值。前端 `vue-tsc --noEmit` / `vite build` 通过,后端 870 单测全绿(无回归)。 ### 新增 - **SymbolPicker 表单状态双向同步**(`web-ui/src/components/SymbolPicker.vue`)—— `code/category/startDate/endDate` 从私有 `ref` 升级为 `defineModel`(带默认值),父组件可读(拼 URL)可写(回填表单)。`defineExpose({ loadBars, loading })` 保留不动,向后兼容已有调用。 ### 变更 - **「查看」跳转 URL 携带完整上下文**(`web-ui/src/views/OptimizeView.vue`)—— 抽 `buildBacktestQuery(strategyName, params)` 统一构造 query,`onViewParams`(单策略网格排名表)/`onViewAll`(全局策略排名表)两个按钮跳转时附带 `symbol/startDate/endDate/category`。 - **回测页回填标的与日期**(`web-ui/src/views/BacktestView.vue`)—— `onMounted` 新增读取 `route.query.symbol/startDate/endDate/category`,各字段独立 `if` 守卫回填到 `SymbolPicker` v-model 镜像 ref。老 URL 缺失字段保持默认值。 ### 已知约束(非 bug) - **URL query `category` 无白名单校验** —— 与既有 `strategy/params` 读取风格一致,非法值由后端 `/bars` 兜底拒绝;前端 `