---
title: "Volume and Volatility Event Studies"
author: "Simon Mueller"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Volume and Volatility Event Studies}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  eval = FALSE
)
```

# Introduction

Traditional event studies focus on price-based abnormal returns. However,
corporate events often affect **trading volume** and **return volatility** even
when the net price effect is ambiguous. For example, an earnings announcement
may trigger heavy trading and volatility spikes without moving the price much
in either direction. Volume and volatility event studies capture these
information-content effects that price-based methods can miss.

The EventStudy package provides two dedicated models:

| Model | Class | Abnormal measure |
|-------|-------|------------------|
| Volume | `VolumeModel` | Deviation of (log-)volume from estimation-window mean |
| Volatility | `VolatilityModel` | Ratio of squared returns to estimation-window variance, minus 1 |

Both models plug into the standard pipeline (`prepare_event_study()` →
`fit_model()` → `calculate_statistics()`) and are compatible with all existing
test statistics.

```{r load-packages}
library(EventStudy)
library(dplyr)
library(ggplot2)
```

# Data Requirements

## Volume Event Study

The `VolumeModel` requires a **`firm_volume`** column in the firm data, in
addition to the standard `symbol`, `date`, and `adjusted` columns. The volume
column should contain raw trading volume (number of shares traded):

```{r volume-data}
firm_tbl <- tibble(
  symbol   = rep("FIRM_A", 300),
  date     = format(seq(as.Date("2014-01-01"), by = "day", length.out = 300),
                    "%d.%m.%Y"),
  adjusted = 100 * cumprod(1 + rnorm(300, 0.0003, 0.015)),
  firm_volume = pmax(0, rnorm(300, mean = 1e6, sd = 2e5))
)
```

The `firm_volume` column is **preserved through the pipeline** automatically.
The standard `EventStudyTask` passes through any extra columns present in the
firm data.

## Volatility Event Study

The `VolatilityModel` does **not** require extra columns---it uses the firm
returns already computed by `prepare_event_study()`. Abnormal volatility is
defined as:

$$AV_{i,t} = \frac{R_{i,t}^2}{\hat{\sigma}_i^2} - 1$$

where $\hat{\sigma}_i^2$ is the variance of firm returns in the estimation
window. Positive values indicate higher-than-expected volatility.

# Volume Event Study

## Setup

```{r volume-setup}
set.seed(42)

# Firm data with volume
n <- 300
firm_tbl <- tibble(
  symbol      = "FIRM_A",
  date        = format(seq(as.Date("2014-06-01"), by = "day",
                           length.out = n), "%d.%m.%Y"),
  adjusted    = 100 * cumprod(1 + rnorm(n, 0.0003, 0.015)),
  firm_volume = pmax(0, rnorm(n, mean = 1e6, sd = 2e5))
)

# Index data
index_tbl <- tibble(
  symbol   = "INDEX_1",
  date     = firm_tbl$date,
  adjusted = 1000 * cumprod(1 + rnorm(n, 0.0002, 0.012))
)

# Request table
request_tbl <- tibble(
  event_id                = 1L,
  firm_symbol             = "FIRM_A",
  index_symbol            = "INDEX_1",
  event_date              = firm_tbl$date[200],
  group                   = "Earnings",
  event_window_start      = -10L,
  event_window_end        = 10L,
  shift_estimation_window = -11L,
  estimation_window_length = 150L
)
```

## Running the Volume Study

The key difference from a standard event study is passing `VolumeModel$new()`
as the return model. The `study_type` field on the `ParameterSet` can be set
to `"volume"` for appropriate axis labels in plots.

```{r volume-run}
task <- EventStudyTask$new(firm_tbl, index_tbl, request_tbl)

params <- ParameterSet$new(
  return_calculation      = SimpleReturn$new(),
  return_model            = VolumeModel$new(log_transform = TRUE),
  single_event_statistics = SingleEventStatisticsSet$new(),
  multi_event_statistics  = MultiEventStatisticsSet$new()
)
params$study_type <- "volume"

task <- task |>
  prepare_event_study(params) |>
  fit_model(params) |>
  calculate_statistics(params)
```

## Log Transform

By default, `VolumeModel` log-transforms volume (`log(volume + 1)`) before
computing abnormal volume. This is standard practice because raw volume is
typically right-skewed. To disable the transform:

```{r volume-no-log}
vm <- VolumeModel$new(log_transform = FALSE)
```

## Interpreting Results

Abnormal volume is the difference between observed (log-)volume and the
estimation-window mean:

$$AVolume_{i,t} = \log(V_{i,t} + 1) - \overline{\log(V_i + 1)}^{\text{est}}$$

Positive values indicate higher-than-normal trading activity. The test
statistics (AR T-test, CAR T-test, etc.) are applied to these abnormal volume
measures in the same way as for abnormal returns.

```{r volume-results}
# Single-event results
task$get_ar(event_id = 1)
task$get_car(event_id = 1)

# Tidy output
tidy.EventStudyTask(task, type = "ar")
```

## Visualization

```{r volume-plot, fig.width=7, fig.height=4}
plot_event_study(task, type = "ar", event_id = 1) +
  labs(title = "Abnormal Volume Around Earnings Announcement",
       y = "Abnormal Log-Volume")
```

# Volatility Event Study

## Setup

The volatility study uses the same data structure as a standard event study---no
extra columns are needed.

```{r volatility-setup}
set.seed(123)

n <- 300
firm_tbl <- tibble(
  symbol   = "FIRM_A",
  date     = format(seq(as.Date("2014-06-01"), by = "day",
                        length.out = n), "%d.%m.%Y"),
  adjusted = 100 * cumprod(1 + rnorm(n, 0.0003, 0.015))
)

index_tbl <- tibble(
  symbol   = "INDEX_1",
  date     = firm_tbl$date,
  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              = firm_tbl$date[200],
  group                   = "Earnings",
  event_window_start      = -10L,
  event_window_end        = 10L,
  shift_estimation_window = -11L,
  estimation_window_length = 150L
)
```

## Running the Volatility Study

```{r volatility-run}
task <- EventStudyTask$new(firm_tbl, index_tbl, request_tbl)

params <- ParameterSet$new(
  return_calculation      = SimpleReturn$new(),
  return_model            = VolatilityModel$new(),
  single_event_statistics = SingleEventStatisticsSet$new(),
  multi_event_statistics  = MultiEventStatisticsSet$new()
)
params$study_type <- "volatility"

task <- task |>
  prepare_event_study(params) |>
  fit_model(params) |>
  calculate_statistics(params)
```

## Interpreting Results

Abnormal volatility is a **variance ratio** minus 1:

$$AV_{i,t} = \frac{R_{i,t}^2}{\hat{\sigma}_i^2} - 1$$

- $AV = 0$: volatility matches estimation-window expectations.
- $AV > 0$: volatility is higher than expected (e.g., information arrival).
- $AV < 0$: volatility is lower than expected (e.g., quiet trading).

Values around event dates are typically large and positive when events carry
new information, regardless of the direction of the price move.

```{r volatility-results}
task$get_ar(event_id = 1)
task$get_car(event_id = 1)

tidy.EventStudyTask(task, type = "ar")
```

## Visualization

```{r volatility-plot, fig.width=7, fig.height=4}
plot_event_study(task, type = "ar", event_id = 1) +
  labs(title = "Abnormal Volatility Around Earnings Announcement",
       y = "Abnormal Volatility (Variance Ratio - 1)")
```

# Combining Price, Volume, and Volatility

A comprehensive event study often examines all three dimensions. You can run
separate studies on the same data and compare results:

```{r combined-study}
set.seed(42)

n <- 300
firm_tbl <- tibble(
  symbol      = "FIRM_A",
  date        = format(seq(as.Date("2014-06-01"), by = "day",
                           length.out = n), "%d.%m.%Y"),
  adjusted    = 100 * cumprod(1 + rnorm(n, 0.0003, 0.015)),
  firm_volume = pmax(0, rnorm(n, mean = 1e6, sd = 2e5))
)

index_tbl <- tibble(
  symbol   = "INDEX_1",
  date     = firm_tbl$date,
  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 = firm_tbl$date[200], group = "Earnings",
  event_window_start = -10L, event_window_end = 10L,
  shift_estimation_window = -11L, estimation_window_length = 150L
)

# --- Price study ---
task_price <- EventStudyTask$new(firm_tbl, index_tbl, request_tbl)
params_price <- ParameterSet$new(return_model = MarketModel$new())
task_price <- run_event_study(task_price, params_price)

# --- Volume study ---
task_vol <- EventStudyTask$new(firm_tbl, index_tbl, request_tbl)
params_vol <- ParameterSet$new(return_model = VolumeModel$new())
params_vol$study_type <- "volume"
task_vol <- task_vol |>
  prepare_event_study(params_vol) |>
  fit_model(params_vol) |>
  calculate_statistics(params_vol)

# --- Volatility study ---
task_volat <- EventStudyTask$new(firm_tbl, index_tbl, request_tbl)
params_volat <- ParameterSet$new(return_model = VolatilityModel$new())
params_volat$study_type <- "volatility"
task_volat <- task_volat |>
  prepare_event_study(params_volat) |>
  fit_model(params_volat) |>
  calculate_statistics(params_volat)
```

```{r combined-plot, fig.width=7, fig.height=8}
# Extract tidy ARs from each study
ar_price  <- tidy.EventStudyTask(task_price, type = "ar") %>%
  mutate(study = "Price (AR)")
ar_vol    <- tidy.EventStudyTask(task_vol, type = "ar") %>%
  mutate(study = "Volume (Abnormal Log-Volume)")
ar_volat  <- tidy.EventStudyTask(task_volat, type = "ar") %>%
  mutate(study = "Volatility (Variance Ratio - 1)")

combined <- bind_rows(ar_price, ar_vol, ar_volat)

ggplot(combined, aes(x = relative_index, y = abnormal_returns)) +
  geom_hline(yintercept = 0, linetype = "dashed", colour = "grey40") +
  geom_vline(xintercept = 0, linetype = "dotted", colour = "red", alpha = 0.6) +
  geom_col(fill = "steelblue", alpha = 0.7) +
  facet_wrap(~ study, ncol = 1, scales = "free_y") +
  labs(
    title = "Price, Volume, and Volatility Event Study",
    x     = "Relative Time to Event",
    y     = "Abnormal Measure"
  ) +
  theme_minimal()
```

This three-panel view reveals whether the event primarily affects prices,
trading activity, volatility, or some combination.

# Diagnostics

Model diagnostics work the same way as for price-based studies:

```{r diagnostics}
# Volume model diagnostics
model_diagnostics(task_vol)
plot_diagnostics(task_vol, event_id = 1)

# Volatility model diagnostics
model_diagnostics(task_volat)
plot_diagnostics(task_volat, event_id = 1)
```

The diagnostics include estimation-window residual normality (Shapiro-Wilk),
autocorrelation (Durbin-Watson, Ljung-Box), and model fit statistics.

# Export

Results can be exported in the same formats as any other study:

```{r export}
export_results(task_vol, "volume_results.csv")
export_results(task_vol, "volume_results.xlsx")
export_results(task_volat, "volatility_results.tex", which = c("model", "ar"))
```

# References

- Campbell, J. Y. & Hentschel, L. (1992). No news is good news: An asymmetric model of changing volatility in stock returns. *Journal of Financial Economics*, 31(3), 281--318.
- Beaver, W. H. (1968). The information content of annual earnings announcements. *Journal of Accounting Research*, 6, 67--92.
- MacKinlay, A. C. (1997). Event Studies in Economics and Finance. *Journal of Economic Literature*, 35(1), 13--39.
