請更新您的瀏覽器

您使用的瀏覽器版本較舊,已不再受支援。建議您更新瀏覽器版本,以獲得最佳使用體驗。

理財

從羅伯.加迪納的選股法出發:在台股尋找小型成長黑馬

TEJ 台灣經濟新報

更新於 07月15日13:07 • 發布於 07月15日05:07
Photo by Juliana Araujo the artist on Unsplash

前言

在投資市場中,小型成長股因具備高度成長潛力與價格彈性,長期以來吸引著專業投資人與基金經理人的關注。美國知名基金經理人羅伯.加迪納(Robert Gardiner)便是此領域中的代表人物。他以管理瓦薩屈微型股基金(Wasatch Micro Cap Fund)聞名,曾在2000年與2001年美股震盪期間,依然交出超過三成與近五成的年報酬率,表現遠勝大盤,顯示其選股邏輯具備極強的抗震與增長能力。

羅伯.加迪納的選股策略核心在於尋找市值偏小、盈餘成長力強且具備基本面優勢的公司。他透過獨創的 ABGC 架構,結合 GARP(合理成長價格)理念,聚焦於 PEG 值低於 1 的潛力股,建構出一套系統性但具彈性的投資方法。為驗證此策略在台股市場的可行性,本文將依據加迪納的篩選精神,提出可量化的選股準則,並以台灣股市為對象進行回測,進一步探討小型成長股策略在本地市場的實證效果。

選股條件說明

在模仿羅伯.加迪納的選股策略時,我們聚焦在他最核心的投資理念:投資於具備強勁成長潛力的小型企業,並以合理價格買進。雖然他在挑選企業時也會納入主觀判斷(例如經營團隊與競爭優勢),但為了便於量化實作,我們將條件轉化為以下幾個可計算的財務指標:

選股條件

1. 小型市值公司:
選取總市值低於市場平均的 30%。這是為了聚焦在小型與微型成長股,加迪納認為這類公司尚未被市場充分關注,成長空間較大。

2. 預估盈餘成長率大於 15%:
僅保留未來一年預估盈餘成長率超過 15% 的公司。強勁的盈餘成長是股價推升的主要驅動力,也是成長型策略的核心。

3. 近四季毛利率高於產業平均值:
比較公司近四季的毛利率與所屬產業的平均水準。毛利率高代表產品具備競爭力,有較強的議價能力與經營效率,間接反映競爭優勢。

4. 董監事持股比率高於市場平均值:
以公司內部人(董監事)持股比率做為指標。當管理層與股東的利益綁在一起時,能提升公司治理品質,也反映對未來的信心。

5. PEG 小於 1:
使用 PEG(本益比除以預估盈餘成長率)作為評價標準。當 PEG 小於 1,代表以相對合理的價格買到高成長的股票,是 GARP(合理成長價格)投資法則的核心精神。6. 從符合條件的股票中,選出 PEG 值最小的 20%:
在通過上述基本條件的股票中,依 PEG 值由小到大排序,僅挑選最前段的 20%。PEG 越小代表股價相對便宜、成長性又高,有助於提升投資組合的整體報酬潛力,並集中火力在最具吸引力的標的上。

回測整體架構說明

為驗證羅伯.加迪納選股邏輯在台股市場的可行性,本研究以 2015 年 1 月 1 日至 2025 年 5 月 27 日為回測期間,設計一套具體的策略執行架構並進行回測。整體流程如下:

  • 回測期間:2015-01-01 至 2025-05-27。

  • 再平衡頻率:每 20 個交易日(約等同每月調整一次投資組合)。

  • 選股邏輯:依據選股條件篩出符合小型成長股特質之標的,並從中挑選 PEG 值最低的前 20%。

  • 資金配置:等權重分配至每一期選中的股票,假設無槓桿、無融資。

此策略以系統化方式執行加迪納的選股精神,並透過等距頻率再平衡與等權重分配,避免個別權重過度偏重某些標的,同時控制過度交易的風險。

資料撈取與前處理程式碼展示

TEJ 代碼 mktcap per r405 r403 r105 fld005 財務指標 股票市值 本益比 稅後淨利成長率 營業利潤成長率 營業毛利率 董監持股比例 TEJToolAPI 抓取資料時會用到的財務代碼 import pandas as pd import numpy as np import tejapi import os import json import matplotlib.pyplot as plt plt.rcParams['font.family'] = 'Arial' tej_key = 'your key' tejapi.ApiConfig.api_key = tej_key os.environ['TEJAPI_BASE'] = "https://api.tej.com.tw" os.environ['TEJAPI_KEY'] = tej_key from zipline.sources.TEJ_Api_Data import get_universe import TejToolAPI from zipline.data.run_ingest import simple_ingest from zipline.api import set_slippage, set_commission, set_benchmark, symbol, record, order_target_percent from zipline.finance import commission, slippage from zipline import run_algorithm start_date = '2010-01-01'; end_date = '2025-05-27' pool = get_universe(start = start_date, end = end_date, mkt_bd_e = ['TSE', 'OTC'], stktp_e = ['Common Stock-Foreign','Common Stock']) columns = ['coid', 'Industry', 'roi', 'mktcap', 'r405', 'r403', 'per', 'r105', 'fld005'] start_dt = pd.Timestamp(start_date, tz = 'UTC') end_dt = pd.Timestamp(end_date, tz = "UTC") data_use = TejToolAPI.get_history_data(start = start_dt, end = end_dt, ticker = pool + ['IR0001'], fin_type = ['Q', 'TTM'], columns = columns, transfer_to_chinese = False) data_use = data_use.sort_values(['mdate', 'coid']) data_use['avg_mkt'] = data_use.groupby('mdate')['Market_Cap_Dollars'].transform('mean') data_use['avg_ds_ratio'] = data_use.groupby('mdate')['Director_and_Supervisor_Holdings_Percentage'].transform('mean') data_use['ind_gross_margin_mean'] = data_use.groupby(['mdate', 'Industry'])['Gross_Margin_Rate_percent_Q'].transform('mean') data_use['PEG'] = data_use['PER_TWSE'] / data_use['Net_Income_Growth_Rate_TTM'] def compute_stock(date, data): df = data[data['mdate'] == pd.to_datetime(date)].reset_index(drop=True) set_1 = set(df[df['Market_Cap_Dollars'] <= df['avg_mkt'] * 0.3]['coid']) set_2 = set(df[df['Net_Income_Growth_Rate_Q'] >= 15]['coid']) set_3 = set(df[df['Gross_Margin_Rate_percent_TTM'] >= df['ind_gross_margin_mean']]['coid']) set_4 = set(df[df['Director_and_Supervisor_Holdings_Percentage'] > df['avg_ds_ratio']]['coid']) set_5 = set(df[df['PEG'] < 1.0]['coid']) passed = set_1 & set_2 & set_3 & set_4 & set_5 top_n = int(len(passed) * 0.2) # 篩選出通過條件的股票 filtered_df = df[df['coid'].isin(passed)] # 排序並取前 top_n 名(例如 PEG 最小) top_df = filtered_df.sort_values(by='PEG').head(top_n) tickers = list(top_df['coid']) sets = [len(set_1), len(set_2), len(set_3), len(set_4), len(set_5)] return tickers, sets

回測架構程式碼展示

pools = pool + ['IR0001', 'IX0043'] start_ingest = start_date.replace('-', '') end_ingest = end_date.replace('-', '') print(f'開始匯入回測資料') simple_ingest(name = 'tquant' , tickers = pools , start_date = start_ingest , end_date = end_ingest) print(f'結束匯入回測資料') back_start = '2015-01-01' codes = ['IR0001', 'IR0043'] co = ['coid','Industry', 'mkt', 'vol', 'open_d', 'high_d', 'low_d', 'close_d', 'roi', 'shares', 'per', 'pbr_tej','mktcap'] data_index = TejToolAPI.get_history_data(start = start_dt, end = end_dt, ticker = codes, columns = co, transfer_to_chinese = False) # 篩選時間 data_index = data_index[data_index['mdate'] >= back_start] # 分別取出 TSE 與 OTC 並標準化 tse = data_index[data_index['coid'] == 'IR0001'][['mdate', 'Close']].copy() otc = data_index[data_index['coid'] == 'IR0043'][['mdate', 'Close']].copy() tse.rename(columns={'Close': 'TSE_Close'}, inplace=True) otc.rename(columns={'Close': 'OTC_Close'}, inplace=True) # 合併(on mdate) merged = pd.merge(tse, otc, on='mdate', how='inner') # 標準化:以首日為基準 merged['TSE_norm'] = merged['TSE_Close'] / merged['TSE_Close'].iloc[0] * 100 merged['OTC_norm'] = merged['OTC_Close'] / merged['OTC_Close'].iloc[0] * 100 # 計算風險偏好比(OTC / TSE) merged['OTC_TSE_ratio'] = merged['OTC_norm'] / merged['TSE_norm'] # 畫圖 fig, axes = plt.subplots(2, 1, figsize=(12, 8), sharex=True) axes[0].plot(merged['mdate'], merged['TSE_norm'], label='TSE') axes[0].plot(merged['mdate'], merged['OTC_norm'], label='OTC') axes[0].set_title('Normalized Index Performance (Base = 100)') axes[0].legend() axes[0].grid(True) axes[1].plot(merged['mdate'], merged['OTC_TSE_ratio'], label='OTC / TSE') axes[1].set_title('Risk Appetite Ratio (OTC / TSE)') axes[1].axhline(1.0, color='gray', linestyle='--', linewidth=1) axes[1].legend() axes[1].grid(True) plt.tight_layout() plt.show()

羅伯.加迪納

此策略主要面向小型成長股,因此另外抓取櫃買指數 IR0043作為後續分析用的資料,此處展示加權股價指數以及櫃買指數的調整後(Base = 100)比較,以及其比值關係的視覺畫圖像。

def initialize(context, re = 20): set_slippage(slippage.VolumeShareSlippage(volume_limit=1, price_impact=0.01)) set_commission(commission.Custom_TW_Commission()) set_benchmark(symbol('IR0001')) context.i = 0 context.state = False context.order_tickers = [] context.last_tickers = [] context.rebalance = re context.set1 = 0 context.set2 = 0 context.set3 = 0 context.set4 = 0 context.set5 = 0 context.set = 0 context.dic = {} def handle_data_1(context, data): # 避免前視偏誤,在篩選股票下一交易日下單 if context.state == True: for i in context.last_tickers: if i not in context.order_tickers: order_target_percent(symbol(i), 0) for i in context.order_tickers: order_target_percent(symbol(i), 1.0 / len(context.order_tickers)) context.dic[i] = data.current(symbol(i), 'price') record(p = context.dic) context.dic = {} print(f"下單日期:{data.current_dt.date()}, 擇股股票數量:{len(context.order_tickers)}, Leverage: {context.account.leverage}") context.last_tickers = context.order_tickers.copy() context.state = False backtest_date = data.current_dt.date() if context.i % context.rebalance == 0: context.state = True context.order_tickers = compute_stock(date = backtest_date, data = data_use)[0] context.set = compute_stock(date = backtest_date, data = data_use)[1] record(tickers = context.order_tickers) record(Leverage = context.account.leverage) if context.account.leverage > 1.2: print(f'{data.current_dt.date()}: Over Leverage, Leverage: {context.account.leverage}') for i in context.order_tickers: order_target_percent(symbol(i), 1 / len(context.order_tickers)) context.i += 1 def analyze(context, perf): plt.style.use('ggplot') # 第一張圖:策略績效與報酬 fig1, axes1 = plt.subplots(nrows=3, ncols=1, figsize=(18, 15), sharex=False) axes1[0].plot(perf.index, perf['algorithm_period_return'], label='Strategy') axes1[0].plot(merged['mdate'], (merged['TSE_norm'] / merged['TSE_norm'].iloc[0])-1, label='Benchmark [TSE]') axes1[0].plot(merged['mdate'], (merged['OTC_norm'] / merged['OTC_norm'].iloc[0])-1, label='Benchmark [OTC]') axes1[0].set_title("Backtest Results") axes1[0].legend() axes1[1].bar(perf.index, perf['algorithm_period_return'] - perf['benchmark_period_return'], label='Excess return', color='#988ED5', alpha = 1.0) axes1[1].set_title('Excess Return with TSE Index') axes1[1].legend() axes1[2].plot(merged['mdate'], merged['OTC_TSE_ratio'], label='OTC / TSE') axes1[2].set_title('Risk Appetite Ratio (OTC / TSE)') axes1[2].axhline(1.0, color='gray', linestyle='--', linewidth=1) axes1[2].legend() axes1[2].grid(True) plt.tight_layout() plt.show() results = run_algorithm( start = pd.Timestamp(back_start, tz = 'utc'), end = pd.Timestamp(end_date, tz = 'utc'), initialize = initialize, handle_data = handle_data_1, analyze = analyze, bundle = 'tquant', capital_base = 1e5)

策略績效分析 & 圖表

羅伯.加迪納

在上圖中的紅色區塊,我們可以看到第二張子圖的超額報酬正處於由負轉正並且贏過大盤(加權股價指數),在同時期段應到 Rolling Alpha 的子圖,alpha 數值也是由負翻正並且維持在正數的區間持續大約一年。這段期間正好是熊牛市交錯時間點,市場整處於震盪情勢,而此策略能夠在這種市場情勢中穩定賺取報酬,顯示出策略有良好的選股能力,能夠無視市場的行情投資於有淺力的股票。我們可以在下一張圖去驗證這個猜想。

羅伯.加迪納

上圖中顯示出了「策略累積報酬率對應OTC、TSE指數」、「Rolling BETA」、「OTC/TSE數比值---代表市場資金的風險偏好」以及「景氣信號燈分數 — 用以分辨市場處於牛市還是熊市」。在藍色區塊中,我們可以看出整段期間正處于熊市逐漸往牛市的期間,此時大盤也出現明顯的回檔,但是此時策略的報酬率已經與大盤脫鉤不斷上漲。同時期對應第二張子圖的 BETA數值確實不論是對應 OTC 還是 TSE的相關性都在0.5左右震盪,顯示出此策略的報酬率不仰賴市場報酬,而是來自於則股能力的超額報酬(alpha),這加深了之前分析的合理性。

然而在近期2025年之後的情勢,OTC/TSE比值直線往下,顯示出市場資金對於大型公司股票的青睞。另一個觀點可以認為是 2025年的市場不確定性上升造成市場投資人對於風險的偏好趨於保守,造成小型股的表現在短期間難以展現。因此我認為此策略的優勢期間需要等待市場不確定性降低,再來進行相關成長股策略的運行。

羅伯.加迪納

從深水圖來看,策略最大回撤約為 -30%,顯示其波動性偏高,尤其在 2025 年初市場資金轉向大型股時,策略表現受壓。由於本策略聚焦小型成長股,在市場風險偏好轉弱時較難維持領先,回撤自然擴大。雖然震盪幅度大,但在風險偏好提升的階段(2022-2023年底),策略具備明顯超額報酬潛力,同時回撤情況很小。建議搭配簡單的市場因子,如 OTC/TSE 比值,作為進出場或降曝險的輔助依據,以提升整體穩定性。

績效表格

回測指標 羅伯加迪納投資法 加權股價指數(大盤) 櫃買指數(OTC) 累積報酬率 249.33% 234.56% 121.744% 年化報酬率 13.257% 12.77% 8.25% 年化波動度 16.61% 16.67% 18.70% 夏普值 0.83 0.81 0.52 卡瑪比率 0.45 0.45 0.25 最大回撤 -29.67% -28.97% -33.137% 註:卡瑪比率計算方式為年化波動度除以期間最大回撤,用以衡量「報酬率對虧損」的比值,概念類似於風暴比,此指標的數值越高越好。

完整程式碼連結

歡迎投資朋友參考,之後也會持續介紹使用 TEJ 資料庫來建構各式指標,並回測指標績效,所以歡迎對各種交易回測有興趣的讀者,選購 TQuant Lab 的相關方案,用高品質的資料庫,建構出適合自己的交易策略。
溫馨提醒,本次分析僅供參考,不代表任何商品或投資上的建議。

【TQuant Lab 回測系統】解決你的量化金融痛點

全方位提供交易回測所需工具

點我註冊會員,開始試用

延伸閱讀

從景氣燈號到資產輪動:一套避開熊市的量化策略

麥克.墨菲高科技股投資風險評估法則

查爾士.布蘭帝 價值型選股法則:打造安全邊際的投資組合

相關連結

查看原始文章

更多理財相關文章

01

ATM新制今上路!8大銀行「宣布新規」 有錢也領不出來

TVBS
02

特斯拉AI晶片選三星非台積?分析師曝關鍵!

NOWnews 今日新聞
03

股市觀察》兩周內決戰!台股要崩了?分析師揭「假利空真洗盤」真相

新頭殼
04

海角36億元拿不回外傳要賣街口電支股票 泰山反應曝光

鏡週刊
05

AI股太貴、非龍頭股不敢買?億級投資人:現在這種產品是好選擇

商周.com
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
查看更多

留言 0

沒有留言。

最新消息

盤中速報 - Bonk大跌8.19%,報0美元

anue鉅亨網

《今日總經速讀》美歐貿易協議達成!帶動殖利率反彈,美債市場氣氛轉趨樂觀

優分析

〈致伸法說〉Q2毛利率創新高、匯率避險奏效 EPS 1.6元與Q1持平

anue鉅亨網

鉅亨買幣速報 - 以太幣(ETH)24小時成交量超過22.6億美元,LTO Network(LTO)24小時漲幅達54.8%

anue鉅亨網

輝達H20晶片重啟對中國出口 傳向台積電追加代工訂單

商傳媒

開放申設數位保險公司 金管會曝5團隊探詢

NOWnews 今日新聞

矽谷最神秘公司 Palantir 現身台灣,自揭市場差異化的關鍵優勢

TechOrange 科技報橘

〈國巨法說〉Q2獲利年減8% 每股純益9.74元 上半年仍賺逾2股本

anue鉅亨網

關稅對景氣影響 世界先進董座:已經鈍化

NOWnews 今日新聞

盤後速報 - 大台北(9908)次交易(30)日除息1.2元,參考價30.05元

anue鉅亨網

盤後速報 - 大汽電(8931)次交易(30)日除息2元,參考價50.8元

anue鉅亨網

盤後速報 - 上洋(6728)次交易(30)日除息8元,參考價153.0元

anue鉅亨網

盤後速報 - 伊雲谷(6689)次交易(30)日除息2元,參考價79.1元

anue鉅亨網

盤後速報 - 動力-KY(6591)次交易(30)日除息2.35元,參考價64.75元

anue鉅亨網

盤後速報 - 瑞祺電通(6416)次交易(30)日除息2.9元,參考價84.4元

anue鉅亨網

盤後速報 - 大城地產(6171)次交易(30)日除息2.5元,參考價33.1元

anue鉅亨網

盤後速報 - 久威(6114)次交易(30)日除息0.5元,參考價35.75元

anue鉅亨網

盤後速報 - 中租-KY甲特(5871A)次交易(30)日除息3.8元,參考價98.2元

anue鉅亨網

盤後速報 - 中租-KY(5871)次交易(30)日除權息6.3元,參考價118.04元

anue鉅亨網

盤後速報 - 遠雄港(5607)次交易(30)日除權息1.3元,參考價43.24元

anue鉅亨網

盤後速報 - 宣德(5457)次交易(30)日除息1.3元,參考價42.0元

anue鉅亨網

盤後速報 - 國眾(5410)次交易(30)日除息1.5元,參考價27.25元

anue鉅亨網

盤後速報 - 強信-KY(4560)次交易(30)日除息1.2元,參考價34.0元

anue鉅亨網

盤後速報 - 冠星-KY(4439)次交易(30)日除息2元,參考價90.0元

anue鉅亨網

盤後速報 - 東隆興(4401)次交易(30)日除息0.7元,參考價15.45元

anue鉅亨網

盤後速報 - 世坤(4305)次交易(30)日除息3.2元,參考價45.55元

anue鉅亨網

盤後速報 - 中華食(4205)次交易(30)日除權息3元,參考價86.91元

anue鉅亨網

AI需求助攻,國巨Q2本業獲利站11季高點,Q3營收、雙率向上

財訊快報

盤後速報 - 先益(3531)次交易(30)日除息0.8元,參考價22.75元

anue鉅亨網

盤後速報 - 晶睿(3454)次交易(30)日除息2.3元,參考價104.2元

anue鉅亨網

盤後速報 - 弘憶股(3312)次交易(30)日除息1.4元,參考價45.7元

anue鉅亨網

盤後速報 - 綠意(2596)次交易(30)日除息1元,參考價30.05元

anue鉅亨網

盤後速報 - 皇普(2528)次交易(30)日除權0.5元,參考價27.43元

anue鉅亨網

盤後速報 - 怡利電(2497)次交易(30)日除息2元,參考價39.0元

anue鉅亨網

盤後速報 - 技嘉(2376)次交易(30)日除息10元,參考價272.5元

anue鉅亨網

盤後速報 - 矽統(2363)次交易(30)日除息0.5元,參考價55.1元

anue鉅亨網

盤後速報 - 智邦(2345)次交易(30)日除息11元,參考價841.0元

anue鉅亨網

盤後速報 - 宇隆(2233)次交易(30)日除息7元,參考價172.0元

anue鉅亨網

盤後速報 - 力肯(1570)次交易(30)日除息1.3元,參考價22.6元

anue鉅亨網

盤後速報 - 元山(6275)下週(8月5日)除息2.26元,預估參考價41.39元

anue鉅亨網