diff --git a/pyproject.toml b/pyproject.toml index 6938cc1..0ae3ece 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "quant-platform-kit" -version = "0.7.26" +version = "0.7.27" description = "Shared broker adapters, domain models, execution ports, and notification utilities for QuantStrategyLab strategies." readme = "README.md" requires-python = ">=3.9" diff --git a/src/quant_platform_kit/common/strategy_plugins.py b/src/quant_platform_kit/common/strategy_plugins.py index 071c891..17301a9 100644 --- a/src/quant_platform_kit/common/strategy_plugins.py +++ b/src/quant_platform_kit/common/strategy_plugins.py @@ -475,9 +475,18 @@ def build_strategy_plugin_alert_messages( route = getattr(signal, "canonical_route", None) or "unknown_route" action = getattr(signal, "suggested_action", None) or "unknown_action" plugin = translate_strategy_plugin_value("name", getattr(signal, "plugin", None), translator=translator) + translated_mode = translate_strategy_plugin_value( + "mode", + getattr(signal, "effective_mode", None), + translator=translator, + ) translated_route = translate_strategy_plugin_value("route", route, translator=translator) translated_action = translate_strategy_plugin_value("action", action, translator=translator) strategy = str(strategy_label or getattr(signal, "strategy", None) or "").strip() or "unknown" + would_trade = _translate_bool( + translator, + bool(getattr(signal, "would_trade_if_enabled", False)), + ) subject = _translate( translator, "strategy_plugin_alert_subject", @@ -490,6 +499,7 @@ def build_strategy_plugin_alert_messages( subject = f"[{context}] {subject}" body_lines = [ _translate(translator, "strategy_plugin_alert_title", fallback="Strategy Plugin Alert"), + "", ] if context: body_lines.append( @@ -502,33 +512,48 @@ def build_strategy_plugin_alert_messages( ) body_lines.extend( [ - _translate( - translator, - "strategy_plugin_line", - fallback="Plugin: {plugin} | status: {route} | notice: {action}", - plugin=plugin, - mode=translate_strategy_plugin_value("mode", getattr(signal, "effective_mode", None), translator=translator), - route=translated_route, - action=translated_action, - ), - _translate( - translator, - "strategy_plugin_alert_strategy", - fallback="Strategy: {strategy}", - strategy=strategy, - ), - _translate( - translator, - "strategy_plugin_alert_as_of", - fallback="Signal as-of: {as_of}", - as_of=getattr(signal, "as_of", None) or "unknown", - ), - _translate( - translator, - "strategy_plugin_alert_would_trade", - fallback="Would trade if enabled: {value}", - value=str(bool(getattr(signal, "would_trade_if_enabled", False))).lower(), - ), + _translate( + translator, + "strategy_plugin_alert_strategy", + fallback="Strategy: {strategy}", + strategy=strategy, + ), + _translate( + translator, + "strategy_plugin_alert_plugin", + fallback="Plugin: {plugin}", + plugin=plugin, + ), + _translate( + translator, + "strategy_plugin_alert_status", + fallback="Status: {route}", + route=translated_route, + ), + _translate( + translator, + "strategy_plugin_alert_action", + fallback="Notice: {action}", + action=translated_action, + ), + _translate( + translator, + "strategy_plugin_alert_mode", + fallback="Mode: {mode}", + mode=translated_mode, + ), + _translate( + translator, + "strategy_plugin_alert_as_of", + fallback="Signal as-of: {as_of}", + as_of=getattr(signal, "as_of", None) or "unknown", + ), + _translate( + translator, + "strategy_plugin_alert_would_trade", + fallback="Would trade if enabled: {value}", + value=would_trade, + ), ] ) source = getattr(signal, "source_uri", None) or getattr(signal, "local_path", None) @@ -661,6 +686,14 @@ def _translate( return translated if translated != key else fallback.format(**kwargs) +def _translate_bool(translator: Callable[..., str] | None, value: bool) -> str: + return _translate( + translator, + "strategy_plugin_alert_yes" if value else "strategy_plugin_alert_no", + fallback="yes" if value else "no", + ) + + def _required_string(value: Any, *, field_name: str) -> str: text = _optional_string(value) if text is None: diff --git a/tests/test_strategy_plugins.py b/tests/test_strategy_plugins.py index 7c8d2e2..762c3d7 100644 --- a/tests/test_strategy_plugins.py +++ b/tests/test_strategy_plugins.py @@ -274,9 +274,14 @@ def test_strategy_plugin_true_crisis_builds_generic_alert_message(self): "strategy_plugin_alert_title": "alert title", "strategy_plugin_line": "plugin={plugin}|mode={mode}|route={route}|action={action}", "strategy_plugin_alert_strategy": "strategy={strategy}", + "strategy_plugin_alert_plugin": "plugin={plugin}", + "strategy_plugin_alert_status": "status={route}", + "strategy_plugin_alert_action": "action={action}", + "strategy_plugin_alert_mode": "mode={mode}", "strategy_plugin_alert_as_of": "as_of={as_of}", "strategy_plugin_alert_would_trade": "would_trade={value}", "strategy_plugin_alert_source": "source={source}", + "strategy_plugin_alert_yes": "yes", "strategy_plugin_name_crisis_response_shadow": "Crisis", "strategy_plugin_mode_shadow": "shadow", "strategy_plugin_route_true_crisis": "true crisis", @@ -294,7 +299,11 @@ def test_strategy_plugin_true_crisis_builds_generic_alert_message(self): self.assertEqual(len(alerts), 1) self.assertEqual(alerts[0].subject, "alert:TQQQ Growth Income:Crisis:true crisis") self.assertIn("strategy_plugin_alert", alerts[0].alert_key) - self.assertIn("plugin=Crisis|mode=shadow|route=true crisis|action=defend", alerts[0].body) + self.assertIn("plugin=Crisis", alerts[0].body) + self.assertIn("status=true crisis", alerts[0].body) + self.assertIn("action=defend", alerts[0].body) + self.assertIn("mode=shadow", alerts[0].body) + self.assertIn("would_trade=yes", alerts[0].body) self.assertIn("source=gs://bucket/latest_signal.json", alerts[0].body)