The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
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_tblFactor 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.
| 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_ratemarket_excess = index_returns - risk_free_rateThese are the dependent and independent variables used by the factor model regressions.
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
)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\]
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)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)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)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)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\]
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 sigmasThe 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.
GARCH is most beneficial when:
For short estimation windows in stable markets, the simple Market Model is often sufficient.
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})\]
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)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:
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.
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.
| 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 |
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.