There are two macros on the List of WRDS Research Macros: EVTSTUDY and BETA, which may be often used.
I like the first one, written by Denys Glushkov. Denys’ codes are always elegant. I don’t like the second one because I believe it contains not minor mistakes and does a lot of unwanted calculation.
Since event study and beta calculation are just two sides of one thing, I make the following macro to output both event study results (e.g., CAR) and beta. My macro heavily borrows from Denys’ codes but differs in the following ways:
- I add beta to the final output. This is the main difference.
- Deny uses CRSP.DSIY to generate the trading calendar and market returns. I cannot see why he uses this dataset. The trouble is not every institution has the subscription to this dataset. Thus, I use a more accessible dataset CRSP.DSI instead (Thank Michael Shen for bringing this to my attention).
- I improve efficiency in generating related trading dates at the security-event level.
- I correct several errors in Denys’ macro: (a) his macro does not sort the input dataset by
permno
andevent date
, leading to a fatal error later on; and (b) I correct a few dataset/variable references. - Deny’s macro switches off warning or error messages, which is inconvenient for debugging. I change this setting.
All changes are commented with /* CHANGE HERE */. I compare the results (CAR and beta) from using my macro and those from using a commercial package, EVENTUS (with the help of my friend who has the license to EVENTUS). The accuracy of my macro is assured (Note: EVENTUS does not take delisting returns by default).
Update: WRDS rolled out the event study web inquiry (so-called Event Study by WRDS). I recently checked the accuracy of that product. To my surprise, the accuracy is unsatisfactory, if not terrible.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 |
%MACRO EVTSTUDY_NEW (INSET=, OUTSET=, OUTSTATS=, ID=, EVTDATE=, ESTPER=, START=, END=, GAP=, GROUP=, MODEL=); /* Summary: Perform event study and calculate beta */ /* Parameters: */ /* - ID : Name of security identifier in INSET: PERMNO or CUSIP */ /* CUSIP should be at least 8 (eight) characters */ /* - INSET : Input dataset containing security IDs and event dates */ /* - OUTSET : Name of the output dataset to store mean CAR and t-stats */ /* - OUTSTATS:Name of the output dataset to store test statistics (Patell Z, etc) */ /* - EVTDATE: Name of the event date variable in INSET dataset */ /* - ESTPER : Length of the estimation period in trading days over which */ /* the risk model is run, e.g., 110; */ /* - START : Beginning of the event window (relative to the event date, eg. -2) */ /* - END : End of the event window (relative to the event date, e.g., +1) */ /* - GAP : Length of pre-event window, i.e., number of trading days between */ /* the end of estimation period and the beginning of the event window */ /* - GROUP: Defines an subgroup (can be more than 2) */ /* - MODEL: Risk model to be used for risk-adjustment */ /* madj - Market-Adjusted Model (assumes stock beta=1) */ /* m - Standard Market Model (CRSP value-weighted index as the market)*/ /* ff - Fama-French three factor model */ /* ffm - Carhart model that includes FF factors plus momentum */ %local evtwin factors abret newvars; %local oldoptions errors; %let oldoptions=%sysfunc(getoption(mprint)) %sysfunc(getoption(notes)) %sysfunc(getoption(source)); %let errors=%sysfunc(getoption(errors)); options notes mprint source errors=0; /*display codes debugging. CHANGE HERE*/ %let evtwin=%eval(&end-&start+1); *length of event window in trading days; /*depending on the model, define the model for abnormal returns*/ %if %lowcase(&model)=madj %then %do; %let factors=vwretd; %let abret=ret-vwretd; %let newvars=(intercept=alpha); %end;%else %if %lowcase(&model)=m %then %do; %let factors=vwretd; %let abret=ret-alpha-beta*vwretd; %let newvars=(intercept=alpha vwretd=beta); %end;%else %if %lowcase(&model)=ff %then %do; %let factors=vwretd smb hml; %let abret=ret-alpha-beta*vwretd-sminb*smb-hminl*hml; %let newvars=(intercept=alpha vwretd=beta smb=sminb hml=hminl); %end;%else %if %lowcase(&model)=ffm %then %do; %let factors=vwretd smb hml mom; %let abret=ret-alpha-beta*vwretd-sminb*smb-hminl*hml-wminl*mom; %let newvars=(intercept=alpha vwretd=beta smb=sminb hml=hminl mom=wminl); %end; %put; %put ### CREATING TRADING DAY CALENDAR...; data _caldates; merge crsp.dsi (keep=date rename=(date=estper_beg)) crsp.dsi (keep=date firstobs=%eval(&estper) rename=(date=estper_end)) crsp.dsi (keep=date firstobs=%eval(&estper+&gap+1) rename=(date=evtwin_beg)) crsp.dsi (keep=date firstobs=%eval(&estper+&gap-&start+1) rename=(date=evtdate)) /*change &evtdate to evtdate. CHANGE HERE*/ crsp.dsi (keep=date firstobs=%eval(&estper+&gap+&evtwin) rename=(date=evtwin_end)); format estper_beg estper_end evtwin_beg evtdate evtwin_end date9.; /*change &evtdate to evtdate. CHANGE HERE*/ if nmiss(estper_beg, estper_end, evtwin_beg, evtdate, evtwin_end)=0; /*change &evtdate to evtdate. CHANGE HERE*/ time+1; run; %put ### DONE!; /*If primary identifier is Cusip, then link in permno*/ %if %lowcase(&id)=cusip %then %do; proc sql; create view _link as select permno, ncusip, min(namedt) as fdate format=date9., max(nameendt) as ldate format=date9. from crsp.dsenames group by permno, ncusip; create table _temp as select distinct b.permno, a.* from &inset a left join _link b on a.cusip=b.ncusip and b.fdate<=a.&evtdate<=b.ldate order by b.permno, a.&evtdate; /*order by both permno and &evtdate. CHANGE HERE*/ quit;%end; %else %do; /*pre-sort the input dataset in case it is not sorted yet*/ proc sort data=&inset out=_temp; by permno &evtdate; /*order by both permno and &evtdate. CHANGE HERE*/ run; %end; /*If event date is a non-trading day, select the closest */ /*trading day that follows the event day */ /*Merge in relevant dates from the trading calendar */ /*CHANGE HERE to improve efficiency and correct errors proc sql; create table _temp (drop=&evtdate) as select a.*, a.&evtdate as _edate format date9., b.* from _temp a left join _caldates (drop=time) b on b.&evtdate-a.&evtdate>=0 group by a.&evtdate having (b.&evtdate-a.&evtdate)=min(b.&evtdate-a.&evtdate); quit;*/ proc sql; create table _temp1 as select a.&evtdate, b.estper_beg, b.estper_end, b.evtwin_beg, b.evtwin_end, b.evtdate from (select distinct &evtdate from _temp) a left join _caldates b on b.evtdate-a.&evtdate>=0 group by a.&evtdate having (b.evtdate-a.&evtdate)=min(b.evtdate-a.&evtdate); create table _temp2 (drop=&evtdate) /*use _temp2 to surpress warnings and retain _temp for later use. CHANGE HERE*/ as select a.*, a.&evtdate as _edate format date9., b.estper_beg, b.estper_end, b.evtwin_beg, b.evtwin_end, b.evtdate from _temp a left join _temp1 b on b.&evtdate=a.&evtdate; quit; %put ; %put ### PREPARING BENCHMARK FACTORS... ; proc sql;create table _factors as select a.date, a.vwretd, b.smb, b.hml, b.umd as mom from crsp.dsi (keep=date vwretd) a left join ff.factors_daily b on a.date=b.date; quit; %put ### DONE! ; %put; %put ### RETRIEVING RETURNS DATA FROM CRSP...; proc sql; create table _evtrets_temp as select a.permno, a.date format date9., a.ret as ret1, b.* from crsp.dsf a, _temp2 b /*change _temp reference. CHANGE HERE*/ where a.permno=b.permno and b.estper_beg<=a.date<=b.evtwin_end; quit; %put ### DONE!; %put; %put ### MERGING IN BECHMARK FACTORS...; proc sql; create table _evtrets1 as select a.*, b.*, (c.time-d.time) as evttime from _evtrets_temp a left join _factors (keep=date &factors) b on a.date=b.date left join _caldates c on a.date=c.evtdate /*change &evtdate to evtdate. CHANGE HERE*/ left join _caldates d on a.evtdate=d.evtdate; /*change condition. CHANGE HERE*/ create table _evtrets (where=(not missing(vwretd))) as select a.*, a.ret1 label='Ret unadjusted for delisting', (1+a.ret1)*sum(1,b.dlret)-1-a.vwretd as exret label='Market-adjusted total ret', (1+a.ret1)*sum(1,b.dlret)-1 as ret "Ret adjusted for delisting" from _evtrets1 a left join crsp.dsedelist (where=(missing(dlret)=0)) b on a.permno=b.permno and a.date=b.dlstdt order by a.permno, a._edate, a.date, a.evttime; quit; %put ### DONE!; %put; %put ### ESTIMATING FACTOR EXPOSURES OVER THE ESTIMATION PERIOD...; %if %lowcase(&model) ne madj %then %do; /*estimate risk factor exposures during the estimation period*/ proc reg data=_evtrets edf outest=_params (rename=&newvars keep=permno _edate intercept &factors _rmse_ _p_ _edf_) noprint; where estper_beg<=date<=estper_end; by permno _edate; model ret=&factors; quit;%end; %else %do; proc reg data=_evtrets edf outest=_params (rename=&newvars keep=permno _edate intercept _rmse_ _p_ _edf_) noprint; where estper_beg<=date<=estper_end; by permno _edate; model ret=; quit;%end; %put ### DONE!; %put; %put ### CALCULATING ONE-DAY ABNORMAL RETURN IN THE EVENT WINDOW...; data _abrets/view=_abrets; merge _evtrets (where=(evtwin_beg<=date<=evtwin_end) in=a) _params; by permno _edate; abret=&abret; logret=log(1+ret); var_estp=_rmse_*_rmse_; nobs=_p_+_edf_; label var_estp='Estimation Period Variance' abret= 'One-day Abnormal Return (AR)' ret= 'Raw Return' _edate= 'Event Date' evttime= "Trading day within (&start,&end) event window"; drop _p_ _edf_ estper_beg estper_end; if a; run; %put ### DONE!; %put; %put ### CALCULATING CARS AND VARIOUS STATISTICS...; proc means data=_abrets noprint; by permno _edate; id &group var_estp; output out=_car sum(logret)=cret sum(abret)=car n(abret)=nrets; /*calculate Standardized Cumulative Abnormal Returns*/ data _car; set _car; poscar=car>0; scar=car/(&evtwin*var_estp)**0.5; cret=exp(cret)-1; label poscar='Positive Abnormal Return Dummy' scar= 'Standardized Cumulative Abnormal Return (SCAR)' car= 'Cumulative Abnormal Return (CAR)' cret= 'Cumulative Raw Return' nrets= 'Number of non-missing abnormal returns within event window'; /*compute stats across all events (i.e., permno-event date combinations*/ proc means data=_car noprint; var cret car scar poscar; class &group; output out=_test mean= n= t=/autoname; /*calculate different stats for assessing */ /*statistical signficance of abnormal returns*/ data &outstats; set _test; tpatell=scar_mean*((scar_n)**0.5); tsign=(poscar_mean-0.5)/sqrt(0.25/poscar_n); format cret_mean car_mean percent7.5; label tpatell= "Patell's t-stat" car_mean= 'Mean Cumulative Abnormal Return' cret_mean= 'Mean Cumulative Raw Return' scar_mean= 'Mean Cumulative Standardized Abnormal Return' car_t= 'Cross-sectional t-stat' scar_t= "Boehmer's et al. (1991) t-stat" car_n= 'Number of events in the portfolio' poscar_mean= 'Percent of positive abnormal returns' tsign= 'Sign-test statistic'; drop cret_N scar_N poscar_N cret_t poscar_t; run; %put ### DONE!; proc print label u; title1 "Output for dataset &inset for a (&start,&end) event window using &model model"; id &group; var cret_mean car_mean scar_mean poscar_mean car_n tsign tpatell car_t scar_t; %if "&group" ne "" %then %do; title2 "Test for Equality of CARs among groups defined by &group"; /*To find out the results of the hypothesis test for comparing groups */ /*find the row of output labeled 'Model' and look at the column labeled */ /*F-value for the Fisher statistic and Pr>F for the associated p-value */ /*HOVTEST tests for whether variances of two groups are the same */ proc glm data=_car; class &group; model scar=&group; means &group /hovtest; proc npar1way data=_car wilcoxon; var scar; class &group; %end; run; /*create the final output dataset*/ %if %lowcase(&model) ne madj %then %do; %let _beta=_params(keep=permno _edate beta); %let _beta_label=beta='Beta'; %end; %else %do; %let _beta=; %let _beta_label=; %end; /*add IF statement. CHANGE HERE*/ data &outset; merge _temp (in=a rename=(&evtdate=_edate)) /*change &inset reference. CHANGE HERE*/ _abrets(keep=permno _edate date evttime ret abret var_estp) _car (keep=permno _edate cret car scar nrets) &_beta; /*add &_beta. CHANGE HERE*/ by permno _edate; rename _edate=&evtdate; /*use original variable name. CHANGE HERE*/ label _edate='Event date' date='Trading date in event window' &_beta_label; /*add &_beta_label. CHANGE HERE*/ format _edate date9. date date9.; if a; run; /*house cleaning*/ proc sql; drop table _caldates, _car, _factors, _test, _params, _temp, _evtrets1, _evtrets_temp, _temp1, _temp2; drop view _abrets; quit; options errors=&errors &oldoptions; %put ;%put ### OUTPUT IN THE DATASET &outset; %put ;%put ### TEST STATISTICS IN THE DATASET &outstats; %MEND; |
Very good!