羅傑.金(Roger E. King)的投資策略

前言

 羅傑.金是誰?

羅傑‧金(Roger E. King)是美國知名的價值型基金經理人,曾任職太陽保險服務公司(Sun Insurance Services)的資深副總裁兼投資長及葛夫科資本管理公司(GULFCO Investment Management)的資深副總裁,1981年創立國王投資顧問公司(King Investment Advisors Inc.),至2001年,投資經歷長達30年。

原本國王投資顧問公司只管理機構投資者的資金與個人退休基金,1996年底才發行第一檔股票共同基金-源頭特別價值基金(Fountainhead Special Value Fund),1997年即一鳴驚人,獲得36.65%的投資報酬率,1999年投資報酬率達133.44%,2000年雖股市表現不佳,但基金績效只損失15.71%,至2001年5月止,3年平均報酬率達18.22%,超越同期S&P500指數表現12.92個百分點,因此獲得晨星公司(Morningstar Co.)基金評等5顆星的評價,投資績效表現非常優異。

他自行研發一種稱為『企業評價方法(Business Valuation Approach)』的選股策略,以由下而上(bottom up)的選股方式為主,分為三種價值評估方式:

(一)私有市場評價(Private-market Valuation,Liquidation or acquired in a cash transaction);
(二)歷史評價(Historical Valuation);
(三)優越的盈餘成長(Superior Earning Growth,Buy growth at a reduced price:GARP)

三種方法所選出的公司皆可為投資標的,再以財務強度及內部人進出等指標進行篩選;雖然羅傑‧金被市場定位為價值型投資者,但他強調並不排斥買進具有投資價值的成長股,他在接受媒體專訪時曾表示『投資成功的秘訣在於買到便宜的價格(The secret of success is to buy at an inexpensive price),本次介紹的是第二種選股方法「歷史評價」。

本文重點概要

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

羅傑.金

選股程序及標準:以七年歷史資料為期,計算以下指標的狀況,判斷目前股價上漲空間(Potential Upside)下檔風險(Potential Downside)。

歷史最高本益比平均及最低本益比平均。

歷史最高股價淨值比平均及最低股價淨值比平均。

歷史最高股價現金流量比平均及最低股價現金流量比平均。

歷史最高股價營收比平均及最低股價營收比平均。

堅強的財務狀況。

內部人進出的狀況。

從上面指標選擇的項目可以看到,羅傑‧金先找出每年的當年度本益比最大值與最小值,然後分別取平均值,計算出選股時點本益比的上下限;接著納入股價淨值比、現金流量、營收等基本面常見指標,以相同的方式計算出各指標與股價間比例的上下限。藉由多個基本面指標設算出的股價的上下檔目標價,並且以目前股價較接近上檔目標價或下檔目標價,來判斷是否該買入此股票。

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

調整後策略

使用方法

選股程序及標準:選定歷史資料使用年數為7年。

運算過程

歷史本益比上檔目標價=最近7個年度最高本益比平均值x最近一季每股盈餘。

歷史本益比下檔目標價=最近7個年度最低本益比平均值x最近一季每股盈餘。

歷史股價淨值比上檔目標價=最近7個年度最高股價淨值比平均值x最近一季每股淨值。

歷史股價淨值比下檔目標價=最近7個年度最低股價淨值比平均值x最近一季每股淨值。

歷史股價現金流量比上檔目標價=最近7個年度最高股價現金流量比平均值x最近四季每股現金流量。

歷史股價現金流量比下檔目標價=最近7個年度最低股價現金流量比平均值x最近四季每股現金流量。

歷史股價營收比上檔目標價=最近7個年度最高股價營收比平均值x最近12個月每股營收總額。

歷史股價營收比下檔目標價=最近7個年度最低股價營收比平均值x最近12個月每股營收總額。

選股標準

以下四個選股標準,即是計算當前股價,是否較接近該指標所算出的下檔目標價,較接近者就代表價格偏移長期行情的中間值,值得買入。

歷史本益比報酬/風險值=[(歷史本益比上檔目標價-目前股價)/目前股價]/ [(目前股價-歷史本益比下檔目標價)/目前股價]>1。

歷史股價淨值比報酬/風險值=[(歷史股價淨值比上檔目標價-目前股價)/目前股價]/ [ (目前股價-歷史股價淨值比下檔目標價)/目前股價]>1。

歷史股價現金流量比報酬/風險值=[(歷史股價現金流量比上檔目標價-目前股價)/目前股價]/ [ (目前股價-歷史股價現金流量比下檔目標價)/目前股價]>1。

歷史股價營收比報酬/風險值=[(歷史股價營收比上檔目標價-目前股價)/目前股價]/[ (目前股價-歷史股價營收比下檔目標價)/目前股價]>1。

以上4項指標,符合1項即為選股標的。再以下面條件過濾篩選:

剃除選股時間點近一季負債比率大於65%的公司。

按照羅傑金的選股標準「歷史本益比報酬/風險值」,其實計算的就是股價距離下檔目標價的距離,是否比距離上檔目標價來的短,也就是股價目前按照長期均勢的本益比來看,是否偏低,若是,就可以買進。


Python實作

  • 資料收集/資料整併
  • 篩選股票
  • 回測績效
  • 績效視覺化

資料收集/資料整併
匯入套件

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('seaborn')
import tejapi
tejapi.ApiConfig.api_key = 'your_key'
tejapi.ApiConfig.ignoretz = True

撈取財務數據

  • 自證券屬性資料表(TWN/ANPRCSTD)得到所有證券代碼,並篩選出上市普通股。
  • IFRS以合併為主簡表(累計)-全產業 (TWN/AIM1A)撈取財務數據,如日期、每股淨值(F)、常續性EPS、負債比率、每股現金流量、每股營業額。
# 獲取上市普通股代碼 #
stk_info = tejapi.get('TWN/ANPRCSTD',
           paginate=True,
           chinese_column_name=True
          )
stk_nums = stk_info[(stk_info['上市別']=='TSE') & (stk_info['證券種類名稱']=='普通股')]['證券碼'].to_list()
# 撈取財務資料 #
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,'columns':['coid','mdate','200D','R535','R505','R303','R304','MV']}).reset_index(drop=True)
    ).reset_index(drop=True)
    print(code)
資料展示

Tips : 如何搜尋想要的欄位代號,輸入關鍵字,並運行下方程式碼,即可找出相對應的欄位代號~

# IFRS財務會計科目說明檔 #
acc = tejapi.get(‘TWN/AIACC’,paginate=True) 
# 搜尋欄位代碼 #
key_word = '輸入關鍵字'
acc[acc[‘cname’].str.find(key_word)!=-1]

資料整合

2010年12月底的本益比(PE), 股價淨值比(PB), 股價現金流量(PC)和股價營收比(PS),由財報在發佈上有落後的情況,因此上述指標的運算皆使用2010-3-31至2011-3-31的股價資料進行計算。

下方程式碼功能為計算個年度本益比(PE), 股價淨值比(PB), 股價現金流量(PC)和股價營收比(PS)之最高和最低值。

PS. 由於資料量較大,程式運行時間也較久大約30~40分鐘

results = pd.DataFrame()
#coid = str(1101)
for coid in data['公司代碼'].unique():
    stock = tejapi.get('TWN/APRCD1',coid = coid, chinese_column_name = True)
    for year in data['year'].unique():
        condition = (data['year']==year) & (data['公司代碼']== coid)
        if (data[condition].size !=0):
            indicator_start = data['indicator_start'][condition].to_list()[0]
            stk_start = data['start'][condition].to_list()[0]
            stk_end = data['end'][condition].to_list()[0]
            # 抓取財報季度前一年的資料 #
            selected = stock[(stock['年月日'] > indicator_start) & (stock['年月日'] < stk_start)].reset_index(drop=True)
            back_test = stock[(stock['年月日'] >= stk_start) & (stock['年月日'] < stk_end)].reset_index(drop=True)
            if (selected.size!=0) and (back_test.size!=0):
                # 計算個年度最高與最低 本益比、股價淨值比、股價現金流量比和股價營收比 #
                price = selected['收盤價(元)'].tail(1).to_list()[0]
                stk_start_price = back_test['收盤價(元)'].head(1).to_list()[0]
                ## 本益比 ##
                EPS = data['常續性EPS'][condition].to_list()[0]
                PE = selected['收盤價(元)']/EPS
                PE_max = PE.max()
                PE_min = PE.min()
                ## 股價淨值比 ##
                BV = data['每股淨值(F)-TSE公告數'][condition].to_list()[0]
                PB = selected['收盤價(元)']/BV
                PB_max = PB.max()
                PB_min = PB.min()
                ## 股價現金流量比 ##
                CFP = data['每股現金流量'][condition].to_list()[0]
                PC = selected['收盤價(元)']/CFP
                PC_max = PC.max()
                PC_min = PC.min()
                ## 股價營收比 ##
                SP = data['每股營業額'][condition].to_list()[0]
                PS = selected['收盤價(元)']/SP
                PS_max = PS.max()
                PS_min = PS.min()
                # 彙整 # 
                result = pd.DataFrame({
                    'year':year,
                    '公司代碼':coid,
                    'stk_start':stk_start,
                    'stk_end':stk_end,
                    'stk_start_price':stk_start_price,
                    'PE_max':PE_max,'PE_min':PE_min,
                    'PB_max':PB_max,'PB_min':PB_min,
                    'PC_max':PC_max,'PC_min':PC_min,
                    'PS_max':PS_max,'PS_min':PS_min
                },index=[0])
                results = results.append(result).reset_index(drop=True)
    print(coid)

滾動平均

根據上面得到的結果針對本益比(PE), 股價淨值比(PB), 股價現金流量(PC)和股價營收比(PS)進行7年移動平均計算,並將當期的EPS、每股淨值、每股現金流量和每股營收合併,即可得出當前的最高目標價最低目標價。最後再進一步求算出風險報酬比。

mean_7 = results.groupby(by=['公司代碼']).rolling(7).mean()[['PE_max','PE_min','PB_max','PB_min','PC_max','PC_min','PS_max','PS_min']]
mean_7 = mean_7.reset_index(drop=True)
mean_7['year'],mean_7['公司代碼'],mean_7['stk_start_price'] = results['year'], results['公司代碼'],results['stk_start_price']
# 合併 #
mean_7 = pd.merge(mean_7.dropna(),data,on=['公司代碼','year'])
# 去除市值小於中位數者 #
mean_7 = mean_7[(mean_7['季底普通股市值']>mean_7['mv_median'])].reset_index(drop=True)
資料展示

篩選股票

篩選條件

歷史本益比報酬/風險值=[(歷史本益比上檔目標價-目前股價)/目前股價]/ [(目前股價-歷史本益比下檔目標價)/目前股價]>1。

歷史股價淨值比報酬/風險值=[(歷史股價淨值比上檔目標價-目前股價)/目前股價]/ [ (目前股價-歷史股價淨值比下檔目標價)/目前股價]>1。

歷史股價現金流量比報酬/風險值=[(歷史股價現金流量比上檔目標價-目前股價)/目前股價]/ [ (目前股價-歷史股價現金流量比下檔目標價)/目前股價]>1。

歷史股價營收比報酬/風險值=[(歷史股價營收比上檔目標價-目前股價)/目前股價]/[ (目前股價-歷史股價營收比下檔目標價)/目前股價]>1。

以上4項指標,符合1項即為選股標的。再以下面條件過濾篩選:

剃除選股時間點近一季負債比率大於65%的公司。

# 目標價計算 #
## 本益比 ##
mean_7['PE_TP_upper'] = mean_7['PE_max']*mean_7['常續性EPS']
mean_7['PE_TP_bottom'] = mean_7['PE_min']*mean_7['常續性EPS']
mean_7['PE_ret_risk'] = (mean_7['PE_TP_upper']-mean_7['stk_start_price'])/(mean_7['stk_start_price']-mean_7['PE_TP_bottom'])
## 股價淨值比 ##
mean_7['PB_TP_upper'] = mean_7['PB_max']*mean_7['每股淨值(F)-TSE公告數']
mean_7['PB_TP_bottom'] = mean_7['PB_min']*mean_7['每股淨值(F)-TSE公告數']
mean_7['PB_ret_risk'] = (mean_7['PB_TP_upper']-mean_7['stk_start_price'])/(mean_7['stk_start_price']-mean_7['PB_TP_bottom'])
## 股價現金流量比 ##
mean_7['PC_TP_upper'] = mean_7['PC_max']*mean_7['每股現金流量']
mean_7['PC_TP_bottom'] = mean_7['PC_min']*mean_7['每股現金流量']
mean_7['PC_ret_risk'] = (mean_7['PC_TP_upper']-mean_7['stk_start_price'])/(mean_7['stk_start_price']-mean_7['PC_TP_bottom'])
## 本益比 ##
mean_7['PS_TP_upper'] = mean_7['PS_max']*mean_7['每股營業額']
mean_7['PS_TP_bottom'] = mean_7['PS_min']*mean_7['每股營業額']
mean_7['PS_ret_risk'] = (mean_7['PS_TP_upper']-mean_7['stk_start_price'])/(mean_7['stk_start_price']-mean_7['PS_TP_bottom'])
# 評分 #
mean_7.loc[:,'score'] = 0
mean_7['score'] = np.where(mean_7['PE_ret_risk'] > 1, 1+mean_7['score'], mean_7['score'])
mean_7['score'] = np.where(mean_7['PB_ret_risk'] > 1, 1+mean_7['score'], mean_7['score'])
mean_7['score'] = np.where(mean_7['PC_ret_risk'] > 1, 1+mean_7['score'], mean_7['score'])
mean_7['score'] = np.where(mean_7['PS_ret_risk'] > 1, 1+mean_7['score'], mean_7['score'])
資料展示

Backtest

  • If one of four conditions is met, we will buy the stock.
  • Rebalancing portfolio per.
  • Equally weighted.
  • The benchmark is twse.
  • The buy date is t+90, holding period is one year, and the sell date is one year after the buy date.
Date/Buy_date/Sell_date/date of yearly return -explanation
%%time
return_=pd.DataFrame()
for year in range(2011,2020):
    pf = mean_7[(mean_7['year']==year) & (mean_7['負債比率'] < 65) & (mean_7['score']>=1)]#.sort_values(by = 'score', ascending = False).reset_index(drop=True)
    ## 將買進日期設在季底+90日 ##
    buy_date = mean_7['start'][mean_7['year']==year].head(1).to_list()[0]
    sell_date = mean_7['end'][mean_7['year']==year].head(1).to_list()[0]
    ret = pd.DataFrame()
    pf_H = pf['公司代碼'].to_list()
    print('投資組合共有{}檔股票'.format(len(pf_H)))
    ## 自 tejapi撈取日報酬資料,日期設定為 buy_date(不含)至 sell_date(含) ##
    print('getting data')
    stk_data = tejapi.get('TWN/APRCD2',
                          coid = pf_H,
                          paginate = True,
                          mdate={'gt':buy_date,'lt':sell_date},
                          chinese_column_name=True)
    # 計算報酬率 #
    print('calculating return')
    pf_ret = stk_data.groupby(by = '年月日').mean()['日報酬率 %']
    pf_ret = pf_ret.reset_index(drop=True)
#     ret[ranking[i]] = pf_ret
    ret['portfolio'] = pf_ret
#     i+=1
    ## 撈取台灣加權指數的日報酬率,日期設定為 buy_date(不含)至 sell_date(不含) ##
    twse = tejapi.get('TWN/APRCD2',coid ='Y9999' ,paginate = True,mdate={'gt':buy_date,'lt':sell_date},chinese_column_name=True)
    bm_return = twse.groupby('年月日').mean()['日報酬率 %'].reset_index(drop=True)
    ret['twse_return'] = bm_return
    ret['Date'] = twse['年月日']
    return_ = return_.append(ret).reset_index(drop=True)
    print(return_)
Daily return

累積報酬

cum_ret = return_[['portfolio','twse_return']].astype(float).apply(lambda x:x*0.01+1).cumprod().reset_index(drop=True)
cum_ret['Date'] = return_['Date']
cum_ret
累積報酬 – 回測

績效視覺化

累積報酬折線圖

cum_ret.plot(x='Date',figsize=(10,6))
累積報酬折線圖

績效指標/報表

# 績效報表 #
yearly_mean_ret = (((cum_ret[['portfolio','twse_return']].tail(1)**(1/len(cum_ret)))**252-1)*100).reset_index(drop=True)
yearly_std = cum_ret[['portfolio','twse_return']].std()*(252**(0.5))
Rf = 1
sp_ratio = (yearly_mean_ret-Rf)/yearly_std
roll_max = cum_ret[['portfolio','twse_return']].cummax()
draw_down = (cum_ret[['portfolio','twse_return']]-roll_max)/roll_max
MDD = draw_down.min()*100
performace_report = pd.DataFrame({
    'portfolio':[yearly_mean_ret['portfolio'].values[0],
                 yearly_std['portfolio'],
                 sp_ratio['portfolio'].values[0],
                 MDD['portfolio']],
    'twse_return':[yearly_mean_ret['twse_return'].values[0],
                   yearly_std['twse_return'],
                   sp_ratio['twse_return'].values[0],
                   MDD['twse_return']]}
    ,index= ['年化報酬','年化波動度','夏普指標','最大回檔'])
performace_report
績效報表

最新一期的投資組合

pf2020 = mean_7[(mean_7['year']==2020) & (mean_7['負債比率'] < 65) & (mean_7['score']>=1)]['公司代碼'].to_list()
stk_info[['證券碼','證券名稱']][stk_info['證券碼'].isin(pf2020)].reset_index(drop=True)
2020之投資組合

總結

由報酬率來看,我們的投資組合確實勝過大盤指數,但將風險納入考慮之後,可以看出台灣加權指數分別在波動度、夏普指標和最大回檔皆勝過我們的投資組合😢😢。

這次的羅傑.金的策略在報酬上只有險勝大盤指數一點,是不是相當意外阿~不過,會出現這樣的結果也不必太過驚訝,畢竟投資領域上並無一個長期穩定的聖杯所有的條件都應該隨著時間進行動態調整 👍👍

重要的是我們如何從這些過往的投資大師身上汲取經驗和刺激想法,最後經過自己的一番練化,去蕪存菁,建構出適合自己的投資策略😄,

我們將在下期繼續分享三一資產管理公司的投資策略,敬請期待!!

最後,如果喜歡本篇文章的內容請幫我們點擊下方圖示👏 ,給予我們更多支持與鼓勵,有任何的問題都歡迎在下方留言/來信,我們會盡快回覆大家👍👍

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

完整程式碼

延伸閱讀

本次使用的相關網站連結

返回總覽頁
Procesing