Skip to contents

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.

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:

# 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

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:

# 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

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

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:

install.packages("rugarch")

Running the Study

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:

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

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:

# 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:

# 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:

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.