請更新您的瀏覽器

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

理財

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

TEJ 台灣經濟新報

更新於 2025年05月14日10:09 • 發布於 2025年05月08日08:30
Photo by Isaac Smith on Unsplash

前言

查爾士‧布蘭帝 是班傑明‧葛拉漢 的得意門生,自1974年創立Brandes Investment Partners 以來,將管理資產從 1.3 億美元成長至逾 750 億美元。旗下 Brandes Global Equity Fund 二十年年化報酬率達 17.91%,顯著領先 MSCI World Index,並獲晨星五顆星與多項國際大獎肯定。其另一代表作 AGF International Value Fund 同樣展現卓越長線績效,布蘭帝本人也曾蟬聯全球頂尖基金經理人之首。

布蘭帝堅守價值投資本質,反對以未來預測資料作為選股依據,強調以「安全邊際」與公司真實價值為核心,並以中長期持有為原則。為忠實還原其投資思想,本研究將其理念量化,採用公司淨值替代傳統折現模型,並搭建回測架構於歷史行情中檢驗策略表現。本篇文章將分別介紹資料處理流程、回測設計與績效分析,期望呈現布蘭帝價值投資在量化框架下的可行性與穩健性。

安全邊際(Margin of Safety):指的是當資產的內在價值高於市場價格時,所存在的保護空間。這個概念強調,即便未來經營或市場環境出現不利變化,因為買入時價格足夠便宜,仍能降低損失風險。

投資標的 & 回測期間

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

策略邏輯

本策略旨在篩選出財務結構穩健、管理階層持股充足,且市場評價偏低、具備安全邊際的股票,具體條件與說明如下:

  • 最近一季負債淨值比 < 40%負債淨值比(負債總額/股東權益)反映企業槓桿水準。低於 40% 意味著公司以自有資本為主,債務壓力小,在市場波動或利率上升時具備更強的抗壓能力。
  • 最新董監事持股比率 > 市場平均值董監事持股比率高於同業平均,代表董事會高層對自身公司前景具信心,且利益與股東高度綁定,有助於確保決策品質與長期穩健經營。
  • 近四季本益比(P/E) < 市場平均值本益比反映市場對公司未來獲利的估值程度。低於市場平均表示該公司相對便宜,或市場對其獲利成長尚未充分認可,具有潛在上漲空間。
  • 股價/近四季現金流量比(P/CF) < 市場平均值以現金流量衡量評價可降低因會計操縱帶來的誤差。該指標低於市場平均顯示投資者以較低成本取得公司真實現金流,風險較小。
  • 最近一季股價/淨值比(P/B) < 市場平均值股價淨值比低於同業平均,代表公司股價相對其帳面價值被低估,能提供更高的安全邊際。
  • 最近一季股價/淨值比(P/B) < 1.0 倍當此指標小於 1 時,表示市場給予的股價低於公司淨資產帳面價值,理論上購買此類股票便具備「低於清算價」的保護層,風險進一步降低。

實務操作上是通過上述的條件進行股票的篩選,篩選出來的股票以等權重的方式進行買入並且持有至下一次的再平衡日。再平衡天數的設計,由於考慮到該策略為價值投資策略,會需要較長的時間來讓股價反應公司的真實價值,因此設定每60天進行換股。

股票篩選程式碼展示

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 ='SZf1BjNEcKQhvQmn96eLrNL60Q2RH1' 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-04-21' pool = get_universe(start = start_date, end = end_date, mkt_bd_e = ['TSE', 'OTC'], stktp_e = 'Common Stock', main_ind_e = 'General Industry') columns = ['coid','bstl', 'bsse', 'fld005', 'close_d', 'per', 'pbr_tej', 'shares', 'cscfo', 'cscfi', 'cscff'] 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, fin_type = 'Q', # 為累計資料 columns = columns, transfer_to_chinese = False) # 確保時間格式正確 data_use['mdate'] = pd.to_datetime(data_use['mdate']) # 計算 Total_cashflow data_use['Total_cashflow'] = data_use['Cash_Flow_from_Operating_Activities_Q'] # 排序 data_use = data_use.sort_values(['coid', 'mdate']) # 轉成季資料:每股公司取每季最後一筆 df_q = data_use.set_index('mdate').groupby('coid', group_keys=False).resample('Q').last().reset_index() # 計算近四季平均本益比 df_q['PER_4Q_avg'] = df_q.groupby('coid')['PER_TWSE'].transform(lambda x: x.rolling(4, min_periods=4).mean()) # 計算近四季總現金流 df_q['Cashflow_4Q_sum'] = df_q.groupby('coid')['Total_cashflow'].transform(lambda x: x.rolling(4, min_periods=4).sum()) # 計算「股價 / 現金流」:若現金流為 0,則設為 NaN 避免除以 0 df_q['Price_to_CF'] = (df_q['Close'] * df_q['Issue_Shares_1000_Shares']) / df_q['Cashflow_4Q_sum'].replace(0, np.nan) # 市場平均本益比 & 市場平均 Price_to_CF df_q['Market_PER_avg'] = df_q.groupby('mdate')['PER_TWSE'].transform('mean') df_q['Market_PCF_avg'] = df_q.groupby('mdate')['Price_to_CF'].transform('mean') # 判斷是否低於市場平均 df_q['PER_below_market'] = df_q['PER_4Q_avg'] < df_q['Market_PER_avg'] df_q['PCF_below_market'] = df_q['Price_to_CF'] < df_q['Market_PCF_avg'] # 確保排序 data_use = data_use.sort_values(['coid', 'mdate']) df_q = df_q.sort_values(['coid', 'mdate']) # 用 merge_asof 把季資料合併回每日 result_list = [] for coid, df_daily_group in data_use.groupby('coid'): df_q_group = df_q[df_q['coid'] == coid] merged = pd.merge_asof( df_daily_group, df_q_group[['mdate', 'PER_below_market', 'PCF_below_market']], on='mdate', direction='backward' ) result_list.append(merged) # 合併所有公司 data_final = pd.concat(result_list).sort_values(['coid', 'mdate']).reset_index(drop=True) def compute_stock(date, data): df = data[data['mdate'] == pd.to_datetime(date)].reset_index(drop = True) df['debt_equity_ratio'] = df['Total_Liabilities_Q'] / df['Total_Equity_Q'] set_1 = set(df[df['debt_equity_ratio'] < .4]['coid']) Director_avg = df['Director_and_Supervisor_Holdings_Percentage'].mean() set_2 = set(df[df['Director_and_Supervisor_Holdings_Percentage'] > Director_avg]['coid']) set_3 = set(df[df['PER_below_market'] == True]['coid']) set_4 = set(df[df['PCF_below_market'] == True]['coid']) PBR_avg = df['PBR_TEJ'].mean() set_5 = set(df[df['PBR_TEJ'] < PBR_avg]['coid']) set_6 = set(df[df['PBR_TEJ'] < 1.0]['coid']) tickers = list(set_1 & set_2 & set_5 & set_6 & set_3 & set_4) return tickers

回測程式碼展示

def initialize(context): 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 = [] def handle_data_1(context, data, rebalance = 60): # 避免前視偏誤,在篩選股票下一交易日下單 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 / len(context.order_tickers)) curr = data.current(symbol(i), 'price') record(price = curr, days = context.i) 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 % rebalance == 0: context.state = True context.order_tickers = compute_stock(date = backtest_date, data = data_final) 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): fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(18, 10), sharex=False) plt.style.use('ggplot') axes[0].plot(perf.index, perf['algorithm_period_return'], label = 'Strategy') axes[0].plot(perf.index, perf['benchmark_period_return'], label = 'Benchmark') axes[0].set_title(f"Backtest_Results") axes[0].legend() axes[1].plot(perf.index, perf['Leverage'], label = 'Leverage') axes[1].set_title(f"Leverage") axes[1].legend plt.tight_layout() plt.show() results = run_algorithm( start = pd.Timestamp('2020-01-01', tz = 'utc'), end = pd.Timestamp('2025-04-21', tz = 'utc'), initialize = initialize, handle_data = handle_data_1, analyze = analyze, bundle = 'tquant', capital_base = 1e6)

回測輸出結果

# ==================== 回測輸出結果 ===================== def analyze(context, perf): fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(18, 10), sharex=False) plt.style.use('ggplot') axes[0].plot(perf.index, perf['algorithm_period_return'], label = 'Strategy') axes[0].plot(perf.index, perf['benchmark_period_return'], label = 'Benchmark') axes[0].set_title(f"Backtest_Results") axes[0].legend() axes[1].plot(perf.index, perf['Leverage'], label = 'Leverage') axes[1].set_title(f"Leverage") axes[1].legend plt.tight_layout() plt.show() results = run_algorithm( start = pd.Timestamp('2020-01-01', tz = 'utc'), end = pd.Timestamp('2025-04-21', tz = 'utc'), initialize = initialize, handle_data = handle_data_1, analyze = analyze, bundle = 'tquant', capital_base = 1e6)

回測績效圖表與分析

價值型選股法則

策略年化報酬率達 23.75%,同期大盤年化僅 15%,明顯超越指數。策略夏普比率為 1.15,代表在扣除無風險報酬後,每承擔一單位風險可獲取 1.15 單位超額報酬。CAPM 回歸結果顯示,alpha = 0.15(年化 15%),說明穩定產生超額收益;beta = 0.61,意味著組合對大盤波動的敏感度僅一半左右,具備良好分散效益。

觀察累積報酬曲線,策略在 2020年底至 2021年第一季一度拉開與大盤的差距,主因當時高品質價值股(如航運、傳產)估值修復;而 2023年初至 2024年初整體牛市階段,價值投資標的亦隨大市上行而受益,使策略持續領先指數,進一步驗證在牛市中以安全邊際為核心的選股方法同樣具備顯著優勢。

價值型選股法則

策略歷經 COVID 初期、高估值修正與升息壓力等多次市場動盪,五大回撤峰值介於約 −10% 至 −30% 之間,多數回撤能在 3 – 6 個月內收斂;但是在 2024 年至2025 年出現一段較大的回撤時段,當時正處於市場牛市的末升段,對於價值投資來說會是機會比較少的時期,同時也是適合出脫部分股票,換手現金或短天期債券的時期。

完整程式碼連結

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

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

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

點我註冊會員,開始試用

延伸閱讀

機器學習算法 XGBoost 提升技術指標一目均衡表的投資績效

事件型因子研究:公司宣告發放股利

揭開投資大師的選股密碼:麥克.喜偉收益型投資四大準則解析

相關連結

查看原始文章

更多理財相關文章

01

華邦電、南亞科等6檔上市櫃股票1/9起列入處置

自由電子報
02

記憶體價格瘋漲!一盒記憶體價格堪比上海一套房

anue鉅亨網
03

台積電洩密收押第4人!借同事帳號偷機密 工程師陳韋傑換手機、電腦「急滅證」

太報
04

科技圈大逃亡?5兆男面對「2000億稅金」!黃仁勳竟煩惱這事:隨便課

三立新聞網
05

台股2025年寫6項歷史新高 拚擠進全球市值第6大股市

中央通訊社
06

「產業軍師」中經院副院長王健全辭世 享年67歲

自由電子報
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...