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/data && echo "=== firms.csv ===" && head -5 firms.csv && echo "..." && wc -l firms.csv && echo && echo "=== recalls.csv ===" && head -5 recalls.csv && echo "..." && wc -l recalls.csv && echo && echo "=== stock_returns.csv ===" && head -5 stock_returns.csv && echo "..." && wc -l stock_returns.csv
=== firms.csv === 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 firms.csv === recalls.csv === 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 recalls.csv === stock_returns.csv === 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 stock_returns.csv
cd /app/data && echo "=== recalls full ===" && cat recalls.csv && echo && echo "=== firms full ===" && cat firms.csv
=== recalls full === 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 5,FIRM_014,2007-04-24,chemical,67821,13,6.49 6,FIRM_013,2007-05-04,lead_paint,519027,3,5.0 7,FIRM_012,2007-05-08,lead_paint,558179,7,9.42 8,FIRM_014,2007-05-16,chemical,1629059,6,8.69 9,FIRM_001,2007-05-24,choking,11694380,7,3.9 10,FIRM_016,2007-05-30,laceration,1525113,6,5.9 11,FIRM_014,2007-05-31,choking,469277,13,5.41 12,FIRM_014,2007-06-13,choking,2022284,4,6.69 13,FIRM_008,2007-06-14,lead_paint,10627235,9,4.01 14,FIRM_015,2007-06-15,lead_paint,509523,13,3.2 15,FIRM_007,2007-06-20,lead_paint,562028,7,9.21 16,FIRM_001,2007-06-25,lead_paint,5091044,10,9.31 17,FIRM_017,2007-07-09,chemical,1579890,8,8.59 18,FIRM_005,2007-07-13,lead_paint,5817593,5,9.8 19,FIRM_002,2007-07-23,choking,2158016,13,7.7 20,FIRM_012,2007-08-09,laceration,6310122,7,7.88 21,FIRM_005,2007-08-10,laceration,466285,10,7.86 22,FIRM_016,2007-08-13,lead_paint,2163078,7,5.33 23,FIRM_008,2007-08-14,choking,1605804,9,5.11 24,FIRM_001,2007-08-21,burn,355518,10,3.16 25,FIRM_018,2007-08-27,choking,211071,6,6.8 26,FIRM_010,2007-09-04,laceration,6161271,9,3.6 27,FIRM_005,2007-09-07,chemical,1301127,6,3.32 28,FIRM_013,2007-09-18,laceration,475322,8,6.27 29,FIRM_010,2007-09-20,lead_paint,1906732,5,4.69 30,FIRM_001,2007-09-25,choking,11961437,11,2.97 31,FIRM_014,2007-09-27,lead_paint,71974,11,8.34 32,FIRM_008,2007-11-07,choking,233330,11,8.75 33,FIRM_013,2007-11-08,burn,551744,5,2.51 34,FIRM_017,2007-11-09,choking,19715949,7,1.07 35,FIRM_011,2007-11-13,lead_paint,3365377,12,4.03 === firms full === 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 FIRM_005,173.95,3944,1.4716,-0.000333,0.0235,3 FIRM_006,378.94,3944,1.3716,0.000584,0.018,0 FIRM_007,2107.82,3944,0.7672,-0.000741,0.0203,2 FIRM_008,1237.08,3942,1.2161,-6.7e-05,0.0162,3 FIRM_009,1771.95,3944,1.1583,0.000181,0.0155,0 FIRM_010,649.59,3942,1.2093,0.000393,0.0156,2 FIRM_011,5194.09,3942,1.5357,0.000456,0.0155,1 FIRM_012,4597.88,3944,1.2118,0.000497,0.0152,2 FIRM_013,1957.13,3944,1.4395,-7.4e-05,0.0219,4 FIRM_014,6993.17,3944,1.1338,-0.000131,0.0168,5 FIRM_015,3168.49,3942,1.2446,0.000529,0.0165,1 FIRM_016,644.74,3942,1.4156,4e-06,0.0187,2 FIRM_017,2814.39,3944,0.3714,-0.000538,0.0157,3 FIRM_018,572.12,3942,0.9402,-0.000467,0.0227,1
cd /app && cat -n analysis.R
1 suppressPackageStartupMessages({
2 library(jsonlite)
3 library(ggplot2)
4 })
5
6 EST_LEN <- 200
7 EST_GAP <- 30
8
9 # =====================================================================
10 # 1. Load and clean
11 # =====================================================================
12 returns <- read.csv("/app/data/stock_returns.csv")
13 recalls <- read.csv("/app/data/recalls.csv")
14 firms <- read.csv("/app/data/firms.csv")
15
16 returns <- returns[!is.na(returns$return) & !is.na(returns$market_return), ]
17 returns <- returns[!duplicated(returns), ]
18 returns$date <- as.Date(returns$date)
19 returns <- returns[order(returns$firm_id, returns$date), ]
20 rownames(returns) <- NULL
21
22 recalls <- recalls[!duplicated(recalls), ]
23 recalls$date <- as.Date(recalls$date)
24
25 firms <- firms[!duplicated(firms), ]
26
27 n_events <- nrow(recalls)
28 n_firms <- nrow(firms)
29 n_firms_with_recalls <- length(unique(recalls$firm_id))
30
31 all_dates <- sort(unique(returns$date))
32 date_to_idx <- setNames(seq_along(all_dates) - 1L, as.character(all_dates))
33
34 # =====================================================================
35 # 2. Market model , basic OLS, returns raw AR (no Patell standardization)
36 # =====================================================================
37 event_market_model <- function(fid, eidx) {
38 est_end <- eidx - EST_GAP - 1
39 est_start <- est_end - EST_LEN + 1
40 if (est_start < 0) return(NULL)
41 est_dates <- all_dates[(est_start + 1):(est_end + 1)]
42 sub <- returns[returns$firm_id == fid & returns$date %in% est_dates, ]
43 if (nrow(sub) < 100) return(NULL)
44 m <- lm(return ~ market_return, data = sub)
45 list(alpha = unname(coef(m)[1]), beta = unname(coef(m)[2]),
46 sigma_eps = sd(resid(m)), n_est = nrow(sub),
47 mean_rm = mean(sub$market_return),
48 sum_sq_dev_rm = sum((sub$market_return - mean(sub$market_return))^2))
49 }
50
51 windows <- list(w3 = c(-1, 1), w2 = c(0, 1), w11 = c(-5, 5))
52
53 event_rows <- list()
54 daily_long <- list()
55
56 for (i in seq_len(n_events)) {
57 fid <- recalls$firm_id[i]
58 edate <- recalls$date[i]
59 estr <- as.character(edate)
60 if (!(estr %in% names(date_to_idx))) next
61 eidx <- as.integer(date_to_idx[estr])
62
63 m <- event_market_model(fid, eidx)
64 if (is.null(m)) next
65
66 firm <- returns[returns$firm_id == fid, ]
67 rownames(firm) <- as.character(firm$date)
68
69 cars <- list(); ar_day0 <- NA_real_; valid_w3 <- TRUE
70 for (wname in names(windows)) {
71 w <- windows[[wname]]; ars <- numeric(0); ok <- TRUE
72 for (off in seq.int(w[1], w[2])) {
73 tidx <- eidx + off
74 if (tidx < 0 || tidx >= length(all_dates)) { ok <- FALSE; break }
75 target <- all_dates[tidx + 1]
76 if (!(as.character(target) %in% rownames(firm))) { ok <- FALSE; break }
77 rm_t <- firm[as.character(target), "market_return"]
78 ret_t <- firm[as.character(target), "return"]
79 ar <- ret_t - (m$alpha + m$beta * rm_t)
80 ars <- c(ars, ar)
81 if (wname == "w3" && off == 0) ar_day0 <- ar
82 }
83 if (!ok) {
84 if (wname == "w3") valid_w3 <- FALSE
85 cars[[wname]] <- NA_real_
86 } else {
87 # NOTE: simple summation of raw ARs , spec wants standardized SCAR
88 cars[[wname]] <- sum(ars)
89 }
90 }
91 if (!valid_w3 || is.na(cars$w3)) next
92
93 event_rows[[length(event_rows) + 1L]] <- data.frame(
94 event_id = as.integer(recalls$event_id[i]),
95 firm_id = fid,
96 date = estr,
97
…[truncated]cd /app && R --version | head -2 && echo "---" && Rscript -e 'for (p in c("jsonlite","ggplot2","sandwich","lmtest","MASS")) cat(p, ":", requireNamespace(p, quietly=TRUE), "\n")'R version 4.2.2 Patched (2022-11-10 r83330) -- "Innocent and Trusting" Copyright (C) 2022 The R Foundation for Statistical Computing --- jsonlite : TRUE ggplot2 : TRUE sandwich : FALSE lmtest : FALSE MASS : TRUE
cd /app && Rscript -e '
returns <- read.csv("/app/data/stock_returns.csv")
cat("raw rows:", nrow(returns), "\n")
cat("NA return:", sum(is.na(returns$return)), "\n")
cat("NA market_return:", sum(is.na(returns$market_return)), "\n")
cat("sentinel return < -10:", sum(returns$return < -10, na.rm=TRUE), "\n")
cat("dup rows (full):", sum(duplicated(returns)), "\n")
cat("unique firms:", length(unique(returns$firm_id)), "\n")
r2 <- returns[!is.na(returns$return) & !is.na(returns$market_return),]
r2 <- r2[r2$return >= -10,]
r2 <- r2[!duplicated(r2),]
cat("after clean:", nrow(r2), "\n")
r2$date <- as.Date(r2$date)
cat("date range:", as.character(min(r2$date)), "to", as.character(max(r2$date)), "\n")
# per-firm obs counts
tb <- table(r2$firm_id)
cat("per-firm obs: min", min(tb), "max", max(tb), "\n")
# duplicated firm-date?
cat("dup firm-date:", sum(duplicated(r2[,c("firm_id","date")])), "\n")
'raw rows: 9364 NA return: 0 NA market_return: 3 sentinel return < -10: 5 dup rows (full): 4 unique firms: 18 after clean: 9352 date range: 2006-01-03 to 2007-12-31 per-firm obs: min 518 max 520 dup firm-date: 0
cd /app && Rscript -e '
returns <- read.csv("/app/data/stock_returns.csv")
returns <- returns[!is.na(returns$return) & !is.na(returns$market_return),]
returns <- returns[returns$return >= -10,]
returns <- returns[!duplicated(returns),]
returns$date <- as.Date(returns$date)
returns <- returns[order(returns$firm_id, returns$date),]
# market return unique per date?
agg <- aggregate(market_return ~ date, data=returns, FUN=function(x) length(unique(round(x,8))))
cat("dates with >1 distinct market_return:", sum(agg$market_return>1), " of ", nrow(agg), "\n")
all_dates <- sort(unique(returns$date))
cat("n unique trading dates:", length(all_dates), "\n")
recalls <- read.csv("/app/data/recalls.csv"); recalls$date <- as.Date(recalls$date)
date_to_idx <- setNames(seq_along(all_dates)-1L, as.character(all_dates))
# check events eidx and whether estimation window fits
EST_LEN<-200; EST_GAP<-30
cnt_valid<-0
for (i in seq_len(nrow(recalls))){
estr<-as.character(recalls$date[i])
if(!(estr%in%names(date_to_idx))) {cat("event",i,"date not a trading day\n");next}
eidx<-as.integer(date_to_idx[estr])
est_end<-eidx-EST_GAP-1; est_start<-est_end-EST_LEN+1
fid<-recalls$firm_id[i]
if(est_start<0){cat("event",i,"est_start<0 eidx=",eidx,"\n");next}
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){cat("event",i,"nest=",nrow(sub),"<100\n");next}
cnt_valid<-cnt_valid+1
}
cat("events passing estimation filter:",cnt_valid,"of",nrow(recalls),"\n")
'dates with >1 distinct market_return: 0 of 520 n unique trading dates: 520 events passing estimation filter: 35 of 35
cd /app && Rscript -e '
returns <- read.csv("/app/data/stock_returns.csv")
returns <- returns[!is.na(returns$return) & !is.na(returns$market_return),]
returns <- returns[returns$return >= -10,]
returns <- returns[!duplicated(returns),]
returns$date <- as.Date(returns$date)
all_dates <- sort(unique(returns$date))
recalls <- read.csv("/app/data/recalls.csv"); recalls$date <- as.Date(recalls$date)
date_to_idx <- setNames(seq_along(all_dates)-1L, as.character(all_dates))
windows<-list(w3=c(-1,1),w2=c(0,1),w11=c(-5,5))
cw3<-0;cw2<-0;cw11<-0;call<-0
for(i in seq_len(nrow(recalls))){
fid<-recalls$firm_id[i];estr<-as.character(recalls$date[i])
eidx<-as.integer(date_to_idx[estr])
firm<-returns[returns$firm_id==fid,]; rn<-as.character(firm$date)
okall<-TRUE
for(wn in names(windows)){w<-windows[[wn]];ok<-TRUE
for(off in seq.int(w[1],w[2])){tidx<-eidx+off
if(tidx<0||tidx>=length(all_dates)){ok<-FALSE;break}
if(!(as.character(all_dates[tidx+1])%in%rn)){ok<-FALSE;break}}
if(wn=="w3"&&ok)cw3<-cw3+1
if(wn=="w2"&&ok)cw2<-cw2+1
if(wn=="w11"&&ok)cw11<-cw11+1
if(!ok)okall<-FALSE}
if(okall)call<-call+1
}
cat("complete w3:",cw3,"w2:",cw2,"w11:",cw11,"all three:",call,"of",nrow(recalls),"\n")
cat("eidx range:",min(date_to_idx[as.character(recalls$date)]),max(date_to_idx[as.character(recalls$date)]),"\n")
'complete w3: 35 w2: 35 w11: 35 all three: 35 of 35 eidx range: 316 485
cd /app && Rscript -e '
# sanity check: Patell prediction SE equals predict() se.fit for a held-out x
set.seed(1); n<-50
x<-rnorm(n); y<-0.3+1.2*x+rnorm(n)
m<-lm(y~x)
s<-summary(m)$sigma # sqrt(SSE/(n-2))
xb<-mean(x); ssx<-sum((x-xb)^2)
newx<-0.8
f<-1+1/n+(newx-xb)^2/ssx
pe_manual<-s*sqrt(f) # prediction error SE (new obs)
pr<-predict(m,newdata=data.frame(x=newx),se.fit=TRUE)
pe_pkg<-sqrt(pr$se.fit^2+s^2) # predict gives se of mean; add residual var for prediction
cat("manual pred SE:",pe_manual," predict-based:",pe_pkg,"\n")
cat("match:", isTRUE(all.equal(pe_manual,pe_pkg)),"\n")
'manual pred SE: 0.9948246 predict-based: 0.9948246 match: TRUE
cd /app && Rscript -e '
hac_se <- function(X, y, L){
n<-nrow(X);k<-ncol(X)
XtXi<-solve(crossprod(X)); beta<-XtXi%*%crossprod(X,y)
u<-as.numeric(y-X%*%beta)
S<-matrix(0,k,k); for(t in 1:n) S<-S+(u[t]^2)*tcrossprod(X[t,])
if(L>=1) for(l in 1:L){w<-1-l/(L+1);G<-matrix(0,k,k)
for(t in (l+1):n) G<-G+u[t]*u[t-l]*tcrossprod(X[t,],X[t-l,]); S<-S+w*(G+t(G))}
V<-XtXi%*%S%*%XtXi; list(beta=as.numeric(beta),se=sqrt(diag(V)))
}
set.seed(7);n<-40
x1<-rnorm(n);x2<-rnorm(n);y<-1+0.5*x1-0.3*x2+rnorm(n)*(1+abs(x1))
X<-cbind(1,x1,x2)
h<-hac_se(X,y,0)
# manual HC0
m<-lm(y~x1+x2);u<-resid(m);XtXi<-solve(crossprod(X))
hc0<-XtXi%*%(t(X)%*%diag(u^2)%*%X)%*%XtXi
cat("HAC L=0 se:",round(h$se,6),"\n")
cat("HC0 se :",round(sqrt(diag(hc0)),6),"\n")
cat("beta match lm:", isTRUE(all.equal(h$beta, unname(coef(m)))),"\n")
'HAC L=0 se: 0.240605 0.305565 0.267325 HC0 se : 0.240605 0.305565 0.267325 beta match lm: TRUE
command: create
path: /app/analysis.R
file_text: suppressPackageStartupMessages({
library(jsonlite)
library(ggplot2)
})
# =====================================================================
# Modern event-study pipeline for product-recall announcements.
# Deterministic; all statistics implemented with base/statistical primitives.
# =====================================================================
EST_LEN <- 200 # estimation-window length (trading days)
EST_GAP <- 30 # gap between estimation window end and the event day
# =====================================================================
# 1. Load and clean
# - drop NAs, sentinel returns (return < -10) and 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), ]
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 used for all window arithmetic)
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))
# Fast per-firm lookup: firm_id -> data.frame rownamed by date string
firm_tables <- lapply(split(returns, returns$firm_id), function(df) {
rownames(df) <- as.character(df$date); df
})
# =====================================================================
# 2. Market model + prediction-error-corrected SARs / SCARs
# Estimation window: EST_LEN days ending EST_GAP+1 days before the event.
# Standardization (Patell 1976): each forecast AR is scaled by its
# prediction standard error s * sqrt(1 + 1/L + (Rm - Rm_bar)^2 / SS_rm),
# where s = sqrt(SSE/(L-2)) is the estimation residual standard error.
# =====================================================================
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)]
ft <- firm_tables[[fid]]
if (is.null(ft)) return(NULL)
sub <- ft[as.character(est_dates), ]
sub <- sub[!is.na(sub$return) & !is.na(sub$market_return), ]
if (nrow(sub) < 100) return(NULL)
m <- lm(return ~ market_return, data = sub)
res <- resid(m)
list(alpha = unname(coef(m)[1]), beta = unname(coef(m)[2]),
sigma_eps = summary(m)$sigma, # sqrt(SSE/(L-2))
n_est = nrow(sub),
mean_rm = mean(sub$market_return),
sum_sq_dev_rm = sum((sub$market_return - mean(sub$market_return))^2),
p_hat = mean(res > 0)) # Cowan estimation-window p_hat
}
windows <- list(w3 = c(-1, 1), w2 = c(0, 1), w11 = c(-5, 5))
event_rows <- list()
per_event <- list() # keeps daily AR/returns for timeline, Corrado, Cowan
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 <- firm_tables[[fid]]
# --- standardized cumulative abnormal returns for each window ---
scars <- list(); ar_day0 <- NA_real_; valid_w3 <- TRUE; ar_w3 <- NULL
for (wname in names(windows)) {
w <- windows[[wname
…[truncated]Existing file was successfully overwritten at /app/analysis.R
cd /app && rm -rf outputs && mkdir -p outputs && time Rscript analysis.R
Done. Valid events: 35 | rho_bar: 0.0057 | calendar days: 230 | HAC lag: 3 [stderr] real 0m2.746s user 0m2.190s sys 0m0.430s
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 && echo "=== event_cars.csv (head) ===" && head -6 outputs/event_cars.csv && echo "..." && wc -l outputs/event_cars.csv && echo && echo "=== HAC vs OLS SE comparison ===" && Rscript -e '
source_lines <- readLines("analysis.R")
suppressMessages(library(jsonlite))
j <- fromJSON("outputs/event_study_results.json")
# recompute OLS se for reference
ev <- read.csv("outputs/event_cars.csv")
rc <- read.csv("data/recalls.csv"); fm <- read.csv("data/firms.csv")
cs <- merge(ev, rc[,c("event_id","units_recalled","media_articles","severity_score")],by="event_id")
cs <- merge(cs, fm[,c("firm_id","market_cap_mm","idio_vol")],by="firm_id")
cs$log_units<-log(cs$units_recalled); cs$log_mcap<-log(cs$market_cap_mm)
m<-lm(scar_3day~log_units+media_articles+severity_score+log_mcap,data=cs)
cat("OLS se:",round(summary(m)$coefficients[,2],4),"\n")
cat("HAC se:",round(c(j$cross_section_hac$se_intercept,j$cross_section_hac$se_log_units,j$cross_section_hac$se_media,j$cross_section_hac$se_severity,j$cross_section_hac$se_log_mcap),4),"\n")
cat("coef match:", all(abs(coef(m)-c(j$cross_section_hac$intercept,j$cross_section_hac$log_units,j$cross_section_hac$media,j$cross_section_hac$severity,j$cross_section_hac$log_mcap))<1e-6),"\n")
'=== event_cars.csv (head) === "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 === HAC vs OLS SE comparison === OLS se: 2.192 0.1171 0.0599 0.0707 0.168 HAC se: 1.99 0.1438 0.0453 0.0797 0.1092 coef match: TRUE
cd /app && Rscript -e '
returns <- read.csv("data/stock_returns.csv")
returns <- returns[!is.na(returns$return)&!is.na(returns$market_return),]
returns <- returns[returns$return>=-10,]; returns<-returns[!duplicated(returns),]
returns$date<-as.Date(returns$date)
ft<-lapply(split(returns,returns$firm_id),function(d){rownames(d)<-as.character(d$date);d})
rb<-list()
for(f in names(ft)){x<-ft[[f]];x<-x[!is.na(x$return)&!is.na(x$market_return),];if(nrow(x)<30)next;mm<-lm(return~market_return,data=x);rb[[f]]<-setNames(resid(mm),as.character(x$date))}
cat("firms in corr step:",length(rb),"\n")
ids<-names(rb);pc<-c();npair<-0
for(a in 1:(length(ids)-1))for(b in (a+1):length(ids)){ra<-rb[[ids[a]]];rc<-rb[[ids[b]]];cm<-intersect(names(ra),names(rc));if(length(cm)<20)next;pc<-c(pc,cor(ra[cm],rc[cm]));npair<-npair+1}
cat("surviving pairs:",npair," (C(18,2)=",choose(18,2),")\n")
cat("rho_bar:",round(mean(pc),6),"\n")
'firms in corr step: 18 surviving pairs: 153 (C(18,2)= 153 ) rho_bar: 0.005682
command: view path: /app/outputs/car_timeline.png
<output_image> …[truncated]
cd /app && Rscript -e '
library(png); img<-readPNG("outputs/car_timeline.png")
cat("dims:",dim(img),"\n")
# check corner pixel (top-left) alpha/color
cat("top-left RGBA:",img[1,1,],"\n")
cat("has alpha channel:",dim(img)[3]==4,"\n")
if(dim(img)[3]==4) cat("min alpha:",min(img[,,4]),"max alpha:",max(img[,,4]),"\n")
' 2>/dev/null || echo "png pkg not available, using file inspection"png pkg not available, using file inspection
cd /app && python3 -c "
from PIL import Image
im = Image.open('outputs/car_timeline.png')
print('mode:', im.mode, 'size:', im.size)
print('top-left pixel:', im.getpixel((0,0)))
print('center-top pixel:', im.getpixel((im.size[0]//2, 10)))
" 2>/dev/null || echo "PIL not available"PIL not available
command: str_replace
path: /app/analysis.R
old_str: x = "Trading days relative to recall announcement (day 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 (day 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: theme_minimal(base_size = 12) +
theme(legend.position = "bottom")
ggsave("/app/outputs/portfolio_comparison.png", p2, width = 10, height = 5, dpi = 120)
new_str: 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: p3 <- 2 * (1 - pnorm(abs(agg_3$patell_z))) new_str: dir_word <- function(b) if (b < 0) "more negative (amplifies the loss)" else if (b > 0) "less negative (dampens the loss)" else "unchanged" p3 <- 2 * (1 - pnorm(abs(agg_3$patell_z)))
The file /app/analysis.R has been edited successfully.
command: str_replace path: /app/analysis.R old_str: fmt(cs_hac$r_squared, 3), ". The economic intuition is that more severe hazards, ", "larger recalled quantities, and heavier media coverage all raise the expected ", "direct and indirect costs of a recall (remediation, litigation, lost brand equity), ", "so they should push abnormal returns more negative; firm size can buffer the impact ", "because larger firms absorb fixed recall costs over a bigger revenue base. The ", "weighted least squares specification (weights = 1/idiosyncratic-variance) yields ", new_str: fmt(cs_hac$r_squared, 3), ". In this sample the estimated effects are: a larger ", "recall scale (log units) makes the reaction ", dir_word(cs_hac$log_units), "; heavier media coverage makes it ", dir_word(cs_hac$media), "; a higher severity ", "score makes it ", dir_word(cs_hac$severity), "; and larger firm size (log market cap) ", "makes it ", dir_word(cs_hac$log_mcap), ". The economic intuition is that more severe ", "hazards, larger recalled quantities, and heavier media coverage tend to raise the ", "expected direct and indirect costs of a recall (remediation, litigation, lost brand ", "equity), pushing abnormal returns more negative, while firm size proxies for ", "diversification and the ability to absorb fixed recall costs over a larger revenue ", "base; the sign and size of each channel should be read directly from the coefficients ", "above, which dominate any prior. The ", "weighted least squares specification (weights = 1/idiosyncratic-variance) yields ",
The file /app/analysis.R has been edited successfully.
cd /app && rm -rf outputs && mkdir -p outputs && Rscript analysis.R && echo "--- FILES ---" && ls -la outputs/ && echo "--- MEMO WORD COUNT ---" && wc -w outputs/research_memo.md
Done. Valid events: 35 | rho_bar: 0.0057 | calendar days: 230 | HAC lag: 3 --- FILES --- total 152 drwxr-sr-x 2 h2tagent h2tagent 4096 Jun 20 18:03 . drwxrws--- 4 h2tagent h2tagent 4096 Jun 20 18:03 .. -rw-r--r-- 1 h2tagent h2tagent 45820 Jun 20 18:03 car_timeline.png -rw-r--r-- 1 h2tagent h2tagent 3619 Jun 20 18:03 event_cars.csv -rw-r--r-- 1 h2tagent h2tagent 1839 Jun 20 18:03 event_study_results.json -rw-r--r-- 1 h2tagent h2tagent 88426 Jun 20 18:03 portfolio_comparison.png -rw-r--r-- 1 h2tagent h2tagent 5646 Jun 20 18:03 research_memo.md --- MEMO WORD COUNT --- 823 outputs/research_memo.md
command: view path: /app/outputs/car_timeline.png
<output_image> …[truncated]
command: view path: /app/outputs/portfolio_comparison.png
<output_image> …[truncated]
cd /app && cat outputs/research_memo.md
# Research Memo: Stock-Market Impact of Toy-Industry Product Recalls ## Executive summary We conducted a modern event study of 35 product-recall announcements across 14 toy manufacturers (18 firms in the estimation universe). Using a market model estimated over a 200-trading-day window ending 30 days before each announcement, we measured standardized abnormal returns (SCARs) in three event windows and corroborated them with parametric and non-parametric tests, a cross-sectional analysis of what drives the reaction, and a calendar-time portfolio. ## 1. Magnitude and significance of the announcement reaction The average three-day [-1,+1] standardized CAR is -1.0116, with 82.9% of events negative. The Patell Z is -5.985 (two-sided p = 0.0000), the cross-sectionally robust BMP t is -4.992, and the Kolari-Pynnonen t (which also corrects for cross-event return correlation, estimated here as rho_bar = 0.0057) is -4.570. The reaction is therefore highly statistically significant (p < 0.01). The two-day [0,+1] window (mean SCAR -0.8622, Patell Z -5.101) and the wider eleven-day [-5,+5] window (mean SCAR -0.4507, Patell Z -2.667) tell a consistent story about the sign and persistence of the price response. A negative mean SCAR indicates that, on average, recall announcements destroy shareholder value in a narrow window around the disclosure, consistent with markets capitalizing expected remediation costs, liability, and reputational damage. ## 2. What drives the cross-sectional heterogeneity Regressing the three-day SCAR on recall and firm characteristics (HAC/Newey-West standard errors, automatic lag L = 3) gives: log(units recalled) -0.2772 (SE 0.1438), media articles -0.1400 (SE 0.0453), severity score -0.0494 (SE 0.0797), and log(market cap) -0.3497 (SE 0.1092); model R-squared = 0.366. In this sample the estimated effects are: a larger recall scale (log units) makes the reaction more negative (amplifies the loss); heavier media coverage makes it more negative (amplifies the loss); a higher severity score makes it more negative (amplifies the loss); and larger firm size (log market cap) makes it more negative (amplifies the loss). The economic intuition is that more severe hazards, larger recalled quantities, and heavier media coverage tend to raise the expected direct and indirect costs of a recall (remediation, litigation, lost brand equity), pushing abnormal returns more negative, while firm size proxies for diversification and the ability to absorb fixed recall costs over a larger revenue base; the sign and size of each channel should be read directly from the coefficients above, which dominate any prior. The weighted least squares specification (weights = 1/idiosyncratic-variance) yields qualitatively similar slopes (e.g., severity -0.0689, log-units -0.2453, R-squared 0.308), indicating the findings are not driven by a few high-variance firms. ## 3. Calendar-time portfolio vs. short-window results The calendar-time portfolio (equal-weighted across firms within +/-30 trading days of a recall, 230 portfolio-days) has a daily alpha of -0.000858 (t = -1.346, p = 0.1797) and a market beta of 1.033. The calendar-time alpha is not statistically significant. Because the calendar-time approach spreads the one-time announcement shock across a long holding window and naturally accounts for cross-sectional correlation among simultaneously-recalling firms, a small or insignificant alpha alongside a significant short-window effect is the expected and economically sensible pattern: the value impact is concentrated in the days around the announcement rather than representing a persistent, tradeable drift. This is the classic Fama (1998) observation that long-horizon abnormal performance often shrinks once returns are measured in calendar time. ## 4. Do the non-parametric tests corroborate the parametric ones? The Corrado (1989) rank test yields z = -2.427 (p = 0.0152), and the Cowan (1992) generalized sign test yields z = 0.254 (p = …[truncated]
command: str_replace path: /app/analysis.R old_str: "fraction of events with a positive AR in the 3-day window = ", fmt(cowan$frac_positive, 3), "). These distribution-free tests do not rely on normality and are robust to the fat ", "tails and event-induced variance typical of daily stock returns. When they agree in ", "direction and significance with the Patell/BMP statistics, we can be confident the ", "parametric results are not an artifact of non-normal returns or a few outliers; any ", "divergence would instead point to skewness or heavy tails in the abnormal-return ", "distribution. The Schipper-Thompson (1983) joint Wald test (statistic ", new_str: "fraction of events with a positive AR in the 3-day window = ", fmt(cowan$frac_positive, 3), "). These distribution-free tests do not rely on normality and are robust to the fat ", "tails and event-induced variance typical of daily stock returns. Here, the Corrado rank ", "test is ", sig_word(corrado$p_value), " and (with z = ", fmt(corrado$z_stat, 2), ") points in the ", if (corrado$z_stat < 0) "same negative direction as" else "opposite direction to", " the parametric Patell/BMP results, which reinforces confidence that the measured ", "price reaction is not an artifact of non-normal returns or a few outliers. The Cowan ", "generalized-sign statistic is ", sig_word(cowan$p_value), "; this is expected given its ", "construction (it compares the fraction of events with at least one positive daily ", "abnormal return in the 3-day window against the roughly one-half baseline positivity ", "rate p_bar), which makes it a deliberately conservative directional test rather than a ", "magnitude test. Taken together, a strongly significant rank test alongside a muted sign ", "test indicates the reaction is driven by the size of abnormal returns (heavy left-tail ", "moves) more than by a simple majority of firms turning negative. The Schipper-Thompson ", "(1983) joint Wald test (statistic ",
[stderr] No replacement was performed, old_str `"fraction of events with a positive AR in the 3-day window = ", fmt(cowan$frac_positive, 3), "). These distribution-free tests do not rely on normality and are robust to the fat ", "tails and event-induced variance typical of daily stock returns. When they agree in ", "direction and significance with the Patell/BMP statistics, we can be confident the ", "parametric results are not an artifact of non-normal returns or a few outliers; any ", "divergence would instead point to skewness or heavy tails in the abnormal-return ", "distribution. The Schipper-Thompson (1983) joint Wald test (statistic ",` did not appear verbatim in /app/analysis.R.
cd /app && grep -n "fraction of events with a positive AR" analysis.R
545:", fraction of events with a positive AR in the 3-day window = ", fmt(cowan$frac_positive, 3),
cd /app && sed -n '544,552p' analysis.R
fmt(cowan$z_stat, 3), " (p = ", fmt(cowan$p_value, 4), "; p_bar = ", fmt(cowan$p_bar, 3), ", fraction of events with a positive AR in the 3-day window = ", fmt(cowan$frac_positive, 3), "). These distribution-free tests do not rely on normality and are robust to the fat ", "tails and event-induced variance typical of daily stock returns. When they agree in ", "direction and significance with the Patell/BMP statistics, we can be confident the ", "parametric results are not an artifact of non-normal returns or a few outliers; any ", "divergence would instead point to skewness or heavy tails in the abnormal-return ", "distribution. The Schipper-Thompson (1983) joint Wald test (statistic ", fmt(schipper$wald_stat, 3), ", df = 3, p = ", fmt(schipper$wald_p, 4), ") evaluates all ",
command: str_replace path: /app/analysis.R old_str: "). These distribution-free tests do not rely on normality and are robust to the fat ", "tails and event-induced variance typical of daily stock returns. When they agree in ", "direction and significance with the Patell/BMP statistics, we can be confident the ", "parametric results are not an artifact of non-normal returns or a few outliers; any ", "divergence would instead point to skewness or heavy tails in the abnormal-return ", "distribution. The Schipper-Thompson (1983) joint Wald test (statistic ", new_str: "). These distribution-free tests do not rely on normality and are robust to the fat ", "tails and event-induced variance typical of daily stock returns. Here, the Corrado rank ", "test is ", sig_word(corrado$p_value), " and (with z = ", fmt(corrado$z_stat, 2), ") points in the ", if (corrado$z_stat < 0) "same negative direction as" else "opposite direction to", " the parametric Patell/BMP results, which reinforces confidence that the measured ", "price reaction is not an artifact of non-normal returns or a few outliers. The Cowan ", "generalized-sign statistic is ", sig_word(cowan$p_value), "; this is expected given its ", "construction (it compares the fraction of events with at least one positive daily ", "abnormal return in the 3-day window against the roughly one-half baseline positivity ", "rate p_bar), making it a deliberately conservative directional test rather than a ", "magnitude test. A strongly significant rank test alongside a muted sign test indicates ", "the reaction is driven more by the size of abnormal returns (heavy left-tail moves) than ", "by a simple majority of firms turning negative. The Schipper-Thompson (1983) joint Wald test (statistic ",
The file /app/analysis.R has been edited successfully.
cd /app && rm -rf outputs && mkdir -p outputs && Rscript analysis.R && echo "--- words:" && wc -w outputs/research_memo.md && echo "--- section 4 ---" && sed -n '/## 4/,/## 5/p' outputs/research_memo.md
Done. Valid events: 35 | rho_bar: 0.0057 | calendar days: 230 | HAC lag: 3 --- words: 912 outputs/research_memo.md --- section 4 --- ## 4. Do the non-parametric tests corroborate the parametric ones? The Corrado (1989) rank test yields z = -2.427 (p = 0.0152), and the Cowan (1992) generalized sign test yields z = 0.254 (p = 0.7998; p_bar = 0.493, fraction of events with a positive AR in the 3-day window = 0.514). These distribution-free tests do not rely on normality and are robust to the fat tails and event-induced variance typical of daily stock returns. Here, the Corrado rank test is statistically significant (p < 0.05) and (with z = -2.43) points in the same negative direction as the parametric Patell/BMP results, which reinforces confidence that the measured price reaction is not an artifact of non-normal returns or a few outliers. The Cowan generalized-sign statistic is not statistically significant; this is expected given its construction (it compares the fraction of events with at least one positive daily abnormal return in the 3-day window against the roughly one-half baseline positivity rate p_bar), making it a deliberately conservative directional test rather than a magnitude test. A strongly significant rank test alongside a muted sign test indicates the reaction is driven more by the size of abnormal returns (heavy left-tail moves) than by a simple majority of firms turning negative. The Schipper-Thompson (1983) joint Wald test (statistic 25.629, df = 3, p = 0.0000) evaluates all three windows simultaneously and guards against cherry-picking a single window. ## 5. Practical conclusion for regulators and investors
cd /app && Rscript -e '
suppressMessages(library(jsonlite))
j <- fromJSON("outputs/event_study_results.json")
ev <- read.csv("outputs/event_cars.csv")
# independent aggregate recompute for 3-day
s <- ev$scar_3day; N<-length(s)
rho <- j$rho_bar
cat("=== window_3day independent vs json ===\n")
cat("patell:", round(sum(s)/sqrt(N),6), "vs", j$aggregate_scars$window_3day$patell_z,"\n")
cat("bmp :", round(mean(s)/(sd(s)/sqrt(N)),6), "vs", j$aggregate_scars$window_3day$bmp_t,"\n")
cat("kp :", round(mean(s)/sqrt(var(s)*(1+(N-1)*rho)/N),6), "vs", j$aggregate_scars$window_3day$kp_t,"\n")
cat("pctneg:", round(mean(s<0),6), "vs", j$aggregate_scars$window_3day$pct_negative,"\n")
# Schipper-Thompson independent
M <- as.matrix(ev[,c("scar_3day","scar_2day","scar_11day")])
mu<-colMeans(M); Sig<-cov(M); W<-as.numeric(nrow(M)*t(mu)%*%solve(Sig)%*%mu)
cat("=== schipper ===\n")
cat("wald:", round(W,6),"vs",j$schipper_thompson$wald_stat," p:",round(1-pchisq(W,3),6),"vs",j$schipper_thompson$wald_p,"\n")
'=== window_3day independent vs json === patell: -5.984886 vs -5.984886 bmp : -4.992138 vs -4.992138 kp : -4.570168 vs -4.570158 pctneg: 0.828571 vs 0.828571 === schipper === wald: 25.62887 vs 25.62887 p: 1.1e-05 vs 1.1e-05
cd /app && Rscript -e '
suppressMessages(library(jsonlite)); j<-fromJSON("outputs/event_study_results.json")
returns<-read.csv("data/stock_returns.csv")
returns<-returns[!is.na(returns$return)&!is.na(returns$market_return),]
returns<-returns[returns$return>=-10,]; returns<-returns[!duplicated(returns),]
returns$date<-as.Date(returns$date); returns<-returns[order(returns$firm_id,returns$date),]
recalls<-read.csv("data/recalls.csv"); recalls$date<-as.Date(recalls$date)
ad<-sort(unique(returns$date)); d2i<-setNames(seq_along(ad)-1L,as.character(ad))
ft<-lapply(split(returns,returns$firm_id),function(d){rownames(d)<-as.character(d$date);d})
# calendar-time, independent
recalls$eidx<-as.integer(d2i[as.character(recalls$date)])
fei<-split(recalls$eidx,recalls$firm_id)
pr<-c();pm<-c()
for(d in 0:(length(ad)-1)){
act<-names(fei)[sapply(fei,function(e)any(abs(d-e)<=30))]
if(length(act)==0)next
ds<-as.character(ad[d+1]); rr<-c();mk<-NA
for(f in act){x<-ft[[f]];if(ds%in%rownames(x)){rr<-c(rr,x[ds,"return"]);mk<-x[ds,"market_return"]}}
if(length(rr)==0)next
pr<-c(pr,mean(rr));pm<-c(pm,mk)
}
m<-lm(pr~pm);sm<-summary(m)
cat("calendar alpha:",round(coef(m)[1],6),"vs",j$calendar_time$alpha,"\n")
cat("calendar beta :",round(coef(m)[2],6),"vs",j$calendar_time$beta,"\n")
cat("t_alpha:",round(sm$coefficients[1,3],6),"vs",j$calendar_time$t_alpha,"\n")
cat("p_value:",round(sm$coefficients[1,4],6),"vs",j$calendar_time$p_value,"\n")
cat("n_days :",length(pr),"vs",j$calendar_time$n_days,"\n")
# Corrado independent
K<-c()
for(i in seq_len(nrow(recalls))){
f<-recalls$firm_id[i];ei<-recalls$eidx[i];x<-ft[[f]]
offs<--30:30;rv<-c();ov<-c()
for(o in offs){ti<-ei+o;if(ti<0||ti>=length(ad))next;ds<-as.character(ad[ti+1]);if(ds%in%rownames(x)){rv<-c(rv,x[ds,"return"]);ov<-c(ov,o)}}
if(length(rv)<2)next
rk<-rank(rv);mr<-(length(rk)+1)/2;sr<-sd(rk);sel<-which(ov%in%c(-1,0,1))
if(length(sel)==0)next
K<-c(K,mean((rk[sel]-mr)/sr))
}
z<-mean(K)/(sd(K)/sqrt(length(K)))
cat("corrado z:",round(z,6),"vs",j$corrado_rank$z_stat," p:",round(2*(1-pnorm(abs(z))),6),"vs",j$corrado_rank$p_value,"\n")
'calendar alpha: -0.000858 vs -0.000858 calendar beta : 1.033203 vs 1.033203 t_alpha: -1.34566 vs -1.34566 p_value: 0.179749 vs 0.179749 n_days : 230 vs 230 corrado z: -2.426518 vs -2.426518 p: 0.015244 vs 0.015244
cd /app && Rscript -e '
suppressMessages(library(jsonlite)); j<-fromJSON("outputs/event_study_results.json")
ev<-read.csv("outputs/event_cars.csv")
rc<-read.csv("data/recalls.csv"); fm<-read.csv("data/firms.csv")
cs<-merge(ev,rc[,c("event_id","units_recalled","media_articles","severity_score")],by="event_id")
cs<-merge(cs,fm[,c("firm_id","market_cap_mm","idio_vol")],by="firm_id")
cs$log_units<-log(cs$units_recalled);cs$log_mcap<-log(cs$market_cap_mm)
cs<-cs[order(cs$event_id),]
X<-cbind(1,cs$log_units,cs$media_articles,cs$severity_score,cs$log_mcap)
y<-cs$scar_3day; w<-1/cs$idio_vol^2; W<-diag(w); n<-nrow(X);k<-ncol(X)
XtWXi<-solve(t(X)%*%W%*%X); b<-XtWXi%*%t(X)%*%W%*%y
res<-as.numeric(y-X%*%b); s2<-sum(w*res^2)/(n-k)
se<-sqrt(diag(s2*XtWXi))
# weighted R2 (centered, weighted mean)
ybar_w<-sum(w*y)/sum(w); sst<-sum(w*(y-ybar_w)^2); ssr<-sum(w*res^2); r2<-1-ssr/sst
cat("WLS manual coef:",round(as.numeric(b),6),"\n")
cat("WLS json coef :",round(c(j$cross_section_wls$intercept,j$cross_section_wls$log_units,j$cross_section_wls$media,j$cross_section_wls$severity,j$cross_section_wls$log_mcap),6),"\n")
cat("WLS manual se :",round(se,6),"\n")
cat("WLS json se :",round(c(j$cross_section_wls$se_intercept,j$cross_section_wls$se_log_units,j$cross_section_wls$se_media,j$cross_section_wls$se_severity,j$cross_section_wls$se_log_mcap),6),"\n")
cat("WLS manual R2 :",round(r2,6)," json:",j$cross_section_wls$r_squared,"\n")
'WLS manual coef: 6.181414 -0.245301 -0.142987 -0.068882 -0.301826 WLS json coef : 6.181414 -0.245301 -0.142987 -0.068882 -0.301826 WLS manual se : 2.373976 0.119944 0.063018 0.074244 0.192047 WLS json se : 2.373976 0.119944 0.063018 0.074244 0.192047 WLS manual R2 : 0.307908 json: 0.307908
cd /app && Rscript -e '
suppressMessages(library(jsonlite)); j<-fromJSON("outputs/event_study_results.json")
returns<-read.csv("data/stock_returns.csv")
returns<-returns[!is.na(returns$return)&!is.na(returns$market_return),]
returns<-returns[returns$return>=-10,]; returns<-returns[!duplicated(returns),]
returns$date<-as.Date(returns$date); returns<-returns[order(returns$firm_id,returns$date),]
recalls<-read.csv("data/recalls.csv"); recalls$date<-as.Date(recalls$date)
ad<-sort(unique(returns$date)); d2i<-setNames(seq_along(ad)-1L,as.character(ad))
ft<-lapply(split(returns,returns$firm_id),function(d){rownames(d)<-as.character(d$date);d})
EST_LEN<-200;EST_GAP<-30
ph<-c();ind<-c(); scar1<-NA
for(i in seq_len(nrow(recalls))){
f<-recalls$firm_id[i];ei<-as.integer(d2i[as.character(recalls$date[i])])
ee<-ei-EST_GAP-1;es<-ee-EST_LEN+1;if(es<0)next
edt<-ad[(es+1):(ee+1)];x<-ft[[f]];sub<-x[as.character(edt),];sub<-sub[!is.na(sub$return),]
if(nrow(sub)<100)next
m<-lm(return~market_return,data=sub);a<-coef(m)[1];bb<-coef(m)[2];s<-summary(m)$sigma
rmb<-mean(sub$market_return);ssx<-sum((sub$market_return-rmb)^2);L<-nrow(sub)
if(L>=50){ph<-c(ph,mean(resid(m)>0))}
# 3-day ARs
ar3<-c()
for(o in -1:1){ti<-ei+o;ds<-as.character(ad[ti+1]);if(!(ds%in%rownames(x))){ar3<-NULL;break};ar3<-c(ar3,x[ds,"return"]-(a+bb*x[ds,"market_return"]))}
if(!is.null(ar3))ind<-c(ind,as.integer(any(ar3>0)))
# SCAR 3-day for event 1 hand-check
if(recalls$event_id[i]==1 && !is.null(ar3)){
fs<-0;for(o in -1:1){ds<-as.character(ad[ei+o+1]);rmt<-x[ds,"market_return"];fs<-fs+(1+1/L+(rmt-rmb)^2/ssx)}
scar1<-sum(ar3)/(s*sqrt(fs))
}
}
Nc<-length(ind);pbar<-mean(ph);fp<-mean(ind);z<-(fp-pbar)/sqrt(pbar*(1-pbar)/Nc)
cat("cowan p_bar:",round(pbar,6),"vs",j$cowan_sign$p_bar,"\n")
cat("cowan frac :",round(fp,6),"vs",j$cowan_sign$frac_positive,"\n")
cat("cowan z :",round(z,6),"vs",j$cowan_sign$z_stat," p:",round(2*(1-pnorm(abs(z))),6),"vs",j$cowan_sign$p_value,"\n")
ev<-read.csv("outputs/event_cars.csv")
cat("SCAR event1 handcalc:",round(scar1,6)," vs csv:",round(ev$scar_3day[ev$event_id==1],6),"\n")
'cowan p_bar: 0.492859 vs 0.492859 cowan frac : 0.514286 vs 0.514286 cowan z : 0.253547 vs 0.253547 p: 0.799846 vs 0.799846 SCAR event1 handcalc: -2.773854 vs csv: -2.773854
command: str_replace
path: /app/analysis.R
old_str: cal_fit <- lm(port_ret ~ market_return, data = cal_df)
cal_sum <- summary(cal_fit)
calendar <- list(
alpha = unname(coef(cal_fit)[1]),
beta = unname(coef(cal_fit)[2]),
t_alpha = unname(cal_sum$coefficients[1, "t value"]),
p_value = unname(cal_sum$coefficients[1, "Pr(>|t|)"]),
n_days = as.integer(n_days)
)
new_str: if (n_days >= 3) {
cal_fit <- lm(port_ret ~ market_return, data = cal_df)
cal_sum <- summary(cal_fit)
calendar <- list(
alpha = unname(coef(cal_fit)[1]),
beta = unname(coef(cal_fit)[2]),
t_alpha = unname(cal_sum$coefficients[1, "t value"]),
p_value = unname(cal_sum$coefficients[1, "Pr(>|t|)"]),
n_days = as.integer(n_days)
)
} else {
calendar <- list(alpha = 0.0, beta = 1.0, t_alpha = 0.0,
p_value = 1.0, n_days = as.integer(n_days))
}The file /app/analysis.R has been edited successfully.
cd /app && rm -rf outputs && mkdir -p outputs && Rscript analysis.R && echo "=== JSON schema validation ===" && Rscript -e '
suppressMessages(library(jsonlite)); j<-fromJSON("outputs/event_study_results.json")
top<-c("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")
cat("top-level ok:", all(top%in%names(j)) && all(names(j)%in%top),"\n")
win<-c("mean_scar","patell_z","bmp_t","kp_t","pct_negative","n")
for(w in c("window_3day","window_2day","window_11day")) cat(w,"ok:",all(win%in%names(j$aggregate_scars[[w]])),"\n")
hac<-c("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")
cat("hac keys ok:",all(hac%in%names(j$cross_section_hac)),"\n")
cat("wls keys ok:",all(setdiff(hac,"hac_lag")%in%names(j$cross_section_wls)),"\n")
cat("calendar keys ok:",all(c("alpha","beta","t_alpha","p_value","n_days")%in%names(j$calendar_time)),"\n")
cat("corrado keys ok:",all(c("z_stat","p_value")%in%names(j$corrado_rank)),"\n")
cat("cowan keys ok:",all(c("z_stat","p_value","p_bar","frac_positive")%in%names(j$cowan_sign)),"\n")
cat("schipper keys ok:",all(c("wald_stat","wald_p","df")%in%names(j$schipper_thompson)),"df=",j$schipper_thompson$df,"\n")
# types
cat("n_events is int:", j$n_events==as.integer(j$n_events),"\n")
cat("CSV header:",paste(names(read.csv("outputs/event_cars.csv")),collapse=","),"\n")
' && echo "=== all output files ===" && ls -la outputs/Done. Valid events: 35 | rho_bar: 0.0057 | calendar days: 230 | HAC lag: 3 === JSON schema validation === top-level ok: TRUE window_3day ok: TRUE window_2day ok: TRUE window_11day ok: TRUE hac keys ok: TRUE wls keys ok: TRUE calendar keys ok: TRUE corrado keys ok: TRUE cowan keys ok: TRUE schipper keys ok: TRUE df= 3 n_events is int: TRUE CSV header: event_id,firm_id,date,scar_3day,scar_2day,scar_11day,ar_day0 === all output files === total 152 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 45820 Jun 20 18:05 car_timeline.png -rw-r--r-- 1 h2tagent h2tagent 3619 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 88426 Jun 20 18:05 portfolio_comparison.png -rw-r--r-- 1 h2tagent h2tagent 6222 Jun 20 18:05 research_memo.md
command: str_replace
path: /app/analysis.R
old_str: # =====================================================================
# 7. Corrado (1989) non-parametric rank test on RAW returns over [-30,+30]
# =====================================================================
K_vals <- c()
for (pe in per_event) {
ok <- !is.na(pe$rets)
if (sum(ok) < 2) next
rr <- pe$rets[ok]; oo <- pe$offs[ok]
rk <- rank(rr) # average ranks for ties (R default)
nrk <- length(rk)
mean_rank <- (nrk + 1) / 2
sd_rank <- sd(rk) # ddof = 1
if (!is.finite(sd_rank) || sd_rank == 0) next
sel <- which(oo %in% c(-1L, 0L, 1L))
if (length(sel) == 0) next
K_vals <- c(K_vals, mean((rk[sel] - mean_rank) / sd_rank))
}
new_str: # =====================================================================
# 7. Corrado (1989) non-parametric rank test on RAW returns over [-30,+30]
# Uses raw returns only (no market model), so it runs over ALL recall
# events with a valid trading-day index -- not only surviving events.
# =====================================================================
K_vals <- c()
for (i in seq_len(n_events)) {
estr <- as.character(recalls$date[i])
if (!(estr %in% names(date_to_idx))) next
eidx <- as.integer(date_to_idx[estr])
firm <- firm_tables[[recalls$firm_id[i]]]
if (is.null(firm)) next
rr <- c(); oo <- c()
for (off in seq.int(-30L, 30L)) {
tidx <- eidx + off
if (tidx < 0 || tidx >= n_all_dates) next
target <- as.character(all_dates[tidx + 1])
if (!(target %in% rownames(firm))) next
rr <- c(rr, firm[target, "return"]); oo <- c(oo, off)
}
if (length(rr) < 2) next
rk <- rank(rr) # average ranks for ties (R default)
nrk <- length(rk)
mean_rank <- (nrk + 1) / 2
sd_rank <- sd(rk) # ddof = 1
if (!is.finite(sd_rank) || sd_rank == 0) next
sel <- which(oo %in% c(-1L, 0L, 1L))
if (length(sel) == 0) next
K_vals <- c(K_vals, mean((rk[sel] - mean_rank) / sd_rank))
}The file /app/analysis.R has been edited successfully.
cd /app && rm -rf outputs && mkdir -p outputs && Rscript analysis.R && Rscript -e '
suppressMessages(library(jsonlite)); j<-fromJSON("outputs/event_study_results.json")
cat("corrado z:",j$corrado_rank$z_stat," p:",j$corrado_rank$p_value,"\n")
cat("cowan z:",j$cowan_sign$z_stat," frac:",j$cowan_sign$frac_positive,"\n")
cat("n_valid_events:",j$n_valid_events,"\n")'Done. Valid events: 35 | rho_bar: 0.0057 | calendar days: 230 | HAC lag: 3 corrado z: -2.426518 p: 0.015244 cowan z: 0.253547 frac: 0.514286 n_valid_events: 35
cd /app && sed -n '1,130p' analysis.R
suppressPackageStartupMessages({
library(jsonlite)
library(ggplot2)
})
# =====================================================================
# Modern event-study pipeline for product-recall announcements.
# Deterministic; all statistics implemented with base/statistical primitives.
# =====================================================================
EST_LEN <- 200 # estimation-window length (trading days)
EST_GAP <- 30 # gap between estimation window end and the event day
# =====================================================================
# 1. Load and clean
# - drop NAs, sentinel returns (return < -10) and 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), ]
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 used for all window arithmetic)
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))
# Fast per-firm lookup: firm_id -> data.frame rownamed by date string
firm_tables <- lapply(split(returns, returns$firm_id), function(df) {
rownames(df) <- as.character(df$date); df
})
# =====================================================================
# 2. Market model + prediction-error-corrected SARs / SCARs
# Estimation window: EST_LEN days ending EST_GAP+1 days before the event.
# Standardization (Patell 1976): each forecast AR is scaled by its
# prediction standard error s * sqrt(1 + 1/L + (Rm - Rm_bar)^2 / SS_rm),
# where s = sqrt(SSE/(L-2)) is the estimation residual standard error.
# =====================================================================
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)]
ft <- firm_tables[[fid]]
if (is.null(ft)) return(NULL)
sub <- ft[as.character(est_dates), ]
sub <- sub[!is.na(sub$return) & !is.na(sub$market_return), ]
if (nrow(sub) < 100) return(NULL)
m <- lm(return ~ market_return, data = sub)
res <- resid(m)
list(alpha = unname(coef(m)[1]), beta = unname(coef(m)[2]),
sigma_eps = summary(m)$sigma, # sqrt(SSE/(L-2))
n_est = nrow(sub),
mean_rm = mean(sub$market_return),
sum_sq_dev_rm = sum((sub$market_return - mean(sub$market_return))^2),
p_hat = mean(res > 0)) # Cowan estimation-window p_hat
}
windows <- list(w3 = c(-1, 1), w2 = c(0, 1), w11 = c(-5, 5))
event_rows <- list()
per_event <- list() # keeps daily AR/returns for timeline, Corrado, Cowan
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 <- firm_tables[[fid]]
# --- standardized cumulative abnormal returns for each window ---
scars <- list(); ar_day0 <- NA_real_; valid_w3 <- TRUE; ar_w3 <- NULL
for (wname in names(windows)) {
w <- windows[[wname]]; ars <- numeric(0); fsum <- 0; ok <- TRUE
…[truncated]cd /app && rm -rf outputs && mkdir -p outputs && Rscript analysis.R 2>warnings.txt; echo "EXIT CODE: $?"; echo "--- warnings/stderr (if any) ---"; cat warnings.txt; rm -f warnings.txt; echo "--- final outputs ---"; ls outputs/
Done. Valid events: 35 | rho_bar: 0.0057 | calendar days: 230 | HAC lag: 3 EXIT CODE: 0 --- warnings/stderr (if any) --- --- final outputs --- car_timeline.png event_cars.csv event_study_results.json portfolio_comparison.png research_memo.md
cd /app && echo "Any value with >6 decimal places?" && grep -oE '[0-9]+\.[0-9]{7,}' outputs/event_study_results.json || echo "NONE - all values clean (<=6 decimals)" && echo && echo "=== full final JSON ===" && cat outputs/event_study_results.jsonAny value with >6 decimal places?
NONE - all values clean (<=6 decimals)
=== full final 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 && for f in event_study_results.json event_cars.csv car_timeline.png portfolio_comparison.png research_memo.md; do if [ -s "outputs/$f" ]; then printf "OK %-28s %6d bytes\n" "$f" "$(wc -c < outputs/$f)"; else printf "MISSING %s\n" "$f"; fi; done
OK event_study_results.json 1839 bytes OK event_cars.csv 3619 bytes OK car_timeline.png 45820 bytes OK portfolio_comparison.png 88426 bytes OK research_memo.md 6222 bytes
No file changes, the agent left the workspace untouched.
# SyncVals normalized verifier summary # task: product-recall-stock-price-event # attempt: 5 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). ---------------------------------------------------------------------- OK event_study_results.json 1839 bytes OK event_cars.csv 3619 bytes OK car_timeline.png 45820 bytes OK portfolio_comparison.png 88426 bytes OK research_memo.md 6222 bytes
Reproduce this trial: git checkout 2f94510 && PYTHONPATH=src python3 scripts/build_site.py , then open trial/trial_f2651a5c669648ae. Re-running the agent live requires EVAL_PLATFORM_ENABLE_OAUTH_SMOKE=1 and is non-deterministic.
Trial trial_f2651a5c669648ae · verifier authoritative; classifier explanatory.