{
  "schema_version": 1,
  "minimum_version": "2.0.19",
  "releases": [
    {
      "version": "2.2.5",
      "date": "2026-10-05",
      "yanked": false,
      "items": [
        {
          "id": "2.2.5-1",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "report.weights",
            "Position.from_report"
          ],
          "trigger": "持有空單的策略在兩次換股之間有持股停損出場",
          "references": [
            "https://github.com/finlab-python/finlab/issues/229",
            "https://github.com/finlab-python/finlab/pull/338"
          ],
          "description": "持有空單的策略在兩次換股之間有持股停損出場時，`report.weights` 會放大其餘空單的權重，以此建立的 `Position.from_report()` 實單會多放空。現在其餘空單維持原本權重，與回測實際持有的部位一致：例如 5 檔空單各 -0.2，其中 1 檔停損後，其餘 4 檔由 -0.25 改回 -0.2。只做多的策略不受影響，回測損益與交易紀錄不變。（Issue #229、PR #338）\n\n    雲端排程會隨本次執行環境升級生效，不需要自行升級。自行執行的用戶請升級套件並重新啟動 Python／Jupyter kernel，下單前先確認實單目標。\n\n    行為變更",
          "changelog_url": "https://finlab.finance/docs/change-log/#225-2026-10-05"
        },
        {
          "id": "2.2.5-2",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.get",
            "data.gets"
          ],
          "trigger": "資料在排程外修正後，再次呼叫 data.get()／data.gets()",
          "references": [
            "https://github.com/finlab-python/finlab/issues/304",
            "https://github.com/finlab-python/finlab/pull/336"
          ],
          "description": "資料修正後，預設的 `data.get()`／`data.gets()` 最慢約 5 分鐘就會取得修正後的資料，不必等下一次排程更新，也不必使用 `force_download=True`。本機快取已是最新時不需連線確認；排程時間已過、資料尚未更新的期間，改為每 5 分鐘最多確認一次，不再每次呼叫都多一次線上查詢。已回傳的 DataFrame 仍是當時的快照，要使用修正後的資料請重新呼叫。`import finlab` 也不再於背景查詢資料更新時間。（Issue #304、PR #336）",
          "changelog_url": "https://finlab.finance/docs/change-log/#225-2026-10-05"
        },
        {
          "id": "2.2.5-3",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "data.describe"
          ],
          "trigger": "新增 `data.describe()`，不下載資料就能查詢資料表或欄位的說明",
          "references": [
            "https://github.com/finlab-python/finlab/issues/88",
            "https://github.com/finlab-python/finlab/pull/313"
          ],
          "description": "新增 `data.describe()`，不下載資料就能查詢資料表或欄位的說明、欄位清單、所屬市場與類別及更新排程，與 `data.search()` 共用目錄快取。查無的名稱會拋出 `KeyError`。（Issue #88、PR #313）\n\n    ```python\n    from finlab import data\n\n    data.describe('price:收盤價')\n    data.describe('monthly_revenue')  # 整張表的欄位與說明\n    data.describe('us_price:close', market='us')\n    ```",
          "changelog_url": "https://finlab.finance/docs/change-log/#225-2026-10-05"
        },
        {
          "id": "2.2.5-4",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "data.get",
            "data.gets",
            "data.free_memory"
          ],
          "trigger": "設定 FINLAB_MAX_DOWNLOAD_WORKERS 或呼叫 data.free_memory()",
          "references": [
            "https://github.com/finlab-python/finlab/issues/119",
            "https://github.com/finlab-python/finlab/pull/337"
          ],
          "description": "新增環境變數 `FINLAB_MAX_DOWNLOAD_WORKERS`，設定 `data.get()`／`data.gets()` 同時下載的數量：預設 8，設為 1 時依序下載，適合限制執行緒數的環境。另新增 `data.free_memory()`，釋放已讀取資料占用的記憶體，下次讀取會從本機快取載入；自己仍持有的 DataFrame 不會被釋放，請先 `del` 不再使用的變數。（Issue #119、PR #337）\n\n    ```python\n    import os\n    from finlab import data\n\n    os.environ['FINLAB_MAX_DOWNLOAD_WORKERS'] = '1'\n    close = data.get('price:收盤價')\n\n    del close\n    data.free_memory()\n    ```",
          "changelog_url": "https://finlab.finance/docs/change-log/#225-2026-10-05"
        },
        {
          "id": "2.2.5-5",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "finlab ai"
          ],
          "trigger": "`finlab ai` 新增 `--history`、`--show` 與 `--json`",
          "references": [
            "https://github.com/finlab-python/finlab/issues/328",
            "https://github.com/finlab-python/finlab/pull/329"
          ],
          "description": "`finlab ai` 新增 `--history [N]` 列出最近的提問（預設 10 則）、`--show <提問ID>` 讀取指定提問與所有回覆（包含事後追加的處理進度），以及 `--json` 輸出，方便自己的程式或 AI agent 讀取。（Issue #328、PR #329）\n\n    ```bash\n    python -m finlab ai --history\n    python -m finlab ai --show <提問ID> --json\n    ```",
          "changelog_url": "https://finlab.finance/docs/change-log/#225-2026-10-05"
        },
        {
          "id": "2.2.5-6",
          "results_may_change": true,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "weight.inverse_volatility",
            "weight.risk_parity",
            "weight.correlation",
            "weight.target_volatility",
            "weight.drawdown_control",
            "event_study",
            "Position.from_weight"
          ],
          "trigger": "pandas 3，價格中間有缺值的股票",
          "references": [
            "https://github.com/finlab-python/finlab/issues/330",
            "https://github.com/finlab-python/finlab/pull/331"
          ],
          "description": "使用 pandas 3 時，價格中間有缺值的股票，缺值後第一天的報酬會被漏算，影響 `position.weight.inverse_volatility()`、`risk_parity()`、`correlation()`、`target_volatility()`、`drawdown_control()` 算出的權重、`event_study()` 的個股與大盤報酬，以及 `Position.from_weight()` 使用槓桿時的保證金分配。現在會先沿用缺值前的價格再計算報酬，結果與 pandas 2 相同。使用 pandas 2 的用戶不受影響。（Issue #330、PR #331）\n\n    結果可能改變",
          "changelog_url": "https://finlab.finance/docs/change-log/#225-2026-10-05"
        },
        {
          "id": "2.2.5-7",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "finlab cloud deploy"
          ],
          "trigger": "`finlab cloud deploy --help` 的小型方案規格說明",
          "references": [
            "https://github.com/finlab-python/finlab/issues/332",
            "https://github.com/finlab-python/finlab/pull/333"
          ],
          "description": "`python -m finlab cloud deploy --help` 與 CLI 文件把小型方案（tier `s`）的規格誤寫成 2 GiB 記憶體、120 秒逾時，已更正為 1 CPU、4 GiB、300 秒。實際執行規格不變。（Issue #332、PR #333）",
          "changelog_url": "https://finlab.finance/docs/change-log/#225-2026-10-05"
        },
        {
          "id": "2.2.5-8",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [],
          "trigger": "新增「版本升級與重現」頁與版本影響 JSON／Atom",
          "references": [
            "https://github.com/finlab-python/finlab/issues/334",
            "https://github.com/finlab-python/finlab/pull/335"
          ],
          "description": "新增 [版本升級與重現](upgrade-guide.md)：整理 2.0.19 起各版會改變選股、回測或實單目標的條件與遷移寫法，附可下載的升級前後對照案例，並說明如何鎖住執行環境、保存資料版本與回測設定。另提供 [JSON](https://finlab.finance/docs/releases.json)／[Atom](https://finlab.finance/docs/releases.atom) 版本影響清單，可用程式篩出會影響自己策略的變更。（Issue #334、PR #335）",
          "changelog_url": "https://finlab.finance/docs/change-log/#225-2026-10-05"
        }
      ]
    },
    {
      "version": "2.2.4",
      "date": "2026-10-04",
      "yanked": false,
      "items": [
        {
          "id": "2.2.4-1",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.get",
            "data.gets"
          ],
          "trigger": "資料已修正，但本機快取尚未到排程更新時間",
          "references": [
            "https://github.com/finlab-python/finlab/issues/304",
            "https://github.com/finlab-python/finlab/pull/326"
          ],
          "description": "撤回 2.2.3 的排程外歷史修正快取檢查，避免每次預設 `data.get()`／`data.gets()` 都增加一次線上查詢。其他 2.2.3 的變更保留。資料修正後，本機快取會在下一次排程更新時過期，重新讀取即可取得新資料；美股更新時間為台北 05:30。要立即取得新資料，請對計算所需的相關欄位使用 `force_download=True` 重新下載。本次保留既有快取行為，不另行修正此現象。（Issue #304、PR #326）",
          "changelog_url": "https://finlab.finance/docs/change-log/#224-2026-10-04"
        },
        {
          "id": "2.2.4-2",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "finlab.portfolio.TradeReview",
            "TradeReview.from_account"
          ],
          "trigger": "新增 `finlab.portfolio.TradeReview`，可由實際成交、股利、股票分割與資金流重建每日持股、現金、淨值及報表，並比對目標部位",
          "references": [
            "https://github.com/finlab-python/finlab/issues/82",
            "https://github.com/finlab-python/finlab/pull/322"
          ],
          "description": "新增 `finlab.portfolio.TradeReview`，可由實際成交、股利、股票分割與資金流重建每日持股、現金、淨值及報表，並比對目標部位。`from_account()` 支援已串接券商的歷史查詢；券商未提供的費稅、入出金或其他事件會依支援範圍估算、採持股口徑，或列入 `limitations`，請確認限制後使用。（Issue #82、PR #322）",
          "changelog_url": "https://finlab.finance/docs/change-log/#224-2026-10-04"
        },
        {
          "id": "2.2.4-3",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "data.indicator"
          ],
          "trigger": "data.indicator(strict=True)",
          "references": [
            "https://github.com/finlab-python/finlab/issues/134",
            "https://github.com/finlab-python/finlab/pull/318"
          ],
          "description": "`data.indicator(..., strict=True)` 可選擇嚴格檢查：TA-Lib 指標只接受數值參數名稱，並拒絕 TA-Lib 的價格輸入 selector；pandas_ta 指標在嚴格模式下會拋出 `NotImplementedError`。只檢查參數名稱，參數值仍由 TA-Lib 檢查。預設模式不變。（Issue #134、PR #318）",
          "changelog_url": "https://finlab.finance/docs/change-log/#224-2026-10-04"
        },
        {
          "id": "2.2.4-4",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "finlab ai"
          ],
          "trigger": "`finlab ai` 對話模式貼上多行內容時，即使最後一行沒有換行，也會合併成一題，不再拆成兩題、扣兩次額度",
          "references": [
            "https://github.com/finlab-python/finlab/pull/310"
          ],
          "description": "`finlab ai` 對話模式貼上多行內容時，即使最後一行沒有換行，也會合併成一題，不再拆成兩題、扣兩次額度。（PR #310）",
          "changelog_url": "https://finlab.finance/docs/change-log/#224-2026-10-04"
        }
      ]
    },
    {
      "version": "2.2.3",
      "date": "2026-10-04",
      "yanked": false,
      "items": [
        {
          "id": "2.2.3-1",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "report.next_weights",
            "Position.from_report"
          ],
          "trigger": "布林部位停損停利後，下一次定期換股的實單目標",
          "references": [
            "https://github.com/finlab-python/finlab/issues/207",
            "https://github.com/finlab-python/finlab/pull/319"
          ],
          "description": "停損／停利後，`report.next_weights` 與 `Position.from_report()` 在下一次換股時，會依回測引擎將資金重新分配給剩餘持股。例如原本等權重持有五檔，停損三檔後，未設權重上限時，剩下兩檔的換股目標會由每檔 20% 改為 50%。本項修正不改回測損益與交易紀錄；實單的下一次換股部位可能變大，`position_limit` 仍會限制個股權重。（Issue #207、PR #319）\n\n    雲端排程會隨本次執行環境升級生效，不需要自行升級。自行執行的用戶請升級套件並重新啟動 Python／Jupyter kernel。若希望停損後保留現金，請在回測前把布林部位轉成浮點權重；例如原五檔各為 0.2，停損三檔後仍保留每檔 0.2。下例只將總權重超過 1 的列縮放至 1；若有缺少價格的股票，請先排除，再計算權重。\n\n    ```python\n    position = position.astype(float)\n    position = position.div(position.abs().sum(axis=1).clip(lower=1), axis=0)\n    report = sim(position, resample=\"M\", stop_loss=0.1)\n    ```\n\n    行為變更",
          "changelog_url": "https://finlab.finance/docs/change-log/#223-2026-10-04"
        },
        {
          "id": "2.2.3-2",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "report.next_weights",
            "Position.from_report"
          ],
          "trigger": "resample=None 換股時排除停損股，且下一次訊號尚未生效",
          "references": [
            "https://github.com/finlab-python/finlab/issues/228",
            "https://github.com/finlab-python/finlab/pull/314"
          ],
          "description": "`resample=None` 時，換股後到下一次部位變動前，`report.next_weights` 與 `Position.from_report()` 不再把換股時因停損／停利排除的股票加回來，實單不會立刻買回，與回測一致。**bool 與 float 部位的實單目標都會改變：** 例如 3 檔各 1/3 的 float 部位，換股時排除 1 檔停損股後，實單目標由 3 檔各 1/3 改為 2 檔各 1/3，另外 1/3 留現金；bool 部位則由 3 檔各 1/3 改為 2 檔各 1/2。回測損益與交易紀錄不變。升級後下單前請先確認實單目標。（Issue #228、PR #314）\n\n    行為變更",
          "changelog_url": "https://finlab.finance/docs/change-log/#223-2026-10-04"
        },
        {
          "id": "2.2.3-3",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.get",
            "data.gets"
          ],
          "trigger": "預設線上 `data.get()`／`data.gets()` 會檢查排程外的歷史修正，即使快取尚未過期也能重新取得修正內容；已回傳的 DataFrame 仍是當時的快照，重新計算時請重新取得相關輸入",
          "references": [
            "https://github.com/finlab-python/finlab/issues/304",
            "https://github.com/finlab-python/finlab/pull/305"
          ],
          "description": "預設線上 `data.get()`／`data.gets()` 會檢查排程外的歷史修正，即使快取尚未過期也能重新取得修正內容；已回傳的 DataFrame 仍是當時的快照，重新計算時請重新取得相關輸入。這會增加線上查詢與快取驗證成本。（Issue #304、PR #305）",
          "changelog_url": "https://finlab.finance/docs/change-log/#223-2026-10-04"
        },
        {
          "id": "2.2.3-4",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "finlab ai"
          ],
          "trigger": "新增 `finlab ai`，可從終端機詢問 FinLab AI 助理",
          "references": [
            "https://github.com/finlab-python/finlab/pull/308"
          ],
          "description": "新增 `finlab ai`，可從終端機詢問 FinLab AI 助理。（PR #308）",
          "changelog_url": "https://finlab.finance/docs/change-log/#223-2026-10-04"
        },
        {
          "id": "2.2.3-5",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": true,
          "functions": [
            "TWStockFuturesMarket"
          ],
          "trigger": "新增個股期口數回測第一階段：`TWStockFuturesMarket` 以整數口數、新台幣權益計算損益、費稅與保證金占用",
          "references": [
            "https://github.com/finlab-python/finlab/issues/219",
            "https://github.com/finlab-python/finlab/pull/292"
          ],
          "description": "新增個股期口數回測第一階段：`TWStockFuturesMarket` 以整數口數、新台幣權益計算損益、費稅與保證金占用。價格採標的現股還原價，保證金比例由使用者提供；尚未包含基差、轉倉、追繳／斷頭、真實期貨資料與實單下單。（Issue #219、PR #292）",
          "changelog_url": "https://finlab.finance/docs/change-log/#223-2026-10-04"
        },
        {
          "id": "2.2.3-6",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "data.get",
            "data.search"
          ],
          "trigger": "支援期貨各月份 1 分 K 的下載與搜尋，保留跨週末夜盤資料",
          "references": [
            "https://github.com/finlab-python/finlab/pull/309"
          ],
          "description": "支援期貨各月份 1 分 K 的下載與搜尋，保留跨週末夜盤資料。（PR #309）",
          "changelog_url": "https://finlab.finance/docs/change-log/#223-2026-10-04"
        },
        {
          "id": "2.2.3-7",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "data.universe",
            "data.get"
          ],
          "trigger": "`data.universe()` 支援指定股票清單；台股 OHLCV 資料支援英文別名",
          "references": [
            "https://github.com/finlab-python/finlab/pull/316",
            "https://github.com/finlab-python/finlab/pull/320"
          ],
          "description": "`data.universe()` 支援指定股票清單；台股 OHLCV 資料支援英文別名。（PR #316、#320）",
          "changelog_url": "https://finlab.finance/docs/change-log/#223-2026-10-04"
        },
        {
          "id": "2.2.3-8",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "data.search"
          ],
          "trigger": "`data.search(details=True)` 提供資料權限與免費資料截止日，預設搜尋也會標示 VIP 資料；免費帳號提示與文件補上台股歷史截止日 2023-12-31 與試用到期限制",
          "references": [
            "https://github.com/finlab-python/finlab/issues/162",
            "https://github.com/finlab-python/finlab/pull/306"
          ],
          "description": "`data.search(details=True)` 提供資料權限與免費資料截止日，預設搜尋也會標示 VIP 資料；免費帳號提示與文件補上台股歷史截止日 2023-12-31 與試用到期限制。（Issue #162、PR #306）",
          "changelog_url": "https://finlab.finance/docs/change-log/#223-2026-10-04"
        },
        {
          "id": "2.2.3-9",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.versions"
          ],
          "trigger": "修正歷史版本時間混合整秒與小數秒時無法解析的問題",
          "references": [
            "https://github.com/finlab-python/finlab/pull/312"
          ],
          "description": "修正歷史版本時間混合整秒與小數秒時無法解析的問題。（PR #312）",
          "changelog_url": "https://finlab.finance/docs/change-log/#223-2026-10-04"
        },
        {
          "id": "2.2.3-10",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.readiness"
          ],
          "trigger": "發行股數的 `data.readiness()` 會核對上市、上櫃、興櫃的官方最新可得月份；缺少、未知或落後的期別會回傳未就緒，逐市場診斷見 `sources[0].periods`",
          "references": [
            "https://github.com/finlab-python/finlab/issues/133",
            "https://github.com/finlab-python/finlab/pull/321"
          ],
          "description": "發行股數的 `data.readiness()` 會核對上市、上櫃、興櫃的官方最新可得月份；缺少、未知或落後的期別會回傳未就緒，逐市場診斷見 `sources[0].periods`。其他月季資料維持現行來源排程診斷。（Issue #133、PR #321）",
          "changelog_url": "https://finlab.finance/docs/change-log/#223-2026-10-04"
        },
        {
          "id": "2.2.3-11",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "report.next_weights",
            "report.is_rebalance_due"
          ],
          "trigger": "說明 `next_weights` 是換股預覽，示範 `report.is_rebalance_due()`，並區分換股到期、資料完整與實際成交",
          "references": [
            "https://github.com/finlab-python/finlab/issues/163",
            "https://github.com/finlab-python/finlab/pull/317"
          ],
          "description": "說明 `next_weights` 是換股預覽，示範 `report.is_rebalance_due()`，並區分換股到期、資料完整與實際成交。（Issue #163、PR #317）",
          "changelog_url": "https://finlab.finance/docs/change-log/#223-2026-10-04"
        },
        {
          "id": "2.2.3-12",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [],
          "trigger": "舊教學網址 `/docs/getting-start/` 轉址至 `/docs/getting-started/`",
          "references": [],
          "description": "舊教學網址 `/docs/getting-start/` 轉址至 `/docs/getting-started/`。",
          "changelog_url": "https://finlab.finance/docs/change-log/#223-2026-10-04"
        }
      ]
    },
    {
      "version": "2.2.2",
      "date": "2026-10-03",
      "yanked": false,
      "items": [
        {
          "id": "2.2.2-1",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.indicator"
          ],
          "trigger": "TA-Lib 指標收到不支援的參數名稱",
          "references": [
            "https://github.com/finlab-python/finlab/issues/274"
          ],
          "description": "`data.indicator()` 使用 TA-Lib 指標時，參數打錯會報錯並列出可用參數，不再忽略錯字、照預設值計算。需要還原價時，請用 `adjust_price=True`（`adj=True` 也可）；`adjusted=True`、`adjust=True` 都不是正確寫法。pandas_ta 指標不受影響。（Issue #274）\n\n    ```python\n    # timeperiod 不要誤寫成 timperiod\n    sma = data.indicator('SMA', timeperiod=10, adjust_price=True)\n    ```\n\n    行為變更",
          "changelog_url": "https://finlab.finance/docs/change-log/#222-2026-10-03"
        },
        {
          "id": "2.2.2-2",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "PortfolioSyncManager.update"
          ],
          "trigger": "本次不換股的策略已占滿 total_balance",
          "references": [
            "https://github.com/finlab-python/finlab/issues/164"
          ],
          "description": "`PortfolioSyncManager.update()` 換股時，若總資金已被這次不換股的策略占滿，現在會報錯並保留原持股，避免誤清倉。**這次更新會全部停止，包含其他策略的停損停利。** 請依錯誤提示調高 `total_balance`，留足買股與備用現金，再重跑 `update()`；成功後才儲存或同步。只做停損停利或移除策略、不重建持股的更新不受影響。（Issue #164）\n\n    錯誤提示的金額只是下限，仍需確認剩餘資金買得起目標股票；有餘額卻不足買一股的情況仍由 [Issue #296](https://github.com/finlab-python/finlab/issues/296) 追蹤。\n\n    行為變更",
          "changelog_url": "https://finlab.finance/docs/change-log/#222-2026-10-03"
        },
        {
          "id": "2.2.2-3",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "sim",
            "FinlabDataFrame.lazy"
          ],
          "trigger": "新版 pandas 且 resample 使用 M+2、Q+1 等位移頻率",
          "references": [
            "https://github.com/finlab-python/finlab/issues/269"
          ],
          "description": "`sim()` 與 lazy 換股排程：修正新版 pandas 無法解析 `resample='M+2'`、`'Q+1'` 等舊頻率名稱加日數位移的問題；先依 pandas 版本轉換頻率名稱，再解析位移。`'M+2'` 與 `'ME+2'` 的換股日期相同，未帶位移的 `'M'` 與 `'W-FRI+1'` 行為不變，pandas 2.1 仍使用舊名稱。（Issue #269）",
          "changelog_url": "https://finlab.finance/docs/change-log/#222-2026-10-03"
        },
        {
          "id": "2.2.2-4",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "report.display",
            "report.to_html"
          ],
          "trigger": "`report.display()` 在缺少 IPython 時，會依環境提示可用的顯示方式：Studio／Pyodide 在 cell 最後一行單獨寫 `report`；一般 Python 腳本用 `report.to_html(\"report.html\")` 儲存後，以瀏覽器開啟",
          "references": [
            "https://github.com/finlab-python/finlab/issues/283"
          ],
          "description": "`report.display()` 在缺少 IPython 時，會依環境提示可用的顯示方式：Studio／Pyodide 在 cell 最後一行單獨寫 `report`；一般 Python 腳本用 `report.to_html(\"report.html\")` 儲存後，以瀏覽器開啟。文字摘要與原有顯示行為不變。（Issue #283）",
          "changelog_url": "https://finlab.finance/docs/change-log/#222-2026-10-03"
        },
        {
          "id": "2.2.2-5",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [],
          "trigger": "恢復中英文 API 參考總覽，舊網址 `https://doc.finlab.tw/reference/` 轉址後可開啟，各模組文件可從總覽進入",
          "references": [
            "https://github.com/finlab-python/finlab/issues/282"
          ],
          "description": "恢復中英文 API 參考總覽，舊網址 `https://doc.finlab.tw/reference/` 轉址後可開啟，各模組文件可從總覽進入。（Issue #282）",
          "changelog_url": "https://finlab.finance/docs/change-log/#222-2026-10-03"
        },
        {
          "id": "2.2.2-6",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "data.us_universe"
          ],
          "trigger": "美股指數篩選文件與範例說明 `us_universe(index='SP500'/'NDX')` 使用最新成分股名單，歷史回測需按日期套用成分股紀錄；既有 API 仍使用最新名單，正式資料不變",
          "references": [
            "https://github.com/finlab-python/finlab/issues/239"
          ],
          "description": "美股指數篩選文件與範例說明 `us_universe(index='SP500'/'NDX')` 使用最新成分股名單，歷史回測需按日期套用成分股紀錄；既有 API 仍使用最新名單，正式資料不變。（Issue #239）",
          "changelog_url": "https://finlab.finance/docs/change-log/#222-2026-10-03"
        },
        {
          "id": "2.2.2-7",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [],
          "trigger": "補記 2.0.19～2.2.0 的資料讀取、季報日期轉換、主機時區與因子分析行為變更；各項記在實際生效的版本段落",
          "references": [
            "https://github.com/finlab-python/finlab/issues/223",
            "https://github.com/finlab-python/finlab/issues/274"
          ],
          "description": "補記 2.0.19～2.2.0 的資料讀取、季報日期轉換、主機時區與因子分析行為變更；各項記在實際生效的版本段落。（Issue #223、#274）",
          "changelog_url": "https://finlab.finance/docs/change-log/#222-2026-10-03"
        }
      ]
    },
    {
      "version": "2.2.1",
      "date": "2026-09-30",
      "yanked": false,
      "items": [
        {
          "id": "2.2.1-1",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "FinlabDataFrame.hold_until"
          ],
          "trigger": "出場條件的日期比進場條件稀疏，未指定 exit_mode",
          "references": [
            "https://github.com/finlab-python/finlab/issues/224"
          ],
          "description": "`hold_until()` 新增 `exit_mode`，預設沿用上一筆出場條件（`'state'`），恢復 2.1.0 以前的出場行為。要維持 2.1.0～2.2.0 只在訊號當天出場的行為，請指定 `'event'`。（Issue #224）\n\n    ```python\n    # 月營收、財報等條件：持續生效，直到下一筆資料更新\n    position = entries.hold_until(exits)\n\n    # 單次事件：只在 exits 列出的日期出場\n    position = entries.hold_until(exits, exit_mode='event')\n    ```\n\n    例如：週一出場訊號為 True，週二有進場訊號，但沒有新的出場資料。`'state'` 會繼續擋住進場；`'event'` 允許週二重新進場。若每天都有出場資料，兩種模式相同。\n\n    使用 `'event'` 時，請保留出場條件原本的日期，避免先與日頻條件混合；lazy 策略請先對出場條件單獨呼叫 `.collect()`。使用 `'state'` 時，日期需由舊到新排序，且最後一筆 True 會持續擋住進場，直到出現 False。其他回測修正仍保留，結果不一定與舊版完全相同。\n\n    結果可能改變",
          "changelog_url": "https://finlab.finance/docs/change-log/#221-2026-09-30"
        },
        {
          "id": "2.2.1-2",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.get"
          ],
          "trigger": "`data.get()`：修正 finlab 2.0.19～2.2.0 搭配 pyarrow < 16 時，快取過期後一直無法取得資料的問題",
          "references": [
            "https://github.com/finlab-python/finlab/pull/241",
            "https://github.com/finlab-python/finlab/issues/232"
          ],
          "description": "`data.get()`：修正 finlab 2.0.19～2.2.0 搭配 pyarrow < 16 時，快取過期後一直無法取得資料的問題。若看到 `RecordBatch` 沒有 `cast` 或 `select` 的錯誤，請升級後**重新啟動 Python 或 Jupyter kernel**，不必刪除快取。（PR #241、Issue #232）\n\n    ```bash\n    pip install -U finlab\n    ```\n\n    暫時無法升級 finlab 時，可改用 `pip install -U \"pyarrow>=16\"`，同樣需要重新啟動。",
          "changelog_url": "https://finlab.finance/docs/change-log/#221-2026-09-30"
        },
        {
          "id": "2.2.1-3",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "FinlabDataFrame.groupby_category"
          ],
          "trigger": "季度資料呼叫 groupby_category()",
          "references": [
            "https://github.com/finlab-python/finlab/pull/220",
            "https://github.com/finlab-python/finlab/issues/212"
          ],
          "description": "季報資料使用 `groupby_category()` 後，產業統計改用當天已公布的財報，避免算入同業尚未公布的數值；結果的索引也從季度改為公布日期。日頻、月頻資料不受影響。（PR #220、Issue #212）\n\n    **只有使用 `groupby_category()` 後的季報結果，才需要因這次變更調整 `.loc` 寫法**：`.loc['2025-Q1']` 會變成查詢 1～3 月的公布資料，不再代表第一季財報，而且不一定報錯。請改成查詢指定日期以前的最新資料：\n\n    ```python\n    industry_mean = quarterly_factor.groupby_category().mean()\n\n    # 查 2025-05-15 當時已公布的產業平均；尚無資料時回傳空表\n    latest = industry_mean.loc[:'2025-05-15'].tail(1)\n    ```\n\n    尚未公布新一季財報的公司，仍使用前一季數值。\n\n    結果可能改變",
          "changelog_url": "https://finlab.finance/docs/change-log/#221-2026-09-30"
        },
        {
          "id": "2.2.1-4",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "FinlabDataFrame.index_str_to_date",
            "FinlabDataFrame.rank"
          ],
          "trigger": "季報日期轉換或排名後，若對原資料使用 `stack().reset_index()` 並讀取 `symbol` 欄位，請先設定 `df.columns.name = \"symbol\"`；套件不再自動替原資料命名欄位",
          "references": [],
          "description": "季報日期轉換或排名後，若對原資料使用 `stack().reset_index()` 並讀取 `symbol` 欄位，請先設定 `df.columns.name = \"symbol\"`；套件不再自動替原資料命名欄位。",
          "changelog_url": "https://finlab.finance/docs/change-log/#221-2026-09-30"
        },
        {
          "id": "2.2.1-5",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.get"
          ],
          "trigger": "`data.get()` 下載失敗時，錯誤訊息會說明原因與處理方式，並移除可直接下載資料的網址，避免貼出錯誤求助時外流",
          "references": [
            "https://github.com/finlab-python/finlab/pull/258",
            "https://github.com/finlab-python/finlab/issues/247"
          ],
          "description": "`data.get()` 下載失敗時，錯誤訊息會說明原因與處理方式，並移除可直接下載資料的網址，避免貼出錯誤求助時外流。（PR #258、Issue #247）\n\n    ```python\n    from finlab.exceptions import DataError\n\n    try:\n        close = data.get('price:收盤價')\n    except DataError as error:\n        print(error)\n    ```\n\n    原本用 `except requests.RequestException` 處理下載錯誤的程式仍可使用；瀏覽器（Pyodide）版請改用 `except DataError`。自訂錯誤處理請用 `error.status` 讀取 HTTP 狀態碼、`error.kind` 判斷錯誤類別；`error.request` 與回應的網址、標頭、內容不再提供，非 HTTP 錯誤的 `error.response` 為 None。",
          "changelog_url": "https://finlab.finance/docs/change-log/#221-2026-09-30"
        },
        {
          "id": "2.2.1-6",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.get"
          ],
          "trigger": "同時開多個 Jupyter kernel 或平行回測時，`data.get()` 可能在歷史資料修正後仍讀到舊資料",
          "references": [
            "https://github.com/finlab-python/finlab/pull/260",
            "https://github.com/finlab-python/finlab/issues/255"
          ],
          "description": "同時開多個 Jupyter kernel 或平行回測時，`data.get()` 可能在歷史資料修正後仍讀到舊資料。現在下次讀取時會重新下載需要更新的資料，不必手動清除快取。（PR #260、Issue #255）",
          "changelog_url": "https://finlab.finance/docs/change-log/#221-2026-09-30"
        },
        {
          "id": "2.2.1-7",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "FinlabDataFrame.industry_rank",
            "FinlabDataFrame.weight.cap_industry",
            "create_custom_report",
            "ffn_core.drop_duplicate_cols"
          ],
          "trigger": "相同資料在不同 Python 行程執行產業計算或重複欄位去重",
          "references": [
            "https://github.com/finlab-python/finlab/issues/225"
          ],
          "description": "修正重新啟動 Python 後，相同資料的計算結果或排列順序可能不同的問題，涉及 `industry_rank()`、`weight.cap_industry()`、`create_custom_report()`、`drop_duplicate_cols()`。升級後結果固定；小數尾數的差異仍可能影響剛好落在選股門檻上的股票。若有儲存結果供比對，請在升級後重新儲存。（Issue #225） 結果可能改變",
          "changelog_url": "https://finlab.finance/docs/change-log/#221-2026-09-30"
        }
      ]
    },
    {
      "version": "2.2.0",
      "date": "2026-09-28",
      "yanked": false,
      "items": [
        {
          "id": "2.2.0-1",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": true,
          "functions": [
            "data.get",
            "data.versions"
          ],
          "trigger": "付費資料會員指定 as_of 或 version",
          "references": [
            "https://github.com/finlab-python/finlab/pull/211"
          ],
          "description": "`data.get(dataset, as_of=...)`／`data.get(dataset, version=...)` 可下載歷史版本，`data.versions(dataset)` 可查詢可用版本。`as_of` 不帶時區時採台北時間，只有日期時代表當天 00:00；`version` 則指定某一版，方便重現結果。限付費會員，下載計入每日配額，快取不重複扣額度。目前保留約兩週，預計延長至 30 天以上。財報請改用 `data.get('financial_statement_versions')`。`as_of` 與 `version` 只能擇一，也不能搭配 `start`／`end`、逐筆／分鐘資料或 `use_local_data_only`；API 仍可能調整。（PR #211） 實驗性",
          "changelog_url": "https://finlab.finance/docs/change-log/#220-2026-09-28"
        },
        {
          "id": "2.2.0-2",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "data.get",
            "FinlabDataFrame.index_str_to_date"
          ],
          "trigger": "`data.get` 支援台股 `*_revised`（更正版）表的索引轉換與季報遮罩；截斷回測時，`revised_at` 晚於截斷日的日期會改成截斷日，數值 item 會發出警告，提醒更正值無法還原成當時的數值",
          "references": [
            "https://github.com/finlab-python/finlab/pull/209"
          ],
          "description": "`data.get` 支援台股 `*_revised`（更正版）表的索引轉換與季報遮罩；截斷回測時，`revised_at` 晚於截斷日的日期會改成截斷日，數值 item 會發出警告，提醒更正值無法還原成當時的數值。**資料表尚未開放。**（PR #209）",
          "changelog_url": "https://finlab.finance/docs/change-log/#220-2026-09-28"
        },
        {
          "id": "2.2.0-3",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "finlab.tools.event_study.create_factor_data"
          ],
          "trigger": "create_factor_data() 的因子日期是補班交易週六",
          "references": [
            "https://github.com/finlab-python/finlab/pull/210",
            "https://github.com/finlab-python/finlab/issues/223"
          ],
          "description": "`finlab.tools.event_study.create_factor_data()` 不再把補班交易的週六延到週一，當天的因子與股價會納入分析，IC 與分組報酬可能改變。非交易日仍延到下一個交易日。這項修正不影響 `index_str_to_date()`；`.deadline()` 遇到補班交易日也會保留當天（月營收見下方 PR #210）。（PR #210；2026-09-30 補記，Issue #223） 行為變更 結果可能改變",
          "changelog_url": "https://finlab.finance/docs/change-log/#220-2026-09-28"
        },
        {
          "id": "2.2.0-4",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "FinlabDataFrame.index_str_to_date",
            "FinlabDataFrame.deadline",
            "sim"
          ],
          "trigger": "季報有值但公布日或截止日缺失",
          "references": [
            "https://github.com/finlab-python/finlab/pull/208",
            "https://github.com/finlab-python/finlab/issues/274"
          ],
          "description": "修正缺少公布日期的季報數值被提前用於回測的問題，適用於 `index_str_to_date()`（含 `sim()` 預設處理）與 `.deadline()`。例如「ROA 前 20 名（`.deadline()`）」月頻策略，修正後不再讓無法交易的 7947 占住一個名額，總報酬由 128.2% 變為 139.5%，交易數由 2465 增為 2575。2026-09-27 的台股檢查只發現 `.deadline()` 缺日期，未發現 `index_str_to_date()` 缺公布日；其他市場未檢查。（PR #208；2026-09-30 更正：原說明誤稱 `index_str_to_date()` 不受影響，Issue #274）\n\n    ??? info \"資料比對紀錄\"\n        2026-09-27 實測台股（含興櫃）全部 211 個季報欄位，涵蓋 2013-Q1～2026-Q2、2,870 個股票代號：15,178,900 筆有值資料中，136 筆缺少 `deadline()` 使用的截止日，占 0.000896%，全部集中在興櫃公司 7947 補丁科的 2026-Q2（92 個原始財報欄位、44 個財務指標）。這裡一筆指「股票 × 季度 × 欄位」，不是 136 家公司；該季公告日為 2026-09-17，本次掃描未發現有數值卻缺少 `index_str_to_date()` 使用之公告日的資料。比例按日期轉換前的原始資料計算，不代表填值後受影響的歷史格子比例。\n\n    結果可能改變",
          "changelog_url": "https://finlab.finance/docs/change-log/#220-2026-09-28"
        },
        {
          "id": "2.2.0-5",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "FinlabDataFrame.deadline",
            "data.get"
          ],
          "trigger": "月營收截止日是補班交易週六",
          "references": [
            "https://github.com/finlab-python/finlab/pull/210"
          ],
          "description": "月營收截止日若是補班交易的週六（2009-01-10、2016-09-10），原本被誤推到週一，現改為當天生效；截止日遇假日仍照常順延到下一個交易日。以範例策略測試：營收動能策略總報酬 471.8% → 459.7%、本益成長比策略 1421.9% → 1427.7%，其他 7 個價格與季報範例策略結果完全相同。（PR #210） 結果可能改變",
          "changelog_url": "https://finlab.finance/docs/change-log/#220-2026-09-28"
        }
      ]
    },
    {
      "version": "2.1.1",
      "date": "2026-09-24",
      "yanked": false,
      "items": [
        {
          "id": "2.1.1-1",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "TWMarket.market_close_at_timestamp"
          ],
          "trigger": "修正 pandas 3 遇到極遠日期（例如 2999 年）時，計算台股收盤時間會出現 `OutOfBoundsDatetime` 的問題",
          "references": [],
          "description": "修正 pandas 3 遇到極遠日期（例如 2999 年）時，計算台股收盤時間會出現 `OutOfBoundsDatetime` 的問題。一般日期與時區行為不變。",
          "changelog_url": "https://finlab.finance/docs/change-log/#211-2026-09-24"
        }
      ]
    },
    {
      "version": "2.1.0",
      "date": "2026-09-24",
      "yanked": false,
      "items": [
        {
          "id": "2.1.0-1",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "sim"
          ],
          "trigger": "`sim()` 回傳的報告新增 `upload_status`，可查看 `skipped`、`pending`、`success` 或 `error`；上傳失敗，以及環境變數覆蓋明確指定的報告名稱時，發出 `StrategyUploadWarning`",
          "references": [
            "https://github.com/finlab-python/finlab/pull/178"
          ],
          "description": "`sim()` 回傳的報告新增 `upload_status`，可查看 `skipped`、`pending`、`success` 或 `error`；上傳失敗，以及環境變數覆蓋明確指定的報告名稱時，發出 `StrategyUploadWarning`。原生 Python 的上傳仍在背景執行，狀態會隨上傳完成而更新。（PR #178）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-2",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "is_largest",
            "is_smallest"
          ],
          "trigger": "`TopNPaddingWarning`：`is_largest(N)`／`is_smallest(N)` 用同分股票補滿 N 檔時，會列出受影響的日期與補入檔數，並提供改寫方式",
          "references": [
            "https://github.com/finlab-python/finlab/pull/200"
          ],
          "description": "`TopNPaddingWarning`：`is_largest(N)`／`is_smallest(N)` 用同分股票補滿 N 檔時，會列出受影響的日期與補入檔數，並提供改寫方式。只有選取邊界的同分會警告，選股結果不變。（PR #200）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-3",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "FinlabDataFrame"
          ],
          "trigger": "`PandasLeftOperandWarning`：一般 pandas DataFrame 在左、`FinlabDataFrame` 在右做 `+ - * / // %  & | ^`，且兩邊日期或股票不同時發出",
          "references": [
            "https://github.com/finlab-python/finlab/pull/193"
          ],
          "description": "`PandasLeftOperandWarning`：一般 pandas DataFrame 在左、`FinlabDataFrame` 在右做 `+ - * / // % ** & | ^`，且兩邊日期或股票不同時發出。這時用的是 pandas 對齊（日期不往前填值、股票取聯集、回傳一般 `DataFrame`），結果與 `FinlabDataFrame` 放在左邊不同。結果不變，可依類別過濾。（PR #193）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-4",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "Position.from_weight",
            "Position.from_report"
          ],
          "trigger": "`ZeroShareAllocationWarning`：`Position.from_weight()`／`from_report()` 在整張下單時，若分配的資金買不起一張，會列出分配到 0 股的股票並提醒",
          "references": [
            "https://github.com/finlab-python/finlab/pull/190",
            "https://github.com/finlab-python/order_executor/pull/23"
          ],
          "description": "`ZeroShareAllocationWarning`：`Position.from_weight()`／`from_report()` 在整張下單時，若分配的資金買不起一張，會列出分配到 0 股的股票並提醒。請改用 `odd_lot=True` 或增加 `fund`。下單數量不變。（PR #190、order_executor PR #23）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-5",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "data.reset_universe",
            "data.get",
            "data.gets",
            "data.universe"
          ],
          "trigger": "`data.reset_universe()`：取消 universe 篩選，之後的 `get()`／`gets()`（含快取）回傳完整資料",
          "references": [
            "https://github.com/finlab-python/finlab/pull/186"
          ],
          "description": "`data.reset_universe()`：取消 universe 篩選，之後的 `get()`／`gets()`（含快取）回傳完整資料。巢狀 `universe()` 離開時仍還原原本的選擇。（PR #186）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-6",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "data.get_calendar",
            "data.readiness",
            "data.is_tradable"
          ],
          "trigger": "`data.get_calendar()`、`data.readiness()`：查看台股交易日曆，以及策略需要的資料是否已更新",
          "references": [
            "https://github.com/finlab-python/finlab/pull/182",
            "https://github.com/finlab-python/finlab/pull/195",
            "https://github.com/finlab-python/finlab/pull/198"
          ],
          "description": "`data.get_calendar()`、`data.readiness()`：查看台股交易日曆，以及策略需要的資料是否已更新。台股日頻的 `data.is_tradable()` 會等資料確實更新才回傳 True；其他市場或無法取得日曆時，沿用快取到期判斷。回測報告也會顯示預定換股日、持股進度、尚缺資料與重跑提示。（PR #182、#195、#198）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-7",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "report.display",
            "report.to_html"
          ],
          "trigger": "回測報告儀表板新增「客服」面板：notebook 中 `display()` 的報告會帶入快取的 FinLab 登入憑證（有效至多 1 小時），不必再登入即可聯絡客服；`to_html()` 匯出的檔案不含憑證",
          "references": [
            "https://github.com/finlab-python/finlab/pull/197"
          ],
          "description": "回測報告儀表板新增「客服」面板：notebook 中 `display()` 的報告會帶入快取的 FinLab 登入憑證（有效至多 1 小時），不必再登入即可聯絡客服；`to_html()` 匯出的檔案不含憑證。分享含輸出的 `.ipynb` 前請留意。（PR #197）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-8",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "sim"
          ],
          "trigger": "`sim()` 的 `upload` 預設（`None`）只在雲端排程（設定 `FINLAB_FORCED_STRATEGY_NAME`）時上傳報告；FinLab Studio 需明確傳入 `upload=True` 才上傳，避免 AI 反覆實驗時每次回測都覆蓋線上報告",
          "references": [
            "https://github.com/finlab-python/finlab/pull/187"
          ],
          "description": "`sim()` 的 `upload` 預設（`None`）只在雲端排程（設定 `FINLAB_FORCED_STRATEGY_NAME`）時上傳報告；FinLab Studio 需明確傳入 `upload=True` 才上傳，避免 AI 反覆實驗時每次回測都覆蓋線上報告。`FINLAB_STRATEGY_NAME` 仍決定上傳時使用的策略名稱。（PR #187） 行為變更",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-9",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.set_universe",
            "data.universe",
            "data.set_us_universe",
            "data.us_universe"
          ],
          "trigger": "set_universe() 不帶條件，或條件全為 ALL",
          "references": [
            "https://github.com/finlab-python/finlab/pull/192"
          ],
          "description": "`data.set_universe()` 不帶篩選條件（或條件皆為 `\"ALL\"`）時等同 `data.reset_universe()`，取消篩選；`with data.universe():`、`set_us_universe()`、`with data.us_universe():` 相同。過去會限制為目前市場目錄中的所有股票，目錄外的代號（例如集保非標準代號、歷史代號）會被默默刪除。（PR #192） 行為變更",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-10",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.set_universe",
            "data.universe"
          ],
          "trigger": "universe 篩選條件找不到股票",
          "references": [
            "https://github.com/finlab-python/finlab/pull/181"
          ],
          "description": "universe 篩選條件選不到任何股票（例如產業名稱打錯）時拋出 `ValueError`，原本的 universe 不變；過去會默默改用全市場。已啟用的 universe 與某張資料表完全沒有交集時，發出 `UserWarning` 並回傳未篩選的資料。股票代號前後的空白會先去除再比對。（PR #181） 行為變更",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-11",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [],
          "trigger": "最低支援 Python 3.10（`requires-python >= 3.10`）",
          "references": [],
          "description": "最低支援 Python 3.10（`requires-python >= 3.10`）。2.0.3 起已不再提供 Python 3.9 的 wheel，現在與套件設定一致。 行為變更",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-12",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "FinlabDataFrame.index_str_to_date"
          ],
          "trigger": "移除 2.0.23 新增的 `MissingDisclosureWarning`",
          "references": [],
          "description": "移除 2.0.23 新增的 `MissingDisclosureWarning`。季度缺少對齊日期時用戶無從處理，改由資料端補齊日期，不再對用戶發出警告。",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-13",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.get",
            "data.gets"
          ],
          "trigger": "`data.get()`／`data.gets()` 下載資料遇到 HTTP 404 或 412 時，會重新取得下載網址，再下載一次；過去直接拋出錯誤",
          "references": [
            "https://github.com/finlab-python/finlab/pull/185",
            "https://github.com/finlab-python/finlab/issues/223"
          ],
          "description": "`data.get()`／`data.gets()` 下載資料遇到 HTTP 404 或 412 時，會重新取得下載網址，再下載一次；過去直接拋出錯誤。只重試一次，重新下載仍失敗時，拋出的是重新下載那一次的錯誤。其他 HTTP 錯誤（例如 403、500）不重試，直接拋出。第一次就下載成功的情況不受影響。（PR #185；2026-09-30 補記，Issue #223）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-14",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "sim"
          ],
          "trigger": "（只換例外型別）`sim()` 的 `resample` 傳入日期清單（`DatetimeIndex`、DataFrame、Series 或 list），而回測期間內沒有其中任何一個日期時，改拋 `ValueError: resample has no dates within the simulation range`；過去是 `IndexError: list index out of range`",
          "references": [
            "https://github.com/finlab-python/finlab/pull/185",
            "https://github.com/finlab-python/finlab/issues/223"
          ],
          "description": "（只換例外型別）`sim()` 的 `resample` 傳入日期清單（`DatetimeIndex`、DataFrame、Series 或 list），而回測期間內沒有其中任何一個日期時，改拋 `ValueError: resample has no dates within the simulation range`；過去是 `IndexError: list index out of range`。`resample` 傳入字串（例如 `'M'`）而回測期間內沒有任何換股日時，仍然是 `IndexError`。兩種情況都要處理的程式請寫 `except (IndexError, ValueError)`。（PR #185；2026-09-30 補記，Issue #223） 行為變更",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-15",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "sim"
          ],
          "trigger": "無時區的部位，在市場日期與主機日期不同時執行",
          "references": [
            "https://github.com/finlab-python/finlab/pull/196",
            "https://github.com/finlab-python/finlab/issues/274"
          ],
          "description": "`sim()` 在部位日期未指定時區時，改以市場時間決定資料截止點（台股採台北時間、美股採 UTC−5），避免 Colab 等 UTC 主機在台北時間 00:00～08:00 執行時，最新訊號與持股落後一天。現在同一時刻在不同時區主機執行，結果一致。影響限於最新一期；從 2.0.23 升級的 UTC 用戶，若同時遇到非交易日 15:00 前的修正，訊號可能一次前進兩天。自訂市場 `tzinfo()` 為 None 時仍採主機時間。（PR #196；2026-09-30 補記，Issue #274） 行為變更 持股可能改變",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-16",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "TWMarket.market_close_at_timestamp"
          ],
          "trigger": "台股收盤時間改以原時間戳的精度設定 15:00，避免日曆不可用、訊號日期超出 pandas 奈秒範圍（例如 2999 年）時拋出 `OutOfBoundsDatetime`",
          "references": [],
          "description": "台股收盤時間改以原時間戳的精度設定 15:00，避免日曆不可用、訊號日期超出 pandas 奈秒範圍（例如 2999 年）時拋出 `OutOfBoundsDatetime`。一般日期與時區行為不變。",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-17",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "sim"
          ],
          "trigger": "持股已下市，或價格尾端至少 20 個交易日缺失",
          "references": [
            "https://github.com/finlab-python/finlab/pull/183"
          ],
          "description": "`sim()` 會處理下市持股，出場後不再於後續換股買入。台股依交易所下市公告，在公告後第一個有成交價的交易日出場；公告晚於最後價格、且下市日已在資料期間內時，改以最後可用價格出場。沒有公告時，價格資料尾端至少 20 個交易日缺價才採用最後價格出場；這是依缺價推定，不代表已確認下市。`resample=None` 在出場日重新分配部位；定期換股則保留現金到下一次換股。受影響時發出 `DelistedHoldingWarning`，列出股票及出場日期。過去可能持續用最後價格估值、反覆買入，交易報酬顯示 NaN；歷史績效、回撤、交易及目前／下一期持股都可能改變。（PR #183） 結果可能改變 持股可能改變",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-18",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "PortfolioSyncManager",
            "Position.from_report"
          ],
          "trigger": "實單換股（`PortfolioSyncManager`、`Position.from_report()`）：台股日頻的換股期間從週末或假日訊號日期的前一個交易日 15:00 開始，目標持股也從這時起改用新一期",
          "references": [
            "https://github.com/finlab-python/finlab/pull/185",
            "https://github.com/finlab-python/order_executor/pull/24",
            "https://github.com/finlab-python/order_executor/pull/25"
          ],
          "description": "實單換股（`PortfolioSyncManager`、`Position.from_report()`）：台股日頻的換股期間從週末或假日訊號日期的前一個交易日 15:00 開始，目標持股也從這時起改用新一期。過去只在晚上執行的排程，會在週五晚上用舊一期的目標重建部位，卻記錄成已完成換股：週頻策略實單每週落後回測一週，月底遇到週末的月頻策略整月維持舊持股，也沒有任何警告。`sl_enter`／`tp_enter` 的暫時出場在同一時間結束。（PR #185、order_executor PR #24、#25）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-19",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "PortfolioSyncManager.update"
          ],
          "trigger": "實單換股：`PortfolioSyncManager.update()` 發現持股仍是已被取代的舊一期目標（排程錯過換股期間，例如主機停機、cron 未觸發）時，記錄警告並補上報告目前的目標；過去會靜默維持舊持股直到下一次換股",
          "references": [
            "https://github.com/finlab-python/finlab/pull/185"
          ],
          "description": "實單換股：`PortfolioSyncManager.update()` 發現持股仍是已被取代的舊一期目標（排程錯過換股期間，例如主機停機、cron 未觸發）時，記錄警告並補上報告目前的目標；過去會靜默維持舊持股直到下一次換股。持股紀錄新增 `applied_signal_at`，記下實際採用的訊號日期；週末或假日的訊號與前一個交易日的訊號在同一個交易日執行時，不視為錯過。無法解析的舊持股紀錄只略過這項檢查，不會讓其他策略無法更新。`update()` 需要每次排程都執行，不要只在 `is_rebalance_due()` 為 True 時才呼叫。（PR #185）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-20",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "report.is_rebalance_due"
          ],
          "trigger": "台股日頻實單換股：`is_rebalance_due()` 在換股期間持續為 True，直到下一期訊號取代；排程即使開盤後或收盤後才執行，也不必再等一期",
          "references": [
            "https://github.com/finlab-python/finlab/pull/185",
            "https://github.com/finlab-python/new_website/pull/103"
          ],
          "description": "台股日頻實單換股：`is_rebalance_due()` 在換股期間持續為 True，直到下一期訊號取代；排程即使開盤後或收盤後才執行，也不必再等一期。無法取得交易日曆時，改依訊號日期的收盤時間判斷。儀表板於執行交易日收盤後停止顯示「換股中」。（PR #185、new_website PR #103）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-21",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "sim",
            "report.next_weights"
          ],
          "trigger": "台股日頻策略在非交易日台北時間 15:00 前執行",
          "references": [
            "https://github.com/finlab-python/finlab/pull/185",
            "https://github.com/finlab-python/finlab/issues/223"
          ],
          "description": "`sim()`：台股日頻策略在非交易日（週末、假日）台北時間 15:00 前執行時，資料截止點不再退回前一天，日期為當天的訊號會納入，`next_weights` 與目前／下一期持股因此可能和 2.0.23 以前不同。過去只要在 15:00 前執行就一律退回前一天，即使當天沒有開盤；交易日 15:00 前仍退回前一天。取不到交易日曆時沿用原本的規則。影響的是執行當下的最新一期。（PR #185；2026-09-30 補記，Issue #223） 持股可能改變",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-22",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "sim",
            "report.is_tradable"
          ],
          "trigger": "`sim(end_date=...)` 的歷史回測不再查詢交易日曆與資料 metadata，`report.is_tradable()` 一律為 False",
          "references": [
            "https://github.com/finlab-python/finlab/pull/185"
          ],
          "description": "`sim(end_date=...)` 的歷史回測不再查詢交易日曆與資料 metadata，`report.is_tradable()` 一律為 False。（PR #185）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-23",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.get_calendar",
            "report.display",
            "report.to_html"
          ],
          "trigger": "交易日曆、儀表板的個股資料與股票圖示、文件內嵌報告改用正式網域 `r2.finlab.finance`，不再使用有流量限制的 `r2.dev` 網址",
          "references": [
            "https://github.com/finlab-python/finlab/pull/185",
            "https://github.com/finlab-python/new_website/pull/103"
          ],
          "description": "交易日曆、儀表板的個股資料與股票圖示、文件內嵌報告改用正式網域 `r2.finlab.finance`，不再使用有流量限制的 `r2.dev` 網址。交易日曆每三小時重新取得；超過有效期限的日曆只要還列有未來交易日就繼續使用，下載失敗也沿用已取得的日曆。（PR #185、new_website PR #103）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-24",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "FinlabDataFrame.__getitem__"
          ],
          "trigger": "部位以日期布林 Series 篩選，且包含 False 日期",
          "references": [
            "https://github.com/finlab-python/finlab/pull/191",
            "https://github.com/finlab-python/finlab/pull/173"
          ],
          "description": "`position[timing]`（`timing` 為日期索引的布林 Series）保留所有日期，`timing` 為 False 的日期空手（布林部位為 False、權重部位為 0），與 `position & timing` 相同。過去會刪除這些日期，`sim()` 往前填值後沿用前一天的持股，擇時濾網在該空手的期間仍然滿倉。股票索引的布林 Series（含 object dtype）仍用來選取欄位。移除只在未發布版本出現過的 `DateMaskSelectionWarning`。（PR #191、#173） 行為變更 結果可能改變",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-25",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "sim"
          ],
          "trigger": "sim() 部位含 NaN 或 object 欄位",
          "references": [
            "https://github.com/finlab-python/finlab/pull/194"
          ],
          "description": "`sim()` 在輸入時統一把部位中的 NaN 視為未持有（等同 `fillna(0)`／`fillna(False)`）；合併兩組部位（`pos_a + pos_b`、`combine_first`、`pd.concat`）產生的 object 欄位會轉成布林或浮點數。過去 `resample=None` 時，經過 NaN 的進出場會被略過，開頭整列都是 NaN 的部位會拋出 `IndexError`，`avgNStock` 也會把 NaN 算成持有。部位開頭有 NaN 列且 `resample=None` 時，回測起點可能提前。以 1,089 個雲端排程策略重跑：275 個的部位含 NaN，0 個的最新目標權重（`next_weights`）改變，79 個的績效指標改變，多數是因為過去略過了第一次換股。（PR #194） 結果可能改變",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-26",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "FinlabDataFrame.rank",
            "FinlabDataFrame.is_largest",
            "FinlabDataFrame.is_smallest",
            "FinlabDataFrame.cs",
            "FinlabDataFrame.sector",
            "neutralize"
          ],
          "trigger": "季度資料做橫斷面排名、產業運算或 neutralize()",
          "references": [
            "https://github.com/finlab-python/finlab/pull/184",
            "https://github.com/finlab-python/finlab/pull/188"
          ],
          "description": "季度資料的橫斷面運算先對齊各公司的財報公告日，再以當日已公開的數值計算：`rank(axis=1)`、`is_largest()`／`is_smallest()`（含 lazy）、`df.cs` 與 `df.sector` 的各方法，以及全為季頻的 `neutralize()`。不再讓提早公布的公司與尚未公布的同季數值比較，避免前視偏誤；`cs.rank()` 對季度資料也不再全為 NaN。保留同值排名規則與未公布資料的空值。（PR #184、#188） 結果可能改變",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-27",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "FinlabDataFrame.hold_until"
          ],
          "trigger": "出場訊號日期比進場訊號稀疏",
          "references": [
            "https://github.com/finlab-python/finlab/pull/177"
          ],
          "description": "`hold_until(exit, ...)`：出場訊號與進場訊號一樣視為事件，只在自己的日期生效。過去 `exit` 的日期比進場稀疏（事件日、週／月／季頻）時，最後一個出場訊號會往後填滿，該股票之後無法再進場。要把低頻出場條件當成持續狀態，請寫 `exit.reindex(entries.index, method='ffill')`。（PR #177） 結果可能改變",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-28",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "FinlabDataFrame.weight.drawdown_control",
            "FinlabDataFrame.weight.target_volatility"
          ],
          "trigger": "風險縮放的原始部位是 bool 或總絕對權重大於 1",
          "references": [
            "https://github.com/finlab-python/finlab/pull/180"
          ],
          "description": "`weight.drawdown_control()`／`target_volatility()` 先將布林或總絕對權重大於 1 的部位轉為 `sim()` 使用的權重，再縮放風險曝險，修正縮小部位後又被 `sim()` 正規化回滿倉的問題。縮放後超過滿倉的曝險仍受 `sim()` 上限限制。（PR #180） 結果可能改變",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-29",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "sim"
          ],
          "trigger": "resample=None 且有尚未生效的未來部位列",
          "references": [
            "https://github.com/finlab-python/finlab/pull/196"
          ],
          "description": "`resample=None` 時，未來日期的部位列保留為報告中的預定換股日，不會進入實際持股；也修正未來日期的列改變稀疏部位的回測期間、或繞過每日訊號截止時間的問題。（PR #196）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-30",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "Position.from_weight"
          ],
          "trigger": "Position.from_weight() 的權重絕對值總和大於 1",
          "references": [
            "https://github.com/finlab-python/finlab/pull/174",
            "https://github.com/finlab-python/order_executor/pull/21"
          ],
          "description": "`Position.from_weight()` 的權重絕對值總和大於 1 時，與 `sim()` 相同地等比例縮為滿倉並發出警告；過去會拋出 `AssertionError: Unexpectedly insufficient funds.`。（PR #174、order_executor PR #21）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-31",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "finlab.tools.factor_metrics.ic",
            "finlab.tools.factor_metrics.calc_metric"
          ],
          "trigger": "`finlab.tools.factor_metrics.ic()`／`calc_metric()` 不再要求日期索引名稱必須為 `date`；未命名或使用其他名稱的索引也能正確分日計算",
          "references": [
            "https://github.com/finlab-python/finlab/pull/176"
          ],
          "description": "`finlab.tools.factor_metrics.ic()`／`calc_metric()` 不再要求日期索引名稱必須為 `date`；未命名或使用其他名稱的索引也能正確分日計算。（PR #176）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-32",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "report.display",
            "report.to_html"
          ],
          "trigger": "可轉債報告從 `cb_published_info` 的「債券簡稱」取得名稱，兼容舊版 `name` 與代號欄位格式，並採用最新的非空名稱；修正交易與持股只顯示代號的問題",
          "references": [
            "https://github.com/finlab-python/finlab/pull/159"
          ],
          "description": "可轉債報告從 `cb_published_info` 的「債券簡稱」取得名稱，兼容舊版 `name` 與代號欄位格式，並採用最新的非空名稱；修正交易與持股只顯示代號的問題。（PR #159）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-33",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "SinopacAccount.update_order"
          ],
          "trigger": "`SinopacAccount.update_order()` 取消零股委託後，若重新下單失敗或未取得訂單代號，改拋 `OddLotRepriceError`，避免只記錄錯誤卻讓呼叫端以為改價成功",
          "references": [
            "https://github.com/finlab-python/order_executor/pull/18"
          ],
          "description": "`SinopacAccount.update_order()` 取消零股委託後，若重新下單失敗或未取得訂單代號，改拋 `OddLotRepriceError`，避免只記錄錯誤卻讓呼叫端以為改價成功。（order_executor PR #18）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-34",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "OrderExecutor.show_alerting_stocks"
          ],
          "trigger": "`OrderExecutor.show_alerting_stocks()`：修正 pandas 3 相容性，警示股代號統一使用字串",
          "references": [
            "https://github.com/finlab-python/order_executor/pull/20"
          ],
          "description": "`OrderExecutor.show_alerting_stocks()`：修正 pandas 3 相容性，警示股代號統一使用字串。（order_executor PR #20）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-35",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "FinlabDataFrame"
          ],
          "trigger": "恢復 2.0.21 的行為：一般 pandas DataFrame 放在左邊與 `FinlabDataFrame` 做四則與邏輯運算（`pd_df * fdf`、`pd_df + fdf`、`-`、`/`、`//`、`%`、``、`&`、`|`、`^`）恢復 2.0.21 的 pandas 對齊：日期與欄位取聯集、不往前填值，回傳一般 `DataFrame`",
          "references": [
            "https://github.com/finlab-python/finlab/pull/171"
          ],
          "description": "恢復 2.0.21 的行為：一般 pandas DataFrame 放在左邊與 `FinlabDataFrame` 做四則與邏輯運算（`pd_df * fdf`、`pd_df + fdf`、`-`、`/`、`//`、`%`、`**`、`&`、`|`、`^`）恢復 2.0.21 的 pandas 對齊：日期與欄位取聯集、不往前填值，回傳一般 `DataFrame`。2.0.22 改用 `FinlabDataFrame` 的對齊（欄位取交集、日期往前填值），「小資族資優生」等範本以 `empty_df = pd.DataFrame(1, index=..., columns=...)` 乘上面額變更比例（`par_value_change_*`）計算市值時，只剩有面額變更事件的股票（上市、上櫃相乘後為 0 檔），部位全為 NaN，`sim()` 拋出 `ValueError`。比較運算（`<`、`>`、`==` 等）在 2.0.21 就走 `FinlabDataFrame` 對齊，維持不變。（PR #171） 行為變更",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-36",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "FinlabDataFrame.align",
            "FinlabDataFrame.reindex",
            "FinlabDataFrame.reindex_like"
          ],
          "trigger": "沒有包成 `FinlabDataFrame` 的季度字串索引 DataFrame 與日期資料運算（包含 `FinlabDataFrame.align()`）不再拋出 2.0.22 新增的 `TypeError`，恢復 2.0.21 的結果",
          "references": [
            "https://github.com/finlab-python/finlab/pull/171"
          ],
          "description": "沒有包成 `FinlabDataFrame` 的季度字串索引 DataFrame 與日期資料運算（包含 `FinlabDataFrame.align()`）不再拋出 2.0.22 新增的 `TypeError`，恢復 2.0.21 的結果。`FinlabDataFrame` 的季度資料在 `reindex()`／`reindex_like()`／`align()` 對齊日期時自動轉為財報公告日的功能保留。（PR #171）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-37",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "report.display",
            "report.to_html"
          ],
          "trigger": "回測報告（`report.display()`／`report.to_html()`）：修正字級與間距跑掉的問題",
          "references": [],
          "description": "回測報告（`report.display()`／`report.to_html()`）：修正字級與間距跑掉的問題。手機上的月報酬表格可左右捲動，數字不再重疊。",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-38",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "report.display",
            "report.to_html"
          ],
          "trigger": "回測報告：打開個股面板（⌘K 搜尋股票）時，寬螢幕上報告改排在面板左側，不再被面板蓋住",
          "references": [],
          "description": "回測報告：打開個股面板（⌘K 搜尋股票）時，寬螢幕上報告改排在面板左側，不再被面板蓋住。報酬比頁「策略」圖例在深色主題下看不見，改為與長條相同的顏色。",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-39",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "plot_tw_stock_radar"
          ],
          "trigger": "`plot_tw_stock_radar()`：預設的 `line_polar` 模式座標上限改為 `cut_bins`（預設 10），過去會乘上股票數（3 檔時為 30），圖形擠在中央；`bar_polar` 堆疊模式不變",
          "references": [],
          "description": "`plot_tw_stock_radar()`：預設的 `line_polar` 模式座標上限改為 `cut_bins`（預設 10），過去會乘上股票數（3 檔時為 30），圖形擠在中央；`bar_polar` 堆疊模式不變。",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-40",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "plot_tw_stock_candles"
          ],
          "trigger": "`plot_tw_stock_candles()`：標題置中，不再與左上角的區間按鈕重疊",
          "references": [],
          "description": "`plot_tw_stock_candles()`：標題置中，不再與左上角的區間按鈕重疊。",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-41",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [],
          "trigger": "PyPI 說明頁改寫為英文，附上每段範例的實際輸出與回測報告各頁截圖",
          "references": [],
          "description": "PyPI 說明頁改寫為英文，附上每段範例的實際輸出與回測報告各頁截圖。",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-42",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "finlab.markets.us.USMarket"
          ],
          "trigger": "美股範例的匯入改為 `from finlab.markets.us import USMarket`（原本的 `from finlab.market import USMarket` 會出錯）",
          "references": [],
          "description": "美股範例的匯入改為 `from finlab.markets.us import USMarket`（原本的 `from finlab.market import USMarket` 會出錯）。",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-43",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "Position.from_weight",
            "Position.from_report"
          ],
          "trigger": "下單文件新增「錯誤 5：有訊號但沒有下單」，說明整張下單的無條件捨去與 `ZeroShareAllocationWarning`",
          "references": [
            "https://github.com/finlab-python/finlab/pull/190"
          ],
          "description": "下單文件新增「錯誤 5：有訊號但沒有下單」，說明整張下單的無條件捨去與 `ZeroShareAllocationWarning`。（PR #190）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-44",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "FinlabDataFrame.is_largest",
            "FinlabDataFrame.is_smallest"
          ],
          "trigger": "`is_largest()`／`is_smallest()` 的說明與 DataFrame 參考文件加上 `score[cond].is_largest(N)` 的寫法",
          "references": [
            "https://github.com/finlab-python/finlab/pull/200"
          ],
          "description": "`is_largest()`／`is_smallest()` 的說明與 DataFrame 參考文件加上 `score[cond].is_largest(N)` 的寫法。（PR #200）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        },
        {
          "id": "2.1.0-45",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "sim"
          ],
          "trigger": "tests：以離線 `sim()` 重現範本寫法，並固定 pandas 在左的各運算子結果與 2.0.21 相同",
          "references": [
            "https://github.com/finlab-python/finlab/pull/171"
          ],
          "description": "tests：以離線 `sim()` 重現範本寫法，並固定 pandas 在左的各運算子結果與 2.0.21 相同。（PR #171）",
          "changelog_url": "https://finlab.finance/docs/change-log/#210-2026-09-24"
        }
      ]
    },
    {
      "version": "2.0.23",
      "date": "2026-09-23",
      "yanked": true,
      "items": [
        {
          "id": "2.0.23-1",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "FinlabDataFrame.index_str_to_date"
          ],
          "trigger": "同日公布多季財報，或較舊一季晚於新季公布",
          "references": [
            "https://github.com/finlab-python/finlab/pull/169"
          ],
          "description": "季報轉日期（`index_str_to_date()`，以及與日頻資料合併時）改用已公布且有值的最新一季。晚交的舊季報不再蓋掉新季報，同一天公布多季也會取最新一季；日期索引不變。以正式資料比對，每股盈餘約 200 檔、1.3 萬個交易日的數值因此修正。（PR #169） 行為變更",
          "changelog_url": "https://finlab.finance/docs/change-log/#2023-2026-09-23"
        },
        {
          "id": "2.0.23-2",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "FinlabDataFrame.index_str_to_date"
          ],
          "trigger": "`index_str_to_date()`：有數值的季度因為沒有對齊日期而被略過時，發出 `MissingDisclosureWarning`，列出筆數與範例；過去會靜默略過並沿用前一季數值",
          "references": [
            "https://github.com/finlab-python/finlab/pull/170"
          ],
          "description": "`index_str_to_date()`：有數值的季度因為沒有對齊日期而被略過時，發出 `MissingDisclosureWarning`，列出筆數與範例；過去會靜默略過並沿用前一季數值。輸出不變。（PR #170）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2023-2026-09-23"
        }
      ]
    },
    {
      "version": "2.0.22",
      "date": "2026-09-23",
      "yanked": true,
      "items": [
        {
          "id": "2.0.22-1",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "data.search"
          ],
          "trigger": "`data.search()` 支援搜尋資料名稱、說明、別名與欄位",
          "references": [
            "https://github.com/finlab-python/finlab/pull/165"
          ],
          "description": "`data.search()` 支援搜尋資料名稱、說明、別名與欄位。例如 `data.search('市值')` 可找到 `etl:market_value`，`data.search('集保')` 可找到 `inventory` 與 `etl:inventory:*`。全半形、大小寫與常見用字差異不影響搜尋；回傳型別不變。（PR #165）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-2",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.get",
            "data.gets"
          ],
          "trigger": "`data.get()`：找不到資料集時改拋 `finlab.exceptions.DatasetNotFoundError`（`RuntimeError` 的子類別，原本的 `except RuntimeError` 仍可捕捉），列出最接近的正確 key 與口徑差異說明，不再顯示內部儲存路徑",
          "references": [
            "https://github.com/finlab-python/finlab/pull/165"
          ],
          "description": "`data.get()`：找不到資料集時改拋 `finlab.exceptions.DatasetNotFoundError`（`RuntimeError` 的子類別，原本的 `except RuntimeError` 仍可捕捉），列出最接近的正確 key 與口徑差異說明，不再顯示內部儲存路徑。已登記為等價的舊 key（例如 `margin_transactions:融資餘額`、`financial_statement:營業活動之淨現金流入（流出）`）會發出 `UserWarning` 並自動改抓正確的 key；`data.gets()` 與 `start`／`end` 讀取同樣適用。（PR #165）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-3",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "sim",
            "sim_batch"
          ],
          "trigger": "`sim(end_date=...)`：指定回測結束日，包含當天；持有中股票也只用該日以前的價格估值",
          "references": [
            "https://github.com/finlab-python/finlab/pull/166"
          ],
          "description": "`sim(end_date=...)`：指定回測結束日，包含當天；持有中股票也只用該日以前的價格估值。`sim_batch()` 與 lazy 部位同樣支援。過去只截斷月頻部位時，報酬曲線仍可能延伸到今天；現在可用 `sim(position, end_date=\"2025-12-31\")` 明確截止。未指定時行為不變。（PR #166）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-4",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "report.get_stats",
            "report.get_benchmark_stats"
          ],
          "trigger": "`report.get_stats()`／`get_benchmark_stats()`：加入與 `get_metrics()` 同名的 camelCase 別名 `annualReturn`、`sharpeRatio`、`sortinoRatio`、`calmarRatio`、`volatility`、`maxDrawdown`、`avgDrawdown`、`avgDrawdownDays`、`m12WinRate`，數值與原本的 ffn 欄位相同，舊欄位保留",
          "references": [
            "https://github.com/finlab-python/finlab/pull/166"
          ],
          "description": "`report.get_stats()`／`get_benchmark_stats()`：加入與 `get_metrics()` 同名的 camelCase 別名 `annualReturn`、`sharpeRatio`、`sortinoRatio`、`calmarRatio`、`volatility`、`maxDrawdown`、`avgDrawdown`、`avgDrawdownDays`、`m12WinRate`，數值與原本的 ffn 欄位相同，舊欄位保留。`win_ratio` 不加別名：它只計算已出場交易，`get_metrics()` 的 `winRate` 包含持有中的交易。（PR #166）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-5",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "finlab.tools.factor_metrics.ic"
          ],
          "trigger": "`finlab.tools.factor_metrics.ic(method=...)`：可選 `'pearson'`（預設，結果不變）或 `'spearman'`（Rank IC）",
          "references": [
            "https://github.com/finlab-python/finlab/pull/166"
          ],
          "description": "`finlab.tools.factor_metrics.ic(method=...)`：可選 `'pearson'`（預設，結果不變）或 `'spearman'`（Rank IC）。（PR #166）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-6",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "report.trades",
            "CloudReport.trades"
          ],
          "trigger": "`report.trades['stock_id']` 改為只有股票代號（例如 `2330`），可直接與價格資料合併",
          "references": [
            "https://github.com/finlab-python/finlab/pull/166"
          ],
          "description": "`report.trades['stock_id']` 改為只有股票代號（例如 `2330`），可直接與價格資料合併。股票名稱請讀新欄位 `name`（例如 `台積電`，查無名稱時為空字串），或讀 `symbol` 取得「代號 名稱」。`CloudReport.trades` 同樣適用；網站、Studio 與 HTML 報表顯示不變。（PR #166） 行為變更",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-7",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "FinlabDataFrame.reindex",
            "FinlabDataFrame.reindex_like",
            "FinlabDataFrame.align",
            "FinlabDataFrame"
          ],
          "trigger": "季度索引對齊日期，或一般 pandas 放在運算左側",
          "references": [
            "https://github.com/finlab-python/finlab/pull/166"
          ],
          "description": "季度字串索引（如 `2013-Q1`）自動對齊日期：`FinlabDataFrame.reindex()`／`reindex_like()`／`align()` 對齊到日期索引時，會先以 `index_str_to_date()` 轉成各公司財報公告日。過去 `reindex(close.index)` 得到全 NaN，`reindex(..., method='ffill')` 拋出 `TypeError`。一般 pandas DataFrame 放在左邊與 `FinlabDataFrame` 運算（`pd_df + fdf`、`pd_df & fdf` 等）也改用 `FinlabDataFrame` 的對齊規則（日期取聯集並往前填值、欄位取交集），回傳 `FinlabDataFrame`，不再是補滿 NaN 的 pandas 結果。沒有包成 `FinlabDataFrame` 的季度索引 DataFrame 與日期資料運算時，改為拋出 `TypeError` 並提示 `FinlabDataFrame(df)` 或 `index_str_to_date()`，不再靜默產生 NaN。（PR #166） 行為變更",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-8",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "finlab.tools.event_study.create_factor_data"
          ],
          "trigger": "create_factor_data() 產生因子分位組別",
          "references": [
            "https://github.com/finlab-python/finlab/pull/166"
          ],
          "description": "`finlab.tools.event_study.create_factor_data()` 的 `<name>_factor_quantile` 改為 1～5 組，與 `df.cs.bucket(5)` 一致。10 檔股票會平均分成每組 2 檔，修正原本分成 1／2／3／1／3 檔、組別為 0～4 的問題。（PR #166） 行為變更",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-9",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "report.get_stats"
          ],
          "trigger": "`report.get_stats(resample=...)`：`resample` 從未生效，現在傳入會發出 `DeprecationWarning`，結果不變；統計一律以日頻報酬計算",
          "references": [
            "https://github.com/finlab-python/finlab/pull/166"
          ],
          "description": "`report.get_stats(resample=...)`：`resample` 從未生效，現在傳入會發出 `DeprecationWarning`，結果不變；統計一律以日頻報酬計算。舊版上傳 API 的 `stats` 內容會多出上述別名欄位。（PR #166）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-10",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "FinlabDataFrame",
            "FinlabDataFrame.__getitem__"
          ],
          "trigger": "FinlabDataFrame 與以股票代號為 index 的 Series 運算",
          "references": [
            "https://github.com/finlab-python/finlab/pull/168"
          ],
          "description": "`FinlabDataFrame` 與 `pd.Series` 運算時，依 Series 的 index 決定對齊方向：以股票代號為 index 的 Series（例如 `pd.Series({'2330': 1.2})`、`close.iloc[-1]`）會套用到每一天，`&`／`|` 中缺少的股票視為 `False`，四則運算為 NaN；過去股票代號被拿去對齊日期，結果全為 NaN 或 `False`。日期（或季／月字串）index 的 Series 行為不變。index 同時符合日期與股票、或兩者都不符合時，改拋 `ValueError`，提示改用 `df.mul(s, axis=0)`（對齊日期）或 `df.mul(s, axis=1)`（對齊股票）。`df[股票布林 Series]` 改為選取欄位。（PR #168） 行為變更",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-11",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "FinlabDataFrame.__getitem__"
          ],
          "trigger": "`df[日期布林 Series]`（例如大盤擇時 `position[timing]`）仍會刪掉 False 的日期，`sim()` 會以前一期持股補上而不是空手；現在發出 `FutureWarning`，建議改用 `position & timing`",
          "references": [
            "https://github.com/finlab-python/finlab/issues/112",
            "https://github.com/finlab-python/finlab/pull/168"
          ],
          "description": "`df[日期布林 Series]`（例如大盤擇時 `position[timing]`）仍會刪掉 False 的日期，`sim()` 會以前一期持股補上而不是空手；現在發出 `FutureWarning`，建議改用 `position & timing`。下個 minor 版將改為保留這些日期並視為 `False`。沒有日期 index 的長表（例如 `broker_transactions`）用 `df[df['stock_id'] == '2330']` 篩選列不受影響。（#112；PR #168）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-12",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "finlab.login"
          ],
          "trigger": "`finlab.login()`：瀏覽器登入失敗、被拒絕或逾時時，過去會直接返回、看起來像已登入；現在拋出 `finlab.exceptions.LoginError`（`RuntimeError` 的子類別），並說明重新登入與無瀏覽器環境改用 `python -m finlab token --env` 的方式",
          "references": [
            "https://github.com/finlab-python/finlab/pull/165"
          ],
          "description": "`finlab.login()`：瀏覽器登入失敗、被拒絕或逾時時，過去會直接返回、看起來像已登入；現在拋出 `finlab.exceptions.LoginError`（`RuntimeError` 的子類別），並說明重新登入與無瀏覽器環境改用 `python -m finlab token --env` 的方式。（PR #165）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-13",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "finlab.tools.factor_metrics.calc_ic"
          ],
          "trigger": "calc_ic(rank=True)",
          "references": [
            "https://github.com/finlab-python/finlab/pull/166"
          ],
          "description": "`finlab.tools.factor_metrics.calc_ic(rank=True)`：過去只對因子排名、報酬維持原值，算出的既不是 Pearson 也不是 Spearman；現在每個日期對因子與報酬都排名，以成對有效值計算真正的 Spearman，結果與 `scipy.stats.spearmanr` 一致，數值會改變。（PR #166） 行為變更",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-14",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "finlab.tools.factor_metrics.calc_ic"
          ],
          "trigger": "`calc_ic(rank=False)`：任一因子欄位全為 NaN 時會拋出 `IndexError`；現在從「每個有 IC 的因子都有值」的第一個日期開始回傳，全 NaN 欄位保留為 NaN",
          "references": [
            "https://github.com/finlab-python/finlab/pull/166"
          ],
          "description": "`calc_ic(rank=False)`：任一因子欄位全為 NaN 時會拋出 `IndexError`；現在從「每個有 IC 的因子都有值」的第一個日期開始回傳，全 NaN 欄位保留為 NaN。（PR #166）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-15",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "sim"
          ],
          "trigger": "`sim()`：部位少於 3 列時拋出 `IndexError`；現在持有到價格資料最後一天",
          "references": [
            "https://github.com/finlab-python/finlab/pull/166"
          ],
          "description": "`sim()`：部位少於 3 列時拋出 `IndexError`；現在持有到價格資料最後一天。（PR #166）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-16",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "report.display",
            "report.to_text",
            "report.to_html"
          ],
          "trigger": "`report.display()`：沒有安裝 IPython 的環境（例如未安裝相依套件的 Pyodide、一般 Python 腳本）會拋出 `ImportError`；現在改印 `report.to_text()` 文字摘要，並提示 `pip install ipython` 或 `report.to_html()`",
          "references": [
            "https://github.com/finlab-python/finlab/pull/166"
          ],
          "description": "`report.display()`：沒有安裝 IPython 的環境（例如未安裝相依套件的 Pyodide、一般 Python 腳本）會拋出 `ImportError`；現在改印 `report.to_text()` 文字摘要，並提示 `pip install ipython` 或 `report.to_html()`。`save_report_path` 仍會先寫出 HTML。（PR #166）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-17",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.indicator"
          ],
          "trigger": "`data.indicator()`：未安裝 TA-Lib 與 pandas_ta 時，錯誤訊息列出確切的 `pip install TA-Lib`／`pip install pandas-ta` 與兩者的指標命名方式；在瀏覽器（Pyodide）中改為說明 FinLab Studio 已預載 TA-Lib、可重新整理頁面，以及 Studio 未預載 pandas_ta",
          "references": [
            "https://github.com/finlab-python/finlab/pull/166"
          ],
          "description": "`data.indicator()`：未安裝 TA-Lib 與 pandas_ta 時，錯誤訊息列出確切的 `pip install TA-Lib`／`pip install pandas-ta` 與兩者的指標命名方式；在瀏覽器（Pyodide）中改為說明 FinLab Studio 已預載 TA-Lib、可重新整理頁面，以及 Studio 未預載 pandas_ta。TA-Lib 沒有該指標時會再嘗試 pandas_ta（過去安裝 TA-Lib 後無法使用 pandas_ta 指標）；兩者都沒有時拋出 `ValueError`，並對大小寫錯誤提示，例如 `'rsi'` → `'RSI'`。（PR #166）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-18",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "finlab.login"
          ],
          "trigger": "登入文件改為目前的流程：本機 `python -m finlab login`；伺服器與排程在本機執行 `python -m finlab token --env`，設定 `FINLAB_REFRESH_TOKEN`、`FINLAB_SESSION_ID`、`FINLAB_API_KEY`；Colab 直接 `finlab.login()`；`FINLAB_API_TOKEN` 已棄用",
          "references": [
            "https://github.com/finlab-python/finlab/pull/165"
          ],
          "description": "登入文件改為目前的流程：本機 `python -m finlab login`；伺服器與排程在本機執行 `python -m finlab token --env`，設定 `FINLAB_REFRESH_TOKEN`、`FINLAB_SESSION_ID`、`FINLAB_API_KEY`；Colab 直接 `finlab.login()`；`FINLAB_API_TOKEN` 已棄用。並列出認證優先順序。（PR #165）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-19",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "sim",
            "report.upload"
          ],
          "trigger": "補上 2.0.0 起未寫進文件的行為變更：`sim()` 的 `upload` 預設為 `None`，只有設定 `FINLAB_STRATEGY_NAME` 或 `FINLAB_FORCED_STRATEGY_NAME`（FinLab Studio、雲端排程）時才上傳報告；本機、Jupyter、Colab 預設不上傳，需要時請傳入 `upload=True` 或呼叫 `report.upload()`",
          "references": [
            "https://github.com/finlab-python/finlab/pull/165"
          ],
          "description": "補上 2.0.0 起未寫進文件的行為變更：`sim()` 的 `upload` 預設為 `None`，只有設定 `FINLAB_STRATEGY_NAME` 或 `FINLAB_FORCED_STRATEGY_NAME`（FinLab Studio、雲端排程）時才上傳報告；本機、Jupyter、Colab 預設不上傳，需要時請傳入 `upload=True` 或呼叫 `report.upload()`。（PR #165）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        },
        {
          "id": "2.0.22-20",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "sim"
          ],
          "trigger": "tests：新增離線 `sim()` 測試工具（合成市場與授權 stub），單元測試可直接執行實際的回測引擎與報表組裝，不需憑證或下載資料",
          "references": [
            "https://github.com/finlab-python/finlab/pull/166"
          ],
          "description": "tests：新增離線 `sim()` 測試工具（合成市場與授權 stub），單元測試可直接執行實際的回測引擎與報表組裝，不需憑證或下載資料。（PR #166）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2022-2026-09-23"
        }
      ]
    },
    {
      "version": "2.0.21",
      "date": "2026-09-21",
      "yanked": false,
      "items": [
        {
          "id": "2.0.21-1",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "FinlabDataFrame.index_str_to_date"
          ],
          "trigger": "季度布林遮罩轉日期後取反或與日頻訊號合併",
          "references": [
            "https://github.com/finlab-python/finlab/pull/160"
          ],
          "description": "季度布林條件與日頻資料合併後，`~mask`、`A & ~B` 等選股條件現在能正確運算，修正選到錯誤股票的問題。**升級後請重新產生舊的條件表。** 首次公布財報前仍為未知值；回測視為不持有。若要使用 `.astype(bool)` 或 `np.where`，請先 `fillna(False)`。（PR #160） 結果可能改變",
          "changelog_url": "https://finlab.finance/docs/change-log/#2021-2026-09-21"
        },
        {
          "id": "2.0.21-2",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "Report.to_pickle"
          ],
          "trigger": "`Report.to_pickle()`：修正回測報告還在背景上傳時，立即存檔可能出現 `RuntimeError: dictionary changed size during iteration` 的問題",
          "references": [
            "https://github.com/finlab-python/finlab/pull/161"
          ],
          "description": "`Report.to_pickle()`：修正回測報告還在背景上傳時，立即存檔可能出現 `RuntimeError: dictionary changed size during iteration` 的問題。（PR #161）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2021-2026-09-21"
        },
        {
          "id": "2.0.21-3",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.get"
          ],
          "trigger": "Pyodide 盤後逐筆／分鐘資料：瀏覽器下載遇到 HTTP 錯誤時，保留伺服器回傳的錯誤內容（例如尚未收齊的交易日、權限不足），不再只顯示一般的 HTTP 錯誤",
          "references": [],
          "description": "Pyodide 盤後逐筆／分鐘資料：瀏覽器下載遇到 HTTP 錯誤時，保留伺服器回傳的錯誤內容（例如尚未收齊的交易日、權限不足），不再只顯示一般的 HTTP 錯誤。",
          "changelog_url": "https://finlab.finance/docs/change-log/#2021-2026-09-21"
        }
      ]
    },
    {
      "version": "2.0.20",
      "date": "2026-09-18",
      "yanked": false,
      "items": [
        {
          "id": "2.0.20-1",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "data.get"
          ],
          "trigger": "`data.get(\"tw_tick:2330\", start=..., end=...)` 與 `data.get(\"tw_minute:2330\", start=..., end=...)`：新增台股盤後逐筆成交與一般交易時段 1 分鐘 OHLCV",
          "references": [],
          "description": "`data.get(\"tw_tick:2330\", start=..., end=...)` 與 `data.get(\"tw_minute:2330\", start=..., end=...)`：新增台股盤後逐筆成交與一般交易時段 1 分鐘 OHLCV。需指定起訖日期，每次最多 31 個日曆日；資料尚未收齊時會報錯。時間採台北時區，同時間多筆成交會保留；分鐘線不補缺漏，13:30 撮合獨立一列，14:30 盤後定價交易只收錄於逐筆資料。台股資料會員可使用，免費樣本為 2330／2026-06-01，歷史資料持續補齊。詳見[盤後逐筆與分鐘資料](details/intraday.md)。",
          "changelog_url": "https://finlab.finance/docs/change-log/#2020-2026-09-18"
        },
        {
          "id": "2.0.20-2",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.gets",
            "data.get"
          ],
          "trigger": "`data.gets()` 不支援逐筆與分鐘資料集：清單中含 `tw_tick:*`、`tw_minute:*` 時拋出 `ValueError`，請改用 `data.get()` 並指定 `start`／`end`",
          "references": [
            "https://github.com/finlab-python/finlab/issues/223"
          ],
          "description": "`data.gets()` 不支援逐筆與分鐘資料集：清單中含 `tw_tick:*`、`tw_minute:*` 時拋出 `ValueError`，請改用 `data.get()` 並指定 `start`／`end`。其他資料集的 `data.gets()` 行為不變。（2026-09-30 補記，Issue #223）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2020-2026-09-18"
        }
      ]
    },
    {
      "version": "2.0.19",
      "date": "2026-09-16",
      "yanked": false,
      "items": [
        {
          "id": "2.0.19-1",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "data.universe.membership"
          ],
          "trigger": "`data.universe.membership()`：新增 `TSE`、`OTC`、`TSE_OTC` 的每日上市櫃資格布林遮罩，可在橫斷面排名前用 `score[eligible].is_largest(20)` 篩選",
          "references": [
            "https://github.com/finlab-python/finlab/issues/140"
          ],
          "description": "`data.universe.membership()`：新增 `TSE`、`OTC`、`TSE_OTC` 的每日上市櫃資格布林遮罩，可在橫斷面排名前用 `score[eligible].is_largest(20)` 篩選。依目前可得的上市櫃與終止紀錄重建有效期間，上市櫃日包含在內、終止日起排除，支援轉板與多段上市期間。缺少可驗證紀錄的期間維持 `False`，並以警告與 `attrs['membership_missing_history']` 揭露不完整歷史；不提供歷史版本快照，也不涵蓋完整 ETF 母體。（#140）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-2",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "data.get"
          ],
          "trigger": "`data.get(dataset, start=..., end=...)`：一般資料集可用關鍵字參數 `start`／`end` 只讀取一段日期，可只給其中一個",
          "references": [
            "https://github.com/finlab-python/finlab/issues/223"
          ],
          "description": "`data.get(dataset, start=..., end=...)`：一般資料集可用關鍵字參數 `start`／`end` 只讀取一段日期，可只給其中一個。篩選比對的是資料表儲存的 `date` 欄，不是轉換後的索引：索引是季別的資料表（例如 `2024-Q1`），比對的是該列儲存的日期，不是季別本身。`start`／`end` 只寫日期時代表當天 00:00，剛好等於 `start` 或 `end` 的資料會包含在內；`date` 欄帶有時間的資料表，`end` 當天 00:00 之後的資料不會回傳。資料集沒有 `date` 欄，或 `start` 晚於 `end` 時拋出 `ValueError`。不帶 `start`／`end` 時回傳值不變。（2026-09-30 補記，Issue #223）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-3",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "CloudReport"
          ],
          "trigger": "`CloudReport`：讀取 legacy 持股資料時，補齊缺少的市場、移動停利與權重等執行預設值，讓設定與 canonical 報表一致；已提供的設定與持股動作不變",
          "references": [
            "https://github.com/finlab-python/finlab/issues/155",
            "https://github.com/finlab-python/finlab/pull/156"
          ],
          "description": "`CloudReport`：讀取 legacy 持股資料時，補齊缺少的市場、移動停利與權重等執行預設值，讓設定與 canonical 報表一致；已提供的設定與持股動作不變。 （#155；PR #156）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-4",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "Position.from_weight",
            "Position.from_report"
          ],
          "trigger": "`Position.from_weight()`／`from_report()`：修正傳入的 `allocation` 回呼被忽略的問題，現在會呼叫自訂分配函式；預設 greedy 分配與張數／零股換算不變",
          "references": [
            "https://github.com/finlab-python/finlab/issues/114",
            "https://github.com/finlab-python/finlab/pull/151",
            "https://github.com/finlab-python/order_executor/issues/17"
          ],
          "description": "`Position.from_weight()`／`from_report()`：修正傳入的 `allocation` 回呼被忽略的問題，現在會呼叫自訂分配函式；預設 greedy 分配與張數／零股換算不變。（#114；PR #151、order_executor#17）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-5",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "sim"
          ],
          "trigger": "`sim()`：空部位或全 NaN 部位會提前拋出 `ValueError`，提示使用 `add(fill_value=0)` 合併不同標的，或以明確的 0 表示空手，取代後續難以判讀的錯誤",
          "references": [
            "https://github.com/finlab-python/finlab/issues/65",
            "https://github.com/finlab-python/finlab/pull/149"
          ],
          "description": "`sim()`：空部位或全 NaN 部位會提前拋出 `ValueError`，提示使用 `add(fill_value=0)` 合併不同標的，或以明確的 0 表示空手，取代後續難以判讀的錯誤。正常零值與稀疏部位仍可通過輸入形狀驗證。（#65；PR #149）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-6",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "sim"
          ],
          "trigger": "回測引擎：對仍追蹤的部位，遇到 0、負值或無限大的交易價時，在除法前拋出含標的與資料列的 `ValueError`，避免除以零或報酬曲線變成 NaN",
          "references": [
            "https://github.com/finlab-python/finlab/issues/45",
            "https://github.com/finlab-python/finlab/pull/150"
          ],
          "description": "回測引擎：對仍追蹤的部位，遇到 0、負值或無限大的交易價時，在除法前拋出含標的與資料列的 `ValueError`，避免除以零或報酬曲線變成 NaN。未持有過的資產與 NaN 缺價處理維持原樣；未修改上游報價。（#45；PR #150）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-7",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "Report.to_html",
            "Report.upload",
            "CloudReport"
          ],
          "trigger": "`Report`：自訂 Series／DataFrame 成交價以 `custom` 標記匯出持股設定，修正 JSON／HTML 匯出失敗，報表顯示「自訂／Custom」",
          "references": [
            "https://github.com/finlab-python/finlab/issues/76",
            "https://github.com/finlab-python/finlab/pull/152",
            "https://github.com/finlab-python/new_website/issues/80"
          ],
          "description": "`Report`：自訂 Series／DataFrame 成交價以 `custom` 標記匯出持股設定，修正 JSON／HTML 匯出失敗，報表顯示「自訂／Custom」。即時 Report 保留原價格矩陣；雲端執行資訊無法還原矩陣，讀回時使用 `close`。（#76；PR #152、new_website#80）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-8",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.get",
            "data.gets"
          ],
          "trigger": "Pyodide 資料下載：ArrayBuffer 改為每次最多轉換 8 MiB，再組合為 Python bytes，避免大型資料超過 JavaScript 單一字串長度限制",
          "references": [
            "https://github.com/finlab-python/finlab/pull/153"
          ],
          "description": "Pyodide 資料下載：ArrayBuffer 改為每次最多轉換 8 MiB，再組合為 Python bytes，避免大型資料超過 JavaScript 單一字串長度限制。保留所有位元組值；瀏覽器整體記憶體限制仍適用。（PR #153）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-9",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "Report.upload"
          ],
          "trigger": "`Report.upload()`：修正雲端執行使用舊版上傳 API 時，尚未檢查伺服器回應就回報成功的問題",
          "references": [
            "https://github.com/finlab-python/finlab/issues/141"
          ],
          "description": "`Report.upload()`：修正雲端執行使用舊版上傳 API 時，尚未檢查伺服器回應就回報成功的問題。HTTP 非 200、無法解析的 JSON 或伺服器回傳的錯誤現在會回傳錯誤結果；若 `FINLAB_FORCED_STRATEGY_NAME` 覆蓋使用者指定的不同報告名稱，也會發出警告，說明實際寫入的部署策略。（#141）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-10",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "CloudReport.from_cloud"
          ],
          "trigger": "`CloudReport.from_cloud()`：當網站報告的 `metrics.backtest.updateDate` 比 canonical 報告新時，改為載入完整的網站報告，避免讀到舊績效與舊持股",
          "references": [
            "https://github.com/finlab-python/finlab/issues/137"
          ],
          "description": "`CloudReport.from_cloud()`：當網站報告的 `metrics.backtest.updateDate` 比 canonical 報告新時，改為載入完整的網站報告，避免讀到舊績效與舊持股。日期相同、較舊、缺漏或非數值時仍沿用 canonical 報告；此判斷不區分資料日期相同的重跑結果。（#137）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-11",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "ml.feature.TalibIndicatorFactory.generate_feature_names"
          ],
          "trigger": "`ml.feature.TalibIndicatorFactory.generate_feature_names()`：特徵名稱去重改為保留首次抽樣順序，修正相同亂數種子下，跨 Python 行程仍可能因 hash randomization 而得到不同特徵欄位順序的問題",
          "references": [
            "https://github.com/finlab-python/finlab/issues/135"
          ],
          "description": "`ml.feature.TalibIndicatorFactory.generate_feature_names()`：特徵名稱去重改為保留首次抽樣順序，修正相同亂數種子下，跨 Python 行程仍可能因 hash randomization 而得到不同特徵欄位順序的問題。（#135）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-12",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.universe"
          ],
          "trigger": "`data.universe()`：表格資料的 `symbol`／`stock_id` 若僅因前後空白而無法匹配目前股票集合，現在會保留這些資料列並發出 `UserWarning`，避免被靜默排除",
          "references": [
            "https://github.com/finlab-python/finlab/issues/132"
          ],
          "description": "`data.universe()`：表格資料的 `symbol`／`stock_id` 若僅因前後空白而無法匹配目前股票集合，現在會保留這些資料列並發出 `UserWarning`，避免被靜默排除。回傳的識別碼仍保留原值，資料來源仍需清理空白。（#132）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-13",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "ml.DEnsembleModel"
          ],
          "trigger": "`ml.DEnsembleModel`：修正 feature selection 使用 `set(...)` 去重時，特徵欄位順序會受 Python hash randomization 影響，導致不同 Python 行程在相同資料與 seed 下仍可能產生不同模型輸出的問題；現在改為保留抽樣順序並去重",
          "references": [],
          "description": "`ml.DEnsembleModel`：修正 feature selection 使用 `set(...)` 去重時，特徵欄位順序會受 Python hash randomization 影響，導致不同 Python 行程在相同資料與 seed 下仍可能產生不同模型輸出的問題；現在改為保留抽樣順序並去重。",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-14",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "ml.DEnsembleModel"
          ],
          "trigger": "`ml.DEnsembleModel`：修正 pandas 3 Copy-on-Write 語意下，feature importance NaN 填補使用 chained `inplace=True` 可能不會寫回原 DataFrame 的問題，改用 assignment 形式填補",
          "references": [],
          "description": "`ml.DEnsembleModel`：修正 pandas 3 Copy-on-Write 語意下，feature importance NaN 填補使用 chained `inplace=True` 可能不會寫回原 DataFrame 的問題，改用 assignment 形式填補。",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-15",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "data.indicator",
            "finlab.ml.feature"
          ],
          "trigger": "行為變更、回測結果可能改變 `data.indicator(..., adj=True)` 改為真的使用還原股價計算",
          "references": [
            "https://github.com/finlab-python/finlab/issues/274"
          ],
          "description": "**行為變更**、**回測結果可能改變** `data.indicator(..., adj=True)` 改為真的使用還原股價計算。2.0.18 以前 `adj` 會原封不動傳給 TA-Lib，TA-Lib 靜默忽略不認得的參數，指標一直用未還原價計算，也不會報錯；`adjust_price=True` 則兩版都用還原價。`finlab.ml.feature` 的 `adj=True` 同樣改為用還原價計算指標：2.0.18 以前指標用未還原價，需要正規化的指標（均線類、`MACD`、`MOM` 等）卻除以還原收盤價。2.0.18 以前寫 `adj=True` 的程式，升到 2.0.19 以後指標數值、訊號與回測結果可能改變。同時對 `adj_open` / `adj_high` / `adj_low` / `adj_close` / `adj_volume` 等不支援的 kwargs 明確拋錯，避免使用者誤以為指標已使用還原價。（2026-09-30 補記行為變更，Issue #274）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-16",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "MaeMfeAnalysis.display"
          ],
          "trigger": "`MaeMfeAnalysis.display()`：移除對 Plotly `figure_factory.create_distplot` 的依賴，改用 `graph_objects` 建立分布圖，修正 Plotly 7 相容性",
          "references": [],
          "description": "`MaeMfeAnalysis.display()`：移除對 Plotly `figure_factory.create_distplot` 的依賴，改用 `graph_objects` 建立分布圖，修正 Plotly 7 相容性。",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-17",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "ffn_core"
          ],
          "trigger": "`ffn_core`：月頻轉換及 `freq=\"m\"`／`freq=\"y\"` 繪圖改用相容的月底／年底頻率，修正 pandas 3 的 `ValueError`；pandas 2.1 以上的既有用法與取樣日期不變",
          "references": [
            "https://github.com/finlab-python/finlab/issues/70",
            "https://github.com/finlab-python/finlab/pull/144"
          ],
          "description": "`ffn_core`：月頻轉換及 `freq=\"m\"`／`freq=\"y\"` 繪圖改用相容的月底／年底頻率，修正 pandas 3 的 `ValueError`；pandas 2.1 以上的既有用法與取樣日期不變。（#70；PR #144）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-18",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "Report.position_info2"
          ],
          "trigger": "`Report.position_info2()`：修正可轉債缺少未還原收盤價時，寫入還原價 fallback 會修改不可變 Index 的錯誤",
          "references": [
            "https://github.com/finlab-python/finlab/issues/64",
            "https://github.com/finlab-python/finlab/pull/143"
          ],
          "description": "`Report.position_info2()`：修正可轉債缺少未還原收盤價時，寫入還原價 fallback 會修改不可變 Index 的錯誤。一般股票仍優先使用未還原收盤價。（#64；PR #143）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-19",
          "results_may_change": true,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "report.metrics.sharpe_ratio",
            "report.metrics.sortino_ratio",
            "report.get_metrics"
          ],
          "trigger": "報表數字可能改變 `report.metrics.sharpe_ratio(riskfree_rate=...)`、`sortino_ratio(riskfree_rate=...)` 與 `report.get_metrics(riskfree_rate=...)`：每次呼叫都依傳入的 `riskfree_rate` 計算",
          "references": [
            "https://github.com/finlab-python/finlab/issues/127",
            "https://github.com/finlab-python/finlab/issues/223"
          ],
          "description": "**報表數字可能改變** `report.metrics.sharpe_ratio(riskfree_rate=...)`、`sortino_ratio(riskfree_rate=...)` 與 `report.get_metrics(riskfree_rate=...)`：每次呼叫都依傳入的 `riskfree_rate` 計算。過去同一份報表沿用第一次計算時的 `riskfree_rate`，之後不論傳入什麼值都得到同一組數字：先用預設值（0.02）算過之後，再傳入其他值沒有作用；第一次呼叫就帶了其他值時，之後用預設值呼叫，得到的也是那個值算出的數字。受影響的是 Sharpe、Sortino 這類用到無風險利率的指標；年化報酬、最大回撤、波動度、Calmar 不隨 `riskfree_rate` 改變。（#127；2026-09-30 補記，Issue #223）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-20",
          "results_may_change": false,
          "behavior_change": true,
          "experimental": false,
          "functions": [
            "PortfolioSyncManager.update"
          ],
          "trigger": "`PortfolioSyncManager.update()`：修正持股清單差異警告中的參數名稱，由不存在的 `force_override=True` 改為正確的 `force_override_difference=True`，更新判斷不變",
          "references": [
            "https://github.com/finlab-python/finlab/issues/75",
            "https://github.com/finlab-python/finlab/pull/142"
          ],
          "description": "`PortfolioSyncManager.update()`：修正持股清單差異警告中的參數名稱，由不存在的 `force_override=True` 改為正確的 `force_override_difference=True`，更新判斷不變。（#75；PR #142）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-21",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "PortfolioSyncManager.update"
          ],
          "trigger": "多策略組合：說明 `odd_lot`／`margin_trading` 選項需在每次 `update()` 傳入，且只套用到這次重建的策略",
          "references": [
            "https://github.com/finlab-python/finlab/issues/62",
            "https://github.com/finlab-python/finlab/pull/145"
          ],
          "description": "多策略組合：說明 `odd_lot`／`margin_trading` 選項需在每次 `update()` 傳入，且只套用到這次重建的策略。（#62；PR #145）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-22",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "sim"
          ],
          "trigger": "移動停利：釐清一般多單以進場成本為分母，並區分出場訊號與實際成交；進場 100、最高價 150、`trail_stop=0.2` 的門檻為 130",
          "references": [
            "https://github.com/finlab-python/finlab/issues/69",
            "https://github.com/finlab-python/finlab/pull/146"
          ],
          "description": "移動停利：釐清一般多單以進場成本為分母，並區分出場訊號與實際成交；進場 100、最高價 150、`trail_stop=0.2` 的門檻為 130。（#69；PR #146）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-23",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "Market.get_benchmark",
            "sim"
          ],
          "trigger": "自訂 benchmark：統一改用回測前覆寫 `Market.get_benchmark()` 的範例；更換比較基準需重跑 `sim()`，直接指定 `report.benchmark` 不會同步更新所有圖表與統計",
          "references": [
            "https://github.com/finlab-python/finlab/issues/77",
            "https://github.com/finlab-python/finlab/issues/80",
            "https://github.com/finlab-python/finlab/pull/147"
          ],
          "description": "自訂 benchmark：統一改用回測前覆寫 `Market.get_benchmark()` 的範例；更換比較基準需重跑 `sim()`，直接指定 `report.benchmark` 不會同步更新所有圖表與統計。（#77、#80；PR #147）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-24",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [
            "FinlabDataFrame"
          ],
          "trigger": "大盤擇時：以 `FinlabDataFrame` 的 `position *= timing` 取代刪除日期的篩選方式，使用既有自動對齊規則，移除不必要的 `reindex` 與轉型",
          "references": [
            "https://github.com/finlab-python/finlab/issues/112",
            "https://github.com/finlab-python/finlab/pull/148"
          ],
          "description": "大盤擇時：以 `FinlabDataFrame` 的 `position *= timing` 取代刪除日期的篩選方式，使用既有自動對齊規則，移除不必要的 `reindex` 與轉型。（#112；PR #148）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-25",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [],
          "trigger": "Pyodide wheel：只建置網站目前使用的 CPython 3.13 版本，避免隨 cibuildwheel 更新而產生不相容的 Python 3.14／3.15 wheel",
          "references": [
            "https://github.com/finlab-python/finlab/pull/157"
          ],
          "description": "Pyodide wheel：只建置網站目前使用的 CPython 3.13 版本，避免隨 cibuildwheel 更新而產生不相容的 Python 3.14／3.15 wheel。 （PR #157）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        },
        {
          "id": "2.0.19-26",
          "results_may_change": false,
          "behavior_change": false,
          "experimental": false,
          "functions": [],
          "trigger": "tests：將 CloudReport 新舊報告判斷的五個回歸測試標記為 `unit`，納入 CI 的 `pytest -m unit`；修正特徵名稱順序測試的 Ruff 檢查與格式問題",
          "references": [
            "https://github.com/finlab-python/finlab/issues/137",
            "https://github.com/finlab-python/finlab/issues/135"
          ],
          "description": "tests：將 CloudReport 新舊報告判斷的五個回歸測試標記為 `unit`，納入 CI 的 `pytest -m unit`；修正特徵名稱順序測試的 Ruff 檢查與格式問題。（#137、#135）",
          "changelog_url": "https://finlab.finance/docs/change-log/#2019-2026-09-16"
        }
      ]
    }
  ]
}
