Schedule Run Telegram Notifications
概述
Cortex scheduler 的排程每次收尾(成功、失敗、等待重試、略過),就發一則 Telegram 訊息到
operator 的私人聊天。功能預設關閉;只有 SCHEDULE_TELEGRAM_ENABLED=true 且設定通過驗證時,
才會連線 Telegram。每日 23:30 的 email 摘要不受影響,見
schedule-summary-mail.md。
scheduler 啟動時會記錄下列三行之一(逐字):
Schedule Telegram notifications: enabledSchedule Telegram notifications: disabledSchedule 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
- 在 Telegram 找
@BotFather,送/newbot,依指示取得 bot token。 - 打開新 bot,按 Start。bot 不能主動私訊沒按過 Start 的人。
- 取得 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_ENABLEDTELEGRAM_CHAT_IDTELEGRAM_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_updateohlc_maintenance_windowcommodityforward_return_updatestory_strategy_refreshmarket_surveillanceetf_retryaccess_log_cleanupticker_integrityoauth_flow_cleanupschedule_summary_mailhub_participant_flowsweekly_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容器執行。