【實戰應用】證券分析之開山始祖一班傑明.葛拉漢的投資心法

TEJ 台灣經濟新報
TEJ-API 金融資料分析
17 min readApr 12, 2021
  • 透過Python實作班傑明.葛拉漢的投資策略,並在台灣市場回測績效

* 前言 — 班傑明.葛拉漢是誰?

價值投資大師班傑明.葛拉漢的防禦型投資策略班傑明.葛拉漢是華爾街公認的證券分析之父,1923年創立第一個私人基金-葛蘭赫公司,初試啼聲操作績效即非常優異。1925年因合夥人意見不合而清算解散,1926年和友人合資設立葛拉漢聯合投資帳戶(Joint Account),至1929年初資金規模由45萬美元成長至250萬美元(非新投資者)。一夕之間,葛拉漢之名成為華爾街的寵兒,多家上市公司的所有人皆希望葛拉漢為他們負責合夥基金,但皆因葛拉漢認為股市已過度飆漲而婉拒。

1934年葛拉漢和陶德(David L. Dodd)合著「有價證券分析」(Security Analysis)一書,成為證券分析的開山始祖在葛拉漢之前,證券分析仍不能被視為一門學問。此書至今仍未絕版,是大學證券分析的標準教科書之一。

當代著名的基金經理人如華倫.巴菲特(Warren Buffett)約翰.奈夫(John Neff)湯姆.芮普(Tom Knapp)等皆是葛拉漢的學生,目前華爾街只要是標榜價值投資法的基金經理人,也都是葛拉漢的徒子徒孫。

本篇文章屬於實戰策略,因此不論是篇幅還是難度上都有一定的程度,但請大家不要慌張,我們在文末都有提供完整程式碼與聯繫方式,若有看不懂或不會的都歡迎詢問我們~

✨本文重點概要 ✨

  • 大師策略/ 調整後的策略 介紹
  • 調整後策略績效展示

🚪本次使用的相關網站連結🚪

🗻 大師策略 🗻

班傑明.葛拉漢的防禦型投資策略

1. 選擇年銷售額逾一億美元的公司,或年銷售額逾 5000萬美元的公用事業股。
2. 流動比例應為 200%以上,且長期負債不超過淨流動資產。
3. 選擇過去十年,每年皆有盈餘的公司
4. 選擇連續 20年都支付股利的公司
5. 利用 3年平均值,選擇過去 10年每股盈餘至少成長 1/3的公司。
6. 股價÷三年平均每股盈餘小於 15倍。
7. 股價淨值比小於 1.5倍。
8. 投資組合中應保持 10–13種股票。

⬇️ 因應時空背景的轉換,我們對上列條件進行了調整與修正⬇️

調整後投資策略

1. 年營業額大於市場平均值的公司
2. 過去 5年皆有盈餘的公司
3. 連續 2年皆支付現金股利的公司
4. 流動比率 > 200%
5. 流動淨資產 — 長期負債 > 0
6. (近 3年平均稅後淨利-近 5年平均稅後淨利)/近 5年平均稅後淨利的絕對值>0.33
7. PER1(以近 3年平均每股盈餘計算)<= 近三年 PER之平均
8. PER(以近 4季每股盈餘計算)*PBR <= 近三年 PER*PBR之平均

以台灣50指數成分股為例

台灣50指數成分股包含了市值排名前5️⃣0️⃣名的公司

建構步驟:

  • 第一步:匯入套件
  • 第二步:資料撈取
  • 第三步:建構大師策略
  • 第四步:策略績效回測
  • 第五步:大師策略 vs 台灣50指數

第一步:匯入套件

import tejapi 
import pandas as pd
import numpy as np
tejapi.ApiConfig.api_key = 'your_key'
import datetime
import matplotlib.pyplot as plt

第二步:資料撈取

首先自 TW50.csv 中擷取台灣50指數成分股資料,但成分股的資料格式為(1101 台泥),而 tejapi.get 函數中 coid 是專門用來控制股票代碼的參數,coid 僅接受數字,程式碼第二行的主要功能在將成分股當中的股票代碼抽離
由於一次抓取多股運行速度較慢,因此建議以迴圈的方式逐一抓取個股基本面數據,再透過 append 的方式擴增。

本次資料庫使用 IFRS以合併為主簡表(累計)-全產業(TWN/AIM1A)、上市(櫃)股價報酬(日)-報酬率 (TWN/APRCD2)和上市(櫃)未調整股價(年) (TWN/APRCY),試用帳號有數據取用上的限制,若想更自由的使用資料的話可以參考 TEJ E Shop🎁

stk_info = pd.read_csv('TW50.csv',engine='python')
stk_nums = stk_info['成份股'].apply(lambda x: str(x).split(' ')[0])
# 撈取財務資料
zz = pd.DataFrame()
for code in stk_nums:
zz = zz.append(tejapi.get('TWN/AIM1A'
,coid=code
,paginate=True,chinese_column_name=True
,opts= {'pivot':True}
)).reset_index(drop=True)
print(code)

第三步:建構大師策略

開始建構大師策略👷👷

✅ 1. 年營業額大於市場平均值的公司

第一項條件針對營業額進行篩選,其目的在於過濾獲利性不佳的公司,而能被納入台灣50指數之個股,基本上都是頗有名氣的大公司,營收穩定,因此我們直接略過第一項條件 🏄🏄~

✅ 2. 過去5年皆有盈餘的公司

  • np.where功能類似 if else 條件句,其好處在於提升程式碼的易讀性。
    -
    np.where(z1[‘常續性稅後淨利’]>0,1,0):符合條件z1[‘稅前淨利’]>0的話輸出1,不符合則輸出0。
  • rolling 為 pandas 內建的功能,括弧內的數字可調整移動窗格的大小。
    - rolling(5).sum()目的在計算近5個位置內的總合。

此處先對當期的稅前淨利進行是否大於0的判斷,然後再對判斷的結果進行滾動加總,最後滾動加總值剛好等於5者,視為符合第二項條件。

# 條件2:過去5年皆有盈餘
z1['earning'] = np.where(z1['常續性稅後淨利']>0,1,0)
z1['earning_continue'] = z1['earning'].rolling(5).sum()
z1['condition_2'] = np.where(z1['earning_continue']==5,1,0)

✅ 3. 連續2年皆支付現金股利的公司

此處先對當期的每股現金股利進行是否大於0的判斷,然後再對判斷的結果進行滾動加總,最後滾動加總值剛好等於2者,視為符合第三項條件。

# 條件3:過去2年皆有支付現金股利
z1['cash_dividend'] = np.where(z1['普通股每股現金股利(盈餘及公積)']>0,1,0)
z1['cash_dividends'] = z1['cash_dividend'].rolling(2).sum()
z1['condition_3'] = np.where(z1['cash_dividends']==2,1,0)

✅ 4. 流動比率>200%

  • 流動比率 = 流動資產╱流動負債

流動比率TEJ已經幫我們內建好了,所以我們不必自己再算一遍💪💪~

# 條件4:流動比率>200%
z1['condition_4'] = np.where(z1['流動比率']>200,1,0)

✅ 5. 流動淨資產 — 長期負債 > 0

  • 流動淨資產 = 流動資產 - 流動負債
  • 非流動負債包含長期負債
# 條件5:流動資產-長期負債>0
z1['condition_5'] = np.where((z1['流動資產']-z1['流動負債']-z1['非流動負債'])>0,1,0)

✅ 6. (近3年平均稅後淨利-近5年平均稅後淨利)/近5年平均稅後淨利的絕對值>0.33

- z1[‘歸屬母公司淨利(損)’].rolling(3).mean():近3年平均稅後淨利
- z1[‘歸屬母公司淨利(損)’].rolling(5).mean():近5年平均稅後淨利

# 條件6:abs【(近3年平均稅後淨利-近5年平均稅後淨利)/近5年平均稅後淨利】 >0.33
z1['近3年平均稅後淨利'] = z1['歸屬母公司淨利(損)'].rolling(3).mean()
z1['近5年平均稅後淨利'] = z1['歸屬母公司淨利(損)'].rolling(5).mean()
z1['condition_6'] = np.where(abs((z1['近3年平均稅後淨利']-z1['近5年平均稅後淨利'])/z1['近5年平均稅後淨利'])>0.33,1,0)

✅ 7. PER1(以近3年平均每股盈餘計算)<= 近三年PER之平均

- z1[‘每股盈餘’].rolling(3).mean():近3年平均每股盈餘
- z1[‘當季季底P/E’].rolling(3).mean():近三年PER之平均
- z1[‘close’]/z1[‘近3年平均EPS’]:PER1(以近3年平均每股盈餘計算)

# 條件7:PER (當年年底收盤價/近3年平均每股盈餘) <= 近3年PER之平均
z1['近3年平均EPS'] = z1['每股盈餘'].rolling(3).mean()
z1['近3年平均PER'] = z1['當季季底P/E'].rolling(3).mean()
z1['PER1'] = z1['close']/z1['近3年平均EPS']
z1['condition_7'] = np.where(z1['PER1']<=z1['近3年平均PER'],1,0)

✅ 8. PER(以近4季每股盈餘計算)*PBR <= 近三年PER*PBR之平均

IFRS以合併為主簡表(累計)-全產業中,第四季財報的EPS相當於當年度的累積總合=EPS(Q1+Q2+Q3+Q4)。而TEJ提供的PER事由累積4季的EPS求算出,故可直接使用TEJ內建的PER。

# 條件8:PER(以近4季每股盈餘計算)*PBR <= 近三年PER*PBR平均
z1['PEPB'] = z1['當季季底P/E']*z1['當季季底P/B']
z1['mean_PEPB_3'] = z1['PEPB'].rolling(3).mean()
z1['condition_8'] = np.where(z1['PEPB']<=z1['mean_PEPB_3'],1,0)

✅ 9. 計算總分

# 計算總分
z1['score'] = z1['condition_2']+z1['condition_3']+z1['condition_4']+z1['condition_5']+z1['condition_6']+z1['condition_7']+z1['condition_8']
大師策略建構

第四步:策略績效回測

(接下來我們將進入這本篇文章的核心,可能會有些難理解的部分,因此請大家再閱讀前先集中精神👀 📖 👍)

以總分排序,由高至低分成5個組距,分別回測5個投資組合績效

  • 由於該策略是會每年更換投資組合的,當拿到當年度的年報資料時,會計算出當期的score,並根據 “score”由高至低排序,依五分位距分成5個投資組合。ex:前20%的股票為第一組、21%到40%的股票為第二組......
五分位距分組

程式碼解說📚

date : 各期財報年月
buy_date : 設12/31為第 t 日,買進日期為 t+90 日
sell_date : 賣出日期為buy_date+365日(持有一年)
pf_H : 儲存各投資組合的股票代碼
data : 撈取多股(pf_H)年報酬率,日期設定在(buy_date, sell_date)之間
q1_ret : 多股年報酬,日期取最靠近 sell_date 為主
tw0050 : 撈取台灣50指數(TRI50)年報酬率,日期設定在(buy_date, sell_date)之間
財報年月/買進日期/賣出日期/年報酬抓取日期-釋例

假設所有個股權重相等,求算投資組合加權平均報酬:

投資組合股票數量 : len(pf_H)
權重 : w = 1/len(pf_H)
加權平均報酬 : sum(w*q1_ret)
手續費+交易稅 : (0.1425*2*len(pf_H) + 0.3*1)

五個投資組合+台灣50指數各期報酬率

第五步:大師策略 vs 台灣50指數

  • 💻計算大師策略和台灣50指數(benchmark)的累積報酬率💻

台灣50指數2002年後才上市,因此第一個位置數值為NAN。

由於2020-12-31的買進日期為2021-03-31,並且要持有一年,也就是要取2022-03-31的年報酬率,但2022年是未來的資料我們無法取得,因此剃除2020-12-31計算出來的報酬率。

#計算累積報酬率#
cum_ret = return_[['p1_return','p2_return','p3_return','p4_return','p5_return','twn50_return']].astype(float).apply(lambda x:(x*0.01+1).cumprod(),axis=0).reset_index(drop=True)
cum_ret['Date'] = return_['Date']
#剔除2020-12-31#
cum_ret = cum_ret[:len(cum_ret)-1]
#欄位排序#
cum_ret = cum_ret[['Date','p1_return','p2_return','p3_return','p4_return','p5_return','twn50_return']]
cum_ret
大師策略+台灣50指數之累積報酬率
  • 📈資料視覺化📈
plt.style.use('seaborn')
plt.figure(figsize=(10,5))
plt.xticks(rotation = 90)
plt.title('master invest strategy',fontsize = 20)
date = cum_ret['Date']
plt.plot(date,cum_ret.p1_return,color ='red',label='p1_return')
plt.plot(date,cum_ret.p2_return,color ='orange',label='p2_return')
plt.plot(date,cum_ret.p3_return,color ='blue',label='p3_return')
plt.plot(date,cum_ret.p4_return,color ='purple',label='p4_return')
plt.plot(date,cum_ret.p5_return,color ='green',label='p5_return')
plt.plot(date,cum_ret.twn50_return,color = 'black',label='twn50_return')
plt.legend(fontsize = 15)
大師策略 vs 台灣50指數

結果顯示,排序最高分的組別(P1),累積報酬率超過其他組別,甚至超過台灣50指數的 5倍🚀。

  • 📝績效/統計指標📝
Ratio = pd.DataFrame()
for col in cum_ret.columns[1:]:
##年化報酬率
cagr = (cum_ret[col].values[-1]) ** (1/len(cum_ret)) -1
##年化標準差
std = return_[col][:len(return_)-1].astype(float).std()
##Sharpe Ratio(假設無風險利率為1%)
sharpe_ratio = (cagr-0.01)/(std*0.01)
##最大回撤
roll_max = cum_ret[col].cummax()
monthly_dd =cum_ret[col]/roll_max - 1.0
max_dd = monthly_dd.cummin()
##表格
ratio = np.reshape(np.round(np.array([100*cagr, std, sharpe_ratio, 100*max_dd.values[-1]]),2),(1,4))
Ratio = Ratio.append(pd.DataFrame(index=[col],
columns=['年化報酬率(%)', '年化標準差(%)', '夏普比率', '期間最大回撤(%)'],
data = ratio))
Ratio.T
績效/統計指標

結果顯示,排序最高分的組別(P1),年化報酬率🚀、夏普值🚀和期間最大回撤(%)🚀表現皆優於其他組別,惟波動度特別大。

2020年第四季的財報算出最新一期的最高分組別 P1

stk_ranking = result[result['財報年月']== '2020-12-01'].sort_values(by='score',ascending=False).reset_index(drop=True)
first_group = round(len(stk_ranking)*(0.2))
stk_ranking = stk_ranking.loc[0:first_group]['公司代碼'].tolist()
# 回台灣 50成分股查詢 P1組合的名稱
stk_info['stk_num'] = stk_info['成份股'].apply(lambda x: str(x).split(' ')[0])
stk_info['stk_cname'] = stk_info['成份股'].apply(lambda x: str(x).split(' ')[1])
stk_info['成份股'][stk_info['stk_num'].isin(stk_ranking)].to_list()
最新一期P1投資組合

結語

我們透過python實作了大師策略,再對其進行了回測,並透過表格及視覺化的方式呈現了結果。看到結果後是不是相當感動阿 😆😆~

本篇文章內容較多,值得讀者們細細品味與消化,葛拉漢真不愧是證券分析的開山祖師,根據我們回測的結果若自2000年開始投資最高分的組別,其累積報酬超過20倍❗️️️️ ❗️️️️

但要做出一個成功的回測要考慮的因素還很多像是資料品質、資料長度、程式是否有BUG、交易成本是否忽略過多、若是使用基本面還會有資料時間軸等等的相關問題。上述這些問題只要有一個地方出錯都會造成回測的失真,如果還依據這個結果將資金丟入市場,最嚴重就是造成虧損,所以一定要再三注意❗️️️️ ❗️️️️ ❗️️️️ ❗️️️️ ❗️️️️

我們之後也會再分享更多量化投資相關的文章,如果讀者們有甚麼想要回測策略都歡迎在下方留言,我們會挑選合適的主題來進行撰寫喔~最後,如果喜歡本篇文章的內容請幫我們點擊下方圖示👏 ,給予我們更多支持與鼓勵,有任何的問題都歡迎在下方留言/來信,我們會盡快回覆大家👍👍

想要一個"穩定""品質高""資料長度長"的資料源該怎麼辦呢?TEJ API就是你最好的選擇!!

再次附上相關網站連結 💪

🌟有任何使用上的問題都歡迎與我們聯繫:聯絡資訊🌟

附上完整程式碼 🚪

--

--

TEJ 台灣經濟新報
TEJ-API 金融資料分析

TEJ 為台灣本土第一大財經資訊公司,成立於 1990 年,提供金融市場基本分析所需資訊,以及信用風險、法遵科技、資產評價、量化分析及 ESG 等解決方案及顧問服務。鑒於財務金融領域日趨多元與複雜,TEJ 結合實務與學術界的精英人才,致力於開發機器學習、人工智慧 AI 及自然語言處理 NLP 等新技術,持續提供創新服務