From 686fa028d3754bc400ffac9782bd75ee70ae6a93 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:09:20 +0800 Subject: [PATCH] Show plugin load errors in strategy notifications --- application/runtime_composer.py | 15 +++++++++++++-- application/runtime_strategy_adapters.py | 4 ++++ main.py | 5 ++++- requirements.txt | 6 +++--- tests/test_request_handling.py | 4 ++-- tests/test_runtime_composer.py | 9 ++++++++- tests/test_runtime_strategy_adapters.py | 5 +++++ 7 files changed, 39 insertions(+), 9 deletions(-) diff --git a/application/runtime_composer.py b/application/runtime_composer.py index a943bb7..9262f20 100644 --- a/application/runtime_composer.py +++ b/application/runtime_composer.py @@ -191,7 +191,12 @@ def build_rebalance_runtime( post_submit_order=notification_adapters.post_submit_order, ) - def build_rebalance_config(self, *, strategy_plugin_signals=()) -> LongBridgeRebalanceConfig: + def build_rebalance_config( + self, + *, + strategy_plugin_signals=(), + strategy_plugin_error: str | None = None, + ) -> LongBridgeRebalanceConfig: market_scope_line = self.translator( "market_scope_detail", market=self.market, @@ -204,6 +209,12 @@ def build_rebalance_config(self, *, strategy_plugin_signals=()) -> LongBridgeReb lambda _signals: (), ) plugin_lines = tuple(build_plugin_lines(tuple(strategy_plugin_signals or ()))) + build_plugin_error_lines = getattr( + self.strategy_adapters, + "build_strategy_plugin_error_notification_lines", + lambda _error: (), + ) + plugin_error_lines = tuple(build_plugin_error_lines(strategy_plugin_error)) return LongBridgeRebalanceConfig( limit_sell_discount=self.limit_sell_discount, limit_buy_premium=self.limit_buy_premium, @@ -219,7 +230,7 @@ def build_rebalance_config(self, *, strategy_plugin_signals=()) -> LongBridgeReb min_order_notional_usd=self.min_order_notional_usd, safe_haven_cash_substitute_threshold_usd=self.safe_haven_cash_substitute_threshold_usd, sleeper=self.sleeper, - extra_notification_lines=(market_scope_line, *plugin_lines), + extra_notification_lines=(market_scope_line, *plugin_lines, *plugin_error_lines), strategy_plugin_signals=tuple(strategy_plugin_signals or ()), execution_dedup_enabled=resolve_execution_dedup_enabled( env_reader=self.env_reader, diff --git a/application/runtime_strategy_adapters.py b/application/runtime_strategy_adapters.py index b3f8664..938d08c 100644 --- a/application/runtime_strategy_adapters.py +++ b/application/runtime_strategy_adapters.py @@ -8,6 +8,7 @@ from quant_platform_kit.common.strategy_plugins import ( build_strategy_plugin_alert_messages, + build_strategy_plugin_error_notification_lines, build_strategy_plugin_notification_lines, should_alert_strategy_plugin_signal, translate_strategy_plugin_value, @@ -89,6 +90,9 @@ def translate_strategy_plugin_value(self, category: str, raw_value: str | None) def build_strategy_plugin_notification_lines(self, signals) -> tuple[str, ...]: return build_strategy_plugin_notification_lines(signals, translator=self.translator) + def build_strategy_plugin_error_notification_lines(self, error) -> tuple[str, ...]: + return build_strategy_plugin_error_notification_lines(error, translator=self.translator) + def should_alert_strategy_plugin_signal(self, signal) -> bool: return should_alert_strategy_plugin_signal(signal) diff --git a/main.py b/main.py index b718725..7a71a6c 100644 --- a/main.py +++ b/main.py @@ -502,7 +502,10 @@ def run_strategy(*, force_run: bool = False, validation_only: bool = False, vali ) cycle_result = run_rebalance_cycle( runtime=rebalance_runtime, - config=composer.build_rebalance_config(strategy_plugin_signals=strategy_plugin_signals), + config=composer.build_rebalance_config( + strategy_plugin_signals=strategy_plugin_signals, + strategy_plugin_error=strategy_plugin_error, + ), ) signal_snapshot = {} if cycle_result is not None: diff --git a/requirements.txt b/requirements.txt index 133edb8..ccb1538 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ flask gunicorn -quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@2a711adf60b585ca02932bab9ee1bac7ce1df7c6 -us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@fdd39ef0313181bee9083319b87b7175c32b364d -hk-equity-strategies @ git+https://github.com/QuantStrategyLab/HkEquityStrategies.git@02af62bc7af7b8ffdbe8575421434e455ab00d66 +quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@1a31ddde0dde0f2ad423e841a84af3ba0869e612 +us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@0ad01faaa2741195d06ecf8dae6a0f3fda712080 +hk-equity-strategies @ git+https://github.com/QuantStrategyLab/HkEquityStrategies.git@1c50d2fcedc41b36d387366d0a9209ee759f0308 pandas requests pytz diff --git a/tests/test_request_handling.py b/tests/test_request_handling.py index 0f200a4..c6582ae 100644 --- a/tests/test_request_handling.py +++ b/tests/test_request_handling.py @@ -489,7 +489,7 @@ def with_prefix(self, message): def build_rebalance_runtime(self, *, silent_cycle_notifications=False): return types.SimpleNamespace() - def build_rebalance_config(self, *, strategy_plugin_signals=()): + def build_rebalance_config(self, *, strategy_plugin_signals=(), strategy_plugin_error=None): return types.SimpleNamespace() module.build_composer = lambda *, dry_run_only_override=None: FakeComposer() @@ -555,7 +555,7 @@ def build_rebalance_runtime(self, *, silent_cycle_notifications=False): observed["silent_cycle_notifications"] = silent_cycle_notifications return types.SimpleNamespace() - def build_rebalance_config(self, *, strategy_plugin_signals=()): + def build_rebalance_config(self, *, strategy_plugin_signals=(), strategy_plugin_error=None): return types.SimpleNamespace() module.build_composer = lambda *, dry_run_only_override=None: observed.__setitem__("override", dry_run_only_override) or FakeComposer() diff --git a/tests/test_runtime_composer.py b/tests/test_runtime_composer.py index 5a6225a..e5565d3 100644 --- a/tests/test_runtime_composer.py +++ b/tests/test_runtime_composer.py @@ -70,6 +70,8 @@ def fake_bootstrap_builder(**kwargs): strategy_adapters=SimpleNamespace( calculate_strategy_indicators="strategy-indicators", resolve_rebalance_plan="resolve-plan", + build_strategy_plugin_notification_lines=lambda signals: tuple(signals), + build_strategy_plugin_error_notification_lines=lambda error: (f"plugin-error:{error}",) if error else (), ), estimate_max_purchase_quantity_fn="estimate-max-purchase", fetch_order_status_fn="fetch-order-status", @@ -106,7 +108,10 @@ def fake_bootstrap_builder(**kwargs): reporting_adapters = composer.build_reporting_adapters() runtime = composer.build_rebalance_runtime() silent_runtime = composer.build_rebalance_runtime(silent_cycle_notifications=True) - config = composer.build_rebalance_config() + config = composer.build_rebalance_config( + strategy_plugin_signals=("plugin-line",), + strategy_plugin_error="bad config", + ) assert notification_adapters.notification_port == "notification-port" assert reporting_adapters == "reporting-adapters" @@ -118,6 +123,8 @@ def fake_bootstrap_builder(**kwargs): assert observed["reporting_builder"]["runtime_assembly"].runtime_target.platform_id == "longbridge" assert observed["reporting_builder"]["runtime_assembly"].runtime_target.strategy_profile == "soxl_soxx_trend_income" assert observed["reporting_builder"]["runtime_assembly"].runtime_target.execution_mode == "paper" + assert "plugin-line" in config.extra_notification_lines + assert "plugin-error:bad config" in config.extra_notification_lines assert observed["bootstrap_builder"]["secret_name"] == "secret-1" assert observed["bootstrap_builder"]["calculate_strategy_indicators_fn"] == "strategy-indicators" assert runtime.bootstrap == "bootstrap" diff --git a/tests/test_runtime_strategy_adapters.py b/tests/test_runtime_strategy_adapters.py index f60949b..b46356e 100644 --- a/tests/test_runtime_strategy_adapters.py +++ b/tests/test_runtime_strategy_adapters.py @@ -275,6 +275,8 @@ def fake_load(mounts, *, strategy_profile): signal_text_fn=lambda icon: f"signal:{icon}", translator=lambda key, **kwargs: { "strategy_plugin_line": "plugin={plugin}|mode={mode}|route={route}|action={action}", + "strategy_plugin_error_line": "plugin-error={reason}|fallback=built-in", + "strategy_plugin_error_reason_ValueError": "config validation failed", "strategy_plugin_name_crisis_response_shadow": "Crisis", "strategy_plugin_mode_shadow": "shadow", "strategy_plugin_route_no_action": "no action", @@ -301,6 +303,9 @@ def fake_load(mounts, *, strategy_profile): assert adapters.build_strategy_plugin_notification_lines(signals) == ( "plugin=Crisis|mode=shadow|route=no action|action=monitor", ) + assert adapters.build_strategy_plugin_error_notification_lines("ValueError: bad config") == ( + "plugin-error=config validation failed|fallback=built-in", + ) assert adapters.build_strategy_plugin_alert_messages(signals) == ()