Skip to content

Commit e00263e

Browse files
Pigbibicodex
andauthored
fix: prefix ibkr telegram notifications with account (#287)
Co-authored-by: Codex <noreply@openai.com>
1 parent 0d91eaf commit e00263e

2 files changed

Lines changed: 108 additions & 2 deletions

File tree

main.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,41 @@ def send_tg_message(message):
633633
telegram_chat_id=TG_CHAT_ID,
634634
webhook_url=_NOTIFICATION_WEBHOOK_URL,
635635
)
636-
sender(message)
636+
sender(_with_platform_notification_prefix(message))
637+
638+
639+
def _platform_notification_prefix() -> str:
640+
runtime_target = getattr(RUNTIME_SETTINGS, "runtime_target", None)
641+
account_selector = getattr(runtime_target, "account_selector", None) or ()
642+
for selector in account_selector:
643+
label = _normalize_account_prefix_label(selector)
644+
if label:
645+
return f"[{label}]"
646+
raw_scope = (
647+
getattr(runtime_target, "account_scope", None)
648+
or ACCOUNT_GROUP
649+
)
650+
label = _normalize_account_prefix_label(raw_scope)
651+
if not label:
652+
return ""
653+
return f"[{label}]"
654+
655+
656+
def _normalize_account_prefix_label(value: object) -> str:
657+
label = str(value or "").strip().upper()
658+
if not label or label in {"DEFAULT", "LIVE", "US"}:
659+
return ""
660+
if label.startswith("LIVE-U") and len(label) > len("LIVE-U"):
661+
return label.removeprefix("LIVE-")
662+
return label
663+
664+
665+
def _with_platform_notification_prefix(message: str) -> str:
666+
prefix = _platform_notification_prefix()
667+
text = str(message or "")
668+
if not prefix or text.lstrip().startswith(prefix):
669+
return text
670+
return f"{prefix} {text}"
637671

638672

639673
def publish_notification(*, detailed_text, compact_text):
@@ -696,7 +730,7 @@ def _notify_runtime_error(exc: Exception, *, route_label: str | None = None) ->
696730
message = _runtime_error_notification_message(exc, route_label=route_label)
697731
for token, chat_id in targets:
698732
send_telegram_message(
699-
message,
733+
_with_platform_notification_prefix(message),
700734
token=token,
701735
chat_id=chat_id,
702736
requests_module=requests,

tests/test_request_handling.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import types
23

34
from application.cycle_result import StrategyCycleResult
@@ -29,6 +30,35 @@ def test_health_route_returns_ok(strategy_module):
2930
assert body == "OK"
3031

3132

33+
def test_platform_notification_prefix_stays_empty_for_default_account(strategy_module):
34+
assert strategy_module._platform_notification_prefix() == ""
35+
assert strategy_module._with_platform_notification_prefix("hello") == "hello"
36+
37+
38+
def test_platform_notification_prefix_uses_ibkr_account_scope(strategy_module_factory):
39+
module = strategy_module_factory(
40+
ACCOUNT_GROUP="live-u1234567",
41+
IB_ACCOUNT_GROUP_CONFIG_JSON=(
42+
'{"groups":{"live-u1234567":{"ib_gateway_instance_name":"127.0.0.1",'
43+
'"ib_gateway_mode":"live","ib_client_id":1,"account_ids":["U1234567"]}}}'
44+
),
45+
RUNTIME_TARGET_JSON=json.dumps(
46+
{
47+
"platform_id": "ibkr",
48+
"strategy_profile": "global_etf_rotation",
49+
"dry_run_only": False,
50+
"execution_mode": "live",
51+
"account_selector": ["U1234567"],
52+
"account_scope": "live-u1234567",
53+
},
54+
separators=(",", ":"),
55+
),
56+
)
57+
58+
assert module._platform_notification_prefix() == "[U1234567]"
59+
assert module._with_platform_notification_prefix("hello") == "[U1234567] hello"
60+
61+
3262
def test_handle_request_get_returns_safe_message(strategy_module, monkeypatch):
3363
def fail_if_called():
3464
raise AssertionError("GET should not execute strategy")
@@ -755,3 +785,45 @@ def fake_post(_url, *, json, timeout):
755785
assert "IBKR 策略运行失败" in text
756786
assert "账户组:" in text
757787
assert "错误: RuntimeError: boom" in text
788+
789+
790+
def test_handle_request_runtime_error_fallback_prefixes_account_scope(strategy_module_factory, monkeypatch):
791+
strategy_module = strategy_module_factory(
792+
ACCOUNT_GROUP="live-u1234567",
793+
IB_ACCOUNT_GROUP_CONFIG_JSON=(
794+
'{"groups":{"live-u1234567":{"ib_gateway_instance_name":"127.0.0.1",'
795+
'"ib_gateway_mode":"live","ib_client_id":1,"account_ids":["U1234567"]}}}'
796+
),
797+
RUNTIME_TARGET_JSON=json.dumps(
798+
{
799+
"platform_id": "ibkr",
800+
"strategy_profile": "global_etf_rotation",
801+
"dry_run_only": False,
802+
"execution_mode": "live",
803+
"account_selector": ["U1234567"],
804+
"account_scope": "live-u1234567",
805+
},
806+
separators=(",", ":"),
807+
),
808+
)
809+
sent_payloads = []
810+
811+
class FakeResponse:
812+
status_code = 200
813+
text = "ok"
814+
815+
def fake_post(_url, *, json, timeout):
816+
sent_payloads.append((json, timeout))
817+
return FakeResponse()
818+
819+
monkeypatch.setattr(strategy_module, "_handle_request", lambda: (_ for _ in ()).throw(RuntimeError("boom")))
820+
monkeypatch.setattr(strategy_module, "TG_TOKEN", "token-1")
821+
monkeypatch.setattr(strategy_module, "TG_CHAT_ID", "chat-1")
822+
monkeypatch.setattr(strategy_module.requests, "post", fake_post)
823+
824+
with strategy_module.app.test_request_context("/run", method="POST"):
825+
body, status = strategy_module.handle_request()
826+
827+
assert status == 500
828+
assert body == "Error"
829+
assert sent_payloads[0][0]["text"].startswith("[U1234567] IBKR strategy run failed")

0 commit comments

Comments
 (0)