diff --git a/AGENT.md b/AGENT.md index acbd337..4e028f6 100644 --- a/AGENT.md +++ b/AGENT.md @@ -817,6 +817,38 @@ factor_exposure 前端组件只依赖这个结构。 +## 27.1 买卖理由:必须能回答「为什么买 / 为什么卖」,且数字来自引擎 + +用户看回测结果时的第一个问题不是「赚了多少」,而是「**为什么在这里买 / 卖**」。 +因此每个买卖点(**成交的与未成交的都要**)必须带结构化理由 +(`TradeReason{code, text, data}`,见 `backend/app/quant/trade_reasons.py`): + +- **原因分类是封闭词表**:组合引擎(`combo_engine.py`)与单策略引擎(`local_engine.py`) + 共用同一套 code 与文案构造器,禁止各自手写措辞 —— 否则同一件事会出现两种说法。 +- **`data` 里只能是引擎当时的真实数字**(名次 / 候选数 / 综合分 / 各因子**原始值** / + 持有交易日 / 预算 / 涨停比值…)。前端**只展示不推算**;拿不到名次就写「未给出名次」, + 不拿旧名次或其他日期的数据冒充。 +- **把事实说准**:跌出 TopN ≠ 不在候选池(被股票池/条件过滤)≠ 全量换仓 + (策略每次调仓先清仓,被卖的股票可能仍排在前列)≠ Tmin 保护暂留 ≠ 超 Tmax 强制了结 + ≠ 涨停/跌停/停牌/现金不足。宁可为一种情况新增一个 code,也不要套一个语义不符的旧 code。 +- **因子曲线**(`result.factor_curves`)= 当日**持仓按市值加权平均的原始值** + (不做 z-score、不按方向取反,空仓日不落点、不插值、不用 0 填充), + 界面必须同时写出方向与单位(如「股息率 %,越高越好」),否则读者会误判曲线的含义。 +- **每条曲线都要能新页面放大**(`/charts/{归档id}?s=...`):放大页从**归档**读同一份数据 + (URL 可分享、口径不漂移);没有归档 id 时如实说明「未归档,无法放大」,不给坏链接。 + +## 27.2 长任务反馈:点了立刻有字、看得出在动、出事了能自救 + +用户原话:「点了回测没有任何反馈,不清楚是不是已经开始」。因此跑异步 Job 的页面必须: + +- 提交**瞬间**就有反馈(提交中 → 排队中),不等到后端返回; +- 显示**真实**作业号 / 阶段 / 逐秒自增的已用时间(用 `lib/jobs.ts` 的 `useJobRunner` + + `components/JobProgress.tsx`,不要各页手写一套); +- 排队/运行中可**取消**;失败显示后端原文;成功给「打开归档 / 去对比」入口; +- 反馈条出现时**自动滚入视野**,并带 `role="status" aria-live="polite"`; +- 同步接口(如 `POST /api/signals`)**没有**作业号与阶段时,如实说明「同步请求、无阶段、 + 不可取消」,**禁止**合成假作业号或假进度喂给反馈组件。 + --- # 28. AI Agent 约束