{"id":418,"date":"2015-09-16T21:42:59","date_gmt":"2015-09-17T01:42:59","guid":{"rendered":"http:\/\/csclub.uwaterloo.ca\/~k55chen\/?p=418"},"modified":"2025-05-28T16:56:55","modified_gmt":"2025-05-28T20:56:55","slug":"sas-macro-for-event-study-and-beta","status":"publish","type":"post","link":"https:\/\/www.kaichen.work\/?p=418","title":{"rendered":"SAS macro for event study and beta"},"content":{"rendered":"<p>There are two macros on the <a href=\"https:\/\/wrds-web.wharton.upenn.edu\/wrds\/research\/macros\/\" target=\"_blank\" rel=\"noopener noreferrer\">List of WRDS Research Macros<\/a>: EVTSTUDY and BETA, both of which may be commonly used.<\/p>\n<p>I like the first one, authored by Denys Glushkov. Denys&#8217; code is always elegant. I don&#8217;t like the second macro because I believe it contains more than minor mistakes and performs many unnecessary calculations.<\/p>\n<p>Since event study and beta calculation are essentially two sides of the same coin, I developed the following macro to output both event study results (e.g., CAR) and beta estimates. My macro heavily borrows from Denys&#8217; code but differs in the following ways:<\/p>\n<ol>\n<li>I add\u00a0beta to the final output. This is the main difference.<\/li>\n<li>Denys&#8217; macro overwrites original event dates if they are not trading dates, which may result in unintended data loss. I correct this issue.<\/li>\n<li>Deny uses CRSP.DSIY to generate the trading calendar and market returns, but not all institutions have subscription to it. Therefore, I use the more accessible CRSP.DSI instead (thanks to Michael Shen for bringing this to my attention).<\/li>\n<li>I enhance efficiency by de-duplicating event dates when generating estimation and events windows.<\/li>\n<li>Deny&#8217;s macro suppresses warnings or error messages, making debugging difficult. I change this setting.<\/li>\n<\/ol>\n<p>All changes are\u00a0commented 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).<\/p>\n<p><strong>Update:<\/strong> WRDS rolled out the\u00a0event study web inquiry (so-called Event Study by WRDS). I recently checked the accuracy of that product. To my surprise, the accuracy\u00a0is unsatisfactory, if not terrible.<\/p>\n<pre class=\"lang:sas decode:true\"> %MACRO EVTSTUDY_NEW (INSET=, OUTSET=, OUTSTATS=, ID=, EVTDATE=, \r\n                      ESTPER=, START=, END=, GAP=, GROUP=, MODEL=);\r\n\r\n\/* Summary: Perform event study and calculate beta                                   *\/\r\n\/* Parameters:                                                                       *\/\r\n\/*    - ID     : Name of security identifier in INSET: PERMNO or CUSIP               *\/\r\n\/*               CUSIP should be at least 8 (eight) characters                       *\/\r\n\/*    - INSET  : Input dataset containing security IDs and event dates               *\/\r\n\/*    - OUTSET : Name of the output dataset to store mean CAR and t-stats            *\/\r\n\/*    - OUTSTATS:Name of the output dataset to store test statistics (Patell Z, etc) *\/\r\n\/*    - EVTDATE: Name of the event date variable in INSET dataset                    *\/\r\n\/*    - ESTPER : Length of the estimation period in trading days over which          *\/\r\n\/*               the risk model is run, e.g., 110;                                   *\/\r\n\/*    - START  : Beginning of the event window (relative to the event date, eg. -2)  *\/\r\n\/*    - END    : End of the event window (relative to the event date, e.g., +1)      *\/\r\n\/*    - GAP    : Length of pre-event window, i.e., number of trading days between    *\/\r\n\/*               the end of estimation period and the beginning of the event window  *\/\r\n\/*    - GROUP: Defines an subgroup (can be more than 2)                              *\/\r\n\/*    - MODEL: Risk model to be used for risk-adjustment                             *\/\r\n\/*             madj - Market-Adjusted Model (assumes stock beta=1)                   *\/\r\n\/*             m    - Standard Market Model (CRSP value-weighted index as the market)*\/\r\n\/*             ff   - Fama-French three factor model                                 *\/\r\n\/*             ffm  - Carhart model that includes FF factors plus momentum           *\/\r\n\r\n  %local evtwin factors abret newvars;\r\n  %local oldoptions errors;\r\n  %let oldoptions=%sysfunc(getoption(mprint)) %sysfunc(getoption(notes))\r\n                   %sysfunc(getoption(source));\r\n  %let errors=%sysfunc(getoption(errors));\r\n  options notes mprint source errors=0; \/*display codes debugging. CHANGE HERE*\/\r\n  \r\n  %let evtwin=%eval(&amp;end-&amp;start+1); *length of event window in trading days;\r\n   \r\n  \/*depending on the model, define the model for abnormal returns*\/\r\n  %if %lowcase(&amp;model)=madj %then %do; %let factors=vwretd;\r\n              %let abret=ret-vwretd;\r\n              %let newvars=(intercept=alpha);\r\n              %end;%else\r\n  %if %lowcase(&amp;model)=m %then  %do; %let factors=vwretd;\r\n              %let abret=ret-alpha-beta*vwretd;\r\n              %let newvars=(intercept=alpha vwretd=beta);\r\n              %end;%else\r\n  %if %lowcase(&amp;model)=ff %then %do;\r\n              %let factors=vwretd smb hml;\r\n              %let abret=ret-alpha-beta*vwretd-sminb*smb-hminl*hml;\r\n              %let newvars=(intercept=alpha vwretd=beta smb=sminb hml=hminl);\r\n              %end;%else\r\n  %if %lowcase(&amp;model)=ffm %then %do;\r\n              %let factors=vwretd smb hml mom;\r\n              %let abret=ret-alpha-beta*vwretd-sminb*smb-hminl*hml-wminl*mom;\r\n              %let newvars=(intercept=alpha vwretd=beta smb=sminb hml=hminl mom=wminl);\r\n              %end;\r\n \r\n  %put; %put ### CREATING TRADING DAY CALENDAR...;\r\n  data _caldates;\r\n   merge crsp.dsi (keep=date rename=(date=estper_beg))\r\n   crsp.dsi (keep=date firstobs=%eval(&amp;estper) rename=(date=estper_end))\r\n   crsp.dsi (keep=date firstobs=%eval(&amp;estper+&amp;gap+1) rename=(date=evtwin_beg))\r\n   crsp.dsi (keep=date firstobs=%eval(&amp;estper+&amp;gap-&amp;start+1) rename=(date=evtdate)) \/*change &amp;evtdate to evtdate. CHANGE HERE*\/\r\n   crsp.dsi (keep=date firstobs=%eval(&amp;estper+&amp;gap+&amp;evtwin) rename=(date=evtwin_end));\r\n   format estper_beg estper_end evtwin_beg evtdate evtwin_end date9.; \/*change &amp;evtdate to evtdate. CHANGE HERE*\/\r\n   if nmiss(estper_beg, estper_end, evtwin_beg, evtdate, evtwin_end)=0; \/*change &amp;evtdate to evtdate. CHANGE HERE*\/\r\n   time+1;\r\n  run;\r\n %put ### DONE!;\r\n  \r\n  \/*If primary identifier is Cusip, then link in permno*\/\r\n  %if %lowcase(&amp;id)=cusip %then %do;\r\n  proc sql;\r\n   create view  _link\r\n   as select permno, ncusip,\r\n   min(namedt) as fdate format=date9., max(nameendt) as ldate format=date9.\r\n   from crsp.dsenames\r\n   group by permno, ncusip;\r\n     \r\n   create table _temp\r\n   as select distinct b.permno, a.*\r\n   from &amp;inset a left join _link b\r\n   on a.cusip=b.ncusip and b.fdate&lt;=a.&amp;evtdate&lt;=b.ldate\r\n   order by b.permno, a.&amp;evtdate; \/*order by both permno and &amp;evtdate. CHANGE HERE*\/\r\n  quit;%end;\r\n  %else %do;\r\n  \/*pre-sort the input dataset in case it is not sorted yet*\/\r\n  proc sort data=&amp;inset out=_temp;\r\n   by permno &amp;evtdate; \/*order by both permno and &amp;evtdate. CHANGE HERE*\/\r\n  run;\r\n  %end;\r\n \r\n  \/*If event date is a non-trading day, select the closest *\/\r\n  \/*trading day that follows the event day                 *\/\r\n  \/*Merge in relevant dates from the trading calendar      *\/\r\n\r\n  \/*CHANGE HERE to improve efficiency and correct errors \r\n  proc sql;\r\n   create table _temp (drop=&amp;evtdate)\r\n   as select a.*, a.&amp;evtdate as _edate format date9., b.*\r\n   from _temp a left join _caldates (drop=time) b\r\n   on b.&amp;evtdate-a.&amp;evtdate&gt;=0\r\n   group by a.&amp;evtdate\r\n   having (b.&amp;evtdate-a.&amp;evtdate)=min(b.&amp;evtdate-a.&amp;evtdate);\r\n  quit;*\/\r\n\r\n  proc sql;\r\n   create table _temp1\r\n   as select a.&amp;evtdate, b.estper_beg, b.estper_end, b.evtwin_beg, b.evtwin_end, b.evtdate\r\n   from (select distinct &amp;evtdate from _temp) a left join _caldates b\r\n   on b.evtdate-a.&amp;evtdate&gt;=0\r\n   group by a.&amp;evtdate\r\n   having (b.evtdate-a.&amp;evtdate)=min(b.evtdate-a.&amp;evtdate);\r\n\r\n   create table _temp2 (drop=&amp;evtdate)  \/*use _temp2 to surpress warnings and retain _temp for later use. CHANGE HERE*\/\r\n   as select a.*, a.&amp;evtdate as _edate format date9., b.estper_beg, b.estper_end, b.evtwin_beg, b.evtwin_end, b.evtdate\r\n   from _temp a left join _temp1 b\r\n   on b.&amp;evtdate=a.&amp;evtdate;\r\n  quit;\r\n  \r\n  %put ; %put ### PREPARING BENCHMARK FACTORS... ;\r\n  proc sql;create table _factors\r\n   as select a.date, a.vwretd, b.smb, b.hml, b.umd as mom\r\n   from crsp.dsi (keep=date vwretd) a left join ff.factors_daily b\r\n   on a.date=b.date;\r\n  quit;\r\n  %put ### DONE! ;\r\n  \r\n  %put; %put ### RETRIEVING RETURNS DATA FROM CRSP...;\r\n  proc sql;\r\n   create table _evtrets_temp\r\n   as select a.permno, a.date format date9., a.ret as ret1, b.*\r\n   from crsp.dsf a, _temp2 b \/*change _temp reference. CHANGE HERE*\/\r\n   where a.permno=b.permno and b.estper_beg&lt;=a.date&lt;=b.evtwin_end;\r\n  quit;\r\n  %put ### DONE!;\r\n  \r\n  %put; %put ### MERGING IN BECHMARK FACTORS...;\r\n  proc sql;\r\n   create table _evtrets1\r\n     as select a.*, b.*, (c.time-d.time) as evttime\r\n   from _evtrets_temp a\r\n   left join _factors (keep=date &amp;factors) b\r\n        on a.date=b.date\r\n   left join _caldates c\r\n        on a.date=c.evtdate \/*change &amp;evtdate to evtdate. CHANGE HERE*\/\r\n   left join _caldates d\r\n        on a.evtdate=d.evtdate; \/*change condition. CHANGE HERE*\/\r\n \r\n   create table _evtrets (where=(not missing(vwretd)))\r\n     as select a.*, a.ret1 label='Ret unadjusted for delisting',\r\n     (1+a.ret1)*sum(1,b.dlret)-1-a.vwretd as exret label='Market-adjusted total ret',\r\n     (1+a.ret1)*sum(1,b.dlret)-1 as ret \"Ret adjusted for delisting\"\r\n   from _evtrets1 a left join crsp.dsedelist (where=(missing(dlret)=0)) b\r\n   on a.permno=b.permno and a.date=b.dlstdt\r\n   order by a.permno, a._edate, a.date, a.evttime;\r\n quit;\r\n %put ### DONE!;\r\n\r\n %put; %put ### ESTIMATING FACTOR EXPOSURES OVER THE ESTIMATION PERIOD...;\r\n %if %lowcase(&amp;model) ne madj %then %do;\r\n  \/*estimate risk factor exposures during the estimation period*\/\r\n  proc reg data=_evtrets edf outest=_params (rename=&amp;newvars\r\n    keep=permno _edate intercept &amp;factors _rmse_  _p_ _edf_) noprint;\r\n    where estper_beg&lt;=date&lt;=estper_end;\r\n    by permno _edate;\r\n    model ret=&amp;factors;\r\n  quit;%end;\r\n  %else %do;\r\n   proc reg data=_evtrets edf outest=_params (rename=&amp;newvars\r\n    keep=permno _edate intercept _rmse_  _p_ _edf_) noprint;\r\n    where estper_beg&lt;=date&lt;=estper_end;\r\n    by permno _edate;\r\n    model ret=;\r\n  quit;%end;\r\n %put ### DONE!;\r\n\r\n %put; %put ### CALCULATING ONE-DAY ABNORMAL RETURN IN THE EVENT WINDOW...;\r\n  data _abrets\/view=_abrets;\r\n    merge _evtrets (where=(evtwin_beg&lt;=date&lt;=evtwin_end) in=a) _params;\r\n     by permno _edate;\r\n     abret=&amp;abret;\r\n     logret=log(1+ret);\r\n     var_estp=_rmse_*_rmse_;\r\n     nobs=_p_+_edf_;\r\n     label var_estp='Estimation Period Variance'\r\n           abret=   'One-day Abnormal Return (AR)'\r\n           ret=     'Raw Return'\r\n           _edate=  'Event Date'\r\n           evttime= \"Trading day within (&amp;start,&amp;end) event window\";\r\n\t drop _p_ _edf_ estper_beg estper_end;\r\n     if a;\r\n  run;\r\n %put ### DONE!;\r\n  \r\n %put; %put ### CALCULATING CARS AND VARIOUS STATISTICS...;\r\n  proc means data=_abrets noprint;\r\n   by permno _edate;\r\n   id &amp;group var_estp;\r\n  output out=_car sum(logret)=cret sum(abret)=car n(abret)=nrets;\r\n  \r\n  \/*calculate Standardized Cumulative Abnormal Returns*\/\r\n  data _car; set _car;\r\n    poscar=car&gt;0;\r\n    scar=car\/(&amp;evtwin*var_estp)**0.5;\r\n    cret=exp(cret)-1;\r\n    label poscar='Positive Abnormal Return Dummy'\r\n          scar=  'Standardized Cumulative Abnormal Return (SCAR)'\r\n          car=   'Cumulative Abnormal Return (CAR)'\r\n          cret=  'Cumulative Raw Return'\r\n         nrets=  'Number of non-missing abnormal returns within event window';\r\n  \r\n  \/*compute stats across all events (i.e., permno-event date combinations*\/\r\n  proc means data=_car noprint;\r\n    var cret car scar poscar;\r\n    class &amp;group;\r\n    output out=_test\r\n  mean= n= t=\/autoname;\r\n  \r\n  \/*calculate different stats for assessing    *\/\r\n  \/*statistical signficance of abnormal returns*\/\r\n  data &amp;outstats; set _test;\r\n    tpatell=scar_mean*((scar_n)**0.5);\r\n    tsign=(poscar_mean-0.5)\/sqrt(0.25\/poscar_n);\r\n    format cret_mean car_mean percent7.5;\r\n    label tpatell=     \"Patell's t-stat\"\r\n     car_mean=    'Mean Cumulative Abnormal Return'\r\n     cret_mean=   'Mean Cumulative Raw Return'\r\n     scar_mean=   'Mean Cumulative Standardized Abnormal Return'\r\n     car_t=       'Cross-sectional t-stat'\r\n     scar_t=      \"Boehmer's et al. (1991) t-stat\"\r\n     car_n=       'Number of events in the portfolio'\r\n     poscar_mean= 'Percent of positive abnormal returns'                                         \r\n     tsign=       'Sign-test statistic';\r\n    drop cret_N scar_N poscar_N cret_t poscar_t;\r\n   run;\r\n  %put ### DONE!;\r\n  \r\n  proc print label u;\r\n    title1 \"Output for dataset &amp;inset for a\r\n   (&amp;start,&amp;end) event window using &amp;model model\";\r\n    id &amp;group;\r\n    var cret_mean car_mean scar_mean poscar_mean\r\n         car_n tsign tpatell car_t scar_t;\r\n  \r\n %if \"&amp;group\" ne \"\" %then %do;\r\n  title2 \"Test for Equality of CARs among groups defined by &amp;group\";\r\n  \r\n \/*To find out the results of the hypothesis test for comparing groups   *\/\r\n \/*find the row of output labeled 'Model' and look at the column labeled *\/\r\n \/*F-value for the Fisher statistic and Pr&gt;F for the associated p-value  *\/\r\n \/*HOVTEST tests for whether variances of two groups are the same        *\/\r\n proc glm data=_car;\r\n   class &amp;group;\r\n   model scar=&amp;group;\r\n   means &amp;group \/hovtest;\r\n  \r\n proc npar1way data=_car wilcoxon;\r\n  var scar;\r\n  class &amp;group;\r\n %end;\r\n run;\r\n \r\n\/*create the final output dataset*\/\r\n  %if %lowcase(&amp;model) ne madj %then %do;\r\n              %let _beta=_params(keep=permno _edate beta);\r\n\t\t\t  %let _beta_label=beta='Beta';\r\n              %end;\r\n  %else %do;\r\n\t\t\t  %let _beta=;\r\n\t\t\t  %let _beta_label=;\r\n\t\t\t  %end;  \/*add IF statement. CHANGE HERE*\/\r\n\r\ndata &amp;outset;\r\n   merge _temp (in=a rename=(&amp;evtdate=_edate))  \/*change &amp;inset reference. CHANGE HERE*\/\r\n         _abrets(keep=permno _edate date evttime ret abret var_estp)\r\n         _car   (keep=permno _edate cret car scar nrets)\r\n         &amp;_beta;  \/*add &amp;_beta. CHANGE HERE*\/\r\n   by permno _edate;\r\n   rename _edate=&amp;evtdate;  \/*use original variable name. CHANGE HERE*\/\r\n   label _edate='Event date'\r\n         date='Trading date in event window'\r\n         &amp;_beta_label;  \/*add &amp;_beta_label. CHANGE HERE*\/\r\n   format _edate date9. date date9.;\r\n   if a;\r\n  run;\r\n  \r\n \/*house cleaning*\/\r\n proc sql; drop table _caldates, _car, _factors, _test,\r\n         _params, _temp, _evtrets1, _evtrets_temp,\r\n         _temp1, _temp2;\r\n          drop view _abrets; quit;\r\n options errors=&amp;errors &amp;oldoptions;\r\n %put ;%put ### OUTPUT IN THE DATASET &amp;outset;\r\n %put ;%put ### TEST STATISTICS IN THE DATASET &amp;outstats;\r\n%MEND;\r\n<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>There are two macros on the List of WRDS Research Macros: EVTSTUDY and BETA, both of which may be commonly used. I like the first one, authored by Denys Glushkov. Denys&#8217; code is always elegant. I don&#8217;t like the second &hellip; <a href=\"https:\/\/www.kaichen.work\/?p=418\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8],"tags":[],"_links":{"self":[{"href":"https:\/\/www.kaichen.work\/index.php?rest_route=\/wp\/v2\/posts\/418"}],"collection":[{"href":"https:\/\/www.kaichen.work\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.kaichen.work\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.kaichen.work\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.kaichen.work\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=418"}],"version-history":[{"count":60,"href":"https:\/\/www.kaichen.work\/index.php?rest_route=\/wp\/v2\/posts\/418\/revisions"}],"predecessor-version":[{"id":2255,"href":"https:\/\/www.kaichen.work\/index.php?rest_route=\/wp\/v2\/posts\/418\/revisions\/2255"}],"wp:attachment":[{"href":"https:\/\/www.kaichen.work\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=418"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.kaichen.work\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=418"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.kaichen.work\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=418"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}