罗杰.金(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就是你最好的选择!!

完整程式码

延伸阅读

本次使用的相关网站连结

返回总览页