From 8aa25a9a7274320ac12d13d6699116d11a40eed0 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Tue, 26 May 2026 04:14:06 +0800 Subject: [PATCH] Add crisis alert Telegram channel config --- .github/workflows/sync-cloud-run-env.yml | 13 ++++++++++++ README.md | 4 +++- pyproject.toml | 4 ++-- requirements.txt | 4 ++-- runtime_config_support.py | 24 +++++++++++++++++++++++ tests/test_runtime_config_support.py | 22 +++++++++++++++++++-- tests/test_sync_cloud_run_env_workflow.py | 17 ++++++++++++++++ 7 files changed, 81 insertions(+), 7 deletions(-) diff --git a/.github/workflows/sync-cloud-run-env.yml b/.github/workflows/sync-cloud-run-env.yml index 430f6e5..603f8a4 100644 --- a/.github/workflows/sync-cloud-run-env.yml +++ b/.github/workflows/sync-cloud-run-env.yml @@ -85,6 +85,12 @@ jobs: CRISIS_ALERT_PUSH_PRIORITY: ${{ vars.CRISIS_ALERT_PUSH_PRIORITY }} CRISIS_ALERT_PUSH_TAGS: ${{ vars.CRISIS_ALERT_PUSH_TAGS }} CRISIS_ALERT_PUSH_BODY_MAX_CHARS: ${{ vars.CRISIS_ALERT_PUSH_BODY_MAX_CHARS }} + CRISIS_ALERT_TELEGRAM_CHAT_IDS: ${{ vars.CRISIS_ALERT_TELEGRAM_CHAT_IDS }} + CRISIS_ALERT_TELEGRAM_BOT_TOKEN_SECRET_NAME: ${{ vars.CRISIS_ALERT_TELEGRAM_BOT_TOKEN_SECRET_NAME }} + CRISIS_ALERT_TELEGRAM_API_BASE_URL: ${{ vars.CRISIS_ALERT_TELEGRAM_API_BASE_URL }} + CRISIS_ALERT_TELEGRAM_PARSE_MODE: ${{ vars.CRISIS_ALERT_TELEGRAM_PARSE_MODE }} + CRISIS_ALERT_TELEGRAM_DISABLE_WEB_PAGE_PREVIEW: ${{ vars.CRISIS_ALERT_TELEGRAM_DISABLE_WEB_PAGE_PREVIEW }} + CRISIS_ALERT_TELEGRAM_BODY_MAX_CHARS: ${{ vars.CRISIS_ALERT_TELEGRAM_BODY_MAX_CHARS }} FIRSTRADE_RUNTIME_EXECUTION_WINDOW_TRADING_DAYS: ${{ vars.FIRSTRADE_RUNTIME_EXECUTION_WINDOW_TRADING_DAYS }} FIRSTRADE_TECH_RUNTIME_EXECUTION_WINDOW_TRADING_DAYS: ${{ vars.FIRSTRADE_TECH_RUNTIME_EXECUTION_WINDOW_TRADING_DAYS }} INCOME_THRESHOLD_USD: ${{ vars.INCOME_THRESHOLD_USD }} @@ -97,6 +103,7 @@ jobs: CRISIS_ALERT_SMS_AUTH_TOKEN: ${{ secrets.CRISIS_ALERT_SMS_AUTH_TOKEN }} CRISIS_ALERT_PUSH_APP_TOKEN: ${{ secrets.CRISIS_ALERT_PUSH_APP_TOKEN }} CRISIS_ALERT_PUSH_ACCESS_TOKEN: ${{ secrets.CRISIS_ALERT_PUSH_ACCESS_TOKEN }} + CRISIS_ALERT_TELEGRAM_BOT_TOKEN: ${{ secrets.CRISIS_ALERT_TELEGRAM_BOT_TOKEN }} FIRSTRADE_USERNAME: ${{ secrets.FIRSTRADE_USERNAME }} FIRSTRADE_PASSWORD: ${{ secrets.FIRSTRADE_PASSWORD }} FIRSTRADE_MFA_SECRET: ${{ secrets.FIRSTRADE_MFA_SECRET }} @@ -493,6 +500,11 @@ jobs: add_optional_env CRISIS_ALERT_PUSH_PRIORITY add_optional_env CRISIS_ALERT_PUSH_TAGS add_optional_env CRISIS_ALERT_PUSH_BODY_MAX_CHARS + add_optional_env CRISIS_ALERT_TELEGRAM_CHAT_IDS + add_optional_env CRISIS_ALERT_TELEGRAM_API_BASE_URL + add_optional_env CRISIS_ALERT_TELEGRAM_PARSE_MODE + add_optional_env CRISIS_ALERT_TELEGRAM_DISABLE_WEB_PAGE_PREVIEW + add_optional_env CRISIS_ALERT_TELEGRAM_BODY_MAX_CHARS add_optional_env FIRSTRADE_RUNTIME_EXECUTION_WINDOW_TRADING_DAYS add_optional_env FIRSTRADE_TECH_RUNTIME_EXECUTION_WINDOW_TRADING_DAYS add_optional_env INCOME_THRESHOLD_USD @@ -506,6 +518,7 @@ jobs: add_optional_secret CRISIS_ALERT_SMS_AUTH_TOKEN CRISIS_ALERT_SMS_AUTH_TOKEN_SECRET_NAME CRISIS_ALERT_SMS_AUTH_TOKEN add_optional_secret CRISIS_ALERT_PUSH_APP_TOKEN CRISIS_ALERT_PUSH_APP_TOKEN_SECRET_NAME CRISIS_ALERT_PUSH_APP_TOKEN add_optional_secret CRISIS_ALERT_PUSH_ACCESS_TOKEN CRISIS_ALERT_PUSH_ACCESS_TOKEN_SECRET_NAME CRISIS_ALERT_PUSH_ACCESS_TOKEN + add_optional_secret CRISIS_ALERT_TELEGRAM_BOT_TOKEN CRISIS_ALERT_TELEGRAM_BOT_TOKEN_SECRET_NAME CRISIS_ALERT_TELEGRAM_BOT_TOKEN add_optional_secret FIRSTRADE_USERNAME FIRSTRADE_USERNAME_SECRET_NAME FIRSTRADE_USERNAME add_optional_secret FIRSTRADE_PASSWORD FIRSTRADE_PASSWORD_SECRET_NAME FIRSTRADE_PASSWORD add_optional_secret FIRSTRADE_MFA_SECRET FIRSTRADE_MFA_SECRET_SECRET_NAME FIRSTRADE_MFA_SECRET diff --git a/README.md b/README.md index 1164bd7..0d60d70 100644 --- a/README.md +++ b/README.md @@ -85,13 +85,15 @@ commit credentials. | `TELEGRAM_TOKEN` | Optional | Telegram bot token for strategy-cycle summaries | | `GLOBAL_TELEGRAM_CHAT_ID` | Optional | Telegram chat ID for strategy-cycle summaries | | `FIRSTRADE_STRATEGY_PLUGIN_MOUNTS_JSON` | Optional | JSON sidecar plugin mount config. Overrides global `STRATEGY_PLUGIN_MOUNTS_JSON` for this platform | -| `CRISIS_ALERT_CHANNELS` | Optional | Crisis alert channel list: `email`, `sms`, and/or `push` | +| `CRISIS_ALERT_CHANNELS` | Optional | Crisis alert channel list: `email`, `sms`, `push`, and/or `telegram` | | `CRISIS_ALERT_EMAIL_RECIPIENTS` | Optional | Email-form recipients. Use a normal mailbox for email-only delivery, or a Google Voice-associated mailbox/address to also trigger Google Voice prompts | | `CRISIS_ALERT_EMAIL_SENDER_EMAIL` | Optional | Sender email address used for crisis alert email. Gmail is the default transport, but the sender naming is provider-neutral | | `CRISIS_ALERT_EMAIL_SENDER_PASSWORD` | Optional | Sender SMTP password or app password, preferably supplied from Secret Manager in Cloud Run | | `CRISIS_ALERT_EMAIL_SMTP_HOST` | Optional | SMTP host override. Defaults to Gmail SMTP when unset | | `CRISIS_ALERT_EMAIL_SMTP_PORT` | Optional | SMTP port override. Defaults to `465` when unset | | `CRISIS_ALERT_EMAIL_SMTP_SECURITY` | Optional | SMTP security override: `ssl`, `starttls`, or `none`. Defaults to `ssl` when unset | +| `CRISIS_ALERT_TELEGRAM_CHAT_IDS` | Optional | Dedicated crisis-alert Telegram chat IDs, separate from the strategy-cycle Telegram chat | +| `CRISIS_ALERT_TELEGRAM_BOT_TOKEN` | Optional | Dedicated crisis-alert Telegram bot token. Prefer `CRISIS_ALERT_TELEGRAM_BOT_TOKEN_SECRET_NAME` in env sync | | `FIRSTRADE_COOKIE_DIR` | Optional | Cookie cache directory, default `.runtime/firstrade-cookies` | | `FIRSTRADE_ENABLE_LIVE_TRADING` | Optional | Must be `true` before any live order can be submitted | | `FIRSTRADE_RUN_SMOKE_ON_HTTP` | Optional | Must be `true` before `/smoke` performs a real login/quote | diff --git a/pyproject.toml b/pyproject.toml index fcda82d..fd4ae0f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,8 +14,8 @@ authors = [ ] dependencies = [ "firstrade==0.0.38", - "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@2fdb7c35edf2f39c1592a7dd9319b7516276fabf", - "us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@b626ee3e42feabbf6cd427af6faf5655ca6cd76f", + "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@b520bcaa216e36b94ce60c8c6a13f982de826830", + "us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@df91828c8b521516c85512e136e724064b6b9dac", "google-cloud-storage", "requests", ] diff --git a/requirements.txt b/requirements.txt index 8e948a6..205ec70 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ flask gunicorn firstrade==0.0.38 -quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@2fdb7c35edf2f39c1592a7dd9319b7516276fabf -us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@b626ee3e42feabbf6cd427af6faf5655ca6cd76f +quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@b520bcaa216e36b94ce60c8c6a13f982de826830 +us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@df91828c8b521516c85512e136e724064b6b9dac google-cloud-storage requests pytest diff --git a/runtime_config_support.py b/runtime_config_support.py index aaa0aff..cad1f71 100644 --- a/runtime_config_support.py +++ b/runtime_config_support.py @@ -82,6 +82,12 @@ class PlatformRuntimeSettings: crisis_alert_push_priority: str | None = None crisis_alert_push_tags: str | None = None crisis_alert_push_body_max_chars: str | None = None + crisis_alert_telegram_chat_ids: tuple[str, ...] = () + crisis_alert_telegram_bot_token: str | None = None + crisis_alert_telegram_api_base_url: str | None = None + crisis_alert_telegram_parse_mode: str | None = None + crisis_alert_telegram_disable_web_page_preview: str | None = None + crisis_alert_telegram_body_max_chars: str | None = None runtime_target: RuntimeTarget | None = None @@ -203,6 +209,24 @@ def load_platform_runtime_settings( crisis_alert_push_body_max_chars=_first_non_empty( os.getenv("CRISIS_ALERT_PUSH_BODY_MAX_CHARS") ), + crisis_alert_telegram_chat_ids=_split_env_list( + os.getenv("CRISIS_ALERT_TELEGRAM_CHAT_IDS") + ), + crisis_alert_telegram_bot_token=_first_non_empty( + os.getenv("CRISIS_ALERT_TELEGRAM_BOT_TOKEN") + ), + crisis_alert_telegram_api_base_url=_first_non_empty( + os.getenv("CRISIS_ALERT_TELEGRAM_API_BASE_URL") + ), + crisis_alert_telegram_parse_mode=_first_non_empty( + os.getenv("CRISIS_ALERT_TELEGRAM_PARSE_MODE") + ), + crisis_alert_telegram_disable_web_page_preview=_first_non_empty( + os.getenv("CRISIS_ALERT_TELEGRAM_DISABLE_WEB_PAGE_PREVIEW") + ), + crisis_alert_telegram_body_max_chars=_first_non_empty( + os.getenv("CRISIS_ALERT_TELEGRAM_BODY_MAX_CHARS") + ), runtime_target=runtime_target, ) diff --git a/tests/test_runtime_config_support.py b/tests/test_runtime_config_support.py index 6112017..9eb4a54 100644 --- a/tests/test_runtime_config_support.py +++ b/tests/test_runtime_config_support.py @@ -77,6 +77,12 @@ def test_reserved_cash_policy_defaults_to_zero(monkeypatch): assert settings.crisis_alert_push_priority is None assert settings.crisis_alert_push_tags is None assert settings.crisis_alert_push_body_max_chars is None + assert settings.crisis_alert_telegram_chat_ids == () + assert settings.crisis_alert_telegram_bot_token is None + assert settings.crisis_alert_telegram_api_base_url is None + assert settings.crisis_alert_telegram_parse_mode is None + assert settings.crisis_alert_telegram_disable_web_page_preview is None + assert settings.crisis_alert_telegram_body_max_chars is None def test_reserved_cash_policy_loads_from_env(monkeypatch): @@ -134,7 +140,7 @@ def test_crisis_alert_sms_settings_load_from_env(monkeypatch): def test_crisis_alert_channels_and_push_settings_load_from_env(monkeypatch): monkeypatch.setenv("RUNTIME_TARGET_JSON", _target_json()) - monkeypatch.setenv("CRISIS_ALERT_CHANNELS", "email;push") + monkeypatch.setenv("CRISIS_ALERT_CHANNELS", "email;push;telegram") monkeypatch.setenv("CRISIS_ALERT_PUSH_RECIPIENTS", "risk-topic; backup-topic") monkeypatch.setenv("CRISIS_ALERT_PUSH_PROVIDER", "ntfy") monkeypatch.setenv("CRISIS_ALERT_PUSH_APP_TOKEN", "app-token") @@ -144,10 +150,16 @@ def test_crisis_alert_channels_and_push_settings_load_from_env(monkeypatch): monkeypatch.setenv("CRISIS_ALERT_PUSH_PRIORITY", "5") monkeypatch.setenv("CRISIS_ALERT_PUSH_TAGS", "warning") monkeypatch.setenv("CRISIS_ALERT_PUSH_BODY_MAX_CHARS", "300") + monkeypatch.setenv("CRISIS_ALERT_TELEGRAM_CHAT_IDS", "12345; @risk_channel") + monkeypatch.setenv("CRISIS_ALERT_TELEGRAM_BOT_TOKEN", "telegram-token") + monkeypatch.setenv("CRISIS_ALERT_TELEGRAM_API_BASE_URL", "https://telegram.example.test") + monkeypatch.setenv("CRISIS_ALERT_TELEGRAM_PARSE_MODE", "HTML") + monkeypatch.setenv("CRISIS_ALERT_TELEGRAM_DISABLE_WEB_PAGE_PREVIEW", "false") + monkeypatch.setenv("CRISIS_ALERT_TELEGRAM_BODY_MAX_CHARS", "900") settings = load_platform_runtime_settings(project_id_resolver=lambda: "project-1") - assert settings.crisis_alert_channels == ("email", "push") + assert settings.crisis_alert_channels == ("email", "push", "telegram") assert settings.crisis_alert_push_recipients == ("risk-topic", "backup-topic") assert settings.crisis_alert_push_provider == "ntfy" assert settings.crisis_alert_push_app_token == "app-token" @@ -157,6 +169,12 @@ def test_crisis_alert_channels_and_push_settings_load_from_env(monkeypatch): assert settings.crisis_alert_push_priority == "5" assert settings.crisis_alert_push_tags == "warning" assert settings.crisis_alert_push_body_max_chars == "300" + assert settings.crisis_alert_telegram_chat_ids == ("12345", "@risk_channel") + assert settings.crisis_alert_telegram_bot_token == "telegram-token" + assert settings.crisis_alert_telegram_api_base_url == "https://telegram.example.test" + assert settings.crisis_alert_telegram_parse_mode == "HTML" + assert settings.crisis_alert_telegram_disable_web_page_preview == "false" + assert settings.crisis_alert_telegram_body_max_chars == "900" def test_reserved_cash_ratio_rejects_invalid_env(monkeypatch): diff --git a/tests/test_sync_cloud_run_env_workflow.py b/tests/test_sync_cloud_run_env_workflow.py index 52eb014..5370b28 100644 --- a/tests/test_sync_cloud_run_env_workflow.py +++ b/tests/test_sync_cloud_run_env_workflow.py @@ -28,6 +28,11 @@ def test_sync_cloud_run_env_workflow_syncs_crisis_alert_settings(): "CRISIS_ALERT_PUSH_PRIORITY", "CRISIS_ALERT_PUSH_TAGS", "CRISIS_ALERT_PUSH_BODY_MAX_CHARS", + "CRISIS_ALERT_TELEGRAM_CHAT_IDS", + "CRISIS_ALERT_TELEGRAM_API_BASE_URL", + "CRISIS_ALERT_TELEGRAM_PARSE_MODE", + "CRISIS_ALERT_TELEGRAM_DISABLE_WEB_PAGE_PREVIEW", + "CRISIS_ALERT_TELEGRAM_BODY_MAX_CHARS", ): assert f"{name}: ${{{{ vars.{name} }}}}" in workflow assert f"add_optional_env {name}" in workflow @@ -70,6 +75,18 @@ def test_sync_cloud_run_env_workflow_syncs_crisis_alert_settings(): "add_optional_secret CRISIS_ALERT_PUSH_ACCESS_TOKEN " "CRISIS_ALERT_PUSH_ACCESS_TOKEN_SECRET_NAME CRISIS_ALERT_PUSH_ACCESS_TOKEN" ) in workflow + assert ( + "CRISIS_ALERT_TELEGRAM_BOT_TOKEN_SECRET_NAME: " + "${{ vars.CRISIS_ALERT_TELEGRAM_BOT_TOKEN_SECRET_NAME }}" + ) in workflow + assert ( + "CRISIS_ALERT_TELEGRAM_BOT_TOKEN: " + "${{ secrets.CRISIS_ALERT_TELEGRAM_BOT_TOKEN }}" + ) in workflow + assert ( + "add_optional_secret CRISIS_ALERT_TELEGRAM_BOT_TOKEN " + "CRISIS_ALERT_TELEGRAM_BOT_TOKEN_SECRET_NAME CRISIS_ALERT_TELEGRAM_BOT_TOKEN" + ) in workflow assert '"CRISIS_ALERT_GOOGLE_VOICE_TO"' in workflow assert '"CRISIS_ALERT_GOOGLE_VOICE_GATEWAY"' in workflow assert '"CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER"' in workflow