diff --git a/main.py b/main.py index e3db73d1..bf73baf4 100644 --- a/main.py +++ b/main.py @@ -449,7 +449,7 @@ def _runtime_error_notification_message(exc): def _notify_runtime_error(exc): token = os.getenv("TG_TOKEN", "") - chat_id = os.getenv("GLOBAL_TELEGRAM_CHAT_ID", "") + chat_id = os.getenv("QSL_GLOBAL_TELEGRAM_CHAT_ID") or os.getenv("GLOBAL_TELEGRAM_CHAT_ID", "") if not token or not chat_id: print("Binance runtime error notification skipped: no Telegram target configured.") return False diff --git a/pyproject.toml b/pyproject.toml index 21958fc7..56bf47f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ version = "0.1.0" description = "QuantStrategyLab platform layer for Binance exchange." requires-python = ">=3.11" dependencies = [ - "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@3acab1923a97b805b077c85c6c19657be0143bac", + "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@9f7e7f8335e97f83f66677ad5e73254a1a421759", "crypto-strategies @ git+https://github.com/QuantStrategyLab/CryptoStrategies.git@ef78312d7653095f585c4f75d45bf765bedc2751", "python-binance", "pandas", @@ -23,7 +23,7 @@ test = [ [tool.uv] override-dependencies = [ - "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@3acab1923a97b805b077c85c6c19657be0143bac", + "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@9f7e7f8335e97f83f66677ad5e73254a1a421759", ] [tool.ruff] diff --git a/qsl.toml b/qsl.toml index d06aae4e..b0dd7746 100644 --- a/qsl.toml +++ b/qsl.toml @@ -9,7 +9,7 @@ expires_at = "2026-09-30" next_action = "keep uv.lock current and maintain QPK/CryptoStrategies pin consistency" [qsl.requires] -quant_platform_kit = "3acab1923a97b805b077c85c6c19657be0143bac" +quant_platform_kit = "9f7e7f8335e97f83f66677ad5e73254a1a421759" crypto_strategies = "ef78312d7653095f585c4f75d45bf765bedc2751" [qsl.compat] diff --git a/runtime_config_support.py b/runtime_config_support.py index 39d56186..6325d70c 100644 --- a/runtime_config_support.py +++ b/runtime_config_support.py @@ -142,7 +142,7 @@ def build_live_runtime( api_key=os.getenv("BINANCE_API_KEY", ""), api_secret=os.getenv("BINANCE_API_SECRET", ""), tg_token=os.getenv("TG_TOKEN", ""), - tg_chat_id=os.getenv("GLOBAL_TELEGRAM_CHAT_ID", ""), + tg_chat_id=os.getenv("QSL_GLOBAL_TELEGRAM_CHAT_ID") or os.getenv("GLOBAL_TELEGRAM_CHAT_ID", ""), state_loader=state_loader, state_writer=state_writer, notifier=notifier, diff --git a/scripts/runtime_workflow_heartbeat.py b/scripts/runtime_workflow_heartbeat.py index 467cba33..7e795ce8 100644 --- a/scripts/runtime_workflow_heartbeat.py +++ b/scripts/runtime_workflow_heartbeat.py @@ -328,7 +328,7 @@ def _assess_runtime_heartbeat( def _send_telegram(message: str) -> bool: token = os.environ.get("TG_TOKEN") or os.environ.get("TELEGRAM_TOKEN") - chats = _split_values(os.environ.get("GLOBAL_TELEGRAM_CHAT_ID")) + chats = _split_values(os.environ.get("QSL_GLOBAL_TELEGRAM_CHAT_ID") or os.environ.get("GLOBAL_TELEGRAM_CHAT_ID")) if not token or not chats: print("Telegram heartbeat notification skipped: target is not configured.", file=sys.stderr) return False diff --git a/tests/test_main_runtime_error_notification.py b/tests/test_main_runtime_error_notification.py index 3b05b306..82291f8f 100644 --- a/tests/test_main_runtime_error_notification.py +++ b/tests/test_main_runtime_error_notification.py @@ -163,6 +163,28 @@ def test_runtime_error_notification_requires_transport_acknowledgement(self): ): self.assertFalse(main._notify_runtime_error(RuntimeError("boom"))) + def test_runtime_error_notification_prefers_qsl_global_telegram_chat_id(self): + observed = {} + + def fake_send_tg_msg(token, chat_id, _text): + observed["token"] = token + observed["chat_id"] = chat_id + return True + + with patch.dict( + os.environ, + { + "TG_TOKEN": "token-1", + "QSL_GLOBAL_TELEGRAM_CHAT_ID": "qsl-chat-id", + "GLOBAL_TELEGRAM_CHAT_ID": "legacy-chat-id", + }, + clear=False, + ): + with patch.object(main, "send_tg_msg", fake_send_tg_msg): + self.assertTrue(main._notify_runtime_error(RuntimeError("boom"))) + + self.assertEqual(observed, {"token": "token-1", "chat_id": "qsl-chat-id"}) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_runtime_config_support.py b/tests/test_runtime_config_support.py index 4213b8e5..2c245b80 100644 --- a/tests/test_runtime_config_support.py +++ b/tests/test_runtime_config_support.py @@ -147,6 +147,22 @@ def test_build_live_runtime_uses_global_telegram_chat_id(self): self.assertEqual(runtime.tg_chat_id, "shared-chat-id") + def test_build_live_runtime_prefers_qsl_global_telegram_chat_id(self): + with patch.dict( + os.environ, + { + "BINANCE_API_KEY": "api-key", + "BINANCE_API_SECRET": "api-secret", + "TG_TOKEN": "tg-token", + "QSL_GLOBAL_TELEGRAM_CHAT_ID": "qsl-chat-id", + "GLOBAL_TELEGRAM_CHAT_ID": "legacy-chat-id", + }, + clear=False, + ): + runtime = build_live_runtime() + + self.assertEqual(runtime.tg_chat_id, "qsl-chat-id") + def test_build_live_runtime_uses_runtime_target_json(self): runtime_target = { "platform_id": "binance", diff --git a/tests/test_runtime_workflow_heartbeat.py b/tests/test_runtime_workflow_heartbeat.py index cc3701d6..ec7f96f9 100644 --- a/tests/test_runtime_workflow_heartbeat.py +++ b/tests/test_runtime_workflow_heartbeat.py @@ -230,6 +230,41 @@ def read(self): with patch.object(heartbeat.urllib.request, "urlopen", return_value=FakeResponse()): self.assertFalse(heartbeat._send_telegram("heartbeat failed")) + def test_send_telegram_prefers_qsl_global_telegram_chat_id(self) -> None: + class FakeResponse: + status = 200 + + def __enter__(self): + return self + + def __exit__(self, *_args): + return None + + def read(self): + return b'{"ok":true}' + + observed = {} + + def fake_urlopen(request, timeout): + observed["body"] = request.data.decode("utf-8") + observed["timeout"] = timeout + return FakeResponse() + + with patch.dict( + os.environ, + { + "TG_TOKEN": "token-1", + "QSL_GLOBAL_TELEGRAM_CHAT_ID": "qsl-chat-id", + "GLOBAL_TELEGRAM_CHAT_ID": "legacy-chat-id", + }, + clear=True, + ): + with patch.object(heartbeat.urllib.request, "urlopen", side_effect=fake_urlopen): + self.assertTrue(heartbeat._send_telegram("heartbeat failed")) + + self.assertIn("chat_id=qsl-chat-id", observed["body"]) + self.assertNotIn("legacy-chat-id", observed["body"]) + if __name__ == "__main__": unittest.main() diff --git a/uv.lock b/uv.lock index a8d7f6c9..8d3970ff 100644 --- a/uv.lock +++ b/uv.lock @@ -17,7 +17,7 @@ resolution-markers = [ ] [manifest] -overrides = [{ name = "quant-platform-kit", git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=3acab1923a97b805b077c85c6c19657be0143bac" }] +overrides = [{ name = "quant-platform-kit", git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=9f7e7f8335e97f83f66677ad5e73254a1a421759" }] [[package]] name = "aiohappyeyeballs" @@ -214,7 +214,7 @@ requires-dist = [ { name = "pandas" }, { name = "pytest", marker = "extra == 'test'", specifier = ">=8" }, { name = "python-binance" }, - { name = "quant-platform-kit", git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=3acab1923a97b805b077c85c6c19657be0143bac" }, + { name = "quant-platform-kit", git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=9f7e7f8335e97f83f66677ad5e73254a1a421759" }, { name = "requests" }, { name = "ruff", marker = "extra == 'test'", specifier = ">=0.12" }, ] @@ -1617,7 +1617,7 @@ wheels = [ [[package]] name = "quant-platform-kit" version = "0.10.0" -source = { git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=3acab1923a97b805b077c85c6c19657be0143bac#3acab1923a97b805b077c85c6c19657be0143bac" } +source = { git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=9f7e7f8335e97f83f66677ad5e73254a1a421759#9f7e7f8335e97f83f66677ad5e73254a1a421759" } [[package]] name = "regex"