跳至主要內容
台股研究 約 12 分鐘閱讀

量化交易 Python 教學:不用上課,
30 行內跑出第一個台股回測(Sharpe 1.48 實證)

量化交易 Python 怎麼入門?pip install finlab 之後,不到 30 行程式碼跑出第一個台股回測。本文用營收動能+價格動能+低波動三因子策略實證:2020-2026 Sharpe 1.48、CAGR 20.9%、最大回撤 -13.3%,附可下載 strategy.py 與回測方法揭露。

30行內跑出第一個台股回測 CAGR 0.2% Sharpe 1.48

Python 量化交易策略 vs 含息 0050 累積淨值曲線(2020-2026,Sharpe 1.48)

量化交易 Python 入門最常見的卡點是順序錯了:多數人還沒跑出任何一個回測結果,就先報名了數十小時的課程,然後在 pandas 語法和爬蟲除錯裡耗盡耐心。這篇教學走相反的順序:pip install finlab 之後,把一段不到 30 行的程式碼貼進去執行,今天就得到一個完整的台股回測,2020-2026 實測 Sharpe 1.48、最大回撤 -13.3%,理論留到跑通之後再補。

示範策略可下載、可重現:營收動能(月營收年增率)、價格動能(120 日報酬)、低波動(120 日報酬標準差,負向)三因子複合,每月選 30 檔、反波動加權、加上大盤站上 120 日均線才進場的濾網。月營收是台股每月公布的基本面資料,拿它當教學主角,也順便把「財報類資料如何對齊公布日」這個新手最常踩的坑示範一次。量化交易的觀念、策略類型與平台全景,整理在 量化交易完整指南;本文只做一件事:讓你的第一個回測動起來。

關鍵數字

下表是這個策略的真實回測結果。招牌期間取 2020-01 至 2026-06,同時附上全期 2007-2026 數字(兩者差異在下方「回測方法」段落會說清楚)。

指標 本策略 2020-2026 本策略 全期 2007-2026 含息 0050(同期 2020-2026)
年化報酬 CAGR 20.9% 14.5% 29.1%
夏普比率 Sharpe(日頻年化) 1.48 0.99 1.21
Sortino 2.25 1.49 2.05
Calmar 1.57 0.74 0.86
最大回撤 -13.3% -19.7% -34.0%
年化波動 12.5% 12.9% 22.4%
alpha / beta(對 0050 日報酬) 0.118 / 0.288
月勝率 59.7% 54.0% 62.3%

兩件事要先講白:第一,raw 報酬上含息 0050 同期 CAGR 29.1%,高過本策略的 20.9%,那是台積電權值股超級多頭的結果,而且 0050 連月勝率都略高(62.3% 對 59.7%)。第二,本策略的價值在風險調整後:Sharpe 1.48 高於含息 0050 同期的 1.21(兩者用同一個 sim() 引擎、同一窗口實跑,0050 以還原權息價格、fee_ratio=0 計算),最大回撤 -13.3% 對上 0050 的 -34.0%。所以本文一律用「以較低回撤、較高 Sharpe 的風險調整後勝出」描述,不會宣稱「報酬贏過大盤」。關於這條口徑,你可以對照我們在 多因子選股能否打敗 0050 裡的同一基準討論。

策略版的完整互動式回測報告直接嵌在下面,逐年、逐月、持股明細都可以點開檢視:

對照組(含息 0050 買進持有,同窗、零成本)的互動報告也公開:含息 0050 買進持有(2020-2026)回測報告

量化交易 Python 四條入門路線:自學、上課、K 線平台,還是 pip install finlab

量化交易的第一道門檻不是策略,是資料。台股的調整收盤價、財報公布日對齊、成交金額、全額交割註記、產業分類,這些散落在公開觀測站、交易所、櫃買的資料,自己用 requests 爬、用 pandas 清,光是把前視偏差處理乾淨就能耗掉好幾週。線上課程與系列教學文則是把這段苦工拍成影片教你做一遍,苦工本身並沒有消失。

四條常見路線攤開比較:

路線 台股資料 回測引擎 投入成本 距離第一個回測
純自學(requests + pandas 自己爬) 自己爬、自己清、自己對齊財報公布日 自己寫,容易埋前視偏差 金錢免費,時間以週計 數週起跳,且難驗證對錯
線上課程 / 系列教學文 多半教你接 API 或爬蟲,資料清理與公布日對齊仍要自己做 跟著課程手刻,或再學一套回測框架 課程費用 + 數十小時跟課 跟完課之後才開始
MultiCharts / TradingView 偏單一商品價量,台股全市場財報資料不齊 內建,但難做橫斷面選股 軟體或平台費用 做不出多因子排名選股
pip install finlab 台股價量、財報、月營收、籌碼一套到位 sim() 已內扣手續費與證交稅 套件免費,資料有免費試用 十分鐘

課程不是沒有價值:想系統性打好 pandas 與資料工程底子的人,跟一門課是合理投資。問題在順序。對多數新手,「先跑出一個真實結果,再回頭補理論」遠比「先學完才動手」有效,因為你會帶著具體問題去學:為什麼月營收要對齊公布期限?為什麼權重要正規化?這些問題在你跑通第一個回測之前,課程講了你也記不住。

MultiCharts 與 TradingView 擅長的是單一商品的進出場訊號,但量化選股的核心是「每月在全市場做橫斷面排名,挑出前段班」,這類運算在 K 線型平台上很難自然表達。finlab 的差異在於它是一個 pip 套件:資料、橫斷面排名、月頻換股、成本內扣都在同一個 DataFrame 流程裡。想更完整地比較工具,可參考 FinLab vs QuantPassFinLab vs TEJ 兩篇評估;若你的最終目標是讓訊號自動下單而非手動換股,整條路的全景在 程式交易是什麼

如果你還不確定「回測」到底在做什麼,讀 什麼是回測 補概念;「量化分析」這個詞背後的方法論,則見 什麼是量化分析

環境安裝:pip install finlab 與登入

整個量化交易程式環境只需要兩步。先安裝套件:

顯示程式碼
pip install finlab

接著登入。第一次使用時,finlab 會自動引導你完成登入(照套件指示在瀏覽器授權即可),不需要手動貼上任何 token:

顯示程式碼
import finlab
finlab.login()   # 第一次執行會自動開啟登入流程,照 finlab 套件的指示完成即可

登入成功後,所有台股資料都用 data.get('資料表名稱') 取得,回傳的是一個增強版的 pandas DataFrame。取單一張價量表的最小範例如下(這也是 用 Python 取得台股資料 的起點):

顯示程式碼
from finlab import data
close = data.get('price:收盤價')   # 全市場每日收盤價,index 是日期、欄位是股票代號
close.tail()

到這裡你的量化交易 python 環境就完成了。接下來把三個因子一個一個拼起來。

三因子複合策略逐步講解

這個策略的設計目標放在風險調整後表現,用三個方向互補的因子換取更平滑的權益曲線與更淺的回撤,而非單純衝高 CAGR。三個因子各自做全市場橫斷面百分位排名,再取平均當綜合分數。順帶說明:我們在 台股選股回測 用的是品質(ROE)為核心的另一組複合因子,回答「哪類選股因子長期有效」;本篇的因子組合與回測窗口刻意與它區隔,主軸是「最快跑通第一個回測」,兩篇可互相對照但不重複。

因子一:營收動能(月營收年增率)

台股是少數要求上市櫃公司每月申報營收的市場(次月 10 日前公布),這讓月營收成為更新頻率最高的基本面資料。基本面好消息公布後,股價平均不會一次反應完,而是延遲漂移一段時間,Bernard & Thomas (1989) 對盈餘公布後漂移(PEAD)的實證是這類「基本面動能」的源頭。關鍵是必須用 index_str_to_date() 把月營收對齊到實際公布期限,否則會用到當下還拿不到的資料,產生前視偏差:

顯示程式碼
rev_yoy = data.get('monthly_revenue:去年同月增減(%)').index_str_to_date().reindex(close.index, method='ffill')

因子二:動能(120 日報酬)

動能因子捕捉「近半年漲得相對強的股票傾向繼續強」這個橫斷面現象,實證源頭是 Jegadeesh & Titman (1993):過去 3 到 12 個月相對強勢的股票,在接下來數月平均仍跑贏弱勢股。本文用 120 個交易日(約半年)的價格比值,落在這個學術上驗證過的形成期區間內:

顯示程式碼
momentum = close / close.shift(120)

因子三:低波動(120 日報酬標準差,負向)

低波動因子是這個策略壓低回撤的關鍵。Ang, Hodrick, Xing & Zhang (2006) 記錄了所謂「低波動異常」:特異波動高的股票,後續風險調整後報酬反而偏低。本文用日報酬的 120 日滾動標準差,並加上負號,讓「波動低」的股票排名靠前:

顯示程式碼
low_vol = -close.pct_change().rolling(120).std()

三因子合成、選股與加權

三個因子各自做橫斷面百分位排名(rank(pct=True))後平均,得到綜合分數;每月選分數最高的 30 檔;權重用 60 日波動的倒數(反波動加權,波動低者權重高);最後乘上大盤濾網,加權報酬指數站上 120 日均線才進場,否則整體空手抱現金。

顯示程式碼
from finlab import data
from finlab.backtest import sim
 
# ── 1. 載入價量資料(登入已在上一步用 finlab.login() 自動完成)──
close = data.get('price:收盤價')
volume = data.get('price:成交股數')
 
# ── 2. 定義可交易股票池:流動性、低價股、全額交割、特殊類別過濾 ──
amount = (close * volume).average(60)            # 60 日均成交金額
flagged = data.get('etl:is_flagged_stock').reindex(close.index, method='ffill').fillna(False)
categories = data.get('security_categories').set_index('stock_id')['category']
excluded = ['金融保險', 'ETF', 'ETN', '存託憑證', '受益證券', '創新板']
bad_ids = set(categories[categories.isin(excluded)].index)
keep = [c for c in close.columns if c not in bad_ids]
universe = (amount > 50_000_000) & (close > 10) & (~flagged)
 
# ── 3. 計算三個因子 ──
# 營收動能:月營收年增率,用 index_str_to_date() 對齊實際公布日,避免前視偏差
rev_yoy = (data.get('monthly_revenue:去年同月增減(%)')
           .index_str_to_date()
           .reindex(close.index, method='ffill'))
momentum = close / close.shift(120)                       # 價格動能:120 日報酬
low_vol = -close.pct_change().rolling(120).std()          # 低波動:120 日報酬標準差(負向)
 
# ── 4. 三因子各自橫斷面百分位排名後平均,得到綜合分數 ──
cols = sorted(set(keep) & set(rev_yoy.columns))
 
def cross_rank(df):
    return df[cols].rank(axis=1, pct=True)               # 橫斷面百分位排名
 
score = (cross_rank(rev_yoy) + cross_rank(momentum) + cross_rank(low_vol)) / 3
score = score.where(universe[cols])
 
# ── 5. 每月選分數最高的 30 檔,反波動加權(波動低者權重高)──
selected = score.rank(axis=1, ascending=False) <= 30
inv_vol = (1 / close.pct_change().rolling(60).std())[cols]
weight = selected * inv_vol
weight = weight.div(weight.sum(axis=1), axis=0).fillna(0)
 
# ── 6. 大盤濾網:加權報酬指數站上 120 日均線才進場,否則空手 ──
twii = data.get('benchmark_return:發行量加權股價報酬指數').iloc[:, 0]
market_up = (twii > twii.rolling(120).mean()).reindex(close.index, method='ffill').fillna(False)
weight = weight.mul(market_up.astype(int), axis=0)
 
# ── 7. 回測:月頻換股,手續費打三折(sim() 預設已內扣證交稅)──
report = sim(weight, resample='M', fee_ratio=1.425/1000/3, name='營收動能低波複合')
report.display()

完整可執行腳本可直接下載:strategy.py。第一次執行時 finlab 會引導你登入,完成後跑完就會看到下面這張權益曲線。

回測結果

下圖是策略累積淨值對照含息 0050 的曲線。可以看到 2020-2026 期間策略走勢相對平滑,在 2022 年台股回檔時(大盤濾網把部位降到現金)避開了大部分跌幅:該年策略 -2.9%,含息 0050 是 -21.4%。

Python 量化交易策略 vs 含息 0050 累積淨值曲線(2020-2026,Sharpe 1.48)

逐年報酬對照如下。策略並非每一年都領先,2024、2025 這種權值股大多頭年明顯落後 0050;它的強項在下跌年虧得少,這正是低波動因子加大盤濾網的設計目的。

台股量化策略 vs 含息 0050 年度報酬率比較

招牌期間的完整指標:Sharpe 1.48、Sortino 2.25、Calmar 1.57、CAGR 20.9%、年化波動 12.5%、alpha 0.118、beta 0.288(對含息 0050 日報酬迴歸)、月勝率 59.7%。beta 只有 0.288,代表策略對大盤的敏感度低,這也是它在風險調整後能勝出的結構性原因。

風險與回撤

回撤(drawdown)比 CAGR 更能反映你實際抱得住抱不住。下圖是策略的回撤曲線,最大回撤 -13.3%,出現在波動最劇烈的區間。

量化交易策略回撤比較,最大回撤 -13.3%

-13.3% 的最大回撤,搭配 20.9% 的 CAGR,得到 Calmar 1.57(報酬除以最大回撤)。含息 0050 同期最大回撤 -34.0%,這是兩者風險輪廓最直觀的差距。需要提醒的是:過去回撤淺不保證未來不會更深,全期 2007-2026 的最大回撤就有 -19.7%,任何單一因子失效或市場結構改變,都可能讓回撤超出歷史區間。

回測方法

漂亮的數字若不交代怎麼算出來,就只是廣告。以下逐項說明這份回測的設定,有做的寫出數值,沒做的直接標明「未做」:

  • 交易成本: finlab 的 sim() 預設已內扣手續費與證交稅。程式裡 fee_ratio=1.425/1000/3 代表手續費以 0.1425% 打三折計算(約 0.0475%);賣出證交稅 0.3% 由 sim() 自動扣除。
  • 滑價: 本文未額外假設滑價。月頻換股、單檔權重分散,衝擊相對可控,但實際衝擊取決於資金規模與當下成交量,本文未估算到滑價層級。
  • 股票池: 全上市櫃個股,經下列流動性與類別過濾後的集合。
  • 流動性過濾: 60 日均成交金額 > 5,000 萬元、股價 > 10 元,確保回測買得到的股票實單也買得到。
  • 排除類別: 排除全額交割股(etl:is_flagged_stock)、金融保險、ETF、ETN、存託憑證、受益證券、創新板。排除全額交割與特殊類別是為了降低生存者偏誤與不可交易的標的。
  • 前視偏差: 月營收用 index_str_to_date() 對齊實際公布期限(次月 10 日),再 ffill 到每日,確保每一天只用到當天真正已公布的營收;動能與低波動只用歷史價格,天然無前視。
  • 基準口徑: 含息 0050 用同一個 sim() 引擎、同一窗口實跑(權重恆為 1、fee_ratio=0),報酬以還原權息價格計算,不會因漏掉配息而低估基準。
  • 權重: 反波動加權(60 日波動倒數),非等權;權重在 30 檔之間正規化加總為 1。本文未設單檔上限,反波動加權本身會壓低個股集中度。
  • 周轉率: 月頻換股(resample='M')。本文未逐期估算 turnover 數值,標明未估。
  • 樣本內外: 本文為全段 in-sample(frontmatter 已標注 in_sample_only),未做嚴格的樣本外保留期測試;改以下一節的參數網格作為穩健性佐證,並會在季度刷新時補上發佈日之後的前瞻區間績效。
  • 全期 vs 招牌期間對照: 招牌數字取 2020-2026(Sharpe 1.48、CAGR 20.9%);全期 2007-2026(資料可回溯的最早起點,含 2008 金融海嘯)為 Sharpe 0.99、Sortino 1.49、CAGR 14.5%、最大回撤 -19.7%。兩者差異主要來自 2020 後的台股多頭環境,讀者應以全期數字作為較保守的預期基準。

關於「回測看起來很好、實單卻失靈」的系統性風險,量化投資的挑戰與過擬合 有更完整的討論,建議搭配閱讀。

參數穩健性

單一組參數跑出 Sharpe 1.48 不能說明問題。Bailey, Borwein, López de Prado & Zhu (2014) 證明了一件對所有回測者都不舒服的事:嘗試的參數組合愈多,光憑運氣挑出漂亮 Sharpe 的機率就愈高,這就是回測過擬合。所以真正該問的是:換個持股數、換個大盤濾網長度,結論會不會崩掉?下圖是 2020-2026 的參數網格結果。

持股數與大盤濾網參數穩健性對 Sharpe 的影響

持股 15 / 20 / 25 / 30 檔,搭配大盤濾網 100MA 或 120MA,Sharpe 落在 1.31 到 1.51 之間,代表「策略風險調整後優於 0050(1.21)」這個結論對「選幾檔、用多長均線」並不敏感。明顯下滑的是把濾網拉長到 150MA,Sharpe 掉到 1.031.22,因為較長均線反應太慢、退場太晚。另外要照 Bailey 等人的警告說明:本文採用的 30 檔 × 120MA(1.48)接近網格中最好的一格,存在選擇效應,把鄰近組合的 1.311.42 當成合理預期區間,比盯著 1.48 務實。

我們在台股選股回測的單因子分測看到:低波動因子報酬墊底,但最大回撤只有 -13.6%,是所有因子裡最小的;營收動能與價格動能則提供報酬來源。三者合成的目的就是讓任一因子失效時,其他兩者仍能撐住整體。

風險調整後的對照

這一節必須講清楚,以免被當成又一篇浮誇的策略文。

含息 0050 在 2020-2026 累積約 5.2 倍、CAGR 29.1%,raw 報酬高過本策略的 20.9%(累積約 3.3 倍)。原因很單純:這段期間台積電等權值股走超級多頭,被動持有 0050 等於重壓贏家。所以就純報酬而言,本策略並沒有贏過含息 0050

本策略的價值在另一個維度:以較低回撤、較高 Sharpe 的風險調整後勝出。同期 Sharpe 1.48 高於含息 0050 的 1.21,年化波動 12.5% 只有 0050(22.4%)的一半多一點,最大回撤 -13.3% 對上 -34.0%。換句話說,它用較低的長期報酬,換到更平穩的曲線與更小的最大虧損。對於抱不住大跌、會在低點停損的投資人,這個風險輪廓的實際意義可能比 raw CAGR 更重要。這個「每承擔一單位風險換到多少報酬」的衡量框架由 Sharpe (1994) 定式化,計算細節與判讀見 夏普比率

新手下一步

如果這是你的第一個量化交易程式,建議照這個順序往下走:

  1. 先把上面的 strategy.py 跑通,確認自己的環境與 token 沒問題。
  2. 一次只改一個因子或一個參數,觀察 Sharpe 與回撤怎麼變,建立對因子的直覺。
  3. FinLab 量化平台新手指南 了解更多資料表與 sim() 參數。
  4. 把選股邏輯換成你自己的想法,可參考 台股選股 5 步驟如何在台股選股
  5. 想理解因子有沒有效,讀 資訊係數 IC 是什麼;想了解 AI 在量化研究的角色,讀 AI 量化研究
  6. 進階階段務必留意風險面:量化交易的缺點與風險量化交易的職涯與薪資,以及若你考慮券商生態,可看 富途牛牛量化交易在台灣

想直接動手?finlab 的資料與回測引擎有免費試用,免費註冊開始,把本文的 strategy.py 跑通、再換上你自己的因子,比看十篇文章更有感覺。

需要策略類型全景、優缺點與平台比較時,回到 量化交易完整指南;所有名詞若有不熟,都可以到 詞彙表 查核心定義後再回來。

FAQ

Q1:量化交易 Python 需要先上課或先學完 Python 嗎? 不需要。本文的策略整段不到 30 行,你只要能把程式碼貼到環境裡、把 token 換成自己的、按下執行,第一個回測今天就會跑出來。「跑得起來」之後再「看得懂」,接著一次改一個參數,是比先跟完數十小時課程更快的學習路徑;課程留給之後想系統性補 pandas 與資料工程底子時再說。

Q2:量化交易程式需要多少錢才能開始? 軟體層面,pip install finlab 本身免費,Python 也免費。資料下載需要 finlab 帳號,有免費試用可先跑回測驗證流程。真正要花錢的是實際投入市場的本金,以及換股產生的手續費與證交稅(本文回測已把這兩項內扣)。

Q3:量化交易 python 和一般技術分析程式有什麼不同? 技術分析程式多半針對單一商品判斷進出場訊號(例如均線交叉)。量化選股則是每月在全市場做橫斷面排名,挑出相對最好的一批股票。本文的三因子複合屬於後者,核心運算是 rank(axis=1) 的橫斷面排名,而非單檔的 K 線訊號。

Q4:回測 Sharpe 1.48 準嗎?實單能複製嗎? 回測是「在歷史資料上的模擬」,準的前提是方法乾淨:本文已處理前視偏差(月營收對齊公布期限)、流動性過濾、排除不可交易類別、內扣成本。即便如此,實單仍會遇到滑價、資金容量、策略隨時間衰退等回測難以完全反映的因素。把全期 2007-2026 的 Sharpe 0.99 當較保守的預期會更務實。

Q5:這個策略的容量(資金上限)大概多少? 假設每檔股票單日成交量參與率不超過 60 日均成交金額的 10%,對 2020-2026 共 61 次月再平衡逐次計算「各持股的 10% 均成交金額除以權重」的最小值,中位數約 NT1.2億、第10百分位約NT1.2 億、第 10 百分位約 NT0.8 億。也就是資金規模到億元等級,部分月份的換股就會開始頂到流動性,需要放寬持股檔數或調整流動性門檻;一般個人資金距離這個上限還很遠。

Q6:為什麼不直接買含息 0050 就好? 就 2020-2026 的純報酬而言,含息 0050 的 CAGR 29.1% 確實高過本策略的 20.9%。本策略的訴求是風險調整後:Sharpe 1.48 高於 0050 的 1.21、最大回撤 -13.3% 對上 -34.0%。適不適合你,取決於你更在意 raw 報酬,還是更在意曲線平穩與回撤控制。

Q7:多久要重新換股?換太頻繁會被成本吃掉嗎? 本策略月頻換股(resample='M')。月頻在台股是成本與訊號新鮮度之間相對平衡的選擇,回測已內扣手續費與證交稅。若改成週頻,turnover 與成本都會上升,需要重新驗證淨報酬是否還划算。

Q8:可以把這套策略套用到我自己挑的因子嗎? 可以,而且這正是建議的進階方向。把 rev_yoymomentumlow_vol 任一個換成你自己的因子(例如 ROE、籌碼指標),其餘合成、選股、加權、濾網的框架不變。換因子後務必重跑回測與參數網格,確認新組合在風險調整後仍站得住。

投資有風險,過去績效不代表未來表現,本文僅供教學參考,不構成投資建議。回測數字會隨資料更新而變動。

最後更新:2026-06|回測區間:2020-01~2026-06(另列全期 2007-2026)|作者:FinLab 量化研究團隊(經量化研究員審閱)


參考文獻

FinLab AI

想建立自己的策略?

用自然語言描述你的選股想法,AI 自動驗證、回測、給你答案

免費開始

更多技術指標研究

查看全部