fix(monitor): 修复监控中心自定义/AI 策略触发记录显示不正确的问题

监控中心触发记录此前只显示「策略监控」等通用 type 标签,缺少策略名和股票名,
看起来像只有内置策略能正常显示 (实际是 demo 数据的假象)。

根因:
1. 触发记录的标签写死为 TYPE_LABEL[source],未使用事件里已有的 rule_name 字段
2. 真实告警的 name 字段全为 null — enriched DataFrame 在 pipeline 计算后 drop 了 name 列
   (indicators/pipeline.py),导致 row.get('name') 永远返回 None
3. 策略告警的 message 空泛 (仅「策略触发」),前端高亮 regex 无法匹配

修复 (向 demo 数据质量对齐):
- 后端 MonitorRuleEngine: 新增 set_name_map(),触发时对 None 的 name 从 instruments 表回填
- 后端 _evaluate_monitors: 每轮评估前注入 symbol→name 映射
- 后端 _default_message: 策略类型改为生成「策略名」买入/卖出信号,对齐 demo 模板格式
- 前端 AlertsList: 标签优先显示规则名 (截取「策略监控 · 空中加油」→「空中加油」)

效果: 自定义/AI/内置策略统一走相同代码路径,触发记录均显示策略名+股票名+格式化消息
This commit is contained in:
shy3130
2026-06-22 11:33:40 +08:00
parent 61ababd544
commit 56e9dc66a2
3 changed files with 49 additions and 5 deletions
+11
View File
@@ -462,6 +462,17 @@ class QuoteService:
if self._app_state:
engine = getattr(self._app_state, "monitor_engine", None)
if engine and engine.rule_count > 0:
# 预构建 symbol → name 映射 (enriched 已 drop name 列, 引擎触发时回填用)
try:
inst_df = self._app_state.repo.get_instruments()
if not inst_df.is_empty() and "symbol" in inst_df.columns and "name" in inst_df.columns:
engine.set_name_map({
row["symbol"]: row["name"]
for row in inst_df.select(["symbol", "name"]).iter_rows(named=True)
if row.get("name")
})
except Exception as e: # noqa: BLE001
logger.debug("name_map 构建失败 (不影响监控): %s", e)
rule_events = engine.evaluate(enriched_today)
if rule_events:
# 落盘到 alerts.jsonl
+32 -4
View File
@@ -268,11 +268,21 @@ class MonitorRuleEngine:
# (rule_id, symbol) → 上次触发时间戳(秒)。用于 cooldown 去重。
self._last_fire: dict[tuple[str, str], float] = {}
self._strategy_engine = None # 延迟注入, type=strategy 规则用它读策略信号
# symbol → 股票名 (enriched DataFrame 已 drop name 列, 触发时从此映射回填)
self._name_map: dict[str, str] = {}
def set_strategy_engine(self, engine) -> None:
"""注入 StrategyEngine, type=strategy 规则据此读策略的 entry/exit_signals。"""
self._strategy_engine = engine
def set_name_map(self, name_map: dict[str, str]) -> None:
"""注入 symbol → 股票名 映射, 用于在告警事件里回填 name 字段。
enriched DataFrame 在 pipeline 计算后不含 name 列 (见 indicators/pipeline.py),
触发时从 instruments 表预构建此映射, 保证 AlertEvent.name 有值。
"""
self._name_map = name_map or {}
# ── 规则管理 ───────────────────────────────────────
def set_rules(self, rules: list[dict]) -> None:
"""批量设置规则 (覆盖)。用于启动时 reload。"""
@@ -363,6 +373,8 @@ class MonitorRuleEngine:
if last is not None and (now - last) < cooldown:
continue # 冷却期内, 跳过
self._last_fire[key] = now
# enriched DataFrame 已 drop name 列 → 从注入的 name_map 回填 (instruments 表)
resolved_name = name if name else self._name_map.get(sym)
ev = {
"ts": int(now * 1000),
"rule_id": rule["id"],
@@ -370,7 +382,7 @@ class MonitorRuleEngine:
"source": source,
"type": ev_type,
"symbol": sym,
"name": name,
"name": resolved_name,
"message": message,
"price": price,
"change_pct": pct,
@@ -461,8 +473,24 @@ class MonitorRuleEngine:
results.append((sym, name, price, pct, hit_sigs))
return results
@staticmethod
def _default_message(rule: dict) -> str:
def _default_message(self, rule: dict) -> str:
"""生成默认 message。策略类型带策略名 + 方向 (对齐 demo 模板格式, 让前端可高亮)。"""
rtype = rule.get("type", "signal")
name_map = {"signal": "信号触发", "price": "价格触发", "market": "市场异动", "strategy": "策略触发"}
if rtype == "strategy":
direction = rule.get("direction", "entry")
action = {"entry": "买入", "exit": "卖出", "both": "买卖"}.get(direction, "买入")
# 从 StrategyEngine 取策略名; 失败则退化为 rule_name 里截取的部分
sname = ""
sid = rule.get("strategy_id")
if sid and self._strategy_engine is not None:
try:
s = self._strategy_engine.get(sid)
sname = s.meta.get("name", "") or s.meta.get("id", "")
except Exception: # noqa: BLE001
sname = ""
if not sname:
rn = rule.get("name", "")
sname = rn.split(" · ", 1)[1] if " · " in rn else (rn or "策略")
return f"策略「{sname}{action}信号"
name_map = {"signal": "信号触发", "price": "价格触发", "market": "市场异动"}
return name_map.get(rtype, "监控触发")