diff --git a/DEVLOG.md b/DEVLOG.md index dc74201..094d751 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -11,12 +11,22 @@ > - **bionic_trader(决策系统)** 跑在 `192.168.16.188:38000`,挂载卷, > `git pull` 后 `docker compose restart backend-api worker-brain` 即生效; > **新增 env 必须 `docker compose up -d --force-recreate`**,restart 不重读 env。 -> 容器时钟是 UTC,看时间戳先加八小时。 > - **akg-factor-bridge(因子桥)** 在 `factorevaluation`,容器名 `akg_factor_bridge`, -> API 在 8300,常驻 API 改码要 compose restart。容器时钟是 UTC。 +> API 在 8300,常驻 API 改码要 compose restart。 +> +> **时区(2026-08-06 查实、2026-08-07 再证一次,这里写的以实测为准)**: +> 不能按「哪个系统」一刀切,要按「哪种时间」分: +> - **容器时钟与容器日志正文** —— 北京时间,**不要加八小时**。 +> 证据:bionic 容器内 `date` 为 `15:37 CST`。 +> - **数据库的时间列**(`strategy_audit_log.created_at`、 +> `strategy_daily_results.updated_at` 等)—— **UTC,要加八小时**。 +> 证据:08-07 北京 09:03 时该库 `SELECT NOW()` = `2026-08-07 01:03:32`。 +> - **日期列**(`audit_date`、`trade_date`)—— 北京日期,不换算。 +> - `docker logs -t` 自己加的那个时间戳前缀是 UTC,跟正文里 Python logging +> 打的时间不是一个口径,别混着读。 > > 记录纪律:判收状态只写「真机见过」或「未判收」两种,不写「应该没问题」。 -> 时间一律写北京时间,引用桥或决策系统的日志时间时先加八小时并标注已换算。 +> 时间一律写北京时间,引用数据库时间列时先加八小时并标注已换算。 --- @@ -450,6 +460,93 @@ PMS_UNAVAILABLE 三个值,而 `pms_advisor._today_exit_verdict` 第 102 行明 --- +## 2026-08-07 · 股票评述里冒出「【手动重跑AKG_PLAN】」:运维口径的话被当成风控质询 + +**做了什么** + +页面上一些股票的评述里出现了「【手动重跑AKG_PLAN】」这种谁也看不懂的字段。第一反应会怀疑 +是前一天新建仓那批改动,但**不是**:三个仓库全文检索「手动重跑」只有一个出处, +`bionic_trader/scripts/rerun_pool.py` 第 237–238 行。链条如下(全部是源码,不是推测): + +1. `rerun_pool.py` 下发 `analyze_strategic_plan.delay(c, reason, None, target_date)`。 + **第二个位置参数不是留痕字段,是 `reviewer_feedback`** —— 风控官质询通道。 +2. `tasks_brain.IntradayClassifier.classify()` 第一道判断是 `if "【盘中" not in text: return None`, + 「【手动重跑】」不带这个前缀,返回 None。 +3. 于是掉进 `elif reviewer_feedback:` 分支,被拼进提示词的 + `[📢 CRITICAL CHALLENGE FROM RISK OFFICER]` 段,后面跟着 + `You MUST address this in your analysis`。 +4. 那是一句命令。大脑照办,把这句运维口径的话写进了 `analysis`。 +5. `generate_chinese_report` → `save_to_database` → 覆盖进 + `strategy_daily_results.analysis_summary`,也就是页面上的评述。 + +**这条真正的教训不是那个字段,是它顺带证实的悬项二。** `rerun_pool` 落库是 +`INSERT ... ON DUPLICATE KEY UPDATE`,把 `signal_type / support_level / pressure_level / +analysis_summary` 一起就地覆盖。查实的落库时刻(库时钟是 UTC,下面已换算成北京时间): + +| trade_date | 行数 | 落库时刻(北京,已换算) | 当时是什么时候 | +|---|---|---|---| +| 20260806 | 16 | 08-06 22:32 → 22:45 | 收盘后,安全 | +| 20260805 | 18 | 08-05 22:30 → **08-06 11:04** | **盘中**,改写的是当时正被读的昨夜结论 | +| 20260804 | 11 | **08-05 13:04** → 08-07 00:50 | **盘中**,补录历史日 | + +08-06 早盘 11:04 与 08-05 午盘 13:04 各有一次盘中重跑。而这张表下游有两个人在读: +bionic 的 `pms_advisor`(支撑压力位算 PMS 买卖区间、`signal_type` 走 `BAD_Y_SIGNALS` 闸)、 +桥 `pool.py` 的 `BAD_SIGNALS`(决定把哪只票请出候选池)。所以「盘中不跑 push-pool」这条纪律 +不只针对桥,`rerun_pool` 同样在内 —— 这次是拿实测把它钉死了,不再是推测。 + +顺带记一条待查的:`SZ000800` 现在有两行相互打架的定性,`trade_date=20260806` 是 `SELL` +(08-06 22:38 写入),`trade_date=20260804` 是 `DROPPED`(08-07 00:50 写入,晚了两个多小时)。 +补录历史日会写出一行「时间上更新、日期上更旧」的结论,取数方按 `trade_date` 排还是按 +`updated_at` 排会拿到不同答案。两边取数口径要对一遍,这次没做。 + +**改法(两处,缺一不可)** + +- **根治** —— `rerun_pool.py` 下发时第二个位置参数固定传 `None`;`--reason` 保留,但改成 + 只印在脚本自己的输出里给人看。 +- **兜底** —— `tasks_brain.py` 加 `_is_ops_note()` 前缀护栏,认 + `【手动重跑】/【运维】/【回补】`,命中就先打一行日志再把 `reviewer_feedback` 置空。 + 为什么两处都要:`rerun_pool` 的用法示例第 2 条明写着鼓励人手填 + `--reason "调整X因子后重跑"`,只堵住默认值,下次手填照样中招。 + +护栏落点选在 `analyze_strategic_plan` 打完那行「🧠 Brain 正在介入」日志之后、 +`IntradayClassifier.classify` 之前。**先打日志再丢弃**是有意的:「谁在什么时候用什么理由 +重跑了这只票」这条线索要留在日志里,它只是不该进大模型的证据。拦在源头一次就够,因为 +下游用到 `reviewer_feedback` 的只有 classify 和风控官质询段两处。 + +**已经被污染的评述救不回来**:就地覆盖,没有历史表。要洗只能改完代码后按同一个池重跑一遍 +让它再覆盖一次,而那又是一次全量重算、支撑压力位会再动 —— 只能收盘后做,别在盘中洗。 + +**动了哪些文件**(都在 bionic_trader,PMS 侧一个字没动) +- `scripts/rerun_pool.py` —— 下发传 `None`;`--reason` 改为本地留痕;文件头补两段说明 + (「什么时候不能跑」「为什么 --reason 不再下发给大脑」)。 +- `workers/tasks_brain.py` —— 新增 `_OPS_NOTE_PREFIXES` 与 `_is_ops_note()`; + `analyze_strategic_plan` 里加四行护栏。既有两条路径(盘中事件、常规风控意见)零改动。 +- `scripts/selftest_ops_note_guard.py`(新增,24 项)—— 不 import `tasks_brain` + (它拖着 milvus/llm),靠 AST 把 `_is_ops_note` 从源码里抠出来单独执行做真实断言, + 再用 AST 校验护栏落点与 `delay` 的第二个实参。做过三次变异验证,确认它真的会失败: + 说明文字塞回去、护栏只打日志不置空、前缀写宽一个字,三次都被抓住。 + +**部署方式** +决策系统那台:`git pull` 后 `docker compose restart worker-brain`。没有新增 env, +不需要 `--force-recreate`。**只重启 worker-brain 就够**,`backend-api` 没动。 + +**真机判收** +未判收。判收两步:① 容器里跑一次 +`docker exec trader_worker_brain python scripts/selftest_ops_note_guard.py` 见「全部通过」; +② 收盘后拿任意一只票跑一次 `rerun_pool.py --group AKG_PLAN --limit 1 --reason "护栏验证"`, +确认 worker 日志里有「🛡️ 运维口径说明已从提示词中剔除」这一行,且该票新落库的 +`analysis_summary` 里不含「手动重跑」。 + +**还欠着什么** +1. 上面两步真机判收。 +2. `SZ000800` 那种「补录历史日写出时间更新、日期更旧的行」,取数方按 `trade_date` 还是 + 按 `updated_at` 排要对一遍口径(`pms_advisor` 与桥 `pool.py` 两处)。 +3. 已污染的三批评述(16/18/11 行)要不要收盘后洗一遍,没定。 +4. `rerun_pool.py` 里 `--dry-run` 的中文说明写的是「干跑」,项目里同类东西叫「试算」, + 措辞不统一。这次没改,因为跟本条改动无关,单独提。 + +--- +