請更新您的瀏覽器

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

理財

彼得‧林區選股哲學實踐:成長與價值兼具的量化策略

TEJ 台灣經濟新報

更新於 07月02日10:01 • 發布於 07月02日06:00
Photo by JESHOOTS.COM on Unsplash

前言

彼得‧林區(Peter Lynch)是美國基金界傳奇人物,曾於1977至1990年間擔任富達麥哲倫基金(Fidelity Magellan Fund)經理人,期間資產規模從2,000萬美元成長至140億美元,年化報酬率高達29.2%,堪稱史上最成功的基金經理之一。他不僅以出色的績效聞名,更以深入淺出的投資哲學啟發無數散戶與專業投資人。

林區倡導「投資你所熟悉的企業」(Invest in what you know),認為個人投資者擁有觀察生活的優勢,能在企業基本面反映於股價前率先發現潛力股。他同時強調選股應建立在企業獲利能力、成長性與財務穩健之上,並透過本益比(P/E)、盈餘成長率(EPS Growth)等指標發掘「被低估的成長股」。

投資標的 & 回測期間

本研究以台灣證券交易所與櫃檯買賣中心掛牌之所有上市櫃公司為投資標的,蒐集2017年起的股價、財務報表與董監事持股等基礎資料,並完成資料清洗與整合。由於策略邏輯需引用最近一年之財務資訊,實際回測期間訂為2018年1月1日至2025年4月29日,僅對符合條件之樣本進行歷史績效模擬,以確保數據完整性與策略檢驗的嚴謹性。

策略邏輯

彼得.林區的選股邏輯核心在於尋找成長與價值兼具的企業,而非依賴市場時機或總體經濟預測。其投資哲學強調深入了解公司本身、強調基礎面調查(kick the tire),並透過量化財務指標輔助篩選出具吸引力的投資標的。為將其理念具體化,本策略將其核心財務標準轉化為以下五項量化條件進行選股:
最近一季負債比率 ≤ 25%企業的財務風險往往來自過高的負債比例。彼得.林區強調挑選財務體質良好的公司,以減少在景氣反轉或利率上升時的償債壓力。設定負債比率(總負債 / 總資產)不超過 25%,能有效過濾掉高槓桿經營的企業,確保投資標的具有良好的償債能力與長期生存空間。

每股淨現金 > 產業平均值企業帳上的現金水位反映其短期流動性與彈性。林區偏好手上現金充裕、對長期負債具備反制能力的公司。本策略以「每股淨現金」作為衡量基準,並要求高於同產業平均水平,以突顯其在資本結構與經營靈活度上的相對優勢。

每股自由現金流本益比(P/FCF) < 產業平均值林區關注企業實際產出現金的能力,遠勝於帳面盈餘。本策略以股價與每股自由現金流(營業現金流減資本支出)的比值,作為評估企業估值的依據。若其低於同產業平均,代表該公司在現金創造力良好的前提下,仍被市場低估,屬具備上漲潛力的價值股。

存貨成長率 < 營收成長率企業營運效率亦是林區選股的重要考量。若存貨累積速度高於營收成長,可能顯示產品滯銷或需求減弱。相反地,當營收成長快於存貨,則意味著企業產銷協調良好、存貨管理得當。此一條件有助篩選出營運體質健全、不易出現存貨壓力的公司。

(1年平均盈餘成長率 + 現金股息率) / 本益比 ≧ 2
本策略中以「1年平均稅後淨利成長率 + 現金股利率」除以本益比,要求結果不低於 2。這樣的標準意味著,只有當企業具備高度成長性與穩定配息,且估值合理的情況下,才會被納入投資名單。

實務操作上是通過上述的條件進行股票的篩選,篩選出來的股票以等權重的方式進行買入並且持有至下一次的再平衡日。再平衡天數的設計,此策略設定每30天進行換股。

股票篩選程式碼展示

導入套件

import pandas as pd import numpy as np import tejapi import os import datetime start='2017-01-01' end='2025-04-29' os.environ['TEJAPI_KEY'] = 'Your Key' from logbook import Logger, StderrHandler, INFO log_handler = StderrHandler(format_string = '[{record.time:%Y-%m-%d %H:%M:%S.%f}]: ' + '{record.level_name}: {record.func_name}: {record.message}', level=INFO) log_handler.push_application() log = Logger('get_universe')

匯入股票池(所有曾經上下市櫃的普通股)

from zipline.sources.TEJ_Api_Data import get_universe # 由文字型態轉為Timestamp,供回測使用 tz = 'UTC' start_dt, end_dt = pd.Timestamp(start, tz = tz), pd.Timestamp(end, tz = tz) from zipline.sources.TEJ_Api_Data import get_universe pool = get_universe(start = start_dt, end = end_dt, mkt=['TWSE','OTC'], stktp_e=['Common Stock-Foreign', 'Common Stock'])

匯入股票財務資訊

import TejToolAPI columns = ['主產業別_中文', '報酬率', '收盤價', '本益比', '現金及約當現金', ' 非流動負債合計', '負債比率', '營收成長率', '現金股利率', '稅後淨利成長率', '存貨', '營運產生現金流量', '投資產生現金流量', '個股市值_元', '加權平均股數'] data__ = TejToolAPI.get_history_data(start = start_dt, end = end_dt, ticker = pool, fin_type = ['Q'], columns = columns, transfer_to_chinese = True)

處理資料

inv_change_df = data.copy() inv_change_df['存貨變動'] = inv_change_df.groupby('股票代碼')['存貨_Q'].diff().ne(0) inv_change_df = inv_change_df.loc[inv_change_df['存貨變動'], ['股票代碼', '日期', '存貨_Q']] inv_change_df['存貨成長率'] = inv_change_df.groupby('股票代碼')['存貨_Q'].pct_change() inv_change_df.loc[inv_change_df['存貨成長率'] == 0, '存貨成長率'] = np.nan idx = pd.MultiIndex.from_frame(data.loc[:, ['股票代碼', '日期']]) inv_growth_series = ( inv_change_df .set_index(['股票代碼', '日期'])['存貨成長率'] .reindex(idx) # 把變動日對齊到日資料中 .groupby(level=0) .ffill() # 把變動日的值填入之後的日期 ) data['存貨成長率'] = inv_growth_series.values data['個股市值(千)'] = data['個股市值_元'] / 1000 # 將個股市值的單位改為(千) data['每股淨現金'] = (data['現金及約當現金_Q'] - data['非流動負債合計_Q']) / data['加權平均股數_Q'] * 1000 data['每股自由現金流'] = (data['營運產生現金流量_Q'] - data['投資產生現金流量_Q']) / data['加權平均股數_Q'] * 1000 data['1年平均稅後淨利成長率'] = data.groupby('股票代碼')['稅後淨利成長率_Q'].rolling(252).mean().reset_index(level=0, drop=True) data['1年平均現金股利率'] = data.groupby('股票代碼')['現金股利率'].rolling(252).mean().reset_index(level=0, drop=True) data['sharpe_ratio'] = data.groupby('股票代碼')['報酬率'].rolling(252).mean().reset_index(0, drop=True) / data.groupby('股票代碼')['報酬率'].rolling(252).std().reset_index(0, drop=True)

匯入回測資料

from zipline.data.run_ingest import simple_ingest pools = pool + ['IR0001'] start_ingest = start.replace('-', '') end_ingest = end.replace('-', '') print(f'開始匯入回測資料') simple_ingest(name = 'tquant' , tickers = pools , start_date = start_ingest , end_date = end_ingest) print(f'結束匯入回測資料')

回測架構

from zipline.pipeline import Pipeline from zipline.pipeline.factors import Returns from zipline.pipeline.filters import SingleAsset from zipline.api import set_slippage, set_commission, set_benchmark, symbol, record, order_target_percent, pipeline_output, attach_pipeline from zipline.finance import commission, slippage def initialize(context): set_slippage(slippage.VolumeShareSlippage(volume_limit=1, price_impact=0.01))#調整volume_limit to 1 and price impact to 0.01 set_commission(commission.Custom_TW_Commission()) set_benchmark(symbol('IR0001')) # attach_pipeline(make_pipeline(), 'my_pipeline') context.i = 0 context.state = False context.order_tickers = [] context.last_tickers = [] def compute_stock(date, data): """ 根據指定的日期進行選股,篩選出符合條件的股票列表。 Parameters: date (str): 選股的日期,格式為 'YYYY-MM-DD'。 data (DataFrame): 包含股票數據的 DataFrame。 Returns: list: 符合條件的股票代碼列表。 """ # 提取指定日期的股票資訊 df = data[data['日期'] == pd.Timestamp(date)].reset_index(drop=True) # 條件 1:負債比例 < 25% set_1 = set(df[df['負債比率_Q'] <= df.groupby('主產業別_中文')['負債比率_Q'].transform('mean')]['股票代碼']) # 條件 2:每股淨現金 > 產業平均每股淨現金 df['產業平均每股淨現金'] = df.groupby('主產業別_中文')['每股淨現金'].transform('mean') set_2 = set(df[df['每股淨現金'] > df['產業平均每股淨現金']]['股票代碼']) # 條件 3: (股價/ 每股自由現金流量) < 產業平均(股價/ 每股自由現金流量) df['股價/每股自由現金流'] = df['收盤價'] / df['每股自由現金流'] df['產業平均股價/每股自由現金流'] = df.groupby('主產業別_中文')['股價/每股自由現金流'].transform('mean') set_3 = set(df[df['股價/每股自由現金流'] < df['產業平均股價/每股自由現金流']]['股票代碼']) # 條件 4: 存貨成長率 < 營收成長率 set_4 = set(df[df['存貨成長率'] < df['營收成長率_Q']]['股票代碼']) # 條件 5: 1年平均(稅後淨利成長率 + 現金股利率)/ 本益比 >= 2 df['條件五'] = (df['1年平均稅後淨利成長率'] + df['1年平均現金股利率']) / (df['本益比']) set_5 = set(df[ df['條件五'] >= 2]['股票代碼']) tickers = list(set_1 & set_2 & set_3 & set_4 & set_5) # 從 df 中找出符合交集條件的股票,並依本益比排序(由低到高) filtered_df = df[df['股票代碼'].isin(tickers)].copy() filtered_df = filtered_df[filtered_df['sharpe_ratio'].notna()] # 移除本益比為空值的資料 sorted_df = filtered_df.sort_values(by='sharpe_ratio', ascending=False) # 取前十檔股票代碼,若不足十檔則全部輸出 top_20 = sorted_df['股票代碼'].tolist()[:20] return top_20 rebalance_period = 21 # 調倉週期 def handle_data_1(context, data, rebalance = rebalance_period): # 避免前視偏誤,在篩選股票下一交易日下單 if context.state == True: print(f"下單日期:{data.current_dt.date()}, 擇股股票數量:{len(context.order_tickers)}") 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 / len(context.order_tickers)) curr = data.current(symbol(i), 'price') record(price = curr, days = context.i) context.last_tickers = context.order_tickers context.state = False backtest_date = data.current_dt.date() # 若今天是月底交易日,執行轉倉前的擇股邏輯 if context.i % rebalance == 0: context.state = True context.order_tickers = compute_stock(date = backtest_date, data = data_) # e.g., ['2330', '2317', …] print(f"月末擇股名單:{context.order_tickers}") context.i += 1 import matplotlib.pyplot as plt import matplotlib def analyze(context, perf): PASS return from zipline import run_algorithm from zipline.utils.calendar_utils import get_calendar calendar_name = 'TEJ' import logging # 關掉 Zipline 訊息 logging.getLogger('zipline').setLevel(logging.WARNING) logging.getLogger('zipline.finance').setLevel(logging.WARNING) logging.getLogger('exchange_calendars').setLevel(logging.WARNING) start = pd.Timestamp('2018-01-01', tz = 'UTC') end_dt = pd.Timestamp('2025-04-29', tz = "UTC") results = run_algorithm( start = start_, end = end_dt, initialize = initialize, handle_data = handle_data_1, analyze = analyze, bundle = 'tquant', capital_base = 1e6, trading_calendar=get_calendar(calendar_name), data_frequency='daily')

查看績效

from pyfolio.utils import extract_rets_pos_txn_from_zipline returns, positions, transactions = extract_rets_pos_txn_from_zipline(results) benchmark_rets = results.benchmark_return # 時區標準化 returns.index = returns.index.tz_localize(None).tz_localize('UTC') positions.index = positions.index.tz_localize(None).tz_localize('UTC') transactions.index = transactions.index.tz_localize(None).tz_localize('UTC') benchmark_rets.index = benchmark_rets.index.tz_localize(None).tz_localize('UTC') import pyfolio from pyfolio.utils import extract_rets_pos_txn_from_zipline import matplotlib.pyplot as plt import matplotlib matplotlib.rcParams['font.family'] = 'SimHei' matplotlib.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False returns, positions, transactions = extract_rets_pos_txn_from_zipline(results) benchmark_rets = results.benchmark_return pyfolio.plot_gross_leverage(returns, positions) pyfolio.tears.create_full_tear_sheet(returns=returns, positions=positions, transactions=transactions, benchmark_rets=benchmark_rets )

績效圖表&分析

彼得‧林區選股哲學實踐:成長與價值兼具的量化策略
彼得‧林區選股哲學實踐:成長與價值兼具的量化策略

本策略於七年期間內累積報酬率達 176.71%,年化報酬率為 15.51%,整體表現略優於基準指數。從報酬表現來看,策略存在正向Alpha,但與大盤相關性也偏高。不過,策略的 年化波動率為 21.49%,顯示資產報酬具有一定波動風險,並在投資期間經歷了最大 -28.02% 的回落

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

【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

沒有留言。

最新消息

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

anue鉅亨網

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

商傳媒

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

TechOrange 科技報橘

〈國巨法說〉Q2業外影響每股純益1.03元 上半年仍賺逾2股本

anue鉅亨網

盤後速報 - 大台北(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鉅亨網

盤後速報 - 達麗(6177)下週(8月5日)除權息4元,預估參考價40.24元

anue鉅亨網

盤後速報 - 宜特(3289)下週(8月5日)除息1元,預估參考價121.0元

anue鉅亨網

盤後速報 - 力特(3051)下週(8月5日)除息1.52元,預估參考價23.78元

anue鉅亨網

盤後速報 - 臺企銀(2834)下週(8月5日)除權息0.8元,預估參考價15.14元

anue鉅亨網

盤後速報 - 根基(2546)下週(8月5日)除權息3.8元,預估參考價77.17元

anue鉅亨網