--- title: "Factor Models, GARCH, and Buy-and-Hold Abnormal Returns" author: "Simon Mueller" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Factor Models, GARCH, and BHAR} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE ) ``` # Introduction The simple Market Model regresses firm returns on a single market index. While widely used, it may miss systematic risk factors that explain return variation. The EventStudy package provides several alternatives: | Model | Class | Factors | |-------|-------|---------| | Fama-French 3-Factor | `FamaFrench3FactorModel` | Market, SMB, HML | | Fama-French 5-Factor | `FamaFrench5FactorModel` | Market, SMB, HML, RMW, CMA | | Carhart 4-Factor | `Carhart4FactorModel` | Market, SMB, HML, MOM | | GARCH(1,1) | `GARCHModel` | Market (time-varying volatility) | | BHAR | `BHARModel` | Market (compounded returns) | Factor models reduce estimation-window residual variance, yielding more powerful tests. GARCH accounts for volatility clustering. BHAR avoids the bias from summing daily abnormal returns over long horizons. ```{r load-packages} library(EventStudy) library(dplyr) library(tibble) library(ggplot2) ``` # Factor Data: The `factor_tbl` Factor models require a **factor table** passed to `EventStudyTask$new()`. This table must contain a `date` column (same format as firm/index data) plus the relevant factor columns. ## Required Columns by Model | Model | Required columns in `factor_tbl` | |-------|----------------------------------| | Fama-French 3-Factor | `smb`, `hml`, `risk_free_rate` | | Fama-French 5-Factor | `smb`, `hml`, `rmw`, `cma`, `risk_free_rate` | | Carhart 4-Factor | `smb`, `hml`, `mom`, `risk_free_rate` | The `risk_free_rate` column is special: when present, `prepare_event_study()` automatically computes **excess returns**: - `excess_return = firm_returns - risk_free_rate` - `market_excess = index_returns - risk_free_rate` These are the dependent and independent variables used by the factor model regressions. ## Sourcing Factor Data Fama-French factor data is publicly available from Kenneth French's website. In practice you might use the `frenchdata` or `tidyquant` packages: ```{r factor-source} # Example: constructing a factor_tbl manually # In practice, download from Kenneth French's data library set.seed(42) n <- 300 dates <- format(seq(as.Date("2014-06-01"), by = "day", length.out = n), "%d.%m.%Y") factor_tbl <- tibble( date = dates, smb = rnorm(n, 0, 0.005), # Small Minus Big hml = rnorm(n, 0, 0.005), # High Minus Low rmw = rnorm(n, 0, 0.004), # Robust Minus Weak cma = rnorm(n, 0, 0.004), # Conservative Minus Aggressive mom = rnorm(n, 0, 0.006), # Momentum risk_free_rate = rep(0.0001, n) # Daily risk-free rate ) ``` # Fama-French Three-Factor Model The three-factor model (Fama & French, 1993) explains stock returns using market risk, size (SMB), and value (HML) factors: $$R_i - R_f = \alpha + \beta_m (R_m - R_f) + \beta_s \text{SMB} + \beta_h \text{HML} + \epsilon$$ ## Running the Study ```{r ff3-run} set.seed(42) n <- 300 dates <- format(seq(as.Date("2014-06-01"), by = "day", length.out = n), "%d.%m.%Y") firm_tbl <- tibble( symbol = "FIRM_A", date = dates, adjusted = 100 * cumprod(1 + rnorm(n, 0.0003, 0.015)) ) index_tbl <- tibble( symbol = "INDEX_1", date = dates, adjusted = 1000 * cumprod(1 + rnorm(n, 0.0002, 0.012)) ) request_tbl <- tibble( event_id = 1L, firm_symbol = "FIRM_A", index_symbol = "INDEX_1", event_date = dates[200], group = "Test", event_window_start = -10L, event_window_end = 10L, shift_estimation_window = -11L, estimation_window_length = 150L ) # Pass factor_tbl to the task task <- EventStudyTask$new(firm_tbl, index_tbl, request_tbl, factor_tbl = factor_tbl) params <- ParameterSet$new( return_calculation = LogReturn$new(), return_model = FamaFrench3FactorModel$new() ) task <- task |> prepare_event_study(params) |> fit_model(params) |> calculate_statistics(params) ``` ## Inspecting Factor Loadings After fitting, the model stores all factor coefficients: ```{r ff3-results} # Model statistics for event 1 stats <- task$data_tbl$model[[1]]$statistics # Factor loadings stats$alpha # Intercept (Jensen's alpha) stats$beta # Market beta (same as `market_excess`) stats$smb # SMB loading stats$hml # HML loading stats$r2 # R-squared stats$sigma # Residual standard error # Full coefficient table task$get_model_stats(event_id = 1) ``` # Fama-French Five-Factor Model The five-factor model (Fama & French, 2015) adds profitability (RMW) and investment (CMA) factors: $$R_i - R_f = \alpha + \beta_m (R_m - R_f) + \beta_s \text{SMB} + \beta_h \text{HML} + \beta_r \text{RMW} + \beta_c \text{CMA} + \epsilon$$ ```{r ff5-run} task_ff5 <- EventStudyTask$new(firm_tbl, index_tbl, request_tbl, factor_tbl = factor_tbl) params_ff5 <- ParameterSet$new( return_calculation = LogReturn$new(), return_model = FamaFrench5FactorModel$new() ) task_ff5 <- task_ff5 |> prepare_event_study(params_ff5) |> fit_model(params_ff5) |> calculate_statistics(params_ff5) ``` # Carhart Four-Factor Model The Carhart (1997) model adds a momentum factor (MOM) to the three-factor model: $$R_i - R_f = \alpha + \beta_m (R_m - R_f) + \beta_s \text{SMB} + \beta_h \text{HML} + \beta_{mom} \text{MOM} + \epsilon$$ ```{r carhart-run} task_c4 <- EventStudyTask$new(firm_tbl, index_tbl, request_tbl, factor_tbl = factor_tbl) params_c4 <- ParameterSet$new( return_calculation = LogReturn$new(), return_model = Carhart4FactorModel$new() ) task_c4 <- task_c4 |> prepare_event_study(params_c4) |> fit_model(params_c4) |> calculate_statistics(params_c4) ``` # GARCH(1,1) Model The GARCH model (Bollerslev, 1986) accounts for **volatility clustering**---the tendency of large returns to follow large returns. The mean equation includes the market return as a regressor (like the Market Model), but the variance is modeled as time-varying: $$R_{i,t} = \mu + \beta R_{m,t} + \epsilon_t, \quad \epsilon_t \sim N(0, \sigma_t^2)$$ $$\sigma_t^2 = \omega + \alpha_1 \epsilon_{t-1}^2 + \beta_1 \sigma_{t-1}^2$$ ## Requirements The GARCH model requires the `rugarch` package: ```{r garch-install} install.packages("rugarch") ``` ## Running the Study ```{r garch-run} task_garch <- EventStudyTask$new(firm_tbl, index_tbl, request_tbl) params_garch <- ParameterSet$new( return_model = GARCHModel$new() ) task_garch <- task_garch |> prepare_event_study(params_garch) |> fit_model(params_garch) |> calculate_statistics(params_garch) ``` ## GARCH-Specific Results The GARCH model stores conditional volatility from the estimation window: ```{r garch-results} stats <- task_garch$data_tbl$model[[1]]$statistics stats$alpha # Mean equation intercept stats$beta # Market beta stats$sigma # Average conditional sigma stats$garch_sigma # Time series of conditional sigmas ``` The time-varying conditional sigma can be particularly useful for standardized test statistics, as it accounts for periods of high and low volatility in the estimation window. ## When to Use GARCH GARCH is most beneficial when: - The estimation window spans periods of varying market conditions (calm and turbulent periods) - The event itself may be associated with a volatility regime change - You want to account for volatility clustering in the test statistics For short estimation windows in stable markets, the simple Market Model is often sufficient. # Buy-and-Hold Abnormal Returns (BHAR) ## Motivation Standard CARs sum daily abnormal returns: $$CAR_i = \sum_{t=1}^{T} AR_{i,t}$$ For **long horizons** (months or years), this introduces a compounding bias. BHAR instead compounds returns, which better reflects the actual investor experience: $$BHAR_i = \prod_{t=1}^{T} (1 + R_{i,t}) - \prod_{t=1}^{T} (1 + R_{m,t})$$ ## Running a BHAR Study ```{r bhar-run} task_bhar <- EventStudyTask$new(firm_tbl, index_tbl, request_tbl) params_bhar <- ParameterSet$new( return_model = BHARModel$new(), single_event_statistics = SingleEventStatisticsSet$new( tests = list(BHARTTest$new()) ) ) task_bhar <- task_bhar |> prepare_event_study(params_bhar) |> fit_model(params_bhar) |> calculate_statistics(params_bhar) ``` ## Interpreting BHAR Results Unlike the Market Model where abnormal returns are computed per observation, BHAR computes **running compounded** differences. The abnormal return at each point represents the cumulative buy-and-hold difference up to that point: ```{r bhar-results} # Tidy output tidy.EventStudyTask(task_bhar, type = "ar") # BHAR test statistic task_bhar$data_tbl$BHART[[1]] ``` ## BHAR vs. CAR: When Does It Matter? The difference between BHAR and CAR grows with the horizon and the magnitude of returns: ```{r bhar-vs-car} # Short window: CAR and BHAR are nearly identical task_short <- EventStudyTask$new(firm_tbl, index_tbl, request_tbl) params_short <- ParameterSet$new(return_model = MarketModel$new()) task_short <- run_event_study(task_short) car_short <- tidy.EventStudyTask(task_short, type = "car") bhar_short <- tidy.EventStudyTask(task_bhar, type = "ar") ``` For a 21-day event window (typical), the difference is usually negligible. For horizons of 6--12 months or longer, BHAR is strongly preferred. # Comparing Models A rigorous event study often compares results across multiple models to assess robustness: ```{r model-comparison} models <- list( "Market Model" = MarketModel$new(), "FF3" = FamaFrench3FactorModel$new(), "FF5" = FamaFrench5FactorModel$new(), "Carhart" = Carhart4FactorModel$new() ) results <- purrr::imap_dfr(models, function(model, name) { t <- EventStudyTask$new(firm_tbl, index_tbl, request_tbl, factor_tbl = factor_tbl) p <- ParameterSet$new( return_calculation = LogReturn$new(), return_model = model ) t <- t |> prepare_event_study(p) |> fit_model(p) |> calculate_statistics(p) tidy.EventStudyTask(t, type = "car") |> mutate(model = name) }) # Plot CARs across models ggplot(results, aes(x = relative_index, y = car, color = model)) + geom_line(linewidth = 0.8) + geom_hline(yintercept = 0, linetype = "dashed", color = "grey40") + geom_vline(xintercept = 0, linetype = "dotted", color = "red", alpha = 0.6) + labs( title = "CAR Comparison Across Models", x = "Relative Time to Event", y = "Cumulative Abnormal Return", color = "Model" ) + theme_minimal() ``` If the main result holds across all models, you have strong evidence that it is not an artifact of the return model specification. # Summary | Model | Best for | Extra data needed | |-------|----------|-------------------| | Market Model | Short horizons, standard studies | None | | FF3 | Controlling for size and value | `factor_tbl` with SMB, HML | | FF5 | Adding profitability and investment | `factor_tbl` with SMB, HML, RMW, CMA | | Carhart | Adding momentum exposure | `factor_tbl` with SMB, HML, MOM | | GARCH | Volatile markets, heteroskedastic returns | None (requires `rugarch`) | | BHAR | Long-horizon studies (6+ months) | None | # References - Fama, E. F. & French, K. R. (1993). Common risk factors in the returns on stocks and bonds. *Journal of Financial Economics*, 33(1), 3--56. - Fama, E. F. & French, K. R. (2015). A five-factor asset pricing model. *Journal of Financial Economics*, 116(1), 1--22. - Carhart, M. M. (1997). On persistence in mutual fund performance. *Journal of Finance*, 52(1), 57--82. - Bollerslev, T. (1986). Generalized autoregressive conditional heteroskedasticity. *Journal of Econometrics*, 31(3), 307--327. - Barber, B. M. & Lyon, J. D. (1997). Detecting long-run abnormal stock returns: The empirical power and specification of test statistics. *Journal of Financial Economics*, 43(3), 341--372. - MacKinlay, A. C. (1997). Event Studies in Economics and Finance. *Journal of Economic Literature*, 35(1), 13--39.