Table of Contents
投資組合的績效表現受到許多因素影響,我們不容易清楚區分績效表現是源於大盤上漲、交易員的選股能力,還是資產配置或產業配置得宜? 故我們可以透過 Brinson(1985)提出的績效歸因方法,幫助我們將投資組合與標竿指數間的超額報酬拆解為選擇效果、交互效果與配置效果,了解績效歸因來源,作為日後投資決策的參照。
本文將以台灣50指數做為 00881 ETF的標竿指數,透過 Brinson model來分析 00881 ETF的績效歸因。
本文使用 Windows OS 並以 Jupyter Notebook 作為編輯器
# 功能模組
import pandas as pd
import numpy as np
import plotly.graph_objects as go
#TEJ API
import tejapi
tejapi.ApiConfig.api_key = "Your key"
Step 1. 匯入 ETF成份股、台灣50指數成分股
我們從 TEJ資料庫匯入2021年9月至11月每日更新的 00881 ETF與台灣50指數成份股,可以節省我們至發行 ETF之投信公司查詢的大量時間。若要抓取大量 ETF成份股的資料,使用 TEJ資料庫的優勢更加明顯。
#%% 匯入 TEJ資料
etf = tejapi.get('TWN/AEHOLD',
coid = '00881',
mdate= {'gte': '2021-09-01','lte':'2021-11-30'},
opts={'columns':['mdate', 'no','pct']},
chinese_column_name=True,paginate=True)
# 標竿指數:臺50指數
benchmark = tejapi.get('TWN/AIDXS',
coid = 'TWN50',
mdate= {'gte': '2021-09-01','lte':'2021-11-30'},
opts={'columns':['mdate','key3','mv_pct']},
chinese_column_name=True,paginate=True)
etf = etf[~etf['標的名稱'].isin(['申贖應付款','保證金','現金'])]
etf['證券碼'] = etf['標的名稱'].str[0:4]
etf['證券碼'] = np.where(etf['證券碼'] == 'TX 台','Y9999',etf['證券碼'])
etf['年'] = etf['日期'].dt.year
etf['月'] = etf['日期'].dt.month
etf = etf.drop_duplicates(subset=['年','月','證券碼'], keep='first')
benchmark['證券碼'] = benchmark['成份股'].str[0:4]
benchmark['年'] = benchmark['年月日'].dt.year
benchmark['月'] = benchmark['年月日'].dt.month
benchmark = benchmark.drop_duplicates(subset=['年','月','證券碼'], keep='first')
etf.head(10)
Step 2. 匯入成份股的產業名稱與調整後股價
我們將得到的 00881 ETF與台灣50指數成份股整理成 list
資料型態,並從 TEJ資料庫撈取所需的成份股調整後股價與所屬產業名稱。
# 獲得 etf與 benchmark的代碼
coid_list = etf['證券碼'].unique().tolist()
coid_list.append('Y9999')
coid_list = coid_list + benchmark['證券碼'].unique().tolist()
# 抓取公司的產業名稱
code = tejapi.get("TWN/EWNPRCSTD",
coid = coid_list,
paginate=True,
opts={'columns':['coid', 'coid_name','tseindnm']},
chinese_column_name=True)
code.head(5)
透過 TEJ資料庫直接獲得成份股每月持有報酬率。
# 股價
price = tejapi.get('TWN/AAPRCM1',
coid = coid_list,
mdate= {'gte': '2021-09-01','lte':'2021-11-30'},
opts={'columns':['coid', 'mdate','roi']},
chinese_column_name=True,
paginate=True)
price['年'] = price['年月'].dt.year
price['月'] = price['年月'].dt.month
price.head(5)
Step 3. 合併產業名稱
我們將產業名稱合併至 ETF與指數的 Dataframe
,若 00881 ETF與 台灣50指數成份股所屬產業存在差集,則以其他
替換 ETF與台灣50指數成份股所屬產業差集的產業名稱,以確保 00881 ETF成份股皆有相對應 台灣50指數成份股的產業名稱。
#%% 合併產業名稱 ETF
# 合併產業名稱
etf = pd.merge(etf ,code , how = 'left' , on = ['證券碼'])
etf = pd.merge(etf ,price ,how = 'left' , left_on=['年','月','證券碼'], right_on=['年','月','證券代碼'])
benchmark = pd.merge(benchmark ,code , how = 'left' , on = ['證券碼'])
benchmark = pd.merge(benchmark ,price ,how = 'left' ,
left_on=['年','月','年月日','證券碼'], right_on=['年','月','年月日','證券代碼'])
# 處理產業不一致問題
# 若 benchmark的產業種類沒有在 etf的產業種類中找到,則 benchmark中特殊的產業改成其他
benchmark['TSE產業名'] = np.where(benchmark['TSE產業名'].isin(etf['TSE產業名'].unique().tolist()),benchmark['TSE產業名'],'其他')
# 若 etf的產業種類沒有在 benchmark的產業種類中找到,則 etf中特殊的產業改成其他
etf['TSE產業名'] = np.where(etf['TSE產業名'].isin(benchmark['TSE產業名'].unique().tolist()),
etf['TSE產業名'],'其他')
Step 4. 計算 00881 ETF與台灣50指數的產業月報酬率
分別計算 00881 ETF與台灣50指數中各產業的產業月報酬率與產業權重。
#%% 計算產業與標竿指數的月報酬率,權重
etf = etf.sort_values(by=['年','月','TSE產業名','證券代碼']).reset_index(drop=True) # 排序年月日
etf['TSE產業名'] = np.where(etf['TSE產業名'].isna(),'其他' ,etf['TSE產業名'])
etf['權重'] = etf['權重'] * 0.01
etf['產業權重'] = etf.groupby(['TSE產業名','年','月'])['權重'].transform('sum')
etf['實際當月報酬率'] = etf['權重'] * etf['當月報酬率']
etf['產業當月報酬率'] = etf.groupby(['TSE產業名','年','月'])['實際當月報酬率'].transform('sum') / etf['產業權重']
etf['實際產業當月報酬率'] = etf['產業當月報酬率'] * etf['產業權重']
etf['ETF 當月報酬率'] = etf.groupby(['年','月'])['實際當月報酬率'].transform('sum')
etf = etf[['年','月','TSE產業名','標的名稱','權重','當月報酬率','產業權重','產業當月報酬率']]
benchmark = benchmark.sort_values(by=['年','月','TSE產業名','證券代碼']).reset_index(drop=True) # 排序年月日
benchmark = benchmark[['年月日','TSE產業名','成份股','證券代碼','年','月','前日市值比重','當月報酬率']]
benchmark['前日市值比重'] = benchmark['前日市值比重'] * 0.01
benchmark['產業權重'] = benchmark.groupby(['TSE產業名','年','月'])['前日市值比重'].transform('sum')
benchmark['實際當月報酬率'] = benchmark['前日市值比重'] * benchmark['當月報酬率']
benchmark['產業當月報酬率'] = benchmark.groupby(['TSE產業名','年','月'])['實際當月報酬率'].transform('sum')
/ benchmark['產業權重']
benchmark['實際產業當月報酬率'] = benchmark['產業當月報酬率'] * benchmark['產業權重']
benchmark['ETF 當月報酬率'] = benchmark.groupby(['年','月'])['實際當月報酬率'].transform('sum')
benchmark.head(5)
透過下表所示我們可以將主動報酬分拆成配置效果(Q2-Q1)、選擇效果(Q3-Q1)與交互效果(Q4-Q3+Q2-Q1)。配置效果主要衡量資產類別、國家、產業偏移對績效的影響;選擇效果主要衡量每項類別下「選擇不同標的證券」對績效所造成的影響;而交互效果實務上常常併入配置效果或選擇效果。
#%% 績效歸因表
table = pd.merge(etf ,benchmark ,how = 'left' , on=['年','月','TSE產業名'])
table['配置效果'] = (table['投組權重'] - table['標竿權重']) *
(table['標竿當月報酬率'] - sum(table[:7]['標竿權重'] * table[:7]['標竿當月報酬率']))
table['選擇效果'] = table['標竿權重'] * (table['投組當月報酬率'] - table['標竿當月報酬率'])
table['交互效果'] = (table['投組權重'] - table['標竿權重']) * (table['投組當月報酬率'] - table['標竿當月報酬率'])
table['主動報酬'] = table['配置效果'] + table['選擇效果'] + table['交互效果']
table.loc['合計',:] = table.sum(axis=0)
table.loc['合計','投組當月報酬率'] = sum(table[:7]['投組權重'] * table[:7]['投組當月報酬率'])
table.loc['合計','標竿當月報酬率'] = sum(table[:7]['標竿權重'] * table[:7]['標竿當月報酬率'])
table = (table * 100).round(2)
table
我們計算出11月績效歸因表,發現 00881 ETF有更精準的選股能力,其選擇效果達 1.61%,而因為 00881 ETF主要是投資半導體、網通、電動車個股,00881 ETF與台灣50指數皆有近 60%的權重在半導體產業,產業重疊性高,所以配置效果僅有 0.47%。
繪製三個月各產業的主動報酬雷達圖,可以分析每月各產業貢獻的主動報酬,發現產業對投組主動報酬的貢獻會隨著時間而輪動。輪動是常態,但要注意整體投組主動報酬是否能大於0。
#%%
fig = go.Figure()
for date in etf['月'].unique():
table = pd.merge(etf ,benchmark ,how = 'left' , on=['年','月','TSE產業名'])
table = table[table['月'] == date]
table = table.drop(['年','月'], axis=1)
table = table.set_index(['TSE產業名'])
table = table.sort_values(by=['投組權重'], ascending=False)
table = table.fillna(0)
table['配置效果'] = (table['投組權重'] - table['標竿權重']) *
(table['標竿當月報酬率'] - sum(table[:7]['標竿權重'] * table[:7]['標竿當月報酬率']))
table['選擇效果'] = table['標竿權重'] * (table['投組當月報酬率'] - table['標竿當月報酬率'])
table['交互效果'] = (table['投組權重'] - table['標竿權重']) * (table['投組當月報酬率'] - table['標竿當月報酬率'])
table['主動報酬'] = table['配置效果'] + table['選擇效果'] + table['交互效果']
table.loc['合計',:] = table.sum(axis=0)
table.loc['合計','投組當月報酬率'] = sum(table[:7]['投組權重'] * table[:7]['投組當月報酬率'])
table.loc['合計','標竿當月報酬率'] = sum(table[:7]['標竿權重'] * table[:7]['標竿當月報酬率'])
table = (table * 100).round(2)
fig.add_trace(go.Scatterpolar(r= table.loc['半導體':'其他','主動報酬'].tolist(),
theta= table.drop(['合計']).index,
fill='toself',
name=str(date) + '月'))
fig.show()
績效歸因可幫助我們釐清投組的選股與產業配置是否得宜,可以作為日後投資分析與投資決定時的參照。最後推薦讀者使用 TEJ資料庫提供的基金與指數成份股,讓我們可以獲得不同期間指數的成份股,方便我們配對成份股所屬的產業,分析不同投資組合的績效歸因。
本文供參考之用,因為本文僅用三個月的資料分析 ETF的績效歸因,並無法代表未來 ETF的績效歸因。因此本文不構成要約、招攬或邀請、誘使、任何不論種類或形式之申述或訂立任何建議及推薦,讀者務請運用個人獨立思考能力,自行作出投資決定,如因相關建議招致損失,概與作者無涉。
電子報訂閱