From bb48c918539de459828a2f5682085b2c4181114c Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 1 Oct 2026 18:10:29 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=90=8C=E6=AD=A5=E3=80=8C=E4=B9=B0?= =?UTF-8?q?=E5=8D=96=E7=90=86=E7=94=B1=20/=20=E5=9B=A0=E5=AD=90=E6=9B=B2?= =?UTF-8?q?=E7=BA=BF=20/=20=E6=9B=B2=E7=BA=BF=E6=94=BE=E5=A4=A7=20/=20?= =?UTF-8?q?=E4=BD=9C=E4=B8=9A=E5=8F=8D=E9=A6=88=E3=80=8D=E4=B8=8E=E9=97=A8?= =?UTF-8?q?=E7=A6=81=E6=9D=A1=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - §6.3 新增两块说明:①回测结果的买卖理由(封闭词表、成交与未成交都覆盖、 区分跌出 TopN / 不在候选池 / 全量换仓 / Tmin / Tmax / 涨跌停 / 现金不足)、 因子曲线口径(持仓市值加权原始值、空仓不落点、带方向与单位)、 `/charts/{id}?s=...` 放大页(Server Component,数据从归档直出,未归档不给假按钮); ②全站统一的作业反馈(useJobRunner + JobProgress:提交瞬间有字、已用每秒自增、 可取消、失败给后端原文、成功给归档入口、自动滚入视野)。 - 门禁条数更新为 524;回测结果契约脚本补充「理由词表/名次/因子值/因子曲线」断言说明; 列出新增的两个理由测试文件。 --- docs/USAGE.md | 47 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/docs/USAGE.md b/docs/USAGE.md index ee85d77..a7e48bf 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -508,6 +508,33 @@ PYTHONPATH=. .venv/bin/python -m app.cli.restore_experiment_from_job \ **归档完整度会明示**:归档页顶部标注「归档完整 N/N」;若曲线因体积预算被裁剪,或该快照产生于 完整存档上线之前(个股曲线只有 60 只),页面直接显示「**归档不完整**」并给出差异与「以此参数重跑」入口。 +> **买卖理由 + 因子曲线 + 曲线放大**(2026-10):回测结果不再只给「成交了哪些」, +> 而是回答「**为什么买 / 为什么卖**」,且数字全部来自引擎当时的计算: +> - `ActionRecord.reason`(`TradeReason{code,text,data}`)覆盖**成交与未成交**的每个买卖点: +> 名次 / 候选数 / 综合分 / 各因子**原始值** / 持有交易日 / 现金预算 / 涨停比值…; +> 原因分类是封闭词表(`quant/trade_reasons.py`),组合引擎与单策略引擎共用同一套构造器, +> 两个引擎对同一件事不会写出两种说法。 +> - 区分「**跌出 TopN**」「**不在候选池**(被股票池/条件过滤,如转 ST)」「**全量换仓**」 +> (单策略引擎每次调仓先清仓再建仓,被卖出的股票可能仍排在 TopN 内,这时不能写「跌出 TopN」) +> 「**Tmin 保护暂留**」「**超 Tmax 强制了结**」「涨停/停牌/现金不足/不足最低佣金」。 +> - `result.factor_curves`:每个策略因子一条曲线 = 当日**持仓按市值加权平均的原始值** +> (不做 z-score、不按方向取反,**空仓日不落点、不插值、不用 0 填充**),带 +> `label / direction / unit`,界面据此写明口径(如「股息率 %」。收益曲线对照成交日, +> 一眼看出「买在什么水平、卖在什么水平」)。 +> - **每条曲线都能新页面放大**:`/charts/{归档id}?s={equity|drawdown|factor:|sym:|monthly}`。 +> 放大页是 Server Component,数据从**归档**直出(URL 可分享、刷新还原同一张图); +> 结果没归档时不显示假按钮,而是写明「未归档,无法放大」。 +> 数字格式与后端 `f"{v:.4f}"` 同规则(四舍六入五成双),避免同一个综合分在理由原文与 +> 数字标签里显示成两个数。 + +> **作业反馈(全站统一)**(2026-10):`lib/jobs.ts` 的 `useJobRunner` + `components/JobProgress.tsx`。 +> 解决的问题是用户原话「点了回测没有任何反馈,不清楚是不是已经开始」: +> **提交瞬间**就显示「排队中 + 作业号 + 发起时间」,**已用时间每秒自增**(不等后端报新阶段), +> 阶段取后端真实 `stage`,排队/运行中可**取消任务**(`POST /jobs/{id}/cancel`), +> 失败显示后端原文、成功给「打开归档 / 去对比」;反馈条出现时自动滚入视野 +> (运行按钮常在长表单底部,不滚过去等于没显示),并带 `role="status" aria-live="polite"`。 +> 各页多个作业互不干扰(各自 `anchorId`)。 + **图表**:全站统一使用 **TradingView Lightweight Charts**(`components/charts/LwChart.tsx`), ECharts 已从依赖中移除。买卖点标记(▲绿=买入 / ▼红=卖出)只落在该 series 真实存在的交易日上, 并按时间升序提交(Lightweight Charts 的硬约束),因此不会出现标记丢失或错位。 @@ -524,6 +551,16 @@ cd backend && PYTHONPATH=. .venv/bin/python ../scripts/verify_strategy_workspace # 只验接口与页面(跳过回测 Job):加 --skip-job ``` +**回测结果契约**(跑一次真实区间,约 3 分钟;校验页面实际读取的每个字段): + +```bash +cd backend && PYTHONPATH=. .venv/bin/python ../scripts/verify_backtest_page_contract.py --end 2023-12-31 +``` + +它除了净值/个股曲线/买卖点落点,还会断言:**每个买卖点都有结构化理由且原因代码在词表内**、 +成交类理由带名次/候选数/综合分/因子原始值、`trades` 两端理由齐全、`factor_curves` 非空且 +日期升序无重复 —— 也就是「买了什么原因、图上的因子曲线」这些字段真的在。 + #### 6.3.2 界面规范与对齐自检(控件尺寸 / 输入友好) 界面不是"能看就行":控件错位、尺寸与内容不匹配、点不中、报错说不清,都会直接变成操作错误。 @@ -625,12 +662,12 @@ Agent 能力边界(10 个内置受控工具,只读 + 受控写库): ```bash cd backend uv run ruff check app tests && uv run ruff format --check app tests -uv run pytest # 全量测试(每个里程碑提交前均须通过,当前 500 条) +uv run pytest # 全量测试(每个里程碑提交前均须通过,当前 524 条) cd frontend/web pnpm run typecheck # tsc --noEmit,0 error pnpm run test:charts # 图表标记逻辑单测(7 条,node --test,无需额外依赖) -pnpm run build # 13 条路由,含 /strategies /backtest /experiments /experiments/[id] +pnpm run build # 路由含 /strategies /backtest /experiments /experiments/[id] /charts/[id] ``` **端到端契约脚本**(会真实提交回测 Job,用于验证「页面实现」与「后端字段」不漂移): @@ -638,7 +675,7 @@ pnpm run build # 13 条路由,含 /strategies /backtest /e ```bash cd backend PYTHONPATH=. .venv/bin/python ../scripts/verify_strategy_workspace.py # 选股策略/字段库/因子目录+参数化/公共配置/回测组合库/归档链路(145 项 skip-job,含真实组合回测更多,约 5 分钟) -PYTHONPATH=. .venv/bin/python ../scripts/verify_backtest_page_contract.py # 回测结果结构契约(约 4 分钟) +PYTHONPATH=. .venv/bin/python ../scripts/verify_backtest_page_contract.py # 回测结果结构契约:字段 + 买卖理由词表/名次/因子值 + 因子曲线(约 3 分钟) python3 ../scripts/verify_ui_alignment.py # UI 对齐与控件一致性(8 个页面 × 2 宽度 = 160 项,约 3 分钟) python3 ../scripts/verify_unit_conversion.py # 字段库单位:只能在给定范围内选 + 界面单位⇄基准单位换算(约 2 分钟) python3 ../scripts/verify_factor_params.py # 因子参数化:暴露真实参数/界面新建参数化因子/越界拒绝/停用不影响历史解析(约 3 分钟) @@ -651,7 +688,9 @@ python3 ../scripts/verify_factor_params.py # 因 归档、Agent 工具白名单与编排、LLM 配置加载、API 端到端、**策略说明推导** (`describe_strategy` 的分支/互斥/缺失值语义)、**股票名称回填**(回测与选股两侧)、 **UI 对齐与控件一致性**(同排等高 / 尺寸归一 / 点击目标 / 标签 / 无障碍名 / 横向溢出, -7 个页面 × 1500 与 375px 两种视口,见 §6.3.2)。 +7 个页面 × 1500 与 375px 两种视口,见 §6.3.2)、**买卖理由与因子曲线** +(理由词表封闭、数字来自引擎、因子曲线持仓市值加权且空仓不落点,见 +`tests/test_trade_reasons.py` 8 条 + `tests/test_local_engine_reasons.py` 14 条)。 ---