還原股價要用向前復權還是向後復權?回測該選哪個?
回測用向後復權(後復權)。差別在錨點:向前復權以最近價格為基準往回調,所以每發生一次除權息,整段歷史價格就被改寫一次——同一份回測今天跑和下個月跑會得到不同數字。向後復權以最早價格為錨,新事件只往後加,歷史不變,回測才可重現。
還原股價
把除權、除息、分割造成的價格跳空接回去,讓不同時間的股價可以互相比較。
先看清楚差在哪:錨點不同,不是精度不同
- 向前復權(前復權)=以最近一根為錨往回調。每次新的除權息發生,之前所有歷史價格都會被重算一次。
- 向後復權(後復權)=以最早一根為錨往後調。新事件只影響它之後的價格,已經算好的歷史不動。
- 兩者畫出來的報酬率曲線形狀一樣,差別在「歷史數字會不會變」。多數看盤軟體顯示前復權,因為它讓最新價格等於實際成交價,看盤方便。
為什麼回測必須用後復權
前復權的歷史是浮動的。你在 1 月跑出一組進出場點與績效,3 月那檔除息一次,同一份程式碼再跑一次,1 月那段的價格已經跟當時不同了——訊號可能沒觸發、停損可能沒打到。回測最基本的要求是可重現,前復權違反的正是這一點。後復權沒有這個問題:新事件只往後加。
還原價不是你能成交的價格
不論哪種復權,還原價都是為了「跨時間比較」而算出來的數字,不是當天市場上真的能買到的價格。用它算報酬率沒問題,但如果你的回測要模擬實際成交、滑價或漲跌停限制,那幾個判斷要回到原始價格去做。
我們供應的是原始價 + 官方調整因子
這裡沒有一支「算好的還原股價」端點。供應的是兩件事:twse-daily-price 的原始開高低收,以及 price-enhanced 的官方除權除息調整因子(來源 TWSE TWT49U)。後復權由你自己算——多一個步驟,但你完全知道每一個數字是怎麼來的,也可以自行驗證。
兩支端點的名字不一樣,連查詢參數都不一樣
- twse-daily-price:查詢參數用 symbol,回應欄位是 symbol 與 date。
- price-enhanced:代號參數用 ticker,日期區間用 date_from / date_to,回應欄位是 ticker 與 trade_date。
- 送錯名字的後果不一樣:對這支送 symbol 會直接回 422,但送 start_date / end_date 不會報錯——它會被靜默丟掉,回給你未過濾的全部歷史。實測用 2020 的區間查,送 start_date/end_date 回了 45 列、橫跨 2003 到 2026;送 date_from/date_to 才正確回 4 列。
- 所以兩邊 join 起來時,鍵名要各自對應,不能假設同名。這些是實際打過的結果,不是文件上的宣告——值得自己打一次確認。
import requests
HEADERS = {"X-API-Key": "sk_live_你的金鑰"}
BASE = "https://api.twmarketdata.com/v2/datasets"
def rows_of(payload):
"""兩種信封都接:價格端點回 rows,調整因子端點回 envelope.data(皆為實測)。"""
return payload.get("rows") or payload.get("envelope", {}).get("data", [])
factors = rows_of(requests.get(
f"{BASE}/price-enhanced",
headers=HEADERS,
# 這支的參數名跟價格端點都不一樣:代號是 ticker(不是 symbol),
# 日期區間是 date_from / date_to(不是 start_date / end_date)。
params={"ticker": "2330", "date_from": "2024-01-01", "date_to": "2026-07-31"},
timeout=20,
).json())
for f in factors:
print(f["trade_date"], f["event_type"],
"factor", f["factor"],
"事件前收盤", f["pre_event_close"],
"參考價", f["reference_price"])先驗證因子的方向,再拿它算
在實際回應上,factor 等於 reference_price ÷ pre_event_close(例:參考價 5.84 ÷ 事件前收盤 5.93 = 0.98482293,與該列的 factor 完全相同)。但別只因為這裡這樣寫就相信——每一列都同時給了這兩個欄位,你可以自己對一列做除法確認,再套到整段歷史。
# 用資料自己驗證因子方向:參考價 ÷ 事件前收盤,跟 factor 對照for f in factors[:3]: pre, ref = f["pre_event_close"], f["reference_price"] if pre and ref: print(f["trade_date"], f["event_type"], "ref/pre =", round(ref / pre, 6), "| factor =", f["factor"])算後復權:累乘,且只往後影響
確認方向之後,後復權就是把每個事件的因子累乘上去,套用在事件之後的價格上。錨點放在序列最早那一根,所以新的除權息只會加在尾端,已經算過的歷史不會變。
prices = rows_of(requests.get( f"{BASE}/twse-daily-price", headers=HEADERS, params={"symbol": "2330", "start_date": "2024-01-01", "end_date": "2026-07-31"}, timeout=20,).json()) # 以最早一根為錨,事件因子累乘後只套用到該事件之後的交易日events = sorted(factors, key=lambda f: f["trade_date"])cumulative, applied = 1.0, []for row in sorted(prices, key=lambda r: r["date"]): while events and events[0]["trade_date"] <= row["date"]: cumulative *= float(events.pop(0)["factor"]) applied.append({**row, "close_back_adjusted": row["close"] * cumulative}) for row in applied[-3:]: print(row["date"], "原始", row["close"], "後復權", round(row["close_back_adjusted"], 2))沒有因子的日子,不會被假裝成有
價格資料每一列都帶 price_method 與 price_confidence,告訴你這個數字是怎麼來的、可信度如何。沒有調整因子的期間就是沒有,不會拿原始收盤價冒充還原價——序列被補得漂亮,回測就會騙你。
我們沒有的東西,先說清楚
- 沒有一支「算好的還原股價」端點。供應的是原始價與官方調整因子,後復權要你自己算(上面的程式碼就是)。
- 沒有即時報價。這裡是歷史與盤後資料。
- 調整因子來自 TWSE 的除權除息資料,涵蓋範圍以資料集頁標示為準,不做推估補值。
常見問題
1為什麼看盤軟體都用前復權?
2後復權的價格看起來跟市價差很多,正常嗎?
3含息還是不含息?
4這跟避免前視偏誤有關係嗎?
想自己查這些資料?免費註冊。