Aug 31 2021

Use python and TEJ database to execute event study

Table of Contents

Seasoned equity offerings is a common financing option for listed firms, and the purpose of it includes** expansion of capital expenditures, retirement of debts and improvement of financial structure**. However, previous literature has implied that this borrowing behavior tends to send negative signals to the market, leading to stock prices plummeting at the date of announcement.

Event study is a statistical method that analyzes the impact of important corporate events(M&A, SEO, stock buyback…) or economic shocks(regulation change, policy, macroeconomic conditions…) on firms’ value. Basically, event study consists of the following steps: event date confirmation, definition of estimation window and event window, calculation of abnormal return and statistical test. Therefore, this week, we will verify whether this negative announcement effect also happens in the Taiwan stock market by adopting the TEJ API database and event study approach !

We use Windows OS and Jupyter Notebook in this article

import tejapi

import pandas as pd

import numpy as np

import matplotlib.pyplot as plt

from sklearn.linear_model import LinearRegression

from scipy import stats

tejapi.ApiConfig.api_key = 'Your Key'

tejapi.ApiConfig.ignoretz = True

*Note: sklearn.linear_model is used for regression；scipy is for statistical test*

- Obtain common stocks issuance events
- Execute event study

- Security Characteristics Document: the code of the database is ‘TWN/ANPRCSTD’, covering the code of listed firms
- Formation of Capital: the code of the database is ‘TWN/ASTK1’, containing SEO and capital reduction information
- Unadjusted Stock Price: the code of the database is ‘TWN/APRCD’, including the stock price of listed firms
- Fama&French Factors: the code of the database is ‘TWN/AFACTO1D’, which have Fama&French factors portfolio return.

`tse_stocks`

security = tejapi.get('TWN/ANPRCSTD',

mkt = 'TSE',

stypenm = '普通股',

opts = {'columns':['coid','mdate','stypenm','mkt']},

paginate = True,

chinese_column_name = True)

tse_stocks = security['證券碼'].tolist()

events = tejapi.get('TWN/ASTK1',

x_mt_date = {'gte':'2019-01-01', 'lte':'2021-08-01'},

opts = {'columns':['coid','cash','x_mt_date']},

paginate = True,

chinese_column_name = True)

This article will treat **the date of resolution of shareholders’ meeting** as event day. The event day falls between January 2019 ~ August 2020

cash = events[events['現金增資(仟股)']>0].reset_index(drop=True)

We only need **issuance of common stock for cash**, instead of earnings or capital surplus transferred to common stock

tse_cash = cash[cash['公司'].isin(tse_stocks)].reset_index(drop=True)

Combined with TSE-listed firms’ code `tse_stocks`

and `isin()`

,we finally obtain issuance of common stock for cash event only for TSE-listed firms** **`tse_cash`

Estimation window(T0~T1): -260~-10 periods is set. The actual returns in this period will be regressed on Fama & French factor-based portfolio return, which yields the coefficient estimates that will be used to calculate expected return within event window

Event day (0): the date of resolution of shareholders’ meeting, since it’s the earliest signal for SEO sent to market

Event window(T2~T3): -5 ~ +5 periods.Thedifference between actual return and expected return predicted by Fama & French model is so-called abnormal return

Because we include 140 events of common stocks issuance, each loop is used to cope with each event, and the output will be stored in variable `final`

. We also exclude some events that lack data or belong to initial public offerings. In the following section, we select some important parts of codes within the loop to interpret and take the first event** ( stock = ‘1314’, date = ‘2019–05–24’ ) **as an example to show what’s going on in each loop. Other parts of codes can be viewed in source code

- Obtain adequate returns

return_set = tejapi.get('TWN/APRCD',

coid = stock,

mdate = {'gte': date + pd.Timedelta(days =

-450), 'lte': date + pd.Timedelta(days = 25)},

opts = {'columns' : ['coid', 'mdate','roi']},

paginate = True,

chinese_column_name = True)

Each event has a unique combination of firm code `stock `

and event date `date`

, which are used to obtain return around event day. Here we choose the returns of 450 days before the event date and 25 days after the event date to make sure we’ll have complete return in estimation window(251 **trading days**) and event window(11 **trading days**)

- Split off the return of estimation window and event window respectively

event_index = return_set[return_set['年月日'] == date].index.values.astype(int)[0]

First of all, the event date’s index should be **used as a basis** to split two windows

estimate_window = return_set[event_index - 260 - 1: event_index - 10].reset_index(drop=True)

event_window = return_set[event_index - 5: event_index + 5 + 1].reset_index(drop=True)

- Find the corresponding Fama & French factor-based return

#估計起始日期(-260)

estimate_start = estimate_window.loc[0, '年月日']

#事件結束日期(+5)

event_end = event_window.loc[5*2, '年月日']

For starters, we need to find the beginning date of the estimation window and the last date of the event window. Then based on the two dates, we directly obtain the daily portfolio return of **market risk premium, size premium, book-to-market premium, profitability and investment factors**. `Y9999 `

means the above returns are based on TSE-listed firms and belong to simple return

fama_french = tejapi.get('TWN/AFACTO1D',

coid = 'Y9999',

mdate = {'gte':estimate_start, 'lte': event_end},

opts = {'columns': ['coid',

'mdate','mrp','smbn','bp','op','inv']},

paginate = True,

chinese_column_name = True)

- Estimate the coefficient of factors

ols_data = estimate_window.merge(fama_french, on = '年月日')

x = ols_data.loc[:,['市場風險溢酬', '規模溢酬(5因子)','淨值市價比溢酬','盈利能力因子','投資因子']].values

y = ols_data['報酬率％'].values.reshape(-1,1)

model = LinearRegression()

model.fit(x,y)

After **combining estimation return and Fama-French portfolio return**, we convert them into two-dimensional array variables which are required in a linear model. Finally, put dependent variable and independent variable in the model

- Calculate abnormal return

predict_data = event_window.merge(fama_french, on = '年月日')

event_x = predict_data.loc[:,['市場風險溢酬','規模溢酬(5因子)','淨值市價比溢酬','盈利能力因子','投資因子']].values

Here we combine the **event window return with Fama & French portfolio return**, and store the factor-based portfolio returns to be independent variables to get the expected return by putting them in fitted model `model.predict()`

. The next step is to calculate the abnormal returns, cumulative returns and add one more columns to indicate the relative day

event_window['預期報酬率'] = model.predict(event_x)

event_window['異常報酬率'] = event_window['報酬率％'] - event_window['預期報酬率']

event_window['累計異常報酬率'] = event_window['異常報酬率'].cumsum()

event_window['相對天數'] = [i for i in range(-5, 5 + 1)]

Each loop will keep repeating the above steps and the output will be added in `final `

table, as the following shown

- Calculate the average abnormal returns and cumulative abnormal returns

sorted_data = final.groupby(by = '相對天數').mean().reset_index()

- Visualization

plt.plot(sorted_data['相對天數'], sorted_data['異常報酬率'], label = 'Average Abnormal Retrun')

plt.plot(sorted_data['相對天數'], sorted_data['累計異常報酬率'], label = 'Cumulative Abnormal Return')

plt.xlabel('Event Day')

plt.title('Event Study')

plt.xticks(np.arange(-5, 5+1 , 1))

plt.legend()

plt.show()

As you can see, there’s** apparently negative abnormal return** one or two days after the event day. However, the abnormal return reverses back around zero afterwards, which imply the market might already digest the information

- Statistical test

ttest = pd.DataFrame()

for day in range(-5,5+1):

sample = final[final['相對天數'] == day]['異常報酬率'].values

t, p_value = stats.ttest_1samp(sample, 0)

if p_value <= 0.01:

significance = '***'

elif 0.01 < p_value <= 0.05:

significance = '**'

elif 0.05 < p_value <= 0.1:

significance = '*'

else:

significance = ''

ttest = ttest.append(pd.DataFrame(np.array([day,t,p_value, significance]).reshape((1,4)), columns = ['相對天數','T檢定值', 'P-value','顯著水準'],)).reset_index(drop=True)ttest = ttest.set_index('相對天數')

We use `stats.ttest_lsamp()`

to carry out T-tests to examine whether the daily abnormal return is **significantly different from zero**. We also add one more significance star column based on p-value

Therefore, we can prove that the announcement of SEO **does significantly impact firms’ value**. The reason for not impacting the return at the event date may result from firms usually declaring important issues after the stock market closes

After today’s introduction, we believe readers have more understanding of event study! Actually still lots of assumptions we proposed in this article can be adjusted based on previous literature or event characteristics, such as estimation and event window or the methods to calculate the abnormal return. To execute event study more efficiently, we highly recommend TEJ API databases as we use in this article given its quality and variety without wasting time on data crawling and cleaning. If readers are interested in observing other events’ impact on firms’ value, welcome to the TEJ E Shop to purchase the data that satisfy your need!

Category