Analysis Map
Main Results:
Extensive Margin (Crisis Period)
Four solvency frameworks × four borrower types:
| A. Full Sample |
No split (all banks) |
4 columns: AnyFed, BTFP, DW, FHLB |
| B. Jiang MTM |
AE = Book Equity/TA − MTM/TA ≥ 0 |
8 columns: 4 × {Solvent, Insolvent} |
| C. Jiang IDCR-100% |
(MV Assets − Uninsured − Insured) / Insured ≥ 0 |
8 columns: 4 × {Solvent, Insolvent} |
| D. DSSW Franchise |
AE + DFV ≥ 0, where DFV = (1−β)×D/TA |
Diagnostic table (prediction: 0 insolvent) |
Additional Tests
| Temporal |
BTFP & DW: Crisis vs. Arbitrage |
Full + Jiang + IDCR-100% |
| Deposit Beta |
Triple interaction: MTM × Uninsured × β^U |
Full + Jiang + IDCR-100% |
| Facility Complementarity |
Multinomial choice, DW→BTFP complement, substitution |
Full sample |
DSSW Theoretical
Note
The DSSW (2023/2025) deposit franchise value for bank \(i\) in cross-section: \[\text{DFV}_i = (1 - \beta_i) \times
\frac{D_i}{TA_i}\]
Full formula: \(F = D \times [(1-\beta) -
c/r] \times [1 - 1/(1+r)^T]\) where \(c
\approx 2\%\), \(r\) = long
rate, \(T\) = deposit runoff horizon.
Since \(c\), \(r\), \(T\)
are constant cross-sectionally, all bank-level variation comes from
\(\beta_i\) and \(D_i/TA_i\).
Key insight: DFV exists only in the no-run
equilibrium. If depositors run, the uninsured franchise is
destroyed. A bank that is DSSW-solvent absent a run can become insolvent
during one — this is the panic region.
SETUP
Packages
rm(list = ls())
library(data.table)
library(dplyr)
library(tidyr)
library(stringr)
library(lubridate)
library(purrr)
library(tibble)
library(ggrepel)
library(fixest)
library(marginaleffects)
library(nnet)
library(broom)
library(modelsummary)
library(knitr)
library(kableExtra)
library(DescTools)
library(ggplot2)
library(gridExtra)
library(scales)
library(patchwork)
library(readr)
library(readxl)
cat("All packages loaded.\n")
## All packages loaded.
Helper Functions
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])
}
standardize_z <- function(x) {
if (all(is.na(x))) return(x)
(x - mean(x, na.rm = TRUE)) / sd(x, na.rm = TRUE)
}
safe_div <- function(num, denom, default = NA_real_) {
ifelse(is.na(denom) | denom == 0, default, num / denom)
}
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_pval <- function(p) {
case_when(is.na(p) ~ "", p < 0.01 ~ "***", p < 0.05 ~ "**", p < 0.10 ~ "*", TRUE ~ "")
}
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) Sys.sleep(wait_sec)
FALSE
}
)
if (isTRUE(result)) return(invisible(NULL))
}
warning("Failed to write ", con, " after ", max_retries, " attempts.")
}
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")
}
Paths
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/Comprehensive_Analysis")
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)
}
Key Dates
BASELINE_MAIN <- "2022Q4"
BASELINE_ARB <- "2023Q3"
CRISIS_START <- as.Date("2023-03-08")
CRISIS_END <- as.Date("2023-05-04")
ARB_START <- as.Date("2023-11-15")
ARB_END <- as.Date("2024-01-24")
DW_DATA_END <- as.Date("2023-12-31")
cat("Crisis:", format(CRISIS_START), "to", format(CRISIS_END), "\n")
## Crisis: 2023-03-08 to 2023-05-04
cat("Arbitrage:", format(ARB_START), "to", format(ARB_END), "\n")
## Arbitrage: 2023-11-15 to 2024-01-24
DATA LOADING
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))
dssw_betas <- read_csv(file.path(DATA_PROC, "dssw_deposit_betas.csv"), show_col_types = FALSE) %>%
mutate(idrssd = as.character(idrssd))
dssw_beta_2022q4 <- dssw_betas %>% filter(estimation_date == "2022Q4") %>%
select(idrssd, beta_overall, beta_insured_w, beta_uninsured_w, gamma_hat, alpha_hat)
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), "\n")
## BTFP Loans: 6734
cat("DW Loans:", nrow(dw_loans_raw), "\n")
## DW Loans: 10008
Exclusions
excluded_banks <- call_q %>%
filter(period == BASELINE_MAIN, failed_bank == 1 | gsib == 1) %>%
pull(idrssd)
btfp_loans <- btfp_loans_raw %>% filter(!rssd_id %in% excluded_banks)
dw_loans <- dw_loans_raw %>% filter(!rssd_id %in% excluded_banks)
cat("Excluded banks:", length(excluded_banks), "\n")
## Excluded banks: 41
BORROWER
INDICATORS
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_crisis <- create_borrower_indicator(btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount", CRISIS_START, CRISIS_END, "btfp_crisis")
btfp_arb <- create_borrower_indicator(btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount", ARB_START, ARB_END, "btfp_arb")
dw_crisis <- create_borrower_indicator(dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount", CRISIS_START, min(CRISIS_END, DW_DATA_END), "dw_crisis")
dw_arb <- create_borrower_indicator(dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount", ARB_START, DW_DATA_END, "dw_arb")
cat("BTFP Crisis:", nrow(btfp_crisis), "| Arb:", nrow(btfp_arb), "\n")
## BTFP Crisis: 526 | Arb: 780
cat("DW Crisis:", nrow(dw_crisis), "| Arb:", nrow(dw_arb), "\n")
## DW Crisis: 459 | Arb: 389
VARIABLE
CONSTRUCTION
construct_analysis_vars <- function(baseline_data) {
baseline_data %>%
mutate(
# === RAW VARIABLES ===
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,
insured_lev_raw = r_insured_deposit,
uninsured_share_raw = uninsured_to_deposit,
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,
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,
# === SOLVENCY FRAMEWORK A: Jiang MTM Adjusted Equity ===
adjusted_equity_raw = book_equity_to_total_asset - mtm_loss_to_total_asset,
mtm_insolvent = as.integer(adjusted_equity_raw < 0),
mtm_solvent = as.integer(adjusted_equity_raw >= 0),
# === SOLVENCY FRAMEWORK B: Jiang IDCR (100% uninsured run) ===
mv_assets = total_asset * (1 - mtm_loss_to_total_asset / 100),
idcr_100 = safe_div(
mv_assets - uninsured_deposit - insured_deposit,
insured_deposit, NA_real_
),
insolvent_idcr_100 = as.integer(idcr_100 < 0),
solvent_idcr_100 = as.integer(idcr_100 >= 0),
# === SOLVENCY FRAMEWORK C: DSSW Deposit Franchise Value ===
# DFV_i = (1 - β_i) × D_i / TA_i (cross-sectional DSSW simplification)
# Full: F = D × [(1-β) - c/r] × [1 - 1/(1+r)^T]; c,r,T constant cross-sectionally
# CRITICAL: DFV exists ONLY in the no-run equilibrium.
# If depositors run, uninsured franchise is destroyed → back to Jiang measure.
dfv_raw = ifelse(!is.na(beta_overall),
(1 - beta_overall) * (dom_deposit / total_asset) * 100, NA_real_),
adjusted_equity_dssw_raw = ifelse(!is.na(dfv_raw),
book_equity_to_total_asset - mtm_loss_to_total_asset + dfv_raw, NA_real_),
dssw_insolvent = as.integer(!is.na(adjusted_equity_dssw_raw) & adjusted_equity_dssw_raw < 0),
dssw_solvent = as.integer(!is.na(adjusted_equity_dssw_raw) & adjusted_equity_dssw_raw >= 0),
# === UNINSURED DEPOSIT BETA (for triple interaction) ===
uninsured_beta_raw = ifelse(!is.na(beta_uninsured_w), beta_uninsured_w, NA_real_),
# === WINSORIZED ===
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),
insured_lev_w = winsorize(r_insured_deposit),
adjusted_equity_w = winsorize(adjusted_equity_raw),
dfv_w = winsorize(dfv_raw),
adjusted_equity_dssw_w = winsorize(adjusted_equity_dssw_raw),
uninsured_beta_w = winsorize(uninsured_beta_raw),
ln_assets_w = winsorize(ln_assets_raw),
cash_ratio_w = winsorize(cash_ratio_raw),
book_equity_ratio_w = winsorize(book_equity_ratio_raw),
roa_w = winsorize(roa_raw),
loan_to_deposit_w = winsorize(loan_to_deposit_raw),
wholesale_w = winsorize(wholesale_raw),
# === Z-STANDARDIZED ===
mtm_total = standardize_z(mtm_total_w),
uninsured_lev = standardize_z(uninsured_lev_w),
insured_lev = standardize_z(insured_lev_w),
adjusted_equity = standardize_z(adjusted_equity_w),
dfv = standardize_z(dfv_w),
adjusted_equity_dssw = standardize_z(adjusted_equity_dssw_w),
uninsured_beta = standardize_z(uninsured_beta_w),
ln_assets = standardize_z(ln_assets_w),
cash_ratio = standardize_z(cash_ratio_w),
book_equity_ratio = standardize_z(book_equity_ratio_w),
roa = standardize_z(roa_w),
loan_to_deposit = standardize_z(loan_to_deposit_w),
wholesale = standardize_z(wholesale_w),
# === INTERACTIONS ===
mtm_x_uninsured = mtm_total * uninsured_lev,
mtm_x_insured = mtm_total * insured_lev,
mtm_x_unins_beta = mtm_total * uninsured_beta,
unins_x_unins_beta = uninsured_lev * uninsured_beta,
mtm_x_unins_x_beta = mtm_total * uninsured_lev * uninsured_beta,
# === PAR BENEFIT & COLLATERAL ===
par_benefit_raw = safe_div(mtm_btfp_raw,
mtm_btfp_raw + 100 * safe_div(omo_eligible, total_asset * 1000, 0), NA_real_),
collateral_capacity_raw = safe_div(omo_eligible, total_asset * 1000, 0) * 100,
size_cat = factor(create_size_category_3(total_asset), levels = size_levels_3)
) %>%
mutate(
par_benefit_w = winsorize(par_benefit_raw),
par_benefit = standardize_z(par_benefit_w),
collateral_capacity_w = winsorize(collateral_capacity_raw),
collateral_capacity = standardize_z(collateral_capacity_w),
par_x_uninsured = par_benefit * uninsured_lev
)
}
BUILD DATASETS
df_2022q4 <- call_q %>%
filter(period == BASELINE_MAIN, !idrssd %in% excluded_banks,
!is.na(omo_eligible) & omo_eligible > 0) %>%
left_join(dssw_beta_2022q4, by = "idrssd") %>%
construct_analysis_vars()
df_2023q3 <- call_q %>%
filter(period == BASELINE_ARB, !idrssd %in% excluded_banks,
!is.na(omo_eligible) & omo_eligible > 0) %>%
left_join(dssw_beta_2022q4, by = "idrssd") %>%
construct_analysis_vars()
cat("=== BASELINES ===\n")
## === BASELINES ===
cat("2022Q4:", nrow(df_2022q4), "banks\n")
## 2022Q4: 4292 banks
cat(" MTM Insolvent:", sum(df_2022q4$mtm_insolvent, na.rm=TRUE), "\n")
## MTM Insolvent: 825
cat(" IDCR-100% Insolvent:", sum(df_2022q4$insolvent_idcr_100, na.rm=TRUE), "\n")
## IDCR-100% Insolvent: 364
cat(" DSSW Insolvent:", sum(df_2022q4$dssw_insolvent, na.rm=TRUE), "\n")
## DSSW Insolvent: 0
cat(" Beta coverage:", sum(!is.na(df_2022q4$beta_overall)), "\n")
## Beta coverage: 4226
join_all_borrowers <- function(df_base, btfp_df, dw_df, btfp_var, dw_var) {
df_base %>%
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(
"{btfp_var}" := replace_na(!!sym(btfp_var), 0L),
"{dw_var}" := replace_na(!!sym(dw_var), 0L),
fhlb_user = as.integer(abnormal_fhlb_borrowing_10pct == 1),
any_fed = as.integer(!!sym(btfp_var) == 1 | !!sym(dw_var) == 1),
both_fed = as.integer(!!sym(btfp_var) == 1 & !!sym(dw_var) == 1),
user_group = factor(case_when(
both_fed == 1 ~ "Both",
!!sym(btfp_var) == 1 ~ "BTFP_Only",
!!sym(dw_var) == 1 ~ "DW_Only",
TRUE ~ "Neither"
), levels = c("Neither", "BTFP_Only", "DW_Only", "Both")),
non_user = as.integer(any_fed == 0 & fhlb_user == 0)
)
}
# Crisis
df_crisis <- join_all_borrowers(df_2022q4, btfp_crisis, dw_crisis, "btfp_crisis", "dw_crisis") %>%
mutate(
btfp_pct = ifelse(btfp_crisis == 1 & btfp_crisis_amt > 0,
100 * btfp_crisis_amt / (total_asset * 1000), NA_real_),
dw_pct = ifelse(dw_crisis == 1 & dw_crisis_amt > 0,
100 * dw_crisis_amt / (total_asset * 1000), NA_real_)
)
# Arbitrage
df_arb <- df_2023q3 %>%
left_join(btfp_arb %>% select(idrssd, btfp_arb, btfp_arb_amt), by = "idrssd") %>%
left_join(dw_arb %>% select(idrssd, dw_arb), by = "idrssd") %>%
mutate(
btfp_arb = replace_na(btfp_arb, 0L),
dw_arb = replace_na(dw_arb, 0L),
fhlb_user = as.integer(abnormal_fhlb_borrowing_10pct == 1),
any_fed = as.integer(btfp_arb == 1 | dw_arb == 1),
non_user = as.integer(any_fed == 0 & fhlb_user == 0),
user_group = factor(case_when(
btfp_arb == 1 & dw_arb == 1 ~ "Both",
btfp_arb == 1 ~ "BTFP_Only",
dw_arb == 1 ~ "DW_Only",
TRUE ~ "Neither"
), levels = c("Neither", "BTFP_Only", "DW_Only", "Both"))
)
cat("=== PERIOD COUNTS ===\n")
## === PERIOD COUNTS ===
cat("Crisis:", nrow(df_crisis), "| BTFP:", sum(df_crisis$btfp_crisis),
"| DW:", sum(df_crisis$dw_crisis), "| Any:", sum(df_crisis$any_fed), "\n")
## Crisis: 4292 | BTFP: 501 | DW: 433 | Any: 828
cat("Arb:", nrow(df_arb), "| BTFP:", sum(df_arb$btfp_arb),
"| DW:", sum(df_arb$dw_arb), "\n")
## Arb: 4214 | BTFP: 749 | DW: 362
REGRESSION
INFRASTRUCTURE
CONTROLS <- "ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + wholesale + roa"
EXPL_BASE <- "mtm_total + uninsured_lev + mtm_x_uninsured"
# Triple interaction for deposit beta channel
EXPL_BETA_FULL <- paste0(
"mtm_total + uninsured_lev + uninsured_beta + ",
"mtm_x_uninsured + mtm_x_unins_beta + unins_x_unins_beta + ",
"mtm_x_unins_x_beta")
EXPL_BETA_NESTED <- paste0(
"mtm_total + uninsured_lev + uninsured_beta + ",
"mtm_x_uninsured + mtm_x_unins_beta + unins_x_unins_beta")
# Facility choice
EXPL_FACILITY <- "mtm_total + uninsured_lev + mtm_x_uninsured + par_benefit + collateral_capacity"
setFixest_dict(c(
mtm_total = "MTM Loss (z)",
uninsured_lev = "Uninsured Leverage (z)",
insured_lev = "Insured Leverage (z)",
uninsured_beta = "Uninsured $\\beta$ (z)",
mtm_x_uninsured = "MTM $\\times$ Uninsured",
mtm_x_insured = "MTM $\\times$ Insured",
mtm_x_unins_beta = "MTM $\\times$ Unins. $\\beta$",
unins_x_unins_beta = "Uninsured $\\times$ Unins. $\\beta$",
mtm_x_unins_x_beta = "MTM $\\times$ Uninsured $\\times$ $\\beta^U$",
par_benefit = "Par Benefit (z)",
collateral_capacity = "OMO Collateral (z)",
par_x_uninsured = "Par $\\times$ Uninsured",
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"
))
COEF_ORDER <- c("Constant", "MTM Loss", "Uninsured Leverage", "Uninsured.*beta",
"MTM.*Uninsured$", "MTM.*Unins.*beta", "Uninsured.*Unins",
"MTM.*Uninsured.*beta", "Par Benefit", "OMO Collateral")
# --- Model runner with safety ---
run_model <- function(data, dv, explanatory, family_type = "lpm", controls = CONTROLS) {
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")
}
}
safe_run <- function(data, dv, explanatory, family_type = "lpm",
controls = CONTROLS, min_n = 30, min_dv1 = 5) {
n_obs <- sum(!is.na(data[[dv]]))
n_dv1 <- sum(data[[dv]] == 1, na.rm = TRUE)
if (n_obs < min_n || n_dv1 < min_dv1) {
message(" Skip: N=", n_obs, " DV=1:", n_dv1, " for ", dv)
return(NULL)
}
tryCatch(
run_model(data, dv, explanatory, family_type, controls),
error = function(e) { message(" Error: ", e$message); NULL }
)
}
# --- Save etable ---
save_etable <- function(models, filename, title_text, notes_text,
fitstat_use = ~ n + r2, extra_lines = NULL) {
models <- models[!sapply(models, is.null)]
if (length(models) == 0) { message("No models for ", filename); return(invisible(NULL)) }
etable(models, title = title_text, notes = notes_text,
fitstat = fitstat_use, order = COEF_ORDER, extralines = extra_lines,
se.below = TRUE, tex = TRUE,
file = file.path(TABLE_PATH, paste0(filename, ".tex")),
replace = TRUE, style.tex = style.tex("aer"))
message("Saved: ", filename, ".tex")
}
theme_paper <- theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", size = 13, hjust = 0),
plot.subtitle = element_text(size = 10, color = "grey40", hjust = 0),
legend.position = "bottom", panel.grid.minor = element_blank(),
strip.text = element_text(face = "bold", size = 11))
cat("=== INFRASTRUCTURE READY ===\n")
## === INFRASTRUCTURE READY ===
REGRESSION SAMPLES
# === Pure comparison samples (Crisis) ===
df_btfp_s <- df_crisis %>% filter(btfp_crisis == 1 | non_user == 1)
df_dw_s <- df_crisis %>% filter(dw_crisis == 1 | non_user == 1)
df_anyfed_s <- df_crisis %>% filter(any_fed == 1 | non_user == 1)
df_fhlb_s <- df_crisis %>% filter(fhlb_user == 1 | non_user == 1)
# === Arbitrage samples ===
df_btfp_arb_s <- df_arb %>% filter(btfp_arb == 1 | non_user == 1)
df_dw_arb_s <- df_arb %>% filter(dw_arb == 1 | non_user == 1)
cat("=== CRISIS SAMPLES ===\n")
## === CRISIS SAMPLES ===
cat("BTFP:", nrow(df_btfp_s), "(", sum(df_btfp_s$btfp_crisis), "borrowers)\n")
## BTFP: 3727 ( 501 borrowers)
cat("DW:", nrow(df_dw_s), "(", sum(df_dw_s$dw_crisis), "borrowers)\n")
## DW: 3659 ( 433 borrowers)
cat("AnyFed:", nrow(df_anyfed_s), "(", sum(df_anyfed_s$any_fed), "borrowers)\n")
## AnyFed: 4054 ( 828 borrowers)
cat("FHLB:", nrow(df_fhlb_s), "(", sum(df_fhlb_s$fhlb_user), "borrowers)\n")
## FHLB: 3528 ( 302 borrowers)
# === Solvency sub-sample counts ===
cat("\n=== SOLVENCY DISTRIBUTIONS IN ANYFED SAMPLE ===\n")
##
## === SOLVENCY DISTRIBUTIONS IN ANYFED SAMPLE ===
cat("Jiang MTM: Solvent=", sum(df_anyfed_s$mtm_solvent, na.rm=T),
" Insolvent=", sum(df_anyfed_s$mtm_insolvent, na.rm=T), "\n")
## Jiang MTM: Solvent= 3252 Insolvent= 792
cat("IDCR-100%: Solvent=", sum(df_anyfed_s$solvent_idcr_100, na.rm=T),
" Insolvent=", sum(df_anyfed_s$insolvent_idcr_100, na.rm=T), "\n")
## IDCR-100%: Solvent= 3661 Insolvent= 352
cat("DSSW: Solvent=", sum(df_anyfed_s$dssw_solvent, na.rm=T),
" Insolvent=", sum(df_anyfed_s$dssw_insolvent, na.rm=T), "\n")
## DSSW: Solvent= 3991 Insolvent= 0
MAIN RESULTS A: Full
Sample (No Solvency Split)
cat("=== MAIN A: FULL SAMPLE (CRISIS) ===\n")
## === MAIN A: FULL SAMPLE (CRISIS) ===
mod_A_any <- run_model(df_anyfed_s, "any_fed", EXPL_BASE)
mod_A_btfp <- run_model(df_btfp_s, "btfp_crisis", EXPL_BASE)
mod_A_dw <- run_model(df_dw_s, "dw_crisis", EXPL_BASE)
mod_A_fhlb <- run_model(df_fhlb_s, "fhlb_user", EXPL_BASE)
models_A <- list(
"Any Fed" = mod_A_any, "BTFP" = mod_A_btfp,
"DW" = mod_A_dw, "FHLB" = mod_A_fhlb)
save_etable(models_A, "MainA_full_sample",
title_text = "Main Results A: Full Sample — Extensive Margin (Crisis Period)",
notes_text = "LPM. DV=1 if borrowed, 0 if pure non-borrower. Crisis: Mar 8--May 4, 2023. FHLB: falsification. Robust SEs. z-standardized.",
extra_lines = list(
c("Borrowers", sum(df_anyfed_s$any_fed), sum(df_btfp_s$btfp_crisis),
sum(df_dw_s$dw_crisis), sum(df_fhlb_s$fhlb_user)),
c("Mean DV", round(mean(df_anyfed_s$any_fed),3), round(mean(df_btfp_s$btfp_crisis),3),
round(mean(df_dw_s$dw_crisis),3), round(mean(df_fhlb_s$fhlb_user),3))))
etable(models_A, fitstat = ~ n + r2, se.below = TRUE)
## Any Fed BTFP DW FHLB
## Dependent Var.: any_fed btfp_crisis dw_crisis fhlb_user
##
## Constant 0.2081*** 0.1435*** 0.1272*** 0.0908***
## (0.0060) (0.0056) (0.0054) (0.0050)
## MTM Loss (z) 0.0300*** 0.0249*** 0.0158** 0.0039
## (0.0070) (0.0062) (0.0061) (0.0056)
## Uninsured Leverage (z) 0.0198** 0.0214** 0.0126 0.0064
## (0.0076) (0.0070) (0.0067) (0.0051)
## MTM $\times$ Uninsured 0.0199*** 0.0193*** 0.0170*** -0.0039
## (0.0057) (0.0052) (0.0051) (0.0043)
## Log(Assets) 0.1064*** 0.0713*** 0.0920*** 0.0266***
## (0.0082) (0.0077) (0.0077) (0.0065)
## Cash Ratio -0.0246*** -0.0285*** -0.0043 -0.0214***
## (0.0061) (0.0048) (0.0053) (0.0039)
## Loan-to-Deposit -0.0035 -0.0107 -0.0007 0.0244***
## (0.0067) (0.0057) (0.0057) (0.0050)
## Book Equity Ratio -0.0135* -0.0118* -0.0006 0.0101*
## (0.0055) (0.0047) (0.0044) (0.0041)
## Wholesale Funding 0.0297*** 0.0263*** 0.0204** 0.0019
## (0.0069) (0.0065) (0.0063) (0.0051)
## ROA -0.0019 -0.0059 -0.0029 -0.0091*
## (0.0057) (0.0049) (0.0048) (0.0043)
## ______________________ __________ ___________ __________ __________
## S.E. type Hete.-rob. Heter.-rob. Hete.-rob. Hete.-rob.
## Observations 4,044 3,717 3,649 3,518
## R2 0.12097 0.09615 0.10314 0.04090
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
MAIN RESULTS B: Jiang
MTM Solvent vs. Insolvent
cat("=== MAIN B: JIANG MTM SOLVENCY SPLIT ===\n")
## === MAIN B: JIANG MTM SOLVENCY SPLIT ===
# Solvent
mod_B_any_sol <- safe_run(df_anyfed_s %>% filter(mtm_solvent==1), "any_fed", EXPL_BASE)
mod_B_btfp_sol <- safe_run(df_btfp_s %>% filter(mtm_solvent==1), "btfp_crisis", EXPL_BASE)
mod_B_dw_sol <- safe_run(df_dw_s %>% filter(mtm_solvent==1), "dw_crisis", EXPL_BASE)
mod_B_fhlb_sol <- safe_run(df_fhlb_s %>% filter(mtm_solvent==1), "fhlb_user", EXPL_BASE)
# Insolvent
mod_B_any_ins <- safe_run(df_anyfed_s %>% filter(mtm_insolvent==1), "any_fed", EXPL_BASE)
mod_B_btfp_ins <- safe_run(df_btfp_s %>% filter(mtm_insolvent==1), "btfp_crisis", EXPL_BASE)
mod_B_dw_ins <- safe_run(df_dw_s %>% filter(mtm_insolvent==1), "dw_crisis", EXPL_BASE)
mod_B_fhlb_ins <- safe_run(df_fhlb_s %>% filter(mtm_insolvent==1), "fhlb_user", EXPL_BASE)
models_B <- list(
"Any (Sol)" = mod_B_any_sol, "Any (Ins)" = mod_B_any_ins,
"BTFP (Sol)" = mod_B_btfp_sol, "BTFP (Ins)" = mod_B_btfp_ins,
"DW (Sol)" = mod_B_dw_sol, "DW (Ins)" = mod_B_dw_ins,
"FHLB (Sol)" = mod_B_fhlb_sol, "FHLB (Ins)" = mod_B_fhlb_ins)
save_etable(models_B, "MainB_jiang_mtm_solvency",
title_text = "Main Results B: Jiang MTM Solvency Split (Crisis Period)",
notes_text = "LPM. Solvency: Adj. Equity = Book Equity/TA $-$ MTM/TA. Solvent: AE $\\geq$ 0. Insolvent: AE $<$ 0. Robust SEs. z-standardized.",
extra_lines = list(
c("Solvency", "Sol", "Ins", "Sol", "Ins", "Sol", "Ins", "Sol", "Ins")))
etable(models_B[!sapply(models_B, is.null)], fitstat = ~ n + r2, se.below = TRUE)
## Any (Sol) Any (Ins) BTFP (Sol) BTFP (Ins) DW (Sol)
## Dependent Var.: any_fed any_fed btfp_crisis btfp_crisis dw_crisis
##
## Constant 0.2096*** 0.0909 0.1420*** 0.0364 0.1322***
## (0.0075) (0.0589) (0.0070) (0.0558) (0.0068)
## MTM Loss (z) 0.0376*** 0.0162 0.0286*** 0.0172 0.0238**
## (0.0085) (0.0280) (0.0073) (0.0266) (0.0076)
## Uninsured Leverage (z) 0.0263** -0.0357 0.0290*** -0.0295 0.0168*
## (0.0091) (0.0301) (0.0083) (0.0276) (0.0081)
## MTM $\times$ Uninsured 0.0213** 0.0549* 0.0213*** 0.0478* 0.0185**
## (0.0069) (0.0243) (0.0061) (0.0231) (0.0062)
## Log(Assets) 0.1001*** 0.1368*** 0.0647*** 0.1045*** 0.0882***
## (0.0088) (0.0222) (0.0082) (0.0219) (0.0083)
## Cash Ratio -0.0202** -0.0789*** -0.0254*** -0.0738*** -0.0019
## (0.0063) (0.0234) (0.0048) (0.0211) (0.0055)
## Loan-to-Deposit -0.0024 0.0126 -0.0099 0.0106 -0.0017
## (0.0071) (0.0220) (0.0059) (0.0206) (0.0061)
## Book Equity Ratio -0.0109 -0.1328** -0.0073 -0.1300** -0.0021
## (0.0058) (0.0434) (0.0048) (0.0414) (0.0048)
## Wholesale Funding 0.0298*** 0.0219 0.0259*** 0.0199 0.0211**
## (0.0077) (0.0150) (0.0072) (0.0148) (0.0070)
## ROA 0.0029 -0.0344* -0.0022 -0.0316* 0.0016
## (0.0060) (0.0167) (0.0051) (0.0153) (0.0051)
## ______________________ __________ __________ ___________ ___________ __________
## S.E. type Hete.-rob. Hete.-rob. Heter.-rob. Heter.-rob. Hete.-rob.
## Observations 3,252 792 2,986 731 2,977
## R2 0.12422 0.11947 0.09650 0.09592 0.10686
##
## DW (Ins) FHLB (Sol) FHLB (I..
## Dependent Var.: dw_crisis fhlb_user fhlb_user
##
## Constant 0.0413 0.0918*** 0.0777
## (0.0487) (0.0060) (0.0414)
## MTM Loss (z) 0.0132 0.0015 0.0298
## (0.0236) (0.0067) (0.0187)
## Uninsured Leverage (z) -0.0197 0.0046 0.0076
## (0.0247) (0.0065) (0.0178)
## MTM $\times$ Uninsured 0.0400 -0.0049 0.0021
## (0.0204) (0.0055) (0.0156)
## Log(Assets) 0.1086*** 0.0285*** 0.0128
## (0.0206) (0.0072) (0.0139)
## Cash Ratio -0.0370* -0.0229*** -0.0106
## (0.0185) (0.0041) (0.0153)
## Loan-to-Deposit 0.0079 0.0219*** 0.0263*
## (0.0179) (0.0056) (0.0129)
## Book Equity Ratio -0.0715* 0.0081 0.0268
## (0.0347) (0.0045) (0.0303)
## Wholesale Funding 0.0149 0.0023 0.0010
## (0.0143) (0.0058) (0.0103)
## ROA -0.0319* -0.0090 -0.0096
## (0.0140) (0.0046) (0.0109)
## ______________________ __________ __________ _________
## S.E. type Hete.-rob. Hete.-rob. Het.-rob.
## Observations 672 2,886 632
## R2 0.10868 0.04299 0.03281
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
MAIN RESULTS C: Jiang
IDCR-100% Solvent vs. Insolvent
cat("=== MAIN C: JIANG IDCR-100% SOLVENCY SPLIT ===\n")
## === MAIN C: JIANG IDCR-100% SOLVENCY SPLIT ===
cat("IDCR-100%: If 100% of uninsured depositors run, can the bank pay insured depositors?\n")
## IDCR-100%: If 100% of uninsured depositors run, can the bank pay insured depositors?
cat("IDCR = (MV_Assets - Uninsured - Insured) / Insured\n\n")
## IDCR = (MV_Assets - Uninsured - Insured) / Insured
# Solvent
mod_C_any_sol <- safe_run(df_anyfed_s %>% filter(solvent_idcr_100==1), "any_fed", EXPL_BASE)
mod_C_btfp_sol <- safe_run(df_btfp_s %>% filter(solvent_idcr_100==1), "btfp_crisis", EXPL_BASE)
mod_C_dw_sol <- safe_run(df_dw_s %>% filter(solvent_idcr_100==1), "dw_crisis", EXPL_BASE)
mod_C_fhlb_sol <- safe_run(df_fhlb_s %>% filter(solvent_idcr_100==1), "fhlb_user", EXPL_BASE)
# Insolvent
mod_C_any_ins <- safe_run(df_anyfed_s %>% filter(insolvent_idcr_100==1), "any_fed", EXPL_BASE)
mod_C_btfp_ins <- safe_run(df_btfp_s %>% filter(insolvent_idcr_100==1), "btfp_crisis", EXPL_BASE)
mod_C_dw_ins <- safe_run(df_dw_s %>% filter(insolvent_idcr_100==1), "dw_crisis", EXPL_BASE)
mod_C_fhlb_ins <- safe_run(df_fhlb_s %>% filter(insolvent_idcr_100==1), "fhlb_user", EXPL_BASE)
models_C <- list(
"Any (Sol)" = mod_C_any_sol, "Any (Ins)" = mod_C_any_ins,
"BTFP (Sol)" = mod_C_btfp_sol, "BTFP (Ins)" = mod_C_btfp_ins,
"DW (Sol)" = mod_C_dw_sol, "DW (Ins)" = mod_C_dw_ins,
"FHLB (Sol)" = mod_C_fhlb_sol, "FHLB (Ins)" = mod_C_fhlb_ins)
save_etable(models_C, "MainC_idcr100_solvency",
title_text = "Main Results C: IDCR-100\\% Solvency Split (Crisis Period)",
notes_text = paste0(
"LPM. Solvency: IDCR = (MV Assets $-$ Uninsured $-$ Insured) / Insured. ",
"This measures whether the bank can pay ALL depositors after a 100\\% uninsured run. ",
"Solvent: IDCR $\\geq$ 0. Insolvent: IDCR $<$ 0. ",
"Robust SEs. z-standardized."),
extra_lines = list(
c("Solvency", "Sol", "Ins", "Sol", "Ins", "Sol", "Ins", "Sol", "Ins")))
etable(models_C[!sapply(models_C, is.null)], fitstat = ~ n + r2, se.below = TRUE)
## Any (Sol) Any (Ins) BTFP (Sol) BTFP (Ins) DW (Sol)
## Dependent Var.: any_fed any_fed btfp_crisis btfp_crisis dw_crisis
##
## Constant 0.2095*** 0.1523 0.1432*** 0.0743 0.1299***
## (0.0065) (0.0831) (0.0061) (0.0830) (0.0059)
## MTM Loss (z) 0.0332*** 0.0241 0.0258*** 0.0321 0.0191**
## (0.0076) (0.0363) (0.0067) (0.0346) (0.0068)
## Uninsured Leverage (z) 0.0234** -0.0106 0.0235** -0.0071 0.0156*
## (0.0083) (0.0409) (0.0077) (0.0355) (0.0074)
## MTM $\times$ Uninsured 0.0205** 0.0308 0.0187** 0.0365 0.0187**
## (0.0064) (0.0317) (0.0059) (0.0284) (0.0059)
## Log(Assets) 0.1038*** 0.1395*** 0.0712*** 0.0728* 0.0895***
## (0.0086) (0.0328) (0.0080) (0.0316) (0.0081)
## Cash Ratio -0.0229*** -0.0598* -0.0269*** -0.0570* -0.0035
## (0.0065) (0.0270) (0.0051) (0.0252) (0.0057)
## Loan-to-Deposit -0.0018 -0.0033 -0.0085 -0.0017 -0.0009
## (0.0073) (0.0345) (0.0063) (0.0322) (0.0064)
## Book Equity Ratio -0.0165** -0.0459 -0.0138** -0.0657 -0.0036
## (0.0062) (0.0571) (0.0053) (0.0561) (0.0051)
## Wholesale Funding 0.0296*** -0.0120 0.0265*** 0.0169 0.0200**
## (0.0070) (0.0839) (0.0066) (0.0821) (0.0064)
## ROA -0.0017 -0.0255 -0.0067 -0.0176 -0.0019
## (0.0062) (0.0231) (0.0053) (0.0213) (0.0053)
## ______________________ __________ __________ ___________ ___________ __________
## S.E. type Hete.-rob. Hete.-rob. Heter.-rob. Heter.-rob. Hete.-rob.
## Observations 3,661 352 3,355 331 3,314
## R2 0.12227 0.10801 0.09933 0.06647 0.10116
##
## DW (Ins) FHLB (Sol) FHLB (I..
## Dependent Var.: dw_crisis fhlb_user fhlb_user
##
## Constant 0.0743 0.0916*** 0.0107
## (0.0658) (0.0053) (0.0408)
## MTM Loss (z) 0.0191 0.0047 0.0229
## (0.0268) (0.0063) (0.0162)
## Uninsured Leverage (z) 0.0061 0.0065 0.0060
## (0.0330) (0.0058) (0.0170)
## MTM $\times$ Uninsured 0.0110 -0.0052 0.0019
## (0.0247) (0.0051) (0.0132)
## Log(Assets) 0.1268*** 0.0273*** 0.0064
## (0.0305) (0.0070) (0.0135)
## Cash Ratio -0.0208 -0.0223*** -0.0101
## (0.0187) (0.0043) (0.0095)
## Loan-to-Deposit -0.0024 0.0246*** 0.0316*
## (0.0253) (0.0058) (0.0144)
## Book Equity Ratio -0.0076 0.0078 -0.0328
## (0.0423) (0.0048) (0.0315)
## Wholesale Funding -0.0466 0.0014 -0.0009
## (0.0568) (0.0052) (0.0478)
## ROA -0.0245 -0.0098* -0.0125
## (0.0203) (0.0047) (0.0154)
## ______________________ __________ __________ _________
## S.E. type Hete.-rob. Hete.-rob. Het.-rob.
## Observations 304 3,200 287
## R2 0.14081 0.04044 0.02156
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
MAIN RESULTS D: DSSW
Deposit Franchise Diagnostic
# ==============================================================================
# DSSW DIAGNOSTIC: Why zero banks are DSSW-insolvent, and why this matters
#
# Key finding: DFV ≈ 68% of assets at the median bank, far exceeding
# average MTM losses of ~5.5%. This makes every bank DSSW-solvent.
#
# Interpretation: The deposit franchise exists ONLY in the no-run equilibrium.
# If depositors run, the uninsured franchise is destroyed, and banks revert
# to Jiang-style insolvency. That 828 banks sought emergency liquidity
# despite being DSSW-solvent is direct evidence that the coordination
# problem — not fundamental insolvency — drove borrowing.
# ==============================================================================
cat("================================================================\n")
## ================================================================
cat(" DSSW DEPOSIT FRANCHISE VALUE DIAGNOSTIC\n")
## DSSW DEPOSIT FRANCHISE VALUE DIAGNOSTIC
cat("================================================================\n\n")
## ================================================================
# Coverage
n_total <- nrow(df_2022q4)
n_beta <- sum(!is.na(df_2022q4$beta_overall))
cat(sprintf("Beta coverage: %d / %d (%.1f%%)\n\n", n_beta, n_total, 100 * n_beta / n_total))
## Beta coverage: 4226 / 4292 (98.5%)
# DFV distribution
cat("--- DFV Distribution (% of assets) ---\n")
## --- DFV Distribution (% of assets) ---
print(summary(df_2022q4$dfv_raw))
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## -8.887 67.675 77.723 74.838 85.413 107.054 66
cat("\n--- DSSW Adjusted Equity Distribution (%) ---\n")
##
## --- DSSW Adjusted Equity Distribution (%) ---
print(summary(df_2022q4$adjusted_equity_dssw_raw))
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 1.223 71.797 81.603 78.951 88.751 112.129 66
# The key result
n_dssw_insol <- sum(df_2022q4$dssw_insolvent, na.rm = TRUE)
n_jiang_insol <- sum(df_2022q4$mtm_insolvent, na.rm = TRUE)
n_idcr_insol <- sum(df_2022q4$insolvent_idcr_100, na.rm = TRUE)
cat("\n================================================================\n")
##
## ================================================================
cat(" SOLVENCY CLASSIFICATION COMPARISON\n")
## SOLVENCY CLASSIFICATION COMPARISON
cat("================================================================\n")
## ================================================================
cat(sprintf(" Jiang MTM insolvent: %d banks\n", n_jiang_insol))
## Jiang MTM insolvent: 825 banks
cat(sprintf(" IDCR-100%% insolvent: %d banks\n", n_idcr_insol))
## IDCR-100% insolvent: 364 banks
cat(sprintf(" DSSW insolvent: %d banks\n", n_dssw_insol))
## DSSW insolvent: 0 banks
cat(sprintf(" Rescued by DFV (Jiang→DSSW solvent): %d banks\n",
sum(df_2022q4$mtm_insolvent == 1 & df_2022q4$dssw_solvent == 1, na.rm = TRUE)))
## Rescued by DFV (Jiang→DSSW solvent): 813 banks
cat("\n--- Why? Mean DFV vs. Mean MTM Loss ---\n")
##
## --- Why? Mean DFV vs. Mean MTM Loss ---
cat(sprintf(" Mean DFV: %.2f%% of assets\n", mean(df_2022q4$dfv_raw, na.rm = TRUE)))
## Mean DFV: 74.84% of assets
cat(sprintf(" Mean MTM Loss: %.2f%% of assets\n", mean(df_2022q4$mtm_total_raw, na.rm = TRUE)))
## Mean MTM Loss: 5.47% of assets
cat(sprintf(" Mean Book Eq: %.2f%% of assets\n", mean(df_2022q4$book_equity_ratio_raw, na.rm = TRUE)))
## Mean Book Eq: 10.22% of assets
cat(" → DFV dwarfs MTM losses. Every bank is DSSW-solvent absent a run.\n")
## → DFV dwarfs MTM losses. Every bank is DSSW-solvent absent a run.
# Cross-tabulation
cat("\n--- Jiang × DSSW Cross-Tab ---\n")
##
## --- Jiang × DSSW Cross-Tab ---
df_2022q4 %>%
filter(!is.na(dssw_solvent)) %>%
count(
Jiang = ifelse(mtm_insolvent == 1, "Insolvent", "Solvent"),
DSSW = ifelse(dssw_insolvent == 1, "Insolvent", "Solvent")
) %>%
print()
## # A tibble: 3 × 3
## Jiang DSSW n
## <chr> <chr> <int>
## 1 Insolvent Solvent 825
## 2 Solvent Solvent 3457
## 3 <NA> Solvent 10
# Among emergency borrowers
cat("\n--- Among Crisis Emergency Borrowers ---\n")
##
## --- Among Crisis Emergency Borrowers ---
crisis_borrowers <- df_crisis %>% filter(any_fed == 1)
cat(sprintf(" Total emergency borrowers: %d\n", nrow(crisis_borrowers)))
## Total emergency borrowers: 828
cat(sprintf(" DSSW-solvent among them: %d (%.1f%%)\n",
sum(crisis_borrowers$dssw_solvent, na.rm = TRUE),
100 * mean(crisis_borrowers$dssw_solvent, na.rm = TRUE)))
## DSSW-solvent among them: 822 (99.3%)
cat(" → Every bank that borrowed was solvent in the no-run equilibrium.\n")
## → Every bank that borrowed was solvent in the no-run equilibrium.
cat(" → Borrowing was driven by coordination failure, not fundamental insolvency.\n")
## → Borrowing was driven by coordination failure, not fundamental insolvency.
DSSW
Visualization
df_scatter <- df_crisis %>%
filter(!is.na(adjusted_equity_dssw_raw)) %>%
mutate(Borrower = case_when(
any_fed == 1 ~ "Emergency Borrower", fhlb_user == 1 ~ "FHLB", TRUE ~ "Non-Borrower"))
p_scatter <- ggplot(df_scatter,
aes(x = adjusted_equity_raw, y = adjusted_equity_dssw_raw,
color = Borrower, alpha = Borrower)) +
geom_point(size = 1.5) +
geom_abline(slope = 1, intercept = 0, linetype = "dashed", color = "grey50") +
geom_hline(yintercept = 0, linetype = "dotted", color = "red") +
geom_vline(xintercept = 0, linetype = "dotted", color = "red") +
scale_color_manual(values = c("Emergency Borrower" = "#003049",
"FHLB" = "#F77F00", "Non-Borrower" = "grey70")) +
scale_alpha_manual(values = c("Emergency Borrower" = 0.8,
"FHLB" = 0.6, "Non-Borrower" = 0.3)) +
labs(
title = "Jiang vs. DSSW Adjusted Equity: All Banks Are DSSW-Solvent",
subtitle = "DFV ≈ 68% of assets rescues every Jiang-insolvent bank. But franchise exists only if depositors stay.",
x = "Jiang Adjusted Equity (Book Eq. − MTM, %)",
y = "DSSW Adjusted Equity (Book Eq. − MTM + DFV, %)"
) +
theme_paper +
annotate("rect", xmin = -Inf, xmax = 0, ymin = 0, ymax = Inf,
fill = "darkgreen", alpha = 0.05) +
annotate("text", x = -3, y = 50, label = "Panic Region:\nJiang-insolvent but\nDSSW-solvent",
color = "darkgreen", fontface = "italic", size = 3.5)
p_scatter

save_figure(p_scatter, "Fig_Jiang_vs_DSSW_Scatter", width = 10, height = 6)
TEMPORAL ROBUSTNESS: Crisis vs. Arbitrage
Full Sample
# ==============================================================================
# TEMPORAL: FULL SAMPLE (no solvency split)
# ==============================================================================
cat("=== TEMPORAL: CRISIS VS ARBITRAGE (FULL SAMPLE) ===\n")
## === TEMPORAL: CRISIS VS ARBITRAGE (FULL SAMPLE) ===
mod_T_btfp_cr <- run_model(df_btfp_s, "btfp_crisis", EXPL_BASE)
mod_T_btfp_arb <- run_model(df_btfp_arb_s, "btfp_arb", EXPL_BASE)
mod_T_dw_cr <- run_model(df_dw_s, "dw_crisis", EXPL_BASE)
mod_T_dw_arb <- safe_run(df_dw_arb_s, "dw_arb", EXPL_BASE)
models_T_full <- list(
"BTFP Crisis" = mod_T_btfp_cr, "BTFP Arb" = mod_T_btfp_arb,
"DW Crisis" = mod_T_dw_cr, "DW Arb" = mod_T_dw_arb)
save_etable(models_T_full, "Temporal_full_sample",
title_text = "Temporal Robustness: Full Sample — Crisis vs. Arbitrage",
notes_text = paste0(
"LPM. Crisis: Mar 8--May 4, 2023 (2022Q4 baseline). ",
"Arbitrage: Nov 15, 2023--Jan 24, 2024 (2023Q3 baseline). ",
"DW data through Dec 31, 2023. ",
"Prediction: MTM $\\times$ Uninsured strong in crisis, weak in arbitrage. ",
"Robust SEs. z-standardized."),
extra_lines = list(
c("Period", "Crisis", "Arb", "Crisis", "Arb"),
c("Facility", "BTFP", "BTFP", "DW", "DW")))
etable(models_T_full[!sapply(models_T_full, is.null)], fitstat = ~ n + r2, se.below = TRUE)
## BTFP Crisis BTFP Arb DW Crisis DW Arb
## Dependent Var.: btfp_crisis btfp_arb dw_crisis dw_arb
##
## Constant 0.1435*** 0.2019*** 0.1272*** 0.1164***
## (0.0056) (0.0060) (0.0054) (0.0056)
## MTM Loss (z) 0.0249*** 0.0217** 0.0158** -0.0037
## (0.0062) (0.0072) (0.0061) (0.0065)
## Uninsured Leverage (z) 0.0214** 0.0103 0.0126 -0.0097
## (0.0070) (0.0070) (0.0067) (0.0063)
## MTM $\times$ Uninsured 0.0193*** 0.0075 0.0170*** 0.0009
## (0.0052) (0.0054) (0.0051) (0.0051)
## Log(Assets) 0.0713*** 0.0644*** 0.0920*** 0.0771***
## (0.0077) (0.0076) (0.0077) (0.0074)
## Cash Ratio -0.0285*** -0.0371*** -0.0043 -0.0103*
## (0.0048) (0.0059) (0.0053) (0.0051)
## Loan-to-Deposit -0.0107 -0.0025 -0.0007 -0.0007
## (0.0057) (0.0064) (0.0057) (0.0052)
## Book Equity Ratio -0.0118* -0.0058 -0.0006 -0.0052
## (0.0047) (0.0058) (0.0044) (0.0046)
## Wholesale Funding 0.0263*** 0.1088*** 0.0204** 0.0324***
## (0.0065) (0.0079) (0.0063) (0.0072)
## ROA -0.0059 -0.0242*** -0.0029 -0.0081
## (0.0049) (0.0059) (0.0048) (0.0048)
## ______________________ ___________ __________ __________ __________
## S.E. type Heter.-rob. Hete.-rob. Hete.-rob. Hete.-rob.
## Observations 3,717 3,794 3,649 3,407
## R2 0.09615 0.16301 0.10314 0.08057
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Jiang MTM Solvency
Split
# ==============================================================================
# TEMPORAL × JIANG MTM SOLVENCY
#
# GP theory prediction:
# Solvent (panic region): Interaction strong in crisis, vanishes in arb
# Insolvent (below θ̲): Interaction weak in BOTH periods
#
# This is a Solvency × Period difference-in-differences argument.
# ==============================================================================
cat("=== TEMPORAL × JIANG MTM SOLVENCY ===\n")
## === TEMPORAL × JIANG MTM SOLVENCY ===
# --- BTFP: Crisis × Solvency ---
mod_T_btfp_cr_sol <- safe_run(df_btfp_s %>% filter(mtm_solvent==1), "btfp_crisis", EXPL_BASE)
mod_T_btfp_cr_ins <- safe_run(df_btfp_s %>% filter(mtm_insolvent==1), "btfp_crisis", EXPL_BASE)
# --- BTFP: Arb × Solvency ---
mod_T_btfp_arb_sol <- safe_run(df_btfp_arb_s %>% filter(mtm_solvent==1), "btfp_arb", EXPL_BASE)
mod_T_btfp_arb_ins <- safe_run(df_btfp_arb_s %>% filter(mtm_insolvent==1), "btfp_arb", EXPL_BASE)
# --- DW: Crisis × Solvency ---
mod_T_dw_cr_sol <- safe_run(df_dw_s %>% filter(mtm_solvent==1), "dw_crisis", EXPL_BASE)
mod_T_dw_cr_ins <- safe_run(df_dw_s %>% filter(mtm_insolvent==1), "dw_crisis", EXPL_BASE)
# --- DW: Arb × Solvency ---
mod_T_dw_arb_sol <- safe_run(df_dw_arb_s %>% filter(mtm_solvent==1), "dw_arb", EXPL_BASE)
mod_T_dw_arb_ins <- safe_run(df_dw_arb_s %>% filter(mtm_insolvent==1), "dw_arb", EXPL_BASE)
models_T_jiang <- list(
"BTFP Cr (Sol)" = mod_T_btfp_cr_sol, "BTFP Cr (Ins)" = mod_T_btfp_cr_ins,
"BTFP Arb (Sol)" = mod_T_btfp_arb_sol, "BTFP Arb (Ins)" = mod_T_btfp_arb_ins,
"DW Cr (Sol)" = mod_T_dw_cr_sol, "DW Cr (Ins)" = mod_T_dw_cr_ins,
"DW Arb (Sol)" = mod_T_dw_arb_sol, "DW Arb (Ins)" = mod_T_dw_arb_ins)
save_etable(models_T_jiang, "Temporal_jiang_solvency",
title_text = "Temporal $\\times$ Jiang MTM Solvency: Crisis vs. Arbitrage",
notes_text = paste0(
"LPM. Jiang solvency: Adj.\\ Equity = Book Equity/TA $-$ MTM/TA $\\geq$ 0. ",
"GP prediction: Among solvent banks (panic region), MTM $\\times$ Uninsured ",
"should be strong in crisis and vanish in arbitrage. Among insolvent banks ",
"(below $\\underline{\\theta}$), the interaction should be weak in both periods ",
"because withdrawal is a dominant strategy regardless of coordination. ",
"Robust SEs. z-standardized."),
extra_lines = list(
c("Period", "Crisis", "Crisis", "Arb", "Arb", "Crisis", "Crisis", "Arb", "Arb"),
c("Solvency", "Sol", "Ins", "Sol", "Ins", "Sol", "Ins", "Sol", "Ins"),
c("Facility", "BTFP", "BTFP", "BTFP", "BTFP", "DW", "DW", "DW", "DW")))
etable(models_T_jiang[!sapply(models_T_jiang, is.null)], fitstat = ~ n + r2, se.below = TRUE)
## BTFP Cr (.. BTFP Cr (...1 BTFP Arb.. BTFP Arb...1
## Dependent Var.: btfp_crisis btfp_crisis btfp_arb btfp_arb
##
## Constant 0.1420*** 0.0364 0.2015*** 0.1900**
## (0.0070) (0.0558) (0.0073) (0.0619)
## MTM Loss (z) 0.0286*** 0.0172 0.0215** 0.0170
## (0.0073) (0.0266) (0.0081) (0.0287)
## Uninsured Leverage (z) 0.0290*** -0.0295 0.0187* -0.0316
## (0.0083) (0.0276) (0.0082) (0.0294)
## MTM $\times$ Uninsured 0.0213*** 0.0478* 0.0122 0.0303
## (0.0061) (0.0231) (0.0062) (0.0240)
## Log(Assets) 0.0647*** 0.1045*** 0.0611*** 0.0783***
## (0.0082) (0.0219) (0.0082) (0.0217)
## Cash Ratio -0.0254*** -0.0738*** -0.0343*** -0.0791***
## (0.0048) (0.0211) (0.0061) (0.0193)
## Loan-to-Deposit -0.0099 0.0106 -0.0113 0.0551*
## (0.0059) (0.0206) (0.0067) (0.0229)
## Book Equity Ratio -0.0073 -0.1300** -0.0040 -0.0546
## (0.0048) (0.0414) (0.0061) (0.0452)
## Wholesale Funding 0.0259*** 0.0199 0.1150*** 0.0850***
## (0.0072) (0.0148) (0.0092) (0.0160)
## ROA -0.0022 -0.0316* -0.0242*** -0.0316
## (0.0051) (0.0153) (0.0061) (0.0198)
## ______________________ ___________ ___________ __________ __________
## S.E. type Heter.-rob. Heter.-rob. Hete.-rob. Hete.-rob.
## Observations 2,986 731 3,048 746
## R2 0.09650 0.09592 0.16995 0.13907
##
## DW Cr (S.. DW Cr (I.. DW Arb (.. DW Arb (...1
## Dependent Var.: dw_crisis dw_crisis dw_arb dw_arb
##
## Constant 0.1322*** 0.0413 0.1162*** 0.0448
## (0.0068) (0.0487) (0.0068) (0.0503)
## MTM Loss (z) 0.0238** 0.0132 -0.0081 0.0374
## (0.0076) (0.0236) (0.0075) (0.0257)
## Uninsured Leverage (z) 0.0168* -0.0197 -0.0091 -0.0187
## (0.0081) (0.0247) (0.0075) (0.0278)
## MTM $\times$ Uninsured 0.0185** 0.0400 0.0006 0.0104
## (0.0062) (0.0204) (0.0061) (0.0230)
## Log(Assets) 0.0882*** 0.1086*** 0.0766*** 0.0771***
## (0.0083) (0.0206) (0.0080) (0.0211)
## Cash Ratio -0.0019 -0.0370* -0.0099 -0.0281
## (0.0055) (0.0185) (0.0053) (0.0151)
## Loan-to-Deposit -0.0017 0.0079 -0.0054 0.0280
## (0.0061) (0.0179) (0.0052) (0.0202)
## Book Equity Ratio -0.0021 -0.0715* -0.0066 -0.0431
## (0.0048) (0.0347) (0.0051) (0.0369)
## Wholesale Funding 0.0211** 0.0149 0.0343*** 0.0247
## (0.0070) (0.0143) (0.0083) (0.0145)
## ROA 0.0016 -0.0319* -0.0083 -0.0124
## (0.0051) (0.0140) (0.0051) (0.0149)
## ______________________ __________ __________ __________ __________
## S.E. type Hete.-rob. Hete.-rob. Hete.-rob. Hete.-rob.
## Observations 2,977 672 2,799 608
## R2 0.10686 0.10868 0.08295 0.08754
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
IDCR-100% Solvency
Split
# ==============================================================================
# TEMPORAL × IDCR-100% SOLVENCY
# Same logic as Jiang split but using stricter run-scenario measure
# ==============================================================================
cat("=== TEMPORAL × IDCR-100% SOLVENCY ===\n")
## === TEMPORAL × IDCR-100% SOLVENCY ===
# --- BTFP ---
mod_T_btfp_cr_idcr_sol <- safe_run(df_btfp_s %>% filter(solvent_idcr_100==1), "btfp_crisis", EXPL_BASE)
mod_T_btfp_cr_idcr_ins <- safe_run(df_btfp_s %>% filter(insolvent_idcr_100==1), "btfp_crisis", EXPL_BASE)
mod_T_btfp_arb_idcr_sol <- safe_run(df_btfp_arb_s %>% filter(solvent_idcr_100==1), "btfp_arb", EXPL_BASE)
mod_T_btfp_arb_idcr_ins <- safe_run(df_btfp_arb_s %>% filter(insolvent_idcr_100==1), "btfp_arb", EXPL_BASE)
# --- DW ---
mod_T_dw_cr_idcr_sol <- safe_run(df_dw_s %>% filter(solvent_idcr_100==1), "dw_crisis", EXPL_BASE)
mod_T_dw_cr_idcr_ins <- safe_run(df_dw_s %>% filter(insolvent_idcr_100==1), "dw_crisis", EXPL_BASE)
mod_T_dw_arb_idcr_sol <- safe_run(df_dw_arb_s %>% filter(solvent_idcr_100==1), "dw_arb", EXPL_BASE)
mod_T_dw_arb_idcr_ins <- safe_run(df_dw_arb_s %>% filter(insolvent_idcr_100==1), "dw_arb", EXPL_BASE)
models_T_idcr <- list(
"BTFP Cr (Sol)" = mod_T_btfp_cr_idcr_sol, "BTFP Cr (Ins)" = mod_T_btfp_cr_idcr_ins,
"BTFP Arb (Sol)" = mod_T_btfp_arb_idcr_sol, "BTFP Arb (Ins)" = mod_T_btfp_arb_idcr_ins,
"DW Cr (Sol)" = mod_T_dw_cr_idcr_sol, "DW Cr (Ins)" = mod_T_dw_cr_idcr_ins,
"DW Arb (Sol)" = mod_T_dw_arb_idcr_sol, "DW Arb (Ins)" = mod_T_dw_arb_idcr_ins)
save_etable(models_T_idcr, "Temporal_idcr100_solvency",
title_text = "Temporal $\\times$ IDCR-100\\% Solvency: Crisis vs. Arbitrage",
notes_text = paste0(
"LPM. IDCR-100\\% solvency: (MV Assets $-$ Uninsured $-$ Insured) / Insured $\\geq$ 0. ",
"Same GP prediction as Jiang split: interaction present only among solvent banks in crisis. ",
"Robust SEs. z-standardized."),
extra_lines = list(
c("Period", "Crisis", "Crisis", "Arb", "Arb", "Crisis", "Crisis", "Arb", "Arb"),
c("Solvency", "Sol", "Ins", "Sol", "Ins", "Sol", "Ins", "Sol", "Ins"),
c("Facility", "BTFP", "BTFP", "BTFP", "BTFP", "DW", "DW", "DW", "DW")))
etable(models_T_idcr[!sapply(models_T_idcr, is.null)], fitstat = ~ n + r2, se.below = TRUE)
## BTFP Cr (.. BTFP Cr (...1 BTFP Arb.. BTFP Ar..
## Dependent Var.: btfp_crisis btfp_crisis btfp_arb btfp_arb
##
## Constant 0.1432*** 0.0743 0.2038*** 0.2812**
## (0.0061) (0.0830) (0.0064) (0.0980)
## MTM Loss (z) 0.0258*** 0.0321 0.0255*** -0.0137
## (0.0067) (0.0346) (0.0077) (0.0437)
## Uninsured Leverage (z) 0.0235** -0.0071 0.0142 -0.0384
## (0.0077) (0.0355) (0.0077) (0.0355)
## MTM $\times$ Uninsured 0.0187** 0.0365 0.0072 0.0440
## (0.0059) (0.0284) (0.0062) (0.0313)
## Log(Assets) 0.0712*** 0.0728* 0.0647*** 0.0340
## (0.0080) (0.0316) (0.0080) (0.0289)
## Cash Ratio -0.0269*** -0.0570* -0.0353*** -0.0727**
## (0.0051) (0.0252) (0.0063) (0.0230)
## Loan-to-Deposit -0.0085 -0.0017 -0.0031 0.0612
## (0.0063) (0.0322) (0.0070) (0.0361)
## Book Equity Ratio -0.0138** -0.0657 -0.0109 -0.0326
## (0.0053) (0.0561) (0.0064) (0.0647)
## Wholesale Funding 0.0265*** 0.0169 0.1061*** 0.2066**
## (0.0066) (0.0821) (0.0080) (0.0724)
## ROA -0.0067 -0.0176 -0.0273*** 0.0103
## (0.0053) (0.0213) (0.0062) (0.0212)
## ______________________ ___________ ___________ __________ _________
## S.E. type Heter.-rob. Heter.-rob. Hete.-rob. Het.-rob.
## Observations 3,355 331 3,510 255
## R2 0.09933 0.06647 0.16767 0.11883
##
## DW Cr (S.. DW Cr (I.. DW Arb (.. DW Arb..
## Dependent Var.: dw_crisis dw_crisis dw_arb dw_arb
##
## Constant 0.1299*** 0.0743 0.1168*** 0.1062
## (0.0059) (0.0658) (0.0058) (0.0834)
## MTM Loss (z) 0.0191** 0.0191 -0.0035 0.0034
## (0.0068) (0.0268) (0.0070) (0.0378)
## Uninsured Leverage (z) 0.0156* 0.0061 -0.0116 -0.0221
## (0.0074) (0.0330) (0.0069) (0.0374)
## MTM $\times$ Uninsured 0.0187** 0.0110 -0.0017 0.0255
## (0.0059) (0.0247) (0.0058) (0.0310)
## Log(Assets) 0.0895*** 0.1268*** 0.0791*** 0.0387
## (0.0081) (0.0305) (0.0077) (0.0310)
## Cash Ratio -0.0035 -0.0208 -0.0081 -0.0327
## (0.0057) (0.0187) (0.0054) (0.0207)
## Loan-to-Deposit -0.0009 -0.0024 0.0010 -0.0448
## (0.0064) (0.0253) (0.0056) (0.0346)
## Book Equity Ratio -0.0036 -0.0076 -0.0086 0.0597
## (0.0051) (0.0423) (0.0052) (0.0531)
## Wholesale Funding 0.0200** -0.0466 0.0316*** 0.0233
## (0.0064) (0.0568) (0.0073) (0.0581)
## ROA -0.0019 -0.0245 -0.0096 0.0132
## (0.0053) (0.0203) (0.0051) (0.0171)
## ______________________ __________ __________ __________ ________
## S.E. type Hete.-rob. Hete.-rob. Hete.-rob. Het.-rob.
## Observations 3,314 304 3,148 230
## R2 0.10116 0.14081 0.08255 0.05624
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
DEPOSIT
BETA CHANNEL: Triple Interaction
Full Sample
# ==============================================================================
# DEPOSIT BETA CHANNEL — FULL SAMPLE
#
# DSSW predicts: panic region is wider when uninsured deposits are more
# rate-sensitive (higher β^U). The triple interaction MTM × Uninsured × β^U
# tests whether panic amplification is stronger for banks with flightier
# deposits. This is the empirical test of the DSSW franchise-run mechanism.
# ==============================================================================
cat("=== DEPOSIT BETA CHANNEL (FULL SAMPLE) ===\n")
## === DEPOSIT BETA CHANNEL (FULL SAMPLE) ===
df_beta_any <- df_anyfed_s %>% filter(!is.na(uninsured_beta_w))
df_beta_btfp <- df_btfp_s %>% filter(!is.na(uninsured_beta_w))
df_beta_dw <- df_dw_s %>% filter(!is.na(uninsured_beta_w))
df_beta_fhlb <- df_fhlb_s %>% filter(!is.na(uninsured_beta_w))
cat("Beta coverage: AnyFed:", nrow(df_beta_any), "| BTFP:", nrow(df_beta_btfp),
"| DW:", nrow(df_beta_dw), "| FHLB:", nrow(df_beta_fhlb), "\n")
## Beta coverage: AnyFed: 3991 | BTFP: 3665 | DW: 3599 | FHLB: 3468
# Nested (without triple) and full (with triple) for comparison
mod_beta_any_n <- safe_run(df_beta_any, "any_fed", EXPL_BETA_NESTED)
mod_beta_any_f <- safe_run(df_beta_any, "any_fed", EXPL_BETA_FULL)
mod_beta_btfp_n <- safe_run(df_beta_btfp, "btfp_crisis", EXPL_BETA_NESTED)
mod_beta_btfp_f <- safe_run(df_beta_btfp, "btfp_crisis", EXPL_BETA_FULL)
mod_beta_dw_n <- safe_run(df_beta_dw, "dw_crisis", EXPL_BETA_NESTED)
mod_beta_dw_f <- safe_run(df_beta_dw, "dw_crisis", EXPL_BETA_FULL)
mod_beta_fhlb_f <- safe_run(df_beta_fhlb, "fhlb_user", EXPL_BETA_FULL)
models_beta_full <- list(
"Any (nested)" = mod_beta_any_n, "Any (full)" = mod_beta_any_f,
"BTFP (nested)" = mod_beta_btfp_n, "BTFP (full)" = mod_beta_btfp_f,
"DW (nested)" = mod_beta_dw_n, "DW (full)" = mod_beta_dw_f,
"FHLB (full)" = mod_beta_fhlb_f)
save_etable(models_beta_full, "DepositBeta_full_sample",
title_text = "Deposit Beta Channel: Full Sample — Triple Interaction",
notes_text = paste0(
"LPM. $\\beta^U$ is the bank's uninsured deposit beta (DSSW 2017/2021). ",
"Triple: MTM $\\times$ Uninsured $\\times$ $\\beta^U$ tests whether ",
"panic amplification is stronger for banks with flightier deposits. ",
"FHLB: falsification. Robust SEs. z-standardized."),
extra_lines = list(
c("Triple $\\beta^U$", "No", "Yes", "No", "Yes", "No", "Yes", "Yes")))
etable(models_beta_full[!sapply(models_beta_full, is.null)], fitstat = ~ n + r2, se.below = TRUE)
## Any (nes.. Any (full) BTFP (nes..
## Dependent Var.: any_fed any_fed btfp_crisis
##
## Constant 0.2077*** 0.2075*** 0.1435***
## (0.0061) (0.0062) (0.0057)
## MTM Loss (z) 0.0302*** 0.0292*** 0.0255***
## (0.0071) (0.0072) (0.0063)
## Uninsured Leverage (z) 0.0208** 0.0192* 0.0230**
## (0.0077) (0.0078) (0.0071)
## Uninsured $\beta$ (z) 0.0067 0.0063 0.0101
## (0.0073) (0.0073) (0.0067)
## MTM $\times$ Uninsured 0.0190** 0.0190** 0.0188***
## (0.0059) (0.0059) (0.0055)
## MTM $\times$ Unins. $\beta$ 0.0067 0.0070 0.0102
## (0.0062) (0.0062) (0.0056)
## Uninsured $\times$ Unins. $\beta$ -0.0041 -0.0089 -0.0033
## (0.0059) (0.0068) (0.0054)
## Log(Assets) 0.1072*** 0.1073*** 0.0714***
## (0.0084) (0.0084) (0.0079)
## Cash Ratio -0.0245*** -0.0243*** -0.0289***
## (0.0064) (0.0064) (0.0051)
## Loan-to-Deposit -0.0030 -0.0034 -0.0108
## (0.0073) (0.0073) (0.0062)
## Book Equity Ratio -0.0145* -0.0143* -0.0128*
## (0.0059) (0.0060) (0.0051)
## Wholesale Funding 0.0297*** 0.0302*** 0.0266***
## (0.0069) (0.0069) (0.0066)
## ROA -0.0032 -0.0030 -0.0070
## (0.0060) (0.0060) (0.0052)
## MTM $\times$ Uninsured $\times$ $\beta^U$ -0.0099
## (0.0057)
## ________________________________________ __________ __________ ___________
## S.E. type Hete.-rob. Hete.-rob. Heter.-rob.
## Observations 3,991 3,991 3,665
## R2 0.12038 0.12113 0.09725
##
## BTFP (full) DW (nest.. DW (full)
## Dependent Var.: btfp_crisis dw_crisis dw_crisis
##
## Constant 0.1433*** 0.1270*** 0.1268***
## (0.0057) (0.0055) (0.0055)
## MTM Loss (z) 0.0250*** 0.0153* 0.0145*
## (0.0063) (0.0062) (0.0062)
## Uninsured Leverage (z) 0.0223** 0.0120 0.0105
## (0.0072) (0.0068) (0.0068)
## Uninsured $\beta$ (z) 0.0099 0.0004 -0.0002
## (0.0067) (0.0064) (0.0064)
## MTM $\times$ Uninsured 0.0187*** 0.0169** 0.0168**
## (0.0055) (0.0052) (0.0052)
## MTM $\times$ Unins. $\beta$ 0.0101 0.0017 0.0018
## (0.0056) (0.0055) (0.0054)
## Uninsured $\times$ Unins. $\beta$ -0.0056 0.0019 -0.0030
## (0.0066) (0.0053) (0.0064)
## Log(Assets) 0.0715*** 0.0935*** 0.0935***
## (0.0079) (0.0079) (0.0079)
## Cash Ratio -0.0287*** -0.0036 -0.0034
## (0.0051) (0.0055) (0.0055)
## Loan-to-Deposit -0.0110 -0.0001 -0.0003
## (0.0062) (0.0063) (0.0063)
## Book Equity Ratio -0.0127* -0.0010 -0.0008
## (0.0051) (0.0048) (0.0048)
## Wholesale Funding 0.0269*** 0.0202** 0.0207**
## (0.0066) (0.0064) (0.0063)
## ROA -0.0070 -0.0032 -0.0032
## (0.0052) (0.0052) (0.0052)
## MTM $\times$ Uninsured $\times$ $\beta^U$ -0.0047 -0.0093
## (0.0053) (0.0051)
## ________________________________________ ___________ __________ __________
## S.E. type Heter.-rob. Hete.-rob. Hete.-rob.
## Observations 3,665 3,599 3,599
## R2 0.09748 0.10264 0.10362
##
## FHLB (fu..
## Dependent Var.: fhlb_user
##
## Constant 0.0903***
## (0.0051)
## MTM Loss (z) 0.0065
## (0.0057)
## Uninsured Leverage (z) 0.0082
## (0.0054)
## Uninsured $\beta$ (z) 0.0154**
## (0.0060)
## MTM $\times$ Uninsured -0.0045
## (0.0045)
## MTM $\times$ Unins. $\beta$ 0.0033
## (0.0048)
## Uninsured $\times$ Unins. $\beta$ 0.0021
## (0.0055)
## Log(Assets) 0.0245***
## (0.0066)
## Cash Ratio -0.0195***
## (0.0041)
## Loan-to-Deposit 0.0215***
## (0.0054)
## Book Equity Ratio 0.0087
## (0.0045)
## Wholesale Funding 0.0032
## (0.0052)
## ROA -0.0092*
## (0.0045)
## MTM $\times$ Uninsured $\times$ $\beta^U$ -0.0028
## (0.0047)
## ________________________________________ __________
## S.E. type Hete.-rob.
## Observations 3,468
## R2 0.04378
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Jiang MTM Solvency
Split
# ==============================================================================
# DEPOSIT BETA × JIANG MTM SOLVENCY
#
# GP prediction:
# Solvent (panic region): Triple interaction β^U should be positive —
# banks with flightier deposits have wider panic region → stronger
# amplification of MTM × Uninsured
# Insolvent (below θ̲): Triple should be zero —
# β^U doesn't matter because withdrawal is dominant strategy
#
# Finding the triple ONLY among solvent banks confirms DSSW mechanism:
# franchise runs occur in the panic region, where franchise fragility (β^U)
# determines whether the good equilibrium holds.
# ==============================================================================
cat("=== DEPOSIT BETA × JIANG MTM SOLVENCY ===\n")
## === DEPOSIT BETA × JIANG MTM SOLVENCY ===
# Solvent samples (panic region)
df_beta_any_sol <- df_beta_any %>% filter(mtm_solvent == 1)
df_beta_btfp_sol <- df_beta_btfp %>% filter(mtm_solvent == 1)
df_beta_dw_sol <- df_beta_dw %>% filter(mtm_solvent == 1)
# Insolvent samples (below θ̲)
df_beta_any_ins <- df_beta_any %>% filter(mtm_insolvent == 1)
df_beta_btfp_ins <- df_beta_btfp %>% filter(mtm_insolvent == 1)
df_beta_dw_ins <- df_beta_dw %>% filter(mtm_insolvent == 1)
cat("Jiang Solvent — Any:", nrow(df_beta_any_sol), "| BTFP:", nrow(df_beta_btfp_sol),
"| DW:", nrow(df_beta_dw_sol), "\n")
## Jiang Solvent — Any: 3210 | BTFP: 2944 | DW: 2936
cat("Jiang Insolvent — Any:", nrow(df_beta_any_ins), "| BTFP:", nrow(df_beta_btfp_ins),
"| DW:", nrow(df_beta_dw_ins), "\n")
## Jiang Insolvent — Any: 781 | BTFP: 721 | DW: 663
# Solvent: full triple specification
mod_beta_any_sol_f <- safe_run(df_beta_any_sol, "any_fed", EXPL_BETA_FULL)
mod_beta_btfp_sol_f <- safe_run(df_beta_btfp_sol, "btfp_crisis", EXPL_BETA_FULL)
mod_beta_dw_sol_f <- safe_run(df_beta_dw_sol, "dw_crisis", EXPL_BETA_FULL)
# Insolvent: full triple specification
mod_beta_any_ins_f <- safe_run(df_beta_any_ins, "any_fed", EXPL_BETA_FULL)
mod_beta_btfp_ins_f <- safe_run(df_beta_btfp_ins, "btfp_crisis", EXPL_BETA_FULL)
mod_beta_dw_ins_f <- safe_run(df_beta_dw_ins, "dw_crisis", EXPL_BETA_FULL)
models_beta_jiang <- list(
"Any (Sol)" = mod_beta_any_sol_f, "Any (Ins)" = mod_beta_any_ins_f,
"BTFP (Sol)" = mod_beta_btfp_sol_f, "BTFP (Ins)" = mod_beta_btfp_ins_f,
"DW (Sol)" = mod_beta_dw_sol_f, "DW (Ins)" = mod_beta_dw_ins_f)
save_etable(models_beta_jiang, "DepositBeta_jiang_solvency",
title_text = "Deposit Beta $\\times$ Jiang Solvency: Triple Interaction",
notes_text = paste0(
"LPM with full triple interaction MTM $\\times$ Uninsured $\\times$ $\\beta^U$. ",
"Jiang solvency: AE = Book Equity/TA $-$ MTM/TA $\\geq$ 0. ",
"GP prediction: triple is positive among solvent (panic region) banks ",
"and zero among insolvent (below $\\underline{\\theta}$) banks. ",
"Finding the triple only among solvent banks confirms the DSSW mechanism: ",
"franchise runs occur where deposit stickiness ($\\beta^U$) determines ",
"whether the good equilibrium holds. Robust SEs. z-standardized."),
extra_lines = list(
c("Solvency", "Sol", "Ins", "Sol", "Ins", "Sol", "Ins")))
etable(models_beta_jiang[!sapply(models_beta_jiang, is.null)], fitstat = ~ n + r2, se.below = TRUE)
## Any (Sol) Any (Ins) BTFP (Sol)
## Dependent Var.: any_fed any_fed btfp_crisis
##
## Constant 0.2100*** 0.1129 0.1430***
## (0.0075) (0.0621) (0.0071)
## MTM Loss (z) 0.0380*** 0.0005 0.0299***
## (0.0087) (0.0297) (0.0075)
## Uninsured Leverage (z) 0.0270** -0.0152 0.0303***
## (0.0092) (0.0351) (0.0083)
## Uninsured $\beta$ (z) 0.0136 -0.0056 0.0166*
## (0.0086) (0.0417) (0.0079)
## MTM $\times$ Uninsured 0.0218** 0.0354 0.0216***
## (0.0070) (0.0276) (0.0063)
## MTM $\times$ Unins. $\beta$ 0.0116 -0.0058 0.0156*
## (0.0074) (0.0307) (0.0067)
## Uninsured $\times$ Unins. $\beta$ -0.0147 0.0619 -0.0050
## (0.0081) (0.0368) (0.0078)
## MTM $\times$ Uninsured $\times$ $\beta^U$ -0.0140* -0.0546 -0.0033
## (0.0067) (0.0286) (0.0062)
## Log(Assets) 0.1004*** 0.1403*** 0.0644***
## (0.0091) (0.0221) (0.0084)
## Cash Ratio -0.0198** -0.0788** -0.0259***
## (0.0067) (0.0241) (0.0051)
## Loan-to-Deposit -0.0050 0.0157 -0.0127
## (0.0078) (0.0224) (0.0065)
## Book Equity Ratio -0.0108 -0.1262** -0.0071
## (0.0064) (0.0443) (0.0053)
## Wholesale Funding 0.0315*** 0.0195 0.0270***
## (0.0078) (0.0152) (0.0072)
## ROA 0.0023 -0.0311 -0.0024
## (0.0064) (0.0168) (0.0054)
## ________________________________________ __________ __________ ___________
## S.E. type Hete.-rob. Hete.-rob. Heter.-rob.
## Observations 3,210 781 2,944
## R2 0.12563 0.12354 0.09891
##
## BTFP (Ins) DW (Sol) DW (Ins)
## Dependent Var.: btfp_crisis dw_crisis dw_crisis
##
## Constant 0.0563 0.1326*** 0.0619
## (0.0584) (0.0068) (0.0499)
## MTM Loss (z) 0.0025 0.0237** -0.0002
## (0.0280) (0.0077) (0.0236)
## Uninsured Leverage (z) -0.0185 0.0159 -0.0002
## (0.0331) (0.0082) (0.0280)
## Uninsured $\beta$ (z) 0.0093 0.0056 -0.0181
## (0.0408) (0.0077) (0.0300)
## MTM $\times$ Uninsured 0.0343 0.0195** 0.0217
## (0.0259) (0.0062) (0.0215)
## MTM $\times$ Unins. $\beta$ -0.0090 0.0047 -0.0024
## (0.0297) (0.0067) (0.0220)
## Uninsured $\times$ Unins. $\beta$ 0.0275 -0.0091 0.0659*
## (0.0343) (0.0078) (0.0277)
## MTM $\times$ Uninsured $\times$ $\beta^U$ -0.0404 -0.0136* -0.0500*
## (0.0274) (0.0063) (0.0214)
## Log(Assets) 0.1065*** 0.0892*** 0.1119***
## (0.0222) (0.0085) (0.0207)
## Cash Ratio -0.0737*** -0.0007 -0.0389*
## (0.0216) (0.0058) (0.0190)
## Loan-to-Deposit 0.0127 -0.0031 0.0111
## (0.0210) (0.0067) (0.0179)
## Book Equity Ratio -0.1239** -0.0020 -0.0621
## (0.0421) (0.0053) (0.0353)
## Wholesale Funding 0.0190 0.0223** 0.0118
## (0.0149) (0.0071) (0.0144)
## ROA -0.0302 0.0013 -0.0271
## (0.0154) (0.0055) (0.0140)
## ________________________________________ ___________ __________ __________
## S.E. type Heter.-rob. Hete.-rob. Hete.-rob.
## Observations 721 2,936 663
## R2 0.10060 0.10842 0.11443
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
IDCR-100% Solvency
Split
# ==============================================================================
# DEPOSIT BETA × IDCR-100% SOLVENCY
# Same logic, stricter solvency definition
# ==============================================================================
cat("=== DEPOSIT BETA × IDCR-100% SOLVENCY ===\n")
## === DEPOSIT BETA × IDCR-100% SOLVENCY ===
df_beta_any_idcr_sol <- df_beta_any %>% filter(solvent_idcr_100 == 1)
df_beta_any_idcr_ins <- df_beta_any %>% filter(insolvent_idcr_100 == 1)
df_beta_btfp_idcr_sol <- df_beta_btfp %>% filter(solvent_idcr_100 == 1)
df_beta_btfp_idcr_ins <- df_beta_btfp %>% filter(insolvent_idcr_100 == 1)
df_beta_dw_idcr_sol <- df_beta_dw %>% filter(solvent_idcr_100 == 1)
df_beta_dw_idcr_ins <- df_beta_dw %>% filter(insolvent_idcr_100 == 1)
cat("IDCR Solvent — Any:", nrow(df_beta_any_idcr_sol), "| BTFP:", nrow(df_beta_btfp_idcr_sol),
"| DW:", nrow(df_beta_dw_idcr_sol), "\n")
## IDCR Solvent — Any: 3648 | BTFP: 3343 | DW: 3302
cat("IDCR Insolvent — Any:", nrow(df_beta_any_idcr_ins), "| BTFP:", nrow(df_beta_btfp_idcr_ins),
"| DW:", nrow(df_beta_dw_idcr_ins), "\n")
## IDCR Insolvent — Any: 343 | BTFP: 322 | DW: 297
mod_beta_any_idcr_sol_f <- safe_run(df_beta_any_idcr_sol, "any_fed", EXPL_BETA_FULL)
mod_beta_any_idcr_ins_f <- safe_run(df_beta_any_idcr_ins, "any_fed", EXPL_BETA_FULL)
mod_beta_btfp_idcr_sol_f <- safe_run(df_beta_btfp_idcr_sol, "btfp_crisis", EXPL_BETA_FULL)
mod_beta_btfp_idcr_ins_f <- safe_run(df_beta_btfp_idcr_ins, "btfp_crisis", EXPL_BETA_FULL)
mod_beta_dw_idcr_sol_f <- safe_run(df_beta_dw_idcr_sol, "dw_crisis", EXPL_BETA_FULL)
mod_beta_dw_idcr_ins_f <- safe_run(df_beta_dw_idcr_ins, "dw_crisis", EXPL_BETA_FULL)
models_beta_idcr <- list(
"Any (Sol)" = mod_beta_any_idcr_sol_f, "Any (Ins)" = mod_beta_any_idcr_ins_f,
"BTFP (Sol)" = mod_beta_btfp_idcr_sol_f, "BTFP (Ins)" = mod_beta_btfp_idcr_ins_f,
"DW (Sol)" = mod_beta_dw_idcr_sol_f, "DW (Ins)" = mod_beta_dw_idcr_ins_f)
save_etable(models_beta_idcr, "DepositBeta_idcr100_solvency",
title_text = "Deposit Beta $\\times$ IDCR-100\\% Solvency: Triple Interaction",
notes_text = paste0(
"LPM with full triple interaction. IDCR-100\\% solvency: ",
"(MV Assets $-$ Uninsured $-$ Insured) / Insured $\\geq$ 0. ",
"Same GP prediction as Jiang split. Robust SEs. z-standardized."),
extra_lines = list(
c("Solvency", "Sol", "Ins", "Sol", "Ins", "Sol", "Ins")))
etable(models_beta_idcr[!sapply(models_beta_idcr, is.null)], fitstat = ~ n + r2, se.below = TRUE)
## Any (Sol) Any (Ins) BTFP (Sol)
## Dependent Var.: any_fed any_fed btfp_crisis
##
## Constant 0.2098*** 0.1451 0.1442***
## (0.0066) (0.0827) (0.0062)
## MTM Loss (z) 0.0328*** 0.0065 0.0265***
## (0.0078) (0.0398) (0.0069)
## Uninsured Leverage (z) 0.0231** -0.0007 0.0244**
## (0.0084) (0.0459) (0.0078)
## Uninsured $\beta$ (z) 0.0086 -3.11e-5 0.0117
## (0.0078) (0.0704) (0.0072)
## MTM $\times$ Uninsured 0.0208** 0.0144 0.0197**
## (0.0064) (0.0345) (0.0060)
## MTM $\times$ Unins. $\beta$ 0.0080 -0.0088 0.0118
## (0.0067) (0.0509) (0.0061)
## Uninsured $\times$ Unins. $\beta$ -0.0092 0.0354 -0.0052
## (0.0073) (0.0486) (0.0071)
## MTM $\times$ Uninsured $\times$ $\beta^U$ -0.0097 -0.0494 -0.0037
## (0.0061) (0.0399) (0.0057)
## Log(Assets) 0.1034*** 0.1478*** 0.0707***
## (0.0087) (0.0331) (0.0082)
## Cash Ratio -0.0228*** -0.0614* -0.0273***
## (0.0066) (0.0277) (0.0052)
## Loan-to-Deposit -0.0045 0.0016 -0.0117
## (0.0075) (0.0358) (0.0064)
## Book Equity Ratio -0.0154* -0.0456 -0.0124*
## (0.0063) (0.0570) (0.0053)
## Wholesale Funding 0.0305*** -0.0801 0.0273***
## (0.0070) (0.0709) (0.0066)
## ROA -0.0013 -0.0240 -0.0062
## (0.0063) (0.0233) (0.0053)
## ________________________________________ __________ __________ ___________
## S.E. type Hete.-rob. Hete.-rob. Heter.-rob.
## Observations 3,648 343 3,343
## R2 0.12318 0.12401 0.10151
##
## BTFP (Ins) DW (Sol) DW (Ins)
## Dependent Var.: btfp_crisis dw_crisis dw_crisis
##
## Constant 0.0740 0.1302*** 0.0847
## (0.0781) (0.0060) (0.0641)
## MTM Loss (z) 0.0141 0.0186** 0.0006
## (0.0368) (0.0069) (0.0287)
## Uninsured Leverage (z) 0.0098 0.0144 0.0077
## (0.0409) (0.0075) (0.0417)
## Uninsured $\beta$ (z) 0.0112 0.0035 -0.0121
## (0.0678) (0.0070) (0.0405)
## MTM $\times$ Uninsured 0.0171 0.0193*** -0.0012
## (0.0311) (0.0058) (0.0284)
## MTM $\times$ Unins. $\beta$ -0.0062 0.0040 -0.0211
## (0.0478) (0.0060) (0.0317)
## Uninsured $\times$ Unins. $\beta$ 0.0432 -0.0023 0.0227
## (0.0485) (0.0070) (0.0363)
## MTM $\times$ Uninsured $\times$ $\beta^U$ -0.0525 -0.0084 -0.0361
## (0.0376) (0.0057) (0.0304)
## Log(Assets) 0.0779* 0.0895*** 0.1335***
## (0.0320) (0.0082) (0.0311)
## Cash Ratio -0.0570* -0.0028 -0.0245
## (0.0259) (0.0057) (0.0183)
## Loan-to-Deposit 0.0040 -0.0023 -0.0022
## (0.0336) (0.0065) (0.0259)
## Book Equity Ratio -0.0657 -0.0028 -0.0019
## (0.0530) (0.0052) (0.0415)
## Wholesale Funding -0.0464 0.0203** -0.0527
## (0.0707) (0.0064) (0.0568)
## ROA -0.0171 -0.0016 -0.0198
## (0.0213) (0.0053) (0.0201)
## ________________________________________ ___________ __________ __________
## S.E. type Heter.-rob. Hete.-rob. Hete.-rob.
## Observations 322 3,302 297
## R2 0.07423 0.10144 0.16869
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Uninsured Beta
Diagnostics
cat("--- Uninsured Beta Distribution ---\n")
## --- Uninsured Beta Distribution ---
print(summary(df_2022q4$uninsured_beta_raw))
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 0.2640 0.3337 0.3895 0.4111 0.4683 0.6692 66
cat("\n--- Mean β^U by Jiang Solvency ---\n")
##
## --- Mean β^U by Jiang Solvency ---
df_2022q4 %>%
filter(!is.na(uninsured_beta_raw)) %>%
group_by(Jiang_Solvent = mtm_solvent) %>%
summarise(N = n(), Mean_BetaU = round(mean(uninsured_beta_raw, na.rm=T), 4),
SD = round(sd(uninsured_beta_raw, na.rm=T), 4), .groups = "drop") %>%
print()
## # A tibble: 2 × 4
## Jiang_Solvent N Mean_BetaU SD
## <int> <int> <dbl> <dbl>
## 1 0 813 0.387 0.0883
## 2 1 3413 0.417 0.108
cat("\n--- Correlation Matrix ---\n")
##
## --- Correlation Matrix ---
cor_df <- df_2022q4 %>%
select(uninsured_beta_raw, beta_overall, uninsured_lev_raw, mtm_total_raw, dfv_raw) %>%
drop_na()
if (nrow(cor_df) > 10) print(round(cor(cor_df), 3))
## uninsured_beta_raw beta_overall uninsured_lev_raw
## uninsured_beta_raw 1.000 0.873 -0.036
## beta_overall 0.873 1.000 0.298
## uninsured_lev_raw -0.036 0.298 1.000
## mtm_total_raw -0.114 -0.190 -0.155
## dfv_raw -0.793 -0.880 -0.171
## mtm_total_raw dfv_raw
## uninsured_beta_raw -0.114 -0.793
## beta_overall -0.190 -0.880
## uninsured_lev_raw -0.155 -0.171
## mtm_total_raw 1.000 0.188
## dfv_raw 0.188 1.000
FACILITY
COMPLEMENTARITY (Model 4)
4a. Multinomial
Facility Choice
cat("=== FACILITY COMPLEMENTARITY ===\n\n")
## === FACILITY COMPLEMENTARITY ===
df_emerg <- df_crisis %>%
filter(any_fed == 1) %>%
mutate(facility_choice = factor(case_when(
both_fed == 1 ~ "Both",
btfp_crisis == 1 ~ "BTFP_Only",
dw_crisis == 1 ~ "DW_Only"),
levels = c("DW_Only", "BTFP_Only", "Both")))
cat("Facility choice (Crisis emergency borrowers):\n")
## Facility choice (Crisis emergency borrowers):
print(table(df_emerg$facility_choice))
##
## DW_Only BTFP_Only Both
## 327 395 106
ff_mnom <- as.formula(paste0("facility_choice ~ mtm_total + uninsured_lev + mtm_x_uninsured + par_benefit + collateral_capacity + ", CONTROLS))
mod4a <- tryCatch(multinom(ff_mnom, data = df_emerg, trace = FALSE),
error = function(e) { message("Multinom failed: ", e$message); NULL })
if (!is.null(mod4a)) {
cat("\nAIC:", AIC(mod4a), "\n\n")
tidy_m <- tidy(mod4a, conf.int = TRUE) %>%
mutate(stars = case_when(p.value < 0.01 ~ "***", p.value < 0.05 ~ "**",
p.value < 0.10 ~ "*", TRUE ~ ""),
disp = sprintf("%.3f%s (%.3f)", estimate, stars, std.error))
cat("--- Key Coefficients (Base = DW Only) ---\n")
tidy_m %>%
filter(term %in% c("mtm_total", "uninsured_lev", "mtm_x_uninsured",
"par_benefit", "collateral_capacity")) %>%
select(Outcome = y.level, Variable = term, Estimate = disp) %>%
print(n = 20)
}
##
## AIC: 1567.913
##
## --- Key Coefficients (Base = DW Only) ---
## # A tibble: 10 × 3
## Outcome Variable Estimate
## <chr> <chr> <chr>
## 1 BTFP_Only mtm_total 0.120 (0.103)
## 2 BTFP_Only uninsured_lev 0.120 (0.093)
## 3 BTFP_Only mtm_x_uninsured -0.103 (0.086)
## 4 BTFP_Only par_benefit -0.342*** (0.131)
## 5 BTFP_Only collateral_capacity 0.240** (0.103)
## 6 Both mtm_total -0.196 (0.172)
## 7 Both uninsured_lev 0.286** (0.134)
## 8 Both mtm_x_uninsured 0.113 (0.129)
## 9 Both par_benefit 0.284 (0.380)
## 10 Both collateral_capacity 0.106 (0.149)
4b. Complementarity:
DW Borrowers → Also BTFP?
df_dw_comp <- df_crisis %>%
filter(dw_crisis == 1) %>%
mutate(also_btfp = as.integer(btfp_crisis == 1))
cat("DW borrowers:", nrow(df_dw_comp), "| Also BTFP:", sum(df_dw_comp$also_btfp), "\n")
## DW borrowers: 433 | Also BTFP: 106
mod4b_base <- safe_run(df_dw_comp, "also_btfp", EXPL_BASE, min_n = 20)
mod4b_fac <- safe_run(df_dw_comp, "also_btfp", EXPL_FACILITY, min_n = 20)
models_4b <- list("Base" = mod4b_base, "Facility" = mod4b_fac)
save_etable(models_4b, "M4b_complementarity",
title_text = "Complementarity: Among DW Borrowers, What Drove Also-BTFP?",
notes_text = "LPM. Sample: DW borrowers (Crisis). DV=1 if also used BTFP. Robust SEs.")
etable(models_4b[!sapply(models_4b, is.null)], fitstat = ~ n + r2, se.below = TRUE)
## Base Facility
## Dependent Var.: also_btfp also_btfp
##
## Constant 0.1686*** 0.1668***
## (0.0235) (0.0231)
## MTM Loss (z) -0.0219 -0.0186
## (0.0242) (0.0246)
## Uninsured Leverage (z) 0.0490* 0.0499*
## (0.0241) (0.0243)
## MTM $\times$ Uninsured 0.0200 0.0200
## (0.0212) (0.0214)
## Log(Assets) 0.0590* 0.0563*
## (0.0247) (0.0253)
## Cash Ratio -0.0472 -0.0422
## (0.0263) (0.0266)
## Loan-to-Deposit -0.0270 -0.0103
## (0.0285) (0.0338)
## Book Equity Ratio -0.0502 -0.0517
## (0.0261) (0.0264)
## Wholesale Funding 0.0244 0.0217
## (0.0176) (0.0177)
## ROA -0.0258 -0.0215
## (0.0236) (0.0240)
## Par Benefit (z) 0.0132
## (0.0154)
## OMO Collateral (z) 0.0261
## (0.0283)
## ______________________ __________ __________
## S.E. type Hete.-rob. Hete.-rob.
## Observations 433 433
## R2 0.08055 0.08293
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
4c. Substitution:
BTFP-Only vs. DW-Only
df_subst <- df_crisis %>%
filter(user_group %in% c("BTFP_Only", "DW_Only")) %>%
mutate(chose_btfp = as.integer(user_group == "BTFP_Only"))
cat("BTFP-only:", sum(df_subst$chose_btfp), "| DW-only:", sum(df_subst$chose_btfp==0), "\n")
## BTFP-only: 395 | DW-only: 327
mod4c_base <- safe_run(df_subst, "chose_btfp", EXPL_BASE, min_n = 20)
mod4c_fac <- safe_run(df_subst, "chose_btfp", EXPL_FACILITY, min_n = 20)
mod4c_stig <- safe_run(df_subst, "chose_btfp",
paste0(EXPL_FACILITY, " + par_x_uninsured"), min_n = 20)
models_4c <- list("Base" = mod4c_base, "Facility" = mod4c_fac, "Stigma" = mod4c_stig)
save_etable(models_4c, "M4c_substitution",
title_text = "Substitution: BTFP-Only vs. DW-Only",
notes_text = "LPM. Sample: single-facility borrowers (Crisis). DV=1 if BTFP, 0 if DW. Robust SEs.")
etable(models_4c[!sapply(models_4c, is.null)], fitstat = ~ n + r2, se.below = TRUE)
## Base Facility Stigma
## Dependent Var.: chose_btfp chose_btfp chose_btfp
##
## Constant 0.5364*** 0.5378*** 0.5341***
## (0.0243) (0.0242) (0.0245)
## MTM Loss (z) 0.0092 0.0277 0.0239
## (0.0240) (0.0243) (0.0245)
## Uninsured Leverage (z) 0.0274 0.0270 0.0257
## (0.0231) (0.0223) (0.0225)
## MTM $\times$ Uninsured -0.0193 -0.0197 -0.0135
## (0.0205) (0.0199) (0.0206)
## Log(Assets) -0.0808*** -0.0791*** -0.0739**
## (0.0233) (0.0229) (0.0236)
## Cash Ratio -0.1242*** -0.1201*** -0.1228***
## (0.0300) (0.0285) (0.0287)
## Loan-to-Deposit -0.0466 -0.0115 -0.0174
## (0.0276) (0.0301) (0.0304)
## Book Equity Ratio -0.0380 -0.0431 -0.0408
## (0.0284) (0.0287) (0.0288)
## Wholesale Funding 0.0058 0.0027 0.0028
## (0.0156) (0.0155) (0.0155)
## ROA -0.0080 -0.0037 -0.0005
## (0.0232) (0.0233) (0.0234)
## Par Benefit (z) -0.0675** -0.0505
## (0.0245) (0.0314)
## OMO Collateral (z) 0.0550* 0.0544*
## (0.0227) (0.0228)
## Par $\times$ Uninsured -0.0294
## (0.0225)
## ______________________ __________ __________ __________
## S.E. type Hete.-rob. Hete.-rob. Hete.-rob.
## Observations 722 722 722
## R2 0.06253 0.07826 0.08051
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
4d. Descriptive
Evidence
desc_vars <- c("mtm_total_raw", "mtm_btfp_raw", "uninsured_lev_raw",
"book_equity_ratio_raw", "adjusted_equity_raw", "dfv_raw",
"ln_assets_raw", "cash_ratio_raw", "par_benefit_raw", "collateral_capacity_raw")
cat("--- Mean Characteristics by Facility Choice ---\n")
## --- Mean Characteristics by Facility Choice ---
df_emerg %>%
group_by(facility_choice) %>%
summarise(N = n(), across(all_of(desc_vars), ~round(mean(., na.rm=T), 3)), .groups = "drop") %>%
print(width = Inf)
## # A tibble: 3 × 12
## facility_choice N mtm_total_raw mtm_btfp_raw uninsured_lev_raw
## <fct> <int> <dbl> <dbl> <dbl>
## 1 DW_Only 327 5.72 0.728 26.8
## 2 BTFP_Only 395 6.18 0.875 26.2
## 3 Both 106 5.83 0.924 32.0
## book_equity_ratio_raw adjusted_equity_raw dfv_raw ln_assets_raw cash_ratio_raw
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 9.10 3.38 71.9 13.8 6.15
## 2 8.19 2.01 73.0 13.5 4.69
## 3 8.14 2.31 66.3 14.6 4.62
## par_benefit_raw collateral_capacity_raw
## <dbl> <dbl>
## 1 0.962 0.009
## 2 0.933 0.012
## 3 0.978 0.011
# T-tests: BTFP-only vs DW-only
cat("\n--- T-Tests: BTFP-Only vs DW-Only ---\n")
##
## --- T-Tests: BTFP-Only vs DW-Only ---
btfp_only <- df_emerg %>% filter(facility_choice == "BTFP_Only")
dw_only <- df_emerg %>% filter(facility_choice == "DW_Only")
for (v in desc_vars) {
tt <- tryCatch(t.test(btfp_only[[v]], dw_only[[v]]), error = function(e) NULL)
if (!is.null(tt)) {
stars <- case_when(tt$p.value < 0.01 ~ "***", tt$p.value < 0.05 ~ "**",
tt$p.value < 0.10 ~ "*", TRUE ~ "")
cat(sprintf(" %-28s BTFP=%.3f DW=%.3f diff=%.3f t=%.2f %s\n", v,
mean(btfp_only[[v]], na.rm=T), mean(dw_only[[v]], na.rm=T),
mean(btfp_only[[v]], na.rm=T) - mean(dw_only[[v]], na.rm=T),
tt$statistic, stars))
}
}
## mtm_total_raw BTFP=6.175 DW=5.718 diff=0.458 t=3.07 ***
## mtm_btfp_raw BTFP=0.875 DW=0.728 diff=0.147 t=2.15 **
## uninsured_lev_raw BTFP=26.154 DW=26.774 diff=-0.621 t=-0.71
## book_equity_ratio_raw BTFP=8.188 DW=9.097 diff=-0.909 t=-3.82 ***
## adjusted_equity_raw BTFP=2.012 DW=3.379 diff=-1.367 t=-4.36 ***
## dfv_raw BTFP=73.045 DW=71.907 diff=1.138 t=1.01
## ln_assets_raw BTFP=13.486 DW=13.833 diff=-0.347 t=-3.22 ***
## cash_ratio_raw BTFP=4.686 DW=6.151 diff=-1.465 t=-3.43 ***
## par_benefit_raw BTFP=0.933 DW=0.962 diff=-0.029 t=-2.15 **
## collateral_capacity_raw BTFP=0.012 DW=0.009 diff=0.002 t=3.15 ***
COMPREHENSIVE SOLVENCY
COMPARISON
# ==============================================================================
# Three-way solvency cross-tab and visualization
# ==============================================================================
cat("=== SOLVENCY COMPARISON ACROSS FRAMEWORKS ===\n\n")
## === SOLVENCY COMPARISON ACROSS FRAMEWORKS ===
# Full cross-tab
solv_tab <- df_2022q4 %>%
filter(!is.na(dssw_solvent)) %>%
count(
Jiang = ifelse(mtm_insolvent == 1, "Jiang-Ins", "Jiang-Sol"),
IDCR = ifelse(insolvent_idcr_100 == 1, "IDCR-Ins", "IDCR-Sol"),
DSSW = ifelse(dssw_insolvent == 1, "DSSW-Ins", "DSSW-Sol")
) %>%
arrange(Jiang, IDCR, DSSW)
cat("--- Three-Way Cross-Tab ---\n")
## --- Three-Way Cross-Tab ---
print(solv_tab)
## # A tibble: 6 × 4
## Jiang IDCR DSSW n
## <chr> <chr> <chr> <int>
## 1 Jiang-Ins IDCR-Ins DSSW-Sol 355
## 2 Jiang-Ins IDCR-Sol DSSW-Sol 470
## 3 Jiang-Sol IDCR-Ins DSSW-Sol 9
## 4 Jiang-Sol IDCR-Sol DSSW-Sol 3417
## 5 Jiang-Sol <NA> DSSW-Sol 31
## 6 <NA> <NA> DSSW-Sol 10
# Summary
cat("\n--- Classification Summary ---\n")
##
## --- Classification Summary ---
cat(sprintf(" Jiang-insolvent: %d (%.1f%%)\n",
sum(df_2022q4$mtm_insolvent, na.rm=T),
100 * mean(df_2022q4$mtm_insolvent, na.rm=T)))
## Jiang-insolvent: 825 (19.3%)
cat(sprintf(" IDCR-insolvent: %d (%.1f%%)\n",
sum(df_2022q4$insolvent_idcr_100, na.rm=T),
100 * mean(df_2022q4$insolvent_idcr_100, na.rm=T)))
## IDCR-insolvent: 364 (8.6%)
cat(sprintf(" DSSW-insolvent: %d (%.1f%%)\n",
sum(df_2022q4$dssw_insolvent, na.rm=T),
100 * mean(df_2022q4$dssw_insolvent, na.rm=T)))
## DSSW-insolvent: 0 (0.0%)
# Euler-style summary of how frameworks relate
cat("\n--- Theoretical Interpretation ---\n")
##
## --- Theoretical Interpretation ---
cat(" Jiang: Book Equity > MTM losses? (asset-side solvency)\n")
## Jiang: Book Equity > MTM losses? (asset-side solvency)
cat(" IDCR: Can bank pay ALL depositors after 100% uninsured run? (run-scenario)\n")
## IDCR: Can bank pay ALL depositors after 100% uninsured run? (run-scenario)
cat(" DSSW: Include franchise value (no-run equilibrium solvency)\n")
## DSSW: Include franchise value (no-run equilibrium solvency)
cat(" → IDCR is stricter than Jiang (accounts for deposit payoff priority)\n")
## → IDCR is stricter than Jiang (accounts for deposit payoff priority)
cat(" → DSSW is most lenient (adds intangible franchise value)\n")
## → DSSW is most lenient (adds intangible franchise value)
cat(" → The gap between DSSW-solvent and Jiang/IDCR-insolvent IS the panic region\n")
## → The gap between DSSW-solvent and Jiang/IDCR-insolvent IS the panic region
# Visualization: Adjusted Equity distributions by framework
df_kde_solv <- df_2022q4 %>%
filter(!is.na(adjusted_equity_dssw_raw)) %>%
select(adjusted_equity_raw, adjusted_equity_dssw_raw, idcr_100) %>%
pivot_longer(cols = c(adjusted_equity_raw, adjusted_equity_dssw_raw),
names_to = "Framework", values_to = "Value") %>%
mutate(Framework = case_when(
Framework == "adjusted_equity_raw" ~ "Jiang (Book Eq. − MTM)",
Framework == "adjusted_equity_dssw_raw" ~ "DSSW (+ Franchise Value)"))
p_solv <- ggplot(df_kde_solv, aes(x = Value, fill = Framework, color = Framework)) +
geom_density(alpha = 0.4, linewidth = 0.8) +
geom_vline(xintercept = 0, linetype = "dotted", color = "red", linewidth = 1) +
scale_fill_manual(values = c("Jiang (Book Eq. − MTM)" = "#D62828",
"DSSW (+ Franchise Value)" = "#2A9D8F")) +
scale_color_manual(values = c("Jiang (Book Eq. − MTM)" = "#D62828",
"DSSW (+ Franchise Value)" = "#2A9D8F")) +
labs(title = "Solvency Distributions: Jiang vs. DSSW",
subtitle = "DSSW shifts the entire distribution rightward by ~68pp. Zero banks are DSSW-insolvent.",
x = "Adjusted Equity (% of Assets)", y = "Density") +
theme_paper +
annotate("text", x = -3, y = 0.08, label = "Jiang\ninsolvent\nzone",
color = "#D62828", size = 3, fontface = "italic")
p_solv

save_figure(p_solv, "Fig_Solvency_Jiang_vs_DSSW_KDE", width = 10, height = 6)
SUMMARY
cat("======================================================================\n")
## ======================================================================
cat(" COMPREHENSIVE ANALYSIS — COMPLETE\n")
## COMPREHENSIVE ANALYSIS — COMPLETE
cat("======================================================================\n\n")
## ======================================================================
cat("MAIN RESULTS (Crisis, Extensive Margin):\n")
## MAIN RESULTS (Crisis, Extensive Margin):
cat(" A. Full Sample: 4 regressions (AnyFed, BTFP, DW, FHLB)\n")
## A. Full Sample: 4 regressions (AnyFed, BTFP, DW, FHLB)
cat(" B. Jiang MTM Sol/Ins: 8 regressions (4 × 2)\n")
## B. Jiang MTM Sol/Ins: 8 regressions (4 × 2)
cat(" C. IDCR-100% Sol/Ins: 8 regressions (4 × 2)\n")
## C. IDCR-100% Sol/Ins: 8 regressions (4 × 2)
cat(" D. DSSW Diagnostic: 0 insolvent → supports coordination story\n\n")
## D. DSSW Diagnostic: 0 insolvent → supports coordination story
cat("TEMPORAL ROBUSTNESS:\n")
## TEMPORAL ROBUSTNESS:
cat(" Full sample: 4 regressions (BTFP/DW × Crisis/Arb)\n")
## Full sample: 4 regressions (BTFP/DW × Crisis/Arb)
cat(" Jiang MTM Sol/Ins: 8 regressions (BTFP/DW × Crisis/Arb × Sol/Ins)\n")
## Jiang MTM Sol/Ins: 8 regressions (BTFP/DW × Crisis/Arb × Sol/Ins)
cat(" IDCR-100% Sol/Ins: 8 regressions (BTFP/DW × Crisis/Arb × Sol/Ins)\n")
## IDCR-100% Sol/Ins: 8 regressions (BTFP/DW × Crisis/Arb × Sol/Ins)
cat(" → Solvency × Period DID: interaction only among solvent banks in crisis\n\n")
## → Solvency × Period DID: interaction only among solvent banks in crisis
cat("DEPOSIT BETA CHANNEL:\n")
## DEPOSIT BETA CHANNEL:
cat(" Full sample: 7 regressions (nested/full × 3 facilities + FHLB)\n")
## Full sample: 7 regressions (nested/full × 3 facilities + FHLB)
cat(" Jiang MTM Sol/Ins: 6 regressions (3 facilities × Sol/Ins)\n")
## Jiang MTM Sol/Ins: 6 regressions (3 facilities × Sol/Ins)
cat(" IDCR-100% Sol/Ins: 6 regressions (3 facilities × Sol/Ins)\n")
## IDCR-100% Sol/Ins: 6 regressions (3 facilities × Sol/Ins)
cat(" → β^U triple should work only among solvent (panic-region) banks\n\n")
## → β^U triple should work only among solvent (panic-region) banks
cat("FACILITY COMPLEMENTARITY:\n")
## FACILITY COMPLEMENTARITY:
cat(" 4a. Multinomial: facility choice {BTFP, DW, Both}\n")
## 4a. Multinomial: facility choice {BTFP, DW, Both}
cat(" 4b. Complementarity: DW borrowers → also BTFP?\n")
## 4b. Complementarity: DW borrowers → also BTFP?
cat(" 4c. Substitution: BTFP-only vs DW-only\n")
## 4c. Substitution: BTFP-only vs DW-only
cat(" 4d. Descriptive t-tests\n\n")
## 4d. Descriptive t-tests
cat("KEY INSIGHT FROM DSSW:\n")
## KEY INSIGHT FROM DSSW:
cat(" Every bank is DSSW-solvent. DFV ≈ 68%/TA >> MTM ≈ 5.5%/TA.\n")
## Every bank is DSSW-solvent. DFV ≈ 68%/TA >> MTM ≈ 5.5%/TA.
cat(" But franchise exists ONLY if depositors stay.\n")
## But franchise exists ONLY if depositors stay.
cat(" 828 banks borrowed despite being DSSW-solvent\n")
## 828 banks borrowed despite being DSSW-solvent
cat(" → Coordination failure, not insolvency, drove borrowing.\n")
## → Coordination failure, not insolvency, drove borrowing.
cat(" → The triple interaction (β^U) directly tests the DSSW mechanism.\n\n")
## → The triple interaction (β^U) directly tests the DSSW mechanism.
cat("Tables:", TABLE_PATH, "\n")
## Tables: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/Comprehensive_Analysis/tables
cat("Figures:", FIG_PATH, "\n")
## Figures: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/Comprehensive_Analysis/figures
cat("======================================================================\n")
## ======================================================================