Skip to content

Commit 5e8199d

Browse files
committed
Show strategy plugin load errors in notifications
1 parent 2a711ad commit 5e8199d

4 files changed

Lines changed: 87 additions & 0 deletions

File tree

src/quant_platform_kit/common/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
build_strategy_plugin_alert_key,
7171
build_strategy_plugin_alert_messages,
7272
build_strategy_plugin_alert_scope_note,
73+
build_strategy_plugin_error_notification_lines,
7374
build_strategy_plugin_notification_lines,
7475
build_strategy_plugin_report_payload,
7576
extract_strategy_plugin_localized_message,
@@ -148,6 +149,7 @@
148149
"build_strategy_plugin_alert_key",
149150
"build_strategy_plugin_alert_messages",
150151
"build_strategy_plugin_alert_scope_note",
152+
"build_strategy_plugin_error_notification_lines",
151153
"build_strategy_plugin_notification_lines",
152154
"build_strategy_plugin_report_payload",
153155
"extract_strategy_plugin_localized_message",

src/quant_platform_kit/common/notification_localization.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@
3030
"strategy_plugin_alert_reason_joiner": "、",
3131
"strategy_plugin_alert_reason_none": "未提供明确原因",
3232
"strategy_plugin_line": "🧩 插件:{plugin} | 状态:{route} | 提醒:{action}",
33+
"strategy_plugin_error_line": "⚠️ 插件信号未加载:{reason};本轮使用策略内置规则",
34+
"strategy_plugin_error_reason_JSONDecodeError": "插件配置 JSON 格式错误",
35+
"strategy_plugin_error_reason_FileNotFoundError": "插件信号文件不存在",
36+
"strategy_plugin_error_reason_PermissionError": "插件信号文件权限不足",
37+
"strategy_plugin_error_reason_ValueError": "插件配置校验失败",
38+
"strategy_plugin_error_reason_OSError": "插件信号文件读取失败",
39+
"strategy_plugin_error_reason_default": "插件加载异常(技术详情见执行报告)",
3340
"strategy_plugin_alert_subject": "策略插件提醒:{plugin} | {route}",
3441
"strategy_plugin_alert_title": "【策略插件提醒】",
3542
"strategy_plugin_alert_context": "发布来源:{context}",
@@ -113,6 +120,13 @@
113120
"strategy_plugin_alert_reason_joiner": ", ",
114121
"strategy_plugin_alert_reason_none": "no explicit reason provided",
115122
"strategy_plugin_line": "🧩 Plugin: {plugin} | status: {route} | notice: {action}",
123+
"strategy_plugin_error_line": "⚠️ Plugin signal failed to load: {reason}; this run falls back to built-in strategy rules",
124+
"strategy_plugin_error_reason_JSONDecodeError": "invalid plugin mount JSON",
125+
"strategy_plugin_error_reason_FileNotFoundError": "plugin signal file not found",
126+
"strategy_plugin_error_reason_PermissionError": "plugin signal file permission denied",
127+
"strategy_plugin_error_reason_ValueError": "plugin configuration validation failed",
128+
"strategy_plugin_error_reason_OSError": "plugin signal file read failed",
129+
"strategy_plugin_error_reason_default": "plugin load failed; see execution report diagnostics",
116130
"strategy_plugin_alert_subject": "Strategy plugin notice: {plugin} | {route}",
117131
"strategy_plugin_alert_title": "【Strategy Plugin Notice】",
118132
"strategy_plugin_alert_context": "Source: {context}",

src/quant_platform_kit/common/strategy_plugins.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,65 @@ def build_strategy_plugin_notification_lines(
928928
return tuple(lines)
929929

930930

931+
def _compact_strategy_plugin_error(error: object, *, max_chars: int = 240) -> str:
932+
text = " ".join(str(error or "").split())
933+
if not text:
934+
return ""
935+
if len(text) <= max_chars:
936+
return text
937+
return text[: max_chars - 1].rstrip() + "…"
938+
939+
940+
def _translator_uses_zh(translator: Callable[..., str] | None) -> bool:
941+
if translator is None:
942+
return False
943+
for key in ("strategy_plugin_alert_locale", "no_trades"):
944+
try:
945+
sample = str(translator(key))
946+
except Exception:
947+
continue
948+
if sample.strip().lower().startswith("zh"):
949+
return True
950+
if any("\u4e00" <= char <= "\u9fff" for char in sample):
951+
return True
952+
return False
953+
954+
955+
def _strategy_plugin_error_reason(error: object, *, translator: Callable[..., str] | None = None) -> str:
956+
detail = _compact_strategy_plugin_error(error)
957+
if not detail:
958+
return ""
959+
error_type = detail.split(":", 1)[0].rsplit(".", 1)[-1].strip()
960+
reason_key = f"strategy_plugin_error_reason_{error_type}" if error_type else ""
961+
if translator is not None and reason_key:
962+
translated = translator(reason_key)
963+
if translated != reason_key:
964+
return translated
965+
if _translator_uses_zh(translator):
966+
translated = translator("strategy_plugin_error_reason_default") if translator is not None else ""
967+
if translated and translated != "strategy_plugin_error_reason_default":
968+
return translated
969+
return detail
970+
971+
972+
def build_strategy_plugin_error_notification_lines(
973+
error: object,
974+
*,
975+
translator: Callable[..., str] | None = None,
976+
) -> tuple[str, ...]:
977+
reason = _strategy_plugin_error_reason(error, translator=translator)
978+
if not reason:
979+
return ()
980+
return (
981+
_translate(
982+
translator,
983+
"strategy_plugin_error_line",
984+
fallback="Plugin signal failed to load: {reason}; this run falls back to built-in strategy rules",
985+
reason=reason,
986+
),
987+
)
988+
989+
931990
def should_alert_strategy_plugin_signal(signal: StrategyPluginSignal) -> bool:
932991
route = _normalize_strategy_plugin_field(getattr(signal, "canonical_route", None))
933992
action = _normalize_strategy_plugin_field(getattr(signal, "suggested_action", None))

tests/test_strategy_plugins.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
StrategyPluginDefinition,
2828
attach_strategy_plugin_metadata,
2929
build_strategy_plugin_alert_messages,
30+
build_strategy_plugin_error_notification_lines,
3031
build_strategy_plugin_notification_lines,
3132
build_strategy_plugin_report_payload,
3233
extract_strategy_plugin_localized_message,
@@ -573,6 +574,8 @@ def test_strategy_plugin_notification_lines_use_translator_when_available(self):
573574
signal = validate_strategy_plugin_signal_payload(_signal_payload())
574575
translations = {
575576
"strategy_plugin_line": "plugin={plugin}|mode={mode}|route={route}|action={action}",
577+
"strategy_plugin_error_line": "plugin-error={reason}|fallback=built-in",
578+
"strategy_plugin_error_reason_ValueError": "config validation failed",
576579
"strategy_plugin_name_crisis_response_shadow": "Crisis",
577580
"strategy_plugin_mode_shadow": "shadow",
578581
"strategy_plugin_route_no_action": "no action",
@@ -587,6 +590,15 @@ def test_strategy_plugin_notification_lines_use_translator_when_available(self):
587590
)
588591

589592
self.assertEqual(lines, ("plugin=Crisis|mode=shadow|route=no action|action=watch only",))
593+
self.assertEqual(
594+
build_strategy_plugin_error_notification_lines(
595+
"ValueError: bad config\ntrace line",
596+
translator=lambda key, **kwargs: translations.get(key, key).format(**kwargs)
597+
if kwargs
598+
else translations.get(key, key),
599+
),
600+
("plugin-error=config validation failed|fallback=built-in",),
601+
)
590602

591603
def test_strategy_plugin_notification_lines_can_use_artifact_localized_message(self):
592604
signal = validate_strategy_plugin_signal_payload(

0 commit comments

Comments
 (0)