Skip to content

Commit d2a62f6

Browse files
committed
Add shared strategy plugin notification i18n
1 parent 8faa61a commit d2a62f6

5 files changed

Lines changed: 153 additions & 0 deletions

File tree

docs/strategy_plugin_runtime_contract.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@ Artifacts may include display-only i18n fields:
167167
Platform renderers may use these fields for notification and log text. Trading
168168
logic must continue to read machine fields such as `schema_version`,
169169
`canonical_route`, `suggested_action`, `reason_codes`, and `position_control`.
170+
Shared `strategy_plugin_*` notification labels live in
171+
`quant_platform_kit.common.notification_localization.STRATEGY_PLUGIN_I18N`;
172+
broker platforms can merge them with local text through
173+
`merge_strategy_plugin_i18n()` so the plugin alert wording stays consistent
174+
across runtimes.
170175

171176
`paper`, `advisory`, and `live` plugin modes are not supported by the shared
172177
contract. Platforms should not maintain plugin ledgers or execute plugin-driven

docs/strategy_plugin_runtime_contract.zh-CN.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ artifact 可以包含展示层 i18n 字段:
152152
平台 renderer 可以使用这些字段渲染通知和日志文案。交易逻辑必须继续读取
153153
`schema_version``canonical_route``suggested_action``reason_codes`
154154
`position_control` 等机器字段。
155+
共享的 `strategy_plugin_*` 通知标签由
156+
`quant_platform_kit.common.notification_localization.STRATEGY_PLUGIN_I18N`
157+
维护;broker 平台可以通过 `merge_strategy_plugin_i18n()` 与本地文案合并,
158+
从而保持各运行时的插件告警文案一致。
155159

156160
共享契约不支持 `paper``advisory``live` 插件模式。平台不应从这条
157161
sidecar 路径维护插件账本或执行插件驱动的 allocation 变更。

src/quant_platform_kit/common/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
from .notification_localization import (
44
COMMON_ZH_NOTIFICATION_REPLACEMENTS,
5+
STRATEGY_PLUGIN_I18N,
56
localize_notification_text,
7+
merge_strategy_plugin_i18n,
68
translator_uses_zh,
79
)
810
from .execution_outcomes import (
@@ -113,6 +115,7 @@
113115
"STRATEGY_PLUGIN_ALERT_CHANNEL_SMS",
114116
"STRATEGY_PLUGIN_ALERT_CHANNEL_TELEGRAM",
115117
"STRATEGY_PLUGIN_ALERT_ACTIONS",
118+
"STRATEGY_PLUGIN_I18N",
116119
"STRATEGY_PLUGIN_NON_ALERT_ROUTES",
117120
"STRATEGY_PLUGIN_NOTIFICATION_TARGETS",
118121
"STRATEGY_PLUGIN_SCHEMA_VERSIONS",
@@ -124,6 +127,7 @@
124127
"is_terminal_funding_block",
125128
"is_terminal_strategy_run_stage",
126129
"localize_notification_text",
130+
"merge_strategy_plugin_i18n",
127131
"normalize_skip_reason",
128132
"normalize_stage",
129133
"resolve_strategy_run_stage",

src/quant_platform_kit/common/notification_localization.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,109 @@
2424
"market_data": ("市场数据", "market data"),
2525
}
2626

27+
STRATEGY_PLUGIN_I18N: dict[str, dict[str, str]] = {
28+
"zh": {
29+
"strategy_plugin_line": "🧩 插件:{plugin} | 状态:{route} | 提醒:{action}",
30+
"strategy_plugin_alert_subject": "🚨 策略插件告警:{plugin} | {route}",
31+
"strategy_plugin_alert_title": "🚨 【策略插件告警】",
32+
"strategy_plugin_alert_context": "运行环境:{context}",
33+
"strategy_plugin_alert_strategy": "策略:{strategy}",
34+
"strategy_plugin_alert_plugin": "插件:{plugin}",
35+
"strategy_plugin_alert_status": "状态:{route}",
36+
"strategy_plugin_alert_action": "人工处理建议:{action}",
37+
"strategy_plugin_alert_mode": "模式:{mode}",
38+
"strategy_plugin_alert_as_of": "信号时间:{as_of}",
39+
"strategy_plugin_alert_guidance": "处置建议:{guidance}",
40+
"strategy_plugin_alert_scope_note": "执行范围:{scope_note}",
41+
"strategy_plugin_alert_scope": "仅作人工复核提醒;插件不会自动下单或改仓位",
42+
"strategy_plugin_name_crisis_response_shadow": "危机观察通知",
43+
"strategy_plugin_name_macro_risk_governor": "宏观风险控制通知",
44+
"strategy_plugin_name_market_regime_control": "市场状态控制通知",
45+
"strategy_plugin_name_panic_reversal_shadow": "恐慌反转观察通知",
46+
"strategy_plugin_name_taco_rebound_shadow": "TACO 反弹观察通知",
47+
"strategy_plugin_mode_shadow": "影子观察",
48+
"strategy_plugin_route_blocked": "已阻断",
49+
"strategy_plugin_route_crisis": "危机",
50+
"strategy_plugin_route_delever": "降杠杆",
51+
"strategy_plugin_route_no_action": "未触发",
52+
"strategy_plugin_route_opportunity_watch": "机会观察",
53+
"strategy_plugin_route_panic_reversal": "恐慌反转",
54+
"strategy_plugin_route_risk_off": "风险关闭",
55+
"strategy_plugin_route_risk_reduced": "风险降低",
56+
"strategy_plugin_route_true_crisis": "真危机",
57+
"strategy_plugin_route_taco_rebound": "TACO 反弹确认",
58+
"strategy_plugin_route_unknown_route": "未知状态",
59+
"strategy_plugin_route_watch": "观察",
60+
"strategy_plugin_action_no_action": "不操作",
61+
"strategy_plugin_action_watch_only": "仅通知",
62+
"strategy_plugin_action_notify_manual_review": "通知人工复核",
63+
"strategy_plugin_action_defend": "防守",
64+
"strategy_plugin_action_delever": "降杠杆",
65+
"strategy_plugin_action_blocked": "已阻断",
66+
"strategy_plugin_action_monitor": "持续观察",
67+
"strategy_plugin_action_unknown_action": "未知提醒",
68+
"strategy_plugin_guidance_crisis_response_shadow_true_crisis_defend": "优先考虑降低杠杆或清理杠杆仓位,暂停加仓;如需保留风险敞口,先降到可承受的小仓位。",
69+
"strategy_plugin_guidance_crisis_response_shadow_no_action_blocked": "危机路线被风控阻断;先核对数据新鲜度和外部情境,不建议仅凭此条加仓。",
70+
"strategy_plugin_guidance_macro_risk_governor_delever_delever": "宏观风险控制建议降低杠杆敞口;是否执行由策略侧可回测规则和仓位适配器决定。",
71+
"strategy_plugin_guidance_macro_risk_governor_crisis_defend": "宏观危机信号建议风险仓位转向防守或现金类资产,直到压力缓和。",
72+
"strategy_plugin_guidance_market_regime_control_risk_off_defend": "市场状态控制进入风险关闭;机会类信号先不执行,风险仓位应保持防守。",
73+
"strategy_plugin_guidance_market_regime_control_risk_reduced_delever": "市场状态控制建议降杠杆;自动仓位调整只按策略侧已批准的可回测规则执行。",
74+
"strategy_plugin_guidance_market_regime_control_opportunity_watch_notify_manual_review": "仅作人工复核:市场状态允许有限机会观察,但插件本身不会下单或直接改仓位。",
75+
"strategy_plugin_guidance_market_regime_control_blocked_blocked": "市场状态控制被数据质量或新鲜度保护阻断;先核对数据源和产物,再决定是否人工处理。",
76+
"strategy_plugin_guidance_taco_rebound_shadow_taco_rebound_notify_manual_review": "TACO 仅提示可能的反弹窗口;可考虑小仓位、分批、预设止损/失效条件的人工博弈,不建议一次性满仓。",
77+
},
78+
"en": {
79+
"strategy_plugin_line": "🧩 Plugin: {plugin} | status: {route} | notice: {action}",
80+
"strategy_plugin_alert_subject": "🚨 Strategy plugin alert: {plugin} | {route}",
81+
"strategy_plugin_alert_title": "🚨 【Strategy Plugin Alert】",
82+
"strategy_plugin_alert_context": "Context: {context}",
83+
"strategy_plugin_alert_strategy": "Strategy: {strategy}",
84+
"strategy_plugin_alert_plugin": "Plugin: {plugin}",
85+
"strategy_plugin_alert_status": "Status: {route}",
86+
"strategy_plugin_alert_action": "Notice: {action}",
87+
"strategy_plugin_alert_mode": "Mode: {mode}",
88+
"strategy_plugin_alert_as_of": "Signal as-of: {as_of}",
89+
"strategy_plugin_alert_guidance": "Manual guidance: {guidance}",
90+
"strategy_plugin_alert_scope_note": "Execution scope: {scope_note}",
91+
"strategy_plugin_alert_scope": "Manual review notice only; the plugin does not place orders or change allocations",
92+
"strategy_plugin_name_crisis_response_shadow": "Crisis Watch Notice",
93+
"strategy_plugin_name_macro_risk_governor": "Macro Risk Governor Notice",
94+
"strategy_plugin_name_market_regime_control": "Market Regime Control Notice",
95+
"strategy_plugin_name_panic_reversal_shadow": "Panic Reversal Watch Notice",
96+
"strategy_plugin_name_taco_rebound_shadow": "TACO Rebound Watch Notice",
97+
"strategy_plugin_mode_shadow": "shadow",
98+
"strategy_plugin_route_blocked": "blocked",
99+
"strategy_plugin_route_crisis": "crisis",
100+
"strategy_plugin_route_delever": "de-lever",
101+
"strategy_plugin_route_no_action": "no alert",
102+
"strategy_plugin_route_opportunity_watch": "opportunity watch",
103+
"strategy_plugin_route_panic_reversal": "panic reversal",
104+
"strategy_plugin_route_risk_off": "risk off",
105+
"strategy_plugin_route_risk_reduced": "risk reduced",
106+
"strategy_plugin_route_true_crisis": "true crisis",
107+
"strategy_plugin_route_taco_rebound": "TACO rebound confirmed",
108+
"strategy_plugin_route_unknown_route": "unknown status",
109+
"strategy_plugin_route_watch": "watch",
110+
"strategy_plugin_action_no_action": "no action",
111+
"strategy_plugin_action_watch_only": "notify only",
112+
"strategy_plugin_action_notify_manual_review": "notify manual review",
113+
"strategy_plugin_action_defend": "defend",
114+
"strategy_plugin_action_delever": "de-lever",
115+
"strategy_plugin_action_blocked": "blocked",
116+
"strategy_plugin_action_monitor": "watch",
117+
"strategy_plugin_action_unknown_action": "unknown notice",
118+
"strategy_plugin_guidance_crisis_response_shadow_true_crisis_defend": "Consider reducing or clearing leveraged exposure, then pause new risk additions; if keeping exposure, resize it to a small amount you can tolerate.",
119+
"strategy_plugin_guidance_crisis_response_shadow_no_action_blocked": "A guard blocked the crisis route; verify data freshness and external context before acting on this alert.",
120+
"strategy_plugin_guidance_macro_risk_governor_delever_delever": "The macro risk governor suggests reducing leveraged exposure; execution is controlled by strategy-side backtestable rules and position adapters.",
121+
"strategy_plugin_guidance_macro_risk_governor_crisis_defend": "The macro crisis signal suggests moving the risk sleeve toward defensive or cash-like exposure until stress de-escalates.",
122+
"strategy_plugin_guidance_market_regime_control_risk_off_defend": "Market regime control is risk-off; opportunity signals should stay blocked and risk exposure should remain defensive.",
123+
"strategy_plugin_guidance_market_regime_control_risk_reduced_delever": "Market regime control suggests de-levering; automatic position changes only follow strategy-side approved, backtestable rules.",
124+
"strategy_plugin_guidance_market_regime_control_opportunity_watch_notify_manual_review": "Manual review only: the market regime allows bounded opportunity watch, but the plugin does not place orders or directly change allocations.",
125+
"strategy_plugin_guidance_market_regime_control_blocked_blocked": "Market regime control was blocked by data-quality or freshness guards; verify source data and artifacts before manual action.",
126+
"strategy_plugin_guidance_taco_rebound_shadow_taco_rebound_notify_manual_review": "TACO only flags a possible rebound window; consider a small staged manual probe with a predefined invalidation level instead of full-size exposure.",
127+
},
128+
}
129+
27130

28131
COMMON_ZH_NOTIFICATION_REPLACEMENTS: tuple[NotificationReplacement, ...] = (
29132
("feature snapshot guard blocked execution", "特征快照校验阻止执行"),
@@ -97,6 +200,21 @@
97200
)
98201

99202

203+
def merge_strategy_plugin_i18n(
204+
i18n: dict[str, dict[str, str]],
205+
*,
206+
shared_wins: bool = True,
207+
) -> dict[str, dict[str, str]]:
208+
merged = {str(locale): dict(values) for locale, values in i18n.items()}
209+
for locale, shared_values in STRATEGY_PLUGIN_I18N.items():
210+
existing = merged.setdefault(locale, {})
211+
if shared_wins:
212+
existing.update(shared_values)
213+
else:
214+
merged[locale] = {**shared_values, **existing}
215+
return merged
216+
217+
100218
def translator_uses_zh(translator: NotificationTranslator) -> bool:
101219
sample = str(translator("no_trades"))
102220
return any("\u4e00" <= ch <= "\u9fff" for ch in sample)

tests/test_notification_localization.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
from quant_platform_kit.common.notification_localization import (
66
COMMON_ZH_NOTIFICATION_REPLACEMENTS,
7+
STRATEGY_PLUGIN_I18N,
78
localize_price_source_label,
89
localize_notification_text,
10+
merge_strategy_plugin_i18n,
911
translator_uses_zh,
1012
)
1113

@@ -73,6 +75,26 @@ def test_localize_price_source_label_supports_broker_sources(self):
7375
"LongBridge daily candlesticks",
7476
)
7577

78+
def test_strategy_plugin_i18n_has_matching_locale_keys(self):
79+
self.assertEqual(set(STRATEGY_PLUGIN_I18N["zh"]), set(STRATEGY_PLUGIN_I18N["en"]))
80+
self.assertIn("strategy_plugin_name_taco_rebound_shadow", STRATEGY_PLUGIN_I18N["zh"])
81+
self.assertEqual(STRATEGY_PLUGIN_I18N["zh"]["strategy_plugin_name_taco_rebound_shadow"], "TACO 反弹观察通知")
82+
83+
def test_merge_strategy_plugin_i18n_fills_and_overrides_shared_keys(self):
84+
merged = merge_strategy_plugin_i18n(
85+
{
86+
"zh": {
87+
"no_trades": "无需调仓",
88+
"strategy_plugin_name_taco_rebound_shadow": "TACO 旧观察通知",
89+
},
90+
"en": {"no_trades": "No trades"},
91+
}
92+
)
93+
94+
self.assertEqual(merged["zh"]["no_trades"], "无需调仓")
95+
self.assertEqual(merged["zh"]["strategy_plugin_name_taco_rebound_shadow"], "TACO 反弹观察通知")
96+
self.assertEqual(merged["en"]["strategy_plugin_route_watch"], "watch")
97+
7698

7799
if __name__ == "__main__":
78100
unittest.main()

0 commit comments

Comments
 (0)