tests/test.sh exit code (0 → resolved); the classification below is post-hoc and cannot change it.cd /app && ls -la && echo "---DATA---" && ls -la data/ && echo "---OUTPUTS---" && ls -la outputs/ 2>/dev/null || echo "no outputs dir"
total 29 drwxrws--- 4 h2tagent h2tagent 4096 Jun 20 17:04 . drwxr-xr-x 26 root root 4096 Jun 20 17:55 .. -rw-rw---- 1 h2tagent h2tagent 12723 Jun 20 17:03 analysis.R drwxrws--- 2 h2tagent h2tagent 4096 Jun 20 17:04 data drwxrws--- 2 h2tagent h2tagent 4096 Jun 20 17:04 outputs ---DATA--- total 366 drwxrws--- 2 h2tagent h2tagent 4096 Jun 20 17:04 . drwxrws--- 4 h2tagent h2tagent 4096 Jun 20 17:04 .. -rw-rw---- 1 h2tagent h2tagent 903 Jun 20 17:03 firms.csv -rw-rw---- 1 h2tagent h2tagent 1716 Jun 20 17:03 recalls.csv -rw-rw---- 1 h2tagent h2tagent 363097 Jun 20 17:03 stock_returns.csv ---OUTPUTS--- total 8 drwxrws--- 2 h2tagent h2tagent 4096 Jun 20 17:04 . drwxrws--- 4 h2tagent h2tagent 4096 Jun 20 17:04 ..
cd /app && echo "===STOCK_RETURNS===" && head -5 data/stock_returns.csv && echo "..." && wc -l data/stock_returns.csv && echo "===RECALLS===" && head -5 data/recalls.csv && wc -l data/recalls.csv && echo "===FIRMS===" && head -5 data/firms.csv && wc -l data/firms.csv
===STOCK_RETURNS=== firm_id,date,return,market_return FIRM_004,2006-11-22,0.005192,-0.004466 FIRM_008,2006-12-11,0.033288,0.00112 FIRM_017,2007-05-16,-0.002451,0.026981 FIRM_018,2007-02-14,0.022314,0.033039 ... 9365 data/stock_returns.csv ===RECALLS=== event_id,firm_id,date,hazard_type,units_recalled,media_articles,severity_score 1,FIRM_007,2007-03-21,burn,9186546,5,7.17 2,FIRM_002,2007-03-28,laceration,75169,3,1.21 3,FIRM_017,2007-04-16,choking,637374,4,4.6 4,FIRM_013,2007-04-20,lead_paint,681155,5,8.88 36 data/recalls.csv ===FIRMS=== firm_id,market_cap_mm,sic_code,beta,alpha,idio_vol,n_recalls FIRM_001,2606.23,3944,1.043,-0.000135,0.0152,4 FIRM_002,519.06,3944,0.6799,-0.000219,0.0187,2 FIRM_003,4449.48,3944,0.6878,-3.8e-05,0.0252,0 FIRM_004,5589.68,3942,1.4253,0.000847,0.0183,0 19 data/firms.csv
cd /app && cat analysis.R
suppressPackageStartupMessages({
library(jsonlite)
library(ggplot2)
})
EST_LEN <- 200
EST_GAP <- 30
# =====================================================================
# 1. Load and clean
# =====================================================================
returns <- read.csv("/app/data/stock_returns.csv")
recalls <- read.csv("/app/data/recalls.csv")
firms <- read.csv("/app/data/firms.csv")
returns <- returns[!is.na(returns$return) & !is.na(returns$market_return), ]
returns <- returns[!duplicated(returns), ]
returns$date <- as.Date(returns$date)
returns <- returns[order(returns$firm_id, returns$date), ]
rownames(returns) <- NULL
recalls <- recalls[!duplicated(recalls), ]
recalls$date <- as.Date(recalls$date)
firms <- firms[!duplicated(firms), ]
n_events <- nrow(recalls)
n_firms <- nrow(firms)
n_firms_with_recalls <- length(unique(recalls$firm_id))
all_dates <- sort(unique(returns$date))
date_to_idx <- setNames(seq_along(all_dates) - 1L, as.character(all_dates))
# =====================================================================
# 2. Market model , basic OLS, returns raw AR (no Patell standardization)
# =====================================================================
event_market_model <- function(fid, eidx) {
est_end <- eidx - EST_GAP - 1
est_start <- est_end - EST_LEN + 1
if (est_start < 0) return(NULL)
est_dates <- all_dates[(est_start + 1):(est_end + 1)]
sub <- returns[returns$firm_id == fid & returns$date %in% est_dates, ]
if (nrow(sub) < 100) return(NULL)
m <- lm(return ~ market_return, data = sub)
list(alpha = unname(coef(m)[1]), beta = unname(coef(m)[2]),
sigma_eps = sd(resid(m)), n_est = nrow(sub),
mean_rm = mean(sub$market_return),
sum_sq_dev_rm = sum((sub$market_return - mean(sub$market_return))^2))
}
windows <- list(w3 = c(-1, 1), w2 = c(0, 1), w11 = c(-5, 5))
event_rows <- list()
daily_long <- list()
for (i in seq_len(n_events)) {
fid <- recalls$firm_id[i]
edate <- recalls$date[i]
estr <- as.character(edate)
if (!(estr %in% names(date_to_idx))) next
eidx <- as.integer(date_to_idx[estr])
m <- event_market_model(fid, eidx)
if (is.null(m)) next
firm <- returns[returns$firm_id == fid, ]
rownames(firm) <- as.character(firm$date)
cars <- list(); ar_day0 <- NA_real_; valid_w3 <- TRUE
for (wname in names(windows)) {
w <- windows[[wname]]; ars <- numeric(0); ok <- TRUE
for (off in seq.int(w[1], w[2])) {
tidx <- eidx + off
if (tidx < 0 || tidx >= length(all_dates)) { ok <- FALSE; break }
target <- all_dates[tidx + 1]
if (!(as.character(target) %in% rownames(firm))) { ok <- FALSE; break }
rm_t <- firm[as.character(target), "market_return"]
ret_t <- firm[as.character(target), "return"]
ar <- ret_t - (m$alpha + m$beta * rm_t)
ars <- c(ars, ar)
if (wname == "w3" && off == 0) ar_day0 <- ar
}
if (!ok) {
if (wname == "w3") valid_w3 <- FALSE
cars[[wname]] <- NA_real_
} else {
# NOTE: simple summation of raw ARs , spec wants standardized SCAR
cars[[wname]] <- sum(ars)
}
}
if (!valid_w3 || is.na(cars$w3)) next
event_rows[[length(event_rows) + 1L]] <- data.frame(
event_id = as.integer(recalls$event_id[i]),
firm_id = fid,
date = estr,
scar_3day = cars$w3,
scar_2day = cars$w2,
scar_11day = cars$w11,
ar_day0 = ar_day0,
alpha_hat = m$alpha,
beta_hat = m$beta,
sigma_eps = m$sigma_eps,
n_est = m$n_est,
stringsAsFactors = FALSE
)
for (off in seq.int(-30L, 30L)) {
tidx <- eidx + off
if (tidx < 0 || tidx >= length(all_dates)) next
target <- all_dates[tidx + 1]
if (!(as.character(target) %in% rownames(firm))) next
daily_long[[length(daily_long) + 1L]] <- data.frame(
date = target, firm_id = fid,
ret = firm[as.character(target), "return"],
market_return = firm[as.character(target), "market_return"],
event_id =
…[truncated]cd /app && Rscript -e 'cat(R.version.string, "\n"); for (p in c("jsonlite","ggplot2","sandwich","lmtest","MASS")) cat(p, ":", as.character(requireNamespace(p, quietly=TRUE)), "\n")'R version 4.2.2 Patched (2022-11-10 r83330) jsonlite : TRUE ggplot2 : TRUE sandwich : FALSE lmtest : FALSE MASS : TRUE
cd /app && Rscript -e '
r <- read.csv("data/stock_returns.csv")
cat("rows:", nrow(r), "\n")
cat("NA return:", sum(is.na(r$return)), " NA mkt:", sum(is.na(r$market_return)), "\n")
cat("sentinel return < -10:", sum(r$return < -10, na.rm=TRUE), "\n")
cat("dup rows:", sum(duplicated(r)), "\n")
cat("firms:", length(unique(r$firm_id)), "\n")
cat("date range:", as.character(min(as.Date(r$date))), "to", as.character(max(as.Date(r$date))), "\n")
print(table(r$firm_id)[1:5])
cat("\n--- recalls ---\n")
rc <- read.csv("data/recalls.csv")
print(summary(rc[,c("units_recalled","media_articles","severity_score")]))
cat("dup recalls:", sum(duplicated(rc)), "\n")
cat("\n--- firms ---\n")
f <- read.csv("data/firms.csv")
print(f[,c("firm_id","idio_vol","market_cap_mm")])
cat("NA idio_vol:", sum(is.na(f$idio_vol)), "\n")
'rows: 9364
NA return: 0 NA mkt: 3
sentinel return < -10: 5
dup rows: 4
firms: 18
date range: 2006-01-03 to 2007-12-31
FIRM_001 FIRM_002 FIRM_003 FIRM_004 FIRM_005
520 520 520 520 520
--- recalls ---
units_recalled media_articles severity_score
Min. : 67821 Min. : 3.000 Min. :1.070
1st Qu.: 492422 1st Qu.: 5.500 1st Qu.:3.955
Median : 1525113 Median : 7.000 Median :5.900
Mean : 3207624 Mean : 7.857 Mean :5.911
3rd Qu.: 4228210 3rd Qu.:10.000 3rd Qu.:8.110
Max. :19715949 Max. :13.000 Max. :9.800
dup recalls: 0
--- firms ---
firm_id idio_vol market_cap_mm
1 FIRM_001 0.0152 2606.23
2 FIRM_002 0.0187 519.06
3 FIRM_003 0.0252 4449.48
4 FIRM_004 0.0183 5589.68
5 FIRM_005 0.0235 173.95
6 FIRM_006 0.0180 378.94
7 FIRM_007 0.0203 2107.82
8 FIRM_008 0.0162 1237.08
9 FIRM_009 0.0155 1771.95
10 FIRM_010 0.0156 649.59
11 FIRM_011 0.0155 5194.09
12 FIRM_012 0.0152 4597.88
13 FIRM_013 0.0219 1957.13
14 FIRM_014 0.0168 6993.17
15 FIRM_015 0.0165 3168.49
16 FIRM_016 0.0187 644.74
17 FIRM_017 0.0157 2814.39
18 FIRM_018 0.0227 572.12
NA idio_vol: 0cd /app && Rscript -e '
r <- read.csv("data/stock_returns.csv")
cat("rows with NA mkt:\n"); print(r[is.na(r$market_return),])
cat("\nsentinel rows:\n"); print(r[which(r$return < -10),])
cat("\ndup rows:\n"); print(r[duplicated(r),])
# firm counts after clean
r2 <- r[!is.na(r$return)&!is.na(r$market_return)&r$return>=-10,]
r2 <- r2[!duplicated(r2),]
cat("\nafter clean rows:", nrow(r2), "\n")
print(range(table(r2$firm_id)))
'rows with NA mkt:
firm_id date return market_return
4393 FIRM_015 2007-10-24 0.046806 NA
4502 FIRM_010 2007-11-12 0.029817 NA
8160 FIRM_010 2006-06-08 0.011398 NA
sentinel rows:
firm_id date return market_return
1214 FIRM_015 2006-04-04 -99 0.044498
1554 FIRM_017 2006-10-27 -99 -0.007913
2270 FIRM_004 2006-02-08 -99 0.006117
7180 FIRM_006 2006-12-21 -99 0.002101
7332 FIRM_013 2006-01-10 -99 -0.035878
dup rows:
firm_id date return market_return
4363 FIRM_015 2007-05-03 -0.046116 -0.026444
6234 FIRM_010 2007-05-18 0.024219 0.028165
6279 FIRM_016 2007-01-22 -0.014105 -0.002825
7814 FIRM_018 2007-05-24 0.044480 0.049075
after clean rows: 9352
[1] 518 520cd /app && Rscript -e '
r <- read.csv("data/stock_returns.csv")
r <- r[!is.na(r$return)&!is.na(r$market_return)&r$return>=-10,]
r <- r[!duplicated(r),]; r$date <- as.Date(r$date)
# market return identical across firms per date?
agg <- aggregate(market_return ~ date, r, function(x) length(unique(round(x,6))))
cat("max distinct mkt per date:", max(agg$market_return), "\n")
all_dates <- sort(unique(r$date))
cat("n trading days:", length(all_dates), "\n")
idx <- setNames(seq_along(all_dates)-1L, as.character(all_dates))
rc <- read.csv("data/recalls.csv"); rc$date <- as.Date(rc$date)
cat("event date range:", as.character(min(rc$date)), as.character(max(rc$date)), "\n")
ei <- idx[as.character(rc$date)]
cat("event indices range:", min(ei,na.rm=T), max(ei,na.rm=T), "\n")
cat("events w/ date in calendar:", sum(!is.na(ei)), " of", nrow(rc), "\n")
# how many have est window start >=0 (eidx-230>=0 => eidx>=230) and +5 within
surv <- sum(ei>=230 & (ei+5) < length(all_dates), na.rm=TRUE)
cat("events with eidx>=230 and +5 in range:", surv, "\n")
cat("eidx>=230:", sum(ei>=230,na.rm=T), "\n")
print(sort(ei))
'max distinct mkt per date: 1
n trading days: 520
event date range: 2007-03-21 2007-11-13
event indices range: 316 485
events w/ date in calendar: 35 of 35
events with eidx>=230 and +5 in range: 35
eidx>=230: 35
2007-03-21 2007-03-28 2007-04-16 2007-04-20 2007-04-24 2007-05-04 2007-05-08
316 321 334 338 340 348 350
2007-05-16 2007-05-24 2007-05-30 2007-05-31 2007-06-13 2007-06-14 2007-06-15
356 362 366 367 376 377 378
2007-06-20 2007-06-25 2007-07-09 2007-07-13 2007-07-23 2007-08-09 2007-08-10
381 384 394 398 404 417 418
2007-08-13 2007-08-14 2007-08-21 2007-08-27 2007-09-04 2007-09-07 2007-09-18
419 420 425 429 435 438 445
2007-09-20 2007-09-25 2007-09-27 2007-11-07 2007-11-08 2007-11-09 2007-11-13
447 450 452 481 482 483 485command: create
path: /app/analysis.R
file_text: suppressPackageStartupMessages({
library(jsonlite)
library(ggplot2)
})
# =====================================================================
# Product-recall event study , deterministic, base-R implementation.
# No event-study packages, no randomness/bootstrapping.
# =====================================================================
EST_LEN <- 200 # estimation-window length (trading days)
EST_GAP <- 30 # gap between estimation window and event date
# =====================================================================
# 1. Load and clean
# - drop NA return / market_return
# - drop sentinel returns (return < -10)
# - drop duplicate rows
# - sort by (firm_id, date)
# =====================================================================
returns <- read.csv("/app/data/stock_returns.csv", stringsAsFactors = FALSE)
recalls <- read.csv("/app/data/recalls.csv", stringsAsFactors = FALSE)
firms <- read.csv("/app/data/firms.csv", stringsAsFactors = FALSE)
returns <- returns[!is.na(returns$return) & !is.na(returns$market_return), ]
returns <- returns[returns$return >= -10, ] # remove sentinel returns
returns <- returns[!duplicated(returns), ] # remove duplicate rows
returns$date <- as.Date(returns$date)
returns <- returns[order(returns$firm_id, returns$date), ]
rownames(returns) <- NULL
recalls <- recalls[!duplicated(recalls), ]
recalls$date <- as.Date(recalls$date)
firms <- firms[!duplicated(firms), ]
n_events <- nrow(recalls)
n_firms <- nrow(firms)
n_firms_with_recalls <- length(unique(recalls$firm_id))
# Global trading-day calendar (0-based index) shared by all firms.
all_dates <- sort(unique(returns$date))
n_all_dates <- length(all_dates)
date_to_idx <- setNames(seq_along(all_dates) - 1L, as.character(all_dates))
# Per-date market return (identical across firms on a given date).
mkt_by_date <- tapply(returns$market_return, as.character(returns$date), function(x) x[1])
# Split the panel into a list keyed by firm, each indexed by date string,
# for fast look-ups.
returns_by_firm <- split(returns, returns$firm_id)
firm_idx <- lapply(returns_by_firm, function(df) {
setNames(seq_len(nrow(df)), as.character(df$date))
})
# =====================================================================
# 2. Market model + prediction-error-corrected SARs / SCARs
# =====================================================================
# Fit market model on the 200-day estimation window ending 30 trading
# days before the event. Returns fitted parameters and the pieces needed
# for Patell prediction-error correction.
event_market_model <- function(fid, eidx) {
est_end <- eidx - EST_GAP - 1 # last estimation index
est_start <- est_end - EST_LEN + 1 # first estimation index (200 days)
if (est_start < 0) return(NULL)
est_dates <- all_dates[(est_start + 1):(est_end + 1)]
fdf <- returns_by_firm[[fid]]
if (is.null(fdf)) return(NULL)
sub <- fdf[fdf$date %in% est_dates, ]
if (nrow(sub) < 100) return(NULL)
m <- lm(return ~ market_return, data = sub)
cf <- coef(m)
s_i <- summary(m)$sigma # sqrt(SSE/(L-2)) , Patell base sd
mean_rm <- mean(sub$market_return)
list(alpha = unname(cf[1]), beta = unname(cf[2]),
s_i = s_i, n_est = nrow(sub),
mean_rm = mean_rm,
ssq_rm = sum((sub$market_return - mean_rm)^2))
}
# Prediction-error correction factor g_t for one event-window day.
g_factor <- function(m, rm_t) {
1 + 1 / m$n_est + (rm_t - m$mean_rm)^2 / m$ssq_rm
}
windows <- list(w3 = c(-1, 1), w2 = c(0, 1), w11 = c(-5, 5))
event_rows <- list()
for (i in seq_len(n_events)) {
fid <- recalls$firm_id[i]
edate <- recalls$date[i]
estr <- as.character(edate)
if (!(estr %in% names(date_to_idx))) next
eidx <- as.integer(date_to_idx[estr])
m <- event_market_model(fid, eidx)
if (is.null(m)) next
fdf <- returns_by_firm[[fid]]
fix <- firm_idx[[fid]]
scars <- lis
…[truncated]Existing file was successfully overwritten at /app/analysis.R
cd /app && rm -f outputs/* && Rscript analysis.R 2>&1 | tail -20
Done. n_valid_events = 35 rho_bar = 0.0057
cd /app && cat outputs/event_study_results.json
{
"n_events": 35,
"n_firms": 18,
"n_firms_with_recalls": 14,
"n_valid_events": 35,
"rho_bar": 0.005682,
"aggregate_scars": {
"window_3day": {
"mean_scar": -1.01163,
"patell_z": -5.984886,
"bmp_t": -4.992138,
"kp_t": -4.570158,
"pct_negative": 0.828571,
"n": 35
},
"window_2day": {
"mean_scar": -0.862243,
"patell_z": -5.101097,
"bmp_t": -4.509969,
"kp_t": -4.128747,
"pct_negative": 0.8,
"n": 35
},
"window_11day": {
"mean_scar": -0.450723,
"patell_z": -2.666514,
"bmp_t": -2.174875,
"kp_t": -1.991035,
"pct_negative": 0.628571,
"n": 35
}
},
"cross_section_hac": {
"intercept": 6.870415,
"log_units": -0.277206,
"media": -0.140035,
"severity": -0.049356,
"log_mcap": -0.349665,
"se_intercept": 1.989976,
"se_log_units": 0.143768,
"se_media": 0.045345,
"se_severity": 0.079685,
"se_log_mcap": 0.109201,
"r_squared": 0.365807,
"n_obs": 35,
"hac_lag": 3
},
"cross_section_wls": {
"intercept": 6.181414,
"log_units": -0.245301,
"media": -0.142987,
"severity": -0.068882,
"log_mcap": -0.301826,
"se_intercept": 2.373976,
"se_log_units": 0.119944,
"se_media": 0.063018,
"se_severity": 0.074244,
"se_log_mcap": 0.192047,
"r_squared": 0.307908,
"n_obs": 35
},
"calendar_time": {
"alpha": -0.000858,
"beta": 1.033203,
"t_alpha": -1.34566,
"p_value": 0.179749,
"n_days": 230
},
"corrado_rank": {
"z_stat": -2.426518,
"p_value": 0.015244
},
"cowan_sign": {
"z_stat": 0.253547,
"p_value": 0.799846,
"p_bar": 0.492859,
"frac_positive": 0.514286
},
"schipper_thompson": {
"wald_stat": 25.628867,
"wald_p": 1.1e-05,
"df": 3
}
}cd /app && head -6 outputs/event_cars.csv && echo "..." && wc -l outputs/event_cars.csv && echo "--- plot files ---" && ls -la outputs/*.png
"event_id","firm_id","date","scar_3day","scar_2day","scar_11day","ar_day0" 1,"FIRM_007","2007-03-21",-2.77385385944802,-2.03060185064421,-2.77014144097435,-0.0188048397516721 2,"FIRM_002","2007-03-28",1.66617104840486,2.2032063219835,1.43531764348162,0.0513410791394269 3,"FIRM_017","2007-04-16",0.94907492719969,1.03224371499574,1.49892343651693,0.00560955526504031 4,"FIRM_013","2007-04-20",-0.275920692089554,-0.623000766624633,-0.852290011168427,-0.0130106860489592 5,"FIRM_014","2007-04-24",-0.933499846911045,-0.478116395743624,-0.884830134388866,0.00353526947807872 ... 36 outputs/event_cars.csv --- plot files --- -rw-r--r-- 1 h2tagent h2tagent 48874 Jun 20 18:00 outputs/car_timeline.png -rw-r--r-- 1 h2tagent h2tagent 95094 Jun 20 18:00 outputs/portfolio_comparison.png
command: view path: /app/outputs/car_timeline.png
<output_image> …[truncated]
command: view path: /app/outputs/portfolio_comparison.png
<output_image> …[truncated]
command: str_replace
path: /app/analysis.R
old_str: x = "Trading days relative to recall announcement (t = 0)",
y = "Cumulative abnormal return") +
theme_minimal(base_size = 12)
ggsave("/app/outputs/car_timeline.png", p1, width = 8, height = 5, dpi = 120)
new_str: x = "Trading days relative to recall announcement (t = 0)",
y = "Cumulative abnormal return") +
theme_minimal(base_size = 12) +
theme(plot.background = element_rect(fill = "white", color = NA),
panel.background = element_rect(fill = "white", color = NA))
ggsave("/app/outputs/car_timeline.png", p1, width = 8, height = 5, dpi = 120, bg = "white")The file /app/analysis.R has been edited successfully.
command: str_replace
path: /app/analysis.R
old_str: x = "Date", y = "Cumulative simple return", color = "Series") +
theme_minimal(base_size = 12) +
theme(legend.position = "bottom")
ggsave("/app/outputs/portfolio_comparison.png", p2, width = 10, height = 5, dpi = 120)
new_str: x = "Date", y = "Cumulative simple return", color = "Series") +
theme_minimal(base_size = 12) +
theme(legend.position = "bottom",
plot.background = element_rect(fill = "white", color = NA),
panel.background = element_rect(fill = "white", color = NA))
ggsave("/app/outputs/portfolio_comparison.png", p2, width = 10, height = 5, dpi = 120, bg = "white")The file /app/analysis.R has been edited successfully.
command: str_replace
path: /app/analysis.R
old_str: scars <- list(w3 = NA_real_, w2 = NA_real_, w11 = NA_real_)
ar_day0 <- NA_real_
valid_w3 <- TRUE
for (wname in names(windows)) {
w <- windows[[wname]]
ar_sum <- 0.0; g_sum <- 0.0; ok <- TRUE
new_str: scars <- list(w3 = NA_real_, w2 = NA_real_, w11 = NA_real_)
raw_cars <- list(w3 = NA_real_, w2 = NA_real_, w11 = NA_real_)
ar_day0 <- NA_real_
valid_w3 <- TRUE
for (wname in names(windows)) {
w <- windows[[wname]]
ar_sum <- 0.0; g_sum <- 0.0; ok <- TRUEThe file /app/analysis.R has been edited successfully.
command: str_replace
path: /app/analysis.R
old_str: } else {
# Patell standardized cumulative abnormal return:
# SCAR = sum(AR) / ( s_i * sqrt( sum_t g_t ) )
scars[[wname]] <- ar_sum / (m$s_i * sqrt(g_sum))
}
}
new_str: } else {
# Patell standardized cumulative abnormal return:
# SCAR = sum(AR) / ( s_i * sqrt( sum_t g_t ) )
scars[[wname]] <- ar_sum / (m$s_i * sqrt(g_sum))
raw_cars[[wname]] <- ar_sum
}
}The file /app/analysis.R has been edited successfully.
command: str_replace
path: /app/analysis.R
old_str: scar_11day = scars$w11,
ar_day0 = ar_day0,
new_str: scar_11day = scars$w11,
raw_car3 = raw_cars$w3,
raw_car11 = raw_cars$w11,
ar_day0 = ar_day0,The file /app/analysis.R has been edited successfully.
command: str_replace
path: /app/analysis.R
old_str: cat("Done. n_valid_events =", n_valid_events, " rho_bar =", round(rho_bar, 4), "\n")
new_str: # =====================================================================
# Research memo (deterministic, data-driven)
# =====================================================================
mean_raw_car3_pct <- mean(event_df$raw_car3, na.rm = TRUE) * 100
mean_raw_car11_pct <- mean(event_df$raw_car11, na.rm = TRUE) * 100
mean_ar0_pct <- mean(event_df$ar_day0, na.rm = TRUE) * 100
cal_ann_pct <- (exp(calendar$alpha * 252) - 1) * 100 # annualized alpha
pf <- function(x, d = 2) formatC(x, format = "f", digits = d)
memo <- paste0(
"# Research Memo: The Stock-Market Impact of Product-Recall Announcements\n\n",
"**Prepared for:** Toy-industry regulators and institutional investors \n",
"**Subject:** A modern event-study assessment of how equity prices respond to product recalls \n",
"**Sample:** ", n_valid_events, " recall events across ", n_firms_with_recalls,
" of ", n_firms, " toy manufacturers (2006-2007 daily return panel)\n\n",
"## 1. Executive summary\n\n",
"Product-recall announcements are followed by an economically large and statistically ",
"decisive **decline** in the announcing firm's stock price. Averaged across the ",
n_valid_events, " events, the three-day announcement window [-1,+1] earns a raw cumulative ",
"abnormal return (CAR) of about **", pf(mean_raw_car3_pct), "%**, with the event-day abnormal ",
"return alone averaging roughly ", pf(mean_ar0_pct), "%. Standardizing each event by its own ",
"estimation-period volatility (the prediction-error-corrected Patell approach) yields a mean ",
"standardized CAR (SCAR) of ", pf(agg_3$mean_scar), " for the three-day window, and ",
pf(agg_3$pct_negative * 100, 1), "% of events have a negative SCAR. In plain terms, recalls ",
"destroy shareholder value quickly and consistently.\n\n",
"## 2. Magnitude and statistical significance of abnormal returns\n\n",
"All three parametric cross-sectional statistics reject the null of zero abnormal performance ",
"for the short windows. For the three-day window the **Patell z = ", pf(agg_3$patell_z),
"**, the **Boehmer-Musumeci-Poulsen (BMP) t = ", pf(agg_3$bmp_t), "**, and the ",
"**Kolari-Pynnonen (KP) t = ", pf(agg_3$kp_t), "**. The BMP statistic matters because it ",
"absorbs the *event-induced variance* that typically inflates naive tests; the KP statistic ",
"goes further and corrects for cross-sectional correlation across events (average pairwise ",
"residual correlation rho-bar = ", pf(rho_bar, 4), "). Because rho-bar is small here, the KP ",
"and BMP statistics are close, which tells us that event clustering is not manufacturing the ",
"significance. The two-day window [0,+1] tells the same story (Patell z = ", pf(agg_2$patell_z),
"), while the wider eleven-day window [-5,+5] is weaker (Patell z = ", pf(agg_11$patell_z),
", mean SCAR ", pf(agg_11$mean_scar), ", raw CAR about ", pf(mean_raw_car11_pct), "%). ",
"The attenuation in the wider window indicates that the price reaction is concentrated in the ",
"days immediately around the announcement rather than accumulating over a longer horizon, ",
"which is the signature of a reasonably efficient, news-driven repricing. The Schipper-Thompson ",
"joint Wald test, which asks whether all three window means are simultaneously zero, is ",
"emphatically rejected (chi-square = ", pf(schipper$wald_stat), ", df = 3, p = ",
formatC(schipper$wald_p, format = "e", digits = 2), ").\n\n",
"## 3. What drives the cross-sectional heterogeneity?\n\n",
"We regress each event's three-day SCAR on log(units recalled), media coverage, severity score, ",
"and log(market capitalization), using Newey-West HAC standard errors (lag = ", cs_hac$hac_lag,
") and, as a robustness check, weighted least squares (weights = 1/idiosyncratic-variance). ",
"The model explains a meaningful share of the variation (R-squared = ", pf(cs_hac$r_squared, 3),
…[truncated]The file /app/analysis.R has been edited successfully.
cd /app && rm -f outputs/* && Rscript analysis.R 2>&1 | tail -5 && echo "--- outputs ---" && ls -la outputs/
Done. n_valid_events = 35 rho_bar = 0.0057 --- outputs --- total 163 drwxrws--- 2 h2tagent h2tagent 4096 Jun 20 18:01 . drwxrws--- 4 h2tagent h2tagent 4096 Jun 20 18:01 .. -rw-r--r-- 1 h2tagent h2tagent 50695 Jun 20 18:01 car_timeline.png -rw-r--r-- 1 h2tagent h2tagent 3618 Jun 20 18:01 event_cars.csv -rw-r--r-- 1 h2tagent h2tagent 1839 Jun 20 18:01 event_study_results.json -rw-r--r-- 1 h2tagent h2tagent 93953 Jun 20 18:01 portfolio_comparison.png -rw-r--r-- 1 h2tagent h2tagent 7043 Jun 20 18:01 research_memo.md
cd /app && echo "memo words: $(wc -w < outputs/research_memo.md)" && echo "--- JSON parse check ---" && python3 -c "import json;d=json.load(open('outputs/event_study_results.json'));print('keys:',list(d.keys()));print('hac keys:',list(d['cross_section_hac'].keys()))" && echo "--- CSV header ---" && head -1 outputs/event_cars.csv && echo "--- CSV ncol check ---" && awk -F, 'NR==1{print NF" columns"}' outputs/event_cars.csvmemo words: 1053 --- JSON parse check --- keys: ['n_events', 'n_firms', 'n_firms_with_recalls', 'n_valid_events', 'rho_bar', 'aggregate_scars', 'cross_section_hac', 'cross_section_wls', 'calendar_time', 'corrado_rank', 'cowan_sign', 'schipper_thompson'] hac keys: ['intercept', 'log_units', 'media', 'severity', 'log_mcap', 'se_intercept', 'se_log_units', 'se_media', 'se_severity', 'se_log_mcap', 'r_squared', 'n_obs', 'hac_lag'] --- CSV header --- "event_id","firm_id","date","scar_3day","scar_2day","scar_11day","ar_day0" --- CSV ncol check --- 7 columns
command: view path: /app/outputs/car_timeline.png
<output_image> …[truncated]
command: view path: /app/outputs/portfolio_comparison.png
<output_image> …[truncated]
cd /app && python3 -c "import numpy,pandas,scipy;print('numpy',numpy.__version__,'pandas',pandas.__version__,'scipy',scipy.__version__)" 2>&1numpy 1.26.4 pandas 2.2.2 scipy 1.13.1
cd /app && cat > /tmp/verify.py << 'PYEOF'
import numpy as np, pandas as pd, json
from scipy import stats
EST_LEN, EST_GAP = 200, 30
R = pd.read_csv("data/stock_returns.csv")
RC = pd.read_csv("data/recalls.csv")
F = pd.read_csv("data/firms.csv")
# clean
R = R[R['return'].notna() & R['market_return'].notna()]
R = R[R['return'] >= -10]
R = R.drop_duplicates()
R['date'] = pd.to_datetime(R['date'])
R = R.sort_values(['firm_id','date']).reset_index(drop=True)
RC = RC.drop_duplicates(); RC['date'] = pd.to_datetime(RC['date'])
F = F.drop_duplicates()
all_dates = np.sort(R['date'].unique())
idx = {d:i for i,d in enumerate(all_dates)}
nD = len(all_dates)
mkt_by_date = R.groupby('date')['market_return'].first()
by_firm = {f:g.set_index('date') for f,g in R.groupby('firm_id')}
def market_model(fid, eidx):
est_end = eidx - EST_GAP - 1; est_start = est_end - EST_LEN + 1
if est_start < 0: return None
dts = all_dates[est_start:est_end+1]
g = by_firm[fid]
sub = g[g.index.isin(dts)]
if len(sub) < 100: return None
x = sub['market_return'].values; y = sub['return'].values
b, a = np.polyfit(x, y, 1)
resid = y - (a + b*x); L = len(sub)
sse = np.sum(resid**2); s = np.sqrt(sse/(L-2))
mrm = x.mean(); ssq = np.sum((x-mrm)**2)
return dict(alpha=a, beta=b, s=s, L=L, mrm=mrm, ssq=ssq)
wins = {'w3':(-1,1),'w2':(0,1),'w11':(-5,5)}
rows=[]
for _,r in RC.iterrows():
fid=r['firm_id']; ed=r['date']
if ed not in idx: continue
e=idx[ed]; m=market_model(fid,e)
if m is None: continue
g=by_firm[fid]
scars={}; ok3=True; ar0=np.nan
for wn,(lo,hi) in wins.items():
ars=0.0; gs=0.0; ok=True
for off in range(lo,hi+1):
t=e+off
if t<0 or t>=nD: ok=False;break
d=all_dates[t]
if d not in g.index: ok=False;break
rm=g.loc[d,'market_return']; rt=g.loc[d,'return']
ar=rt-(m['alpha']+m['beta']*rm); ars+=ar
gs+=1+1/m['L']+(rm-m['mrm'])**2/m['ssq']
if wn=='w3' and off==0: ar0=ar
if not ok:
if wn=='w3': ok3=False
scars[wn]=np.nan
else:
scars[wn]=ars/(m['s']*np.sqrt(gs))
if not ok3 or np.isnan(scars['w3']): continue
rows.append(dict(event_id=int(r['event_id']),firm_id=fid,date=ed,
scar_3day=scars['w3'],scar_2day=scars['w2'],scar_11day=scars['w11'],
ar_day0=ar0,alpha=m['alpha'],beta=m['beta'],eidx=e))
E=pd.DataFrame(rows).sort_values('event_id').reset_index(drop=True)
print("n_valid_events",len(E))
# rho_bar
fr={}
for f,g in by_firm.items():
if len(g)<30: continue
x=g['market_return'].values;y=g['return'].values
b,a=np.polyfit(x,y,1); fr[f]=pd.Series(y-(a+b*x),index=g.index)
fs=list(fr); cors=[]
for i in range(len(fs)):
for j in range(i+1,len(fs)):
a=fr[fs[i]];b=fr[fs[j]];c=a.index.intersection(b.index)
if len(c)<20: continue
cors.append(np.corrcoef(a.loc[c],b.loc[c])[0,1])
rho=np.mean(cors) if cors else 0.0
print("rho_bar",round(rho,6))
def agg(v):
v=v[~np.isnan(v)];n=len(v);mv=v.mean();var=v.var(ddof=1);sd=np.sqrt(var)
return dict(mean=mv,patell=v.sum()/np.sqrt(n),bmp=mv/(sd/np.sqrt(n)),
kp=mv/np.sqrt(var*(1+(n-1)*rho)/n),pneg=np.mean(v<0),n=n)
for w in ['scar_3day','scar_2day','scar_11day']:
a=agg(E[w].values)
print(w,"mean=%.6f patell=%.6f bmp=%.6f kp=%.6f pneg=%.6f"%(a['mean'],a['patell'],a['bmp'],a['kp'],a['pneg']))
# cross-section HAC
cs=E.merge(RC[['event_id','units_recalled','media_articles','severity_score']],on='event_id')
cs=cs.merge(F[['firm_id','market_cap_mm','idio_vol']],on='firm_id').sort_values('event_id').reset_index(drop=True)
X=np.column_stack([np.ones(len(cs)),np.log(cs['units_recalled']),cs['media_articles'],
cs['severity_score'],np.log(cs['market_cap_mm'])])
y=cs['scar_3day'].values;N,k=X.shape
XtXi=np.linalg.inv(X.T@X);bh=XtXi@X.T@y;res=y-X@bh
r2=1-np.sum(res**2)/np.sum((y-y.mean())**2)
L=int(np.floor(4*(N/100)**(2/9)))
u=X*res
…[truncated][stderr]
Traceback (most recent call last):
File "/tmp/verify.py", line 67, in <module>
E=pd.DataFrame(rows).sort_values('event_id').reset_index(drop=True)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/pandas/core/frame.py", line 7189, in sort_values
k = self._get_label_or_level_values(by[0], axis=axis)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/pandas/core/generic.py", line 1911, in _get_label_or_level_values
raise KeyError(key)
KeyError: 'event_id'cd /app && sed -i 's/^all_dates = np.sort(R\[.date.\].unique())/all_dates = [pd.Timestamp(d) for d in np.sort(R["date"].unique())]/' /tmp/verify.py
sed -i 's/^idx = {d:i for i,d in enumerate(all_dates)}/idx = {d:i for i,d in enumerate(all_dates)}/' /tmp/verify.py
python3 /tmp/verify.pyn_valid_events 35 rho_bar 0.005682 scar_3day mean=-1.011630 patell=-5.984886 bmp=-4.992138 kp=-4.570158 pneg=0.828571 scar_2day mean=-0.862243 patell=-5.101097 bmp=-4.509969 kp=-4.128747 pneg=0.800000 scar_11day mean=-0.450723 patell=-2.666514 bmp=-2.174875 kp=-1.991035 pneg=0.628571 HAC beta [ 6.870415 -0.277206 -0.140035 -0.049356 -0.349665] r2 0.365807 lag 3 HAC se [1.989976 0.143768 0.045345 0.079685 0.109201] WLS beta [ 6.181414 -0.245301 -0.142987 -0.068882 -0.301826] r2 0.307908 WLS se [2.373976 0.119944 0.063018 0.074244 0.192047] calendar alpha=-0.000858 beta=1.033203 t=-1.345660 p=0.179749 n=230 corrado z=-2.426518 p=0.015244 N=35 cowan z=0.253547 p=0.799846 pbar=0.492859 frac=0.514286 N=35 schipper wald=25.628867 p=1.141e-05
cd /app && sed -n '1,20p' outputs/research_memo.md && echo "..." && grep -o 'raw cumulative abnormal return (CAR) of about [^ ]*%' outputs/research_memo.md && grep -o 'annualized, that alpha is only about [^ ]*%' outputs/research_memo.md
# Research Memo: The Stock-Market Impact of Product-Recall Announcements **Prepared for:** Toy-industry regulators and institutional investors **Subject:** A modern event-study assessment of how equity prices respond to product recalls **Sample:** 35 recall events across 14 of 18 toy manufacturers (2006-2007 daily return panel) ## 1. Executive summary Product-recall announcements are followed by an economically large and statistically decisive **decline** in the announcing firm's stock price. Averaged across the 35 events, the three-day announcement window [-1,+1] earns a raw cumulative abnormal return (CAR) of about **-2.92%**, with the event-day abnormal return alone averaging roughly -1.31%. Standardizing each event by its own estimation-period volatility (the prediction-error-corrected Patell approach) yields a mean standardized CAR (SCAR) of -1.01 for the three-day window, and 82.9% of events have a negative SCAR. In plain terms, recalls destroy shareholder value quickly and consistently. ## 2. Magnitude and statistical significance of abnormal returns All three parametric cross-sectional statistics reject the null of zero abnormal performance for the short windows. For the three-day window the **Patell z = -5.98**, the **Boehmer-Musumeci-Poulsen (BMP) t = -4.99**, and the **Kolari-Pynnonen (KP) t = -4.57**. The BMP statistic matters because it absorbs the *event-induced variance* that typically inflates naive tests; the KP statistic goes further and corrects for cross-sectional correlation across events (average pairwise residual correlation rho-bar = 0.0057). Because rho-bar is small here, the KP and BMP statistics are close, which tells us that event clustering is not manufacturing the significance. The two-day window [0,+1] tells the same story (Patell z = -5.10), while the wider eleven-day window [-5,+5] is weaker (Patell z = -2.67, mean SCAR -0.45, raw CAR about -2.50%). The attenuation in the wider window indicates that the price reaction is concentrated in the days immediately around the announcement rather than accumulating over a longer horizon, which is the signature of a reasonably efficient, news-driven repricing. The Schipper-Thompson joint Wald test, which asks whether all three window means are simultaneously zero, is emphatically rejected (chi-square = 25.63, df = 3, p = 1.14e-05). ## 3. What drives the cross-sectional heterogeneity? We regress each event's three-day SCAR on log(units recalled), media coverage, severity score, and log(market capitalization), using Newey-West HAC standard errors (lag = 3) and, as a robustness check, weighted least squares (weights = 1/idiosyncratic-variance). The model explains a meaningful share of the variation (R-squared = 0.366). The economically and statistically strongest driver is **recall scale**: the coefficient on log(units recalled) is -0.277 (HAC SE 0.144), meaning larger recalls trigger more negative abnormal returns. **Media coverage** carries a negative coefficient (-0.140, HAC SE 0.045): the more intense the press attention, the larger the value loss, consistent with reputational and demand-side spillovers beyond the direct cost of the recall. The **severity score** enters negatively but more weakly (-0.049), and **firm size** (log market cap) is negative (-0.350), a pattern consistent with larger, more visible brands having more franchise value at risk, though size effects are imprecisely estimated. The WLS estimates (log-units -0.245, media -0.143) are qualitatively identical, so the conclusions are not artifacts of a few high-variance firms. The economic intuition is intuitive: investors price the expected cash-flow hit, which scales with the number of affected units and the breadth of public attention, plus a reputational penalty that is largest when coverage is most intense. ## 4. Calendar-time portfolio versus short-window results ... raw cumulative abnormal return (CAR) of about **-2.92%
command: str_replace
path: /app/analysis.R
old_str: "). The model explains a meaningful share of the variation (R-squared = ", pf(cs_hac$r_squared, 3),
"). The economically and statistically strongest driver is **recall scale**: the coefficient on ",
"log(units recalled) is ", pf(cs_hac$log_units, 3), " (HAC SE ", pf(cs_hac$se_log_units, 3),
"), meaning larger recalls trigger more negative abnormal returns. **Media coverage** carries a ",
"negative coefficient (", pf(cs_hac$media, 3), ", HAC SE ", pf(cs_hac$se_media, 3),
"): the more intense the press attention, the larger the value loss, consistent with reputational ",
"and demand-side spillovers beyond the direct cost of the recall. The **severity score** enters ",
"negatively but more weakly (", pf(cs_hac$severity, 3), "), and **firm size** (log market cap) is ",
"negative (", pf(cs_hac$log_mcap, 3), "), a pattern consistent with larger, more visible brands ",
"having more franchise value at risk, though size effects are imprecisely estimated. The WLS ",
new_str: "). The model explains a meaningful share of the variation (R-squared = ", pf(cs_hac$r_squared, 3),
"). All four characteristics push abnormal returns in the *same* (negative) direction. In terms of ",
"raw economic magnitude, **recall scale** has the largest swing: the coefficient on log(units ",
"recalled) is ", pf(cs_hac$log_units, 3), " (HAC SE ", pf(cs_hac$se_log_units, 3),
", roughly marginal statistically), and because units recalled span several orders of magnitude ",
"this translates into the widest spread in predicted SCARs - larger recalls trigger more negative ",
"abnormal returns. The most *statistically precise* effects are **media coverage** ",
"(coefficient ", pf(cs_hac$media, 3), ", HAC SE ", pf(cs_hac$se_media, 3),
") and **firm size** (log market cap, coefficient ", pf(cs_hac$log_mcap, 3), ", HAC SE ",
pf(cs_hac$se_log_mcap, 3), "), both comfortably significant: heavier press attention amplifies the ",
"value loss - consistent with reputational and demand-side spillovers beyond the direct cost of the ",
"recall - while larger, more visible brands see bigger proportional hits, consistent with having ",
"more franchise value at risk. The **severity score** enters negatively but weakly ",
"(", pf(cs_hac$severity, 3), ", HAC SE ", pf(cs_hac$se_severity, 3),
"), suggesting investors react more to the breadth and publicity of a recall than to the engineering ",
"hazard label itself. The WLS ",[stderr]
No replacement was performed, old_str `"). The model explains a meaningful share of the variation (R-squared = ", pf(cs_hac$r_squared, 3),
"). The economically and statistically strongest driver is **recall scale**: the coefficient on ",
"log(units recalled) is ", pf(cs_hac$log_units, 3), " (HAC SE ", pf(cs_hac$se_log_units, 3),
"), meaning larger recalls trigger more negative abnormal returns. **Media coverage** carries a ",
"negative coefficient (", pf(cs_hac$media, 3), ", HAC SE ", pf(cs_hac$se_media, 3),
"): the more intense the press attention, the larger the value loss, consistent with reputational ",
"and demand-side spillovers beyond the direct cost of the recall. The **severity score** enters ",
"negatively but more weakly (", pf(cs_hac$severity, 3), "), and **firm size** (log market cap) is ",
"negative (", pf(cs_hac$log_mcap, 3), "), a pattern consistent with larger, more visible brands ",
"having more franchise value at risk, though size effects are imprecisely estimated. The WLS ",` did not appear verbatim in /app/analysis.R.cd /app && grep -n 'economically and statistically strongest driver' analysis.R
565:"). The economically and statistically strongest driver is **recall scale**: the coefficient on ",
cd /app && sed -n '563,575p' analysis.R | cat -A | head -20
") and, as a robustness check, weighted least squares (weights = 1/idiosyncratic-variance). ",$
"The model explains a meaningful share of the variation (R-squared = ", pf(cs_hac$r_squared, 3),$
"). The economically and statistically strongest driver is **recall scale**: the coefficient on ",$
"log(units recalled) is ", pf(cs_hac$log_units, 3), " (HAC SE ", pf(cs_hac$se_log_units, 3),$
"), meaning larger recalls trigger more negative abnormal returns. **Media coverage** carries a ",$
"negative coefficient (", pf(cs_hac$media, 3), ", HAC SE ", pf(cs_hac$se_media, 3),$
"): the more intense the press attention, the larger the value loss, consistent with reputational ",$
"and demand-side spillovers beyond the direct cost of the recall. The **severity score** enters ",$
"negatively but more weakly (", pf(cs_hac$severity, 3), "), and **firm size** (log market cap) is ",$
"negative (", pf(cs_hac$log_mcap, 3), "), a pattern consistent with larger, more visible brands ",$
"having more franchise value at risk, though size effects are imprecisely estimated. The WLS ",$
"estimates (log-units ", pf(cs_wls$log_units, 3), ", media ", pf(cs_wls$media, 3),$
") are qualitatively identical, so the conclusions are not artifacts of a few high-variance firms. ",$command: str_replace
path: /app/analysis.R
old_str: "). The economically and statistically strongest driver is **recall scale**: the coefficient on ",
"log(units recalled) is ", pf(cs_hac$log_units, 3), " (HAC SE ", pf(cs_hac$se_log_units, 3),
"), meaning larger recalls trigger more negative abnormal returns. **Media coverage** carries a ",
"negative coefficient (", pf(cs_hac$media, 3), ", HAC SE ", pf(cs_hac$se_media, 3),
"): the more intense the press attention, the larger the value loss, consistent with reputational ",
"and demand-side spillovers beyond the direct cost of the recall. The **severity score** enters ",
"negatively but more weakly (", pf(cs_hac$severity, 3), "), and **firm size** (log market cap) is ",
"negative (", pf(cs_hac$log_mcap, 3), "), a pattern consistent with larger, more visible brands ",
"having more franchise value at risk, though size effects are imprecisely estimated. The WLS ",
new_str: "). All four characteristics push abnormal returns in the *same* (negative) direction. In terms of ",
"raw economic magnitude, **recall scale** has the largest swing: the coefficient on log(units ",
"recalled) is ", pf(cs_hac$log_units, 3), " (HAC SE ", pf(cs_hac$se_log_units, 3),
", roughly marginal statistically), and because units recalled span several orders of magnitude ",
"this translates into the widest spread in predicted SCARs - larger recalls trigger more negative ",
"abnormal returns. The most *statistically precise* effects are **media coverage** ",
"(coefficient ", pf(cs_hac$media, 3), ", HAC SE ", pf(cs_hac$se_media, 3),
") and **firm size** (log market cap, coefficient ", pf(cs_hac$log_mcap, 3), ", HAC SE ",
pf(cs_hac$se_log_mcap, 3), "), both comfortably significant: heavier press attention amplifies the ",
"value loss - consistent with reputational and demand-side spillovers beyond the direct cost of the ",
"recall - while larger, more visible brands see bigger proportional hits, consistent with having ",
"more franchise value at risk. The **severity score** enters negatively but weakly ",
"(", pf(cs_hac$severity, 3), ", HAC SE ", pf(cs_hac$se_severity, 3),
"), suggesting investors react more to the breadth and publicity of a recall than to the engineering ",
"hazard label itself. The WLS ",The file /app/analysis.R has been edited successfully.
cd /app && rm -f outputs/* && Rscript analysis.R 2>&1 | tail -3 && echo "--- files ---" && ls outputs/ && echo "--- memo words ---" && wc -w < outputs/research_memo.md && echo "--- section 3 ---" && sed -n '/## 3\./,/## 4\./p' outputs/research_memo.md | head -5
Done. n_valid_events = 35 rho_bar = 0.0057 --- files --- car_timeline.png event_cars.csv event_study_results.json portfolio_comparison.png research_memo.md --- memo words --- 1112 --- section 3 --- ## 3. What drives the cross-sectional heterogeneity? We regress each event's three-day SCAR on log(units recalled), media coverage, severity score, and log(market capitalization), using Newey-West HAC standard errors (lag = 3) and, as a robustness check, weighted least squares (weights = 1/idiosyncratic-variance). The model explains a meaningful share of the variation (R-squared = 0.366). All four characteristics push abnormal returns in the *same* (negative) direction. In terms of raw economic magnitude, **recall scale** has the largest swing: the coefficient on log(units recalled) is -0.277 (HAC SE 0.144, roughly marginal statistically), and because units recalled span several orders of magnitude this translates into the widest spread in predicted SCARs - larger recalls trigger more negative abnormal returns. The most *statistically precise* effects are **media coverage** (coefficient -0.140, HAC SE 0.045) and **firm size** (log market cap, coefficient -0.350, HAC SE 0.109), both comfortably significant: heavier press attention amplifies the value loss - consistent with reputational and demand-side spillovers beyond the direct cost of the recall - while larger, more visible brands see bigger proportional hits, consistent with having more franchise value at risk. The **severity score** enters negatively but weakly (-0.049, HAC SE 0.080), suggesting investors react more to the breadth and publicity of a recall than to the engineering hazard label itself. The WLS estimates (log-units -0.245, media -0.143) are qualitatively identical, so the conclusions are not artifacts of a few high-variance firms. The economic intuition is intuitive: investors price the expected cash-flow hit, which scales with the number of affected units and the breadth of public attention, plus a reputational penalty that is largest when coverage is most intense. ## 4. Calendar-time portfolio versus short-window results
cd /app && cat >> /tmp/verify.py << 'PYEOF'
# ---- automated comparison against the R-produced JSON ----
J = json.load(open("outputs/event_study_results.json"))
def close(a,b,tol=1e-5): return abs(a-b) <= tol*(1+abs(b))
checks=[]
checks.append(("n_valid_events", J["n_valid_events"], len(E)))
checks.append(("rho_bar", J["rho_bar"], round(rho,6)))
for wn,col in [("window_3day","scar_3day"),("window_2day","scar_2day"),("window_11day","scar_11day")]:
a=agg(E[col].values); jw=J["aggregate_scars"][wn]
for key,val in [("mean_scar",a["mean"]),("patell_z",a["patell"]),("bmp_t",a["bmp"]),("kp_t",a["kp"]),("pct_negative",a["pneg"])]:
checks.append((wn+"."+key, jw[key], val))
jh=J["cross_section_hac"]
for nm,val in zip(["intercept","log_units","media","severity","log_mcap"],bh): checks.append(("hac."+nm,jh[nm],val))
for nm,val in zip(["se_intercept","se_log_units","se_media","se_severity","se_log_mcap"],se): checks.append(("hac."+nm,jh[nm],val))
checks.append(("hac.r_squared",jh["r_squared"],r2)); checks.append(("hac.hac_lag",jh["hac_lag"],L))
jw_=J["cross_section_wls"]
for nm,val in zip(["intercept","log_units","media","severity","log_mcap"],bw): checks.append(("wls."+nm,jw_[nm],val))
for nm,val in zip(["se_intercept","se_log_units","se_media","se_severity","se_log_mcap"],sew): checks.append(("wls."+nm,jw_[nm],val))
checks.append(("wls.r_squared",jw_["r_squared"],r2w))
jc=J["calendar_time"]
for nm,val in [("alpha",bc[0]),("beta",bc[1]),("t_alpha",ta),("p_value",pv),("n_days",len(pr))]: checks.append(("cal."+nm,jc[nm],val))
checks.append(("corrado.z",J["corrado_rank"]["z_stat"],K.mean()/(K.std(ddof=1)/np.sqrt(len(K)))))
checks.append(("cowan.z",J["cowan_sign"]["z_stat"],z if False else (fp-pb)/np.sqrt(pb*(1-pb)/Nc)))
checks.append(("cowan.pbar",J["cowan_sign"]["p_bar"],pb))
checks.append(("cowan.frac",J["cowan_sign"]["frac_positive"],fp))
checks.append(("schipper.wald",J["schipper_thompson"]["wald_stat"],wald))
bad=[(n,jv,pv2) for (n,jv,pv2) in checks if not close(float(jv),float(pv2),1e-4)]
print("TOTAL CHECKS",len(checks),"| MISMATCHES",len(bad))
for n,jv,pv2 in bad: print(" MISMATCH",n,"json=",jv,"py=",pv2)
print("ALL MATCH" if not bad else "SOME MISMATCH")
PYEOF
python3 /tmp/verify.py 2>&1 | tail -5corrado z=-2.426518 p=0.015244 N=35 cowan z=0.253547 p=0.799846 pbar=0.492859 frac=0.514286 N=35 schipper wald=25.628867 p=1.141e-05 TOTAL CHECKS 50 | MISMATCHES 0 ALL MATCH
cd /app && python3 << 'PYEOF'
import json
d=json.load(open("outputs/event_study_results.json"))
req_top=["n_events","n_firms","n_firms_with_recalls","n_valid_events","rho_bar",
"aggregate_scars","cross_section_hac","cross_section_wls","calendar_time",
"corrado_rank","cowan_sign","schipper_thompson"]
print("top-level keys OK:", all(k in d for k in req_top), "| count", len(d))
for w in ["window_3day","window_2day","window_11day"]:
ks=set(d["aggregate_scars"][w]); need={"mean_scar","patell_z","bmp_t","kp_t","pct_negative","n"}
print(w,"keys OK:",need<=ks)
print("hac keys OK:", {"intercept","log_units","media","severity","log_mcap","se_intercept","se_log_units","se_media","se_severity","se_log_mcap","r_squared","n_obs","hac_lag"}<=set(d["cross_section_hac"]))
print("wls keys OK:", {"intercept","log_units","media","severity","log_mcap","se_intercept","se_log_units","se_media","se_severity","se_log_mcap","r_squared","n_obs"}<=set(d["cross_section_wls"]))
print("cal keys OK:", {"alpha","beta","t_alpha","p_value","n_days"}<=set(d["calendar_time"]))
print("corrado keys OK:", {"z_stat","p_value"}<=set(d["corrado_rank"]))
print("cowan keys OK:", {"z_stat","p_value","p_bar","frac_positive"}<=set(d["cowan_sign"]))
print("st keys OK:", {"wald_stat","wald_p","df"}<=set(d["schipper_thompson"]), "| df=",d["schipper_thompson"]["df"])
# type checks
ints=[d["n_events"],d["n_firms"],d["n_firms_with_recalls"],d["n_valid_events"],
d["cross_section_hac"]["n_obs"],d["cross_section_hac"]["hac_lag"],
d["cross_section_wls"]["n_obs"],d["calendar_time"]["n_days"],
d["schipper_thompson"]["df"],d["aggregate_scars"]["window_3day"]["n"]]
print("ints are int:", all(isinstance(x,int) for x in ints))
print("n_events,n_firms,n_firms_with_recalls,n_valid:",d["n_events"],d["n_firms"],d["n_firms_with_recalls"],d["n_valid_events"])
PYEOFtop-level keys OK: True | count 12 window_3day keys OK: True window_2day keys OK: True window_11day keys OK: True hac keys OK: True wls keys OK: True cal keys OK: True corrado keys OK: True cowan keys OK: True st keys OK: True | df= 3 ints are int: True n_events,n_firms,n_firms_with_recalls,n_valid: 35 18 14 35
cd /app && rm -rf outputs && time Rscript /app/analysis.R 2>&1 | tail -2 && echo "=== files present ===" && ls -1 outputs/ && echo "=== CSV tail ===" && tail -3 outputs/event_cars.csv && echo "=== rows (incl header) ===" && wc -l < outputs/event_cars.csv
Done. n_valid_events = 35 rho_bar = 0.0057 === files present === car_timeline.png event_cars.csv event_study_results.json portfolio_comparison.png research_memo.md === CSV tail === 33,"FIRM_013","2007-11-08",-0.526356372773951,-0.52373532134165,-0.308505132913324,-0.00467616980098902 34,"FIRM_017","2007-11-09",-2.56743883437834,-1.705510789566,-0.929888690726546,-0.026450830109875 35,"FIRM_011","2007-11-13",-2.71199846009415,-2.36135841115297,-1.190983990059,-0.0178536276477926 === rows (incl header) === 36 [stderr] real 0m2.646s user 0m2.130s sys 0m0.440s
cd /app && Rscript -e 'options(warn=1); source("/app/analysis.R")' 2>&1 | grep -i 'warn\|error' | head || echo "no warnings/errors"<system>Tool ran without output or errors</system>
cd /app && sed -n '1,120p' analysis.R
suppressPackageStartupMessages({
library(jsonlite)
library(ggplot2)
})
# =====================================================================
# Product-recall event study , deterministic, base-R implementation.
# No event-study packages, no randomness/bootstrapping.
# =====================================================================
EST_LEN <- 200 # estimation-window length (trading days)
EST_GAP <- 30 # gap between estimation window and event date
# =====================================================================
# 1. Load and clean
# - drop NA return / market_return
# - drop sentinel returns (return < -10)
# - drop duplicate rows
# - sort by (firm_id, date)
# =====================================================================
returns <- read.csv("/app/data/stock_returns.csv", stringsAsFactors = FALSE)
recalls <- read.csv("/app/data/recalls.csv", stringsAsFactors = FALSE)
firms <- read.csv("/app/data/firms.csv", stringsAsFactors = FALSE)
returns <- returns[!is.na(returns$return) & !is.na(returns$market_return), ]
returns <- returns[returns$return >= -10, ] # remove sentinel returns
returns <- returns[!duplicated(returns), ] # remove duplicate rows
returns$date <- as.Date(returns$date)
returns <- returns[order(returns$firm_id, returns$date), ]
rownames(returns) <- NULL
recalls <- recalls[!duplicated(recalls), ]
recalls$date <- as.Date(recalls$date)
firms <- firms[!duplicated(firms), ]
n_events <- nrow(recalls)
n_firms <- nrow(firms)
n_firms_with_recalls <- length(unique(recalls$firm_id))
# Global trading-day calendar (0-based index) shared by all firms.
all_dates <- sort(unique(returns$date))
n_all_dates <- length(all_dates)
date_to_idx <- setNames(seq_along(all_dates) - 1L, as.character(all_dates))
# Per-date market return (identical across firms on a given date).
mkt_by_date <- tapply(returns$market_return, as.character(returns$date), function(x) x[1])
# Split the panel into a list keyed by firm, each indexed by date string,
# for fast look-ups.
returns_by_firm <- split(returns, returns$firm_id)
firm_idx <- lapply(returns_by_firm, function(df) {
setNames(seq_len(nrow(df)), as.character(df$date))
})
# =====================================================================
# 2. Market model + prediction-error-corrected SARs / SCARs
# =====================================================================
# Fit market model on the 200-day estimation window ending 30 trading
# days before the event. Returns fitted parameters and the pieces needed
# for Patell prediction-error correction.
event_market_model <- function(fid, eidx) {
est_end <- eidx - EST_GAP - 1 # last estimation index
est_start <- est_end - EST_LEN + 1 # first estimation index (200 days)
if (est_start < 0) return(NULL)
est_dates <- all_dates[(est_start + 1):(est_end + 1)]
fdf <- returns_by_firm[[fid]]
if (is.null(fdf)) return(NULL)
sub <- fdf[fdf$date %in% est_dates, ]
if (nrow(sub) < 100) return(NULL)
m <- lm(return ~ market_return, data = sub)
cf <- coef(m)
s_i <- summary(m)$sigma # sqrt(SSE/(L-2)) , Patell base sd
mean_rm <- mean(sub$market_return)
list(alpha = unname(cf[1]), beta = unname(cf[2]),
s_i = s_i, n_est = nrow(sub),
mean_rm = mean_rm,
ssq_rm = sum((sub$market_return - mean_rm)^2))
}
# Prediction-error correction factor g_t for one event-window day.
g_factor <- function(m, rm_t) {
1 + 1 / m$n_est + (rm_t - m$mean_rm)^2 / m$ssq_rm
}
windows <- list(w3 = c(-1, 1), w2 = c(0, 1), w11 = c(-5, 5))
event_rows <- list()
for (i in seq_len(n_events)) {
fid <- recalls$firm_id[i]
edate <- recalls$date[i]
estr <- as.character(edate)
if (!(estr %in% names(date_to_idx))) next
eidx <- as.integer(date_to_idx[estr])
m <- event_market_model(fid, eidx)
if (is.null(m)) next
fdf <- returns_by_firm[[fid]]
fix <- firm_idx[[fid]]
scars <- list(w3 = NA_real_, w2 = NA_real_, w11 = NA_real_)
…[truncated]cd /app && echo "== headers ==" && grep '^##' outputs/research_memo.md && echo && echo "== keyword coverage ==" && for kw in "significan" "severity" "media" "firm size\|market cap" "calendar-time" "alpha" "Corrado" "Cowan" "non-parametric\|nonparametric" "regulator" "investor" "predictab"; do printf "%-28s %s\n" "$kw" "$(grep -ciE "$kw" outputs/research_memo.md)"; done
== headers == ## 1. Executive summary ## 2. Magnitude and statistical significance of abnormal returns ## 3. What drives the cross-sectional heterogeneity? ## 4. Calendar-time portfolio versus short-window results ## 5. Do the non-parametric tests agree? ## 6. Bottom line for regulators and investors == keyword coverage == significan 5 severity 1 media 3 firm size\|market cap 0 calendar-time 3 alpha 2 Corrado 1 Cowan 1 non-parametric\|nonparametric 0 regulator 3 investor 4 predictab 1
cd /app && echo "firm size: $(grep -ciE 'firm size|market cap' outputs/research_memo.md)"; echo "non-param: $(grep -ciE 'non-parametric|nonparametric' outputs/research_memo.md)"; echo "---context---"; grep -oiE 'firm size|log market cap|franchise value' outputs/research_memo.md | sort | uniq -c; grep -oiE 'non-parametric' outputs/research_memo.md | uniq -c
firm size: 1
non-param: 1
---context---
1 firm size
1 franchise value
1 log market cap
1 non-parametriccd / && rm -rf /app/outputs && Rscript /app/analysis.R && echo "=== EXIT CODE: $? ===" && echo "=== DELIVERABLES ===" && ls -la /app/outputs/
Done. n_valid_events = 35 rho_bar = 0.0057 === EXIT CODE: 0 === === DELIVERABLES === total 164 drwxr-sr-x 2 h2tagent h2tagent 4096 Jun 20 18:05 . drwxrws--- 4 h2tagent h2tagent 4096 Jun 20 18:05 .. -rw-r--r-- 1 h2tagent h2tagent 50695 Jun 20 18:05 car_timeline.png -rw-r--r-- 1 h2tagent h2tagent 3618 Jun 20 18:05 event_cars.csv -rw-r--r-- 1 h2tagent h2tagent 1839 Jun 20 18:05 event_study_results.json -rw-r--r-- 1 h2tagent h2tagent 93953 Jun 20 18:05 portfolio_comparison.png -rw-r--r-- 1 h2tagent h2tagent 7434 Jun 20 18:05 research_memo.md
No file changes, the agent left the workspace untouched.
# SyncVals normalized verifier summary # task: product-recall-stock-price-event # attempt: 10 status: completed raw_score: 0.0 # reward rule: binary: score 0.0 != 1.0 # => reward = 0.0 (FAIL) # # Note: these tasks score via an external hidden grader; no pytest/verifier # block is embedded in the agent transcript. The block below is the last # sizable tool-output captured in the run (supporting evidence). ---------------------------------------------------------------------- Done. n_valid_events = 35 rho_bar = 0.0057 === EXIT CODE: 0 === === DELIVERABLES === total 164 drwxr-sr-x 2 h2tagent h2tagent 4096 Jun 20 18:05 . drwxrws--- 4 h2tagent h2tagent 4096 Jun 20 18:05 .. -rw-r--r-- 1 h2tagent h2tagent 50695 Jun 20 18:05 car_timeline.png -rw-r--r-- 1 h2tagent h2tagent 3618 Jun 20 18:05 event_cars.csv -rw-r--r-- 1 h2tagent h2tagent 1839 Jun 20 18:05 event_study_results.json -rw-r--r-- 1 h2tagent h2tagent 93953 Jun 20 18:05 portfolio_comparison.png -rw-r--r-- 1 h2tagent h2tagent 7434 Jun 20 18:05 research_memo.md
Reproduce this trial: git checkout 2f94510 && PYTHONPATH=src python3 scripts/build_site.py , then open trial/trial_c2c284e5fbc14ae4. Re-running the agent live requires EVAL_PLATFORM_ENABLE_OAUTH_SMOKE=1 and is non-deterministic.
Trial trial_c2c284e5fbc14ae4 · verifier authoritative; classifier explanatory.