事件型因子研究:公司宣告發放股利

普遍來說,公司宣告發放股利意味著公司營運上相對穩健,有足夠盈餘或是資本公積可供發放,除了能回饋股東長期以來的支持,也可向外宣告公司的經營良好並提高整體股價表現。但也有另一派觀點認為,公司若發放股利恐被市場認為,其本業上缺乏現金流為正的投資機會,因此選擇將資金發還給股東,而這也代表公司股價長期下來可能有所下降。

前言

普遍來說,公司宣告發放股利意味著公司營運上相對穩健,有足夠盈餘或是資本公積可供發放,除了能回饋股東長期以來的支持,也可向外宣告公司的經營良好並提高整體股價表現。但也有另一派觀點認為,公司若發放股利恐被市場認為,其本業上缺乏現金流為正的投資機會,因此選擇將資金發還給股東,而這也代表公司股價長期下來可能有所下降。

因此本研究透過TEJ的股東會事項資料庫中的股利政策資訊,使用事件研究法,針對2013年1月至2025年1月的上下市櫃公司,探討公司宣告發放股利後是否能帶動股價上漲賺取異常報酬。

  • 編輯環境與模組需求

本文使用Windows 11系統和Jupyter Notebook作為編輯器。

  • 資料導入與環境設定

套件引入與環境參數設定

import tejapi

import os

import warnings

warnings.filterwarnings('ignore')

os.environ['TEJAPI_KEY'] = 'Your Api Key'

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

plt.style.use('ggplot')

plt.rcParams['font.sans-serif'] = ['SimHei'] 

plt.rcParams['axes.unicode_minus'] = False

樣本資料:2013/01/01至2025/01/20,所有曾上市櫃並且發放股利之普通股(含KY股)。

  • get_universe:抓取2013/01/01至2025/01/20所有曾上市櫃的普通股股票代碼。
  • simple_ingest:綁入指定期間與股票池的相關資料進Zipline-tej回測引擎。
  • tejapi:抓取tej資料庫中的上市(櫃)股東會事項(TWN/AMT)表,作為本次研究的樣本事件來源,並且以其中的董事會日期欄位作為事件日。
  • get_bundle, get_fundamentals:將前面simple_ingest綁入的資料匯出成dataframe,方便後續研究進行整併分析。
from zipline.sources.TEJ_Api_Data import get_universe

from zipline.data.run_ingest import simple_ingest

from zipline.data.data_portal import get_bundle, get_fundamentals

pool = get_universe('20130101', '20250120', mkt = ['TWSE', 'OTC'], stktp_e = ['Common Stock', 'Common Stock-Foreign'])

simple_ingest(name = 'tquant' , tickers = pool, start_date = '20130101' , end_date = '20250120')

simple_ingest(name = 'fundamentals' , tickers =  pool, start_date = '20130101' , end_date = '20250120' , fields = ['main_ind_c', 'Market_Cap_Dollars'])

bundle_price = get_bundle(start_dt = pd.Timestamp('20130101', tz = 'utc'), end_dt = pd.Timestamp('20250120' ,tz = 'utc'))

bundle_fundamentals= get_fundamentals(start_dt = pd.Timestamp('20130101', tz = 'utc'), end_dt = pd.Timestamp('20250120' ,tz = 'utc'))

bundle_fundamentals['date'] = pd.to_datetime(bundle_fundamentals['date'])

bundle_price['date'] = pd.to_datetime(bundle_price['date'])

df = tejapi.get('TWN/AMT', coid = pool, mdate={'gte': '20130101', 'lte': '20250120'}, paginate = True, chinese_column_name = True)
  • 資料清洗與整併

由於上市(櫃)股東會事項表中除了公司宣告發放股利的事件外,尚有其餘事件如董監改選、現金增資/減資等,因此需進一步對資料進行清洗方便後續研究。

此處主要將表中無盈餘分配_年度、現金股利(元)加上無償配股合計(元)為0的事件排除。

dividend = df.sort_values(['公司', '董事會日期', '盈餘分派_年度'])[['公司', '董事會日期', '盈餘分派_年度', '股息分配型態', '現金股利(元)', '無償配股合計(元)']].dropna(subset=['盈餘分派_年度']).reset_index(drop=1)

dividend['dividend'] = dividend['現金股利(元)'] + dividend['無償配股合計(元)']

dividend = dividend.drop_duplicates(subset=['公司', '董事會日期']).reset_index(drop=1)

dividend = dividend.rename(columns={'公司': 'symbol', '董事會日期': 'date'})

dividend = dividend.query("dividend != 0")

由於董事會日期並不一定為股市交易日,若不將其進行調整恐在資料合併時遺失樣本,因此從get_calendar中獲取股市實際交易日,並使用searchsorted函式找尋最接近董事會日期的交易日作為date。

from zipline.master import get_calendar

cal = get_calendar('TEJ').all_sessions.tz_convert(None)

cal = cal[(cal >= '20130101') & (cal <= '20250120')]

dividend['date'] = dividend['date'].apply(lambda x: cal[cal.searchsorted(x)])

dividend['date'] = pd.DatetimeIndex(dividend['date']).tz_localize(None)

與前述經由simple_ingest導入的基本面資料(主產業別、個股市值)進行合併,並排除無產業別或市值的樣本(上市(櫃)股東會事項表中會包含公司處於興櫃時的資料,非本次研究範圍故排除)。

此外,為求將上市與上櫃股票的產業別統合起來方便查看與分析,使用下列字典進行正規化,將27個主產業進一步聚合為3大產業(傳統、金融與電子)。

dividend = dividend.merge(bundle_fundamentals, on=['date', 'symbol'], how='left')

dividend = dividend.dropna(subset=['Industry', ‘Market_Cap_Dollars’]).reset_index(drop=1)

ind_dict = {'水泥工業': '水泥工業', '食品工業': '食品工業', '農業科技': '農業科技', '觀光餐旅': '觀光餐旅', '其他': '其他', '塑膠工業': '塑膠工業', '化學生技醫療': '化學生技醫療', '建材營造': '建材營造', '汽車工業': '汽車工業', '電子類': '電子工業', '紡織纖維': '紡織纖維', '貿易百貨': '貿易百貨', '電子工業': '電子工業', '電機機械': '電機機械', '鋼鐵工業': '鋼鐵工業', '化學生技': '化學生技醫療', '其它': '其他', '運動休閒': '運動休閒', '電器電纜': '電器電纜', '玻璃陶瓷': '玻璃陶瓷', '造紙工業': '造紙工業', '居家生活': '居家生活', '橡膠工業': '橡膠工業', '橡膠類': '橡膠工業', '航運業': '航運業', '油電燃氣業': '油電燃氣業', '數位雲端': '數位雲端', '金融業': '金融業', '証券': '金融業', '百貨類': '貿易百貨', '文化創意業': '文化創意業', '綠能環保': '綠能環保', '電子商務': '電子商務', '證券類': '金融業'}

top_ind_dict = {'水泥工業': '傳統產業', '食品工業': '傳統產業', '農業科技': '傳統產業', '觀光餐旅': '傳統產業', '其他': '傳統產業', '塑膠工業': '傳統產業', '化學生技醫療': '傳統產業', '建材營造': '傳統產業', '汽車工業': '傳統產業', '紡織纖維': '傳統產業', '貿易百貨': '傳統產業', '電機機械': '傳統產業', '鋼鐵工業': '傳統產業', '運動休閒': '傳統產業', '電器電纜': '傳統產業', '玻璃陶瓷': '傳統產業', '造紙工業': '傳統產業', '居家生活': '傳統產業', '橡膠工業': '傳統產業', '航運業': '傳統產業', '油電燃氣業': '傳統產業', '文化創意業': '傳統產業', '綠能環保': '傳統產業', '數位雲端': '傳統產業', '電子商務': '傳統產業', '電子工業': '電子產業', '電子類': '電子產業', '金融業': '金融產業', '證券': '金融產業'}

dividend['Industry'] = dividend['Industry'].str.split(' ').str[-1].map(ind_dict)

dividend['top_ind'] = dividend['Industry'].map(top_ind_dict)

經過上述資料清洗與整併後的資料應如下圖所示,共計有15031筆樣本。

  • 事件研究法(Event Study Methodology)

事件研究法常用來分析公司發生特定事件前後,市場對該公司股票的反應是否存在顯著的異常報酬(Abnormal Return, AR),以及在該事件前後的累積異常報酬(Cumulative Abnormal Return, CAR)是否能持續顯著,若此事件存有顯著的異常報酬與累積異常報酬,則可預期未來當公司再次發生相同事件時,投資者可進場買入該公司股票賺取顯著正報酬。

而在事件研究法中最關鍵點莫過於預期報酬的估計方式,常見估計方式有平均調整模式、市場指數調整模式、OLS估計法與GARCH估計法等眾多方法,本研究使用市場指數調整模式,主要分析公司在經歷特定事件後的股票報酬是否能超過同期市場指數報酬,從而出現顯著異常報酬值。

  • ret:使用前面經simple_ingest綁入的調整後收盤價計算個股報酬率。
  • benchmark:使用get_Benchmark_Return函式載入2013/01/01至2025/01/20期間的加權報酬指數日報酬。
from zipline.sources.TEJ_Api_Data import get_Benchmark_Return

ret = bundle_price.pivot(index='date', columns='symbol', values='close_adj').pct_change().fillna(0)

benchmark = get_Benchmark_Return('20130101', '20250120', 'IR0001').to_frame('IR0001')

excess_ret = ret.join(benchmark)

excess_ret = excess_ret.sub(excess_ret['IR0001'], axis=0)

excess_ret.index = excess_ret.index.tz_convert(None)

有了經過同期間市場指數調整後的個股日報酬值後,本研究取事件日前後21交易日作為事件窗口,觀察公司在股利宣告日前後約一個月期間的異常報酬變化。其中事件日後報酬(aft_ret)取事件日後一日(t1)開始之個股日報酬,因為董事會宣布發放股利屬重大訊息,原則上申報時點為事實發生日次一營業日交易時間開始2小時前,因此對投資人而言均是董事會開會次一交易日才能接收到股利宣告消息並進行交易買賣。

dividend['-t21_date'] = dividend.apply(lambda x: cal[cal.searchsorted(x['date']) - 21], axis=1)

dividend['t1_date'] = dividend.apply(lambda x: cal[cal.searchsorted(x['date']) + 1], axis=1)

dividend['t21_date'] = dividend.apply(lambda x: cal[cal.searchsorted(x['date']) + 21 if cal.searchsorted(x['date']) + 21 < len(cal) else len(cal)-1], axis=1)

dividend['bef_ret'] = dividend.apply(lambda x: excess_ret.loc[x['-t21_date']: x['date'], x['symbol']].tolist(), axis=1)

dividend['aft_ret'] = dividend.apply(lambda x: excess_ret.loc[x['t1_date']: x['t21_date'], x['symbol']].tolist(), axis=1)

bef_ret = pd.DataFrame(dividend['bef_ret'].tolist(), columns=[f"t{i-21}" for i in range(22)])

aft_ret = pd.DataFrame(dividend['aft_ret'].tolist(), columns=[f"t+{i}" for i in range(1, 22)])

ar = bef_ret.join(aft_ret).mean().to_frame('ar')

ar = ar.assign(car=lambda x: x['ar'].cumsum())

ar = ar.rename_axis('date').reset_index()

import plotly.graph_objects as go

fig = go.Figure()

fig.add_trace(go.Bar(x=ar.index, y=ar['ar'], name='AAR', marker_color='blue', opacity=0.6))

fig.add_trace(go.Scatter(x=ar.index, y=ar['car'], mode='lines+markers', name='CAAR', line=dict(color='red')))

fig.update_xaxes(ticktext=ar.date, tickvals=ar.index)

fig.update_layout(

    title="股利宣告日前後21天AAR與CAAR",

    xaxis_title="事件日",

    yaxis_title="報酬",

    legend_title=" ",

)

fig.add_vline(x=21, line_width=3, line_dash="dash", annotation_text='股利宣告事件日')

fig.show()

使用plotly.express進行視覺化呈現,從下圖可以看出,股利宣告日(t0)前21個交易日,CAAR就從t-21日的0.076%增加至t0日的1.228%,且均達到5%內顯著水平,顯示說市場在董事會開會前就有所反應,可能預期宣告股利後有進一步正向的股票反應故提前買入。

而在股利宣告後的次一交易日(t+1)中可看出,市場普遍對股票有正向的反應,AAR為0.368%,CAAR來到1.595%,也均達到5%內顯著水平。此外直至t+17日前仍持續有正向的AAR,CAAR最高來到2.193%,後續AAR才逐漸轉負,顯示市場約花3週時間吸收並反應該事件消息。

註: ^代表介於5%至10%顯著水平;*代表達5%內顯著水平。

接著可以使用前面聚合的各產業來進一步觀察股利宣告效果是否存在於各大產業,抑或是集中於特定某大產業中。

bef_ret = dividend.groupby('top_ind').apply(lambda x: pd.DataFrame(x['bef_ret'].tolist(), columns=[f"t{-i}" for i in range(21, -1, -1)])).rename_axis(['top_ind', 'index']).reset_index()

aft_ret = dividend.groupby('top_ind').apply(lambda x: pd.DataFrame(x['aft_ret'].tolist(), columns=[f"t+{i}" for i in range(1, 22)])).rename_axis(['top_ind', 'index']).reset_index()

ind_ar = bef_ret.merge(aft_ret, on=['index', 'top_ind'], how='left').drop('index', axis=1)

ind_ar = ind_ar.groupby('top_ind').mean()

ind_ar = pd.concat([ind_ar, ind_ar.cumsum(axis=1)])

ind_ar['type'] = ['ar'] * 3 + ['car'] * 3

from plotly.subplots import make_subplots

import plotly.graph_objects as go

fig = make_subplots(rows=3, cols=1, row_heights=[60, 60, 60], 

                    subplot_titles=("傳統產業", "金融產業", "電子產業"), vertical_spacing=0.08)

for index, (ind_namem, data) in enumerate(ind_ar.groupby(level=0)):

    data = data.set_index('type').T

    fig.add_trace(go.Bar(x=data.index, y=data['ar'], name='AAR', marker_color='blue', opacity=0.6, showlegend=(index == 0)), row=index+1, col=1, )

    fig.add_trace(go.Scatter(x=data.index, y=data['car'], mode='lines+markers', name='CAAR', line=dict(color='red'), showlegend=(index == 0)), row=index+1, col=1)

    fig.update_xaxes(ticktext=data.index, tickvals=data.index)

    fig.update_yaxes(title='報酬', row=index+1, col=1)

fig.update_layout(

    height=1200,

    title="股利宣告日前後21天AAR與CAAR-三大產業",

    legend_title=" ",

    showlegend=True

)

fig.add_vline(x=21, line_width=3, line_dash="dash", annotation_text='股利宣告事件日')

fig.show()

從下圖中可看出,劃分成三大產業後股利宣告效果又有所不同,總體來說,傳統與電子產業類似,股價在股利宣告日前21個交易日時就有所反應,AAR普遍均為正帶動CAAR穩定增加,顯示市場普遍看好並提前卡位。但金融產業較為特別,t-21日至t-5日的AAR相當波動,整體CAAR從-0.085%略為降低至-0.26%,與另外兩大產業在t-5日之前的表現有明顯差異,但在t-5日後AAR隨即出現連續的正向反應,帶動CAAR回正至0.030%,顯示市場多在董事會會議前一週才進場卡位等待金融公司宣布股利發放消息。

而在股利宣告次一交易日(t+1)中,傳統與電子產業的AAR分別為0.302%與0.417%相差不大,金融產業則高達0.610%,相當於傳統產業兩倍,並且拉動CAAR來到5%顯著水平的0.640%,顯示投資人積極買入帶動股價超越同期市場指數。

宣告日後的t+2至t+21日則能看出,傳統與電子產業基本持續穩定向上,CAAR分別達到5%顯著的1.792%和2.511%,但反觀金融產業則是相對萎靡,宣布後首週內(t+5)反而下降至0.661%,並且在之後的時間中也僅略微爬升至0.739%。顯示市場對傳統和電子產業公司的股票反應較慢且較為正向,在宣告日後仍持續有正向的AAR。但對金融產業公司股票反應迅速,推測是因為市場對於金融產業資訊掌握度較高,因此在股票價格上較不容易出現反應不足的問題。

CAAR%t-21~t0t-21~t+1t-21~t+21
傳統產業0.848*1.150*1.792*
金融產業0.0300.640*0.739*
電子產業1.686*2.103*2.511*

註: ^代表介於5%至10%顯著水平;*代表達5%內顯著水平。

除了劃分不同產業外,另外也可以根據最新一期股利金額與前一期股利金額變化來進行分析,觀察股利的增加、減少與不變是否有不同的資訊內涵。但由於2018/11/01公司法228-1條實施新規可於一年內發放多次股利,因此在計算時還需根據股息分配型態欄位切割不同頻率(年頻、半年頻和季頻)的股利資料,避免計算出公司從年頻轉為季頻而被誤認為減少股利發放。

→2018年之後公司可於一年發放多次股利,各家公司股利發放不同步,如何比較? 

TQuant LAB股利政策資料,整理年度股利總額,讓您快速統整所以有公司發放情況 

->點我了解  TQuant Lab 資料

dividend.loc[dividend['股息分配型態'].eq('A0'), 'pay_type'] = 'yearly'

dividend.loc[np.where(dividend['股息分配型態'].str.contains('Q'))[0], 'pay_type'] = 'quarterly'

dividend.loc[np.where(dividend['股息分配型態'].str.contains('H'))[0], 'pay_type'] = 'half-yearly'

dividend['cash_chg'] = dividend.groupby(['symbol', 'pay_type'])['dividend'].transform(lambda x: x.pct_change())

div_chg = dividend.dropna(subset=['cash_chg'])

div_chg['chg'] = np.where(div_chg['cash_chg'] > 0, 'increase', np.where(div_chg['cash_chg'] < 0, 'decrease', 'unchange'))

bef_ret = div_chg.groupby('chg').apply(lambda x: pd.DataFrame(x['bef_ret'].tolist(), columns=[f"t{-i}" for i in range(21, -1, -1)])).rename_axis(['chg', 'index']).reset_index()

aft_ret = div_chg.groupby('chg').apply(lambda x: pd.DataFrame(x['aft_ret'].tolist(), columns=[f"t+{i}" for i in range(1, 22)])).rename_axis(['chg', 'index']).reset_index()

chg_ar = bef_ret.merge(aft_ret, on=['index', 'chg'], how='left').drop('index', axis=1)

chg_ar = chg_ar.groupby('chg').mean()

chg_ar = pd.concat([chg_ar, chg_ar.cumsum(axis=1)])

chg_ar['type'] = ['ar'] * 3 + ['car'] * 3

from plotly.subplots import make_subplots

import plotly.graph_objects as go

fig = make_subplots(rows=3, cols=1, row_heights=[60, 60, 60], 

                    subplot_titles=("股利減少", "股利增加", '股利不變'), vertical_spacing=0.08)

for index, (ind_namem, data) in enumerate(chg_ar.groupby(level=0)):

    data = data.set_index('type').T

    fig.add_trace(go.Bar(x=data.index, y=data['ar'], name='AAR', marker_color='blue', opacity=0.6, showlegend=(index == 0)), row=index+1, col=1, )

    fig.add_trace(go.Scatter(x=data.index, y=data['car'], mode='lines+markers', name='CAAR', line=dict(color='red'), showlegend=(index == 0)), row=index+1, col=1)

    fig.update_xaxes(ticktext=data.index, tickvals=data.index)

    fig.update_yaxes(title='報酬', row=index+1, col=1)

fig.update_layout(

    height=1200,

    title="股利宣告日前後21天AAR與CAAR-股利金額變動",

    legend_title=" ",

    showlegend=True

)

fig.add_vline(x=21, line_width=3, line_dash="dash", annotation_text='股利宣告事件日')

fig.show()

從下圖可看出,股利減少的樣本雖然在t-21日至t-12日期間還有正向的AAR,帶動CAAR最高來到0.437%,但隨後迅速下降直到t+1日宣告股利後AAR為-0.231%,將CAAR從略為正的0.055%拉低至-0.176%,並且在隨後一週內(t+1~t+6)降至最低點-0.557%。顯示出市場能提前兩週反應股利將減少的公司股價,使其股價在宣告日前產生連續的負向AAR,並在宣告日一週內大量賣出股票。

相對的,股利增加的樣本則從t-21日至t0日均有正向的AAR,使其CAAR在宣告日前就達到5%顯著水平的2.237%,並在隨後的t+1日中大幅拉高,AAR達到0.867%,CAAR攀升至3.104%,之後的t+2日至t+21日仍持續上升,最終達到5%顯著的4.448%。顯示市場已提前預估出公司營運良好,有充足現金流來維持既有股利,甚至可望進一步提高股利,因此反應至股票上帶動股價接連上升,最終使CAAR在宣告日後一個月內都持續緩慢增長。

最終對於股利不變的樣本而言,CAAR在宣告日前的t-21日至t0日僅為不顯著的0.269%,這一結果主要來自期間內 AAR 的持續波動,顯示市場對於這類公司是否能維持既有股利發放仍存有高度不確定性。而在t+1日對於公司能維持既有股利的發放給予正面看法,AAR達到0.170%,使CAAR上升至5%顯著的0.439%,並且在之後的一個月內AAR仍充滿波動的緩慢增長,最終達到5%顯著的0.840%。

CAAR%t-21~t0t-21~t+1t-21~t+21
股利減少0.055-0.176-0.445*
股利增加2.237*3.104*4.448*
股利不變0.2690.439*0.840*

註: ^代表介於5%至10%顯著水平;*代表達5%內顯著水平。

結論

總體來說,股票在股利宣告日次一日有顯著的平均異常報酬(0.368%),能賺取優於同期市場指數的報酬率。其中又以金融產業為最高(0.610%),電子產業次之(0.417%),傳統產業最低(0.302%)。但金融產業的異常報酬持續效果不佳,可能與市場對其掌握資訊較完整,導致股價不容易出現反應不足的現象;反觀電子與傳統產業中仍存在較多中小型公司,市場對其資訊掌握度可能較為不足,也導致股價在事件後續因反應不足而能持續緩步增長。若再根據公司前後兩期股利金額變化來分析,股利增長公司在股利宣告日次一日有更為顯著的平均異常報酬(0.867%);反之,股利減少公司則為負向的平均異常報酬(-0.231%);股利不變公司則並無顯著的平均異常報酬(0.170%)。


深入了解台灣企業股東會資訊與股利政策

TEJ股東會事項資料庫與股利政策資料庫,提供完整的台灣上市櫃公司歷年股東會資訊與股利政策資訊,幫助投資者掌握企業的長期經營策略與資本分配模式。透過分析股東會與股利政策資訊,您可以:

  • 整合股東會議攸關日期與會議資訊,企業重要決策不漏訊息
  • 完整收錄企業的歷史股利資訊,幫助評估未來股利政策
  • 依據最新的股東會決議,調整投資組合策略

立即造訪 TEJ資料銀行,了解更多TEJ提供的專業財金資料庫內容!

TQuant Lab 回測系統

TQuant Lab 提供專業的回測工具以及因子分析模組,透過TEJ專為量化投資者打造的TEJ API,整合回測工具,讓投資人可以用簡單的程式架構去實現心中的交易策略。

【TQuant Lab 回測系統】解決你的量化金融痛點

全方位提供交易回測所需工具

延伸閱讀

返回總覽頁
Procesing