跳轉到

台股換股期間與資料就緒

data.get() 的資料名稱、DataFrame 格式與快取用法保持不變。新版向 auth API 額外要求同一版本的來源更新證據;舊版客戶端維持原本的下載網址。交易日曆是獨立的小型公開 JSON,透過 data.get_calendar() 取得,不會下載行情,也不占資料下載額度。

from finlab import data

close = data.get('price:收盤價')
calendar = data.get_calendar()
status = data.readiness()
print(status['final_ready'])
print(status['datasets'])

台股日頻資料的 data.is_tradable() 等同於 data.readiness()['final_ready']。它表示這次載入的資料版本包含「下一個交易日開盤前最後一輪預定更新」的成功來源證據,不以查詢時間或快取是否到期推論就緒。休市日不需要當天行情,但週末仍有排程的來源必須等最後一輪更新。已發布的 metadata 必須與策略實際使用的資料版本相符;查到較新的 metadata 不會讓舊 DataFrame 被視為就緒。

目前支援台股日頻。每個來源的 checked_at 是開始讀來源的時間,必須不早於最後一輪排程;新版 completed_at 記錄成功讀取並通過既有品質檢查的時間,不能早於開始、晚於現在或到達開盤。開始讀取在排程之前,即使完成時間已過排程仍不能認證。舊版成功來源證據沒有 completed_at 時仍相容;它只在來源成功後發布,不能由 auth 查詢時間補造。物件排程只能加嚴來源門檻,不能取代來源證據。例如 24 號到下次開盤沒有月營收更新排程,且最後一輪已有成功來源證據,月營收可繼續使用,不必每天重下載。爬蟲延遲、失敗、部分輪次回傳空結果、ETL 缺少依賴證據時均不標為就緒。沒有排程的來源、舊檔案、免費歷史截斷資料也可能回傳未知,此時 bool 為 False。非台股與盤中資料沒有來源排程,readiness() 回傳 unsupported_market;取不到交易日曆時回傳 readiness_unavailable(error_type 為 CalendarUnavailable)。這兩種情況下 data.is_tradable() 改用原本的快取到期規則,不會一律回傳 False;其他無法取得證據的情況(例如 metadata 查詢失敗)仍回傳 False。明確指定 end_date 的歷史回測,report.is_tradable() 一律為 False。策略若載入這些資料,應檢視 reason;不能把 False 一律理解為「再等十五分鐘就會好」。成功證據代表已通過該來源既有檢查,不保證每家公司每個欄位都已揭露或永遠不會修訂。

datasets[name]['sources'] 列出 checked_at、可選的 completed_at、required_refresh_at、data_date 與 pending。required_refresh_at 是預定來源檢查時間,不是保證到貨期限。suggested_tradable_time() 回傳尚未到達的最晚必要排程;排程已過、來源仍未知或沒有資料時回傳 None。更新完成後需要重新執行策略,再確認就緒。

datasets[name]['data_period'] 是可選的資料日期描述,與載入版本綁定。label_date 保留資料中的日期標籤;集保週資料不固定改成週五,月營收另以 value 表示營收月份,例如 2026-09-10 標籤對應 2026-08。未定義期別的來源只顯示已知日期,不從發布日期猜財報季度。completeness 固定為 unknown,這些欄位不會讓 final_ready 變成 True,也不表示所有公司都已到齊。資料日期仍可顯示在舊報告中,更新確認狀態則須重新檢查。

使用 data.get(..., start=..., end=...) 或 truncate_start/truncate_end 取得的範圍資料,不沿用完整快取的日期與即時就緒證據;reason 會是 historical_data_selection。這個標記會保留到目前資料 context 清除,避免離開暫時截斷範圍後,又把歷史資料當成最新資料。程式自行對回傳的 DataFrame 切片不在這項追蹤範圍內。

回測會在讀取行情、benchmark 與公司名稱等報表資料前,保存訊號資料的就緒快照。這些自動載入不會污染下一個策略的依賴清單。若有停損、停利、移動停損或 touched exit,還會核對影響決策的執行價格;無法追蹤的自訂價格不會被認證。明確指定 end_date 的歷史回測不標示為可交易。自行執行多個策略時,既有 data.get() 載入集合仍屬於目前 context;需要精確隔離時應使用不同執行環境。

更新期間

原本的 resample 日期與訊號取樣保持不變。週頻 W 仍採週日邊界;月末若在週六,M 不會取到星期日才出現的訊號。引擎仍採下一個行情交易日前最後一筆已取樣訊號。

execution = report.execution
info = execution.rebalance_timing()
# scheduled_date:原訂日期
# update_start_at:該日或之前最後交易日的 15:00(台北)
# update_end_at:原訂日期之後首個交易日的 09:00(台北)
# execution_session:預定執行交易日
# rebalance_end_at:execution_session 的收盤時間(台北 13:30),儀表板在此之後不再顯示「換股中」

update_start_at 到 update_end_at 是資料更新期間,正好開盤時關閉。is_rebalance_due() 從 update_start_at 起為 True,直到下一個訊號日期取代這次換股(執行交易日 15:00 後資料更新、重新回測時):週末整段可換股,執行交易日開盤後、甚至收盤後才執行的排程也仍會換股,不必延到下一期;這段期間重複執行只會重建相同的目標持股。Position.from_report 與 PortfolioSyncManager 從 update_start_at 起改用 next_weights,所以週五晚上執行週日邊界的換股,會直接採用新的目標持股。這和「資料已就緒」分開判斷。15:00 是允許更新的門檻,不是交易所收盤時間,也不保證來源已更新。沒有未來換股訊號的 resample=None/自訂 index 不會猜測下一次日期;卡片顯示已持有的交易日數與「下次換股未定」。明確提供的未來變化/自訂排程日期會另存為預定計畫,不提前改變 weights、next_weights 或 actions;只有未來日期、沒有可回測訊號時會回報清楚的 ValueError。資料不足或日曆未知時,rebalance_timing() 回報無法確認;此時 is_rebalance_due() 與目標持股改用原本依訊號日期收盤判斷的規則,日曆過期或尚未公告下一年時,實單換股不會因此停止。這個規則對週末或假日的訊號日期要等到該日 15:00 才換股,只在平日晚上執行的排程會晚一個交易日;下一年度的日曆應在年底前發布,避免每年一月出現這種延遲。

自訂 index(resample=pd.DatetimeIndex(...) 或日期 list)中的週末或假日日期,要到該日期當天或之後才會出現在報告中。只要在該日期當天到下一個交易日開盤前執行一次排程(例如週末當天,或週一開盤前),就會準時換股;這段期間沒有執行換股時,要到下一個交易日晚上才補上,比回測晚一個交易日。只在平日晚上執行的排程屬於這種情況;需要準時換股時,請在週末或開盤前加跑一次排程,或把自訂日期設在交易日。

PortfolioSyncManager.update() 每次都要執行,不要只在 is_rebalance_due() 為 True 時才呼叫。排程錯過換股期間時,update() 會發現持股仍是舊一期的目標,記錄警告並補上目前的目標;這個檢查只有在 update() 被呼叫時才會發生。持股紀錄的 applied_signal_at 記下實際採用的訊號日期;台股日頻以執行交易日比較,週末或假日的訊號與前一個交易日的訊號在同一個交易日執行時,不視為錯過。

日曆包含已公告年度的休市日期,行程內共用,每三小時重新取得一次;下載失敗時沿用已取得的日曆,五分鐘後再試。日曆超過 expires_at 仍可使用,只要還列有今天之後的交易日:已公告的休市日很少變動,舊日曆最多只會暫時漏掉臨時休市,而臨時休市不影響換股時機(換股期間在下一個訊號取代前都有效,停市當天的委託作廢後,下一次排程會重新下單),只會讓資料就緒與儀表板的執行日標示暫時不準。尚未公告的下一年、超出範圍或已沒有未來交易日的日曆不猜成平日交易;臨時休市依 fdata 維護紀錄修訂,不宣稱可自動偵測所有停市。

持股進度

進度是 交易日數 (a, b] / 交易日數 (a, c],a 是 weights.name,b 是台北今天,c 是 next_weights.name。不計入 a;b 與 c 若是交易日便計入,不按小時插值。b 超出區間時限制於 a~c;整段沒有交易日則無百分比。

from datetime import date

calendar.position_progress(date(2026, 9, 11), date(2026, 9, 16), date(2026, 9, 18))
# elapsed_sessions=3, total_sessions=5, progress=0.6

週日換股的進度可能週五就達到 100%,但週末更新窗口仍開放。進度 100% 不表示資料已就緒。若已有待執行換股,進度終點優先使用它;否則使用獨立的 planned_rebalance_date。例如 9/17 至 9/30 的預定計畫,在 9/24 為 5/7(71.4%)。計畫日期到達後仍須重新執行策略,單純等待不會把預告轉成可執行持股。

報告新增 rebalanceTiming、dataReadiness 與保留時區的 currentSignalAt/nextSignalAt;原有欄位保留。舊報告沒有新欄位時,持股頁維持原畫面。新介面預設收起資料明細;執行日開盤後顯示「換股中」,資料是否就緒與換股階段分開呈現,不代表已送單或成交。日曆改版、證據過期或時間戳缺失時,總狀態與明細都撤銷就緒標記;舊執行日期也不能繼續宣告「換股中」。

rebalanceTiming 增加 held_sessions、planned_rebalance_date、planned_window 與 progress_end_date,執行用的原欄位保留。plannedRebalanceDate 在 canonical、舊格式及持股 payload 中保留;舊報告沒有這個欄位時不推測計畫。

data.readiness() 的 scope=next_market_open 表示檢查下一次市場開盤;顯式指定 execution_session 時為 scope=execution_session。前端直接標明例如「9/29 開盤資料」,與「9/24 換股中」分開顯示。來源資料就緒不代表策略現在應換股,也不保證更晚的預定換股所需資料已齊備。