Skip to content

Commit 042f72f

Browse files
committed
Localize IBKR notification guard text
1 parent 1c14da6 commit 042f72f

2 files changed

Lines changed: 71 additions & 5 deletions

File tree

application/rebalance_service.py

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,34 @@
1010
)
1111

1212

13+
_ZH_REASON_REPLACEMENTS = (
14+
("feature snapshot guard blocked execution", "特征快照校验阻止执行"),
15+
("feature snapshot required", "需要特征快照"),
16+
("feature snapshot compute failed", "特征快照计算失败"),
17+
("feature_snapshot_download_failed", "特征快照下载失败"),
18+
("feature_snapshot_compute_failed", "特征快照计算失败"),
19+
("feature_snapshot_path_missing", "缺少特征快照路径"),
20+
("feature_snapshot_missing", "特征快照不存在"),
21+
("feature_snapshot_stale", "特征快照过旧"),
22+
("feature_snapshot_manifest_missing", "缺少快照清单"),
23+
("feature_snapshot_profile_mismatch", "快照策略名不匹配"),
24+
("feature_snapshot_config_name_mismatch", "快照配置名不匹配"),
25+
("feature_snapshot_config_path_mismatch", "快照配置路径不匹配"),
26+
("feature_snapshot_contract_version_mismatch", "快照契约版本不匹配"),
27+
("outside_execution_window", "当前不在执行窗口"),
28+
("pending_orders_detected", "检测到未完成订单"),
29+
("same_day_execution_locked", "当日执行锁已存在"),
30+
("same_day_fills_detected", "检测到当日成交"),
31+
("insufficient_buying_power", "购买力不足"),
32+
("missing_price", "缺少报价"),
33+
("no_equity", "无净值"),
34+
("fail_closed", "关闭执行"),
35+
("reason=", "原因="),
36+
("fail_reason=", "失败原因="),
37+
("decision=", "决策="),
38+
)
39+
40+
1341
def _format_text(value, *, fallback: str) -> str:
1442
text = str(value).strip() if value is not None else ""
1543
return text or fallback
@@ -26,6 +54,21 @@ def _format_symbol_preview(symbols, *, limit: int = 3) -> str:
2654
return ",".join(shown)
2755

2856

57+
def _translator_uses_zh(translator) -> bool:
58+
sample = str(translator("no_trades"))
59+
return any("\u4e00" <= ch <= "\u9fff" for ch in sample)
60+
61+
62+
def _localize_notification_text(text: str, *, translator) -> str:
63+
value = str(text or "").strip()
64+
if not value or not _translator_uses_zh(translator):
65+
return value
66+
localized = value
67+
for source, target in _ZH_REASON_REPLACEMENTS:
68+
localized = localized.replace(source, target)
69+
return localized
70+
71+
2972
def _summarize_target_changes(target_vs_current, *, limit: int = 5) -> str | None:
3073
rows = []
3174
for row in target_vs_current or ():
@@ -229,15 +272,17 @@ def build_dashboard(
229272
translator("snapshot_as_of_detail", value=_format_text(snapshot_as_of, fallback="<none>")) if snapshot_as_of else None,
230273
]
231274
diagnostics_text = " | ".join(part for part in diagnostics if part)
275+
localized_status_desc = _localize_notification_text(status_desc, translator=translator)
276+
localized_signal_desc = _localize_notification_text(signal_desc, translator=translator)
232277
return (
233278
f"{translator('equity')}: ${equity:,.2f} | {translator('buying_power')}: ${buying_power:,.2f}\n"
234279
f"{separator}\n"
235280
f"{position_text}\n"
236281
f"{separator}\n"
237282
f"{diagnostics_text}\n"
238283
f"{separator}\n"
239-
f"{status_icon} {status_desc}\n"
240-
f"🎯 {signal_desc}\n"
284+
f"{status_icon} {localized_status_desc}\n"
285+
f"🎯 {localized_signal_desc}\n"
241286
f"{separator}\n"
242287
f"{translator('target_weights_title')}:\n{target_text}"
243288
)
@@ -290,11 +335,11 @@ def run_strategy_core(
290335
fail_reason = signal_metadata.get("fail_reason")
291336
no_op_text = translator("no_trades")
292337
if decision:
293-
no_op_text = f"{no_op_text} | decision={decision}"
338+
no_op_text = f"{no_op_text} | {_localize_notification_text(f'decision={decision}', translator=translator)}"
294339
if no_op_reason:
295-
no_op_text = f"{no_op_text} | reason={no_op_reason}"
340+
no_op_text = f"{no_op_text} | {_localize_notification_text(f'reason={no_op_reason}', translator=translator)}"
296341
if fail_reason:
297-
no_op_text = f"{no_op_text} | fail_reason={fail_reason}"
342+
no_op_text = f"{no_op_text} | {_localize_notification_text(f'fail_reason={fail_reason}', translator=translator)}"
298343
record = build_reconciliation_record(
299344
strategy_profile=signal_metadata.get("strategy_profile"),
300345
mode="dry_run" if signal_metadata.get("dry_run_only") else "paper",

tests/test_rebalance_service.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,27 @@ def test_build_dashboard_localizes_strategy_details():
9191
assert "快照账龄" not in dashboard
9292

9393

94+
def test_build_dashboard_localizes_snapshot_guard_text_for_zh():
95+
dashboard = build_dashboard(
96+
positions={},
97+
account_values={"equity": 1000.0, "buying_power": 500.0},
98+
signal_desc="feature snapshot guard blocked execution",
99+
status_desc="fail_closed | reason=feature_snapshot_path_missing",
100+
strategy_profile="qqq_tech_enhancement",
101+
target_weights={},
102+
signal_metadata={
103+
"allocation": _weight_allocation({}, safe_haven_symbols=("BOXX",)),
104+
},
105+
translator=build_translator("zh"),
106+
separator="---",
107+
strategy_display_name="QQQ 科技增强",
108+
status_icon="🛑",
109+
)
110+
111+
assert "🛑 关闭执行 | 原因=缺少特征快照路径" in dashboard
112+
assert "🎯 特征快照校验阻止执行" in dashboard
113+
114+
94115
def test_run_strategy_core_passes_signal_metadata_to_execution():
95116
observed = {"messages": [], "strategy_symbols": None}
96117

0 commit comments

Comments
 (0)