ESG量化因子選股

本文重點概要

  • 文章難度:★★★☆☆
  • 檢驗ESG因子是否帶來顯著超額報酬
  • 閱讀建議: 本篇文主要探討員工流動率的差異,是否與公司未來報酬率有關連性,而員工流動率為衡量公司治理的一項指標,因此本文提供讀者基礎的架構去驗證ESG投資的可行性。欲瞭解更詳細的選股與回測流程,推薦觀看TEJ ESG量化多因子選股影片, 挖掘更多能帶來獲利的因子!

前言

近年來開始興起一種投資,叫做環境、社會和治理(ESG)投資,指的是在投資決策時,不再只考慮公司的財務表現,而是額外考慮企業對於環境、社會的影響力,以及公司的行為與準則等。彼此之間不再是抵換關係,而是一種良善的循環,提供公司未來前景的保證,進而為投資者帶來豐富的報酬。

因子投資,即試圖找出幾個關鍵的影響因素,像是文獻常見的規模因子、帳市值比因子、風險因子等等,並預期這些因素能帶來超額報酬。因此本週我們以TEJ資料庫提供的「員工流動率」當作因子,看看ESG投資的成果吧!

編輯環境及模組需求

本文使用 Windows OS 並以 Jupyter Notebook 作為編輯器

#功能與視覺化模組
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
#TEJ
import tejapi
tejapi.ApiConfig.api_key = 'Your Key'
tejapi.ApiConfig.ignoretz = True

資料庫使用

資料處理

Step 1. 員工流動率資料撈取

turnover = tejapi.get('TWN/ACSR01A',
        paginate = True,
        opts = {'columns':['coid','mdate','turn_rate','num_staff']},
        chinese_column_name = True)

Step 2. 刪除缺乏員工流動率的資料

turnover = turnover[turnover['員工流動率(%)'].isnull() == False]

投組建立

Step 1. 空表格建立,用於儲存分群結果、投組報酬率;並建立日期列表

result = pd.DataFrame()
ret_table = pd.DataFrame()

因為員工流動率於年報揭露,為了避免前視偏誤,因此本文以年報最晚公布日 (隔年三月底)作為投組建構日,並持有一年。但由於按照2020年報資訊所建立的投組,投組持有期間為 2021–03–31 ~ 2022–03–31,尚未持滿一年,因此會排除該年。

date_list = sorted(turnover['年度'].unique())[:-1]

Step 2. 每年按員工流動性大小建立10個投組,並計算報酬率

接下來以迴圈進行每年報酬率計算,以下內容以第一年 (date = ‘2008–01–01’)的資料處理情形幫助理解,完整迴圈請參考完整程式碼

按照員工流動率,將當年樣本分10群

#當期資料選取
data = turnover[turnover['年度'] == date].reset_index(drop=True)
#刪除員工人數過少
data = data[data['員工人數'] >= data['員工人數'].quantile(0.1)]
    
#分群
data['group'] = pd.qcut(data['員工流動率(%)'], q=10,labels = [i for i in range(1,11)])
    
#儲存
result = result.append(data)

首先選取該年資料,並且刪除員工人數過低 (小於十分位數)樣本。接著使用 函數 pd.qcut(),依員工流動率由小到大形成十個組別,並呈現在group欄,最後儲存到 result

計算當年各個投組的報酬率

#投組賣出日期 
sell_date = date + pd.Timedelta(days = 365 + 90 + 365)
    
#投組報酬
port_ret = [date]

根據2008年資訊建立的十個投組,都將於 sell_date (2010–03–31)賣出。而日期 (2008–01–01)與這些投組報酬,將存放於 port_ret 列表

#計算當年各組的投組報酬
for group in range(1,11):
        
      #當年,某組的資料
      sub_data = data[data['group'] == group].reset_index(drop=True)
    
      #報酬率撈取
      ret = tejapi.get('TWN/APRCD2',
                   coid = sub_data['公司碼'].tolist(),
                   mdate = {'gte':sell_date - pd.Timedelta(days = 5), 'lte':sell_date},
                   opts = {'columns':['coid','mdate', 'roi_y']},
                   paginate = True,
                   chinese_column_name = True)
        
       #只需要最後一筆
       ret = ret.groupby(by='證券代碼').last().reset_index()
    
       #投組報酬(%)
       port_ret.append(ret['年報酬率 %'].mean())
#表格
ret_table =  ret_table.append(pd.DataFrame(data = np.array(port_ret).reshape((1,11)), columns = ['日期'] + [i for i in range(1,11)])).reset_index(drop=True)

接著按照組別由小到大進行迴圈。首先先進一步篩選出某組的資料,然後根據這個組別包含的公司、賣出日期撈取年報酬率(%)。這邊採用的技巧是先撈取靠近賣出日的年報酬率資料,接著取最靠近賣出日期的年報酬,此即為過去完整一年內的報酬率。最後再取平均值,即為該組投組的等權報酬率,各組都計算完成後,再將列表 port_ret 形成表格後存入 ret_table

其他年份一樣重複以上步驟,透過迴圈不斷地去更新 result ret_table ,最後得到以下結果。

資料結果視覺化 (詳見完整程式碼)

每組平均員工流動率 (%)

每年每組報酬率表現

 

累積報酬率

cum_ret = ret_table[[i for i in range(1,11)]].apply(lambda x : (x*0.01 + 1)).cumprod()
cum_ret.insert(0, '日期', date_list)

先計算這十組投組的累積報酬率,再補上日期,最後再畫出

夏普值(設無風險利率 1%)

sharpe_list = []
for i in range(1,11):
    
    #年化報酬率
    cagr = (cum_ret[i].values[-1]**(1/len(cum_ret)) - 1)*100
    
    #年化標準差
    std = ret_table[i].std()
    
    #更新list
    sharpe_list.append(i)
    sharpe_list.append((cagr-1)/std)
#形成表格
sharpe = pd.DataFrame(np.array(sharpe_list).reshape((10,2)), columns = ["group","夏普比率(%)"])

先計算出夏普比率,再畫出

結論

從累積報酬圖與夏普值可以發現,員工流動率最高的第十組表現最差,即使其在部分年間有好的報酬表現。但這並不意味著員工流動率低,公司的表現就一定會比較好,例如從累積報酬圖來看,第七組是表現最好的,或許這也代表保持一定的員工流動,反而能維持公司的競爭力與創新意識。

整體而言,在市場表現差時,員工流動率較差的公司損失幅度較大,代表ESG因子某種程度提供一定的下行風險保護。如果讀者對於其他ESG資料有興趣,歡迎到 TEJ E-Shop 選擇最適的方案,找出更多創造超額報酬的因子!

完整程式碼

延伸閱讀

相關連結

返回總覽頁
Procesing