Depositor Runs = f(Fundamentals, Liquidity Mismatch)
Where: - Fundamentals = Mark-to-Market (MTM) Losses on securities - Captures bank soundness / solvency risk - Higher MTM loss ? worse fundamentals ? higher run probability
Exclusions: - Failed banks (SVB, Signature, First Republic, Silvergate) - G-SIB banks (33 entities across periods) - Banks without OMO-eligible securities
Rationale: Focus on BTFP-eligible population
H1 (Extensive Margin): Banks with higher run risk (various set up using mtm loss, adjusted equity, and uninsured leverage by quartile) are more likely to access emergency facilities.
H2 (Facility Choice): - BTFP attracts banks with OMO-eligible collateral and MTM losses (Par Valuation utilization motive) - DW used by banks with urgent liquidity needs regardless of collateral type
Research Question: Did BTFP stem deposit runs for eligible banks?
Design:
# ==============================================================================
# SETUP: Load all required packages
# ==============================================================================
rm(list = ls())
# Core data manipulation
library(data.table)
library(dplyr)
library(tidyr)
library(stringr)
library(lubridate)
library(purrr)
library(tibble)
# Econometrics
library(fixest)
library(marginaleffects)
library(nnet)
library(broom)
# Tables and output
library(modelsummary)
library(knitr)
library(kableExtra)
#library(gt)
# Statistics
library(DescTools)
# Visualization
library(ggplot2)
library(gridExtra)
library(scales)
library(patchwork)
# File I/O
library(readr)
library(readxl)
cat("All packages loaded.\n")
## All packages loaded.
# ==============================================================================
# HELPER FUNCTIONS: Summary Statistics
# ==============================================================================
library(simplermarkdown)
library(tidyr)
library(data.table)
library(DescTools) # For Winsorize function
summary_stats_function <- function(df, column_list, group_by = NULL) {
# Select the specified columns from call_chg
summary_stat_df <- df %>%
select(all_of(column_list)) %>%
data.table
# If group_by is provided, group by the specified column, else no grouping
if (!is.null(group_by)) {
summary_stats <- summary_stat_df %>%
group_by(across(all_of(group_by))) %>%
summarise(across(everything(), list(
Obs = ~ length(.),
Mean = ~ round(mean(., na.rm = TRUE), 2),
SD = ~ round(sd(., na.rm = TRUE), 2),
P10 = ~ round(quantile(., 0.1, na.rm = TRUE), 2),
P25 = ~ round(quantile(., 0.25, na.rm = TRUE), 2),
P50 = ~ round(quantile(., 0.5, na.rm = TRUE), 2),
P75 = ~ round(quantile(., 0.75, na.rm = TRUE), 2),
P90 = ~ round(quantile(., 0.9, na.rm = TRUE), 2)
), .names = "{col}__{fn}")) %>%
pivot_longer(cols = everything(), names_to = c("Variable", ".value"), names_sep = "__")
} else {
summary_stats <- summary_stat_df %>%
summarise(across(everything(), list(
Obs = ~ length(.),
Mean = ~ round(mean(., na.rm = TRUE), 2),
SD = ~ round(sd(., na.rm = TRUE), 2),
P10 = ~ round(quantile(., 0.1, na.rm = TRUE), 2),
P25 = ~ round(quantile(., 0.25, na.rm = TRUE), 2),
P50 = ~ round(quantile(., 0.5, na.rm = TRUE), 2),
P75 = ~ round(quantile(., 0.75, na.rm = TRUE), 2),
P90 = ~ round(quantile(., 0.9, na.rm = TRUE), 2)
), .names = "{col}__{fn}")) %>%
pivot_longer(cols = everything(), names_to = c("Variable", ".value"), names_sep = "__")
}
# Return the summary statistics
return(summary_stats)
}
mean_by_group <- function(df, columns_to_include, group_var, win_probs = c(0, 1)) {
# Ensure the df is a data.table
df <- as.data.table(df)
setnames(df, group_var, "group_var")
# Calculate the number of observations for each group
count_table <- df[, .(N = .N), by = group_var]
# Filter data and calculate the winsorized mean for the specified columns
result_table <- df[, lapply(.SD, function(x) list(winsorized_Mean = round(mean(
Winsorize(x, quantile(x, probs = win_probs, na.rm = TRUE)), na.rm = TRUE), 3))),
by = group_var, .SDcols = columns_to_include]
# Merge the count table with the result table
result_table <- merge(count_table, result_table, by = "group_var")
# Sort by group_var
setorder(result_table, group_var)
# Transpose the table
transposed_result_table <- t(result_table)
# Set column names to be the first row, and remove the first row
colnames(transposed_result_table) <- unlist(transposed_result_table[1, ])
transposed_result_table <- transposed_result_table[-1, ]
# Add a first column for 'N' and the column names from 'columns_to_include'
first_column <- c("N", columns_to_include)
transposed_result_table <- cbind(first_column, transposed_result_table)
# Convert the transposed table to a data frame
transposed_result_table <- as.data.table(transposed_result_table)
# Convert the columns to numeric where applicable
numeric_cols <- names(transposed_result_table)[sapply(transposed_result_table, is.numeric)]
transposed_result_table[, (numeric_cols) := lapply(.SD, round, digits = 2), .SDcols = numeric_cols]
return(transposed_result_table)
}
# ==============================================================================
# HELPER FUNCTIONS
# ==============================================================================
# Winsorization function (2.5% / 97.5% by default)
winsorize <- function(x, probs = c(0.025, 0.975)) {
if (all(is.na(x))) return(x)
q <- quantile(x, probs = probs, na.rm = TRUE, names = FALSE)
pmax(pmin(x, q[2]), q[1])
}
# Z-score standardization function
standardize_z <- function(x) {
if (all(is.na(x))) return(x)
(x - mean(x, na.rm = TRUE)) / sd(x, na.rm = TRUE)
}
# Safe division (avoid division by zero)
safe_div <- function(num, denom, default = NA_real_) {
ifelse(is.na(denom) | denom == 0, default, num / denom)
}
# Size category function (3 categories)
create_size_category_3 <- function(assets_thousands) {
assets_millions <- assets_thousands / 1000
case_when(
assets_millions >= 100000 ~ "Large (>$100B)",
assets_millions >= 1000 ~ "Medium ($1B-$100B)",
TRUE ~ "Small (<$1B)"
)
}
size_levels_3 <- c("Small (<$1B)", "Medium ($1B-$100B)", "Large (>$100B)")
# Format P-Value with Stars
format_pval <- function(p) {
case_when(
is.na(p) ~ "",
p < 0.01 ~ "***",
p < 0.05 ~ "**",
p < 0.10 ~ "*",
TRUE ~ ""
)
}
# ==============================================================================
# SAFE FILE-WRITE WRAPPER (OneDrive resilience)
# ==============================================================================
safe_writeLines <- function(text, con, max_retries = 5, wait_sec = 2) {
for (i in seq_len(max_retries)) {
result <- tryCatch({
writeLines(text, con)
TRUE
}, error = function(e) {
if (i < max_retries) {
message(" Retry ", i, "/", max_retries, " for ", basename(con), ": ", e$message)
Sys.sleep(wait_sec)
}
FALSE
})
if (isTRUE(result)) return(invisible(NULL))
}
warning("Failed to write ", con, " after ", max_retries, " attempts.")
}
# ==============================================================================
# OUTPUT FUNCTIONS: Simple table saving
# ==============================================================================
# Save data frame to LaTeX only
save_table <- function(tbl, filename, caption_text = "") {
# LaTeX version only
latex_file <- file.path(TABLE_PATH, paste0(filename, ".tex"))
latex_content <- knitr::kable(tbl, format = "latex", caption = caption_text, booktabs = TRUE)
safe_writeLines(as.character(latex_content), latex_file)
message("Saved: ", filename, ".tex")
}
# Save modelsummary regression table to LaTeX only
save_reg_table <- function(models, filename, title_text = "", notes_text = "",
coef_map_use = NULL, gof_map_use = NULL, add_rows_use = NULL, ...) {
tryCatch({
# LaTeX version only
modelsummary(models, stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
coef_map = coef_map_use, gof_map = gof_map_use, add_rows = add_rows_use,
title = title_text, notes = notes_text,
output = file.path(TABLE_PATH, paste0(filename, ".tex")), ...)
message("Saved: ", filename, ".tex")
}, error = function(e) {
message("Warning: Could not save ", filename, ": ", e$message)
})
}
# Save simple (non-regression) LaTeX table with consistent formatting
save_simple_latex_table <- function(df, filename, title_text = "", notes_text = "",
col_names = NULL, digits = 2) {
# Use kable to generate tabular content
if (!is.null(col_names)) {
names(df) <- col_names
}
tabular_content <- df %>%
kable(format = "latex", booktabs = TRUE, digits = digits, escape = FALSE) %>%
as.character()
# Build complete LaTeX document
latex_complete <- paste0(
"\\begin{table}[htbp]\n",
"\\centering\n",
"\\begin{threeparttable}\n",
"\\caption{", title_text, "}\n",
"\\vspace{0.5em}\n",
"\\small\n",
tabular_content, "\n",
"\\begin{tablenotes}[flushleft]\n",
"\\footnotesize\n",
"\\item \\textit{Notes:} ", notes_text, "\n",
"\\end{tablenotes}\n",
"\\end{threeparttable}\n",
"\\end{table}"
)
safe_writeLines(latex_complete, file.path(TABLE_PATH, paste0(filename, ".tex")))
message("Saved: ", filename, ".tex")
}
# Save ggplot figure to PDF and PNG
save_figure <- function(plot_obj, filename, width = 12, height = 8) {
ggsave(file.path(FIG_PATH, paste0(filename, ".pdf")), plot = plot_obj,
width = width, height = height, device = "pdf")
message("Saved: ", filename, ".pdf")
}
# Aliases for run_4spec_models and create_n_rows (used in later sections)
run_4spec_models <- function(data, dv, family_type = c("lpm","logit")) {
family_type <- match.arg(family_type)
forms <- list(
"(1) Base" = build_formula(dv, "mtm_total + uninsured_lev + mtm_x_uninsured"),
"(2) +Risk1" = build_formula(dv, "run_risk_1"),
"(3) +Risk1,2" = build_formula(dv, "run_risk_1 + run_risk_2"),
"(4) Risk2,3,4" = build_formula(dv, "run_risk_2 + run_risk_3 + run_risk_4")
)
if (family_type == "lpm") {
lapply(forms, function(ff) feols(ff, data = data, vcov = "hetero"))
} else {
lapply(forms, function(ff) feglm(ff, data = data, family = binomial("logit"), vcov = "hetero"))
}
}
create_n_rows <- function(data, dv, n_models = 4) {
n_ones <- sum(data[[dv]] == 1, na.rm = TRUE)
n_sample <- nrow(data)
out <- data.frame(term = c(paste0("N (", dv, "=1)"), "N (Sample)"))
for (i in 1:n_models) out[[paste0("(", i, ")")]] <- c(n_ones, n_sample)
out
}
# ==============================================================================
# PATHS AND KEY DATES
# ==============================================================================
BASE_PATH <- "C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025"
DATA_PROC <- file.path(BASE_PATH, "01_data/processed")
OUTPUT_PATH <- file.path(BASE_PATH, "03_documentation/crisis_borrowing_result_all")
TABLE_PATH <- file.path(OUTPUT_PATH, "tables")
FIG_PATH <- file.path(OUTPUT_PATH, "figures")
for (path in c(TABLE_PATH, FIG_PATH)) {
if (!dir.exists(path)) dir.create(path, recursive = TRUE)
}
# Baseline periods for different analyses
BASELINE_MAIN <- "2022Q4" # For Acute, Post-Acute
BASELINE_ARB <- "2023Q3" # For Arbitrage
BASELINE_WIND <- "2023Q4" # For Wind-down
# Key dates
DATE_MAR01 <- as.Date("2023-03-01")
DATE_MAR09 <- as.Date("2023-03-09")
DATE_MAR10 <- as.Date("2023-03-10")
DATE_MAR12 <- as.Date("2023-03-12")
DATE_MAR13 <- as.Date("2023-03-13")
DATE_MAR14 <- as.Date("2023-03-14")
# Period definitions
ACUTE_START <- as.Date("2023-03-13")
ACUTE_END <- as.Date("2023-05-01")
POST_ACUTE_END <- as.Date("2023-10-31")
ARB_START <- as.Date("2023-11-01")
ARB_END <- as.Date("2024-01-24")
WIND_START <- as.Date("2024-01-25")
WIND_END <- as.Date("2024-03-11")
DW_DATA_END <- as.Date("2023-12-31")
OVERALL_START <- as.Date("2023-03-01")
OVERALL_END <- as.Date("2024-03-11")
# ==============================================================================
# LOAD DATA
# ==============================================================================
call_q <- read_csv(file.path(DATA_PROC, "final_call_gsib.csv"), show_col_types = FALSE) %>%
mutate(idrssd = as.character(idrssd))
btfp_loans_raw <- read_csv(file.path(DATA_PROC, "btfp_loan_bank_only.csv"), show_col_types = FALSE) %>%
mutate(rssd_id = as.character(rssd_id), btfp_loan_date = mdy(btfp_loan_date))
dw_loans_raw <- read_csv(file.path(DATA_PROC, "dw_loan_bank_2023.csv"), show_col_types = FALSE) %>%
mutate(rssd_id = as.character(rssd_id), dw_loan_date = ymd(dw_loan_date))
cat("=== DATA LOADED ===\n")
## === DATA LOADED ===
cat("Call Report:", nrow(call_q), "obs |", n_distinct(call_q$idrssd), "banks\n")
## Call Report: 75989 obs | 5074 banks
cat("BTFP Loans:", nrow(btfp_loans_raw), "| DW Loans:", nrow(dw_loans_raw), "\n")
## BTFP Loans: 6734 | DW Loans: 10008
# ==============================================================================
# EXCLUDE FAILED BANKS AND G-SIBs
# ==============================================================================
excluded_banks <- call_q %>%
filter(period == BASELINE_MAIN, failed_bank == 1 | gsib == 1) %>%
pull(idrssd)
cat("Excluded banks (failed + G-SIBs):", length(excluded_banks), "\n")
## Excluded banks (failed + G-SIBs): 41
btfp_loans <- btfp_loans_raw %>% filter(!rssd_id %in% excluded_banks)
dw_loans <- dw_loans_raw %>% filter(!rssd_id %in% excluded_banks)
# ==============================================================================
# HELPER FUNCTION: Create Borrower Indicator
# ==============================================================================
create_borrower_indicator <- function(loans_df, date_col, id_col, amount_col,
start_date, end_date, prefix) {
loans_df %>%
filter(!!sym(date_col) >= start_date, !!sym(date_col) <= end_date) %>%
group_by(!!sym(id_col)) %>%
summarise(
"{prefix}" := 1L,
"{prefix}_amt" := sum(!!sym(amount_col), na.rm = TRUE),
"{prefix}_first" := min(!!sym(date_col)),
.groups = "drop"
) %>%
rename(idrssd = !!sym(id_col))
}
# ==============================================================================
# BTFP BORROWERS BY PERIOD
# ==============================================================================
btfp_mar10 <- create_borrower_indicator(
btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount",
DATE_MAR10, DATE_MAR10, "btfp_mar10"
)
btfp_mar10_13 <- create_borrower_indicator(
btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount",
DATE_MAR10, DATE_MAR13, "btfp_mar10_13"
)
btfp_acute <- create_borrower_indicator(
btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount",
ACUTE_START, ACUTE_END, "btfp_acute"
)
btfp_post <- create_borrower_indicator(
btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount",
ACUTE_END + 1, POST_ACUTE_END, "btfp_post"
)
btfp_arb <- create_borrower_indicator(
btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount",
ARB_START, ARB_END, "btfp_arb"
)
btfp_wind <- create_borrower_indicator(
btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount",
WIND_START, WIND_END, "btfp_wind"
)
btfp_overall <- create_borrower_indicator(
btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount",
OVERALL_START, OVERALL_END, "btfp_overall"
)
# ==============================================================================
# DW BORROWERS BY PERIOD
# ==============================================================================
dw_prebtfp <- create_borrower_indicator(
dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount",
DATE_MAR01, DATE_MAR12, "dw_prebtfp"
)
dw_mar10 <- create_borrower_indicator(
dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount",
DATE_MAR10, DATE_MAR10, "dw_mar10"
)
dw_mar10_13 <- create_borrower_indicator(
dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount",
DATE_MAR10, DATE_MAR13, "dw_mar10_13"
)
dw_acute <- create_borrower_indicator(
dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount",
ACUTE_START, min(ACUTE_END, DW_DATA_END), "dw_acute"
)
dw_post <- create_borrower_indicator(
dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount",
ACUTE_END + 1, min(POST_ACUTE_END, DW_DATA_END), "dw_post"
)
dw_overall <- create_borrower_indicator(
dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount",
OVERALL_START, DW_DATA_END, "dw_overall"
)
cat("\n=== BORROWER COUNTS BY PERIOD ===\n")
##
## === BORROWER COUNTS BY PERIOD ===
cat("BTFP: Mar10=", nrow(btfp_mar10), "| Mar10-13=", nrow(btfp_mar10_13),
"| Acute=", nrow(btfp_acute), "| Post=", nrow(btfp_post),
"| Arb=", nrow(btfp_arb), "| Wind=", nrow(btfp_wind), "\n")
## BTFP: Mar10= 0 | Mar10-13= 3 | Acute= 485 | Post= 811 | Arb= 797 | Wind= 237
cat("DW: Pre=", nrow(dw_prebtfp), "| Mar10=", nrow(dw_mar10),
"| Mar10-13=", nrow(dw_mar10_13), "| Acute=", nrow(dw_acute), "\n")
## DW: Pre= 106 | Mar10= 50 | Mar10-13= 94 | Acute= 417
# ==============================================================================
# FUNCTION: Construct Analysis Variables
# ==============================================================================
construct_analysis_vars <- function(baseline_data) {
baseline_data %>%
mutate(
# ========================================================================
# RAW VARIABLES (original scale)
# ========================================================================
mtm_total_raw = mtm_loss_to_total_asset,
mtm_btfp_raw = mtm_loss_omo_eligible_to_total_asset,
mtm_other_raw = mtm_loss_non_omo_eligible_to_total_asset,
uninsured_lev_raw = uninsured_deposit_to_total_asset,
uninsured_share_raw = uninsured_to_deposit,
mm_asset = mm_asset,
ln_assets_raw = log(total_asset),
cash_ratio_raw = cash_to_total_asset,
securities_ratio_raw = security_to_total_asset,
loan_ratio_raw = total_loan_to_total_asset,
book_equity_ratio_raw = book_equity_to_total_asset,
tier1_ratio_raw = tier1cap_to_total_asset,
roa_raw = roa,
fhlb_ratio_raw = fhlb_to_total_asset,
loan_to_deposit_raw = loan_to_deposit,
wholesale_raw = safe_div(
fed_fund_purchase + repo + replace_na(other_borrowed_less_than_1yr, 0),
total_liability, 0
) * 100,
# Deposit outflows
uninsured_outflow_raw = change_uninsured_fwd_q,
insured_outflow_raw = change_insured_deposit_fwd_q,
total_outflow_raw = change_total_deposit_fwd_q,
# Jiang et al. insolvency measures
adjusted_equity_raw = book_equity_to_total_asset - mtm_loss_to_total_asset,
mv_adjustment_raw = if_else(mm_asset == 0 | is.na(mm_asset), NA_real_, (total_asset / mm_asset) - 1),
idcr_1_raw = safe_div(mm_asset - 0.5 * uninsured_deposit - insured_deposit, insured_deposit),
idcr_2_raw = safe_div(mm_asset - 1.0 * uninsured_deposit - insured_deposit, insured_deposit),
insolvency_1_raw = safe_div((total_asset - total_liability) - 0.5 * uninsured_deposit * mv_adjustment_raw, total_asset),
insolvency_2_raw = safe_div((total_asset - total_liability) - 1.0 * uninsured_deposit * mv_adjustment_raw, total_asset),
# ========================================================================
# WINSORIZED VARIABLES (for outlier control)
# ========================================================================
mtm_total_w = winsorize(mtm_total_raw),
mtm_btfp_w = winsorize(mtm_btfp_raw),
mtm_other_w = winsorize(mtm_other_raw),
uninsured_lev_w = winsorize(uninsured_lev_raw),
uninsured_share_w = winsorize(uninsured_share_raw),
ln_assets_w = winsorize(ln_assets_raw),
cash_ratio_w = winsorize(cash_ratio_raw),
securities_ratio_w = winsorize(securities_ratio_raw),
loan_ratio_w = winsorize(loan_ratio_raw),
book_equity_ratio_w = winsorize(book_equity_ratio_raw),
tier1_ratio_w = winsorize(tier1_ratio_raw),
roa_w = winsorize(roa_raw),
fhlb_ratio_w = winsorize(fhlb_ratio_raw),
loan_to_deposit_w = winsorize(loan_to_deposit_raw),
wholesale_w = winsorize(wholesale_raw),
# ========================================================================
# Z-SCORE STANDARDIZED VARIABLES (for economic interpretation)
# ========================================================================
mtm_total = standardize_z(mtm_total_w),
mtm_btfp = standardize_z(mtm_btfp_w),
mtm_other = standardize_z(mtm_other_w),
uninsured_lev = standardize_z(uninsured_lev_w),
uninsured_share = standardize_z(uninsured_share_w),
# Interaction term (standardized)
mtm_x_uninsured = mtm_total * uninsured_lev,
# Controls (standardized)
ln_assets = standardize_z(ln_assets_w),
cash_ratio = standardize_z(cash_ratio_w),
securities_ratio = standardize_z(securities_ratio_w),
loan_ratio = standardize_z(loan_ratio_w),
book_equity_ratio = standardize_z(book_equity_ratio_w),
tier1_ratio = standardize_z(tier1_ratio_w),
roa = standardize_z(roa_w),
fhlb_ratio = standardize_z(fhlb_ratio_w),
loan_to_deposit = standardize_z(loan_to_deposit_w),
wholesale = standardize_z(wholesale_w),
# Deposit outflows
uninsured_outflow = standardize_z(winsorize(uninsured_outflow_raw)),
insured_outflow = standardize_z(winsorize(insured_outflow_raw)),
total_outflow = standardize_z(winsorize(total_outflow_raw)),
# Jiang et al. measures (winsorized)
adjusted_equity_w = winsorize(adjusted_equity_raw), # Winsorized for quartile calculations
adjusted_equity = standardize_z(winsorize(adjusted_equity_raw)), # Z-standardized for regressions
mv_adjustment = winsorize(mv_adjustment_raw),
idcr_1 = winsorize(idcr_1_raw),
idcr_2 = winsorize(idcr_2_raw),
insolvency_1 = winsorize(insolvency_1_raw),
insolvency_2 = winsorize(insolvency_2_raw),
# Insolvency indicators (use winsorized values for binary cutoffs)
mtm_insolvent = as.integer(adjusted_equity_w < 0),
insolvent_idcr_s50 = as.integer(idcr_1 < 0),
insolvent_idcr_s100 = as.integer(idcr_2 < 0),
insolvent_cap_s50 = as.integer(insolvency_1 < 0),
insolvent_cap_s100 = as.integer(insolvency_2 < 0),
# High/Low indicators
high_uninsured = as.integer(uninsured_lev > median(uninsured_lev, na.rm = TRUE)),
high_mtm_loss = as.integer(mtm_total > median(mtm_total, na.rm = TRUE)),
# Size category
size_cat = factor(create_size_category_3(total_asset), levels = size_levels_3),
# State for clustering
state = if("state" %in% names(.)) state else NA_character_,
fed_district = if("fed_district" %in% names(.)) fed_district else NA_character_
)
}
# ==============================================================================
# FUNCTION: Add Run Risk Dummies (based on medians)
# ==============================================================================
add_run_risk_dummies <- function(data) {
medians <- data %>%
summarise(
median_mtm = median(mtm_total_w, na.rm = TRUE),
median_uninsured = median(uninsured_lev_w, na.rm = TRUE)
)
# ============================================================================
# QUARTILE CUTOFFS for all key variables
# ============================================================================
quartiles <- data %>%
summarise(
# Adjusted Equity quartiles
adj_eq_q1 = quantile(adjusted_equity_w, 0.25, na.rm = TRUE),
adj_eq_q2 = quantile(adjusted_equity_w, 0.50, na.rm = TRUE), # Median
adj_eq_q3 = quantile(adjusted_equity_w, 0.75, na.rm = TRUE),
# Uninsured Leverage quartiles
unins_lev_q1 = quantile(uninsured_lev_w, 0.25, na.rm = TRUE),
unins_lev_q2 = quantile(uninsured_lev_w, 0.50, na.rm = TRUE), # Median
unins_lev_q3 = quantile(uninsured_lev_w, 0.75, na.rm = TRUE),
# MTM Loss quartiles
mtm_q1 = quantile(mtm_total_w, 0.25, na.rm = TRUE),
mtm_q2 = quantile(mtm_total_w, 0.50, na.rm = TRUE), # Median
mtm_q3 = quantile(mtm_total_w, 0.75, na.rm = TRUE)
)
data %>%
mutate(
# Run Risk Dummies (based on WINSORIZED values before standardization)
# Risk 1: Low MTM & Low Uninsured (Reference Category)
# Risk 2: Low MTM & High Uninsured
# Risk 3: High MTM & Low Uninsured
# Risk 4: High MTM & High Uninsured (Insolvent & Illiquid)
# NOTE: Use replace_na(..., 0L) to handle NA values in underlying variables
run_risk_1 = replace_na(as.integer(mtm_total_w < medians$median_mtm & uninsured_lev_w < medians$median_uninsured), 0L),
run_risk_2 = replace_na(as.integer(mtm_total_w < medians$median_mtm & uninsured_lev_w >= medians$median_uninsured), 0L),
run_risk_3 = replace_na(as.integer(mtm_total_w >= medians$median_mtm & uninsured_lev_w < medians$median_uninsured), 0L),
run_risk_4 = replace_na(as.integer(mtm_total_w >= medians$median_mtm & uninsured_lev_w >= medians$median_uninsured), 0L),
# Store medians for reference
median_mtm_used = medians$median_mtm,
median_uninsured_used = medians$median_uninsured,
# ==========================================================================
# QUARTILE INDICATORS: Adjusted Equity
# Q1 = Bottom 25% (lowest equity), Q4 = Top 25% (highest equity)
# NOTE: Use replace_na(..., 0L) to handle NA values in underlying variables
# ==========================================================================
adj_equity_q1 = replace_na(as.integer(adjusted_equity_w <= quartiles$adj_eq_q1), 0L),
adj_equity_q2 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q1 & adjusted_equity_w <= quartiles$adj_eq_q2), 0L),
adj_equity_q3 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q2 & adjusted_equity_w <= quartiles$adj_eq_q3), 0L),
adj_equity_q4 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q3), 0L),
# Adjusted Equity quartile (factor for regressions)
adj_equity_quartile = case_when(
adjusted_equity_w <= quartiles$adj_eq_q1 ~ "Q1 (Lowest)",
adjusted_equity_w <= quartiles$adj_eq_q2 ~ "Q2",
adjusted_equity_w <= quartiles$adj_eq_q3 ~ "Q3",
TRUE ~ "Q4 (Highest)"
),
adj_equity_quartile = factor(adj_equity_quartile,
levels = c("Q4 (Highest)", "Q3", "Q2", "Q1 (Lowest)")),
# ==========================================================================
# QUARTILE INDICATORS: Uninsured Leverage
# Q1 = Bottom 25% (lowest uninsured), Q4 = Top 25% (highest uninsured risk)
# NOTE: Use replace_na(..., 0L) to handle any NA values in underlying variables
# ==========================================================================
unins_lev_q1 = replace_na(as.integer(uninsured_lev_w <= quartiles$unins_lev_q1), 0L),
unins_lev_q2 = replace_na(as.integer(uninsured_lev_w > quartiles$unins_lev_q1 & uninsured_lev_w <= quartiles$unins_lev_q2), 0L),
unins_lev_q3 = replace_na(as.integer(uninsured_lev_w > quartiles$unins_lev_q2 & uninsured_lev_w <= quartiles$unins_lev_q3), 0L),
unins_lev_q4 = replace_na(as.integer(uninsured_lev_w > quartiles$unins_lev_q3), 0L),
# Uninsured Leverage quartile (factor for regressions)
unins_lev_quartile = case_when(
uninsured_lev_w <= quartiles$unins_lev_q1 ~ "Q1 (Lowest)",
uninsured_lev_w <= quartiles$unins_lev_q2 ~ "Q2",
uninsured_lev_w <= quartiles$unins_lev_q3 ~ "Q3",
TRUE ~ "Q4 (Highest)"
),
unins_lev_quartile = factor(unins_lev_quartile,
levels = c("Q1 (Lowest)", "Q2", "Q3", "Q4 (Highest)")),
# ==========================================================================
# QUARTILE INDICATORS: MTM Loss
# Q1 = Bottom 25% (lowest MTM loss), Q4 = Top 25% (highest MTM loss)
# NOTE: Use replace_na(..., 0L) to handle any NA values in underlying variables
# ==========================================================================
mtm_q1 = replace_na(as.integer(mtm_total_w <= quartiles$mtm_q1), 0L),
mtm_q2 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q1 & mtm_total_w <= quartiles$mtm_q2), 0L),
mtm_q3 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q2 & mtm_total_w <= quartiles$mtm_q3), 0L),
mtm_q4 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q3), 0L),
# MTM Loss quartile (factor for regressions)
mtm_quartile = case_when(
mtm_total_w <= quartiles$mtm_q1 ~ "Q1 (Lowest)",
mtm_total_w <= quartiles$mtm_q2 ~ "Q2",
mtm_total_w <= quartiles$mtm_q3 ~ "Q3",
TRUE ~ "Q4 (Highest)"
),
mtm_quartile = factor(mtm_quartile,
levels = c("Q1 (Lowest)", "Q2", "Q3", "Q4 (Highest)")),
# ==========================================================================
# QUARTILE-BASED RISK CATEGORIES: MTM x Uninsured Leverage
# 16-cell matrix based on quartile combinations
# ==========================================================================
# Simplified 4-category version using extreme quartiles
# High Risk = Q4 MTM & Q4 Uninsured
# MTM Risk = Q4 MTM & Q1-Q2 Uninsured
# Liquidity Risk = Q1-Q2 MTM & Q4 Uninsured
# Low Risk = Q1-Q2 MTM & Q1-Q2 Uninsured
mtm_unins_risk_q = case_when(
mtm_total_w > quartiles$mtm_q3 & uninsured_lev_w > quartiles$unins_lev_q3 ~ "Dual Risk (Q4-Q4)",
mtm_total_w > quartiles$mtm_q3 & uninsured_lev_w <= quartiles$unins_lev_q2 ~ "MTM Risk (Q4, Q1-Q2)",
mtm_total_w <= quartiles$mtm_q2 & uninsured_lev_w > quartiles$unins_lev_q3 ~ "Liquidity Risk (Q1-Q2, Q4)",
mtm_total_w <= quartiles$mtm_q2 & uninsured_lev_w <= quartiles$unins_lev_q2 ~ "Low Risk (Q1-Q2, Q1-Q2)",
TRUE ~ "Medium Risk"
),
mtm_unins_risk_q = factor(mtm_unins_risk_q,
levels = c("Low Risk (Q1-Q2, Q1-Q2)", "Medium Risk",
"MTM Risk (Q4, Q1-Q2)", "Liquidity Risk (Q1-Q2, Q4)",
"Dual Risk (Q4-Q4)")),
# Binary indicators for quartile-based risk categories
risk_q_low = as.integer(mtm_unins_risk_q == "Low Risk (Q1-Q2, Q1-Q2)"),
risk_q_medium = as.integer(mtm_unins_risk_q == "Medium Risk"),
risk_q_mtm = as.integer(mtm_unins_risk_q == "MTM Risk (Q4, Q1-Q2)"),
risk_q_liquidity = as.integer(mtm_unins_risk_q == "Liquidity Risk (Q1-Q2, Q4)"),
risk_q_dual = as.integer(mtm_unins_risk_q == "Dual Risk (Q4-Q4)"),
# ==========================================================================
# 16 COMBINATION DUMMIES: Adjusted Equity x Uninsured Leverage Quartiles
# Format: aeq{1-4}_ulq{1-4} = 1 if bank in that cell, 0 otherwise
# Reference: aeq4_ulq1 = Highest Equity + Lowest Uninsured (safest)
# NOTE: Use replace_na(..., 0L) to handle any NA values in source variables
# ==========================================================================
# AdjEq Q1 (Lowest Equity) combinations
aeq1_ulq1 = replace_na(as.integer(adjusted_equity_w <= quartiles$adj_eq_q1 &
uninsured_lev_w <= quartiles$unins_lev_q1), 0L),
aeq1_ulq2 = replace_na(as.integer(adjusted_equity_w <= quartiles$adj_eq_q1 &
uninsured_lev_w > quartiles$unins_lev_q1 & uninsured_lev_w <= quartiles$unins_lev_q2), 0L),
aeq1_ulq3 = replace_na(as.integer(adjusted_equity_w <= quartiles$adj_eq_q1 &
uninsured_lev_w > quartiles$unins_lev_q2 & uninsured_lev_w <= quartiles$unins_lev_q3), 0L),
aeq1_ulq4 = replace_na(as.integer(adjusted_equity_w <= quartiles$adj_eq_q1 &
uninsured_lev_w > quartiles$unins_lev_q3), 0L),
# AdjEq Q2 combinations
aeq2_ulq1 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q1 & adjusted_equity_w <= quartiles$adj_eq_q2 &
uninsured_lev_w <= quartiles$unins_lev_q1), 0L),
aeq2_ulq2 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q1 & adjusted_equity_w <= quartiles$adj_eq_q2 &
uninsured_lev_w > quartiles$unins_lev_q1 & uninsured_lev_w <= quartiles$unins_lev_q2), 0L),
aeq2_ulq3 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q1 & adjusted_equity_w <= quartiles$adj_eq_q2 &
uninsured_lev_w > quartiles$unins_lev_q2 & uninsured_lev_w <= quartiles$unins_lev_q3), 0L),
aeq2_ulq4 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q1 & adjusted_equity_w <= quartiles$adj_eq_q2 &
uninsured_lev_w > quartiles$unins_lev_q3), 0L),
# AdjEq Q3 combinations
aeq3_ulq1 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q2 & adjusted_equity_w <= quartiles$adj_eq_q3 &
uninsured_lev_w <= quartiles$unins_lev_q1), 0L),
aeq3_ulq2 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q2 & adjusted_equity_w <= quartiles$adj_eq_q3 &
uninsured_lev_w > quartiles$unins_lev_q1 & uninsured_lev_w <= quartiles$unins_lev_q2), 0L),
aeq3_ulq3 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q2 & adjusted_equity_w <= quartiles$adj_eq_q3 &
uninsured_lev_w > quartiles$unins_lev_q2 & uninsured_lev_w <= quartiles$unins_lev_q3), 0L),
aeq3_ulq4 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q2 & adjusted_equity_w <= quartiles$adj_eq_q3 &
uninsured_lev_w > quartiles$unins_lev_q3), 0L),
# AdjEq Q4 (Highest Equity) combinations
aeq4_ulq1 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q3 &
uninsured_lev_w <= quartiles$unins_lev_q1), 0L), # REFERENCE (safest)
aeq4_ulq2 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q3 &
uninsured_lev_w > quartiles$unins_lev_q1 & uninsured_lev_w <= quartiles$unins_lev_q2), 0L),
aeq4_ulq3 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q3 &
uninsured_lev_w > quartiles$unins_lev_q2 & uninsured_lev_w <= quartiles$unins_lev_q3), 0L),
aeq4_ulq4 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q3 &
uninsured_lev_w > quartiles$unins_lev_q3), 0L),
# ==========================================================================
# EXTREME RISK INDICATORS (Top quartile thresholds)
# NOTE: Use replace_na(..., 0L) to handle any NA values in underlying variables
# ==========================================================================
high_mtm_q4 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q3), 0L),
high_unins_q4 = replace_na(as.integer(uninsured_lev_w > quartiles$unins_lev_q3), 0L),
low_adj_equity_q1 = replace_na(as.integer(adjusted_equity_w <= quartiles$adj_eq_q1), 0L),
# ==========================================================================
# 16 COMBINATION DUMMIES: MTM Loss x Uninsured Leverage Quartiles
# Format: mtmq{1-4}_ulq{1-4} = 1 if bank in that cell, 0 otherwise
# Reference: mtmq1_ulq1 = Lowest MTM Loss + Lowest Uninsured (safest)
# ==========================================================================
# MTM Q1 (Lowest MTM Loss) combinations
mtmq1_ulq1 = replace_na(as.integer(mtm_total_w <= quartiles$mtm_q1 &
uninsured_lev_w <= quartiles$unins_lev_q1), 0L), # REFERENCE (safest)
mtmq1_ulq2 = replace_na(as.integer(mtm_total_w <= quartiles$mtm_q1 &
uninsured_lev_w > quartiles$unins_lev_q1 & uninsured_lev_w <= quartiles$unins_lev_q2), 0L),
mtmq1_ulq3 = replace_na(as.integer(mtm_total_w <= quartiles$mtm_q1 &
uninsured_lev_w > quartiles$unins_lev_q2 & uninsured_lev_w <= quartiles$unins_lev_q3), 0L),
mtmq1_ulq4 = replace_na(as.integer(mtm_total_w <= quartiles$mtm_q1 &
uninsured_lev_w > quartiles$unins_lev_q3), 0L),
# MTM Q2 combinations
mtmq2_ulq1 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q1 & mtm_total_w <= quartiles$mtm_q2 &
uninsured_lev_w <= quartiles$unins_lev_q1), 0L),
mtmq2_ulq2 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q1 & mtm_total_w <= quartiles$mtm_q2 &
uninsured_lev_w > quartiles$unins_lev_q1 & uninsured_lev_w <= quartiles$unins_lev_q2), 0L),
mtmq2_ulq3 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q1 & mtm_total_w <= quartiles$mtm_q2 &
uninsured_lev_w > quartiles$unins_lev_q2 & uninsured_lev_w <= quartiles$unins_lev_q3), 0L),
mtmq2_ulq4 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q1 & mtm_total_w <= quartiles$mtm_q2 &
uninsured_lev_w > quartiles$unins_lev_q3), 0L),
# MTM Q3 combinations
mtmq3_ulq1 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q2 & mtm_total_w <= quartiles$mtm_q3 &
uninsured_lev_w <= quartiles$unins_lev_q1), 0L),
mtmq3_ulq2 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q2 & mtm_total_w <= quartiles$mtm_q3 &
uninsured_lev_w > quartiles$unins_lev_q1 & uninsured_lev_w <= quartiles$unins_lev_q2), 0L),
mtmq3_ulq3 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q2 & mtm_total_w <= quartiles$mtm_q3 &
uninsured_lev_w > quartiles$unins_lev_q2 & uninsured_lev_w <= quartiles$unins_lev_q3), 0L),
mtmq3_ulq4 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q2 & mtm_total_w <= quartiles$mtm_q3 &
uninsured_lev_w > quartiles$unins_lev_q3), 0L),
# MTM Q4 (Highest MTM Loss) combinations
mtmq4_ulq1 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q3 &
uninsured_lev_w <= quartiles$unins_lev_q1), 0L),
mtmq4_ulq2 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q3 &
uninsured_lev_w > quartiles$unins_lev_q1 & uninsured_lev_w <= quartiles$unins_lev_q2), 0L),
mtmq4_ulq3 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q3 &
uninsured_lev_w > quartiles$unins_lev_q2 & uninsured_lev_w <= quartiles$unins_lev_q3), 0L),
mtmq4_ulq4 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q3 &
uninsured_lev_w > quartiles$unins_lev_q3), 0L),
# Store quartile cutoffs for reference
adj_eq_q1_used = quartiles$adj_eq_q1,
adj_eq_q2_used = quartiles$adj_eq_q2,
adj_eq_q3_used = quartiles$adj_eq_q3,
unins_lev_q1_used = quartiles$unins_lev_q1,
unins_lev_q2_used = quartiles$unins_lev_q2,
unins_lev_q3_used = quartiles$unins_lev_q3,
mtm_q1_used = quartiles$mtm_q1,
mtm_q2_used = quartiles$mtm_q2,
mtm_q3_used = quartiles$mtm_q3
)
}
# ==============================================================================
# CREATE BASELINE DATASETS
# ==============================================================================
# Baseline sample (2022Q4, OMO-eligible only for main analysis)
df_2022q4 <- call_q %>%
filter(period == BASELINE_MAIN, !idrssd %in% excluded_banks,
!is.na(omo_eligible) & omo_eligible > 0) %>%
construct_analysis_vars() %>%
add_run_risk_dummies()
df_2023q3 <- call_q %>%
filter(period == BASELINE_ARB, !idrssd %in% excluded_banks,
!is.na(omo_eligible) & omo_eligible > 0) %>%
construct_analysis_vars() %>%
add_run_risk_dummies()
df_2023q4 <- call_q %>%
filter(period == BASELINE_WIND, !idrssd %in% excluded_banks,
!is.na(omo_eligible) & omo_eligible > 0) %>%
construct_analysis_vars() %>%
add_run_risk_dummies()
cat("\n=== BASELINE DATASETS ===\n")
##
## === BASELINE DATASETS ===
cat("2022Q4:", nrow(df_2022q4), "banks\n")
## 2022Q4: 4292 banks
cat("2023Q3:", nrow(df_2023q3), "banks\n")
## 2023Q3: 4214 banks
cat("2023Q4:", nrow(df_2023q4), "banks\n")
## 2023Q4: 4197 banks
# Verify no NAs in key variables after filtering
cat("\n=== DATA QUALITY CHECK (should all be 0) ===\n")
##
## === DATA QUALITY CHECK (should all be 0) ===
na_check <- df_2022q4 %>%
summarise(
adjusted_equity_w_NAs = sum(is.na(adjusted_equity_w)),
uninsured_lev_w_NAs = sum(is.na(uninsured_lev_w)),
mtm_total_w_NAs = sum(is.na(mtm_total_w))
)
cat("NAs in 2022Q4 key variables:\n")
## NAs in 2022Q4 key variables:
print(na_check)
## # A tibble: 1 × 3
## adjusted_equity_w_NAs uninsured_lev_w_NAs mtm_total_w_NAs
## <int> <int> <int>
## 1 10 0 10
# Verify 16 combination dummies have no NAs and sum to 1 for each bank
combo_check <- df_2022q4 %>%
mutate(combo_sum = aeq1_ulq1 + aeq1_ulq2 + aeq1_ulq3 + aeq1_ulq4 +
aeq2_ulq1 + aeq2_ulq2 + aeq2_ulq3 + aeq2_ulq4 +
aeq3_ulq1 + aeq3_ulq2 + aeq3_ulq3 + aeq3_ulq4 +
aeq4_ulq1 + aeq4_ulq2 + aeq4_ulq3 + aeq4_ulq4) %>%
count(combo_sum, name = "N_banks")
cat("\n16 combination dummy row sums (should all equal 1):\n")
##
## 16 combination dummy row sums (should all equal 1):
print(combo_check)
## # A tibble: 2 × 2
## combo_sum N_banks
## <int> <int>
## 1 0 10
## 2 1 4282
cat("\n=== RUN RISK MEDIANS (2022Q4) ===\n")
##
## === RUN RISK MEDIANS (2022Q4) ===
cat("Median MTM Loss (winsorized):", round(df_2022q4$median_mtm_used[1], 4), "%\n")
## Median MTM Loss (winsorized): 5.32 %
cat("Median Uninsured Leverage (winsorized):", round(df_2022q4$median_uninsured_used[1], 4), "%\n")
## Median Uninsured Leverage (winsorized): 22.25 %
cat("\n=== QUARTILE CUTOFFS (2022Q4) ===\n")
##
## === QUARTILE CUTOFFS (2022Q4) ===
cat("Adjusted Equity: Q1=", round(df_2022q4$adj_eq_q1_used[1], 4),
"%, Q2(Med)=", round(df_2022q4$adj_eq_q2_used[1], 4),
"%, Q3=", round(df_2022q4$adj_eq_q3_used[1], 4), "%\n")
## Adjusted Equity: Q1= 0.8534 %, Q2(Med)= 3.621 %, Q3= 6.435 %
cat("Uninsured Lev: Q1=", round(df_2022q4$unins_lev_q1_used[1], 4),
"%, Q2(Med)=", round(df_2022q4$unins_lev_q2_used[1], 4),
"%, Q3=", round(df_2022q4$unins_lev_q3_used[1], 4), "%\n")
## Uninsured Lev: Q1= 15.29 %, Q2(Med)= 22.25 %, Q3= 30.35 %
cat("MTM Loss: Q1=", round(df_2022q4$mtm_q1_used[1], 4),
"%, Q2(Med)=", round(df_2022q4$mtm_q2_used[1], 4),
"%, Q3=", round(df_2022q4$mtm_q3_used[1], 4), "%\n")
## MTM Loss: Q1= 3.863 %, Q2(Med)= 5.32 %, Q3= 6.975 %
# ==============================================================================
# CRITICAL: CLEAN SAMPLE CONSTRUCTION
# For binary outcomes: 0 = PURE NON-BORROWERS ONLY
# Exclude other facility users from the 0 category
# ==============================================================================
# Join all borrower indicators to baseline
join_all_borrowers <- function(df_acute, btfp_df, dw_df, btfp_var, dw_var) {
df_acute %>%
left_join(btfp_df %>% select(idrssd, starts_with(btfp_var)), by = "idrssd") %>%
left_join(dw_df %>% select(idrssd, starts_with(dw_var)), by = "idrssd") %>%
mutate(
# Fill NAs with 0
"{btfp_var}" := replace_na(!!sym(btfp_var), 0L),
"{dw_var}" := replace_na(!!sym(dw_var), 0L),
# FHLB user (from call report)
fhlb_user = as.integer(abnormal_fhlb_borrowing_10pct == 1),
# User group classification (for descriptive tables)
user_group = case_when(
!!sym(btfp_var) == 1 & !!sym(dw_var) == 1 ~ "Both",
!!sym(btfp_var) == 1 & !!sym(dw_var) == 0 ~ "BTFP_Only",
!!sym(btfp_var) == 0 & !!sym(dw_var) == 1 ~ "DW_Only",
TRUE ~ "Neither"
),
user_group = factor(user_group, levels = c("Neither", "BTFP_Only", "DW_Only", "Both")),
# Combined indicators
any_fed = as.integer(!!sym(btfp_var) == 1 | !!sym(dw_var) == 1),
both_fed = as.integer(!!sym(btfp_var) == 1 & !!sym(dw_var) == 1),
all_user = as.integer(!!sym(btfp_var) == 1 | !!sym(dw_var) == 1 | fhlb_user == 1),
# PURE non-user (no BTFP, no DW, no FHLB) - for clean comparison
non_user = as.integer(!!sym(btfp_var) == 0 & !!sym(dw_var) == 0 & fhlb_user == 0)
)
}
# ==============================================================================
# CREATE PERIOD DATASETS
# ==============================================================================
# Acute Period (Mar 13 - May 1, 2023)
df_acute <- join_all_borrowers(df_2022q4, btfp_acute, dw_acute, "btfp_acute", "dw_acute") %>%
mutate(
# Intensive margin variables
btfp_pct = ifelse(btfp_acute == 1 & btfp_acute_amt > 0,
100 * btfp_acute_amt / (total_asset * 1000), NA_real_),
dw_pct = ifelse(dw_acute == 1 & dw_acute_amt > 0,
100 * dw_acute_amt / (total_asset * 1000), NA_real_),
log_btfp_amt = ifelse(btfp_acute == 1 & btfp_acute_amt > 0, log(btfp_acute_amt), NA_real_),
log_dw_amt = ifelse(dw_acute == 1 & dw_acute_amt > 0, log(dw_acute_amt), NA_real_)
)
# March 10 (SVB Closure Day)
df_mar10 <- join_all_borrowers(df_2022q4, btfp_mar10, dw_mar10, "btfp_mar10", "dw_mar10")
# March 10-13 (BTFP Launch Window)
df_mar10_13 <- join_all_borrowers(df_2022q4, btfp_mar10_13, dw_mar10_13, "btfp_mar10_13", "dw_mar10_13")
# Pre-BTFP (Mar 1 - Mar 12)
btfp_prebtfp <- create_borrower_indicator(
btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount",
DATE_MAR01, DATE_MAR12, "btfp_prebtfp"
)
df_prebtfp <- join_all_borrowers(df_2022q4, btfp_prebtfp, dw_prebtfp, "btfp_prebtfp", "dw_prebtfp")
# Post-Acute Period (May 2 - Oct 31, 2023)
df_post <- df_2022q4 %>%
left_join(btfp_post %>% select(idrssd, btfp_post, btfp_post_amt), by = "idrssd") %>%
left_join(dw_post %>% select(idrssd, dw_post, dw_post_amt), by = "idrssd") %>%
mutate(
btfp_post = replace_na(btfp_post, 0L),
dw_post = replace_na(dw_post, 0L),
fhlb_user = as.integer(abnormal_fhlb_borrowing_10pct == 1),
any_fed = as.integer(btfp_post == 1 | dw_post == 1),
non_user = as.integer(btfp_post == 0 & dw_post == 0 & fhlb_user == 0),
user_group = case_when(
btfp_post == 1 & dw_post == 1 ~ "Both",
btfp_post == 1 ~ "BTFP_Only",
dw_post == 1 ~ "DW_Only",
TRUE ~ "Neither"
),
user_group = factor(user_group, levels = c("Neither", "BTFP_Only", "DW_Only", "Both"))
)
# Arbitrage Period (Nov 1, 2023 - Jan 24, 2024) - 2023Q3 Baseline
df_arb <- df_2023q3 %>%
left_join(btfp_arb %>% select(idrssd, btfp_arb, btfp_arb_amt), by = "idrssd") %>%
mutate(
btfp_arb = replace_na(btfp_arb, 0L),
fhlb_user = as.integer(abnormal_fhlb_borrowing_10pct == 1),
any_fed = btfp_arb,
non_user = as.integer(btfp_arb == 0 & fhlb_user == 0),
user_group = factor(ifelse(btfp_arb == 1, "BTFP_Only", "Neither"),
levels = c("Neither", "BTFP_Only", "DW_Only", "Both"))
)
# Wind-down Period (Jan 25 - Mar 11, 2024) - 2023Q4 Baseline
df_wind <- df_2023q4 %>%
left_join(btfp_wind %>% select(idrssd, btfp_wind, btfp_wind_amt), by = "idrssd") %>%
mutate(
btfp_wind = replace_na(btfp_wind, 0L),
fhlb_user = as.integer(abnormal_fhlb_borrowing_10pct == 1),
any_fed = btfp_wind,
non_user = as.integer(btfp_wind == 0 & fhlb_user == 0),
user_group = factor(ifelse(btfp_wind == 1, "BTFP_Only", "Neither"),
levels = c("Neither", "BTFP_Only", "DW_Only", "Both"))
)
# Overall Period
df_overall <- join_all_borrowers(df_2022q4, btfp_overall, dw_overall, "btfp_overall", "dw_overall")
cat("\n=== USER GROUP COUNTS (ACUTE PERIOD) ===\n")
##
## === USER GROUP COUNTS (ACUTE PERIOD) ===
print(table(df_acute$user_group))
##
## Neither BTFP_Only DW_Only Both
## 3531 368 299 94
cat("\nPure Non-Users:", sum(df_acute$non_user), "\n")
##
## Pure Non-Users: 3285
# ==============================================================================
# MODEL SETTINGS (DEFINE ONCE, USE EVERYWHERE)
# ==============================================================================
# --- 0A. Controls -----------------------------------------------------------
CONTROLS_1 <- "ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + wholesale + roa"
CONTROLS_2 <- "ln_assets + cash_ratio + loan_to_deposit + wholesale + roa" # when AE is explanatory
# --- 0B. Formula builder (FIXED: default was CONTROLS which didn't exist) ----
build_formula <- function(dv, explanatory, controls = CONTROLS_1) {
as.formula(paste(dv, "~", explanatory, "+", controls))
}
# Separate builder for AE-based models (drops book_equity_ratio to avoid collinearity)
build_formula_ae <- function(dv, explanatory, controls = CONTROLS_2) {
as.formula(paste(dv, "~", explanatory, "+", controls))
}
# --- 0C. Variable dictionary for etable (replaces COEF_MAP) -----------------
setFixest_dict(c(
# Key explanatory
mtm_total = "MTM Loss (z)",
uninsured_lev = "Uninsured Leverage (z)",
mtm_x_uninsured = "MTM $\\times$ Uninsured",
adjusted_equity = "Adjusted Equity (z)",
ae_x_uninsured = "Adj. Equity $\\times$ Uninsured",
# Risk dummies (median split)
run_risk_1 = "Risk 1: Low MTM, Low Unins.",
run_risk_2 = "Risk 2: Low MTM, High Unins.",
run_risk_3 = "Risk 3: High MTM, Low Unins.",
run_risk_4 = "Risk 4: High MTM, High Unins.",
# MTM quartile dummies
mtm_q2 = "MTM Q2",
mtm_q3 = "MTM Q3",
mtm_q4 = "MTM Q4 (Highest Loss)",
# Adjusted Equity quartile dummies (Q4=Highest=reference)
adj_equity_q1 = "Adj. Equity Q1 (Lowest)",
adj_equity_q2 = "Adj. Equity Q2",
adj_equity_q3 = "Adj. Equity Q3",
# Uninsured leverage quartile dummies
unins_lev_q2 = "Unins. Lev. Q2",
unins_lev_q3 = "Unins. Lev. Q3",
unins_lev_q4 = "Unins. Lev. Q4 (Highest)",
# 16-cell MTM x Uninsured dummies (mtmq1_ulq1 = reference)
mtmq1_ulq2 = "MTM-Q1 $\\times$ UL-Q2",
mtmq1_ulq3 = "MTM-Q1 $\\times$ UL-Q3",
mtmq1_ulq4 = "MTM-Q1 $\\times$ UL-Q4",
mtmq2_ulq1 = "MTM-Q2 $\\times$ UL-Q1",
mtmq2_ulq2 = "MTM-Q2 $\\times$ UL-Q2",
mtmq2_ulq3 = "MTM-Q2 $\\times$ UL-Q3",
mtmq2_ulq4 = "MTM-Q2 $\\times$ UL-Q4",
mtmq3_ulq1 = "MTM-Q3 $\\times$ UL-Q1",
mtmq3_ulq2 = "MTM-Q3 $\\times$ UL-Q2",
mtmq3_ulq3 = "MTM-Q3 $\\times$ UL-Q3",
mtmq3_ulq4 = "MTM-Q3 $\\times$ UL-Q4",
mtmq4_ulq1 = "MTM-Q4 $\\times$ UL-Q1",
mtmq4_ulq2 = "MTM-Q4 $\\times$ UL-Q2",
mtmq4_ulq3 = "MTM-Q4 $\\times$ UL-Q3",
mtmq4_ulq4 = "MTM-Q4 $\\times$ UL-Q4",
# 16-cell AE x Uninsured dummies (aeq4_ulq1 = reference)
aeq1_ulq1 = "AE-Q1 $\\times$ UL-Q1",
aeq1_ulq2 = "AE-Q1 $\\times$ UL-Q2",
aeq1_ulq3 = "AE-Q1 $\\times$ UL-Q3",
aeq1_ulq4 = "AE-Q1 $\\times$ UL-Q4",
aeq2_ulq1 = "AE-Q2 $\\times$ UL-Q1",
aeq2_ulq2 = "AE-Q2 $\\times$ UL-Q2",
aeq2_ulq3 = "AE-Q2 $\\times$ UL-Q3",
aeq2_ulq4 = "AE-Q2 $\\times$ UL-Q4",
aeq3_ulq1 = "AE-Q3 $\\times$ UL-Q1",
aeq3_ulq2 = "AE-Q3 $\\times$ UL-Q2",
aeq3_ulq3 = "AE-Q3 $\\times$ UL-Q3",
aeq3_ulq4 = "AE-Q3 $\\times$ UL-Q4",
aeq4_ulq2 = "AE-Q4 $\\times$ UL-Q2",
aeq4_ulq3 = "AE-Q4 $\\times$ UL-Q3",
aeq4_ulq4 = "AE-Q4 $\\times$ UL-Q4",
# Controls
ln_assets = "Log(Assets)",
cash_ratio = "Cash Ratio",
loan_to_deposit = "Loan-to-Deposit",
book_equity_ratio = "Book Equity Ratio",
wholesale = "Wholesale Funding",
roa = "ROA"
))
# --- 0D. GOF statistics for etable ------------------------------------------
gof_lpm <- c("nobs", "r2")
gof_logit <- c("nobs", "ll", "aic")
# --- 0F. Explanatory variable strings ----------------------------------------
# Base continuous (MTM)
EXPL_BASE_MTM <- "mtm_total + uninsured_lev + mtm_x_uninsured"
# Base continuous (AE)
EXPL_BASE_AE <- "adjusted_equity + uninsured_lev + ae_x_uninsured"
# Risk dummies (median split)
EXPL_RISK <- "run_risk_2 + run_risk_3 + run_risk_4"
# Quartile dummies: individual dimensions
EXPL_MTM_Q <- "mtm_q2 + mtm_q3 + mtm_q4" # ref = mtm_q1
EXPL_AE_Q <- "adj_equity_q1 + adj_equity_q2 + adj_equity_q3" # ref = adj_equity_q4 (safest)
EXPL_UNINS_Q <- "unins_lev_q2 + unins_lev_q3 + unins_lev_q4" # ref = unins_lev_q1
# 16-cell quartile interactions: MTM x Uninsured (ref = mtmq1_ulq1)
EXPL_MTM_UL_Q16 <- paste(
"mtmq1_ulq2 + mtmq1_ulq3 + mtmq1_ulq4",
"mtmq2_ulq1 + mtmq2_ulq2 + mtmq2_ulq3 + mtmq2_ulq4",
"mtmq3_ulq1 + mtmq3_ulq2 + mtmq3_ulq3 + mtmq3_ulq4",
"mtmq4_ulq1 + mtmq4_ulq2 + mtmq4_ulq3 + mtmq4_ulq4",
sep = " + "
)
# 16-cell quartile interactions: AE x Uninsured (ref = aeq4_ulq1)
EXPL_AE_UL_Q16 <- paste(
"aeq1_ulq1 + aeq1_ulq2 + aeq1_ulq3 + aeq1_ulq4",
"aeq2_ulq1 + aeq2_ulq2 + aeq2_ulq3 + aeq2_ulq4",
"aeq3_ulq1 + aeq3_ulq2 + aeq3_ulq3 + aeq3_ulq4",
"aeq4_ulq2 + aeq4_ulq3 + aeq4_ulq4",
sep = " + "
)
# ==============================================================================
# SECTION 0.5: CREATE MISSING VARIABLE (ae_x_uninsured)
# This interaction was never created in construct_analysis_vars()
# ==============================================================================
add_ae_interaction <- function(df) {
df %>% mutate(ae_x_uninsured = adjusted_equity * uninsured_lev)
}
# Apply to all analysis datasets
df_acute <- add_ae_interaction(df_acute)
df_post <- add_ae_interaction(df_post)
df_arb <- add_ae_interaction(df_arb)
df_wind <- add_ae_interaction(df_wind)
df_prebtfp <- add_ae_interaction(df_prebtfp)
df_mar10 <- add_ae_interaction(df_mar10)
df_mar10_13 <- add_ae_interaction(df_mar10_13)
df_overall <- add_ae_interaction(df_overall)
cat("ae_x_uninsured interaction added to all datasets.\n")
## ae_x_uninsured interaction added to all datasets.
# ==============================================================================
# SECTION 1: MODEL RUNNER FUNCTIONS
# ==============================================================================
# --- 1A. Generic single-model runner -----------------------------------------
run_one <- function(data, dv, explanatory, family_type = "lpm", controls = CONTROLS_1) {
ff <- as.formula(paste(dv, "~", explanatory, "+", controls))
if (family_type == "lpm") {
feols(ff, data = data, vcov = "hetero")
} else {
feglm(ff, data = data, family = binomial("logit"), vcov = "hetero")
}
}
# --- 1B. Full specification set (7 models) ------------------------------------
# Returns named list: Base, Risk, MTM-Q, AE-Q, Unins-Q, MTMxUL-Q16, AExUL-Q16
run_full_specs <- function(data, dv, family_type = "lpm") {
list(
"Base" = run_one(data, dv, EXPL_BASE_MTM, family_type, CONTROLS_1),
"Risk" = run_one(data, dv, EXPL_RISK, family_type, CONTROLS_1),
"MTM-Q" = run_one(data, dv, EXPL_MTM_Q, family_type, CONTROLS_1),
"AE-Q" = run_one(data, dv, EXPL_AE_Q, family_type, CONTROLS_2),
"Unins-Q" = run_one(data, dv, EXPL_UNINS_Q, family_type, CONTROLS_1),
"MTMxUL-Q16" = run_one(data, dv, EXPL_MTM_UL_Q16, family_type, CONTROLS_1),
"AExUL-Q16" = run_one(data, dv, EXPL_AE_UL_Q16, family_type, CONTROLS_2)
)
}
# --- 1C. Core specification set (5 models for main tables) -------------------
# Base, Risk, + 3 quartile-only models
run_core_specs <- function(data, dv, family_type = "lpm") {
list(
"Base" = run_one(data, dv, EXPL_BASE_MTM, family_type, CONTROLS_1),
"Risk" = run_one(data, dv, EXPL_RISK, family_type, CONTROLS_1),
"MTM-Q" = run_one(data, dv, EXPL_MTM_Q, family_type, CONTROLS_1),
"AE-Q" = run_one(data, dv, EXPL_AE_Q, family_type, CONTROLS_2),
"Unins-Q" = run_one(data, dv, EXPL_UNINS_Q, family_type, CONTROLS_1)
)
}
# --- 1D. AE-based specification set (3 models) -------------------------------
run_ae_specs <- function(data, dv, family_type = "lpm") {
list(
"AE-Base" = run_one(data, dv, EXPL_BASE_AE, family_type, CONTROLS_2),
"AE-Q" = run_one(data, dv, EXPL_AE_Q, family_type, CONTROLS_2),
"AExUL-Q16" = run_one(data, dv, EXPL_AE_UL_Q16, family_type, CONTROLS_2)
)
}
# --- 1E. Temporal pair (Base + Risk + 3 quartile) ----------------------------
run_temporal_pair <- function(data, dv, family_type = "lpm") {
list(
Base = run_one(data, dv, EXPL_BASE_MTM, family_type, CONTROLS_1),
Risk = run_one(data, dv, EXPL_RISK, family_type, CONTROLS_1)
)
}
# --- 1F. Sample count helper --------------------------------------------------
count_info <- function(data, dv) {
paste0("N(", dv, "=1)=", sum(data[[dv]] == 1, na.rm = TRUE),
", N(sample)=", nrow(data))
}
# --- 1G. etable saver (LaTeX) ------------------------------------------------
save_etable <- function(models, filename, title_text, notes_text,
fitstat_use = ~ n + r2, drop_controls = TRUE,
extra_lines = NULL) {
# Optionally hide controls to keep tables compact
drop_pattern <- if (drop_controls) "Log\\(Assets\\)|Cash Ratio|Loan-to-Deposit|Book Equity|Wholesale|ROA" else NULL
etable(
models,
title = title_text,
notes = notes_text,
fitstat = fitstat_use,
drop = drop_pattern,
extralines = extra_lines,
tex = TRUE,
file = file.path(TABLE_PATH, paste0(filename, ".tex")),
replace = TRUE,
style.tex = style.tex("aer")
)
message("Saved: ", filename, ".tex")
}
# ==============================================================================
# SECTION 2: ACUTE PERIOD — EXTENSIVE MARGIN
# ==============================================================================
cat("\n========================================\n")
##
## ========================================
cat("SECTION 2: ACUTE PERIOD REGRESSIONS\n")
## SECTION 2: ACUTE PERIOD REGRESSIONS
cat("========================================\n")
## ========================================
# --- 2A. Prepare samples -----------------------------------------------------
df_btfp_acute_s <- df_acute %>% filter(btfp_acute == 1 | non_user == 1)
df_dw_acute_s <- df_acute %>% filter(dw_acute == 1 | non_user == 1)
df_anyfed_acute_s <- df_acute %>% filter(any_fed == 1 | non_user == 1)
df_all_acute_s <- df_acute %>% filter(all_user == 1 | non_user == 1)
cat("BTFP acute sample:", count_info(df_btfp_acute_s, "btfp_acute"), "\n")
## BTFP acute sample: N(btfp_acute=1)=462, N(sample)=3747
cat("DW acute sample:", count_info(df_dw_acute_s, "dw_acute"), "\n")
## DW acute sample: N(dw_acute=1)=393, N(sample)=3678
cat("Any Fed sample:", count_info(df_anyfed_acute_s, "any_fed"), "\n")
## Any Fed sample: N(any_fed=1)=761, N(sample)=4046
cat("All User sample:", count_info(df_all_acute_s, "all_user"), "\n")
## All User sample: N(all_user=1)=1007, N(sample)=4292
# ==============================================================================
# TABLE A1: BTFP Acute — Core Specs (LPM)
# Columns: Base | Risk | MTM-Q | AE-Q | Unins-Q
# ==============================================================================
m_btfp_acute_lpm <- run_core_specs(df_btfp_acute_s, "btfp_acute", "lpm")
etable(m_btfp_acute_lpm, fitstat = ~ n + r2,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
title = "BTFP Participation During Acute Crisis (LPM)")
## Base Risk
## Dependent Var.: btfp_acute btfp_acute
##
## Constant 0.1316*** (0.0055) 0.1160*** (0.0096)
## MTM Loss (z) 0.0243*** (0.0059)
## Uninsured Leverage (z) 0.0199** (0.0068)
## MTM $\times$ Uninsured 0.0181*** (0.0051)
## Risk 2: Low MTM, High Unins. -0.0060 (0.0136)
## Risk 3: High MTM, Low Unins. 0.0014 (0.0131)
## Risk 4: High MTM, High Unins. 0.0622*** (0.0170)
## MTM Q2
## MTM Q3
## MTM Q4 (Highest Loss)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## Unins. Lev. Q2
## Unins. Lev. Q3
## Unins. Lev. Q4 (Highest)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,737 3,747
## R2 0.09017 0.08930
##
## MTM-Q AE-Q
## Dependent Var.: btfp_acute btfp_acute
##
## Constant 0.1074*** (0.0091) 0.0953*** (0.0088)
## MTM Loss (z)
## Uninsured Leverage (z)
## MTM $\times$ Uninsured
## Risk 2: Low MTM, High Unins.
## Risk 3: High MTM, Low Unins.
## Risk 4: High MTM, High Unins.
## MTM Q2 0.0120 (0.0130)
## MTM Q3 0.0391** (0.0145)
## MTM Q4 (Highest Loss) 0.0369* (0.0146)
## Adj. Equity Q1 (Lowest) 0.0671*** (0.0151)
## Adj. Equity Q2 0.0605*** (0.0144)
## Adj. Equity Q3 0.0086 (0.0123)
## Unins. Lev. Q2
## Unins. Lev. Q3
## Unins. Lev. Q4 (Highest)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,747 3,747
## R2 0.08552 0.08760
##
## Unins-Q
## Dependent Var.: btfp_acute
##
## Constant 0.1138*** (0.0099)
## MTM Loss (z)
## Uninsured Leverage (z)
## MTM $\times$ Uninsured
## Risk 2: Low MTM, High Unins.
## Risk 3: High MTM, Low Unins.
## Risk 4: High MTM, High Unins.
## MTM Q2
## MTM Q3
## MTM Q4 (Highest Loss)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## Unins. Lev. Q2 0.0056 (0.0134)
## Unins. Lev. Q3 0.0222 (0.0148)
## Unins. Lev. Q4 (Highest) 0.0343* (0.0162)
## _____________________________ __________________
## S.E. type Heteroskedas.-rob.
## Observations 3,747
## R2 0.08488
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
m_btfp_acute_lpm, "Tab_BTFP_Acute_Core_LPM",
title_text = "BTFP Participation During Acute Crisis (LPM): Core Specifications",
notes_text = paste(
"LPM estimates. DV = BTFP participation (Mar 13--May 1, 2023).",
"Col 1: continuous MTM, Uninsured, interaction.",
"Col 2: Risk dummies (median split, Risk 1 = reference).",
"Col 3: MTM quartile dummies (Q1 = lowest = ref).",
"Col 4: Adjusted Equity quartile dummies (Q4 = highest = ref).",
"Col 5: Uninsured Leverage quartile dummies (Q1 = lowest = ref).",
"Sample: BTFP users vs. pure non-users. Controls suppressed.",
"Robust SEs in parentheses."
),
fitstat_use = ~ n + r2
)
# ==============================================================================
# TABLE A2: BTFP Acute — Core Specs (Logit)
# ==============================================================================
m_btfp_acute_logit <- run_core_specs(df_btfp_acute_s, "btfp_acute", "logit")
etable(m_btfp_acute_logit, fitstat = ~ n + ll + aic,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
title = "BTFP Participation During Acute Crisis (Logit)")
## Base Risk
## Dependent Var.: btfp_acute btfp_acute
##
## Constant -2.320*** (0.0711) -2.553*** (0.1482)
## MTM Loss (z) 0.2141*** (0.0622)
## Uninsured Leverage (z) 0.2089*** (0.0626)
## MTM $\times$ Uninsured 0.0461 (0.0569)
## Risk 2: Low MTM, High Unins. 0.1922 (0.1884)
## Risk 3: High MTM, Low Unins. 0.1765 (0.1843)
## Risk 4: High MTM, High Unins. 0.5916** (0.1805)
## MTM Q2
## MTM Q3
## MTM Q4 (Highest Loss)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## Unins. Lev. Q2
## Unins. Lev. Q3
## Unins. Lev. Q4 (Highest)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,737 3,747
## Log-Likelihood -1,214.5 -1,217.2
## AIC 2,449.0 2,454.5
##
## MTM-Q AE-Q
## Dependent Var.: btfp_acute btfp_acute
##
## Constant -2.604*** (0.1446) -2.835*** (0.1479)
## MTM Loss (z)
## Uninsured Leverage (z)
## MTM $\times$ Uninsured
## Risk 2: Low MTM, High Unins.
## Risk 3: High MTM, Low Unins.
## Risk 4: High MTM, High Unins.
## MTM Q2 0.2571 (0.1785)
## MTM Q3 0.4756** (0.1743)
## MTM Q4 (Highest Loss) 0.4226* (0.1770)
## Adj. Equity Q1 (Lowest) 0.9410*** (0.1799)
## Adj. Equity Q2 0.8724*** (0.1748)
## Adj. Equity Q3 0.3458. (0.1862)
## Unins. Lev. Q2
## Unins. Lev. Q3
## Unins. Lev. Q4 (Highest)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,747 3,747
## Log-Likelihood -1,220.9 -1,221.3
## AIC 2,461.7 2,460.7
##
## Unins-Q
## Dependent Var.: btfp_acute
##
## Constant -2.620*** (0.1482)
## MTM Loss (z)
## Uninsured Leverage (z)
## MTM $\times$ Uninsured
## Risk 2: Low MTM, High Unins.
## Risk 3: High MTM, Low Unins.
## Risk 4: High MTM, High Unins.
## MTM Q2
## MTM Q3
## MTM Q4 (Highest Loss)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## Unins. Lev. Q2 0.2484 (0.1793)
## Unins. Lev. Q3 0.4123* (0.1763)
## Unins. Lev. Q4 (Highest) 0.5203** (0.1848)
## _____________________________ __________________
## S.E. type Heteroskedas.-rob.
## Observations 3,747
## Log-Likelihood -1,220.0
## AIC 2,460.0
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
m_btfp_acute_logit, "Tab_BTFP_Acute_Core_Logit",
title_text = "BTFP Participation During Acute Crisis (Logit): Core Specifications",
notes_text = paste(
"Logit estimates. DV = BTFP participation (Mar 13--May 1, 2023).",
"Columns as in LPM table. Sample: BTFP users vs. pure non-users.",
"Controls suppressed. Robust SEs in parentheses."
),
fitstat_use = ~ n + ll + aic
)
# ==============================================================================
# TABLE A3: BTFP Acute — 16-Cell Quartile Interactions (LPM)
# Columns: MTMxUL-Q16 | AExUL-Q16
# ==============================================================================
m_btfp_acute_q16_lpm <- list(
"MTM x Unins Q16" = run_one(df_btfp_acute_s, "btfp_acute", EXPL_MTM_UL_Q16, "lpm", CONTROLS_1),
"AE x Unins Q16" = run_one(df_btfp_acute_s, "btfp_acute", EXPL_AE_UL_Q16, "lpm", CONTROLS_2)
)
etable(m_btfp_acute_q16_lpm, fitstat = ~ n + r2,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
title = "BTFP Acute: 16-Cell Quartile Interactions (LPM)")
## MTM x Unins Q16 AE x Unins Q16
## Dependent Var.: btfp_acute btfp_acute
##
## Constant 0.1266*** (0.0138) 0.0981*** (0.0106)
## MTM-Q1 $\times$ UL-Q2 -0.0067 (0.0208)
## MTM-Q1 $\times$ UL-Q3 -0.0709*** (0.0179)
## MTM-Q1 $\times$ UL-Q4 -0.0140 (0.0218)
## MTM-Q2 $\times$ UL-Q1 -0.0266 (0.0180)
## MTM-Q2 $\times$ UL-Q2 -0.0190 (0.0220)
## MTM-Q2 $\times$ UL-Q3 -0.0058 (0.0250)
## MTM-Q2 $\times$ UL-Q4 0.0225 (0.0300)
## MTM-Q3 $\times$ UL-Q1 -0.0321 (0.0214)
## MTM-Q3 $\times$ UL-Q2 -0.0010 (0.0244)
## MTM-Q3 $\times$ UL-Q3 0.0786** (0.0293)
## MTM-Q3 $\times$ UL-Q4 0.0274 (0.0286)
## MTM-Q4 $\times$ UL-Q1 -0.0104 (0.0200)
## MTM-Q4 $\times$ UL-Q2 -0.0009 (0.0255)
## MTM-Q4 $\times$ UL-Q3 0.0335 (0.0296)
## MTM-Q4 $\times$ UL-Q4 0.0783* (0.0371)
## AE-Q1 $\times$ UL-Q1 0.0182 (0.0225)
## AE-Q1 $\times$ UL-Q2 0.0449. (0.0234)
## AE-Q1 $\times$ UL-Q3 0.0870** (0.0267)
## AE-Q1 $\times$ UL-Q4 0.1057*** (0.0294)
## AE-Q2 $\times$ UL-Q1 0.0198 (0.0218)
## AE-Q2 $\times$ UL-Q2 0.0192 (0.0218)
## AE-Q2 $\times$ UL-Q3 0.1378*** (0.0287)
## AE-Q2 $\times$ UL-Q4 0.0475. (0.0278)
## AE-Q3 $\times$ UL-Q1 0.0008 (0.0185)
## AE-Q3 $\times$ UL-Q2 0.0160 (0.0200)
## AE-Q3 $\times$ UL-Q3 -0.0403* (0.0186)
## AE-Q3 $\times$ UL-Q4 0.0421 (0.0267)
## AE-Q4 $\times$ UL-Q2 0.0122 (0.0206)
## AE-Q4 $\times$ UL-Q3 -0.0502** (0.0184)
## AE-Q4 $\times$ UL-Q4 0.0131 (0.0233)
## _____________________ ___________________ __________________
## S.E. type Heteroskedast.-rob. Heteroskedas.-rob.
## Observations 3,747 3,747
## R2 0.09347 0.09867
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
m_btfp_acute_q16_lpm, "Tab_BTFP_Acute_Q16_LPM",
title_text = "BTFP Participation During Acute Crisis: Quartile Interaction Models (LPM)",
notes_text = paste(
"LPM estimates. DV = BTFP participation (Mar 13--May 1, 2023).",
"Col 1: 16 MTM x Uninsured Leverage quartile dummies (ref = Q1-Q1, lowest risk).",
"Col 2: 16 Adjusted Equity x Uninsured Leverage quartile dummies (ref = Q4-Q1, safest).",
"Sample: BTFP users vs. pure non-users. Controls suppressed. Robust SEs."
),
fitstat_use = ~ n + r2
)
# ==============================================================================
# TABLE A4: BTFP Acute — 16-Cell Quartile Interactions (Logit)
# ==============================================================================
m_btfp_acute_q16_logit <- list(
"MTM x Unins Q16" = run_one(df_btfp_acute_s, "btfp_acute", EXPL_MTM_UL_Q16, "logit", CONTROLS_1),
"AE x Unins Q16" = run_one(df_btfp_acute_s, "btfp_acute", EXPL_AE_UL_Q16, "logit", CONTROLS_2)
)
etable(m_btfp_acute_q16_logit, fitstat = ~ n + ll + aic,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
title = "BTFP Acute: 16-Cell Quartile Interactions (Logit)")
## MTM x Unins Q16 AE x Unins Q16
## Dependent Var.: btfp_acute btfp_acute
##
## Constant -2.833*** (0.3673) -3.387*** (0.3612)
## MTM-Q1 $\times$ UL-Q2 0.5390 (0.4585)
## MTM-Q1 $\times$ UL-Q3 -0.7358 (0.5765)
## MTM-Q1 $\times$ UL-Q4 0.6026 (0.4295)
## MTM-Q2 $\times$ UL-Q1 0.0502 (0.4815)
## MTM-Q2 $\times$ UL-Q2 0.3903 (0.4326)
## MTM-Q2 $\times$ UL-Q3 0.5619 (0.4172)
## MTM-Q2 $\times$ UL-Q4 0.7483. (0.4202)
## MTM-Q3 $\times$ UL-Q1 0.1282 (0.4789)
## MTM-Q3 $\times$ UL-Q2 0.5708 (0.4114)
## MTM-Q3 $\times$ UL-Q3 1.109** (0.3992)
## MTM-Q3 $\times$ UL-Q4 0.7256. (0.4126)
## MTM-Q4 $\times$ UL-Q1 0.4754 (0.4159)
## MTM-Q4 $\times$ UL-Q2 0.5091 (0.4110)
## MTM-Q4 $\times$ UL-Q3 0.7524. (0.4109)
## MTM-Q4 $\times$ UL-Q4 1.004* (0.4244)
## AE-Q1 $\times$ UL-Q1 1.072* (0.4320)
## AE-Q1 $\times$ UL-Q2 1.339*** (0.3982)
## AE-Q1 $\times$ UL-Q3 1.642*** (0.3950)
## AE-Q1 $\times$ UL-Q4 1.759*** (0.4042)
## AE-Q2 $\times$ UL-Q1 1.080* (0.4316)
## AE-Q2 $\times$ UL-Q2 1.100** (0.4111)
## AE-Q2 $\times$ UL-Q3 1.945*** (0.3929)
## AE-Q2 $\times$ UL-Q4 1.334** (0.4208)
## AE-Q3 $\times$ UL-Q1 0.6742 (0.4695)
## AE-Q3 $\times$ UL-Q2 1.048* (0.4199)
## AE-Q3 $\times$ UL-Q3 0.3102 (0.4635)
## AE-Q3 $\times$ UL-Q4 1.282** (0.4211)
## AE-Q4 $\times$ UL-Q2 0.9203. (0.4733)
## AE-Q4 $\times$ UL-Q3 -0.1532 (0.5552)
## AE-Q4 $\times$ UL-Q4 1.066* (0.4348)
## _____________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,747 3,747
## Log-Likelihood -1,206.2 -1,199.1
## AIC 2,456.4 2,440.2
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
m_btfp_acute_q16_logit, "Tab_BTFP_Acute_Q16_Logit",
title_text = "BTFP Participation During Acute Crisis: Quartile Interaction Models (Logit)",
notes_text = paste(
"Logit estimates. DV = BTFP participation (Mar 13--May 1, 2023).",
"Col 1: 16 MTM x Uninsured Leverage quartile dummies (ref = Q1-Q1).",
"Col 2: 16 Adjusted Equity x Uninsured Leverage quartile dummies (ref = Q4-Q1).",
"Sample: BTFP users vs. pure non-users. Controls suppressed. Robust SEs."
),
fitstat_use = ~ n + ll + aic
)
# ==============================================================================
# TABLE B1: DW Acute — Core Specs (LPM)
# ==============================================================================
m_dw_acute_lpm <- run_core_specs(df_dw_acute_s, "dw_acute", "lpm")
etable(m_dw_acute_lpm, fitstat = ~ n + r2,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
title = "DW Usage During Acute Crisis (LPM)")
## Base Risk
## Dependent Var.: dw_acute dw_acute
##
## Constant 0.1151*** (0.0052) 0.1001*** (0.0092)
## MTM Loss (z) 0.0161** (0.0059)
## Uninsured Leverage (z) 0.0131* (0.0064)
## MTM $\times$ Uninsured 0.0160** (0.0049)
## Risk 2: Low MTM, High Unins. -0.0024 (0.0132)
## Risk 3: High MTM, Low Unins. 0.0075 (0.0123)
## Risk 4: High MTM, High Unins. 0.0495** (0.0162)
## MTM Q2
## MTM Q3
## MTM Q4 (Highest Loss)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## Unins. Lev. Q2
## Unins. Lev. Q3
## Unins. Lev. Q4 (Highest)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,668 3,678
## R2 0.09557 0.09498
##
## MTM-Q AE-Q
## Dependent Var.: dw_acute dw_acute
##
## Constant 0.0923*** (0.0093) 0.1047*** (0.0093)
## MTM Loss (z)
## Uninsured Leverage (z)
## MTM $\times$ Uninsured
## Risk 2: Low MTM, High Unins.
## Risk 3: High MTM, Low Unins.
## Risk 4: High MTM, High Unins.
## MTM Q2 0.0137 (0.0132)
## MTM Q3 0.0403** (0.0144)
## MTM Q4 (Highest Loss) 0.0288* (0.0146)
## Adj. Equity Q1 (Lowest) 0.0104 (0.0144)
## Adj. Equity Q2 0.0356* (0.0147)
## Adj. Equity Q3 -0.0131 (0.0125)
## Unins. Lev. Q2
## Unins. Lev. Q3
## Unins. Lev. Q4 (Highest)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,678 3,678
## R2 0.09331 0.09430
##
## Unins-Q
## Dependent Var.: dw_acute
##
## Constant 0.1025*** (0.0091)
## MTM Loss (z)
## Uninsured Leverage (z)
## MTM $\times$ Uninsured
## Risk 2: Low MTM, High Unins.
## Risk 3: High MTM, Low Unins.
## Risk 4: High MTM, High Unins.
## MTM Q2
## MTM Q3
## MTM Q4 (Highest Loss)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## Unins. Lev. Q2 0.0036 (0.0121)
## Unins. Lev. Q3 0.0172 (0.0140)
## Unins. Lev. Q4 (Highest) 0.0208 (0.0147)
## _____________________________ __________________
## S.E. type Heteroskedas.-rob.
## Observations 3,678
## R2 0.09198
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
m_dw_acute_lpm, "Tab_DW_Acute_Core_LPM",
title_text = "Discount Window Usage During Acute Crisis (LPM): Core Specifications",
notes_text = paste(
"LPM estimates. DV = DW usage (Mar 13--May 1, 2023).",
"Col 1: continuous MTM, Uninsured, interaction.",
"Col 2: Risk dummies. Col 3: MTM quartiles. Col 4: AE quartiles. Col 5: Unins. quartiles.",
"Sample: DW users vs. pure non-users. Controls suppressed. Robust SEs."
),
fitstat_use = ~ n + r2
)
# ==============================================================================
# TABLE B2: DW Acute — Core Specs (Logit)
# ==============================================================================
m_dw_acute_logit <- run_core_specs(df_dw_acute_s, "dw_acute", "logit")
etable(m_dw_acute_logit, fitstat = ~ n + ll + aic,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
title = "DW Usage During Acute Crisis (Logit)")
## Base Risk
## Dependent Var.: dw_acute dw_acute
##
## Constant -2.426*** (0.0672) -2.801*** (0.1651)
## MTM Loss (z) 0.1944** (0.0679)
## Uninsured Leverage (z) 0.1603** (0.0615)
## MTM $\times$ Uninsured 0.0832 (0.0535)
## Risk 2: Low MTM, High Unins. 0.2964 (0.2011)
## Risk 3: High MTM, Low Unins. 0.3925. (0.2115)
## Risk 4: High MTM, High Unins. 0.7321*** (0.2037)
## MTM Q2
## MTM Q3
## MTM Q4 (Highest Loss)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## Unins. Lev. Q2
## Unins. Lev. Q3
## Unins. Lev. Q4 (Highest)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,668 3,678
## Log-Likelihood -1,080.9 -1,080.6
## AIC 2,181.9 2,181.1
##
## MTM-Q AE-Q
## Dependent Var.: dw_acute dw_acute
##
## Constant -2.787*** (0.1470) -2.705*** (0.1385)
## MTM Loss (z)
## Uninsured Leverage (z)
## MTM $\times$ Uninsured
## Risk 2: Low MTM, High Unins.
## Risk 3: High MTM, Low Unins.
## Risk 4: High MTM, High Unins.
## MTM Q2 0.2767 (0.1873)
## MTM Q3 0.6057** (0.1845)
## MTM Q4 (Highest Loss) 0.5035** (0.1949)
## Adj. Equity Q1 (Lowest) 0.4623* (0.1892)
## Adj. Equity Q2 0.6231*** (0.1732)
## Adj. Equity Q3 0.0239 (0.1769)
## Unins. Lev. Q2
## Unins. Lev. Q3
## Unins. Lev. Q4 (Highest)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,678 3,678
## Log-Likelihood -1,082.5 -1,083.3
## AIC 2,185.0 2,184.6
##
## Unins-Q
## Dependent Var.: dw_acute
##
## Constant -2.773*** (0.1619)
## MTM Loss (z)
## Uninsured Leverage (z)
## MTM $\times$ Uninsured
## Risk 2: Low MTM, High Unins.
## Risk 3: High MTM, Low Unins.
## Risk 4: High MTM, High Unins.
## MTM Q2
## MTM Q3
## MTM Q4 (Highest Loss)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## Unins. Lev. Q2 0.3415. (0.2018)
## Unins. Lev. Q3 0.5005* (0.1965)
## Unins. Lev. Q4 (Highest) 0.4807* (0.1954)
## _____________________________ __________________
## S.E. type Heteroskedas.-rob.
## Observations 3,678
## Log-Likelihood -1,084.6
## AIC 2,189.2
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
m_dw_acute_logit, "Tab_DW_Acute_Core_Logit",
title_text = "Discount Window Usage During Acute Crisis (Logit): Core Specifications",
notes_text = paste(
"Logit estimates. DV = DW usage (Mar 13--May 1, 2023).",
"Columns as in LPM table. Sample: DW users vs. pure non-users.",
"Controls suppressed. Robust SEs."
),
fitstat_use = ~ n + ll + aic
)
# ==============================================================================
# TABLE B3: DW Acute — 16-Cell Quartile Interactions (LPM)
# ==============================================================================
m_dw_acute_q16_lpm <- list(
"MTM x Unins Q16" = run_one(df_dw_acute_s, "dw_acute", EXPL_MTM_UL_Q16, "lpm", CONTROLS_1),
"AE x Unins Q16" = run_one(df_dw_acute_s, "dw_acute", EXPL_AE_UL_Q16, "lpm", CONTROLS_2)
)
etable(m_dw_acute_q16_lpm, fitstat = ~ n + r2,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
title = "DW Acute: 16-Cell Quartile Interactions (LPM)")
## MTM x Unins Q16 AE x Unins Q16
## Dependent Var.: dw_acute dw_acute
##
## Constant 0.0965*** (0.0131) 0.1065*** (0.0117)
## MTM-Q1 $\times$ UL-Q2 0.0013 (0.0191)
## MTM-Q1 $\times$ UL-Q3 0.0023 (0.0222)
## MTM-Q1 $\times$ UL-Q4 -0.0236 (0.0199)
## MTM-Q2 $\times$ UL-Q1 0.0055 (0.0173)
## MTM-Q2 $\times$ UL-Q2 -7.31e-6 (0.0205)
## MTM-Q2 $\times$ UL-Q3 -0.0124 (0.0230)
## MTM-Q2 $\times$ UL-Q4 0.0462 (0.0299)
## MTM-Q3 $\times$ UL-Q1 0.0391. (0.0227)
## MTM-Q3 $\times$ UL-Q2 0.0055 (0.0225)
## MTM-Q3 $\times$ UL-Q3 0.0619* (0.0276)
## MTM-Q3 $\times$ UL-Q4 0.0456. (0.0273)
## MTM-Q4 $\times$ UL-Q1 -0.0215 (0.0171)
## MTM-Q4 $\times$ UL-Q2 0.0317 (0.0254)
## MTM-Q4 $\times$ UL-Q3 0.0491. (0.0286)
## MTM-Q4 $\times$ UL-Q4 0.0677. (0.0348)
## AE-Q1 $\times$ UL-Q1 -0.0201 (0.0191)
## AE-Q1 $\times$ UL-Q2 0.0103 (0.0225)
## AE-Q1 $\times$ UL-Q3 0.0072 (0.0245)
## AE-Q1 $\times$ UL-Q4 0.0370 (0.0272)
## AE-Q2 $\times$ UL-Q1 -0.0059 (0.0199)
## AE-Q2 $\times$ UL-Q2 0.0085 (0.0225)
## AE-Q2 $\times$ UL-Q3 0.0850** (0.0280)
## AE-Q2 $\times$ UL-Q4 0.0446 (0.0285)
## AE-Q3 $\times$ UL-Q1 -0.0014 (0.0188)
## AE-Q3 $\times$ UL-Q2 -0.0174 (0.0187)
## AE-Q3 $\times$ UL-Q3 -0.0359. (0.0205)
## AE-Q3 $\times$ UL-Q4 -0.0033 (0.0249)
## AE-Q4 $\times$ UL-Q2 -0.0011 (0.0209)
## AE-Q4 $\times$ UL-Q3 -0.0032 (0.0244)
## AE-Q4 $\times$ UL-Q4 -0.0073 (0.0237)
## _____________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,678 3,678
## R2 0.09859 0.09874
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
m_dw_acute_q16_lpm, "Tab_DW_Acute_Q16_LPM",
title_text = "DW Usage During Acute Crisis: Quartile Interaction Models (LPM)",
notes_text = paste(
"LPM estimates. DV = DW usage (Mar 13--May 1, 2023).",
"Col 1: 16 MTM x Uninsured Leverage quartile dummies (ref = Q1-Q1).",
"Col 2: 16 Adjusted Equity x Uninsured Leverage quartile dummies (ref = Q4-Q1).",
"Sample: DW users vs. pure non-users. Controls suppressed. Robust SEs."
),
fitstat_use = ~ n + r2
)
# ==============================================================================
# TABLE B4: DW Acute — 16-Cell Quartile Interactions (Logit)
# ==============================================================================
m_dw_acute_q16_logit <- list(
"MTM x Unins Q16" = run_one(df_dw_acute_s, "dw_acute", EXPL_MTM_UL_Q16, "logit", CONTROLS_1),
"AE x Unins Q16" = run_one(df_dw_acute_s, "dw_acute", EXPL_AE_UL_Q16, "logit", CONTROLS_2)
)
etable(m_dw_acute_q16_logit, fitstat = ~ n + ll + aic,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
title = "DW Acute: 16-Cell Quartile Interactions (Logit)")
## MTM x Unins Q16 AE x Unins Q16
## Dependent Var.: dw_acute dw_acute
##
## Constant -3.678*** (0.4998) -3.173*** (0.3393)
## MTM-Q1 $\times$ UL-Q2 1.064. (0.5704)
## MTM-Q1 $\times$ UL-Q3 1.158* (0.5574)
## MTM-Q1 $\times$ UL-Q4 0.9299. (0.5303)
## MTM-Q2 $\times$ UL-Q1 0.9133 (0.6053)
## MTM-Q2 $\times$ UL-Q2 1.110* (0.5640)
## MTM-Q2 $\times$ UL-Q3 1.032. (0.5497)
## MTM-Q2 $\times$ UL-Q4 1.467** (0.5347)
## MTM-Q3 $\times$ UL-Q1 1.634** (0.5612)
## MTM-Q3 $\times$ UL-Q2 1.255* (0.5557)
## MTM-Q3 $\times$ UL-Q3 1.754** (0.5389)
## MTM-Q3 $\times$ UL-Q4 1.500** (0.5360)
## MTM-Q4 $\times$ UL-Q1 0.6455 (0.5905)
## MTM-Q4 $\times$ UL-Q2 1.522** (0.5551)
## MTM-Q4 $\times$ UL-Q3 1.641** (0.5490)
## MTM-Q4 $\times$ UL-Q4 1.688** (0.5587)
## AE-Q1 $\times$ UL-Q1 0.3427 (0.4858)
## AE-Q1 $\times$ UL-Q2 0.9826* (0.4113)
## AE-Q1 $\times$ UL-Q3 0.9660* (0.4130)
## AE-Q1 $\times$ UL-Q4 1.200** (0.4012)
## AE-Q2 $\times$ UL-Q1 0.6069 (0.4561)
## AE-Q2 $\times$ UL-Q2 0.9123* (0.4124)
## AE-Q2 $\times$ UL-Q3 1.492*** (0.3850)
## AE-Q2 $\times$ UL-Q4 1.121** (0.3956)
## AE-Q3 $\times$ UL-Q1 0.5196 (0.4589)
## AE-Q3 $\times$ UL-Q2 0.4977 (0.4241)
## AE-Q3 $\times$ UL-Q3 0.3393 (0.4259)
## AE-Q3 $\times$ UL-Q4 0.6657. (0.3907)
## AE-Q4 $\times$ UL-Q2 0.6339 (0.4526)
## AE-Q4 $\times$ UL-Q3 0.6169 (0.4322)
## AE-Q4 $\times$ UL-Q4 0.6241 (0.3984)
## _____________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,678 3,678
## Log-Likelihood -1,071.1 -1,074.2
## AIC 2,186.2 2,190.5
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
m_dw_acute_q16_logit, "Tab_DW_Acute_Q16_Logit",
title_text = "DW Usage During Acute Crisis: Quartile Interaction Models (Logit)",
notes_text = paste(
"Logit estimates. DV = DW usage (Mar 13--May 1, 2023).",
"Col 1: 16 MTM x UL quartile dummies (ref = Q1-Q1).",
"Col 2: 16 AE x UL quartile dummies (ref = Q4-Q1).",
"Sample: DW users vs. pure non-users. Controls suppressed. Robust SEs."
),
fitstat_use = ~ n + ll + aic
)
# ==============================================================================
# TABLE C1: AE-Based Models — Acute, BTFP (LPM + Logit)
# Columns: AE-Base | AE-Q | AExUL-Q16 (LPM) | AE-Base | AE-Q | AExUL-Q16 (Logit)
# ==============================================================================
m_btfp_ae_lpm <- run_ae_specs(df_btfp_acute_s, "btfp_acute", "lpm")
m_btfp_ae_logit <- run_ae_specs(df_btfp_acute_s, "btfp_acute", "logit")
models_ae_btfp <- c(
setNames(m_btfp_ae_lpm, paste0(names(m_btfp_ae_lpm), " (LPM)")),
setNames(m_btfp_ae_logit, paste0(names(m_btfp_ae_logit), " (Logit)"))
)
etable(models_ae_btfp, fitstat = ~ n + r2 + ll + aic,
drop = "Log|Cash|Loan-to|Wholesale|ROA",
title = "BTFP Acute: Adjusted Equity Specifications")
## AE-Base (LPM) AE-Q (LPM)
## Dependent Var.: btfp_acute btfp_acute
##
## Constant 0.1277*** (0.0053) 0.0953*** (0.0088)
## Adjusted Equity (z) -0.0294*** (0.0054)
## Uninsured Leverage (z) 0.0164* (0.0065)
## Adj. Equity $\times$ Uninsured -0.0160*** (0.0045)
## Adj. Equity Q1 (Lowest) 0.0671*** (0.0151)
## Adj. Equity Q2 0.0605*** (0.0144)
## Adj. Equity Q3 0.0086 (0.0123)
## AE-Q1 $\times$ UL-Q1
## AE-Q1 $\times$ UL-Q2
## AE-Q1 $\times$ UL-Q3
## AE-Q1 $\times$ UL-Q4
## AE-Q2 $\times$ UL-Q1
## AE-Q2 $\times$ UL-Q2
## AE-Q2 $\times$ UL-Q3
## AE-Q2 $\times$ UL-Q4
## AE-Q3 $\times$ UL-Q1
## AE-Q3 $\times$ UL-Q2
## AE-Q3 $\times$ UL-Q3
## AE-Q3 $\times$ UL-Q4
## AE-Q4 $\times$ UL-Q2
## AE-Q4 $\times$ UL-Q3
## AE-Q4 $\times$ UL-Q4
## ______________________________ ___________________ __________________
## Family OLS OLS
## S.E. type Heteroskedast.-rob. Heteroskedas.-rob.
## Observations 3,737 3,747
## R2 0.08934 0.08760
## Log-Likelihood -975.09 -976.97
## AIC 1,968.2 1,971.9
##
## AExUL-Q16 (LPM) AE-Base (Logit)
## Dependent Var.: btfp_acute btfp_acute
##
## Constant 0.0981*** (0.0106) -2.327*** (0.0699)
## Adjusted Equity (z) -0.4367*** (0.0741)
## Uninsured Leverage (z) 0.2017** (0.0630)
## Adj. Equity $\times$ Uninsured -0.0265 (0.0621)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## AE-Q1 $\times$ UL-Q1 0.0182 (0.0225)
## AE-Q1 $\times$ UL-Q2 0.0449. (0.0234)
## AE-Q1 $\times$ UL-Q3 0.0870** (0.0267)
## AE-Q1 $\times$ UL-Q4 0.1057*** (0.0294)
## AE-Q2 $\times$ UL-Q1 0.0198 (0.0218)
## AE-Q2 $\times$ UL-Q2 0.0192 (0.0218)
## AE-Q2 $\times$ UL-Q3 0.1378*** (0.0287)
## AE-Q2 $\times$ UL-Q4 0.0475. (0.0278)
## AE-Q3 $\times$ UL-Q1 0.0008 (0.0185)
## AE-Q3 $\times$ UL-Q2 0.0160 (0.0200)
## AE-Q3 $\times$ UL-Q3 -0.0403* (0.0186)
## AE-Q3 $\times$ UL-Q4 0.0421 (0.0267)
## AE-Q4 $\times$ UL-Q2 0.0122 (0.0206)
## AE-Q4 $\times$ UL-Q3 -0.0502** (0.0184)
## AE-Q4 $\times$ UL-Q4 0.0131 (0.0233)
## ______________________________ __________________ ___________________
## Family OLS Logit
## S.E. type Heteroskedas.-rob. Heteroskedast.-rob.
## Observations 3,747 3,737
## R2 0.09867 --
## Log-Likelihood -954.09 -1,214.6
## AIC 1,950.2 2,447.3
##
## AE-Q (Logit) AExUL-Q16 (Logit)
## Dependent Var.: btfp_acute btfp_acute
##
## Constant -2.835*** (0.1479) -3.387*** (0.3612)
## Adjusted Equity (z)
## Uninsured Leverage (z)
## Adj. Equity $\times$ Uninsured
## Adj. Equity Q1 (Lowest) 0.9410*** (0.1799)
## Adj. Equity Q2 0.8724*** (0.1748)
## Adj. Equity Q3 0.3458. (0.1862)
## AE-Q1 $\times$ UL-Q1 1.072* (0.4320)
## AE-Q1 $\times$ UL-Q2 1.339*** (0.3982)
## AE-Q1 $\times$ UL-Q3 1.642*** (0.3950)
## AE-Q1 $\times$ UL-Q4 1.759*** (0.4042)
## AE-Q2 $\times$ UL-Q1 1.080* (0.4316)
## AE-Q2 $\times$ UL-Q2 1.100** (0.4111)
## AE-Q2 $\times$ UL-Q3 1.945*** (0.3929)
## AE-Q2 $\times$ UL-Q4 1.334** (0.4208)
## AE-Q3 $\times$ UL-Q1 0.6742 (0.4695)
## AE-Q3 $\times$ UL-Q2 1.048* (0.4199)
## AE-Q3 $\times$ UL-Q3 0.3102 (0.4635)
## AE-Q3 $\times$ UL-Q4 1.282** (0.4211)
## AE-Q4 $\times$ UL-Q2 0.9203. (0.4733)
## AE-Q4 $\times$ UL-Q3 -0.1532 (0.5552)
## AE-Q4 $\times$ UL-Q4 1.066* (0.4348)
## ______________________________ __________________ __________________
## Family Logit Logit
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,747 3,747
## R2 -- --
## Log-Likelihood -1,221.3 -1,199.1
## AIC 2,460.7 2,440.2
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
models_ae_btfp, "Tab_BTFP_Acute_AE_LPM_Logit",
title_text = "BTFP Participation: Adjusted Equity Specifications (LPM and Logit)",
notes_text = paste(
"DV = BTFP participation (Mar 13--May 1, 2023).",
"Cols 1--3: LPM. Cols 4--6: Logit.",
"AE-Base: continuous Adj. Equity, Uninsured, interaction.",
"AE-Q: Adj. Equity quartile dummies (Q4 = highest = ref).",
"AExUL-Q16: 16-cell Adj. Equity x Uninsured quartile interaction (ref = Q4-Q1).",
"Controls: CONTROLS\\_2 (excludes book equity). Robust SEs."
),
fitstat_use = ~ n + r2 + ll + aic
)
# ==============================================================================
# TABLE C2: AE-Based Models — Acute, DW (LPM + Logit)
# ==============================================================================
m_dw_ae_lpm <- run_ae_specs(df_dw_acute_s, "dw_acute", "lpm")
m_dw_ae_logit <- run_ae_specs(df_dw_acute_s, "dw_acute", "logit")
models_ae_dw <- c(
setNames(m_dw_ae_lpm, paste0(names(m_dw_ae_lpm), " (LPM)")),
setNames(m_dw_ae_logit, paste0(names(m_dw_ae_logit), " (Logit)"))
)
etable(models_ae_dw, fitstat = ~ n + r2 + ll + aic,
drop = "Log|Cash|Loan-to|Wholesale|ROA",
title = "DW Acute: Adjusted Equity Specifications")
## AE-Base (LPM) AE-Q (LPM)
## Dependent Var.: dw_acute dw_acute
##
## Constant 0.1121*** (0.0051) 0.1047*** (0.0093)
## Adjusted Equity (z) -0.0115* (0.0052)
## Uninsured Leverage (z) 0.0090 (0.0061)
## Adj. Equity $\times$ Uninsured -0.0095* (0.0043)
## Adj. Equity Q1 (Lowest) 0.0104 (0.0144)
## Adj. Equity Q2 0.0356* (0.0147)
## Adj. Equity Q3 -0.0131 (0.0125)
## AE-Q1 $\times$ UL-Q1
## AE-Q1 $\times$ UL-Q2
## AE-Q1 $\times$ UL-Q3
## AE-Q1 $\times$ UL-Q4
## AE-Q2 $\times$ UL-Q1
## AE-Q2 $\times$ UL-Q2
## AE-Q2 $\times$ UL-Q3
## AE-Q2 $\times$ UL-Q4
## AE-Q3 $\times$ UL-Q1
## AE-Q3 $\times$ UL-Q2
## AE-Q3 $\times$ UL-Q3
## AE-Q3 $\times$ UL-Q4
## AE-Q4 $\times$ UL-Q2
## AE-Q4 $\times$ UL-Q3
## AE-Q4 $\times$ UL-Q4
## ______________________________ __________________ __________________
## Family OLS OLS
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,668 3,678
## R2 0.09320 0.09430
## Log-Likelihood -720.98 -716.31
## AIC 1,460.0 1,450.6
##
## AExUL-Q16 (LPM) AE-Base (Logit)
## Dependent Var.: dw_acute dw_acute
##
## Constant 0.1065*** (0.0117) -2.440*** (0.0676)
## Adjusted Equity (z) -0.2891*** (0.0757)
## Uninsured Leverage (z) 0.1383* (0.0592)
## Adj. Equity $\times$ Uninsured -0.0206 (0.0570)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## AE-Q1 $\times$ UL-Q1 -0.0201 (0.0191)
## AE-Q1 $\times$ UL-Q2 0.0103 (0.0225)
## AE-Q1 $\times$ UL-Q3 0.0072 (0.0245)
## AE-Q1 $\times$ UL-Q4 0.0370 (0.0272)
## AE-Q2 $\times$ UL-Q1 -0.0059 (0.0199)
## AE-Q2 $\times$ UL-Q2 0.0085 (0.0225)
## AE-Q2 $\times$ UL-Q3 0.0850** (0.0280)
## AE-Q2 $\times$ UL-Q4 0.0446 (0.0285)
## AE-Q3 $\times$ UL-Q1 -0.0014 (0.0188)
## AE-Q3 $\times$ UL-Q2 -0.0174 (0.0187)
## AE-Q3 $\times$ UL-Q3 -0.0359. (0.0205)
## AE-Q3 $\times$ UL-Q4 -0.0033 (0.0249)
## AE-Q4 $\times$ UL-Q2 -0.0011 (0.0209)
## AE-Q4 $\times$ UL-Q3 -0.0032 (0.0244)
## AE-Q4 $\times$ UL-Q4 -0.0073 (0.0237)
## ______________________________ __________________ ___________________
## Family OLS Logit
## S.E. type Heteroskedas.-rob. Heteroskedast.-rob.
## Observations 3,678 3,668
## R2 0.09874 --
## Log-Likelihood -707.28 -1,082.9
## AIC 1,456.6 2,183.7
##
## AE-Q (Logit) AExUL-Q16 (Logit)
## Dependent Var.: dw_acute dw_acute
##
## Constant -2.705*** (0.1385) -3.173*** (0.3393)
## Adjusted Equity (z)
## Uninsured Leverage (z)
## Adj. Equity $\times$ Uninsured
## Adj. Equity Q1 (Lowest) 0.4623* (0.1892)
## Adj. Equity Q2 0.6231*** (0.1732)
## Adj. Equity Q3 0.0239 (0.1769)
## AE-Q1 $\times$ UL-Q1 0.3427 (0.4858)
## AE-Q1 $\times$ UL-Q2 0.9826* (0.4113)
## AE-Q1 $\times$ UL-Q3 0.9660* (0.4130)
## AE-Q1 $\times$ UL-Q4 1.200** (0.4012)
## AE-Q2 $\times$ UL-Q1 0.6069 (0.4561)
## AE-Q2 $\times$ UL-Q2 0.9123* (0.4124)
## AE-Q2 $\times$ UL-Q3 1.492*** (0.3850)
## AE-Q2 $\times$ UL-Q4 1.121** (0.3956)
## AE-Q3 $\times$ UL-Q1 0.5196 (0.4589)
## AE-Q3 $\times$ UL-Q2 0.4977 (0.4241)
## AE-Q3 $\times$ UL-Q3 0.3393 (0.4259)
## AE-Q3 $\times$ UL-Q4 0.6657. (0.3907)
## AE-Q4 $\times$ UL-Q2 0.6339 (0.4526)
## AE-Q4 $\times$ UL-Q3 0.6169 (0.4322)
## AE-Q4 $\times$ UL-Q4 0.6241 (0.3984)
## ______________________________ __________________ __________________
## Family Logit Logit
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,678 3,678
## R2 -- --
## Log-Likelihood -1,083.3 -1,074.2
## AIC 2,184.6 2,190.5
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
models_ae_dw, "Tab_DW_Acute_AE_LPM_Logit",
title_text = "DW Usage: Adjusted Equity Specifications (LPM and Logit)",
notes_text = paste(
"DV = DW usage (Mar 13--May 1, 2023).",
"Cols 1--3: LPM. Cols 4--6: Logit.",
"AE-Base, AE-Q, AExUL-Q16 as defined above.",
"Controls: CONTROLS\\_2 (excludes book equity). Robust SEs."
),
fitstat_use = ~ n + r2 + ll + aic
)
# ==============================================================================
# SECTION 3: TEMPORAL ANALYSIS — BTFP
# ==============================================================================
cat("\n========================================\n")
##
## ========================================
cat("SECTION 3: TEMPORAL BTFP REGRESSIONS\n")
## SECTION 3: TEMPORAL BTFP REGRESSIONS
cat("========================================\n")
## ========================================
# --- 3A. Prepare temporal samples ---------------------------------------------
df_btfp_acute_s <- df_acute %>% filter(btfp_acute == 1 | non_user == 1)
df_btfp_post_s <- df_post %>% filter(btfp_post == 1 | non_user == 1)
df_btfp_arb_s <- df_arb %>% filter(btfp_arb == 1 | non_user == 1)
df_btfp_wind_s <- df_wind %>% filter(btfp_wind == 1 | non_user == 1)
# ==============================================================================
# TABLE D1: BTFP Temporal — Base + Risk (LPM)
# Columns: Acute(Base) | Acute(Risk) | Post(Base) | Post(Risk) | Arb(Base) | Arb(Risk) | Wind(Base) | Wind(Risk)
# ==============================================================================
m_temp_acute_lpm <- run_temporal_pair(df_btfp_acute_s, "btfp_acute", "lpm")
m_temp_post_lpm <- run_temporal_pair(df_btfp_post_s, "btfp_post", "lpm")
m_temp_arb_lpm <- run_temporal_pair(df_btfp_arb_s, "btfp_arb", "lpm")
m_temp_wind_lpm <- run_temporal_pair(df_btfp_wind_s, "btfp_wind", "lpm")
models_btfp_temp_lpm <- list(
"Acute (Base)" = m_temp_acute_lpm$Base, "Acute (Risk)" = m_temp_acute_lpm$Risk,
"Post (Base)" = m_temp_post_lpm$Base, "Post (Risk)" = m_temp_post_lpm$Risk,
"Arb (Base)" = m_temp_arb_lpm$Base, "Arb (Risk)" = m_temp_arb_lpm$Risk,
"Wind (Base)" = m_temp_wind_lpm$Base, "Wind (Risk)" = m_temp_wind_lpm$Risk
)
# Extra lines for N and baseline info
temp_extra_lpm <- list(
"__N (BTFP=1)" = c(
sum(df_btfp_acute_s$btfp_acute), sum(df_btfp_acute_s$btfp_acute),
sum(df_btfp_post_s$btfp_post), sum(df_btfp_post_s$btfp_post),
sum(df_btfp_arb_s$btfp_arb), sum(df_btfp_arb_s$btfp_arb),
sum(df_btfp_wind_s$btfp_wind), sum(df_btfp_wind_s$btfp_wind)
),
"__Baseline" = rep(c("2022Q4", "2022Q4", "2022Q4", "2022Q4",
"2023Q3", "2023Q3", "2023Q4", "2023Q4"))
)
etable(models_btfp_temp_lpm, fitstat = ~ n + r2,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
extralines = temp_extra_lpm,
title = "BTFP Temporal Analysis (LPM)")
## Acute (Base) Acute (Risk)
## Dependent Var.: btfp_acute btfp_acute
##
## Constant 0.1316*** (0.0055) 0.1160*** (0.0096)
## MTM Loss (z) 0.0243*** (0.0059)
## Uninsured Leverage (z) 0.0199** (0.0068)
## MTM $\times$ Uninsured 0.0181*** (0.0051)
## Risk 2: Low MTM, High Unins. -0.0060 (0.0136)
## Risk 3: High MTM, Low Unins. 0.0014 (0.0131)
## Risk 4: High MTM, High Unins. 0.0622*** (0.0170)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,737 3,747
## R2 0.09017 0.08930
## N (BTFP=1) 462 462
## Baseline 2022Q4 2022Q4
##
## Post (Base) Post (Risk)
## Dependent Var.: btfp_post btfp_post
##
## Constant 0.2363*** (0.0071) 0.2188*** (0.0138)
## MTM Loss (z) 0.0259** (0.0079)
## Uninsured Leverage (z) 0.0191* (0.0085)
## MTM $\times$ Uninsured 0.0167** (0.0062)
## Risk 2: Low MTM, High Unins. -0.0054 (0.0191)
## Risk 3: High MTM, Low Unins. 0.0077 (0.0187)
## Risk 4: High MTM, High Unins. 0.0627** (0.0226)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,437 3,447
## R2 0.08472 0.08459
## N (BTFP=1) 775 775
## Baseline 2022Q4 2022Q4
##
## Arb (Base) Arb (Risk)
## Dependent Var.: btfp_arb btfp_arb
##
## Constant 0.1907*** (0.0058) 0.1616*** (0.0109)
## MTM Loss (z) 0.0223** (0.0068)
## Uninsured Leverage (z) 0.0135* (0.0066)
## MTM $\times$ Uninsured 0.0082 (0.0051)
## Risk 2: Low MTM, High Unins. 0.0292. (0.0157)
## Risk 3: High MTM, Low Unins. 0.0404* (0.0161)
## Risk 4: High MTM, High Unins. 0.0411* (0.0180)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 4,038 4,055
## R2 0.14242 0.14127
## N (BTFP=1) 766 766
## Baseline 2023Q3 2023Q3
##
## Wind (Base) Wind (Risk)
## Dependent Var.: btfp_wind btfp_wind
##
## Constant 0.0568*** (0.0036) 0.0367*** (0.0061)
## MTM Loss (z) 0.0023 (0.0042)
## Uninsured Leverage (z) 0.0073. (0.0043)
## MTM $\times$ Uninsured -0.0010 (0.0033)
## Risk 2: Low MTM, High Unins. 0.0288** (0.0095)
## Risk 3: High MTM, Low Unins. 0.0185. (0.0096)
## Risk 4: High MTM, High Unins. 0.0323** (0.0108)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 4,043 4,061
## R2 0.06273 0.06431
## N (BTFP=1) 229 229
## Baseline 2023Q4 2023Q4
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
models_btfp_temp_lpm, "Tab_BTFP_Temporal_LPM",
title_text = "BTFP Participation Across Program Periods (LPM)",
notes_text = paste(
"LPM estimates. Acute: Mar 13--May 1, 2023. Post-Acute: May 2--Oct 31, 2023.",
"Arbitrage: Nov 1, 2023--Jan 24, 2024. Wind-down: Jan 25--Mar 11, 2024.",
"Base: continuous MTM, Uninsured, interaction. Risk: Risk 2,3,4 dummies.",
"Acute/Post use 2022Q4 baseline; Arb uses 2023Q3; Wind uses 2023Q4.",
"Sample: BTFP users vs. pure non-users. Controls suppressed. Robust SEs."
),
fitstat_use = ~ n + r2,
extra_lines = temp_extra_lpm
)
# ==============================================================================
# TABLE D2: BTFP Temporal — Base + Risk (Logit)
# ==============================================================================
m_temp_acute_logit <- run_temporal_pair(df_btfp_acute_s, "btfp_acute", "logit")
m_temp_post_logit <- run_temporal_pair(df_btfp_post_s, "btfp_post", "logit")
m_temp_arb_logit <- run_temporal_pair(df_btfp_arb_s, "btfp_arb", "logit")
m_temp_wind_logit <- run_temporal_pair(df_btfp_wind_s, "btfp_wind", "logit")
models_btfp_temp_logit <- list(
"Acute (Base)" = m_temp_acute_logit$Base, "Acute (Risk)" = m_temp_acute_logit$Risk,
"Post (Base)" = m_temp_post_logit$Base, "Post (Risk)" = m_temp_post_logit$Risk,
"Arb (Base)" = m_temp_arb_logit$Base, "Arb (Risk)" = m_temp_arb_logit$Risk,
"Wind (Base)" = m_temp_wind_logit$Base, "Wind (Risk)" = m_temp_wind_logit$Risk
)
etable(models_btfp_temp_logit, fitstat = ~ n + ll + aic,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
extralines = temp_extra_lpm,
title = "BTFP Temporal Analysis (Logit)")
## Acute (Base) Acute (Risk)
## Dependent Var.: btfp_acute btfp_acute
##
## Constant -2.320*** (0.0711) -2.553*** (0.1482)
## MTM Loss (z) 0.2141*** (0.0622)
## Uninsured Leverage (z) 0.2089*** (0.0626)
## MTM $\times$ Uninsured 0.0461 (0.0569)
## Risk 2: Low MTM, High Unins. 0.1922 (0.1884)
## Risk 3: High MTM, Low Unins. 0.1765 (0.1843)
## Risk 4: High MTM, High Unins. 0.5916** (0.1805)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,737 3,747
## Log-Likelihood -1,214.5 -1,217.2
## AIC 2,449.0 2,454.5
## N (BTFP=1) 462 462
## Baseline 2022Q4 2022Q4
##
## Post (Base) Post (Risk)
## Dependent Var.: btfp_post btfp_post
##
## Constant -1.366*** (0.0481) -1.489*** (0.1032)
## MTM Loss (z) 0.1307** (0.0493)
## Uninsured Leverage (z) 0.1222* (0.0514)
## MTM $\times$ Uninsured 0.0319 (0.0433)
## Risk 2: Low MTM, High Unins. 0.0719 (0.1426)
## Risk 3: High MTM, Low Unins. 0.0903 (0.1332)
## Risk 4: High MTM, High Unins. 0.3256* (0.1399)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,437 3,447
## Log-Likelihood -1,671.2 -1,673.3
## AIC 3,362.5 3,366.6
## N (BTFP=1) 775 775
## Baseline 2022Q4 2022Q4
##
## Arb (Base) Arb (Risk)
## Dependent Var.: btfp_arb btfp_arb
##
## Constant -1.805*** (0.0533) -2.109*** (0.1137)
## MTM Loss (z) 0.1648** (0.0526)
## Uninsured Leverage (z) 0.1433** (0.0510)
## MTM $\times$ Uninsured -0.0141 (0.0438)
## Risk 2: Low MTM, High Unins. 0.4084** (0.1483)
## Risk 3: High MTM, Low Unins. 0.4011** (0.1447)
## Risk 4: High MTM, High Unins. 0.3881** (0.1491)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 4,038 4,055
## Log-Likelihood -1,661.2 -1,664.2
## AIC 3,342.4 3,348.4
## N (BTFP=1) 766 766
## Baseline 2023Q3 2023Q3
##
## Wind (Base) Wind (Risk)
## Dependent Var.: btfp_wind btfp_wind
##
## Constant -3.183*** (0.0857) -3.726*** (0.2191)
## MTM Loss (z) 0.0421 (0.0898)
## Uninsured Leverage (z) 0.1852* (0.0869)
## MTM $\times$ Uninsured -0.0689 (0.0743)
## Risk 2: Low MTM, High Unins. 0.7941** (0.2709)
## Risk 3: High MTM, Low Unins. 0.5213* (0.2577)
## Risk 4: High MTM, High Unins. 0.7747** (0.2690)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 4,043 4,061
## Log-Likelihood -779.83 -776.67
## AIC 1,579.7 1,573.3
## N (BTFP=1) 229 229
## Baseline 2023Q4 2023Q4
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
models_btfp_temp_logit, "Tab_BTFP_Temporal_Logit",
title_text = "BTFP Participation Across Program Periods (Logit)",
notes_text = paste(
"Logit estimates. Period definitions as in LPM table.",
"Sample: BTFP users vs. pure non-users. Controls suppressed. Robust SEs."
),
fitstat_use = ~ n + ll + aic,
extra_lines = temp_extra_lpm
)
# ==============================================================================
# TABLE D3: BTFP Temporal — Quartile Models (LPM)
# For each period: MTM-Q | AE-Q | Unins-Q
# ==============================================================================
m_tq_acute_lpm <- list(
"Acute: MTM-Q" = run_one(df_btfp_acute_s, "btfp_acute", EXPL_MTM_Q, "lpm", CONTROLS_1),
"Acute: AE-Q" = run_one(df_btfp_acute_s, "btfp_acute", EXPL_AE_Q, "lpm", CONTROLS_2),
"Acute: Unins-Q" = run_one(df_btfp_acute_s, "btfp_acute", EXPL_UNINS_Q, "lpm", CONTROLS_1)
)
m_tq_post_lpm <- list(
"Post: MTM-Q" = run_one(df_btfp_post_s, "btfp_post", EXPL_MTM_Q, "lpm", CONTROLS_1),
"Post: AE-Q" = run_one(df_btfp_post_s, "btfp_post", EXPL_AE_Q, "lpm", CONTROLS_2),
"Post: Unins-Q" = run_one(df_btfp_post_s, "btfp_post", EXPL_UNINS_Q, "lpm", CONTROLS_1)
)
m_tq_arb_lpm <- list(
"Arb: MTM-Q" = run_one(df_btfp_arb_s, "btfp_arb", EXPL_MTM_Q, "lpm", CONTROLS_1),
"Arb: AE-Q" = run_one(df_btfp_arb_s, "btfp_arb", EXPL_AE_Q, "lpm", CONTROLS_2),
"Arb: Unins-Q" = run_one(df_btfp_arb_s, "btfp_arb", EXPL_UNINS_Q, "lpm", CONTROLS_1)
)
m_tq_wind_lpm <- list(
"Wind: MTM-Q" = run_one(df_btfp_wind_s, "btfp_wind", EXPL_MTM_Q, "lpm", CONTROLS_1),
"Wind: AE-Q" = run_one(df_btfp_wind_s, "btfp_wind", EXPL_AE_Q, "lpm", CONTROLS_2),
"Wind: Unins-Q" = run_one(df_btfp_wind_s, "btfp_wind", EXPL_UNINS_Q, "lpm", CONTROLS_1)
)
models_btfp_temp_q_lpm <- c(m_tq_acute_lpm, m_tq_post_lpm, m_tq_arb_lpm, m_tq_wind_lpm)
temp_q_extra <- list(
"__N (BTFP=1)" = c(
rep(sum(df_btfp_acute_s$btfp_acute), 3),
rep(sum(df_btfp_post_s$btfp_post), 3),
rep(sum(df_btfp_arb_s$btfp_arb), 3),
rep(sum(df_btfp_wind_s$btfp_wind), 3)
),
"__Baseline" = c(
rep("2022Q4", 3), rep("2022Q4", 3), rep("2023Q3", 3), rep("2023Q4", 3)
)
)
etable(models_btfp_temp_q_lpm, fitstat = ~ n + r2,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
extralines = temp_q_extra,
title = "BTFP Temporal: Quartile Specifications (LPM)")
## Acute: MTM-Q Acute: AE-Q
## Dependent Var.: btfp_acute btfp_acute
##
## Constant 0.1074*** (0.0091) 0.0953*** (0.0088)
## MTM Q2 0.0120 (0.0130)
## MTM Q3 0.0391** (0.0145)
## MTM Q4 (Highest Loss) 0.0369* (0.0146)
## Adj. Equity Q1 (Lowest) 0.0671*** (0.0151)
## Adj. Equity Q2 0.0605*** (0.0144)
## Adj. Equity Q3 0.0086 (0.0123)
## Unins. Lev. Q2
## Unins. Lev. Q3
## Unins. Lev. Q4 (Highest)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,747 3,747
## R2 0.08552 0.08760
## N (BTFP=1) 462 462
## Baseline 2022Q4 2022Q4
##
## Acute: Unins-Q Post: MTM-Q
## Dependent Var.: btfp_acute btfp_post
##
## Constant 0.1138*** (0.0099) 0.2040*** (0.0134)
## MTM Q2 0.0237 (0.0184)
## MTM Q3 0.0349. (0.0200)
## MTM Q4 (Highest Loss) 0.0615** (0.0206)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## Unins. Lev. Q2 0.0056 (0.0134)
## Unins. Lev. Q3 0.0222 (0.0148)
## Unins. Lev. Q4 (Highest) 0.0343* (0.0162)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,747 3,447
## R2 0.08488 0.08342
## N (BTFP=1) 462 775
## Baseline 2022Q4 2022Q4
##
## Post: AE-Q Post: Unins-Q
## Dependent Var.: btfp_post btfp_post
##
## Constant 0.1823*** (0.0128) 0.2005*** (0.0136)
## MTM Q2
## MTM Q3
## MTM Q4 (Highest Loss)
## Adj. Equity Q1 (Lowest) 0.0843*** (0.0208)
## Adj. Equity Q2 0.0793*** (0.0195)
## Adj. Equity Q3 0.0443* (0.0179)
## Unins. Lev. Q2 0.0433* (0.0189)
## Unins. Lev. Q3 0.0539** (0.0200)
## Unins. Lev. Q4 (Highest) 0.0370. (0.0212)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,447 3,447
## R2 0.08273 0.08329
## N (BTFP=1) 775 775
## Baseline 2022Q4 2022Q4
##
## Arb: MTM-Q Arb: AE-Q
## Dependent Var.: btfp_arb btfp_arb
##
## Constant 0.1536*** (0.0107) 0.1511*** (0.0105)
## MTM Q2 0.0415** (0.0153)
## MTM Q3 0.0336* (0.0162)
## MTM Q4 (Highest Loss) 0.0699*** (0.0176)
## Adj. Equity Q1 (Lowest) 0.0760*** (0.0182)
## Adj. Equity Q2 0.0398* (0.0157)
## Adj. Equity Q3 0.0386* (0.0150)
## Unins. Lev. Q2
## Unins. Lev. Q3
## Unins. Lev. Q4 (Highest)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 4,055 4,055
## R2 0.14291 0.14243
## N (BTFP=1) 766 766
## Baseline 2023Q3 2023Q3
##
## Arb: Unins-Q Wind: MTM-Q
## Dependent Var.: btfp_arb btfp_wind
##
## Constant 0.1655*** (0.0113) 0.0552*** (0.0067)
## MTM Q2 -0.0053 (0.0089)
## MTM Q3 0.0133 (0.0102)
## MTM Q4 (Highest Loss) -0.0014 (0.0106)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## Unins. Lev. Q2 0.0341* (0.0158)
## Unins. Lev. Q3 0.0307. (0.0165)
## Unins. Lev. Q4 (Highest) 0.0313. (0.0175)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 4,055 4,061
## R2 0.14093 0.06285
## N (BTFP=1) 766 229
## Baseline 2023Q3 2023Q4
##
## Wind: AE-Q Wind: Unins-Q
## Dependent Var.: btfp_wind btfp_wind
##
## Constant 0.0462*** (0.0063) 0.0471*** (0.0073)
## MTM Q2
## MTM Q3
## MTM Q4 (Highest Loss)
## Adj. Equity Q1 (Lowest) 0.0199. (0.0112)
## Adj. Equity Q2 0.0158 (0.0098)
## Adj. Equity Q3 0.0070 (0.0084)
## Unins. Lev. Q2 -0.0011 (0.0100)
## Unins. Lev. Q3 0.0192. (0.0108)
## Unins. Lev. Q4 (Highest) 0.0206. (0.0115)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 4,061 4,061
## R2 0.06184 0.06361
## N (BTFP=1) 229 229
## Baseline 2023Q4 2023Q4
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
models_btfp_temp_q_lpm, "Tab_BTFP_Temporal_Quartile_LPM",
title_text = "BTFP Participation: Quartile Specifications Across Periods (LPM)",
notes_text = paste(
"LPM estimates. Each period has 3 columns: MTM quartile, AE quartile, Uninsured quartile.",
"Reference categories: MTM Q1 (lowest), AE Q4 (highest), Unins Q1 (lowest).",
"Acute/Post use 2022Q4; Arb uses 2023Q3; Wind uses 2023Q4.",
"Sample: BTFP users vs. pure non-users. Controls suppressed. Robust SEs."
),
fitstat_use = ~ n + r2,
extra_lines = temp_q_extra
)
# ==============================================================================
# TABLE D4: BTFP Temporal — Quartile Models (Logit)
# ==============================================================================
m_tq_acute_logit <- list(
"Acute: MTM-Q" = run_one(df_btfp_acute_s, "btfp_acute", EXPL_MTM_Q, "logit", CONTROLS_1),
"Acute: AE-Q" = run_one(df_btfp_acute_s, "btfp_acute", EXPL_AE_Q, "logit", CONTROLS_2),
"Acute: Unins-Q" = run_one(df_btfp_acute_s, "btfp_acute", EXPL_UNINS_Q, "logit", CONTROLS_1)
)
m_tq_post_logit <- list(
"Post: MTM-Q" = run_one(df_btfp_post_s, "btfp_post", EXPL_MTM_Q, "logit", CONTROLS_1),
"Post: AE-Q" = run_one(df_btfp_post_s, "btfp_post", EXPL_AE_Q, "logit", CONTROLS_2),
"Post: Unins-Q" = run_one(df_btfp_post_s, "btfp_post", EXPL_UNINS_Q, "logit", CONTROLS_1)
)
m_tq_arb_logit <- list(
"Arb: MTM-Q" = run_one(df_btfp_arb_s, "btfp_arb", EXPL_MTM_Q, "logit", CONTROLS_1),
"Arb: AE-Q" = run_one(df_btfp_arb_s, "btfp_arb", EXPL_AE_Q, "logit", CONTROLS_2),
"Arb: Unins-Q" = run_one(df_btfp_arb_s, "btfp_arb", EXPL_UNINS_Q, "logit", CONTROLS_1)
)
m_tq_wind_logit <- list(
"Wind: MTM-Q" = run_one(df_btfp_wind_s, "btfp_wind", EXPL_MTM_Q, "logit", CONTROLS_1),
"Wind: AE-Q" = run_one(df_btfp_wind_s, "btfp_wind", EXPL_AE_Q, "logit", CONTROLS_2),
"Wind: Unins-Q" = run_one(df_btfp_wind_s, "btfp_wind", EXPL_UNINS_Q, "logit", CONTROLS_1)
)
models_btfp_temp_q_logit <- c(m_tq_acute_logit, m_tq_post_logit, m_tq_arb_logit, m_tq_wind_logit)
etable(models_btfp_temp_q_logit, fitstat = ~ n + ll + aic,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
extralines = temp_q_extra,
title = "BTFP Temporal: Quartile Specifications (Logit)")
## Acute: MTM-Q Acute: AE-Q
## Dependent Var.: btfp_acute btfp_acute
##
## Constant -2.604*** (0.1446) -2.835*** (0.1479)
## MTM Q2 0.2571 (0.1785)
## MTM Q3 0.4756** (0.1743)
## MTM Q4 (Highest Loss) 0.4226* (0.1770)
## Adj. Equity Q1 (Lowest) 0.9410*** (0.1799)
## Adj. Equity Q2 0.8724*** (0.1748)
## Adj. Equity Q3 0.3458. (0.1862)
## Unins. Lev. Q2
## Unins. Lev. Q3
## Unins. Lev. Q4 (Highest)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,747 3,747
## Log-Likelihood -1,220.9 -1,221.3
## AIC 2,461.7 2,460.7
## N (BTFP=1) 462 462
## Baseline 2022Q4 2022Q4
##
## Acute: Unins-Q Post: MTM-Q
## Dependent Var.: btfp_acute btfp_post
##
## Constant -2.620*** (0.1482) -1.580*** (0.1091)
## MTM Q2 0.2206 (0.1406)
## MTM Q3 0.2517. (0.1418)
## MTM Q4 (Highest Loss) 0.3594* (0.1426)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## Unins. Lev. Q2 0.2484 (0.1793)
## Unins. Lev. Q3 0.4123* (0.1763)
## Unins. Lev. Q4 (Highest) 0.5203** (0.1848)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,747 3,447
## Log-Likelihood -1,220.0 -1,673.7
## AIC 2,460.0 3,367.4
## N (BTFP=1) 462 775
## Baseline 2022Q4 2022Q4
##
## Post: AE-Q Post: Unins-Q
## Dependent Var.: btfp_post btfp_post
##
## Constant -1.782*** (0.1111) -1.657*** (0.1051)
## MTM Q2
## MTM Q3
## MTM Q4 (Highest Loss)
## Adj. Equity Q1 (Lowest) 0.6258*** (0.1431)
## Adj. Equity Q2 0.6198*** (0.1392)
## Adj. Equity Q3 0.4299** (0.1431)
## Unins. Lev. Q2 0.3628** (0.1338)
## Unins. Lev. Q3 0.4155** (0.1355)
## Unins. Lev. Q4 (Highest) 0.3338* (0.1454)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,447 3,447
## Log-Likelihood -1,677.1 -1,671.4
## AIC 3,372.3 3,362.8
## N (BTFP=1) 775 775
## Baseline 2022Q4 2022Q4
##
## Arb: MTM-Q Arb: AE-Q
## Dependent Var.: btfp_arb btfp_arb
##
## Constant -2.142*** (0.1192) -2.209*** (0.1182)
## MTM Q2 0.4397** (0.1470)
## MTM Q3 0.3560* (0.1506)
## MTM Q4 (Highest Loss) 0.5631*** (0.1553)
## Adj. Equity Q1 (Lowest) 0.7071*** (0.1554)
## Adj. Equity Q2 0.4756** (0.1467)
## Adj. Equity Q3 0.4859** (0.1486)
## Unins. Lev. Q2
## Unins. Lev. Q3
## Unins. Lev. Q4 (Highest)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 4,055 4,055
## Log-Likelihood -1,662.0 -1,665.6
## AIC 3,344.1 3,349.1
## N (BTFP=1) 766 766
## Baseline 2023Q3 2023Q3
##
## Arb: Unins-Q Wind: MTM-Q
## Dependent Var.: btfp_arb btfp_wind
##
## Constant -2.068*** (0.1101) -3.230*** (0.1943)
## MTM Q2 -0.0237 (0.2494)
## MTM Q3 0.2799 (0.2433)
## MTM Q4 (Highest Loss) -0.0198 (0.2620)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## Unins. Lev. Q2 0.3523** (0.1361)
## Unins. Lev. Q3 0.3350* (0.1394)
## Unins. Lev. Q4 (Highest) 0.3677* (0.1467)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 4,055 4,061
## Log-Likelihood -1,664.9 -780.83
## AIC 3,349.7 1,581.7
## N (BTFP=1) 766 229
## Baseline 2023Q3 2023Q4
##
## Wind: AE-Q Wind: Unins-Q
## Dependent Var.: btfp_wind btfp_wind
##
## Constant -3.496*** (0.2000) -3.413*** (0.1846)
## MTM Q2
## MTM Q3
## MTM Q4 (Highest Loss)
## Adj. Equity Q1 (Lowest) 0.5235* (0.2537)
## Adj. Equity Q2 0.5108* (0.2479)
## Adj. Equity Q3 0.3323 (0.2569)
## Unins. Lev. Q2 0.0358 (0.2303)
## Unins. Lev. Q3 0.4166. (0.2320)
## Unins. Lev. Q4 (Highest) 0.4752. (0.2505)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 4,061 4,061
## Log-Likelihood -783.70 -778.97
## AIC 1,585.4 1,577.9
## N (BTFP=1) 229 229
## Baseline 2023Q4 2023Q4
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
models_btfp_temp_q_logit, "Tab_BTFP_Temporal_Quartile_Logit",
title_text = "BTFP Participation: Quartile Specifications Across Periods (Logit)",
notes_text = paste(
"Logit estimates. Each period has 3 columns: MTM quartile, AE quartile, Uninsured quartile.",
"Reference categories: MTM Q1 (lowest), AE Q4 (highest), Unins Q1 (lowest).",
"Acute/Post use 2022Q4; Arb uses 2023Q3; Wind uses 2023Q4.",
"Sample: BTFP users vs. pure non-users. Controls suppressed. Robust SEs."
),
fitstat_use = ~ n + ll + aic,
extra_lines = temp_q_extra
)
# ==============================================================================
# SECTION 4: TEMPORAL ANALYSIS — DW
# ==============================================================================
cat("\n========================================\n")
##
## ========================================
cat("SECTION 4: TEMPORAL DW REGRESSIONS\n")
## SECTION 4: TEMPORAL DW REGRESSIONS
cat("========================================\n")
## ========================================
# --- 4A. Prepare DW temporal samples ------------------------------------------
df_dw_pre_s <- df_prebtfp %>% filter(dw_prebtfp == 1 | non_user == 1)
df_dw_acute_s <- df_acute %>% filter(dw_acute == 1 | non_user == 1)
# DW post-acute (uses 2022Q4 baseline, data through Dec 2023)
df_dw_post_s <- df_post %>% filter(dw_post == 1 | non_user == 1)
# ==============================================================================
# TABLE E1: DW Temporal — Base + Risk (LPM)
# ==============================================================================
m_dw_pre_lpm <- run_temporal_pair(df_dw_pre_s, "dw_prebtfp", "lpm")
m_dw_ac_lpm <- run_temporal_pair(df_dw_acute_s, "dw_acute", "lpm")
m_dw_post_lpm <- run_temporal_pair(df_dw_post_s, "dw_post", "lpm")
models_dw_temp_lpm <- list(
"Pre-BTFP (Base)" = m_dw_pre_lpm$Base, "Pre-BTFP (Risk)" = m_dw_pre_lpm$Risk,
"Acute (Base)" = m_dw_ac_lpm$Base, "Acute (Risk)" = m_dw_ac_lpm$Risk,
"Post (Base)" = m_dw_post_lpm$Base, "Post (Risk)" = m_dw_post_lpm$Risk
)
dw_temp_extra <- list(
"__N (DW=1)" = c(
rep(sum(df_dw_pre_s$dw_prebtfp), 2),
rep(sum(df_dw_acute_s$dw_acute), 2),
rep(sum(df_dw_post_s$dw_post), 2)
),
"__Baseline" = rep("2022Q4", 6)
)
etable(models_dw_temp_lpm, fitstat = ~ n + r2,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
extralines = dw_temp_extra,
title = "DW Temporal Analysis (LPM)")
## Pre-BTFP (Base) Pre-BTFP (Risk)
## Dependent Var.: dw_prebtfp dw_prebtfp
##
## Constant 0.0259*** (0.0025) 0.0267*** (0.0048)
## MTM Loss (z) 0.0009 (0.0032)
## Uninsured Leverage (z) 0.0009 (0.0031)
## MTM $\times$ Uninsured 0.0024 (0.0024)
## Risk 2: Low MTM, High Unins. -0.0044 (0.0067)
## Risk 3: High MTM, Low Unins. 0.0010 (0.0063)
## Risk 4: High MTM, High Unins. -0.0007 (0.0080)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,988 3,998
## R2 0.03566 0.03550
## N (DW=1) 100 100
## Baseline 2022Q4 2022Q4
##
## Acute (Base) Acute (Risk)
## Dependent Var.: dw_acute dw_acute
##
## Constant 0.1151*** (0.0052) 0.1001*** (0.0092)
## MTM Loss (z) 0.0161** (0.0059)
## Uninsured Leverage (z) 0.0131* (0.0064)
## MTM $\times$ Uninsured 0.0160** (0.0049)
## Risk 2: Low MTM, High Unins. -0.0024 (0.0132)
## Risk 3: High MTM, Low Unins. 0.0075 (0.0123)
## Risk 4: High MTM, High Unins. 0.0495** (0.0162)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,668 3,678
## R2 0.09557 0.09498
## N (DW=1) 393 393
## Baseline 2022Q4 2022Q4
##
## Post (Base) Post (Risk)
## Dependent Var.: dw_post dw_post
##
## Constant 0.2568*** (0.0070) 0.2313*** (0.0135)
## MTM Loss (z) 0.0118 (0.0080)
## Uninsured Leverage (z) 0.0204* (0.0086)
## MTM $\times$ Uninsured 0.0193** (0.0063)
## Risk 2: Low MTM, High Unins. 0.0238 (0.0195)
## Risk 3: High MTM, Low Unins. 0.0034 (0.0184)
## Risk 4: High MTM, High Unins. 0.0672** (0.0224)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,549 3,559
## R2 0.12864 0.12902
## N (DW=1) 887 887
## Baseline 2022Q4 2022Q4
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
models_dw_temp_lpm, "Tab_DW_Temporal_LPM",
title_text = "Discount Window Usage: Pre-BTFP, Acute, and Post-Acute (LPM)",
notes_text = paste(
"LPM estimates. Pre-BTFP: Mar 1--12, 2023. Acute: Mar 13--May 1, 2023.",
"Post-Acute: May 2--Oct 31, 2023 (DW data through Dec 2023).",
"Base: continuous MTM, Uninsured, interaction. Risk: Risk 2,3,4 dummies.",
"All use 2022Q4 baseline. Sample: DW users vs. pure non-users.",
"Controls suppressed. Robust SEs."
),
fitstat_use = ~ n + r2,
extra_lines = dw_temp_extra
)
# ==============================================================================
# TABLE E2: DW Temporal — Base + Risk (Logit)
# ==============================================================================
m_dw_pre_logit <- run_temporal_pair(df_dw_pre_s, "dw_prebtfp", "logit")
m_dw_ac_logit <- run_temporal_pair(df_dw_acute_s, "dw_acute", "logit")
m_dw_post_logit <- run_temporal_pair(df_dw_post_s, "dw_post", "logit")
models_dw_temp_logit <- list(
"Pre-BTFP (Base)" = m_dw_pre_logit$Base, "Pre-BTFP (Risk)" = m_dw_pre_logit$Risk,
"Acute (Base)" = m_dw_ac_logit$Base, "Acute (Risk)" = m_dw_ac_logit$Risk,
"Post (Base)" = m_dw_post_logit$Base, "Post (Risk)" = m_dw_post_logit$Risk
)
etable(models_dw_temp_logit, fitstat = ~ n + ll + aic,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
extralines = dw_temp_extra,
title = "DW Temporal Analysis (Logit)")
## Pre-BTFP (Base) Pre-BTFP (Risk)
## Dependent Var.: dw_prebtfp dw_prebtfp
##
## Constant -4.344*** (0.1803) -4.500*** (0.3089)
## MTM Loss (z) 0.0775 (0.1449)
## Uninsured Leverage (z) 0.0716 (0.1188)
## MTM $\times$ Uninsured 0.0058 (0.1062)
## Risk 2: Low MTM, High Unins. 0.0780 (0.3951)
## Risk 3: High MTM, Low Unins. 0.3230 (0.3851)
## Risk 4: High MTM, High Unins. 0.1762 (0.3984)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,988 3,998
## Log-Likelihood -401.67 -401.53
## AIC 823.34 823.05
## N (DW=1) 100 100
## Baseline 2022Q4 2022Q4
##
## Acute (Base) Acute (Risk)
## Dependent Var.: dw_acute dw_acute
##
## Constant -2.426*** (0.0672) -2.801*** (0.1651)
## MTM Loss (z) 0.1944** (0.0679)
## Uninsured Leverage (z) 0.1603** (0.0615)
## MTM $\times$ Uninsured 0.0832 (0.0535)
## Risk 2: Low MTM, High Unins. 0.2964 (0.2011)
## Risk 3: High MTM, Low Unins. 0.3925. (0.2115)
## Risk 4: High MTM, High Unins. 0.7321*** (0.2037)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,668 3,678
## Log-Likelihood -1,080.9 -1,080.6
## AIC 2,181.9 2,181.1
## N (DW=1) 393 393
## Baseline 2022Q4 2022Q4
##
## Post (Base) Post (Risk)
## Dependent Var.: dw_post dw_post
##
## Constant -1.239*** (0.0439) -1.476*** (0.1007)
## MTM Loss (z) 0.0827. (0.0485)
## Uninsured Leverage (z) 0.1327** (0.0487)
## MTM $\times$ Uninsured 0.0800. (0.0414)
## Risk 2: Low MTM, High Unins. 0.2616* (0.1303)
## Risk 3: High MTM, Low Unins. 0.1490 (0.1334)
## Risk 4: High MTM, High Unins. 0.4780*** (0.1373)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,549 3,559
## Log-Likelihood -1,761.0 -1,760.6
## AIC 3,542.0 3,541.3
## N (DW=1) 887 887
## Baseline 2022Q4 2022Q4
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
models_dw_temp_logit, "Tab_DW_Temporal_Logit",
title_text = "Discount Window Usage: Pre-BTFP, Acute, and Post-Acute (Logit)",
notes_text = paste(
"Logit estimates. Period definitions as in LPM table.",
"Sample: DW users vs. pure non-users. Controls suppressed. Robust SEs."
),
fitstat_use = ~ n + ll + aic,
extra_lines = dw_temp_extra
)
# ==============================================================================
# TABLE E3: DW Temporal — Quartile Models (LPM)
# ==============================================================================
m_dw_tq_pre_lpm <- list(
"Pre: MTM-Q" = run_one(df_dw_pre_s, "dw_prebtfp", EXPL_MTM_Q, "lpm", CONTROLS_1),
"Pre: AE-Q" = run_one(df_dw_pre_s, "dw_prebtfp", EXPL_AE_Q, "lpm", CONTROLS_2),
"Pre: Unins-Q" = run_one(df_dw_pre_s, "dw_prebtfp", EXPL_UNINS_Q, "lpm", CONTROLS_1)
)
m_dw_tq_ac_lpm <- list(
"Acute: MTM-Q" = run_one(df_dw_acute_s, "dw_acute", EXPL_MTM_Q, "lpm", CONTROLS_1),
"Acute: AE-Q" = run_one(df_dw_acute_s, "dw_acute", EXPL_AE_Q, "lpm", CONTROLS_2),
"Acute: Unins-Q" = run_one(df_dw_acute_s, "dw_acute", EXPL_UNINS_Q, "lpm", CONTROLS_1)
)
m_dw_tq_post_lpm <- list(
"Post: MTM-Q" = run_one(df_dw_post_s, "dw_post", EXPL_MTM_Q, "lpm", CONTROLS_1),
"Post: AE-Q" = run_one(df_dw_post_s, "dw_post", EXPL_AE_Q, "lpm", CONTROLS_2),
"Post: Unins-Q" = run_one(df_dw_post_s, "dw_post", EXPL_UNINS_Q, "lpm", CONTROLS_1)
)
models_dw_temp_q_lpm <- c(m_dw_tq_pre_lpm, m_dw_tq_ac_lpm, m_dw_tq_post_lpm)
dw_temp_q_extra <- list(
"__N (DW=1)" = c(
rep(sum(df_dw_pre_s$dw_prebtfp), 3),
rep(sum(df_dw_acute_s$dw_acute), 3),
rep(sum(df_dw_post_s$dw_post), 3)
),
"__Baseline" = rep("2022Q4", 9)
)
etable(models_dw_temp_q_lpm, fitstat = ~ n + r2,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
extralines = dw_temp_q_extra,
title = "DW Temporal: Quartile Specifications (LPM)")
## Pre: MTM-Q Pre: AE-Q
## Dependent Var.: dw_prebtfp dw_prebtfp
##
## Constant 0.0305*** (0.0052) 0.0268*** (0.0049)
## MTM Q2 -0.0117. (0.0064)
## MTM Q3 -0.0088 (0.0072)
## MTM Q4 (Highest Loss) 0.0009 (0.0079)
## Adj. Equity Q1 (Lowest) -0.0016 (0.0077)
## Adj. Equity Q2 0.0062 (0.0075)
## Adj. Equity Q3 -0.0096 (0.0061)
## Unins. Lev. Q2
## Unins. Lev. Q3
## Unins. Lev. Q4 (Highest)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,998 3,998
## R2 0.03654 0.03659
## N (DW=1) 100 100
## Baseline 2022Q4 2022Q4
##
## Pre: Unins-Q Acute: MTM-Q
## Dependent Var.: dw_prebtfp dw_acute
##
## Constant 0.0256*** (0.0047) 0.0923*** (0.0093)
## MTM Q2 0.0137 (0.0132)
## MTM Q3 0.0403** (0.0144)
## MTM Q4 (Highest Loss) 0.0288* (0.0146)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## Unins. Lev. Q2 0.0026 (0.0063)
## Unins. Lev. Q3 -0.0036 (0.0068)
## Unins. Lev. Q4 (Highest) 0.0008 (0.0076)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,998 3,678
## R2 0.03555 0.09331
## N (DW=1) 100 393
## Baseline 2022Q4 2022Q4
##
## Acute: AE-Q Acute: Unins-Q
## Dependent Var.: dw_acute dw_acute
##
## Constant 0.1047*** (0.0093) 0.1025*** (0.0091)
## MTM Q2
## MTM Q3
## MTM Q4 (Highest Loss)
## Adj. Equity Q1 (Lowest) 0.0104 (0.0144)
## Adj. Equity Q2 0.0356* (0.0147)
## Adj. Equity Q3 -0.0131 (0.0125)
## Unins. Lev. Q2 0.0036 (0.0121)
## Unins. Lev. Q3 0.0172 (0.0140)
## Unins. Lev. Q4 (Highest) 0.0208 (0.0147)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,678 3,678
## R2 0.09430 0.09198
## N (DW=1) 393 393
## Baseline 2022Q4 2022Q4
##
## Post: MTM-Q Post: AE-Q
## Dependent Var.: dw_post dw_post
##
## Constant 0.2343*** (0.0139) 0.2335*** (0.0133)
## MTM Q2 0.0202 (0.0193)
## MTM Q3 0.0433* (0.0206)
## MTM Q4 (Highest Loss) 0.0152 (0.0211)
## Adj. Equity Q1 (Lowest) 0.0253 (0.0209)
## Adj. Equity Q2 0.0451* (0.0201)
## Adj. Equity Q3 0.0120 (0.0185)
## Unins. Lev. Q2
## Unins. Lev. Q3
## Unins. Lev. Q4 (Highest)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,559 3,559
## R2 0.12722 0.12736
## N (DW=1) 887 887
## Baseline 2022Q4 2022Q4
##
## Post: Unins-Q
## Dependent Var.: dw_post
##
## Constant 0.2461*** (0.0137)
## MTM Q2
## MTM Q3
## MTM Q4 (Highest Loss)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## Unins. Lev. Q2 -0.0237 (0.0185)
## Unins. Lev. Q3 0.0269 (0.0200)
## Unins. Lev. Q4 (Highest) 0.0282 (0.0219)
## ________________________ __________________
## S.E. type Heteroskedas.-rob.
## Observations 3,559
## R2 0.12824
## N (DW=1) 887
## Baseline 2022Q4
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
models_dw_temp_q_lpm, "Tab_DW_Temporal_Quartile_LPM",
title_text = "DW Usage: Quartile Specifications Across Periods (LPM)",
notes_text = paste(
"LPM estimates. Each period has 3 columns: MTM quartile, AE quartile, Uninsured quartile.",
"Reference categories: MTM Q1 (lowest), AE Q4 (highest), Unins Q1 (lowest).",
"All use 2022Q4 baseline. Sample: DW users vs. pure non-users.",
"Controls suppressed. Robust SEs."
),
fitstat_use = ~ n + r2,
extra_lines = dw_temp_q_extra
)
# ==============================================================================
# TABLE E4: DW Temporal — Quartile Models (Logit)
# ==============================================================================
m_dw_tq_pre_logit <- list(
"Pre: MTM-Q" = run_one(df_dw_pre_s, "dw_prebtfp", EXPL_MTM_Q, "logit", CONTROLS_1),
"Pre: AE-Q" = run_one(df_dw_pre_s, "dw_prebtfp", EXPL_AE_Q, "logit", CONTROLS_2),
"Pre: Unins-Q" = run_one(df_dw_pre_s, "dw_prebtfp", EXPL_UNINS_Q, "logit", CONTROLS_1)
)
m_dw_tq_ac_logit <- list(
"Acute: MTM-Q" = run_one(df_dw_acute_s, "dw_acute", EXPL_MTM_Q, "logit", CONTROLS_1),
"Acute: AE-Q" = run_one(df_dw_acute_s, "dw_acute", EXPL_AE_Q, "logit", CONTROLS_2),
"Acute: Unins-Q" = run_one(df_dw_acute_s, "dw_acute", EXPL_UNINS_Q, "logit", CONTROLS_1)
)
m_dw_tq_post_logit <- list(
"Post: MTM-Q" = run_one(df_dw_post_s, "dw_post", EXPL_MTM_Q, "logit", CONTROLS_1),
"Post: AE-Q" = run_one(df_dw_post_s, "dw_post", EXPL_AE_Q, "logit", CONTROLS_2),
"Post: Unins-Q" = run_one(df_dw_post_s, "dw_post", EXPL_UNINS_Q, "logit", CONTROLS_1)
)
models_dw_temp_q_logit <- c(m_dw_tq_pre_logit, m_dw_tq_ac_logit, m_dw_tq_post_logit)
etable(models_dw_temp_q_logit, fitstat = ~ n + ll + aic,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
extralines = dw_temp_q_extra,
title = "DW Temporal: Quartile Specifications (Logit)")
## Pre: MTM-Q Pre: AE-Q
## Dependent Var.: dw_prebtfp dw_prebtfp
##
## Constant -4.171*** (0.2598) -4.510*** (0.2838)
## MTM Q2 -0.5449. (0.3189)
## MTM Q3 -0.3192 (0.3055)
## MTM Q4 (Highest Loss) 0.0604 (0.3101)
## Adj. Equity Q1 (Lowest) 0.3287 (0.3576)
## Adj. Equity Q2 0.5011 (0.3131)
## Adj. Equity Q3 -0.2864 (0.3400)
## Unins. Lev. Q2
## Unins. Lev. Q3
## Unins. Lev. Q4 (Highest)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,998 3,998
## Log-Likelihood -399.37 -398.94
## AIC 818.73 815.89
## N (DW=1) 100 100
## Baseline 2022Q4 2022Q4
##
## Pre: Unins-Q Acute: MTM-Q
## Dependent Var.: dw_prebtfp dw_acute
##
## Constant -4.529*** (0.3059) -2.787*** (0.1470)
## MTM Q2 0.2767 (0.1873)
## MTM Q3 0.6057** (0.1845)
## MTM Q4 (Highest Loss) 0.5035** (0.1949)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## Unins. Lev. Q2 0.3776 (0.3657)
## Unins. Lev. Q3 0.1063 (0.3770)
## Unins. Lev. Q4 (Highest) 0.2112 (0.3813)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,998 3,678
## Log-Likelihood -401.31 -1,082.5
## AIC 822.62 2,185.0
## N (DW=1) 100 393
## Baseline 2022Q4 2022Q4
##
## Acute: AE-Q Acute: Unins-Q
## Dependent Var.: dw_acute dw_acute
##
## Constant -2.705*** (0.1385) -2.773*** (0.1619)
## MTM Q2
## MTM Q3
## MTM Q4 (Highest Loss)
## Adj. Equity Q1 (Lowest) 0.4623* (0.1892)
## Adj. Equity Q2 0.6231*** (0.1732)
## Adj. Equity Q3 0.0239 (0.1769)
## Unins. Lev. Q2 0.3415. (0.2018)
## Unins. Lev. Q3 0.5005* (0.1965)
## Unins. Lev. Q4 (Highest) 0.4807* (0.1954)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,678 3,678
## Log-Likelihood -1,083.3 -1,084.6
## AIC 2,184.6 2,189.2
## N (DW=1) 393 393
## Baseline 2022Q4 2022Q4
##
## Post: MTM-Q Post: AE-Q
## Dependent Var.: dw_post dw_post
##
## Constant -1.411*** (0.0963) -1.474*** (0.0972)
## MTM Q2 0.1558 (0.1269)
## MTM Q3 0.3138* (0.1296)
## MTM Q4 (Highest Loss) 0.1676 (0.1366)
## Adj. Equity Q1 (Lowest) 0.3304* (0.1405)
## Adj. Equity Q2 0.3917** (0.1280)
## Adj. Equity Q3 0.1686 (0.1236)
## Unins. Lev. Q2
## Unins. Lev. Q3
## Unins. Lev. Q4 (Highest)
## ________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,559 3,559
## Log-Likelihood -1,764.5 -1,764.1
## AIC 3,549.0 3,546.2
## N (DW=1) 887 887
## Baseline 2022Q4 2022Q4
##
## Post: Unins-Q
## Dependent Var.: dw_post
##
## Constant -1.353*** (0.0959)
## MTM Q2
## MTM Q3
## MTM Q4 (Highest Loss)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## Unins. Lev. Q2 -0.0694 (0.1318)
## Unins. Lev. Q3 0.2390. (0.1269)
## Unins. Lev. Q4 (Highest) 0.2289. (0.1349)
## ________________________ __________________
## S.E. type Heteroskedas.-rob.
## Observations 3,559
## Log-Likelihood -1,762.8
## AIC 3,545.5
## N (DW=1) 887
## Baseline 2022Q4
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
models_dw_temp_q_logit, "Tab_DW_Temporal_Quartile_Logit",
title_text = "DW Usage: Quartile Specifications Across Periods (Logit)",
notes_text = paste(
"Logit estimates. Each period has 3 columns: MTM quartile, AE quartile, Uninsured quartile.",
"Reference categories: MTM Q1 (lowest), AE Q4 (highest), Unins Q1 (lowest).",
"All use 2022Q4 baseline. Sample: DW users vs. pure non-users.",
"Controls suppressed. Robust SEs."
),
fitstat_use = ~ n + ll + aic,
extra_lines = dw_temp_q_extra
)
# ==============================================================================
# SECTION 5: ROBUSTNESS CHECKS
# ==============================================================================
cat("\n========================================\n")
##
## ========================================
cat("SECTION 5: ROBUSTNESS\n")
## SECTION 5: ROBUSTNESS
cat("========================================\n")
## ========================================
# --- 5A. Logit vs LPM comparison (BTFP Acute) --------------------------------
df_rob <- df_acute %>% filter(btfp_acute == 1 | non_user == 1)
m_rob_lpm <- run_one(df_rob, "btfp_acute", EXPL_BASE_MTM, "lpm", CONTROLS_1)
m_rob_logit <- run_one(df_rob, "btfp_acute", EXPL_BASE_MTM, "logit", CONTROLS_1)
# --- 5B. Alternative winsorization (1%/99%) -----------------------------------
df_rob_1 <- df_rob %>%
mutate(
mtm_total_1 = standardize_z(winsorize(mtm_total_raw, probs = c(0.01, 0.99))),
uninsured_lev_1 = standardize_z(winsorize(uninsured_lev_raw, probs = c(0.01, 0.99))),
mtm_x_unins_1 = mtm_total_1 * uninsured_lev_1
)
m_rob_win <- feols(
btfp_acute ~ mtm_total_1 + uninsured_lev_1 + mtm_x_unins_1 +
ln_assets + cash_ratio + book_equity_ratio + loan_to_deposit + wholesale + roa,
data = df_rob_1, vcov = "hetero"
)
# --- 5C. Exclude large banks --------------------------------------------------
m_rob_small <- run_one(
df_rob %>% filter(size_cat != "Large (>$100B)"),
"btfp_acute", EXPL_BASE_MTM, "lpm", CONTROLS_1
)
models_rob <- list(
"(1) LPM" = m_rob_lpm,
"(2) Logit" = m_rob_logit,
"(3) Win 1/99" = m_rob_win,
"(4) Excl Large" = m_rob_small
)
etable(models_rob, fitstat = ~ n + r2 + ll,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
title = "Robustness Checks: BTFP Acute")
## (1) LPM (2) Logit (3) Win 1/99
## Dependent Var.: btfp_acute btfp_acute btfp_acute
##
## Constant 0.1316*** (0.0055) -2.320*** (0.0711) 0.1307*** (0.0054)
## MTM Loss (z) 0.0243*** (0.0059) 0.2141*** (0.0622)
## Uninsured Leverage (z) 0.0199** (0.0068) 0.2089*** (0.0626)
## MTM $\times$ Uninsured 0.0181*** (0.0051) 0.0461 (0.0569)
## mtm_total_1 0.0243*** (0.0060)
## uninsured_lev_1 0.0209** (0.0069)
## mtm_x_unins_1 0.0183*** (0.0050)
## ______________________ __________________ __________________ __________________
## Family OLS Logit OLS
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,737 3,737 3,737
## R2 0.09017 -- 0.09035
## Log-Likelihood -973.38 -1,214.5 -973.01
##
## (4) Excl Large
## Dependent Var.: btfp_acute
##
## Constant 0.1315*** (0.0055)
## MTM Loss (z) 0.0246*** (0.0059)
## Uninsured Leverage (z) 0.0193** (0.0068)
## MTM $\times$ Uninsured 0.0184*** (0.0051)
## mtm_total_1
## uninsured_lev_1
## mtm_x_unins_1
## ______________________ __________________
## Family OLS
## S.E. type Heteroskedas.-rob.
## Observations 3,722
## R2 0.08976
## Log-Likelihood -959.34
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
save_etable(
models_rob, "Tab_Robustness_BTFP_Acute",
title_text = "Robustness Checks: BTFP Participation During Acute Crisis",
notes_text = paste(
"DV = BTFP participation (Mar 13--May 1, 2023). Col 1: LPM baseline.",
"Col 2: Logit. Col 3: Alternative winsorization (1/99\\%).",
"Col 4: Excluding banks with assets > \\$100B.",
"Sample: BTFP users vs. pure non-users. Controls suppressed. Robust SEs."
),
fitstat_use = ~ n + r2 + ll
)
# ==============================================================================
# DW SHORT WINDOW: March 10 & March 10-13
# ==============================================================================
# --- Samples ---
df_dw_mar10_s <- df_mar10 %>% filter(dw_mar10 == 1 | non_user == 1)
df_dw_mar10_13_s <- df_mar10_13 %>% filter(dw_mar10_13 == 1 | non_user == 1)
# --- Models (using run_one from analysis_regressions_complete.R) ---
models_dw_short <- list(
"Mar10: Base" = run_one(df_dw_mar10_s, "dw_mar10", EXPL_BASE_MTM, "lpm", CONTROLS_1),
"Mar10: Risk" = run_one(df_dw_mar10_s, "dw_mar10", EXPL_RISK, "lpm", CONTROLS_1),
"Mar10: MTM-Q" = run_one(df_dw_mar10_s, "dw_mar10", EXPL_MTM_Q, "lpm", CONTROLS_1),
"Mar10: AE-Q" = run_one(df_dw_mar10_s, "dw_mar10", EXPL_AE_Q, "lpm", CONTROLS_2),
"M10-13: Base" = run_one(df_dw_mar10_13_s, "dw_mar10_13", EXPL_BASE_MTM, "lpm", CONTROLS_1),
"M10-13: Risk" = run_one(df_dw_mar10_13_s, "dw_mar10_13", EXPL_RISK, "lpm", CONTROLS_1),
"M10-13: MTM-Q"= run_one(df_dw_mar10_13_s, "dw_mar10_13", EXPL_MTM_Q, "lpm", CONTROLS_1),
"M10-13: AE-Q" = run_one(df_dw_mar10_13_s, "dw_mar10_13", EXPL_AE_Q, "lpm", CONTROLS_2)
)
# --- Extra lines ---
dw_short_extra <- list(
"__N (DW=1)" = c(
rep(sum(df_dw_mar10_s$dw_mar10), 4),
rep(sum(df_dw_mar10_13_s$dw_mar10_13), 4)
)
)
# --- Display ---
etable(models_dw_short, fitstat = ~ n + r2,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
extralines = dw_short_extra,
title = "DW Usage: March 10 and March 10-13 (LPM)")
## Mar10: Base Mar10: Risk
## Dependent Var.: dw_mar10 dw_mar10
##
## Constant 0.0122*** (0.0018) 0.0145*** (0.0035)
## MTM Loss (z) -0.0022 (0.0022)
## Uninsured Leverage (z) 0.0011 (0.0021)
## MTM $\times$ Uninsured 0.0007 (0.0018)
## Risk 2: Low MTM, High Unins. -0.0032 (0.0045)
## Risk 3: High MTM, Low Unins. -0.0052 (0.0042)
## Risk 4: High MTM, High Unins. -0.0006 (0.0058)
## MTM Q2
## MTM Q3
## MTM Q4 (Highest Loss)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,986 3,996
## R2 0.02398 0.02381
## N (DW=1) 47 47
##
## Mar10: MTM-Q Mar10: AE-Q
## Dependent Var.: dw_mar10 dw_mar10
##
## Constant 0.0137*** (0.0034) 0.0109*** (0.0028)
## MTM Loss (z)
## Uninsured Leverage (z)
## MTM $\times$ Uninsured
## Risk 2: Low MTM, High Unins.
## Risk 3: High MTM, Low Unins.
## Risk 4: High MTM, High Unins.
## MTM Q2 -0.0015 (0.0044)
## MTM Q3 -0.0014 (0.0050)
## MTM Q4 (Highest Loss) -0.0033 (0.0051)
## Adj. Equity Q1 (Lowest) -0.0016 (0.0043)
## Adj. Equity Q2 0.0082. (0.0049)
## Adj. Equity Q3 -0.0018 (0.0038)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,996 3,996
## R2 0.02354 0.02489
## N (DW=1) 47 47
##
## M10-13: Base M10-13: Risk
## Dependent Var.: dw_mar10_13 dw_mar10_13
##
## Constant 0.0236*** (0.0024) 0.0267*** (0.0045)
## MTM Loss (z) -0.0021 (0.0029)
## Uninsured Leverage (z) 0.0078* (0.0033)
## MTM $\times$ Uninsured 0.0046. (0.0026)
## Risk 2: Low MTM, High Unins. -0.0053 (0.0062)
## Risk 3: High MTM, Low Unins. -0.0120* (0.0053)
## Risk 4: High MTM, High Unins. 0.0040 (0.0076)
## MTM Q2
## MTM Q3
## MTM Q4 (Highest Loss)
## Adj. Equity Q1 (Lowest)
## Adj. Equity Q2
## Adj. Equity Q3
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,988 3,998
## R2 0.04684 0.04531
## N (DW=1) 90 90
##
## M10-13: MTM-Q M10-13: AE-Q
## Dependent Var.: dw_mar10_13 dw_mar10_13
##
## Constant 0.0255*** (0.0047) 0.0274*** (0.0047)
## MTM Loss (z)
## Uninsured Leverage (z)
## MTM $\times$ Uninsured
## Risk 2: Low MTM, High Unins.
## Risk 3: High MTM, Low Unins.
## Risk 4: High MTM, High Unins.
## MTM Q2 -0.0025 (0.0062)
## MTM Q3 -0.0009 (0.0069)
## MTM Q4 (Highest Loss) -0.0065 (0.0068)
## Adj. Equity Q1 (Lowest) -0.0116. (0.0065)
## Adj. Equity Q2 0.0029 (0.0071)
## Adj. Equity Q3 -0.0087 (0.0060)
## _____________________________ __________________ __________________
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,998 3,998
## R2 0.04397 0.04522
## N (DW=1) 90 90
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# --- Save LaTeX ---
save_etable(
models_dw_short, "Tab_DW_Mar10_Short_LPM",
title_text = "Discount Window Usage During Immediate Crisis (LPM)",
notes_text = paste(
"LPM estimates. Cols 1--4: March 10, 2023 only (SVB closure, pre-BTFP).",
"Cols 5--8: March 10--13, 2023 (includes BTFP announcement March 12).",
"DV = DW access indicator. Base: continuous MTM, Uninsured, interaction.",
"Risk: Risk 2,3,4 dummies (median split). MTM-Q: MTM quartile dummies (Q1 ref).",
"AE-Q: Adjusted Equity quartile dummies (Q4 ref, CONTROLS\\_2).",
"Sample: DW users vs. pure non-users. 2022Q4 baseline.",
"Controls suppressed. Robust SEs."
),
fitstat_use = ~ n + r2,
extra_lines = dw_short_extra
)
DIFFERENCE-IN-DIFFERENCES ANALYSIS
Treatment: Banks with OMO-eligible securities (can access BTFP) Control: Banks without OMO-eligible securities (cannot access BTFP) Event: BTFP announcement (March 12, 2023) Outcome: Deposit stability (quarterly change in deposits)
# ==============================================================================
# DiD PANEL DATA (INCLUDE BOTH OMO AND NON-OMO BANKS)
# ==============================================================================
df_did_panel <- call_q %>%
filter(!idrssd %in% excluded_banks,
period %in% c("2022Q1", "2022Q2", "2022Q3", "2022Q4",
"2023Q1", "2023Q2", "2023Q3")) %>%
select(
idrssd, period,
omo_eligible,
change_total_deposit_fwd_q, change_uninsured_fwd_q,
total_asset, uninsured_deposit, total_deposit, total_liability, total_equity,
cash_to_total_asset, total_loan, roa,
mtm_loss_to_total_asset,
fed_fund_purchase, repo, other_borrowed_less_than_1yr,
book_equity_to_total_asset, loan_to_deposit
) %>%
# Create variables BEFORE filtering
mutate(
has_omo = as.integer(!is.na(omo_eligible) & omo_eligible > 0),
post_btfp = as.integer(period >= "2023Q1"),
did_term = has_omo * post_btfp,
# Raw controls
mtm_total_raw = mtm_loss_to_total_asset,
uninsured_lev_raw = safe_div(uninsured_deposit, total_asset),
ln_assets_raw = log(total_asset),
cash_ratio_raw = cash_to_total_asset,
book_equity_ratio_raw = book_equity_to_total_asset,
loan_to_deposit_raw = loan_to_deposit,
wholesale_raw = safe_div(
replace_na(fed_fund_purchase, 0) + replace_na(repo, 0) +
replace_na(other_borrowed_less_than_1yr, 0),
total_liability, 0
) * 100,
roa_raw = roa,
# Outcome
outflow_total_dep = -1 * lag(change_total_deposit_fwd_q),
outflow_uninsured = -1 * lag(change_uninsured_fwd_q)
) %>%
ungroup() %>%
# Filter valid observations
filter(!is.na(ln_assets_raw), is.finite(ln_assets_raw),
is.finite(outflow_total_dep), is.finite(outflow_uninsured)) %>%
# Winsorize and standardize
mutate(
mtm_total = standardize_z(winsorize(mtm_total_raw)),
uninsured_lev = standardize_z(winsorize(uninsured_lev_raw)),
ln_assets = standardize_z(winsorize(ln_assets_raw)),
cash_ratio = standardize_z(winsorize(cash_ratio_raw)),
book_equity_ratio = standardize_z(winsorize(book_equity_ratio_raw)),
loan_to_deposit = standardize_z(winsorize(loan_to_deposit_raw)),
wholesale = standardize_z(winsorize(wholesale_raw)),
roa = standardize_z(winsorize(roa_raw))
)
cat("\n=== DiD PANEL SUMMARY ===\n")
##
## === DiD PANEL SUMMARY ===
cat("Total Obs:", nrow(df_did_panel), "\n")
## Total Obs: 32284
cat("Treated (OMO):", n_distinct(df_did_panel$idrssd[df_did_panel$has_omo == 1]), "banks\n")
## Treated (OMO): 4460 banks
cat("Control (No OMO):", n_distinct(df_did_panel$idrssd[df_did_panel$has_omo == 0]), "banks\n")
## Control (No OMO): 576 banks
# ==============================================================================
# STEP 1: PREPARE INTENSIVE MARGIN PANEL (OMO BANKS ONLY)
# ==============================================================================
# Create a reference for MTM losses at 2022Q4 (The Pre-Crisis Shock Level)
df_shock_ref <- df_did_panel %>%
filter(period == "2022Q4") %>%
select(idrssd, mtm_treatment = mtm_total)
# Merge back to panel and restrict to BTFP-eligible banks
df_did_fixed <- df_did_panel %>%
filter(has_omo == 1) %>%
left_join(df_shock_ref, by = "idrssd") %>%
# Filter for banks that actually have the treatment value
filter(!is.na(mtm_treatment))
# ==============================================================================
# STEP 2: RUN MODELS
# ==============================================================================
# 1. Static Continuous DiD
m_static_cont <- feols(
outflow_total_dep ~ mtm_treatment:post_btfp +
uninsured_lev + ln_assets + cash_ratio +
book_equity_ratio + loan_to_deposit + wholesale + roa |
idrssd + period,
data = df_did_fixed,
cluster = ~idrssd
)
# 2. Event Study (Parallel Trends Validation)
m_event_cont <- feols(
outflow_total_dep ~ i(period, mtm_treatment, ref = "2022Q4") +
uninsured_lev + ln_assets + cash_ratio +
book_equity_ratio + loan_to_deposit + wholesale + roa |
idrssd + period,
data = df_did_fixed,
cluster = ~idrssd
)
# ==============================================================================
# STEP 3: VISUALIZE & SAVE
# ==============================================================================
# Display Table in R
etable(m_static_cont, m_event_cont)
## m_static_cont m_event_cont
## Dependent Var.: outflow_total_dep outflow_total_dep
##
## Uninsured Leverage (z) -3.211*** (0.4140) -3.206*** (0.4152)
## Log(Assets) -41.36*** (4.519) -40.90*** (4.556)
## Cash Ratio -0.1146 (0.4658) -0.1479 (0.4646)
## Book Equity Ratio 0.1758 (0.3852) 0.2934 (0.4030)
## Loan-to-Deposit 4.111*** (1.064) 4.095*** (1.063)
## Wholesale Funding 0.2988* (0.1176) 0.3012* (0.1174)
## ROA 0.1355 (0.1816) 0.1392 (0.1822)
## mtm_treatment x post_btfp -0.5517*** (0.1357)
## mtm_treatment x period = 2022Q1 -0.4495* (0.2070)
## mtm_treatment x period = 2022Q2 -0.0632 (0.2045)
## mtm_treatment x period = 2022Q3 -0.1488 (0.2167)
## mtm_treatment x period = 2023Q1 -0.7268*** (0.1645)
## mtm_treatment x period = 2023Q2 -0.6422*** (0.1695)
## mtm_treatment x period = 2023Q3 -0.7216*** (0.1647)
## Fixed-Effects: ------------------- -------------------
## idrssd Yes Yes
## period Yes Yes
## _______________________________ ___________________ ___________________
## S.E.: Clustered by: idrssd by: idrssd
## Observations 29,211 29,211
## R2 0.37331 0.37359
## Within R2 0.14584 0.14622
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Visualize - This is what the advisor needs to see!
iplot(m_event_cont,
main = "Corrected Event Study: MTM Intensity (Eligible Banks Only)",
xlab = "Quarter", ylab = "Effect of MTM Loss on Outflow")
# Save Result
COEF_MAP_FIXED <- c(
# DiD Terms
"mtm_treatment:post_btfp" = "MTM Intensity ? Post-BTFP",
"period::2022Q1:mtm_treatment" = "MTM Intensity (2022Q1)",
"period::2022Q2:mtm_treatment" = "MTM Intensity (2022Q2)",
"period::2022Q3:mtm_treatment" = "MTM Intensity (2022Q3)",
"period::2023Q1:mtm_treatment" = "MTM Intensity (2023Q1)",
"period::2023Q2:mtm_treatment" = "MTM Intensity (2023Q2)",
"period::2023Q3:mtm_treatment" = "MTM Intensity (2023Q3)",
# Controls
"uninsured_lev" = "Uninsured Deposits",
"ln_assets" = "Log Assets",
"cash_ratio" = "Cash Ratio",
"book_equity_ratio" = "Book Equity Ratio",
"loan_to_deposit" = "Loan-to-Deposit",
"wholesale" = "Wholesale Funding",
"roa" = "ROA"
)
# If the error persists, run: names(coef(m_event_cont))
# and replace the keys above with the exact strings printed.
# Save Result
save_reg_table(list("Static" = m_static_cont, "Event" = m_event_cont),
"Table_Fixed_DiD_Intensive",
title_text = "Different-in-Different: Intensive Margin of MTM Losses",
notes_text = "This table reports the impact of MTM loss intensity on deposit stability among eligible banks. The treatment is the continuous MTM loss as of 2022Q4. The event study demonstrates parallel trends throughout 2022 and a significant stabilization effect starting in 2023Q1.",
coef_map_use = COEF_MAP_FIXED,
gof_map_use = c("nobs", "r.squared"))
# Define the filename and dimensions
pdf("Figure_Event_Study_MTM_Intensity.pdf", width = 10, height = 7)
# Generate the plot
iplot(m_event_cont,
main = "Impact of MTM Intensity on Deposit Stability",
xlab = "Quarter",
ylab = "Coefficient: MTM Intensity on Deposit Outflow",
pt.join = TRUE, # Connect the dots to show the trend clearly
ref.line = TRUE # Highlight the 2022Q4 reference line
)
# Close the file device
dev.off()
## png
## 2
# ==============================================================================
# ROBUSTNESS CHECKS
# ==============================================================================
# ==============================================================================
# ROBUSTNESS CHECKS — BTFP Acute
# ==============================================================================
df_rob <- df_acute %>% filter(btfp_acute == 1 | non_user == 1)
# 1. Base LPM
m_rob_lpm <- run_one(df_rob, "btfp_acute", EXPL_BASE_MTM, "lpm", CONTROLS_1)
# 2. Logit
m_rob_logit <- run_one(df_rob, "btfp_acute", EXPL_BASE_MTM, "logit", CONTROLS_1)
# 3. Alternative winsorization (1%/99%)
df_rob_1 <- df_rob %>%
mutate(
mtm_total_1 = standardize_z(winsorize(mtm_total_raw, probs = c(0.01, 0.99))),
uninsured_lev_1 = standardize_z(winsorize(uninsured_lev_raw, probs = c(0.01, 0.99))),
mtm_x_unins_1 = mtm_total_1 * uninsured_lev_1
)
m_rob_win <- feols(
btfp_acute ~ mtm_total_1 + uninsured_lev_1 + mtm_x_unins_1 +
ln_assets + cash_ratio + book_equity_ratio + loan_to_deposit + wholesale + roa,
data = df_rob_1, vcov = "hetero"
)
# 4. Exclude large banks
m_rob_small <- run_one(
df_rob %>% filter(size_cat != "Large (>$100B)"),
"btfp_acute", EXPL_BASE_MTM, "lpm", CONTROLS_1
)
# 5. Probit
m_rob_probit <- feglm(
build_formula("btfp_acute", EXPL_BASE_MTM),
data = df_rob, family = binomial("probit"), vcov = "hetero"
)
models_rob <- list(
"(1) LPM" = m_rob_lpm,
"(2) Logit" = m_rob_logit,
"(3) Probit" = m_rob_probit,
"(4) Win 1/99" = m_rob_win,
"(5) Excl Large" = m_rob_small
)
# --- Display ---
etable(models_rob, fitstat = ~ n + r2 + ll,
drop = "Log|Cash|Loan-to|Book|Wholesale|ROA",
title = "Robustness Checks: BTFP Acute")
## (1) LPM (2) Logit (3) Probit
## Dependent Var.: btfp_acute btfp_acute btfp_acute
##
## Constant 0.1316*** (0.0055) -2.320*** (0.0711) -1.313*** (0.0348)
## MTM Loss (z) 0.0243*** (0.0059) 0.2141*** (0.0622) 0.1187*** (0.0325)
## Uninsured Leverage (z) 0.0199** (0.0068) 0.2089*** (0.0626) 0.1132** (0.0348)
## MTM $\times$ Uninsured 0.0181*** (0.0051) 0.0461 (0.0569) 0.0302 (0.0315)
## mtm_total_1
## uninsured_lev_1
## mtm_x_unins_1
## ______________________ __________________ __________________ __________________
## Family OLS Logit Probit
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,737 3,737 3,737
## R2 0.09017 -- --
## Log-Likelihood -973.38 -1,214.5 -1,214.6
##
## (4) Win 1/99 (5) Excl Large
## Dependent Var.: btfp_acute btfp_acute
##
## Constant 0.1307*** (0.0054) 0.1315*** (0.0055)
## MTM Loss (z) 0.0246*** (0.0059)
## Uninsured Leverage (z) 0.0193** (0.0068)
## MTM $\times$ Uninsured 0.0184*** (0.0051)
## mtm_total_1 0.0243*** (0.0060)
## uninsured_lev_1 0.0209** (0.0069)
## mtm_x_unins_1 0.0183*** (0.0050)
## ______________________ __________________ __________________
## Family OLS OLS
## S.E. type Heteroskedas.-rob. Heteroskedas.-rob.
## Observations 3,737 3,722
## R2 0.09035 0.08976
## Log-Likelihood -973.01 -959.34
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# --- Save LaTeX ---
save_etable(
models_rob, "Tab_Robustness_BTFP_Acute",
title_text = "Robustness Checks: BTFP Participation During Acute Crisis",
notes_text = paste(
"DV = BTFP participation (Mar 13--May 1, 2023).",
"Col 1: LPM baseline. Col 2: Logit. Col 3: Probit.",
"Col 4: Alternative winsorization (1/99\\% instead of 2.5/97.5\\%).",
"Col 5: Excluding banks with assets $>$ \\$100B.",
"All specifications: continuous MTM Loss, Uninsured Leverage, and interaction.",
"Sample: BTFP users vs. pure non-users. 2022Q4 baseline.",
"Controls suppressed. Robust SEs."
),
fitstat_use = ~ n + r2 + ll
)