knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE )
Traditional event studies measure abnormal returns at a daily frequency, but many corporate events --- earnings announcements, regulatory decisions, central-bank communications --- have effects that materialize within minutes or hours. Intraday event studies capture these short-lived dynamics by operating on high-frequency (tick or bar) data.
The EventStudy package supports two intraday workflows:
IntradayEventStudyTask and prepare_intraday_event_study(), which compute intraday returns and assign estimation/event windows.nonparametric_intraday_test(), which implements the methodology of Rinaudo & Saha (2014) in pure R.This vignette walks through both.
The non-parametric test requires three inputs:
| Input | Description |
|-------|-------------|
| estimation_window | A data frame with columns day, time, and abnormalReturn. Contains intraday abnormal returns for multiple estimation days (e.g., 20 trading days before the event). Each day has the same time grid. |
| event_window | A data frame with columns time and abnormalReturn for the single event day. |
| event_times | A character vector of intraday times at which events occurred (e.g., c("10:00", "14:30")). |
The time column should be a character representation of the intraday time (e.g., "10:15", "14:30"), consistent across all days. The abnormalReturn column contains the pre-computed abnormal return for each bar.
library(tibble) # Estimation window: 20 days x 50 bars per day estimation_window <- tibble( day = rep(1:20, each = 50), time = rep(sprintf("%02d:%02d", 10L + 1:50 %/% 60L, 1:50 %% 60L), 20), abnormalReturn = rnorm(1000, 0, 0.001) ) # Event window: single day, same 50 bars event_window <- tibble( time = sprintf("%02d:%02d", 10L + 1:50 %/% 60L, 1:50 %% 60L), abnormalReturn = rnorm(50, 0, 0.001) ) # Event times to test event_times <- c("10:10", "10:30")
For studies where you want to use the full pipeline (return computation, window assignment), start with IntradayEventStudyTask:
library(EventStudy) # Firm intraday data firm_data <- tibble( symbol = "FIRM_A", timestamp = seq(as.POSIXct("2020-06-15 09:30:00", tz = "UTC"), by = "1 min", length.out = 500), price = 100 * cumprod(1 + rnorm(500, 0, 0.001)) ) # Index intraday data index_data <- tibble( symbol = "INDEX_1", timestamp = seq(as.POSIXct("2020-06-15 09:30:00", tz = "UTC"), by = "1 min", length.out = 500), price = 1000 * cumprod(1 + rnorm(500, 0, 0.0008)) ) # Request table request <- tibble( event_id = 1L, firm_symbol = "FIRM_A", index_symbol = "INDEX_1", event_timestamp = as.POSIXct("2020-06-15 14:30:00", tz = "UTC"), group = "Earnings", event_window_start = -30L, event_window_end = 30L, shift_estimation_window = -31L, estimation_window_length = 120L ) # Create task and prepare task <- IntradayEventStudyTask$new(firm_data, index_data, request) ps <- ParameterSet$new() task <- prepare_intraday_event_study(task, ps)
The prepared task contains returns (firm_returns, index_returns) and window flags (event_window, estimation_window) in the nested data column.
The nonparametric_intraday_test() function implements the algorithm of Rinaudo & Saha (2014). The key idea:
This approach is non-parametric: no distributional assumptions are needed. The confidence band comes directly from the empirical distribution of estimation-day CARs.
results <- nonparametric_intraday_test( estimation_window = estimation_window, event_window = event_window, event_times = event_times, p = 0.05, init_window = 5L, upper = FALSE )
Parameters:
p: Significance level (default 0.05). Smaller values require more extreme event-day CARs.init_window: Number of bars to accumulate before the first significance check (default 5).upper: If TRUE, test the upper tail (positive abnormal returns); if FALSE (default), test the lower tail (negative abnormal returns).The function returns a named list of tibbles, one per event time:
results[["10:10"]] #> # A tibble: 12 x 4 #> id CAR CI fitCI #> <int> <dbl> <dbl> <dbl> #> 1 1 -0.00823 -0.00654 -0.00671 #> 2 2 -0.00912 -0.00701 -0.00698 #> ...
| Column | Description |
|--------|-------------|
| id | Observation index within the significant window (1, 2, ...) |
| CAR | Cumulative abnormal return on the event day |
| CI | Raw empirical confidence interval boundary from estimation days |
| fitCI | Polynomial-smoothed CI (degree-4 polynomial) for smoother visualization |
When an event time is not significant, the result is a single-row tibble with all values equal to zero:
results[["10:30"]] #> # A tibble: 1 x 4 #> id CAR CI fitCI #> <int> <dbl> <dbl> <dbl> #> 1 1 0 0 0
The fitCI column applies a degree-4 polynomial smoothing to the raw CI values. This removes noise from the empirical percentile boundary and produces cleaner confidence bands for visualization.
The CAR path with confidence bands can be visualized with ggplot2:
library(ggplot2) plot_np_result <- function(result_tbl, event_time) { if (nrow(result_tbl) <= 1 && result_tbl$CAR[1] == 0) { message("Event at ", event_time, " is not significant.") return(invisible(NULL)) } ggplot(result_tbl, aes(x = id)) + geom_line(aes(y = CAR), colour = "steelblue", linewidth = 1) + geom_line(aes(y = fitCI), colour = "firebrick", linetype = "dashed", linewidth = 0.8) + geom_ribbon(aes(ymin = fitCI, ymax = 0), alpha = 0.1, fill = "firebrick") + labs( title = paste("Non-Parametric Intraday CAR — Event at", event_time), x = "Bars after event", y = "Cumulative Abnormal Return" ) + theme_minimal() } # Plot the first significant event plot_np_result(results[["10:10"]], "10:10")
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.