Data Pipeline 規格文檔
概述
資料排程與資料處理由三個部分組成:
- 每日/批次排程入口:
investment-agent/backend/scripts/scheduler.py— docker-compose 的scheduler服務(containercortex-scheduler, commandpython scripts/scheduler.py)。此服務不註冊即時抓價工作。 - 即時抓價入口:
investment-agent/backend/scripts/realtime_quote_scheduler.py—realtime-quote-scheduler服務(containercortex-realtime-quote-scheduler), 只輪詢台股即時報價,可獨立啟停。 data_pipeline/函式庫:investment-agent/backend/data_pipeline/— 共用函式庫(指標計算、commodity 爬蟲),不是獨立服務, 沒有自己的 daemon 或 container。
歷史備註:
data_pipeline/scheduler.py過去有一個DataPipelineScheduler類別(獨立的 per-market 股票排程器),已於 #601 移除 — 它沒有任何 live caller。該檔案現在只保留run_commodity_pipeline_standalone。
1. 排程入口:scripts/scheduler.py
檔案: investment-agent/backend/scripts/scheduler.py
- 使用 APScheduler(
AsyncIOScheduler),時區Asia/Taipei。 - 每日更新於台灣時間 15:30(收盤 13:30 後)執行;資料未到齊時每 30 分鐘
重試,最多重試 6 小時(起跑時刻、重試預算與由此得出的 21:30 截止時點
定義於
services/daily_update_window.py;僅供排程與重試使用,投資績效 不再以每日 checkpoint 判定計算完整性)。 - 每週五 20:00 執行 weekly full update(
weekly_full_update_job)。 - 每週日 02:00 執行 weekly strategy discovery(
_run_weekly_discovery,TA/CT/IR/COM)。 - Commodity pipeline:cron 由
config/commodities.yaml的 scheduler 設定 決定,呼叫data_pipeline.scheduler.run_commodity_pipeline_standalone; 另有 catch-up 路徑(commodity_ohlc落後台股 OHLC 時補跑)。 - 台股 OHLC 抓取由
scripts/taiwan_stocks.py執行:FinMind 為主要來源 (#409),yfinance 為備援(#396)— 不經過data_pipeline/的爬蟲。
CLI:
python scripts/scheduler.py # 啟動排程器(daemon)
python scripts/scheduler.py --run-now # 先立即執行一次每日更新,再啟動排程器
python scripts/scheduler.py --setup # 執行初始 setup(下載全部股票)後結束
需要 DATABASE_URL 環境變數,未設定時直接退出。其餘必要環境變數
(FRED_API_KEY、FINMIND_TOKEN 等)見 investment-agent/docker-compose.yml
的 scheduler 服務定義。
2. 即時抓價:scripts/realtime_quote_scheduler.py
- 僅註冊
realtime_quote_poll,不匯入每日排程,也不執行 startup recovery/catch-up。 - 間隔與交易日/時段沿用
config/core.yaml的realtime_quote設定及官方日曆。 非交易日或設定時段外不呼叫任一來源,也不開啟寫入交易。 - 寫入
taiwan.realtime_quote_snapshots;provider deadline、交易 timeout、 去重與前日資料保留規則不變。SIGTERM/SIGINT停止新工作、取消進行中的 輪詢並清理 DB engine。 - 預設
data_hub,需DATABASE_URL、HUB_API_URL、HUB_SERVICE_TOKEN、Python path 與台北時區;REALTIME_QUOTE_SOURCE=finmind時改用FINMIND_TOKEN。 兩來源共用一個 job,不自動 fallback。完整來源/freshness/操作契約見 即時來源文件。不依賴 FRED、Redis 或 LLM。 以 backend 目錄為工作目錄並配置 Python path 後執行:
python scripts/realtime_quote_scheduler.py
即時報價 REST/SSE 使用原有資料表與契約。SIT 的獨立容器為
cortex-sit-realtime-quote-scheduler。
3. data_pipeline/ 函式庫
目錄: investment-agent/backend/data_pipeline/
| 模組 | 內容 | 主要使用者 |
|---|---|---|
indicator_contract.py |
V2 CONTRACT — 78 欄指標的凍結 spec(first_valid/mature/zero_denominator/文獻出處,issue #1356) |
indicator_engine_v2.py、oracle harness、schema-parity 測試 |
indicator_primitives.py |
V2 共用數值 primitives(recursive_ema/wilder_rma/true_range/輸入驗證函數) |
V2 indicator_formulas_*.py、indicator_engine_v2.py |
indicator_formulas_*.py |
V2 個別指標公式實作(channels/core/custom/trend/volatility/volume_flow/wave 七個分片) | indicator_engine_v2.py |
indicator_engine_v2.py |
compute_all_v2 — 組裝 78 欄 V2 輸出;公式行為凍結(PR1) |
indicator_adapter_v2.py |
indicator_adapter_v2.py |
compute_indicator_frame — V2 production adapter(issue #1356 PR2 Commit 1) |
未來 snapshot writer(PR2 Commit 3 起) |
base_crawler.py |
BaseCrawler + CrawlerError — 共用 retry / rate-limit 基礎 |
FredCrawler、YFinanceCommodityCrawler 繼承 |
fred_crawler.py |
FredCrawler — FRED commodity 資料 |
commodity pipeline |
commodity_crawler.py |
YFinanceCommodityCrawler |
commodity pipeline |
commodity_orchestrator.py |
CommodityCrawlerOrchestrator |
commodity pipeline |
commodity_indicators.py |
CommodityIndicatorCalculator |
commodity pipeline、scripts/backfill_commodity_history.py |
commodity_validation.py |
CommoditySourceValidator |
commodity pipeline |
commodity_daily_write.py |
commodity OHLC / snapshot 寫入函數 | commodity pipeline |
scheduler.py |
run_commodity_pipeline_standalone(唯一保留的函數) |
scripts/scheduler.py(排程 job + catch-up) |
config_loader.py |
load_core_yaml / load_markets_yaml |
scripts/scheduler.py 等 |
ma_helpers.py / volume_helpers.py |
指標輔助函數 | snapshot / backfill 流程(daily_analysis_snapshot.py、ta_index_payload.py、signal_scanner.py 等) |
safe_cast.py |
safe_float / safe_int / safe_str / safe_bool |
各處 |
indicators.py(IndicatorCalculator,V1 技術指標計算引擎)與ta_compat.py(pandas-ta ARM 相容 patch)已於 #1356 決策 5 移除:V1 引擎全面停用,原本的呼叫端(scripts/daily_analysis_snapshot.py、services/periodic_technical_raw.py、api/routes/stocks.py、scripts/run_periodic_recap.py)全部改用 V2 production adapterdata_pipeline/indicator_adapter_v2.compute_indicator_frame;ta_compat.py只服務 V1 的 pandas-ta ARM 相依,隨之一併移除,V2 引擎 不需要這個 patch。data_pipeline/__init__.py也不再匯出IndicatorCalculator。
crawler.py(StockCrawler,yfinance 股票爬蟲)已隨 #620 移除: 它最後的 production caller 是 #601 刪除的DataPipelineScheduler, 之後只剩測試與文檔引用。台股 OHLC 由scripts/taiwan_stocks.py負責。
processor.py(DataProcessor,OHLC upsert)已隨 #628 移除: 沒有任何 production caller,只剩自身行為的測試與__init__re-export。 OHLC 寫入的 live 路徑是database/crud.py的save_ohlc_batch與 各 script(scripts/taiwan_stocks.py等)。
download_history.py(yfinance 5 年歷史 OHLC 批次回補)已隨 #855 移除: 它呼叫yf.Ticker().history()未帶auto_adjust=False(寫入還原價, 違反 raw-traded-price 紀律,#396),且繞過 #856 的 no-trade placeholder 過濾(非交易日會插入捏造的零量 bar,#847)。沒有任何 production caller, 台股歷史回補由scripts/backfill_taiwan_ohlc_raw.py(FinMind 官方 raw 價)負責。
ema_helpers.py(compute_ema_series,EMA 輔助函數)已隨 #678 移除: 其最後的 production callerscripts/backfill_real_ema_periods.py在661 退役後即孤兒化,只剩自身測試。EMA 計算的 live 路徑是
indicators.py的IndicatorCalculator(#637 起全歷史單引擎)。
4. Docker 配置
實際的服務定義在 investment-agent/docker-compose.yml(節錄):
services:
scheduler:
build:
context: ..
dockerfile: investment-agent/backend/Dockerfile
container_name: cortex-scheduler
command: python scripts/scheduler.py
environment:
- DATABASE_URL=postgresql+asyncpg://...
- PYTHONPATH=/app:/app/core
- TZ=Asia/Taipei
- FRED_API_KEY=${FRED_API_KEY:?...}
- FINMIND_TOKEN=${FINMIND_TOKEN:?...}
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
realtime-quote-scheduler:
build:
context: ..
dockerfile: investment-agent/backend/Dockerfile
container_name: cortex-realtime-quote-scheduler
working_dir: /app/backend
command: python scripts/realtime_quote_scheduler.py
environment:
- DATABASE_URL=postgresql+asyncpg://...
- PYTHONPATH=/app/backend:/app:/app/core
- TZ=Asia/Taipei
- REALTIME_QUOTE_SOURCE=${REALTIME_QUOTE_SOURCE:-data_hub}
- HUB_API_URL=${HUB_API_URL:-http://host.docker.internal:58080}
- HUB_SERVICE_TOKEN=${HUB_SERVICE_TOKEN:-}
- FINMIND_TOKEN=${FINMIND_TOKEN:-}
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
兩個服務都不會 hot-reload;套用變更時依實際受影響的入口更新/重啟服務。
首次拆分必須先讓原 scheduler 載入已移除 quote job 的版本,再啟動新 worker,
避免同時存在兩個輪詢器。既有資料重建或 DB 維護尚在執行時,先完成其停止寫入
安排,再切換服務。
需要停止所有 DB writers 的維護流程必須包含 API、每日 scheduler 與 quote
scheduler。SIT 部署、rollback、DB rebase 與 dev cutover rehearsal 已納入此
inventory;操作步驟見 docs/runbooks/sit-deploy.md。舊版本沒有獨立 worker 時,
rollback 只恢復舊 API/scheduler,保持新 worker 停止。
5. 測試
tests/test_commodity_crawler.py— commodity 爬蟲(BaseCrawler 行為)tests/test_ohlc_market_column.py— OHLC 寫入路徑的 market 欄位(source inspection)tests/test_realtime_quote_scheduler_isolation.py— 入口分離、單一 job 與停止清理tests/test_scheduler_realtime_quote_job.py— quote trigger 與設定tests/integration/test_scheduler_realtime_quote_pg.py— 真實 PostgreSQL 輪詢/timeout/保留資料
執行方式見 repo 根目錄 CLAUDE.md 的 Build & Test Commands。