「我出邏輯、AI 出代碼、我來負責踩雷與排雷。」
我是 QTnewbie。本週是我自學計畫的第一週。但我立刻就遇到了一個讓人懷疑人生的問題:為什麼同樣的策略,換了一種數據,我的報酬率會直接差了70%?
- 本週實驗目標: 追蹤消失的 70% 獲利
- 本週踩雷點:
Close(收盤價) 數據的隱形帳單
在量化金融領域,大家總說回測要用 Adj Close。但對我這種剛進場的小白來說,Close (收盤價) 看起來親切多了,圖表長得完全沒問題啊。
難道這又是金融圈用來唬弄新手的術語?
一、 這不是消失的密室,是消失的 70% 獲利
為了搞清楚,我做了一個很單純(甚至有點天真)的實驗。
- 標的:00878(國泰永續高股息)
- 回測時間:2020/07/10 ~ 2026/03/27
- 小白策略: 簡單粗暴的「站上均線就買,跌破就賣」。股價 > 20 天平均線 (MA20) 買進,反之賣出。
# 策略邏輯:當 Close > MA20 買入 (持有=1), 否則賣出 (持有=0)
data['MA20_Close'] = data['Close'].rolling(window=20).mean()
data['Signal_Close'] = np.where(data['Close'] > data['MA20_Close'], 1, 0)

然後,我算出報酬率—— 193.90%
老實說,我當下的心情是:
「欸?這樣就可以了嗎?量化也太簡單了吧?」
(謝謝大家,我們今天到這裡結束——)
但糾斗媽爹,我們先冷靜一下。
既然大家一直強調 Adj Close,我還是決定做一件負責任的事:
同一個策略,用 Adj Close 再跑一次

結果出來的那一刻,我真的愣住—— 263.30%
整整比 Close 多出了快 70%!
一樣的策略、一樣的時間,為什麼換個數據,我的錢就多出了「一整台特斯拉」的價差?到底是 Adj Close 會點石成金,還是 Close 偷偷吃了我的錢?
二、 Adj Close 的身世之謎:誰偷了我的股利?
為了解密這 70% 的差距,我開始查Adj Close 到底是什麼。
原來,問題出在「除息」。
股票配息時,股價會往下掉。想像一下:
昨天股價 100 元。
今天公司配息 5 元給你(你的銀行帳戶多了 5 元)。
今天股價自動變成 95 元(除息)。
收盤時漲回 97 元(填息一部分)。
這時候,程式碼的「冷血邏輯」就出事了:
它只會計算 (今天收盤價 97 - 昨天收盤價 100) / 100 = -3%。 在程式的眼裡,你虧了 3 塊錢。但現實中,你口袋裡明明有 5 元股息加上 97 元股票,總價值是 102 元,你其實賺了 2 塊錢!
程式碼直接忽略了你領走股利這件事。對它而言,那 5 塊錢像是突然從人間蒸發了。
長期下來,00878 這種季季配息的股票,你的報酬率就像一個底下破洞的存錢筒,每三個月就漏一次錢,難怪最後差了 70%!
三、 Adj Close 的計算玄學:還原記憶的橡皮擦
Adj Close 的做法其實很單純,本質就是一件事 :
「既然你領到了 5 元,那我就把這 5 元當作是你當初買股票的『退款』。」
所以,它會跑回歷史紀錄,把你當初買進的 100 元,改成 95 元。
這就像是百貨公司的「買貴退差價」。因為你拿到了配息,所以你過去買股票的「成本變低了」。當成本變低,你算出來的漲幅自然就變大。累積四年下來,00878 這種高頻率配息的股票,透過這種「追溯減價」疊加出來的複利效應,就是那消失的 70% 獲利來源!
為了做到「追溯減價」這件事,Adj Close 會乘以一個「調整係數」把過去的股價往下縮,讓歷史股價看起來平滑連續。

(AI 幫我手刻了一版,我貼在下面👇)
def calculate_manual_adj(price_df):
"""
手動計算調整因子:從最後一次配息往回連乘。
為什麼要倒著算?因為我們要保持「今天」的價格真實,去修正「過去」的價格。
"""
adj_factors = pd.Series(1.0, index=price_df.index)
div_dates = price_df[price_df['Dividends'] > 0].index
# 從最新的配息倒推回過去
for ex_date in div_dates[::-1]:
div_amount = price_df.loc[ex_date, 'Dividends']
prev_idx = price_df.index.get_loc(ex_date) - 1
if prev_idx >= 0:
pre_ex_price = price_df.iloc[prev_idx]['Close']
# 計算調整因子:(除息前股價 - 股利) / 除息前股價
factor = (pre_ex_price - div_amount) / pre_ex_price
# 將該日期之前的所有歷史價格都乘以這個因子,縮小過去的股價
adj_factors.iloc[:prev_idx + 1] *= factor
return price_df['Close'] * adj_factors

你會發現,當我們把Close和 Adj Close 放在一起比較,因為不停乘以小於 1 的係數,越久以前的 Adj Close 就會被縮得越小。
也就是說,越早期的價格,會被壓得越低。
四、 小白的碎碎念環節:理論與現實的拉扯
在本週的排雷實驗中,我總結出幾件重要的事情:
- 小心「隱形帳單」: 如果你只看
Close做回測,你其實是在模擬一個「領到股利後就把現金丟進大海」的行為。你的策略沒變,但你的錢就在這四年中慢慢漏掉了。 - 關於「假訊號」的都市傳說: 網路上很多文章警告說,用
Close會因為除息的大跳空導致技術指標(如 MA20)發生「骨折」,發出錯誤的賣出訊號。但我實測 00878 的結果發現:其實沒那麼明顯。 可能是因為這幾年是強勢牛市,股價漲幅完全蓋過了除息的跌幅,所以指標並沒有因此崩潰。不過,這不代表它不存在,只是剛好這次被運氣救了一命。 - 穿越時空的虛擬股價: 雖然
Adj Close找回了獲利,但也帶來了一個很靈異的現象。 回頭看 2022 年,我的Adj Close資料顯示股價只有 10.0 元 左右,但事實上,當年翻遍報紙,00878 的最低價明明還有 12.多塊。
本週排雷總結:
- 踩到的雷: 用
Close做長線回測,會嚴重低估你的複利能力。 - 避雷方案: 算報酬率時請認準
Adj Close。
下期劇透:既然 Adj Close 這麼好,幫我們還原了真實獲利,那為什麼它顯示的「10.0 元」是真實世界裡根本沒出現過的價格?如果我當時真的設了一個「股價低於 12 元就買進」的指令,這場回測是不是就會變成一場「根本不可能成交」的穿越劇?
👉 下週目標:「Adj Close 是解答,還是另一個陷阱?」
我們下期見!

















