mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 17:54:15 +08:00
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:
@@ -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
|
||||
|
||||
@@ -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, "监控触发")
|
||||
|
||||
Reference in New Issue
Block a user