跳轉到

Schedule Run Telegram Notifications

概述

Cortex scheduler 的排程每次收尾(成功、失敗、等待重試、略過),就發一則 Telegram 訊息到 operator 的私人聊天。功能預設關閉;只有 SCHEDULE_TELEGRAM_ENABLED=true 且設定通過驗證時, 才會連線 Telegram。每日 23:30 的 email 摘要不受影響,見 schedule-summary-mail.md。

scheduler 啟動時會記錄下列三行之一(逐字):

  • Schedule Telegram notifications: enabled
  • Schedule Telegram notifications: disabled
  • Schedule Telegram notifications: configuration invalid: <原因,會指出出問題的變數名稱>

訊息格式

✅ Cortex 排程成功(dev)
排程:daily_flow_institutional
資料源:TWSE、TPEx
結束:2026-10-21 15:42:10(Asia/Taipei)
耗時:3 分 12.4 秒
執行編號:12345
訊息:institutional complete for 2026-10-21

圖示:✅ 成功、❌ 失敗、🔁 等待重試、⏭️ 已略過、ℹ️ 來源端未更新。

當成功/略過 run 的訊息以 來源端未更新: 開頭,標題顯示 ℹ️ Cortex 來源端未更新(dev)(依環境切換)。內容相同仍為成功,來源尚未發布為略過;失敗永遠保留 ❌,不因文字前綴改成資訊提示。每次修訂檢查照常發送,沒有同來源/目標日去重或次數限制。純補跑當日已成功而停止抓取時不建立新來源 run,也不另發來源通知;獨立衍生恢復仍沿用其自身 run 與通知。ETF 混合未發布及完整性警示會同時說明,長期缺口因每日節流未抓取則明確告警,不視為剛確認來源未更新。

「訊息」欄會先把換行等空白壓成單一空格(多行訊息會變成一行),再遮蔽 DSN、Bearer、token= 等內容,最後截到 300 字。已知限制: Authorization: Basic xxx 只遮到 Basic,不涵蓋 secret=。

資料源行

「資料源」列出這次執行有送出請求的外部來源,在排程執行期間記在記憶體裡,不寫資料庫。 記錄時機是送出請求前,所以連線失敗的來源也會列出;失敗細節看「訊息」行。 這次沒連任何外部來源(例如衍生計算、清理、摘要信)就沒有這一行,維持六行。

顯示名稱 記錄條件
TWSE 連到 *.twse.com.tw(主機第一段是 mops 開頭的除外),含 isin.twse.com.tw
TPEx 連到 *.tpex.org.tw
MOPS 連到 mops/mopsfin/mopsov 開頭的 *.twse.com.tw
TDCC 連到 *.tdcc.com.tw
FinMind 連到 *.finmindtrade.com
Yahoo 商品 yf.download、還原價備援 yf.Ticker().history
FRED 連到 *.stlouisfed.org(fredapi 的 API 主機)
Data Hub Data Hub 分頁請求
投信代號 ETF 持股抓取期間連到上面以外的主機,顯示該投信的程式代號(例如 ctbc、yuanta、kgi)
  • 顯示順序固定:TWSE、TPEx、MOPS、TDCC、FinMind、Yahoo、FRED、Data Hub,最後是投信代號(依字母排序)。
  • 判斷依據是實際連到的主機。TPEx 名冊的 ISIN 頁面在 TWSE 的 isin.twse.com.tw,所以名冊同步顯示 TWSE。
  • 經 requests 或 urllib(urlopen)送出的請求,由 scheduler 啟動時安裝的連線紀錄器依網址記錄,只在通知啟用時安裝; Data Hub(網址來自環境變數)與 Yahoo(yfinance 走自己的連線庫與執行緒)在各自的抓取點記錄。
  • 通知啟用時,scheduler 每次收尾另寫一行 log,可用來逐一核對:
docker logs cortex-scheduler 2>&1 | rg "Schedule run data sources"

格式:Schedule run data sources: job=<job_name> run_id=<id> sources=<來源,或「無」>。

已知限制:

  • 記的是「有送出請求」,不代表拿到資料。
  • 經 run_in_executor(不帶 contextvars)的執行緒抓取不會被記錄。目前會通知的排程沒有這種路徑; FRED/yfinance 的 crawl_all_async 變體會走這條,但沒有呼叫者。
  • 同一次執行被收尾兩次且兩次都寫入成功時,第二則通知沒有資料源行。收尾寫入失敗後由例外分支重新收尾的那一則仍有資料源行。

建立 bot 與取得 chat id

  1. 在 Telegram 找 @BotFather,送 /newbot,依指示取得 bot token。
  2. 打開新 bot,按 Start。bot 不能主動私訊沒按過 Start 的人。
  3. 取得 chat id。下列指令(zsh)不會把 token 留在畫面或 shell history:
read -rs "tg_token?Telegram bot token(輸入不會顯示): " && echo && printf 'url = "https://api.telegram.org/bot%s/getUpdates"\n' "$tg_token" | curl -sS --max-time 15 -K - | python3 -c 'import json,sys; [print(v["chat"]["id"], v["chat"].get("type")) for u in json.load(sys.stdin).get("result",[]) for v in u.values() if isinstance(v,dict) and "chat" in v]'; unset tg_token

輸出每行是 <chat id> <type>。只支援 private 的 chat id(正整數),例如 123456789。

Runtime 設定

只有 scheduler 服務會讀這些變數;agent-api 不會收到 TELEGRAM_BOT_TOKEN。

環境變數 必填條件 預設 說明
SCHEDULE_TELEGRAM_ENABLED 永遠 false true/false;空白或未設視為 false;必須明確啟用
TELEGRAM_BOT_TOKEN enabled 空 Secret;不得提交或印入 log
TELEGRAM_CHAT_ID enabled 空 私人聊天的 chat id,正整數
TELEGRAM_TIMEOUT_SECONDS 選填 空(Python 預設 15) 空白即使用預設;預設值只定義在 Python,compose 傳入空字串

Dev 啟用

把設定放在 gitignored 的 investment-agent/.env(不要 commit):

SCHEDULE_TELEGRAM_ENABLED=true
TELEGRAM_BOT_TOKEN=<bot token>
TELEGRAM_CHAT_ID=<private chat id>

重建 scheduler 讓新環境變數生效。docker restart 不會重讀 .env 與 compose,必須用 --force-recreate:

docker compose -f investment-agent/docker-compose.yml up -d --force-recreate scheduler

重建前先確認當天沒有 status='running' 的 update_runs,並避開排程時段。

確認狀態行:

docker logs cortex-scheduler 2>&1 | rg "Schedule Telegram notifications"

優先用上面的啟動狀態行確認設定。不要直接看 docker compose config 的輸出:除了 token,它還會印出 含資料庫密碼的 DATABASE_URL、SMTP_PASSWORD,以及 FRED/Gemini/OpenAI 的 API key。若一定要看, 只用白名單列出三個非機密變數:

docker compose -f investment-agent/docker-compose.yml config scheduler | grep -E '^[[:space:]]+(SCHEDULE_TELEGRAM_ENABLED|TELEGRAM_CHAT_ID|TELEGRAM_TIMEOUT_SECONDS):'

SIT 啟用

SIT CD 不讀主機 .env.sit。在 GitHub Settings → Secrets and variables → Actions 設定:

Secrets:

  • TELEGRAM_BOT_TOKEN

Variables:

  • SCHEDULE_TELEGRAM_ENABLED
  • TELEGRAM_CHAT_ID
  • TELEGRAM_TIMEOUT_SECONDS(可留空)

dev 與 SIT 若共用同一個聊天室,送出間隔各自控制、不互相協調。

會通知的範圍

所有寫入 update_runs 的排程收尾,包含:

  • daily_flow_<flow>
  • twse_calendar_refresh:官方交易日曆更新,14:00–17:50 每 10 分鐘觸發,但只在三種情況寫入 update_runs、才會發訊息:當天第一次確認成交、年度表新增或內容改變、每一次失敗(持續失敗時每 10 分鐘一則)。其餘觸發只記 log。抓取用 httpx 且發生在建立執行紀錄之前,所以訊息沒有「資料源」行。細節見 investment-agent/docs/runbooks/twse-calendar-db.zh-TW.md(不用連結:該檔在 MkDocs 的 docs/ 之外,連結會讓 mkdocs build --strict 失敗)
  • taipei_suspension_check:臺北市停班檢查,每個預定交易日 07:00–09:00 每 10 分鐘觸發;臺北市公告「今天停止上班」就把當天登記為休市。只在三種情況寫入 update_runs、才會發訊息:當天第一次成功判定、這次寫入了休市、每一次失敗(DGPA 整個早上抓不到時最多 13 則)。其餘觸發只記 log。抓取用 httpx 且發生在建立執行紀錄之前,所以訊息沒有「資料源」行。細節見 investment-agent/docs/runbooks/twse-calendar-db.zh-TW.md 第 8 節(不用連結,理由同上)
  • weekly_full_update
  • ohlc_maintenance_window
  • commodity
  • forward_return_update
  • story_strategy_refresh
  • market_surveillance
  • etf_retry
  • access_log_cleanup
  • ticker_integrity
  • oauth_flow_cleanup
  • schedule_summary_mail
  • hub_participant_flows
  • weekly_discovery
  • 動態啟動的 ownership_retry、nightly_chip_chain、daily_derived_* 等

不會通知的情況

  • 條件不符直接 return 的觸發(沒寫 update_runs 紀錄)。
  • daily_flow_margin 同一目標交易日已有成功紀錄時,後續觸發直接跳過,不再抓資料或發通知;首次執行或先前失敗仍會執行。
  • 盤中即時報價輪詢(在另一個容器 cortex-realtime-quote-scheduler)。
  • APScheduler misfire 或 max_instances 擋下的執行。
  • 建立紀錄前就拋出例外。
  • scheduler 當機(紀錄停在 running)。
  • 資料庫連不上。
  • UI/API 手動單檔刷新。
  • task 層級與 sync_status。

會遺失訊息的情況

  • 送出失敗(HTTP 429、逾時、其他 HTTP 錯誤)不重試。
  • scheduler 停止或重建時,佇列中尚未送出的訊息會遺失。

佇列堆積只會讓訊息晚送。送出速率:同一聊天室每則至少間隔 1 秒(Telegram 官方 Bots FAQ)。

預估量

dev 實測(update_runs 2026-10-14~10-20 每日筆數):平日 113~171 則/天、週末 19~31 則; 另外夜間 OHLC 維護每週最多 6 則。

夜間 OHLC 維護

夜間 OHLC 維護現在會寫 ohlc_maintenance_window 執行紀錄,與 Telegram 開關無關。若觸發被延後到 窗口已關,會寫一筆 skipped,當日 email 摘要因此顯示 PARTIAL,屬預期行為。

停用與回滾

dev:把 investment-agent/.env 的 SCHEDULE_TELEGRAM_ENABLED 改為 false,再重建 scheduler:

docker compose -f investment-agent/docker-compose.yml up -d --force-recreate scheduler

SIT:把 GitHub Variable SCHEDULE_TELEGRAM_ENABLED 改為 false,再依 sit-deploy.md 重新部署。

關閉後不建立任何 Telegram 連線。

驗證與診斷

  • 查 scheduler 狀態行:docker logs cortex-scheduler 2>&1 | rg "Schedule Telegram notifications"。
  • 已啟用但設定有誤時,除了啟動時的 configuration invalid 狀態行,之後每個排程收尾都會記錄 Schedule Telegram notification skipped: <原因,會指出出問題的變數名稱>,且不會送出任何訊息。
  • log 中 Telegram 請求網址的 token 會顯示為 bot<redacted>。
  • 送出失敗的 log 只含 HTTP status、error_code 與例外類別名,不含訊息內容或 token。
  • 不要在 cortex-scheduler 容器裡跑 pytest:該容器帶有真正的 Telegram 設定,走到真實收尾流程的 測試可能送出真訊息。測試請用 scripts/run_backend_tests.sh 或在 cortex-api 容器執行。