-
Notifications
You must be signed in to change notification settings - Fork 0
fix: limit plugin telegram alerts to manual review #171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,8 @@ | |
|
|
||
| _DEFAULT_TELEGRAM_BODY_MAX_CHARS = 3900 | ||
| _MARKET_REGIME_CONTROL_PLUGIN = "market_regime_control" | ||
| _AUTOMATED_POSITION_ACTIONS = frozenset({"defend", "delever"}) | ||
| _MANUAL_REVIEW_ACTIONS = frozenset({"notify_manual_review"}) | ||
| _ZH_MARKET_REGIME_ROUTE_LABELS = { | ||
| "true_crisis": "黑天鹅", | ||
| "crisis": "黑天鹅", | ||
|
|
@@ -164,11 +166,15 @@ def publish_strategy_plugin_telegram_alerts( | |
| log_message: Callable[..., Any] = print, | ||
| ) -> StrategyPluginTelegramAlertPublishResult: | ||
| settings = StrategyPluginTelegramSettings.from_object(telegram_settings) | ||
| del context_label # The plugin Telegram bot is a unified manual-review channel. | ||
| manual_review_signals = tuple( | ||
| signal for signal in signals if _is_manual_review_telegram_signal(signal) | ||
| ) | ||
| messages = build_strategy_plugin_alert_messages( | ||
| signals, | ||
| manual_review_signals, | ||
|
Comment on lines
+170
to
+174
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| translator=translator, | ||
| strategy_label=strategy_label, | ||
| context_label=context_label, | ||
| context_label=None, | ||
| alert_namespace="strategy_plugin_telegram_alert", | ||
| ) | ||
| deliveries: list[StrategyPluginTelegramAlertDelivery] = [] | ||
|
|
@@ -210,6 +216,63 @@ def publish_strategy_plugin_telegram_alerts( | |
| return result | ||
|
|
||
|
|
||
| def _is_manual_review_telegram_signal(signal: object) -> bool: | ||
| if _is_auto_consumable_signal(signal): | ||
| return False | ||
| target_type = str(getattr(signal, "target_type", None) or "strategy").strip().lower() | ||
| if target_type == "notification_target": | ||
| return True | ||
| action = str(getattr(signal, "suggested_action", None) or "").strip().lower() | ||
| if action in _MANUAL_REVIEW_ACTIONS: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a guard/data-quality signal arrives with Useful? React with 👍 / 👎. |
||
| return True | ||
| controls = _signal_execution_controls(signal) | ||
| policy = _signal_consumption_policy(signal) | ||
| status = str( | ||
| controls.get("consumption_evidence_status") | ||
| or policy.get("evidence_status") | ||
| or "" | ||
| ).strip().lower() | ||
| if status == "notification_only": | ||
| return True | ||
| for key in ("capital_impact", "notification_profile"): | ||
| value = str(controls.get(key) or policy.get(key) or "").strip().lower() | ||
| if value in {"notification_only", "shadow_only"}: | ||
| return True | ||
| return False | ||
|
|
||
|
|
||
| def _is_auto_consumable_signal(signal: object) -> bool: | ||
| action = str(getattr(signal, "suggested_action", None) or "").strip().lower() | ||
| if action not in _AUTOMATED_POSITION_ACTIONS: | ||
| return False | ||
| controls = _signal_execution_controls(signal) | ||
| policy = _signal_consumption_policy(signal) | ||
| status = str( | ||
| controls.get("consumption_evidence_status") | ||
| or policy.get("evidence_status") | ||
| or "" | ||
| ).strip().lower() | ||
| if status != "automation_approved": | ||
| return False | ||
| return ( | ||
| _coerce_bool(controls.get("position_control_allowed"), default=False) | ||
| or _coerce_bool(policy.get("position_control_allowed"), default=False) | ||
| ) | ||
|
Comment on lines
+257
to
+260
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For Useful? React with 👍 / 👎. |
||
|
|
||
|
|
||
| def _signal_execution_controls(signal: object) -> Mapping[str, Any]: | ||
| controls = getattr(signal, "execution_controls", {}) or {} | ||
| return controls if isinstance(controls, Mapping) else {} | ||
|
|
||
|
|
||
| def _signal_consumption_policy(signal: object) -> Mapping[str, Any]: | ||
| payload = getattr(signal, "payload", {}) or {} | ||
| if not isinstance(payload, Mapping): | ||
| return {} | ||
| policy = payload.get("consumption_policy") or {} | ||
| return policy if isinstance(policy, Mapping) else {} | ||
|
|
||
|
|
||
| def _delivery( | ||
| message: StrategyPluginAlertMessage, | ||
| *, | ||
|
|
@@ -277,6 +340,7 @@ def _build_compact_market_regime_body(message: StrategyPluginAlertMessage) -> st | |
| if use_zh: | ||
| return "\n".join( | ||
| ( | ||
| "统一人工复核信号", | ||
| f"日期:{as_of}", | ||
| f"市场状态:{_market_regime_route_label(route, use_zh=True)}", | ||
| f"背景情况:{background}", | ||
|
|
@@ -285,6 +349,7 @@ def _build_compact_market_regime_body(message: StrategyPluginAlertMessage) -> st | |
| ) | ||
| return "\n".join( | ||
| ( | ||
| "Unified manual-review signal", | ||
| f"Date: {as_of}", | ||
| f"Market state: {_market_regime_route_label(route, use_zh=False)}", | ||
| f"Background: {background}", | ||
|
|
@@ -420,5 +485,3 @@ def _coerce_int(value: Any, default: int) -> int: | |
|
|
||
| def _fallback_alert_key(message: StrategyPluginAlertMessage) -> str: | ||
| return "strategy_plugin_telegram_alert/" + _clean_relative_key(message.subject or "unknown") | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The runtime contract in
docs/strategy_plugin_runtime_contract.md:203-205listswatch_onlyas remaining in the plugin-alert stream, but this new action set only allowsnotify_manual_review. For a strategy-mounted watch signal withcanonical_route='watch',suggested_action='watch_only', and the default controls (nonotification_profile/capital_impact),build_strategy_plugin_alert_messages()would create an alert because the route is notno_action, yet the Telegram prefilter rejects it before the builder runs, so documented watch-only Telegram alerts disappear.Useful? React with 👍 / 👎.