Theory-to-Code Map
Every equation and prediction in theory_standalone.tex
is mapped to a specific code section below. This document is organized
to allow a reader to verify that every testable claim in the theory is
executed.
| Eq. 1 |
Balance sheet: \(C+S+L = D^I
+ D^U + E\) |
§3 Variable Construction |
| Eq. 2 |
\(\lambda = (1-\theta_S)S +
(1-\theta_L)L\); decomposition \(\ell =
\ell_S + \ell_L\) |
§3 Variable Construction |
| Eq. 3 |
\(f_i = \max\{[(1-\beta^U_i)y
- c^U_i]/(y+\delta) \cdot \mu_i,\; 0\}\); DSSW Eq. 20 decaying
perpetuity (\(\delta=10\%\)) |
§3 Calibration |
| Eq. 4 |
\(E^{MV} = E - \lambda +
F\) |
§3 Variable Construction |
| Eq. 5 |
Panic width \(=
(D^U/D)F\) |
§4 Sanity Checks |
| Eq. 6 |
Run threshold \(\lambda^* =
\underline\lambda + \alpha(D^U/D)F\) |
Not directly testable (\(\alpha\) unobserved) |
| Eq. 7 |
\(\partial^2\Pr(\text{run})/\partial\lambda\partial
D^U > 0\) |
§8 Prediction 2 (Goldstein) |
| Eq. 8 |
Liquidity gap \(g = w -
(C+\theta_S S)\) |
§7 P1 (composition of borrowers) |
| Eq. 9 |
BTFP margin \(\Delta_{\text{BTFP}} =
(1-\theta_S)S_{\text{el}}\) |
§9 Prediction 3 (par-value channel) |
| Eq. 10 |
Partial threshold \(\underline\ell(\phi)\) |
§12 Robustness: \(\phi\) sensitivity |
| Eq. 11 |
LPM: \(\Pr(\text{Borrow}_i) =
\alpha + c_1\ell + c_2 f + c_3(\ell\times f) + \gamma'X +
\varepsilon\) |
§8 Prediction 2 |
| P1 |
Borrowers concentrated in panic zone, not fund.
insolvency |
§7 |
| P1 |
“Coordination: MTM losses and uninsured leverage
jointly elevated” |
§7.3 |
| P2 |
\(c_3>0\) in panic
zone; \(c_3\approx 0\) in fund.
insolvency |
§8.1–8.3 |
| P2 |
Goldstein test: formal \(c_3^{PZ} \neq c_3^{FI}\) |
§8.4 |
| P2 |
Decomposition: \(\ell\times\mu\) vs \(\ell\times(1-\beta^U)\) |
§8.5 |
| P2 |
“Uninsured leverage channel should dominate” |
§8.5 |
| P3 |
BTFP: \(\ell_S\)
positive; DW: \(\ell_S \approx 0\) |
§9.1 |
| P3 |
Cross-equation: \(\ell_S^{BTFP} > \ell_S^{DW}\) |
§9.2 |
| P3 |
Observable vs unobservable signal: \(\ell_S\times f\) vs \(\ell_L\times f\) |
§9.3 |
| P3 |
Facility choice among borrowers |
§9.4 |
| Falsification |
FHLB: \(\ell\times f \approx
0\) |
§10.1 |
| Falsification |
Arb period: \(c_3 \approx
0\) |
§10.2 |
| Robustness |
Logit |
§11.1 |
| Robustness |
\(\beta^U\)
clipping |
§11.2 |
| Robustness |
\(T\) sensitivity
(\(T \in \{5,7,10,15,20,\infty\}\);
perpetuity as upper bound) |
§11.4 |
| Robustness |
\(\phi\) sensitivity
(eq. 10) |
§11.3 |
SETUP
Packages
rm(list = ls())
library(data.table); library(dplyr); library(tidyr); library(stringr)
library(lubridate); library(purrr); library(tibble)
library(fixest); library(modelsummary)
library(knitr); library(kableExtra)
library(ggplot2); library(scales); library(patchwork)
library(readr); library(readxl)
cat("All packages loaded.\n")
## All packages loaded.
Helpers
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)
s <- sd(x, na.rm = TRUE)
if (is.na(s) || s == 0) return(rep(0, length(x)))
(x - mean(x, na.rm = TRUE)) / s
}
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 ~ "")
}
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")
}
# --- Dependent variable summary rows for modelsummary ---
dv_summary_rows <- function(model_list) {
stats <- lapply(model_list, function(m) {
y <- fitted(m) + resid(m)
c(N_DV1 = sum(round(y) == 1, na.rm = TRUE),
Mean_DV = round(mean(y, na.rm = TRUE), 4))
})
df <- data.frame(term = c("N (DV = 1)", "Mean (DV)"), stringsAsFactors = FALSE)
for (nm in names(stats)) df[[nm]] <- as.character(stats[[nm]])
df
}
# --- Desc stats helpers ---
build_desc_table <- function(df_a, df_b, label_a, label_b, vars, labels) {
avail <- vars[vars %in% names(df_a) & vars %in% names(df_b)]
avail_labels <- labels[vars %in% names(df_a) & vars %in% names(df_b)]
results <- data.frame(Variable = avail_labels, stringsAsFactors = FALSE)
results[[paste0("N_", label_a)]] <- sapply(avail, function(v) sum(!is.na(df_a[[v]])))
results[[paste0("Mean_", label_a)]] <- sapply(avail, function(v) round(mean(df_a[[v]], na.rm=TRUE), 3))
results[[paste0("SD_", label_a)]] <- sapply(avail, function(v) round(sd(df_a[[v]], na.rm=TRUE), 3))
results[[paste0("Median_", label_a)]] <- sapply(avail, function(v) round(median(df_a[[v]], na.rm=TRUE), 3))
results[[paste0("N_", label_b)]] <- sapply(avail, function(v) sum(!is.na(df_b[[v]])))
results[[paste0("Mean_", label_b)]] <- sapply(avail, function(v) round(mean(df_b[[v]], na.rm=TRUE), 3))
results[[paste0("SD_", label_b)]] <- sapply(avail, function(v) round(sd(df_b[[v]], na.rm=TRUE), 3))
results[[paste0("Median_", label_b)]] <- sapply(avail, function(v) round(median(df_b[[v]], na.rm=TRUE), 3))
results$Diff <- results[[paste0("Mean_", label_a)]] - results[[paste0("Mean_", label_b)]]
results$t_stat <- sapply(avail, function(v) {
tt <- tryCatch(t.test(df_a[[v]], df_b[[v]]), error = function(e) NULL)
if (!is.null(tt)) round(tt$statistic, 2) else NA_real_
})
results$p_val <- sapply(avail, function(v) {
tt <- tryCatch(t.test(df_a[[v]], df_b[[v]]), error = function(e) NULL)
if (!is.null(tt)) round(tt$p.value, 4) else NA_real_
})
results$Stars <- sapply(results$p_val, format_pval)
return(results)
}
display_desc_table <- function(desc_tbl, label_a, label_b, n_a, n_b, caption_text) {
disp <- desc_tbl %>%
mutate(
!!paste0(label_a, " Mean (SD)") := sprintf("%.3f (%.3f)",
.data[[paste0("Mean_", label_a)]], .data[[paste0("SD_", label_a)]]),
!!paste0(label_b, " Mean (SD)") := sprintf("%.3f (%.3f)",
.data[[paste0("Mean_", label_b)]], .data[[paste0("SD_", label_b)]]),
Difference = sprintf("%.3f%s", Diff, Stars),
`t-stat` = round(t_stat, 2)
) %>%
select(Variable, all_of(paste0(label_a, " Mean (SD)")),
all_of(paste0(label_b, " Mean (SD)")), Difference, `t-stat`)
kbl(disp, format = "html", escape = FALSE,
caption = sprintf("%s: %s (N=%d) vs. %s (N=%d)", caption_text, label_a, n_a, label_b, n_b)) %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = FALSE, position = "left") %>%
footnote(general = "*** p<0.01, ** p<0.05, * p<0.10 (Welch t-test).", general_title = "")
}
save_desc_latex <- function(desc_tbl, label_a, label_b, n_a, n_b, caption_text, filename) {
tex <- desc_tbl %>%
mutate(col_a = sprintf("%.3f (%.3f)", .data[[paste0("Mean_",label_a)]], .data[[paste0("SD_",label_a)]]),
col_b = sprintf("%.3f (%.3f)", .data[[paste0("Mean_",label_b)]], .data[[paste0("SD_",label_b)]]),
diff_col = sprintf("%.3f%s", Diff, Stars)) %>%
select(Variable, col_a, col_b, diff_col, t_stat)
names(tex) <- c("Variable", paste0(label_a," Mean (SD)"), paste0(label_b," Mean (SD)"), "Difference", "t-stat")
kbl_tex <- kbl(tex, format="latex", booktabs=TRUE, escape=FALSE,
caption=sprintf("%s: %s (N=%d) vs. %s (N=%d)", caption_text, label_a, n_a, label_b, n_b)) %>%
kable_styling(latex_options = c("hold_position","scale_down")) %>%
footnote(general="*** p$<$0.01, ** p$<$0.05, * p$<$0.10.", escape=FALSE, general_title="")
writeLines(kbl_tex, file.path(TABLE_PATH, paste0(filename, ".tex")))
cat(sprintf("Saved: %s.tex\n", filename))
}
# --- Save kbl table as LaTeX ---
save_kbl_latex <- function(df, filename, col.names = NULL, caption = "", align = NULL) {
tex <- kbl(df, format = "latex", booktabs = TRUE, escape = FALSE,
col.names = col.names, caption = caption, align = align) %>%
kable_styling(latex_options = c("hold_position", "scale_down")) %>%
footnote(general = "", escape = FALSE, general_title = "")
writeLines(tex, file.path(TABLE_PATH, paste0(filename, ".tex")))
cat(sprintf("Saved: %s.tex\n", filename))
}
# --- Save modelsummary regression table as LaTeX ---
save_reg_latex <- function(model_list, filename, ...) {
tex_path <- file.path(TABLE_PATH, paste0(filename, ".tex"))
msummary(model_list, output = tex_path,
stars = c("*" = .10, "**" = .05, "***" = .01),
gof_omit = "AIC|BIC|Log|RMSE", ...)
cat(sprintf("Saved: %s.tex\n", filename))
}
Paths &
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/run_Analysis_03232026")
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_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")
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, beta_uninsured,
beta_insured_w, beta_uninsured_w, gamma_hat, alpha_hat)
public_flag <- read_csv(file.path(DATA_PROC, "public_bank_flag.csv"), show_col_types = FALSE) %>%
mutate(idrssd = as.character(idrssd)) %>% select(idrssd, period, is_public)
# ── Load deposit costs from DSSW hedonic estimation ──
# (Generated by deposit_cost_estimation.Rmd)
deposit_costs_file <- file.path(DATA_PROC, "dssw_deposit_costs.csv")
if (file.exists(deposit_costs_file)) {
deposit_costs <- read_csv(deposit_costs_file, show_col_types = FALSE) %>%
mutate(idrssd = as.character(idrssd))
deposit_costs_2022q4 <- deposit_costs %>%
filter(period == "2022Q4") %>%
select(idrssd, deposit_cost_weighted, deposit_cost_insured, deposit_cost_uninsured)
cat("Deposit costs loaded:", nrow(deposit_costs_2022q4), "banks at 2022Q4\n")
HAS_DEPOSIT_COSTS <- TRUE
} else {
cat("WARNING: dssw_deposit_costs.csv not found. Run deposit_cost_estimation.Rmd first.\n")
cat(" Using c = 0 (deposit costs omitted from franchise value).\n")
deposit_costs_2022q4 <- NULL
HAS_DEPOSIT_COSTS <- FALSE
}
## Deposit costs loaded: 4685 banks at 2022Q4
cat("Call Report:", nrow(call_q), "obs |", n_distinct(call_q$idrssd), "banks\n")
## Call Report: 75989 obs | 5074 banks
Exclusions &
Borrower Indicators
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:", length(excluded_banks), "banks (G-SIB + failed)\n")
## Excluded: 41 banks (G-SIB + failed)
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), "| DW Crisis:", nrow(dw_crisis), "\n")
## BTFP Crisis: 526 | DW Crisis: 459
CALIBRATION &
VARIABLE CONSTRUCTION
Calibration [Theory
Eq. 3]
# ==============================================================================
# DSSW Eq. (20): Decaying perpetuity (Gordon Growth Model)
#
# Deposits decay at rate δ per year. The franchise rent (1-β^U)y - c^U
# on each dollar of uninsured deposits is capitalized as a decaying
# perpetuity:
#
# f_i = max{ [(1-β^U_i) y - c^U_i] / (y + δ) × μ_i , 0 }
#
# where:
# y = 10-year Treasury yield (the discount rate for a stream with
# duration 1/δ = 10 years)
# δ = deposit decay rate (10% per year; DSSW baseline)
# β^U_i = uninsured deposit beta (bank-level, DSSW)
# c^U_i = uninsured deposit operating cost (bank-level, DSSW hedonic)
# μ_i = D^U_i / A_i (uninsured deposit leverage)
#
# The cap factor 1/(y+δ) = 7.18 is constant across banks.
# The pmax ensures f ≥ 0: if operating costs exceed franchise rents,
# the franchise is worthless (not negative).
# ==============================================================================
y_10yr <- 0.0370 # 10-year Treasury yield (March 10, 2023) — from FRED DGS10
delta_decay <- 0.10 # Deposit decay rate (DSSW baseline: 10% per year)
cap_factor <- 1 / (y_10yr + delta_decay) # = 7.18
cat("=== CALIBRATION (DSSW Eq. 20: Decaying Perpetuity) ===\n")
## === CALIBRATION (DSSW Eq. 20: Decaying Perpetuity) ===
cat(sprintf(" y (10yr Treasury) = %.4f\n", y_10yr))
## y (10yr Treasury) = 0.0370
cat(sprintf(" δ (decay rate) = %.2f (deposit half-life ≈ %.1f years)\n",
delta_decay, log(2)/delta_decay))
## δ (decay rate) = 0.10 (deposit half-life ≈ 6.9 years)
cat(sprintf(" Cap. factor = 1/(y+δ) = 1/%.4f = %.2f\n",
y_10yr + delta_decay, cap_factor))
## Cap. factor = 1/(y+δ) = 1/0.1370 = 7.30
cat(sprintf(" Duration match = 1/δ = %.0f years → 10yr Treasury\n", 1/delta_decay))
## Duration match = 1/δ = 10 years → 10yr Treasury
Variable Construction
[Theory Eq. 1–4, Table 1]
# ==============================================================================
# Maps EVERY variable in Theory Table 1 to data:
# ℓ_S = (1-θ_S)S/A → total securities MTM losses / TA
# ℓ_L = (1-θ_L)L/A → mtm_loss_total_loan_to_total_asset
# ℓ = ℓ_S + ℓ_L → mtm_loss_to_total_asset
# BTFP-eligible → mtm_loss_omo_eligible_to_total_asset [OMO securities]
# non-BTFP → mtm_loss_non_omo_eligible_to_total_asset [non-OMO sec + loans]
# μ = D^U/A → uninsured_deposit_to_total_asset
# β^U → beta_uninsured (DSSW)
# c = deposit cost → deposit_cost_weighted (DSSW hedonic)
# f = (1-β^U)(r-c) μ × AF(T,δ) [Franchise value / TA, finite-horizon annuity]
# e = E/A → book_equity_to_total_asset
# ℓ̄ = e + f → solvency threshold
# ℓ_ = e + (D^I/D)f → liquidity threshold
#
# β^U is CLIPPED to [0,1] per theory assumption (β ∈ [0,1]).
# δ = 10yr Treasury yield; r = Fed funds rate; c = deposit cost.
# ==============================================================================
construct_analysis_vars <- function(baseline_data) {
baseline_data %>%
mutate(
# ── Eq. 2: MTM loss components ──
# ℓ = total MTM losses / TA
# ℓ_S = (1-θ_S)S/A = total securities losses / TA (= ℓ - ℓ_L)
# ℓ_L = (1-θ_L)L/A = total loan losses / TA
mtm_total_raw = mtm_loss_to_total_asset,
mtm_loan_raw = mtm_loss_total_loan_to_total_asset, # ℓ_L: Loan losses
mtm_sec_raw = mtm_loss_to_total_asset - # ℓ_S: Securities losses
mtm_loss_total_loan_to_total_asset, # = ℓ - ℓ_L
# BTFP-eligible (OMO) vs non-BTFP decomposition (for P3 par-channel tests)
mtm_btfp_raw = mtm_loss_omo_eligible_to_total_asset, # OMO-eligible securities
mtm_other_raw = mtm_loss_non_omo_eligible_to_total_asset, # non-OMO sec + loans
# ── Balance sheet ratios ──
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(
replace_na(fed_fund_purchase, 0) + replace_na(repo, 0) +
replace_na(other_borrowed_less_than_1yr, 0),
total_liability, 0) * 100,
# ── Deposit beta: clip to [0,1] per theory ──
uninsured_beta_raw = ifelse(!is.na(beta_uninsured), beta_uninsured, NA_real_),
beta_u_clipped = pmin(pmax(uninsured_beta_raw, 0), 1),
beta_was_clipped = as.integer(!is.na(uninsured_beta_raw) &
(uninsured_beta_raw < 0 | uninsured_beta_raw > 1)),
# ── Uninsured deposit cost c^U (from DSSW hedonic estimation) ──
# DSSW Eq. 20: cost is subtracted from the franchise rent, not the rate.
# Use uninsured-specific cost (not weighted average across all deposits).
cost_u_raw = ifelse(!is.na(deposit_cost_uninsured), deposit_cost_uninsured, 0),
# ── Jiang AE (Framework A) ──
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),
# ── IDCR-100% (Framework B) ──
mv_assets = mm_asset,
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),
# ── DSSW Eq. 20: Franchise value (decaying perpetuity) ──
# f_i = pmax( ((1 - β^U_i) * y - c^U_i) / (y + δ) * μ_i, 0 )
#
# The numerator (1-β^U)*y is the gross franchise rent per dollar of
# uninsured deposits: the spread between the market rate y and the
# deposit rate β^U*y. Subtracting c^U gives the NET rent after
# operating costs. Capitalizing at (y+δ) reflects both discounting
# and deposit decay. Multiplying by μ scales to assets.
#
# NOTE: (1-β)*y - c ≠ (1-β)*(y-c). The cost is on the RENT, not the RATE.
mu_decimal = uninsured_deposit / total_asset,
insured_share = safe_div(insured_deposit, insured_deposit + uninsured_deposit, NA_real_),
uninsured_share_d = safe_div(uninsured_deposit, insured_deposit + uninsured_deposit, NA_real_),
# Gross franchise rent per $ of uninsured deposits
gross_rent = (1 - beta_u_clipped) * y_10yr,
# Net rent = gross rent - operating cost
net_rent = gross_rent - cost_u_raw,
# f in decimal (fraction of total assets)
f_decimal = ifelse(!is.na(beta_u_clipped),
pmax(net_rent * cap_factor * mu_decimal, 0), NA_real_),
f_pp = ifelse(!is.na(f_decimal), f_decimal * 100, NA_real_),
# ── Eq. 4: E^MV = e - ℓ + f ──
emv_pp = ifelse(!is.na(f_pp),
book_equity_to_total_asset - mtm_loss_to_total_asset + f_pp, NA_real_),
# Post-run equity = e - ℓ + (D^I/D)f
e_postrun_pp = ifelse(!is.na(f_pp) & !is.na(insured_share),
book_equity_to_total_asset - mtm_loss_to_total_asset + insured_share * f_pp, NA_real_),
# ── Thresholds ──
threshold_solvency = ifelse(!is.na(f_pp), book_equity_to_total_asset + f_pp, NA_real_),
threshold_liquidity = ifelse(!is.na(f_pp) & !is.na(insured_share),
book_equity_to_total_asset + insured_share * f_pp, NA_real_),
# ── Eq. 5: Panic width = (D^U/D)f ──
panic_width = ifelse(!is.na(f_pp) & !is.na(uninsured_share_d),
uninsured_share_d * f_pp, NA_real_),
# ── Three-region classification ──
model_region = case_when(
is.na(threshold_solvency) | is.na(threshold_liquidity) ~ NA_character_,
mtm_loss_to_total_asset <= threshold_liquidity ~ "No-Run Region",
mtm_loss_to_total_asset <= threshold_solvency ~ "Panic Zone",
TRUE ~ "Fundamental Insolvency"),
model_region = factor(model_region,
levels = c("No-Run Region","Panic Zone","Fundamental Insolvency")),
in_norun = as.integer(model_region == "No-Run Region"),
in_panic = as.integer(model_region == "Panic Zone"),
in_fund_ins = as.integer(model_region == "Fundamental Insolvency"),
# ── Eq. 7: Liquidity gap proxy ──
# g = w - (C + θ_S S). Market value of securities = book - ALL securities MTM losses.
# liquid_buffer = C/A + (S/A - ℓ_S) = cash + market-value securities
liquid_buffer_raw = cash_to_total_asset + security_to_total_asset - mtm_sec_raw,
# ── Winsorized ──
mtm_total_w=winsorize(mtm_total_raw), mtm_sec_w=winsorize(mtm_sec_raw),
mtm_loan_w=winsorize(mtm_loan_raw),
mtm_btfp_w=winsorize(mtm_btfp_raw), # BTFP-eligible (OMO)
mtm_other_w=winsorize(mtm_other_raw), # non-BTFP (non-OMO sec + loans)
uninsured_lev_w=winsorize(uninsured_lev_raw),
insured_lev_w=winsorize(r_insured_deposit), adjusted_equity_w=winsorize(adjusted_equity_raw),
f_pp_w=winsorize(f_pp), emv_pp_w=winsorize(emv_pp),
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),
deposit_cost_w=winsorize(cost_u_raw),
# ── Z-standardized (full-sample z-scores) ──
mtm_total=standardize_z(mtm_total_w), mtm_sec=standardize_z(mtm_sec_w),
mtm_loan=standardize_z(mtm_loan_w),
mtm_btfp=standardize_z(mtm_btfp_w), # BTFP-eligible (OMO) z-score
mtm_other=standardize_z(mtm_other_w), # non-BTFP z-score
uninsured_lev=standardize_z(uninsured_lev_w),
insured_lev=standardize_z(insured_lev_w), adjusted_equity=standardize_z(adjusted_equity_w),
franchise_value=standardize_z(f_pp_w), emv=standardize_z(emv_pp_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),
# ── Eq. 6 / Eq. 11: Interactions ──
# NOTE: The theory's comparative static (Eq. 7) is ∂²Pr/∂λ∂D^U,
# i.e. w.r.t. λ and D^U (uninsured deposits). The regression specification
# (Eq. 11) uses ℓ × f instead, where f = (1-β^U)(r-c)μ/δ conflates
# the leverage channel (ℓ × μ) and the stickiness channel (ℓ × (1-β^U)).
# This is the paper's stated specification; the decomposition in §8.5
# separates the two channels as a robustness check.
mtm_x_franchise = mtm_total * franchise_value,
mtm_x_uninsured = mtm_total * uninsured_lev,
mtm_x_unins_beta = mtm_total * uninsured_beta,
# ── Eq. 9: Par benefit & collateral ──
collateral_capacity_raw = omo_eligible_to_total_asset,
par_benefit_raw = mtm_loss_omo_eligible_to_omo_eligible,
size_cat = factor(create_size_category_3(total_asset), levels = size_levels_3)
) %>%
mutate(collateral_capacity_w = winsorize(collateral_capacity_raw),
collateral_capacity = standardize_z(collateral_capacity_w),
par_benefit_w = winsorize(par_benefit_raw),
par_benefit = standardize_z(par_benefit_w))
}
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") %>%
{ if (HAS_DEPOSIT_COSTS) left_join(., deposit_costs_2022q4, by="idrssd") else . } %>%
left_join(public_flag %>% filter(period=="2022Q4") %>% select(idrssd,is_public), by="idrssd") %>%
mutate(is_public=replace_na(is_public,0L)) %>% 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") %>%
{ if (HAS_DEPOSIT_COSTS) left_join(., deposit_costs_2022q4, by="idrssd") else . } %>%
left_join(public_flag %>% filter(period=="2023Q3") %>% select(idrssd,is_public), by="idrssd") %>%
mutate(is_public=replace_na(is_public,0L)) %>% construct_analysis_vars()
cat("2022Q4:", nrow(df_2022q4), "| Beta^U available:", sum(!is.na(df_2022q4$f_pp)),
"| Beta clipped:", sum(df_2022q4$beta_was_clipped, na.rm=TRUE), "\n")
## 2022Q4: 4292 | Beta^U available: 4226 | Beta clipped: 8
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))
}
df_crisis <- join_all_borrowers(df_2022q4, btfp_crisis, dw_crisis, "btfp_crisis", "dw_crisis")
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))
cat("Crisis:", nrow(df_crisis), "| Any Fed:", sum(df_crisis$any_fed), "\n")
## Crisis: 4292 | Any Fed: 828
Sample Cleaning
core_vars <- c("book_equity_to_total_asset","total_asset","total_liability",
"cash_to_total_asset","security_to_total_asset","total_loan_to_total_asset","roa")
cleaning_log <- list()
log_step <- function(step, description, df) {
cleaning_log[[length(cleaning_log) + 1]] <<- list(
Step = step, Description = description, N = nrow(df),
Borrowers = sum(df$any_fed, na.rm = TRUE))
invisible(df)
}
log_step(1, "Call Report universe (all bank-quarters)", call_q %>% mutate(any_fed = NA_integer_))
log_step(2, sprintf("Filter to baseline period (%s)", BASELINE_MAIN),
call_q %>% filter(period == BASELINE_MAIN) %>% mutate(any_fed = NA_integer_))
log_step(3, "Exclude G-SIB and failed banks",
call_q %>% filter(period == BASELINE_MAIN, !idrssd %in% excluded_banks) %>% mutate(any_fed = NA_integer_))
log_step(4, "Require OMO-eligible securities > 0",
call_q %>% filter(period == BASELINE_MAIN, !idrssd %in% excluded_banks,
!is.na(omo_eligible) & omo_eligible > 0) %>% mutate(any_fed = NA_integer_))
log_step(5, "Merge DSSW betas + borrower indicators", df_crisis)
df_tmp <- df_crisis
log_step(6, "Drop missing MTM losses", df_tmp <- df_tmp %>% filter(!is.na(mtm_loss_to_total_asset)))
log_step(7, "Drop missing core balance sheet",
df_tmp <- df_tmp %>% filter(if_all(all_of(core_vars), ~!is.na(.))))
log_step(8, "Drop missing deposits",
df_tmp <- df_tmp %>% filter(!is.na(uninsured_deposit) & !is.na(insured_deposit) &
insured_deposit > 0 & !is.na(uninsured_deposit_to_total_asset)))
log_step(9, "Drop missing FHLB indicator",
df_tmp <- df_tmp %>% filter(!is.na(abnormal_fhlb_borrowing_10pct)))
log_step(10, "Drop non-positive total assets",
df_tmp <- df_tmp %>% filter(total_asset > 0 & is.finite(ln_assets_raw)))
log_step(11, "Drop missing adjusted equity / IDCR",
df_tmp <- df_tmp %>% filter(!is.na(adjusted_equity_raw) & !is.na(idcr_100)))
df_crisis_clean <- df_tmp
df_crisis_model <- df_crisis_clean %>% filter(!is.na(model_region))
log_step(12, "Require DSSW beta^U -> theory model", df_crisis_model)
df_arb_clean <- df_arb %>%
filter(!is.na(mtm_loss_to_total_asset)) %>%
filter(if_all(all_of(core_vars), ~!is.na(.))) %>%
filter(!is.na(uninsured_deposit) & !is.na(insured_deposit) & insured_deposit>0) %>%
filter(!is.na(abnormal_fhlb_borrowing_10pct)) %>%
filter(total_asset>0) %>% filter(!is.na(adjusted_equity_raw) & !is.na(idcr_100))
cat(sprintf("Clean: %d | Theory model: %d | Arb: %d\n",
nrow(df_crisis_clean), nrow(df_crisis_model), nrow(df_arb_clean)))
## Clean: 4251 | Theory model: 4226 | Arb: 4168
cl <- bind_rows(lapply(cleaning_log, as.data.frame))
cl$Dropped <- c(NA, -diff(cl$N)); cl$Dropped[1] <- 0
cl$Pct_of_Prev <- c(NA, round(100*cl$Dropped[-1]/cl$N[-nrow(cl)],1)); cl$Pct_of_Prev[1] <- 0
cl$Borrowers_disp <- ifelse(cl$Step >= 5, cl$Borrowers, "---")
disp_cl <- cl %>%
mutate(N_fmt=formatC(N,format="d",big.mark=","),
Drop_fmt=ifelse(Dropped==0,"---",formatC(Dropped,format="d",big.mark=",")),
Pct_fmt=ifelse(is.na(Pct_of_Prev)|Pct_of_Prev==0,"---",paste0(Pct_of_Prev,"%")),
Borr_fmt=Borrowers_disp) %>%
select(Step, Description, N_fmt, Drop_fmt, Pct_fmt, Borr_fmt)
kbl(disp_cl, format="html", escape=FALSE,
col.names=c("Step","Filter","N Remaining","Dropped","% Dropped","Fed Borrowers"),
caption="Sample Construction") %>%
kable_styling(bootstrap_options=c("striped","hover","condensed"), full_width=FALSE) %>%
row_spec(nrow(disp_cl), bold=TRUE, background="#e8f5e9")
Sample Construction
|
Step
|
Filter
|
N Remaining
|
Dropped
|
% Dropped
|
Fed Borrowers
|
|
1
|
Call Report universe (all bank-quarters)
|
75,989
|
—
|
—
|
—
|
|
2
|
Filter to baseline period (2022Q4)
|
4,737
|
71,252
|
93.8%
|
—
|
|
3
|
Exclude G-SIB and failed banks
|
4,696
|
41
|
0.9%
|
—
|
|
4
|
Require OMO-eligible securities > 0
|
4,292
|
404
|
8.6%
|
—
|
|
5
|
Merge DSSW betas + borrower indicators
|
4,292
|
—
|
—
|
828
|
|
6
|
Drop missing MTM losses
|
4,282
|
10
|
0.2%
|
828
|
|
7
|
Drop missing core balance sheet
|
4,282
|
—
|
—
|
828
|
|
8
|
Drop missing deposits
|
4,251
|
31
|
0.7%
|
828
|
|
9
|
Drop missing FHLB indicator
|
4,251
|
—
|
—
|
828
|
|
10
|
Drop non-positive total assets
|
4,251
|
—
|
—
|
828
|
|
11
|
Drop missing adjusted equity / IDCR
|
4,251
|
—
|
—
|
828
|
|
12
|
Require DSSW beta^U -> theory model
|
4,226
|
25
|
0.6%
|
822
|
# --- LaTeX export ---
save_kbl_latex(disp_cl, "Table_SampleConstruction",
col.names=c("Step","Filter","N Remaining","Dropped","\\% Dropped","Fed Borrowers"),
caption="Sample Construction")
## Saved: Table_SampleConstruction.tex
SANITY CHECKS [Theory
Eq. 4–5]
df_check <- df_crisis_model
cat("=== SANITY CHECKS (Theory Equations 4-5) ===\n\n")
## === SANITY CHECKS (Theory Equations 4-5) ===
# Eq. 2: ℓ = ℓ_S + ℓ_L (securities + loans)
decomp_err <- max(abs(df_check$mtm_total_raw - (df_check$mtm_sec_raw + df_check$mtm_loan_raw)), na.rm=TRUE)
cat(sprintf("Eq. 2: max|ℓ - (ℓ_S + ℓ_L)| = %.6f → %s\n", decomp_err,
ifelse(decomp_err < 0.01, "PASS", "FAIL")))
## Eq. 2: max|ℓ - (ℓ_S + ℓ_L)| = 0.000000 → PASS
# Eq. 9: ℓ_S is the coordination signal (observable); ℓ_L is the fundamental channel (unobservable)
cat(sprintf("Eq. 2/9: Securities MTM (ℓ_S): mean=%.2f | Loan MTM (ℓ_L): mean=%.2f | Sum check: max|ℓ-(ℓ_S+ℓ_L)|=%.6f → PASS\n",
mean(df_check$mtm_sec_raw, na.rm=TRUE), mean(df_check$mtm_loan_raw, na.rm=TRUE), decomp_err))
## Eq. 2/9: Securities MTM (ℓ_S): mean=2.14 | Loan MTM (ℓ_L): mean=3.35 | Sum check: max|ℓ-(ℓ_S+ℓ_L)|=0.000000 → PASS
# Eq. 3 (DSSW): f >= 0 (guaranteed by pmax in formula)
stopifnot(all(df_check$f_pp >= 0, na.rm = TRUE))
cat("DSSW Eq. 20: f >= 0 for all banks (pmax in formula) → PASS\n")
## DSSW Eq. 20: f >= 0 for all banks (pmax in formula) → PASS
# Diagnostic: how many banks have negative net rent BEFORE the pmax floor?
n_neg_rent <- sum(!is.na(df_check$beta_u_clipped) &
((1 - df_check$beta_u_clipped) * y_10yr - df_check$cost_u_raw) < 0, na.rm = TRUE)
cat(sprintf(" Banks with (1-β^U)y < c^U (negative net rent, floored to f=0): %d (%.1f%%)\n",
n_neg_rent, 100 * n_neg_rent / sum(!is.na(df_check$beta_u_clipped))))
## Banks with (1-β^U)y < c^U (negative net rent, floored to f=0): 47 (1.1%)
# Eq. 4: E^MV = e - ℓ + f
emv_err <- max(abs(df_check$emv_pp - (df_check$book_equity_ratio_raw - df_check$mtm_total_raw + df_check$f_pp)), na.rm=TRUE)
cat(sprintf("Eq. 4: max|E^MV - (e-ℓ+f)| = %.6f → %s\n", emv_err,
ifelse(emv_err < 0.001, "PASS", "CHECK")))
## Eq. 4: max|E^MV - (e-ℓ+f)| = 0.000000 → PASS
# Eq. 5: ℓ̄ >= ℓ_ (solvency >= liquidity threshold)
stopifnot(all(df_check$threshold_solvency >= df_check$threshold_liquidity - 1e-10, na.rm=TRUE))
cat("Eq. 5: Solvency threshold >= Liquidity threshold → PASS\n")
## Eq. 5: Solvency threshold >= Liquidity threshold → PASS
# Eq. 5: panic_width = (D^U/D)*f
pw_err <- max(abs(df_check$panic_width - df_check$uninsured_share_d*df_check$f_pp), na.rm=TRUE)
cat(sprintf("Eq. 5: max|panic_width - (D^U/D)f| = %.6f → %s\n", pw_err,
ifelse(pw_err < 0.001, "PASS", "CHECK")))
## Eq. 5: max|panic_width - (D^U/D)f| = 0.000000 → PASS
# Region ordering
nr <- df_check %>% filter(model_region=="No-Run Region")
pz <- df_check %>% filter(model_region=="Panic Zone")
fi <- df_check %>% filter(model_region=="Fundamental Insolvency")
stopifnot(all(nr$mtm_total_raw <= nr$threshold_liquidity + 1e-10))
stopifnot(all(pz$mtm_total_raw > pz$threshold_liquidity - 1e-10))
stopifnot(all(pz$mtm_total_raw <= pz$threshold_solvency + 1e-10))
stopifnot(all(fi$mtm_total_raw > fi$threshold_solvency - 1e-10))
cat("Region assignments consistent with thresholds → PASS\n")
## Region assignments consistent with thresholds → PASS
cat(sprintf("\nBeta^U clipped: %d banks (%.1f%%)\n",
sum(df_check$beta_was_clipped,na.rm=T), 100*mean(df_check$beta_was_clipped,na.rm=T)))
##
## Beta^U clipped: 8 banks (0.2%)
cat("\n=== ALL SANITY CHECKS PASSED ===\n")
##
## === ALL SANITY CHECKS PASSED ===
THREE-REGION CLASSIFICATION
Sensitivity to φ (Run
Assumption) [Theory Eq. 10]
Theory (Eq. 10): \(\underline\ell(\phi) = e + [(D^I +
(1-\phi)D^U)/D]\,f\). Baseline uses \(\phi = 1\) (full uninsured run). Lower
\(\phi\) raises the liquidity
threshold, shrinking the panic zone and moving banks toward no-run.
phi_grid_main <- c(0.25, 0.50, 0.75, 1.00)
phi_table <- map_dfr(phi_grid_main, function(phi) {
df_phi <- df_crisis_model %>%
mutate(
# Eq. 10: generalised liquidity threshold
th_liq_phi = book_equity_to_total_asset +
safe_div(insured_deposit + (1 - phi) * uninsured_deposit,
insured_deposit + uninsured_deposit, NA_real_) * f_pp,
region_phi = case_when(
is.na(th_liq_phi) | is.na(threshold_solvency) ~ NA_character_,
mtm_total_raw <= th_liq_phi ~ "No-Run",
mtm_total_raw <= threshold_solvency ~ "Panic",
TRUE ~ "Fund. Insolvency"
)
) %>% filter(!is.na(region_phi))
n_tot <- nrow(df_phi)
n_nr <- sum(df_phi$region_phi == "No-Run")
n_pz <- sum(df_phi$region_phi == "Panic")
n_fi <- sum(df_phi$region_phi == "Fund. Insolvency")
# Borrowing rates by region
br_nr <- if (n_nr > 0) mean(df_phi$any_fed[df_phi$region_phi == "No-Run"], na.rm = TRUE) else NA_real_
br_pz <- if (n_pz > 0) mean(df_phi$any_fed[df_phi$region_phi == "Panic"], na.rm = TRUE) else NA_real_
br_fi <- if (n_fi > 0) mean(df_phi$any_fed[df_phi$region_phi == "Fund. Insolvency"], na.rm = TRUE) else NA_real_
# Mean panic width = (D^U/D)f (solvency threshold unchanged; only liquidity moves)
mean_width <- mean(df_phi$threshold_solvency - df_phi$th_liq_phi, na.rm = TRUE)
tibble(
phi = phi, N = n_tot,
NoRun = n_nr, Panic = n_pz, FundIns = n_fi,
Borr_NoRun = br_nr, Borr_Panic = br_pz, Borr_FundIns = br_fi,
PanicWidth = round(mean_width, 2)
)
})
disp_phi <- phi_table %>%
mutate(
`φ` = sprintf("%.0f%%", phi * 100),
`No-Run` = sprintf("%d (%.1f%%)", NoRun, 100 * NoRun / N),
`Panic Zone` = sprintf("%d (%.1f%%)", Panic, 100 * Panic / N),
`Fund. Insolvency` = sprintf("%d (%.1f%%)", FundIns, 100 * FundIns / N),
`Borr. Rate (NR)` = ifelse(is.na(Borr_NoRun), "---", sprintf("%.1f%%", 100 * Borr_NoRun)),
`Borr. Rate (PZ)` = ifelse(is.na(Borr_Panic), "---", sprintf("%.1f%%", 100 * Borr_Panic)),
`Borr. Rate (FI)` = ifelse(is.na(Borr_FundIns), "---", sprintf("%.1f%%", 100 * Borr_FundIns)),
`Width (pp)` = PanicWidth
) %>%
select(`φ`, N, `No-Run`, `Panic Zone`, `Fund. Insolvency`,
`Borr. Rate (NR)`, `Borr. Rate (PZ)`, `Borr. Rate (FI)`, `Width (pp)`)
kbl(disp_phi, format = "html", escape = FALSE,
caption = sprintf("Three-Region Classification Under Alternative Run Assumptions (N=%d, δ=%.2f%%)",
phi_table$N[1], delta_decay * 100)) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE) %>%
row_spec(which(phi_table$phi == 1.00), bold = TRUE, background = "#e8f5e9") %>%
add_header_above(c(" " = 2, "Region Counts" = 3, "Fed Borrowing Rate" = 3, " " = 1)) %>%
footnote(general = c(
"φ = fraction of uninsured depositors who run. φ=100% is the baseline (worst-case full run).",
"Liquidity threshold: ℓ_(φ) = e + [(D^I + (1-φ)D^U)/D]f. Solvency threshold unchanged: ℓ̄ = e + f.",
"Lower φ raises ℓ_(φ), shrinking the panic zone and reclassifying marginal banks as no-run."))
Three-Region Classification Under Alternative Run Assumptions (N=4226,
δ=10.00%)
|
|
Region Counts
|
Fed Borrowing Rate
|
|
|
φ
|
N
|
No-Run
|
Panic Zone
|
Fund. Insolvency
|
Borr. Rate (NR)
|
Borr. Rate (PZ)
|
Borr. Rate (FI)
|
Width (pp)
|
|
25%
|
4226
|
3877 (91.7%)
|
14 (0.3%)
|
335 (7.9%)
|
18.9%
|
21.4%
|
26.3%
|
0.21
|
|
50%
|
4226
|
3852 (91.2%)
|
39 (0.9%)
|
335 (7.9%)
|
18.7%
|
33.3%
|
26.3%
|
0.42
|
|
75%
|
4226
|
3822 (90.4%)
|
69 (1.6%)
|
335 (7.9%)
|
18.7%
|
30.4%
|
26.3%
|
0.63
|
|
100%
|
4226
|
3789 (89.7%)
|
102 (2.4%)
|
335 (7.9%)
|
18.5%
|
32.4%
|
26.3%
|
0.84
|
|
Note:
|
|
φ = fraction of uninsured depositors who run. φ=100% is the
baseline (worst-case full run).
|
|
Liquidity threshold: ℓ_(φ) = e + [(D^I +
(1-φ)D^U)/D]f. Solvency threshold unchanged: ℓ̄ = e + f.
|
|
Lower φ raises ℓ_(φ), shrinking the panic zone and
reclassifying marginal banks as no-run.
|
# --- LaTeX export ---
tex_phi <- kbl(disp_phi, format="latex", booktabs=TRUE, escape=FALSE,
caption=sprintf("Three-Region Classification Under Alternative Run Assumptions (N=%d, $\\delta$=%.2f\\%%)",
phi_table$N[1], delta_decay*100)) %>%
kable_styling(latex_options=c("hold_position","scale_down")) %>%
add_header_above(c(" "=2, "Region Counts"=3, "Fed Borrowing Rate"=3, " "=1)) %>%
footnote(general=c("$\\phi$=1 (baseline): full uninsured-depositor run.",
"Liquidity threshold: $\\underline{\\ell}(\\phi) = e + [(D^I + (1-\\phi)D^U)/D]f$."),
escape=FALSE, general_title="")
writeLines(tex_phi, file.path(TABLE_PATH, "Table_Sensitivity_Phi.tex"))
cat("Saved: Table_Sensitivity_Phi.tex\n")
## Saved: Table_Sensitivity_Phi.tex
Sensitivity to δ
(Discount Rate)
# DSSW uses δ=0.10. We test δ ∈ {0.05, 0.075, 0.10, 0.125, 0.15, 0.20}
# Lower δ = slower decay = longer-lived deposits = higher cap factor = higher f
sens <- map_dfr(c(0.05, 0.075, 0.10, 0.125, 0.15, 0.20), function(dd) {
cf_dd <- 1 / (y_10yr + dd)
df_s <- df_crisis_clean %>% filter(!is.na(beta_u_clipped)) %>%
mutate(c_u = ifelse(!is.na(cost_u_raw), cost_u_raw, 0),
f_s = pmax(((1 - beta_u_clipped) * y_10yr - c_u) * cf_dd * mu_decimal, 0) * 100,
th_sol = book_equity_to_total_asset + f_s,
th_liq = book_equity_to_total_asset + insured_share * f_s,
reg = case_when(mtm_loss_to_total_asset <= th_liq ~ "NoRun",
mtm_loss_to_total_asset <= th_sol ~ "Panic", TRUE ~ "FundIns"))
tibble(delta_pct = dd * 100, N = nrow(df_s), NoRun = sum(df_s$reg == "NoRun"),
Panic = sum(df_s$reg == "Panic"), FundIns = sum(df_s$reg == "FundIns"),
Cap_Factor = round(cf_dd, 2),
Mean_f = round(mean(df_s$f_s, na.rm = TRUE), 2),
Width = round(mean(df_s$th_sol - df_s$th_liq, na.rm = TRUE), 2))
})
disp_s <- sens %>% mutate(`No-Run`=sprintf("%d (%.1f%%)",NoRun,100*NoRun/N),
`Panic`=sprintf("%d (%.1f%%)",Panic,100*Panic/N),
`Fund.Ins.`=sprintf("%d (%.1f%%)",FundIns,100*FundIns/N)) %>%
select(delta_pct, Cap_Factor, N, `No-Run`, Panic, `Fund.Ins.`, Mean_f, Width)
kbl(disp_s, format="html", escape=FALSE,
col.names=c("δ (%)","1/(y+δ)","N","No-Run","Panic","Fund.Ins.","Mean f (pp)","Width (pp)"),
caption=sprintf("Sensitivity to Deposit Decay Rate (y=%.2f%%, baseline δ=%.0f%%)",
y_10yr*100, delta_decay*100)) %>%
kable_styling(bootstrap_options=c("striped","hover","condensed"), full_width=FALSE) %>%
row_spec(which(abs(sens$delta_pct - delta_decay*100) < 0.01), bold=TRUE, background="#e8f5e9") %>%
footnote(general=c(
"δ = deposit decay rate (fraction of deposits that leave per year).",
"Lower δ → slower decay → higher cap factor → higher f → fewer banks in Panic/FI.",
"DSSW baseline: δ = 10% (duration = 10 years)."))
Sensitivity to Deposit Decay Rate (y=3.70%, baseline δ=10%)
|
δ (%)
|
1/(y+δ)
|
N
|
No-Run
|
Panic
|
Fund.Ins.
|
Mean f (pp)
|
Width (pp)
|
|
5.0
|
11.49
|
4226
|
3934 (93.1%)
|
96 (2.3%)
|
196 (4.6%)
|
3.85
|
1.32
|
|
7.5
|
8.93
|
4226
|
3853 (91.2%)
|
103 (2.4%)
|
270 (6.4%)
|
2.99
|
1.03
|
|
10.0
|
7.30
|
4226
|
3789 (89.7%)
|
102 (2.4%)
|
335 (7.9%)
|
2.44
|
0.84
|
|
12.5
|
6.17
|
4226
|
3754 (88.8%)
|
98 (2.3%)
|
374 (8.8%)
|
2.07
|
0.71
|
|
15.0
|
5.35
|
4226
|
3714 (87.9%)
|
96 (2.3%)
|
416 (9.8%)
|
1.79
|
0.62
|
|
20.0
|
4.22
|
4226
|
3662 (86.7%)
|
84 (2.0%)
|
480 (11.4%)
|
1.41
|
0.49
|
|
Note:
|
|
δ = deposit decay rate (fraction of deposits that leave per
year).
|
|
Lower δ → slower decay → higher cap factor → higher f →
fewer banks in Panic/FI.
|
|
DSSW baseline: δ = 10% (duration = 10 years).
|
# --- LaTeX export ---
save_kbl_latex(disp_s, "Table_Sensitivity_Delta",
col.names=c("$\\delta$ (\\%)","$1/(y+\\delta)$","N","No-Run","Panic","Fund.Ins.",
"Mean $f$ (pp)","Width (pp)"),
caption=sprintf("Sensitivity to Deposit Decay Rate ($y$=%.2f\\%%, baseline $\\delta$=%.0f\\%%)",
y_10yr*100, delta_decay*100))
## Saved: Table_Sensitivity_Delta.tex
Cross-Framework
Comparison
n_all <- nrow(df_crisis_clean); nm <- nrow(df_crisis_model)
comp <- tribble(~Framework, ~`Solvent/NoRun`, ~Panic, ~`Insol/FundIns`)
ns <- sum(df_crisis_clean$mtm_solvent,na.rm=T)
comp <- comp %>% add_row(Framework="Jiang AE (e-l>=0)",
`Solvent/NoRun`=sprintf("%d (%.1f%%)",ns,100*ns/n_all), Panic="---",
`Insol/FundIns`=sprintf("%d (%.1f%%)",n_all-ns,100*(n_all-ns)/n_all))
for (phi in c(0.25, 0.50, 0.75, 1.00)) {
ip <- safe_div(df_crisis_clean$mv_assets-phi*df_crisis_clean$uninsured_deposit-df_crisis_clean$insured_deposit,
df_crisis_clean$insured_deposit, NA_real_)
ns <- sum(ip>=0,na.rm=T); ni <- sum(ip<0,na.rm=T); nt <- ns+ni
comp <- comp %>% add_row(Framework=sprintf("IDCR (phi=%.0f%%)",phi*100),
`Solvent/NoRun`=sprintf("%d (%.1f%%)",ns,100*ns/nt), Panic="---",
`Insol/FundIns`=sprintf("%d (%.1f%%)",ni,100*ni/nt))
}
comp <- comp %>% add_row(
Framework=sprintf("Theory Model (N=%d, r=%.1f%%, δ=%.2f%%)",nm,y_10yr*100,delta_decay*100),
`Solvent/NoRun`=sprintf("%d (%.1f%%)",sum(df_crisis_model$in_norun),100*mean(df_crisis_model$in_norun)),
Panic=sprintf("%d (%.1f%%)",sum(df_crisis_model$in_panic),100*mean(df_crisis_model$in_panic)),
`Insol/FundIns`=sprintf("%d (%.1f%%)",sum(df_crisis_model$in_fund_ins),100*mean(df_crisis_model$in_fund_ins)))
kbl(comp, format="html", escape=FALSE,
col.names=c("Framework","Solvent/No-Run","Panic Zone","Fund.Insolvency"),
caption="Bank Classification (2022Q4)") %>%
kable_styling(bootstrap_options=c("striped","hover","condensed"), full_width=FALSE) %>%
row_spec(nrow(comp), bold=TRUE, background="#e8f5e9")
Bank Classification (2022Q4)
|
Framework
|
Solvent/No-Run
|
Panic Zone
|
Fund.Insolvency
|
|
Jiang AE (e-l>=0)
|
3426 (80.6%)
|
—
|
825 (19.4%)
|
|
IDCR (phi=25%)
|
4157 (97.8%)
|
—
|
94 (2.2%)
|
|
IDCR (phi=50%)
|
4072 (95.8%)
|
—
|
179 (4.2%)
|
|
IDCR (phi=75%)
|
3809 (89.6%)
|
—
|
442 (10.4%)
|
|
IDCR (phi=100%)
|
3041 (71.5%)
|
—
|
1210 (28.5%)
|
|
Theory Model (N=4226, r=3.7%, δ=10.00%)
|
3789 (89.7%)
|
102 (2.4%)
|
335 (7.9%)
|
# --- LaTeX export ---
save_kbl_latex(comp, "Table_CrossFramework",
col.names=c("Framework","Solvent/No-Run","Panic Zone","Fund.Insolvency"),
caption="Bank Classification: Cross-Framework Comparison (2022Q4)")
## Saved: Table_CrossFramework.tex
FRANCHISE VALUE
DIAGNOSTICS
df_fv <- df_crisis_model
cat("================================================================\n")
## ================================================================
cat(" FRANCHISE VALUE DIAGNOSTICS [Theory Eq. 3]\n")
## FRANCHISE VALUE DIAGNOSTICS [Theory Eq. 3]
cat("================================================================\n\n")
## ================================================================
cat(sprintf("Banks: %d | f: mean=%.2f, med=%.2f, sd=%.2f\n",
nrow(df_fv), mean(df_fv$f_pp), median(df_fv$f_pp), sd(df_fv$f_pp)))
## Banks: 4226 | f: mean=2.44, med=2.18, sd=1.50
cat(sprintf(" P10=%.2f, P25=%.2f, P75=%.2f, P90=%.2f\n",
quantile(df_fv$f_pp,.10), quantile(df_fv$f_pp,.25), quantile(df_fv$f_pp,.75), quantile(df_fv$f_pp,.90)))
## P10=0.84, P25=1.45, P75=3.14, P90=4.28
cat(sprintf("\nInputs: beta^U (clipped): mean=%.3f | mu: mean=%.3f\n",
mean(df_fv$beta_u_clipped,na.rm=T), mean(df_fv$mu_decimal,na.rm=T)))
##
## Inputs: beta^U (clipped): mean=0.331 | mu: mean=0.238
cat(sprintf(" Uninsured cost (c^U): mean=%.4f | Net rent ((1-β^U)y - c^U): mean=%.4f\n",
mean(df_fv$cost_u_raw, na.rm=T),
mean((1 - df_fv$beta_u_clipped) * y_10yr - df_fv$cost_u_raw, na.rm=T)))
## Uninsured cost (c^U): mean=0.0108 | Net rent ((1-β^U)y - c^U): mean=0.0139
cat(sprintf(" y = %.2f%% | δ (decay) = %.0f%% | Cap.factor = 1/(y+δ) = %.2f\n",
y_10yr*100, delta_decay*100, cap_factor))
## y = 3.70% | δ (decay) = 10% | Cap.factor = 1/(y+δ) = 7.30
cat(sprintf("\nThresholds: Solvency=%.2f | Liquidity=%.2f | Width=%.2f | Actual MTM=%.2f\n",
mean(df_fv$threshold_solvency), mean(df_fv$threshold_liquidity),
mean(df_fv$panic_width,na.rm=T), mean(df_fv$mtm_total_raw)))
##
## Thresholds: Solvency=12.05 | Liquidity=11.21 | Width=0.84 | Actual MTM=5.49
cat(sprintf("E^MV: mean=%.2f | E^post-run: mean=%.2f\n",
mean(df_fv$emv_pp), mean(df_fv$e_postrun_pp,na.rm=T)))
## E^MV: mean=6.55 | E^post-run: mean=5.72
DESCRIPTIVE STATISTICS
desc_vars_A <- c("ln_assets_raw","cash_ratio_raw","securities_ratio_raw","loan_ratio_raw",
"book_equity_ratio_raw","roa_raw","fhlb_ratio_raw","wholesale_raw","loan_to_deposit_raw")
desc_labels_A <- c("Log(Assets)","Cash / TA","Securities / TA","Loans / TA",
"Book Equity / TA (e)","ROA","FHLB / TA","Wholesale (%)","Loan / Deposit")
desc_vars_B <- c("mtm_total_raw","mtm_sec_raw","mtm_loan_raw","mtm_btfp_raw","mtm_other_raw",
"cost_u_raw","net_rent",
"uninsured_lev_raw","insured_lev_raw","uninsured_share_raw","uninsured_beta_raw",
"f_pp","emv_pp","e_postrun_pp","threshold_solvency","threshold_liquidity","panic_width")
desc_labels_B <- c("Total MTM (l)","Securities MTM (l_S)","Loan MTM (l_L)",
"BTFP-elig MTM (OMO)","Non-BTFP MTM","Deposit Cost (c)","Net Spread (r-c)",
"Uninsured/TA (mu)","Insured/TA","D^U/D","Beta^U","Franchise (f)",
"MV Equity (E^MV)","Post-Run Equity","Solvency Thres.","Liquidity Thres.","Panic Width")
desc_vars_C <- c("collateral_capacity_raw","par_benefit_raw")
desc_labels_C <- c("OMO Collateral/TA (%)","Par Benefit")
desc_vars_all <- c(desc_vars_A, desc_vars_B, desc_vars_C)
desc_labels_all <- c(desc_labels_A, desc_labels_B, desc_labels_C)
Panel: Borrower vs
Non-Borrower by Region
for (reg in c("No-Run Region","Panic Zone","Fundamental Insolvency")) {
df_reg <- df_crisis_model %>% filter(model_region==reg)
df_b <- df_reg %>% filter(any_fed==1); df_n <- df_reg %>% filter(any_fed==0)
if (nrow(df_b)>=5 & nrow(df_n)>=5) {
d <- build_desc_table(df_b,df_n,"Borrower","NonBorrower",desc_vars_all,desc_labels_all)
cat(sprintf("\n### %s (B:%d, NB:%d)\n\n", reg, nrow(df_b), nrow(df_n)))
print(display_desc_table(d,"Borrower","NonBorrower",nrow(df_b),nrow(df_n),reg))
save_desc_latex(d,"Borrower","NonBorrower",nrow(df_b),nrow(df_n),reg,
sprintf("Table_Desc_%s",gsub("[^A-Za-z]","",reg)))
cat("\n")
}
}
No-Run Region
(B:701, NB:3088)
No-Run Region: Borrower (N=701) vs. NonBorrower (N=3088)
|
Variable
|
Borrower Mean (SD)
|
NonBorrower Mean (SD)
|
Difference
|
t-stat
|
|
Log(Assets)
|
13.846 (1.551)
|
12.710 (1.383)
|
1.136***
|
17.85
|
|
Cash / TA
|
5.477 (5.748)
|
8.959 (9.442)
|
-3.482***
|
-12.63
|
|
Securities / TA
|
24.099 (12.820)
|
23.841 (14.820)
|
0.258
|
0.47
|
|
Loans / TA
|
64.502 (13.570)
|
61.076 (16.915)
|
3.426***
|
5.75
|
|
Book Equity / TA (e)
|
9.252 (2.792)
|
10.355 (6.154)
|
-1.103***
|
-7.21
|
|
ROA
|
1.123 (0.609)
|
1.081 (2.006)
|
0.042
|
0.97
|
|
FHLB / TA
|
3.703 (4.806)
|
2.396 (4.019)
|
1.307***
|
6.69
|
|
Wholesale (%)
|
1.443 (3.367)
|
0.868 (3.087)
|
0.575***
|
4.15
|
|
Loan / Deposit
|
76.824 (18.608)
|
72.087 (24.244)
|
4.737***
|
5.73
|
|
Total MTM (l)
|
5.535 (1.794)
|
5.081 (2.058)
|
0.454***
|
5.88
|
|
Securities MTM (l_S)
|
2.043 (1.360)
|
1.837 (1.523)
|
0.206***
|
3.53
|
|
Loan MTM (l_L)
|
3.492 (1.526)
|
3.244 (1.744)
|
0.248***
|
3.79
|
|
BTFP-elig MTM (OMO)
|
0.729 (0.735)
|
0.612 (0.775)
|
0.117***
|
3.78
|
|
Non-BTFP MTM
|
4.740 (1.730)
|
4.304 (1.926)
|
0.436***
|
5.90
|
|
Deposit Cost (c)
|
0.011 (0.000)
|
0.011 (0.000)
|
0.000
|
NA
|
|
Net Spread (r-c)
|
0.013 (0.004)
|
0.014 (0.004)
|
-0.001***
|
-3.23
|
|
Uninsured/TA (mu)
|
27.553 (12.330)
|
23.081 (11.822)
|
4.472***
|
8.74
|
|
Insured/TA
|
57.612 (12.895)
|
62.568 (13.089)
|
-4.956***
|
-9.16
|
|
D^U/D
|
32.538 (14.554)
|
27.066 (13.902)
|
5.472***
|
9.06
|
|
Beta^U
|
0.345 (0.119)
|
0.329 (0.105)
|
0.016***
|
3.19
|
|
Franchise (f)
|
2.741 (1.589)
|
2.389 (1.490)
|
0.352***
|
5.36
|
|
MV Equity (E^MV)
|
6.458 (3.621)
|
7.662 (6.619)
|
-1.204***
|
-6.64
|
|
Post-Run Equity
|
5.409 (3.359)
|
6.854 (6.512)
|
-1.445***
|
-8.37
|
|
Solvency Thres.
|
11.993 (2.980)
|
12.743 (6.063)
|
-0.750***
|
-4.79
|
|
Liquidity Thres.
|
10.944 (2.704)
|
11.935 (5.990)
|
-0.991***
|
-6.67
|
|
Panic Width
|
1.049 (1.066)
|
0.808 (0.956)
|
0.241***
|
5.50
|
|
OMO Collateral/TA (%)
|
10.243 (9.419)
|
10.645 (10.265)
|
-0.402
|
-1.00
|
|
Par Benefit
|
8.294 (11.284)
|
10.869 (224.740)
|
-2.575
|
-0.63
|
|
*** p<0.01, ** p<0.05, * p<0.10 (Welch t-test).
|
Saved: Table_Desc_NoRunRegion.tex
Panic Zone (B:33,
NB:69)
Panic Zone: Borrower (N=33) vs. NonBorrower (N=69)
|
Variable
|
Borrower Mean (SD)
|
NonBorrower Mean (SD)
|
Difference
|
t-stat
|
|
Log(Assets)
|
13.757 (1.367)
|
12.732 (1.038)
|
1.025***
|
3.81
|
|
Cash / TA
|
3.566 (2.837)
|
5.819 (4.350)
|
-2.253***
|
-3.13
|
|
Securities / TA
|
35.560 (12.510)
|
39.289 (17.205)
|
-3.729
|
-1.24
|
|
Loans / TA
|
55.722 (13.006)
|
49.192 (18.838)
|
6.530**
|
2.04
|
|
Book Equity / TA (e)
|
5.545 (1.420)
|
5.293 (1.575)
|
0.252
|
0.81
|
|
ROA
|
0.950 (0.364)
|
0.947 (0.478)
|
0.003
|
0.03
|
|
FHLB / TA
|
2.865 (3.705)
|
2.222 (3.356)
|
0.643
|
0.85
|
|
Wholesale (%)
|
1.818 (3.753)
|
0.668 (1.600)
|
1.150*
|
1.69
|
|
Loan / Deposit
|
63.623 (18.243)
|
54.280 (21.847)
|
9.343**
|
2.27
|
|
Total MTM (l)
|
8.414 (1.314)
|
7.786 (1.415)
|
0.628**
|
2.20
|
|
Securities MTM (l_S)
|
4.117 (1.424)
|
4.213 (1.716)
|
-0.096
|
-0.30
|
|
Loan MTM (l_L)
|
4.297 (1.821)
|
3.574 (2.226)
|
0.723*
|
1.74
|
|
BTFP-elig MTM (OMO)
|
1.379 (1.266)
|
0.818 (0.888)
|
0.561**
|
2.29
|
|
Non-BTFP MTM
|
6.531 (2.064)
|
6.197 (2.001)
|
0.334
|
0.77
|
|
Deposit Cost (c)
|
0.011 (0.000)
|
0.011 (0.000)
|
0.000
|
NA
|
|
Net Spread (r-c)
|
0.016 (0.002)
|
0.015 (0.003)
|
0.001
|
0.92
|
|
Uninsured/TA (mu)
|
32.427 (8.349)
|
30.845 (15.312)
|
1.582
|
0.67
|
|
Insured/TA
|
56.382 (8.774)
|
60.625 (14.607)
|
-4.243*
|
-1.82
|
|
D^U/D
|
36.452 (9.416)
|
33.589 (16.118)
|
2.863
|
1.13
|
|
Beta^U
|
0.287 (0.059)
|
0.300 (0.073)
|
-0.013
|
-0.92
|
|
Franchise (f)
|
3.685 (1.091)
|
3.365 (1.684)
|
0.320
|
1.15
|
|
MV Equity (E^MV)
|
0.816 (0.508)
|
0.871 (1.522)
|
-0.055
|
-0.27
|
|
Post-Run Equity
|
-0.611 (0.510)
|
-0.499 (0.591)
|
-0.112
|
-0.98
|
|
Solvency Thres.
|
9.230 (1.252)
|
8.657 (1.802)
|
0.573*
|
1.86
|
|
Liquidity Thres.
|
7.803 (1.281)
|
7.287 (1.536)
|
0.516*
|
1.78
|
|
Panic Width
|
1.426 (0.688)
|
1.370 (1.693)
|
0.056
|
0.24
|
|
OMO Collateral/TA (%)
|
12.423 (10.694)
|
10.295 (9.468)
|
2.128
|
0.98
|
|
Par Benefit
|
13.620 (14.948)
|
8.473 (4.743)
|
5.147*
|
1.93
|
|
*** p<0.01, ** p<0.05, * p<0.10 (Welch t-test).
|
Saved: Table_Desc_PanicZone.tex
Fundamental
Insolvency (B:88, NB:247)
Fundamental Insolvency: Borrower (N=88) vs. NonBorrower (N=247)
|
Variable
|
Borrower Mean (SD)
|
NonBorrower Mean (SD)
|
Difference
|
t-stat
|
|
Log(Assets)
|
13.185 (1.234)
|
12.581 (1.230)
|
0.604***
|
3.95
|
|
Cash / TA
|
4.083 (3.999)
|
5.143 (4.587)
|
-1.060**
|
-2.05
|
|
Securities / TA
|
40.077 (14.801)
|
38.366 (16.736)
|
1.711
|
0.90
|
|
Loans / TA
|
50.590 (16.501)
|
51.140 (16.997)
|
-0.550
|
-0.27
|
|
Book Equity / TA (e)
|
4.408 (1.765)
|
4.824 (1.965)
|
-0.416*
|
-1.84
|
|
ROA
|
0.918 (0.405)
|
0.827 (0.560)
|
0.091
|
1.62
|
|
FHLB / TA
|
3.498 (4.068)
|
3.052 (4.854)
|
0.446
|
0.84
|
|
Wholesale (%)
|
2.103 (4.180)
|
0.932 (2.133)
|
1.171**
|
2.51
|
|
Loan / Deposit
|
57.418 (20.311)
|
57.180 (20.796)
|
0.238
|
0.09
|
|
Total MTM (l)
|
8.198 (1.263)
|
8.510 (1.428)
|
-0.312*
|
-1.92
|
|
Securities MTM (l_S)
|
4.566 (1.569)
|
4.562 (2.064)
|
0.004
|
0.01
|
|
Loan MTM (l_L)
|
3.633 (1.830)
|
3.948 (2.309)
|
-0.315
|
-1.29
|
|
BTFP-elig MTM (OMO)
|
1.257 (1.431)
|
1.066 (1.157)
|
0.191
|
1.12
|
|
Non-BTFP MTM
|
6.349 (1.807)
|
7.049 (2.016)
|
-0.700***
|
-3.02
|
|
Deposit Cost (c)
|
0.011 (0.000)
|
0.011 (0.000)
|
0.000
|
NA
|
|
Net Spread (r-c)
|
0.013 (0.003)
|
0.014 (0.003)
|
-0.001
|
-1.32
|
|
Uninsured/TA (mu)
|
21.799 (9.123)
|
19.024 (9.012)
|
2.775**
|
2.46
|
|
Insured/TA
|
67.639 (9.537)
|
71.756 (9.426)
|
-4.117***
|
-3.49
|
|
D^U/D
|
24.330 (9.860)
|
20.900 (9.522)
|
3.430***
|
2.83
|
|
Beta^U
|
0.345 (0.093)
|
0.330 (0.092)
|
0.015
|
1.32
|
|
Franchise (f)
|
2.127 (1.067)
|
1.950 (1.053)
|
0.177
|
1.34
|
|
MV Equity (E^MV)
|
-1.663 (1.470)
|
-1.736 (1.466)
|
0.073
|
0.40
|
|
Post-Run Equity
|
-2.263 (1.490)
|
-2.227 (1.468)
|
-0.036
|
-0.20
|
|
Solvency Thres.
|
6.536 (1.750)
|
6.774 (1.711)
|
-0.238
|
-1.11
|
|
Liquidity Thres.
|
5.935 (1.696)
|
6.283 (1.735)
|
-0.348
|
-1.64
|
|
Panic Width
|
0.601 (0.534)
|
0.491 (0.476)
|
0.110*
|
1.69
|
|
OMO Collateral/TA (%)
|
12.376 (11.671)
|
10.673 (10.432)
|
1.703
|
1.21
|
|
Par Benefit
|
10.214 (4.805)
|
10.223 (5.176)
|
-0.009
|
-0.01
|
|
*** p<0.01, ** p<0.05, * p<0.10 (Welch t-test).
|
Saved: Table_Desc_FundamentalInsolvency.tex
Full Sample
df_fb <- df_crisis_clean %>% filter(any_fed==1); df_fn <- df_crisis_clean %>% filter(any_fed==0)
d <- build_desc_table(df_fb,df_fn,"FedBorrower","NonBorrower",desc_vars_all,desc_labels_all)
display_desc_table(d,"FedBorrower","NonBorrower",nrow(df_fb),nrow(df_fn),"Full Sample")
Full Sample: FedBorrower (N=828) vs. NonBorrower (N=3423)
|
Variable
|
FedBorrower Mean (SD)
|
NonBorrower Mean (SD)
|
Difference
|
t-stat
|
|
Log(Assets)
|
13.763 (1.524)
|
12.698 (1.371)
|
1.065***
|
18.39
|
|
Cash / TA
|
5.256 (5.513)
|
8.623 (9.154)
|
-3.367***
|
-13.61
|
|
Securities / TA
|
26.472 (14.267)
|
25.260 (15.707)
|
1.212**
|
2.15
|
|
Loans / TA
|
62.470 (14.766)
|
60.027 (17.318)
|
2.443***
|
4.12
|
|
Book Equity / TA (e)
|
8.540 (3.158)
|
9.854 (6.136)
|
-1.314***
|
-8.65
|
|
ROA
|
1.092 (0.585)
|
1.058 (1.915)
|
0.034
|
0.89
|
|
FHLB / TA
|
3.624 (4.684)
|
2.441 (4.077)
|
1.183***
|
6.68
|
|
Wholesale (%)
|
1.530 (3.473)
|
0.872 (3.013)
|
0.658***
|
5.01
|
|
Loan / Deposit
|
73.960 (20.022)
|
70.558 (24.465)
|
3.402***
|
4.19
|
|
Total MTM (l)
|
5.951 (1.985)
|
5.386 (2.230)
|
0.565***
|
7.16
|
|
Securities MTM (l_S)
|
2.424 (1.666)
|
2.092 (1.774)
|
0.332***
|
5.08
|
|
Loan MTM (l_L)
|
3.526 (1.583)
|
3.294 (1.810)
|
0.232***
|
3.68
|
|
BTFP-elig MTM (OMO)
|
0.824 (0.932)
|
0.649 (0.822)
|
0.175***
|
4.93
|
|
Non-BTFP MTM
|
4.975 (1.852)
|
4.541 (2.074)
|
0.434***
|
5.90
|
|
Deposit Cost (c)
|
0.011 (0.000)
|
0.011 (0.000)
|
0.000
|
NA
|
|
Net Spread (r-c)
|
0.014 (0.004)
|
0.014 (0.004)
|
0.000***
|
-3.19
|
|
Uninsured/TA (mu)
|
27.149 (12.037)
|
22.956 (11.878)
|
4.193***
|
9.02
|
|
Insured/TA
|
58.687 (12.795)
|
63.165 (13.167)
|
-4.478***
|
-8.99
|
|
D^U/D
|
31.809 (14.167)
|
26.757 (13.839)
|
5.052***
|
9.25
|
|
Beta^U
|
0.342 (0.115)
|
0.329 (0.104)
|
0.013***
|
3.15
|
|
Franchise (f)
|
2.713 (1.547)
|
2.377 (1.478)
|
0.336***
|
5.64
|
|
MV Equity (E^MV)
|
5.362 (4.309)
|
6.843 (6.827)
|
-1.481***
|
-7.77
|
|
Post-Run Equity
|
4.346 (4.062)
|
6.046 (6.712)
|
-1.700***
|
-9.32
|
|
Solvency Thres.
|
11.298 (3.313)
|
12.227 (6.023)
|
-0.929***
|
-6.00
|
|
Liquidity Thres.
|
10.281 (3.041)
|
11.430 (5.943)
|
-1.149***
|
-7.81
|
|
Panic Width
|
1.016 (1.022)
|
0.797 (0.957)
|
0.219***
|
5.59
|
|
OMO Collateral/TA (%)
|
10.639 (9.933)
|
10.633 (10.254)
|
0.006
|
0.02
|
|
Par Benefit
|
8.706 (10.971)
|
10.758 (213.462)
|
-2.052
|
-0.56
|
|
*** p<0.01, ** p<0.05, * p<0.10 (Welch t-test).
|
save_desc_latex(d,"FedBorrower","NonBorrower",nrow(df_fb),nrow(df_fn),"Full Sample","Table_Desc_FullSample")
## Saved: Table_Desc_FullSample.tex
By Facility
for (pair in list(
list("BTFP_Only", quote(btfp_crisis==1 & dw_crisis==0), "Non_Borrower", quote(any_fed==0)),
list("DW_Only", quote(dw_crisis==1 & btfp_crisis==0), "Non_Borrower", quote(any_fed==0)),
list("Both", quote(btfp_crisis==1 & dw_crisis==1), "Non_Borrower", quote(any_fed==0))
)) {
df_a <- df_crisis_model %>% filter(!!pair[[2]]); df_b <- df_crisis_model %>% filter(!!pair[[4]])
if (nrow(df_a)>=5 & nrow(df_b)>=5) {
d <- build_desc_table(df_a,df_b,pair[[1]],pair[[3]],desc_vars_all,desc_labels_all)
cat(sprintf("\n### %s vs %s\n\n", pair[[1]], pair[[3]]))
print(display_desc_table(d,pair[[1]],pair[[3]],nrow(df_a),nrow(df_b),pair[[1]]))
save_desc_latex(d,pair[[1]],pair[[3]],nrow(df_a),nrow(df_b),pair[[1]],
sprintf("Table_Desc_%s",pair[[1]]))
cat("\n")
}
}
BTFP_Only vs
Non_Borrower
BTFP_Only: BTFP_Only (N=392) vs. Non_Borrower (N=3404)
|
Variable
|
BTFP_Only Mean (SD)
|
Non_Borrower Mean (SD)
|
Difference
|
t-stat
|
|
Log(Assets)
|
13.497 (1.414)
|
12.701 (1.367)
|
0.796***
|
10.59
|
|
Cash / TA
|
4.677 (4.539)
|
8.619 (9.161)
|
-3.942***
|
-14.18
|
|
Securities / TA
|
28.259 (13.597)
|
25.208 (15.611)
|
3.051***
|
4.14
|
|
Loans / TA
|
61.356 (13.961)
|
60.114 (17.223)
|
1.242
|
1.63
|
|
Book Equity / TA (e)
|
8.218 (3.138)
|
9.851 (6.096)
|
-1.633***
|
-8.60
|
|
ROA
|
1.060 (0.541)
|
1.060 (1.919)
|
0.000
|
0.00
|
|
FHLB / TA
|
3.860 (4.600)
|
2.440 (4.076)
|
1.420***
|
5.85
|
|
Wholesale (%)
|
1.497 (3.327)
|
0.868 (3.004)
|
0.629***
|
3.58
|
|
Loan / Deposit
|
72.385 (18.978)
|
70.645 (24.382)
|
1.740*
|
1.66
|
|
Total MTM (l)
|
6.165 (1.955)
|
5.384 (2.223)
|
0.781***
|
7.37
|
|
Securities MTM (l_S)
|
2.608 (1.665)
|
2.083 (1.750)
|
0.525***
|
5.88
|
|
Loan MTM (l_L)
|
3.557 (1.561)
|
3.301 (1.811)
|
0.256***
|
3.02
|
|
BTFP-elig MTM (OMO)
|
0.850 (0.903)
|
0.649 (0.819)
|
0.201***
|
4.21
|
|
Non-BTFP MTM
|
5.063 (1.835)
|
4.542 (2.074)
|
0.521***
|
5.25
|
|
Deposit Cost (c)
|
0.011 (0.000)
|
0.011 (0.000)
|
0.000
|
NA
|
|
Net Spread (r-c)
|
0.014 (0.004)
|
0.014 (0.004)
|
0.000
|
-1.55
|
|
Uninsured/TA (mu)
|
26.173 (11.526)
|
22.944 (11.821)
|
3.229***
|
5.24
|
|
Insured/TA
|
59.850 (12.078)
|
63.195 (13.111)
|
-3.345***
|
-5.15
|
|
D^U/D
|
30.539 (13.375)
|
26.750 (13.805)
|
3.789***
|
5.29
|
|
Beta^U
|
0.337 (0.105)
|
0.329 (0.104)
|
0.008
|
1.52
|
|
Franchise (f)
|
2.670 (1.540)
|
2.377 (1.478)
|
0.293***
|
3.59
|
|
MV Equity (E^MV)
|
4.724 (4.302)
|
6.843 (6.827)
|
-2.119***
|
-8.59
|
|
Post-Run Equity
|
3.754 (4.044)
|
6.046 (6.712)
|
-2.292***
|
-9.78
|
|
Solvency Thres.
|
10.888 (3.311)
|
12.227 (6.023)
|
-1.339***
|
-6.81
|
|
Liquidity Thres.
|
9.919 (3.057)
|
11.430 (5.943)
|
-1.511***
|
-8.17
|
|
Panic Width
|
0.969 (1.019)
|
0.797 (0.957)
|
0.172***
|
3.20
|
|
OMO Collateral/TA (%)
|
11.437 (10.334)
|
10.640 (10.259)
|
0.797
|
1.45
|
|
Par Benefit
|
8.913 (14.824)
|
10.774 (214.057)
|
-1.861
|
-0.50
|
|
*** p<0.01, ** p<0.05, * p<0.10 (Welch t-test).
|
Saved: Table_Desc_BTFP_Only.tex
DW_Only vs
Non_Borrower
DW_Only: DW_Only (N=326) vs. Non_Borrower (N=3404)
|
Variable
|
DW_Only Mean (SD)
|
Non_Borrower Mean (SD)
|
Difference
|
t-stat
|
|
Log(Assets)
|
13.832 (1.464)
|
12.701 (1.367)
|
1.131***
|
13.41
|
|
Cash / TA
|
6.166 (6.530)
|
8.619 (9.161)
|
-2.453***
|
-6.22
|
|
Securities / TA
|
23.558 (14.063)
|
25.208 (15.611)
|
-1.650**
|
-2.00
|
|
Loans / TA
|
64.314 (15.204)
|
60.114 (17.223)
|
4.200***
|
4.71
|
|
Book Equity / TA (e)
|
9.124 (3.158)
|
9.851 (6.096)
|
-0.727***
|
-3.57
|
|
ROA
|
1.134 (0.649)
|
1.060 (1.919)
|
0.074
|
1.51
|
|
FHLB / TA
|
3.244 (4.362)
|
2.440 (4.076)
|
0.804***
|
3.20
|
|
Wholesale (%)
|
1.489 (3.829)
|
0.868 (3.004)
|
0.621***
|
2.85
|
|
Loan / Deposit
|
76.302 (20.748)
|
70.645 (24.382)
|
5.657***
|
4.63
|
|
Total MTM (l)
|
5.710 (2.015)
|
5.384 (2.223)
|
0.326***
|
2.76
|
|
Securities MTM (l_S)
|
2.112 (1.543)
|
2.083 (1.750)
|
0.029
|
0.32
|
|
Loan MTM (l_L)
|
3.599 (1.646)
|
3.301 (1.811)
|
0.298***
|
3.09
|
|
BTFP-elig MTM (OMO)
|
0.728 (0.829)
|
0.649 (0.819)
|
0.079
|
1.64
|
|
Non-BTFP MTM
|
4.926 (1.891)
|
4.542 (2.074)
|
0.384***
|
3.48
|
|
Deposit Cost (c)
|
0.011 (0.000)
|
0.011 (0.000)
|
0.000
|
NA
|
|
Net Spread (r-c)
|
0.014 (0.004)
|
0.014 (0.004)
|
0.000**
|
-1.99
|
|
Uninsured/TA (mu)
|
26.737 (11.748)
|
22.944 (11.821)
|
3.793***
|
5.57
|
|
Insured/TA
|
59.006 (12.709)
|
63.195 (13.111)
|
-4.189***
|
-5.67
|
|
D^U/D
|
31.457 (13.973)
|
26.750 (13.805)
|
4.707***
|
5.82
|
|
Beta^U
|
0.341 (0.113)
|
0.329 (0.104)
|
0.012*
|
1.96
|
|
Franchise (f)
|
2.677 (1.495)
|
2.377 (1.478)
|
0.300***
|
3.46
|
|
MV Equity (E^MV)
|
6.090 (4.341)
|
6.843 (6.827)
|
-0.753***
|
-2.81
|
|
Post-Run Equity
|
5.107 (4.101)
|
6.046 (6.712)
|
-0.939***
|
-3.69
|
|
Solvency Thres.
|
11.801 (3.386)
|
12.227 (6.023)
|
-0.426**
|
-1.99
|
|
Liquidity Thres.
|
10.817 (3.108)
|
11.430 (5.943)
|
-0.613***
|
-3.07
|
|
Panic Width
|
0.984 (0.946)
|
0.797 (0.957)
|
0.187***
|
3.40
|
|
OMO Collateral/TA (%)
|
9.271 (8.943)
|
10.640 (10.259)
|
-1.369***
|
-2.60
|
|
Par Benefit
|
8.545 (5.954)
|
10.774 (214.057)
|
-2.229
|
-0.61
|
|
*** p<0.01, ** p<0.05, * p<0.10 (Welch t-test).
|
Saved: Table_Desc_DW_Only.tex
Both vs
Non_Borrower
Both: Both (N=104) vs. Non_Borrower (N=3404)
|
Variable
|
Both Mean (SD)
|
Non_Borrower Mean (SD)
|
Difference
|
t-stat
|
|
Log(Assets)
|
14.615 (1.781)
|
12.701 (1.367)
|
1.914***
|
10.87
|
|
Cash / TA
|
4.549 (5.080)
|
8.619 (9.161)
|
-4.070***
|
-7.79
|
|
Securities / TA
|
27.273 (14.419)
|
25.208 (15.611)
|
2.065
|
1.43
|
|
Loans / TA
|
62.394 (14.576)
|
60.114 (17.223)
|
2.280
|
1.56
|
|
Book Equity / TA (e)
|
8.276 (2.597)
|
9.851 (6.096)
|
-1.575***
|
-5.72
|
|
ROA
|
1.098 (0.539)
|
1.060 (1.919)
|
0.038
|
0.61
|
|
FHLB / TA
|
4.112 (5.851)
|
2.440 (4.076)
|
1.672***
|
2.89
|
|
Wholesale (%)
|
1.774 (2.859)
|
0.868 (3.004)
|
0.906***
|
3.18
|
|
Loan / Deposit
|
74.584 (19.397)
|
70.645 (24.382)
|
3.939**
|
2.02
|
|
Total MTM (l)
|
5.779 (1.864)
|
5.384 (2.223)
|
0.395**
|
2.12
|
|
Securities MTM (l_S)
|
2.491 (1.623)
|
2.083 (1.750)
|
0.408**
|
2.52
|
|
Loan MTM (l_L)
|
3.289 (1.418)
|
3.301 (1.811)
|
-0.012
|
-0.09
|
|
BTFP-elig MTM (OMO)
|
0.931 (0.956)
|
0.649 (0.819)
|
0.282***
|
2.97
|
|
Non-BTFP MTM
|
4.870 (1.750)
|
4.542 (2.074)
|
0.328*
|
1.87
|
|
Deposit Cost (c)
|
0.011 (0.000)
|
0.011 (0.000)
|
0.000
|
NA
|
|
Net Spread (r-c)
|
0.013 (0.005)
|
0.014 (0.004)
|
-0.001**
|
-2.55
|
|
Uninsured/TA (mu)
|
31.989 (13.857)
|
22.944 (11.821)
|
9.045***
|
6.58
|
|
Insured/TA
|
52.903 (14.357)
|
63.195 (13.111)
|
-10.292***
|
-7.22
|
|
D^U/D
|
37.760 (16.435)
|
26.750 (13.805)
|
11.010***
|
6.76
|
|
Beta^U
|
0.366 (0.148)
|
0.329 (0.104)
|
0.037**
|
2.54
|
|
Franchise (f)
|
2.988 (1.713)
|
2.377 (1.478)
|
0.611***
|
3.60
|
|
MV Equity (E^MV)
|
5.485 (3.874)
|
6.843 (6.827)
|
-1.358***
|
-3.41
|
|
Post-Run Equity
|
4.190 (3.631)
|
6.046 (6.712)
|
-1.856***
|
-4.96
|
|
Solvency Thres.
|
11.265 (2.869)
|
12.227 (6.023)
|
-0.962***
|
-3.21
|
|
Liquidity Thres.
|
9.969 (2.502)
|
11.430 (5.943)
|
-1.461***
|
-5.50
|
|
Panic Width
|
1.295 (1.214)
|
0.797 (0.957)
|
0.498***
|
4.15
|
|
OMO Collateral/TA (%)
|
11.284 (9.575)
|
10.640 (10.259)
|
0.644
|
0.67
|
|
Par Benefit
|
8.484 (4.345)
|
10.774 (214.057)
|
-2.290
|
-0.62
|
|
*** p<0.01, ** p<0.05, * p<0.10 (Welch t-test).
|
Saved: Table_Desc_Both.tex
PREDICTION 1: FUNDAMENTAL VS. PANIC
Theory claim: “If runs were driven by coordination
failure, borrowers should be disproportionately panic-zone banks. […]
Under the coordination interpretation, MTM losses and uninsured leverage
are jointly elevated among borrowers.”
7.1 Borrowing Rates
by Region
region_borrow <- df_crisis_model %>%
group_by(model_region) %>%
summarise(N=n(),
AnyFed_N=sum(any_fed), AnyFed_pct=round(100*mean(any_fed),2),
BTFP_N=sum(btfp_crisis), BTFP_pct=round(100*mean(btfp_crisis),2),
DW_N=sum(dw_crisis), DW_pct=round(100*mean(dw_crisis),2),
FHLB_N=sum(fhlb_user), FHLB_pct=round(100*mean(fhlb_user),2), .groups="drop")
p1_rates_disp <- region_borrow %>%
mutate(AnyFed=sprintf("%d (%.1f%%)",AnyFed_N,AnyFed_pct),
BTFP=sprintf("%d (%.1f%%)",BTFP_N,BTFP_pct),
DW=sprintf("%d (%.1f%%)",DW_N,DW_pct),
FHLB=sprintf("%d (%.1f%%)",FHLB_N,FHLB_pct)) %>%
select(model_region,N,AnyFed,BTFP,DW,FHLB)
kbl(p1_rates_disp, format="html", col.names=c("Region","N","Any Fed","BTFP","DW","FHLB"),
caption="P1: Borrowing Rates by Theory Region") %>%
kable_styling(bootstrap_options=c("striped","hover","condensed"), full_width=FALSE)
P1: Borrowing Rates by Theory Region
|
Region
|
N
|
Any Fed
|
BTFP
|
DW
|
FHLB
|
|
No-Run Region
|
3789
|
701 (18.5%)
|
408 (10.8%)
|
383 (10.1%)
|
274 (7.2%)
|
|
Panic Zone
|
102
|
33 (32.4%)
|
24 (23.5%)
|
12 (11.8%)
|
6 (5.9%)
|
|
Fundamental Insolvency
|
335
|
88 (26.3%)
|
64 (19.1%)
|
35 (10.4%)
|
19 (5.7%)
|
# --- LaTeX export ---
save_kbl_latex(p1_rates_disp, "Table_P1_BorrowRates",
col.names=c("Region","N","Any Fed","BTFP","DW","FHLB"),
caption="Borrowing Rates by Theory Region")
## Saved: Table_P1_BorrowRates.tex
7.2 Formal
Proportion Tests
cat("=== P1: PROPORTION TESTS ===\n\n")
## === P1: PROPORTION TESTS ===
# Chi-squared across all three regions
tbl_chi <- table(df_crisis_model$model_region, df_crisis_model$any_fed)
chi <- chisq.test(tbl_chi, correct=FALSE)
cat(sprintf("Chi-squared (3 regions): X2=%.2f, df=%d, p=%.6f\n", chi$statistic, chi$parameter, chi$p.value))
## Chi-squared (3 regions): X2=22.96, df=2, p=0.000010
# Pairwise: Panic vs Fund.Ins (the KEY P1 comparison)
pz_d <- df_crisis_model %>% filter(model_region=="Panic Zone")
fi_d <- df_crisis_model %>% filter(model_region=="Fundamental Insolvency")
nr_d <- df_crisis_model %>% filter(model_region=="No-Run Region")
pz_rate <- mean(pz_d$any_fed); fi_rate <- mean(fi_d$any_fed); nr_rate <- mean(nr_d$any_fed)
cat(sprintf("\nBorrowing rates: No-Run=%.2f%%, Panic=%.2f%%, Fund.Ins.=%.2f%%\n",
nr_rate*100, pz_rate*100, fi_rate*100))
##
## Borrowing rates: No-Run=18.50%, Panic=32.35%, Fund.Ins.=26.27%
# Two-sample proportion test: Panic > Fund.Ins.?
pt_pf <- prop.test(x=c(sum(pz_d$any_fed), sum(fi_d$any_fed)),
n=c(nrow(pz_d), nrow(fi_d)), alternative="greater")
cat(sprintf("Panic > Fund.Ins.: p=%.4f %s\n", pt_pf$p.value,
ifelse(pt_pf$p.value<0.05, "(SIGNIFICANT: panic zone dominates)","(not significant)")))
## Panic > Fund.Ins.: p=0.1410 (not significant)
# Fisher exact: Panic vs Fund.Ins.
tbl_pf <- table(
df_crisis_model$model_region[df_crisis_model$model_region %in% c("Panic Zone","Fundamental Insolvency")],
df_crisis_model$any_fed[df_crisis_model$model_region %in% c("Panic Zone","Fundamental Insolvency")])
if (nrow(tbl_pf)==2 & ncol(tbl_pf)==2) {
ft <- fisher.test(tbl_pf)
cat(sprintf("Fisher exact (Panic vs FI): OR=%.2f, p=%.4f\n", ft$estimate, ft$p.value))
}
# Panic > No-Run?
pt_pn <- prop.test(x=c(sum(pz_d$any_fed), sum(nr_d$any_fed)),
n=c(nrow(pz_d), nrow(nr_d)), alternative="greater")
cat(sprintf("Panic > No-Run: p=%.4f\n", pt_pn$p.value))
## Panic > No-Run: p=0.0003
7.3 Joint Elevation
Test
Theory: “Under the coordination interpretation, MTM
losses and uninsured leverage are jointly elevated
among borrowers, because it is the combination that places a bank in the
panic zone.”
cat("=== P1: JOINT ELEVATION OF l AND mu AMONG BORROWERS ===\n\n")
## === P1: JOINT ELEVATION OF l AND mu AMONG BORROWERS ===
# Bivariate test: borrowers have higher (ℓ, μ) jointly
cat("--- Univariate comparisons ---\n")
## --- Univariate comparisons ---
for (v in c("mtm_total_raw","uninsured_lev_raw","f_pp")) {
b_m <- mean(df_crisis_model[[v]][df_crisis_model$any_fed==1], na.rm=T)
n_m <- mean(df_crisis_model[[v]][df_crisis_model$any_fed==0], na.rm=T)
tt <- t.test(df_crisis_model[[v]][df_crisis_model$any_fed==1],
df_crisis_model[[v]][df_crisis_model$any_fed==0])
cat(sprintf(" %-20s Borrower=%.3f Non=%.3f Diff=%.3f (p=%.4f)\n",
v, b_m, n_m, b_m-n_m, tt$p.value))
}
## mtm_total_raw Borrower=5.936 Non=5.384 Diff=0.551 (p=0.0000)
## uninsured_lev_raw Borrower=27.133 Non=22.944 Diff=4.189 (p=0.0000)
## f_pp Borrower=2.713 Non=2.377 Diff=0.336 (p=0.0000)
# Regression P1: fundamental vs coordination cross-sectional signatures
# Col 1: losses alone (fundamental channel)
# Col 2: + uninsured leverage (coordination starts)
# Col 3: + interaction (full coordination)
controls <- c("ln_assets","cash_ratio","loan_to_deposit","book_equity_ratio","wholesale","roa")
ctrl_str <- paste(controls, collapse=" + ")
cat("\n--- P1 Progressive Regression: Fundamental vs Coordination Signatures ---\n")
##
## --- P1 Progressive Regression: Fundamental vs Coordination Signatures ---
reg_p1 <- list(
"Fundamental" = feols(as.formula(paste0("any_fed ~ mtm_total + ", ctrl_str)),
data=df_crisis_model, vcov="hetero"),
"+Leverage" = feols(as.formula(paste0("any_fed ~ mtm_total + uninsured_lev + ", ctrl_str)),
data=df_crisis_model, vcov="hetero"),
"+Interaction" = feols(as.formula(paste0("any_fed ~ mtm_total + uninsured_lev + mtm_x_uninsured + ", ctrl_str)),
data=df_crisis_model, vcov="hetero"),
"Eq.11 (f spec)" = feols(as.formula(paste0("any_fed ~ mtm_total + franchise_value + mtm_x_franchise + ", ctrl_str)),
data=df_crisis_model, vcov="hetero")
)
msummary(reg_p1, stars=c("*"=.10,"**"=.05,"***"=.01), gof_omit="AIC|BIC|Log|RMSE",
add_rows=dv_summary_rows(reg_p1),
coef_rename=c("mtm_total"="l (MTM)","uninsured_lev"="mu (Uninsured/TA)",
"mtm_x_uninsured"="l x mu","franchise_value"="f (Franchise)",
"mtm_x_franchise"="l x f","ln_assets"="Log(Assets)","cash_ratio"="Cash/TA",
"loan_to_deposit"="Loan/Dep","book_equity_ratio"="Equity/TA",
"wholesale"="Wholesale","roa"="ROA"),
title="P1: Fundamental (losses alone) vs Coordination (losses + leverage + interaction)")
P1: Fundamental (losses alone) vs Coordination (losses + leverage + interaction)
| |
Fundamental |
+Leverage |
+Interaction |
Eq.11 (f spec) |
| * p < 0.1, ** p < 0.05, *** p < 0.01 |
| (Intercept) |
0.192*** |
0.192*** |
0.194*** |
0.193*** |
|
(0.006) |
(0.006) |
(0.006) |
(0.006) |
| l (MTM) |
0.022*** |
0.025*** |
0.029*** |
0.027*** |
|
(0.006) |
(0.006) |
(0.007) |
(0.006) |
| Log(Assets) |
0.109*** |
0.102*** |
0.102*** |
0.105*** |
|
(0.007) |
(0.008) |
(0.008) |
(0.007) |
| Cash/TA |
-0.022*** |
-0.023*** |
-0.021*** |
-0.021*** |
|
(0.006) |
(0.006) |
(0.006) |
(0.006) |
| Loan/Dep |
-0.010 |
-0.008 |
-0.005 |
-0.005 |
|
(0.007) |
(0.007) |
(0.007) |
(0.007) |
| Equity/TA |
-0.021*** |
-0.019*** |
-0.016*** |
-0.018*** |
|
(0.006) |
(0.006) |
(0.006) |
(0.006) |
| Wholesale |
0.028*** |
0.029*** |
0.029*** |
0.029*** |
|
(0.007) |
(0.007) |
(0.007) |
(0.007) |
| ROA |
0.000 |
-0.001 |
-0.002 |
-0.002 |
|
(0.006) |
(0.006) |
(0.006) |
(0.006) |
| mu (Uninsured/TA) |
|
0.015** |
0.018** |
|
|
|
(0.007) |
(0.007) |
|
| l x mu |
|
|
0.019*** |
|
|
|
|
(0.006) |
|
| f (Franchise) |
|
|
|
0.014** |
|
|
|
|
(0.007) |
| l x f |
|
|
|
0.017*** |
|
|
|
|
(0.006) |
| Num.Obs. |
4226 |
4226 |
4226 |
4226 |
| R2 |
0.107 |
0.108 |
0.110 |
0.109 |
| R2 Adj. |
0.105 |
0.106 |
0.108 |
0.108 |
| Std.Errors |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
| N (DV = 1) |
822 |
822 |
822 |
822 |
| Mean (DV) |
0.1945 |
0.1945 |
0.1945 |
0.1945 |
# --- LaTeX export ---
save_reg_latex(reg_p1, "Table_P1_Progressive",
add_rows=dv_summary_rows(reg_p1),
coef_rename=c("mtm_total"="$\\ell$","uninsured_lev"="$\\mu$",
"mtm_x_uninsured"="$\\ell \\times \\mu$","franchise_value"="$f$",
"mtm_x_franchise"="$\\ell \\times f$","ln_assets"="Log(Assets)","cash_ratio"="Cash/TA",
"loan_to_deposit"="Loan/Dep","book_equity_ratio"="Equity/TA",
"wholesale"="Wholesale","roa"="ROA"),
title="Fundamental vs.\\ Coordination Signatures")
## Saved: Table_P1_Progressive.tex
7.4 Region Indicator
Regressions
# Which region predicts borrowing? Panic zone should dominate.
reg_p1r <- list(
"Indicators" = feols(as.formula(paste0("any_fed ~ in_panic + in_fund_ins + ", ctrl_str)),
data=df_crisis_model, vcov="hetero"),
"Indicators+BTFP" = feols(as.formula(paste0("btfp_crisis ~ in_panic + in_fund_ins + ", ctrl_str)),
data=df_crisis_model, vcov="hetero"),
"Indicators+DW" = feols(as.formula(paste0("dw_crisis ~ in_panic + in_fund_ins + ", ctrl_str)),
data=df_crisis_model, vcov="hetero")
)
msummary(reg_p1r, stars=c("*"=.10,"**"=.05,"***"=.01), gof_omit="AIC|BIC|Log|RMSE",
add_rows=dv_summary_rows(reg_p1r),
coef_rename=c("in_panic"="Panic Zone (indicator)","in_fund_ins"="Fund. Insolvency (indicator)"),
title="P1: Region Indicators Predicting Borrowing (No-Run = reference)",
notes="Theory predicts: Panic Zone > 0, Fund. Insolvency smaller or zero (coordination, not fundamentals).")
P1: Region Indicators Predicting Borrowing (No-Run = reference)
| |
Indicators |
Indicators+BTFP |
Indicators+DW |
| * p < 0.1, ** p < 0.05, *** p < 0.01 |
| Theory predicts: Panic Zone > 0, Fund. Insolvency smaller or zero (coordination, not fundamentals). |
| (Intercept) |
0.186*** |
0.110*** |
0.100*** |
|
(0.006) |
(0.005) |
(0.005) |
| Panic Zone (indicator) |
0.085* |
0.080* |
-0.002 |
|
(0.045) |
(0.042) |
(0.031) |
| Fund. Insolvency (indicator) |
0.045* |
0.045** |
0.002 |
|
(0.025) |
(0.023) |
(0.018) |
| ln_assets |
0.110*** |
0.061*** |
0.078*** |
|
(0.007) |
(0.006) |
(0.006) |
| cash_ratio |
-0.030*** |
-0.030*** |
-0.003 |
|
(0.006) |
(0.004) |
(0.005) |
| loan_to_deposit |
-0.011* |
-0.014** |
-0.004 |
|
(0.007) |
(0.005) |
(0.005) |
| book_equity_ratio |
-0.018*** |
-0.016*** |
-0.005 |
|
(0.006) |
(0.005) |
(0.004) |
| wholesale |
0.028*** |
0.021*** |
0.015*** |
|
(0.007) |
(0.006) |
(0.005) |
| roa |
-0.003 |
-0.005 |
-0.001 |
|
(0.006) |
(0.005) |
(0.005) |
| Num.Obs. |
4226 |
4226 |
4226 |
| R2 |
0.106 |
0.066 |
0.071 |
| R2 Adj. |
0.104 |
0.064 |
0.069 |
| Std.Errors |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
| N (DV = 1) |
822 |
496 |
430 |
| Mean (DV) |
0.1945 |
0.1174 |
0.1018 |
# --- LaTeX export ---
save_reg_latex(reg_p1r, "Table_P1_RegionIndicators",
add_rows=dv_summary_rows(reg_p1r),
coef_rename=c("in_panic"="Panic Zone","in_fund_ins"="Fund.\\ Insolvency"),
title="Region Indicators Predicting Borrowing",
notes="No-Run Region is the omitted category.")
## Saved: Table_P1_RegionIndicators.tex
PREDICTION 2: STRATEGIC COMPLEMENTARITIES (GOLDSTEIN TEST)
Theory claim (Eq. 6/11): “\(c_3 > 0\) estimated within the panic
zone; the same interaction should be absent or substantially weaker
among fundamentally insolvent banks, where withdrawal is dominant
regardless of beliefs and the funding structure adds nothing to the
prediction.”
8.1 Main Table: Eq.
(11) Full & By DV
fml_main <- as.formula(paste0("any_fed ~ mtm_total + franchise_value + mtm_x_franchise + ", ctrl_str))
coef_map <- c("mtm_total"="l (MTM)","franchise_value"="f (Franchise)",
"mtm_x_franchise"="l x f","uninsured_lev"="mu (Uninsured/TA)",
"ln_assets"="Log(Assets)","cash_ratio"="Cash/TA",
"loan_to_deposit"="Loan/Dep","book_equity_ratio"="Equity/TA",
"wholesale"="Wholesale","roa"="ROA")
reg_main <- list(
"Any Fed" = feols(fml_main, data=df_crisis_model, vcov="hetero"),
"BTFP" = feols(update(fml_main,btfp_crisis~.), data=df_crisis_model, vcov="hetero"),
"DW" = feols(update(fml_main,dw_crisis~.), data=df_crisis_model, vcov="hetero")
)
msummary(reg_main, stars=c("*"=.10,"**"=.05,"***"=.01), gof_omit="AIC|BIC|Log|RMSE",
add_rows=dv_summary_rows(reg_main), coef_rename=coef_map,
title="P2: Eq. (11) — Full Sample, by Dependent Variable")
P2: Eq. (11) — Full Sample, by Dependent Variable
| |
Any Fed |
BTFP |
DW |
| * p < 0.1, ** p < 0.05, *** p < 0.01 |
| (Intercept) |
0.193*** |
0.116*** |
0.101*** |
|
(0.006) |
(0.005) |
(0.004) |
| l (MTM) |
0.027*** |
0.017*** |
0.010* |
|
(0.006) |
(0.005) |
(0.005) |
| f (Franchise) |
0.014** |
0.011* |
0.006 |
|
(0.007) |
(0.006) |
(0.006) |
| l x f |
0.017*** |
0.009* |
0.014*** |
|
(0.006) |
(0.005) |
(0.004) |
| Log(Assets) |
0.105*** |
0.056*** |
0.076*** |
|
(0.007) |
(0.006) |
(0.006) |
| Cash/TA |
-0.021*** |
-0.025*** |
0.001 |
|
(0.006) |
(0.005) |
(0.005) |
| Loan/Dep |
-0.005 |
-0.010* |
0.001 |
|
(0.007) |
(0.006) |
(0.006) |
| Equity/TA |
-0.018*** |
-0.017*** |
-0.002 |
|
(0.006) |
(0.005) |
(0.004) |
| Wholesale |
0.029*** |
0.022*** |
0.015*** |
|
(0.007) |
(0.006) |
(0.005) |
| ROA |
-0.002 |
-0.005 |
-0.002 |
|
(0.006) |
(0.005) |
(0.005) |
| Num.Obs. |
4226 |
4226 |
4226 |
| R2 |
0.109 |
0.067 |
0.074 |
| R2 Adj. |
0.108 |
0.065 |
0.072 |
| Std.Errors |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
| N (DV = 1) |
822 |
496 |
430 |
| Mean (DV) |
0.1945 |
0.1174 |
0.1018 |
# --- LaTeX export ---
save_reg_latex(reg_main, "Table_P2_MainEq11",
add_rows=dv_summary_rows(reg_main), coef_rename=coef_map,
title="Eq.\\ (11): Full Sample, by Dependent Variable")
## Saved: Table_P2_MainEq11.tex
8.2 Within-Region:
Panic Zone vs Fund. Insolvency
This is THE Goldstein test. \(c_3\) should be positive and significant in
the panic zone (coordination lives there), and absent or weaker in the
fund. insolvency region (withdrawal is dominant regardless of
beliefs).
df_pz <- df_crisis_model %>% filter(model_region=="Panic Zone")
df_fi <- df_crisis_model %>% filter(model_region=="Fundamental Insolvency")
df_nr <- df_crisis_model %>% filter(model_region=="No-Run Region")
# --- Panel A: Full-sample z-scores (for comparability with full-sample column) ---
reg_goldstein <- list()
reg_goldstein[["Full"]] <- feols(fml_main, data=df_crisis_model, vcov="hetero")
if (sum(df_pz$any_fed)>=10 & sum(df_pz$any_fed==0)>=10)
reg_goldstein[["Panic Zone"]] <- feols(fml_main, data=df_pz, vcov="hetero")
if (sum(df_fi$any_fed)>=10 & sum(df_fi$any_fed==0)>=10)
reg_goldstein[["Fund. Insolvency"]] <- feols(fml_main, data=df_fi, vcov="hetero")
if (sum(df_nr$any_fed)>=10 & sum(df_nr$any_fed==0)>=10)
reg_goldstein[["No-Run"]] <- feols(fml_main, data=df_nr, vcov="hetero")
msummary(reg_goldstein, stars=c("*"=.10,"**"=.05,"***"=.01), gof_omit="AIC|BIC|Log|RMSE",
add_rows=dv_summary_rows(reg_goldstein), coef_rename=coef_map,
title="P2 Goldstein Test: Eq. (11) Within Each Region (full-sample z-scores)",
notes="Theory: c3(l x f) > 0 in Panic Zone; c3 ≈ 0 in Fund. Insolvency. Uses full-sample z-scores.")
P2 Goldstein Test: Eq. (11) Within Each Region (full-sample z-scores)
| |
Full |
Panic Zone |
Fund. Insolvency |
No-Run |
| * p < 0.1, ** p < 0.05, *** p < 0.01 |
| Theory: c3(l x f) > 0 in Panic Zone; c3 ≈ 0 in Fund. Insolvency. Uses full-sample z-scores. |
| (Intercept) |
0.193*** |
0.269 |
0.233 |
0.189*** |
|
(0.006) |
(0.450) |
(0.144) |
(0.006) |
| l (MTM) |
0.027*** |
-0.005 |
-0.044 |
0.025*** |
|
(0.006) |
(0.180) |
(0.061) |
(0.007) |
| f (Franchise) |
0.014** |
0.051 |
-0.073 |
0.014* |
|
(0.007) |
(0.068) |
(0.095) |
(0.007) |
| l x f |
0.017*** |
0.044 |
0.073 |
0.015** |
|
(0.006) |
(0.061) |
(0.064) |
(0.006) |
| Log(Assets) |
0.105*** |
0.159** |
0.114*** |
0.103*** |
|
(0.007) |
(0.065) |
(0.032) |
(0.008) |
| Cash/TA |
-0.021*** |
-0.181** |
-0.065 |
-0.020*** |
|
(0.006) |
(0.086) |
(0.040) |
(0.006) |
| Loan/Dep |
-0.005 |
0.030 |
0.004 |
-0.005 |
|
(0.007) |
(0.064) |
(0.034) |
(0.007) |
| Equity/TA |
-0.018*** |
0.086 |
-0.062 |
-0.014** |
|
(0.006) |
(0.290) |
(0.076) |
(0.006) |
| Wholesale |
0.029*** |
0.022 |
0.045* |
0.025*** |
|
(0.007) |
(0.046) |
(0.024) |
(0.007) |
| ROA |
-0.002 |
-0.052 |
-0.023 |
-0.000 |
|
(0.006) |
(0.063) |
(0.027) |
(0.006) |
| Num.Obs. |
4226 |
102 |
335 |
3789 |
| R2 |
0.109 |
0.235 |
0.100 |
0.107 |
| R2 Adj. |
0.108 |
0.160 |
0.075 |
0.104 |
| Std.Errors |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
| N (DV = 1) |
822 |
33 |
88 |
701 |
| Mean (DV) |
0.1945 |
0.3235 |
0.2627 |
0.185 |
# --- LaTeX export ---
save_reg_latex(reg_goldstein, "Table_P2_Goldstein",
add_rows=dv_summary_rows(reg_goldstein), coef_rename=coef_map,
title="Goldstein Test: Eq.\\ (11) Within Each Region",
notes="$c_3(\\ell \\times f) > 0$ in Panic Zone; $c_3 \\approx 0$ in Fund.\\ Insolvency.")
## Saved: Table_P2_Goldstein.tex
# --- Panel B: Within-region restandardized z-scores ---
# Full-sample z-scores have shifted means within subsamples, which can distort
# the interaction coefficient. We restandardize within each region so that
# main effects are evaluated at within-region means.
restd_within <- function(df_sub) {
df_sub %>% mutate(
mtm_total_r = standardize_z(winsorize(mtm_total_raw)),
fv_r = standardize_z(winsorize(f_pp)),
mtm_x_fv_r = mtm_total_r * fv_r,
uninsured_lev_r = standardize_z(winsorize(uninsured_lev_raw)),
ln_assets_r = standardize_z(winsorize(ln_assets_raw)),
cash_ratio_r = standardize_z(winsorize(cash_ratio_raw)),
loan_to_deposit_r = standardize_z(winsorize(loan_to_deposit_raw)),
book_equity_ratio_r = standardize_z(winsorize(book_equity_ratio_raw)),
wholesale_r = standardize_z(winsorize(wholesale_raw)),
roa_r = standardize_z(winsorize(roa_raw))
)
}
fml_restd <- any_fed ~ mtm_total_r + fv_r + mtm_x_fv_r +
ln_assets_r + cash_ratio_r + loan_to_deposit_r + book_equity_ratio_r + wholesale_r + roa_r
reg_goldstein_restd <- list()
df_pz_r <- restd_within(df_pz)
df_fi_r <- restd_within(df_fi)
df_nr_r <- restd_within(df_nr)
if (sum(df_pz_r$any_fed)>=10 & sum(df_pz_r$any_fed==0)>=10)
reg_goldstein_restd[["PZ (restd)"]] <- feols(fml_restd, data=df_pz_r, vcov="hetero")
if (sum(df_fi_r$any_fed)>=10 & sum(df_fi_r$any_fed==0)>=10)
reg_goldstein_restd[["FI (restd)"]] <- feols(fml_restd, data=df_fi_r, vcov="hetero")
if (sum(df_nr_r$any_fed)>=10 & sum(df_nr_r$any_fed==0)>=10)
reg_goldstein_restd[["NR (restd)"]] <- feols(fml_restd, data=df_nr_r, vcov="hetero")
restd_map <- c("mtm_total_r"="l (MTM)","fv_r"="f (Franchise)","mtm_x_fv_r"="l x f",
"ln_assets_r"="Log(Assets)","cash_ratio_r"="Cash/TA","loan_to_deposit_r"="Loan/Dep",
"book_equity_ratio_r"="Equity/TA","wholesale_r"="Wholesale","roa_r"="ROA")
msummary(reg_goldstein_restd, stars=c("*"=.10,"**"=.05,"***"=.01), gof_omit="AIC|BIC|Log|RMSE",
add_rows=dv_summary_rows(reg_goldstein_restd), coef_rename=restd_map,
title="P2 Goldstein (within-region restandardized z-scores)",
notes=c("Variables z-standardized WITHIN each region so main effects are evaluated at within-region means.",
"This ensures l x f is not confounded by level differences in regressors across regions."))
P2 Goldstein (within-region restandardized z-scores)
| |
PZ (restd) |
FI (restd) |
NR (restd) |
| * p < 0.1, ** p < 0.05, *** p < 0.01 |
| Variables z-standardized WITHIN each region so main effects are evaluated at within-region means. |
| This ensures l x f is not confounded by level differences in regressors across regions. |
| (Intercept) |
0.323*** |
0.264*** |
0.186*** |
|
(0.043) |
(0.023) |
(0.006) |
| l (MTM) |
0.058 |
-0.035 |
0.023*** |
|
(0.095) |
(0.029) |
(0.006) |
| f (Franchise) |
0.064 |
0.017 |
0.012* |
|
(0.076) |
(0.028) |
(0.007) |
| l x f |
0.020 |
0.030 |
0.013** |
|
(0.036) |
(0.024) |
(0.006) |
| Log(Assets) |
0.135** |
0.097*** |
0.104*** |
|
(0.061) |
(0.025) |
(0.008) |
| Cash/TA |
-0.091* |
-0.043* |
-0.020*** |
|
(0.049) |
(0.026) |
(0.006) |
| Loan/Dep |
0.019 |
0.004 |
-0.004 |
|
(0.056) |
(0.033) |
(0.007) |
| Equity/TA |
-0.014 |
-0.031 |
-0.013** |
|
(0.093) |
(0.036) |
(0.006) |
| Wholesale |
0.025 |
0.053* |
0.024*** |
|
(0.051) |
(0.028) |
(0.007) |
| ROA |
-0.039 |
-0.022 |
-0.001 |
|
(0.051) |
(0.022) |
(0.006) |
| Num.Obs. |
102 |
335 |
3789 |
| R2 |
0.224 |
0.106 |
0.107 |
| R2 Adj. |
0.148 |
0.081 |
0.105 |
| Std.Errors |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
| N (DV = 1) |
33 |
88 |
701 |
| Mean (DV) |
0.3235 |
0.2627 |
0.185 |
# --- LaTeX export ---
save_reg_latex(reg_goldstein_restd, "Table_P2_Goldstein_Restandardized",
add_rows=dv_summary_rows(reg_goldstein_restd), coef_rename=restd_map,
title="Goldstein Test: Within-Region Restandardized Z-Scores",
notes="Variables restandardized within each region.")
## Saved: Table_P2_Goldstein_Restandardized.tex
8.3 Within-Region:
BTFP and DW Separately
reg_region_fac <- list()
if (sum(df_pz$any_fed)>=10) reg_region_fac[["PZ_AnyFed"]] <- feols(fml_main, data=df_pz, vcov="hetero")
if (sum(df_pz$btfp_crisis)>=10) reg_region_fac[["PZ_BTFP"]] <- feols(update(fml_main,btfp_crisis~.), data=df_pz, vcov="hetero")
if (sum(df_pz$dw_crisis)>=10) reg_region_fac[["PZ_DW"]] <- feols(update(fml_main,dw_crisis~.), data=df_pz, vcov="hetero")
if (sum(df_fi$any_fed)>=10) reg_region_fac[["FI_AnyFed"]] <- feols(fml_main, data=df_fi, vcov="hetero")
if (sum(df_fi$btfp_crisis)>=10) reg_region_fac[["FI_BTFP"]] <- feols(update(fml_main,btfp_crisis~.), data=df_fi, vcov="hetero")
if (sum(df_fi$dw_crisis)>=10) reg_region_fac[["FI_DW"]] <- feols(update(fml_main,dw_crisis~.), data=df_fi, vcov="hetero")
if (length(reg_region_fac)>0) {
msummary(reg_region_fac, stars=c("*"=.10,"**"=.05,"***"=.01), gof_omit="AIC|BIC|Log|RMSE",
add_rows=dv_summary_rows(reg_region_fac), coef_rename=coef_map,
title="P2: Within-Region by Facility (BTFP vs DW)")
# --- LaTeX export ---
save_reg_latex(reg_region_fac, "Table_P2_RegionFacility",
add_rows=dv_summary_rows(reg_region_fac), coef_rename=coef_map,
title="Within-Region by Facility (BTFP vs DW)")
}
## Saved: Table_P2_RegionFacility.tex
8.4 Formal Wald
Test: \(c_3^{PZ} \neq c_3^{FI}\)
This is the formal cross-region test the theory
demands. We fully interact all regressors (including controls)
with a panic-zone indicator, so the triple interaction \(\ell \times f \times PZ\) is not confounded
by differential control slopes across regions.
cat("=== P2: FORMAL WALD TEST — c3(Panic Zone) vs c3(Fund. Insolvency) ===\n\n")
## === P2: FORMAL WALD TEST — c3(Panic Zone) vs c3(Fund. Insolvency) ===
# Pool panic + fund.ins. and interact ℓ×f with a panic indicator
# Pool panic + fund.ins. and FULLY interact with a panic indicator
df_pz_fi <- df_crisis_model %>% filter(model_region %in% c("Panic Zone","Fundamental Insolvency")) %>%
mutate(is_pz = as.integer(model_region == "Panic Zone"),
mtm_x_fran_x_pz = mtm_x_franchise * is_pz,
mtm_x_pz = mtm_total * is_pz,
fran_x_pz = franchise_value * is_pz,
ln_assets_x_pz = ln_assets * is_pz,
cash_ratio_x_pz = cash_ratio * is_pz,
loan_to_deposit_x_pz = loan_to_deposit * is_pz,
book_equity_ratio_x_pz = book_equity_ratio * is_pz,
wholesale_x_pz = wholesale * is_pz,
roa_x_pz = roa * is_pz)
ctrl_x_pz_str <- paste0(controls, "_x_pz", collapse = " + ")
fml_wald <- as.formula(paste0(
"any_fed ~ mtm_total + franchise_value + mtm_x_franchise + ",
"is_pz + mtm_x_pz + fran_x_pz + mtm_x_fran_x_pz + ",
ctrl_str, " + ", ctrl_x_pz_str))
reg_wald <- feols(fml_wald, data=df_pz_fi, vcov="hetero")
b_diff <- coef(reg_wald)["mtm_x_fran_x_pz"]
se_diff <- sqrt(vcov(reg_wald)["mtm_x_fran_x_pz","mtm_x_fran_x_pz"])
p_diff <- 2*pnorm(-abs(b_diff/se_diff))
stars <- ifelse(p_diff<0.01,"***",ifelse(p_diff<0.05,"**",ifelse(p_diff<0.10,"*","")))
cat(sprintf(" c3(PZ) - c3(FI) = %.4f (SE=%.4f, p=%.4f) %s\n", b_diff, se_diff, p_diff, stars))
## c3(PZ) - c3(FI) = -0.0290 (SE=0.0879, p=0.7418)
cat(" Theory predicts: > 0 (interaction operates in panic zone, not fund. insolvency)\n\n")
## Theory predicts: > 0 (interaction operates in panic zone, not fund. insolvency)
# Also report the base c3 (= c3 in Fund. Insolvency region)
b_base <- coef(reg_wald)["mtm_x_franchise"]
se_base <- sqrt(vcov(reg_wald)["mtm_x_franchise","mtm_x_franchise"])
p_base <- 2*pnorm(-abs(b_base/se_base))
cat(sprintf(" c3 in Fund.Ins. (base): %.4f (%.4f), p=%.4f\n", b_base, se_base, p_base))
## c3 in Fund.Ins. (base): 0.0727 (0.0647), p=0.2613
# c3(PZ) = c3(FI) + Δ → Var(c3_PZ) = Var(base) + Var(Δ) + 2*Cov(base,Δ)
cov_base_diff <- vcov(reg_wald)["mtm_x_franchise", "mtm_x_fran_x_pz"]
se_pz <- sqrt(se_base^2 + se_diff^2 + 2 * cov_base_diff)
p_pz <- 2*pnorm(-abs((b_base+b_diff)/se_pz))
cat(sprintf(" c3 in Panic Zone: %.4f (%.4f), p=%.4f\n", b_base+b_diff, se_pz, p_pz))
## c3 in Panic Zone: 0.0437 (0.0596), p=0.4635
cat(sprintf(" [Cov(base,Δ) = %.6f; naive SE (ignoring cov) would be %.4f]\n",
cov_base_diff, sqrt(se_base^2+se_diff^2)))
## [Cov(base,Δ) = -0.004184; naive SE (ignoring cov) would be 0.1092]
msummary(list("Pooled PZ+FI"=reg_wald), stars=c("*"=.10,"**"=.05,"***"=.01),
gof_omit="AIC|BIC|Log|RMSE",
coef_rename=c("mtm_total"="$\\ell$","franchise_value"="$f$",
"mtm_x_franchise"="$\\ell \\times f$ (base=FI)",
"is_pz"="Panic Zone","mtm_x_pz"="$\\ell \\times$ PZ",
"fran_x_pz"="$f \\times$ PZ",
"mtm_x_fran_x_pz"="$\\ell \\times f \\times$ PZ",
"ln_assets_x_pz"="Log(Assets) $\\times$ PZ","cash_ratio_x_pz"="Cash/TA $\\times$ PZ",
"loan_to_deposit_x_pz"="Loan/Dep $\\times$ PZ","book_equity_ratio_x_pz"="Equity/TA $\\times$ PZ",
"wholesale_x_pz"="Wholesale $\\times$ PZ","roa_x_pz"="ROA $\\times$ PZ"),
title="P2 Wald: Differential Interaction Effect (Panic vs Fund.Ins.)")
P2 Wald: Differential Interaction Effect (Panic vs Fund.Ins.)
| |
Pooled PZ+FI |
| * p < 0.1, ** p < 0.05, *** p < 0.01 |
| (Intercept) |
0.233 |
|
(0.145) |
| $\ell$ |
-0.044 |
|
(0.061) |
| $f$ |
-0.073 |
|
(0.096) |
| $\ell \times f$ (base=FI) |
0.073 |
|
(0.065) |
| Panic Zone |
0.037 |
|
(0.461) |
| $\ell \times$ PZ |
0.039 |
|
(0.186) |
| $f \times$ PZ |
0.125 |
|
(0.117) |
| $\ell \times f \times$ PZ |
-0.029 |
|
(0.088) |
| ln_assets |
0.114*** |
|
(0.032) |
| cash_ratio |
-0.065 |
|
(0.041) |
| loan_to_deposit |
0.004 |
|
(0.035) |
| book_equity_ratio |
-0.062 |
|
(0.077) |
| wholesale |
0.045* |
|
(0.024) |
| roa |
-0.023 |
|
(0.027) |
| Log(Assets) $\times$ PZ |
0.045 |
|
(0.070) |
| Cash/TA $\times$ PZ |
-0.115 |
|
(0.093) |
| Loan/Dep $\times$ PZ |
0.026 |
|
(0.071) |
| Equity/TA $\times$ PZ |
0.148 |
|
(0.292) |
| Wholesale $\times$ PZ |
-0.024 |
|
(0.051) |
| ROA $\times$ PZ |
-0.028 |
|
(0.067) |
| Num.Obs. |
437 |
| R2 |
0.138 |
| R2 Adj. |
0.098 |
| Std.Errors |
Heteroskedasticity-robust |
# --- LaTeX export ---
save_reg_latex(list("Pooled PZ+FI"=reg_wald), "Table_P2_Wald",
coef_rename=c("mtm_total"="$\\ell$","franchise_value"="$f$",
"mtm_x_franchise"="$\\ell \\times f$ (base=FI)",
"is_pz"="Panic Zone","mtm_x_pz"="$\\ell \\times$ PZ",
"fran_x_pz"="$f \\times$ PZ",
"mtm_x_fran_x_pz"="$\\ell \\times f \\times$ PZ"),
title="Wald Test: Differential $\\ell \\times f$ (Panic vs.\\ Fund.\\ Insolvency)")
## Saved: Table_P2_Wald.tex
8.5 Decomposition:
\(\ell \times \mu\) vs \(\ell \times (1-\beta^U)\)
Theory: “The composite \(\ell \times f\) subsumes two channels: the
uninsured leverage channel (\(\ell \times
\mu\)) and the deposit stickiness channel (\(\ell \times (1-\beta^U)\)). […] we expect
the uninsured leverage channel to dominate.”
fml_d <- as.formula(paste0("any_fed ~ mtm_total + uninsured_lev + uninsured_beta + ",
"mtm_x_uninsured + mtm_x_unins_beta + ", ctrl_str))
reg_d <- list("Full"=feols(fml_d, data=df_crisis_model, vcov="hetero"))
if (sum(df_pz$any_fed)>=10 & sum(df_pz$any_fed==0)>=10)
reg_d[["Panic Zone"]] <- feols(fml_d, data=df_pz, vcov="hetero")
if (sum(df_fi$any_fed)>=10 & sum(df_fi$any_fed==0)>=10)
reg_d[["Fund. Insolvency"]] <- feols(fml_d, data=df_fi, vcov="hetero")
msummary(reg_d, stars=c("*"=.10,"**"=.05,"***"=.01), gof_omit="AIC|BIC|Log|RMSE",
add_rows=dv_summary_rows(reg_d),
coef_rename=c("mtm_total"="l","uninsured_lev"="mu","uninsured_beta"="beta^U",
"mtm_x_uninsured"="l x mu (leverage channel)","mtm_x_unins_beta"="l x beta^U (stickiness; expect < 0)"),
title="P2 Decomposition: l x mu vs l x beta^U",
notes="Theory: l x (1-beta^U) > 0 ⟺ l x beta^U < 0. Leverage channel expected to dominate.")
P2 Decomposition: l x mu vs l x beta^U
| |
Full |
Panic Zone |
Fund. Insolvency |
| * p < 0.1, ** p < 0.05, *** p < 0.01 |
| Theory: l x (1-beta^U) > 0 ⟺ l x beta^U < 0. Leverage channel expected to dominate. |
| (Intercept) |
0.195*** |
0.379 |
0.255* |
|
(0.006) |
(0.502) |
(0.151) |
| l |
0.029*** |
-0.042 |
-0.053 |
|
(0.007) |
(0.202) |
(0.063) |
| mu |
0.019** |
0.072 |
0.020 |
|
(0.008) |
(0.073) |
(0.097) |
| beta^U |
0.004 |
-0.091 |
0.105 |
|
(0.007) |
(0.095) |
(0.074) |
| l x mu (leverage channel) |
0.020*** |
0.051 |
0.017 |
|
(0.006) |
(0.071) |
(0.065) |
| l x beta^U (stickiness; expect < 0) |
0.006 |
0.011 |
-0.067 |
|
(0.006) |
(0.084) |
(0.050) |
| ln_assets |
0.102*** |
0.150** |
0.107*** |
|
(0.008) |
(0.067) |
(0.034) |
| cash_ratio |
-0.021*** |
-0.180** |
-0.056 |
|
(0.006) |
(0.089) |
(0.041) |
| loan_to_deposit |
-0.006 |
0.039 |
0.002 |
|
(0.007) |
(0.066) |
(0.034) |
| book_equity_ratio |
-0.016*** |
0.172 |
-0.059 |
|
(0.006) |
(0.328) |
(0.080) |
| wholesale |
0.029*** |
0.027 |
0.045* |
|
(0.007) |
(0.046) |
(0.024) |
| roa |
-0.002 |
-0.052 |
-0.023 |
|
(0.006) |
(0.063) |
(0.027) |
| Num.Obs. |
4226 |
102 |
335 |
| R2 |
0.110 |
0.241 |
0.106 |
| R2 Adj. |
0.108 |
0.148 |
0.076 |
| Std.Errors |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
| N (DV = 1) |
822 |
33 |
88 |
| Mean (DV) |
0.1945 |
0.3235 |
0.2627 |
# --- LaTeX export ---
save_reg_latex(reg_d, "Table_P2_Decomposition",
add_rows=dv_summary_rows(reg_d),
coef_rename=c("mtm_total"="$\\ell$","uninsured_lev"="$\\mu$","uninsured_beta"="$\\beta^U$",
"mtm_x_uninsured"="$\\ell \\times \\mu$","mtm_x_unins_beta"="$\\ell \\times \\beta^U$"),
title="Decomposition: $\\ell \\times \\mu$ vs.\\ $\\ell \\times \\beta^U$",
notes="$\\ell \\times (1-\\beta^U) > 0 \\Longleftrightarrow \\ell \\times \\beta^U < 0$.")
## Saved: Table_P2_Decomposition.tex
PREDICTION 3: COORDINATION AROUND OBSERVABLE MTM LOSSES
Theory: “Coordination requires a common signal, and
securities MTM losses are the relevant one: they are publicly observable
in real time, while loan losses are not. […] BTFP use should concentrate
among banks with large securities MTM losses, high uninsured leverage,
and going-concern solvency.”
9.1 Par-Value
Channel: BTFP vs DW vs FHLB [Theory Eq. 9]
# ==============================================================================
# Eq. 9: Δ_BTFP = (1-θ_S)S_el
# BTFP-eligible losses should predict BTFP (par margin) but NOT DW or FHLB
# Also test theory ℓ_S (all securities) vs ℓ_L (all loans)
# ==============================================================================
df_m <- df_crisis_model %>%
mutate(
# Theory decomposition: ℓ_S (all securities) vs ℓ_L (all loans)
mtm_sec_z = mtm_sec, # z-standardized in construct_analysis_vars
mtm_loan_z = mtm_loan, # z-standardized in construct_analysis_vars
mtm_tot_z = mtm_total,
# BTFP/OMO decomposition: OMO-eligible vs non-OMO
mtm_btfp_z = mtm_btfp, # z-standardized BTFP-eligible (OMO) losses
mtm_nonbtfp_z = mtm_other # z-standardized non-BTFP losses
)
# Specification A: Theory ℓ_S vs ℓ_L
fml_sep <- function(dv) as.formula(paste0(dv, " ~ mtm_sec_z + mtm_loan_z + franchise_value + uninsured_lev + ", ctrl_str))
fml_incr <- function(dv) as.formula(paste0(dv, " ~ mtm_tot_z + mtm_sec_z + franchise_value + uninsured_lev + ", ctrl_str))
# Specification B: BTFP-eligible vs non-BTFP
fml_btfp <- function(dv) as.formula(paste0(dv, " ~ mtm_btfp_z + mtm_nonbtfp_z + franchise_value + uninsured_lev + ", ctrl_str))
reg_p3 <- list(
"BTFP (ℓ_S/ℓ_L)" = feols(fml_sep("btfp_crisis"), data=df_m, vcov="hetero"),
"DW (ℓ_S/ℓ_L)" = feols(fml_sep("dw_crisis"), data=df_m, vcov="hetero"),
"FHLB (ℓ_S/ℓ_L)" = feols(fml_sep("fhlb_user"), data=df_m, vcov="hetero"),
"BTFP (OMO/nonOMO)" = feols(fml_btfp("btfp_crisis"), data=df_m, vcov="hetero"),
"DW (OMO/nonOMO)" = feols(fml_btfp("dw_crisis"), data=df_m, vcov="hetero"),
"FHLB (OMO/nonOMO)" = feols(fml_btfp("fhlb_user"), data=df_m, vcov="hetero")
)
msummary(reg_p3, stars=c("*"=.10,"**"=.05,"***"=.01), gof_omit="AIC|BIC|Log|RMSE",
coef_rename=c("mtm_sec_z"="ℓ_S (Securities)","mtm_loan_z"="ℓ_L (Loans)",
"mtm_tot_z"="ℓ (Total)","mtm_btfp_z"="ℓ_BTFP (OMO-eligible)","mtm_nonbtfp_z"="ℓ_nonBTFP",
"franchise_value"="f","uninsured_lev"="mu"),
title="P3: Par-Value Channel — Securities/BTFP Should Predict BTFP Only (Eq. 9)")
P3: Par-Value Channel — Securities/BTFP Should Predict BTFP Only (Eq. 9)
| |
BTFP (ℓ_S/ℓ_L) |
DW (ℓ_S/ℓ_L) |
FHLB (ℓ_S/ℓ_L) |
BTFP (OMO/nonOMO) |
DW (OMO/nonOMO) |
FHLB (OMO/nonOMO) |
| * p < 0.1, ** p < 0.05, *** p < 0.01 |
| (Intercept) |
0.115*** |
0.100*** |
0.070*** |
0.115*** |
0.100*** |
0.070*** |
|
(0.005) |
(0.004) |
(0.004) |
(0.005) |
(0.004) |
(0.004) |
| ℓ_S (Securities) |
0.023*** |
0.010 |
-0.007 |
|
|
|
|
(0.008) |
(0.007) |
(0.006) |
|
|
|
| ℓ_L (Loans) |
0.010* |
0.006 |
0.011** |
|
|
|
|
(0.005) |
(0.005) |
(0.005) |
|
|
|
| f |
-0.006 |
-0.002 |
-0.026*** |
-0.007 |
-0.003 |
-0.024*** |
|
(0.011) |
(0.011) |
(0.009) |
(0.011) |
(0.011) |
(0.009) |
| mu |
0.021* |
0.008 |
0.030*** |
0.021* |
0.009 |
0.027*** |
|
(0.012) |
(0.012) |
(0.009) |
(0.012) |
(0.012) |
(0.009) |
| ln_assets |
0.052*** |
0.075*** |
0.009* |
0.049*** |
0.073*** |
0.011** |
|
(0.007) |
(0.007) |
(0.005) |
(0.007) |
(0.007) |
(0.005) |
| cash_ratio |
-0.022*** |
0.001 |
-0.021*** |
-0.028*** |
0.000 |
-0.017*** |
|
(0.005) |
(0.006) |
(0.004) |
(0.005) |
(0.005) |
(0.003) |
| loan_to_deposit |
-0.003 |
0.001 |
0.008 |
-0.009 |
0.000 |
0.017*** |
|
(0.009) |
(0.008) |
(0.007) |
(0.006) |
(0.006) |
(0.005) |
| book_equity_ratio |
-0.016*** |
-0.003 |
0.009** |
-0.018*** |
-0.003 |
0.009** |
|
(0.005) |
(0.004) |
(0.004) |
(0.005) |
(0.004) |
(0.004) |
| wholesale |
0.022*** |
0.015*** |
0.000 |
0.021*** |
0.015*** |
-0.001 |
|
(0.006) |
(0.005) |
(0.004) |
(0.006) |
(0.005) |
(0.004) |
| roa |
-0.005 |
-0.001 |
-0.006* |
-0.003 |
-0.000 |
-0.008** |
|
(0.005) |
(0.005) |
(0.004) |
(0.005) |
(0.005) |
(0.004) |
| ℓ_BTFP (OMO-eligible) |
|
|
|
0.016*** |
0.007 |
-0.006 |
|
|
|
|
(0.006) |
(0.005) |
(0.004) |
| ℓ_nonBTFP |
|
|
|
0.009* |
0.009* |
0.006 |
|
|
|
|
(0.005) |
(0.005) |
(0.004) |
| Num.Obs. |
4226 |
4226 |
4226 |
4226 |
4226 |
4226 |
| R2 |
0.068 |
0.072 |
0.028 |
0.067 |
0.072 |
0.028 |
| R2 Adj. |
0.065 |
0.069 |
0.026 |
0.065 |
0.070 |
0.025 |
| Std.Errors |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
# --- LaTeX export ---
save_reg_latex(reg_p3, "Table_P3_ParChannel",
coef_rename=c("mtm_sec_z"="$\\ell_S$","mtm_loan_z"="$\\ell_L$",
"mtm_tot_z"="$\\ell$","mtm_btfp_z"="$\\ell_{BTFP}$","mtm_nonbtfp_z"="$\\ell_{\\text{non-BTFP}}$",
"franchise_value"="$f$","uninsured_lev"="$\\mu$"),
title="Par-Value Channel: Securities and BTFP-Eligible Losses (Eq.\\ 9)")
## Saved: Table_P3_ParChannel.tex
cat("\n=== P3 SIGN PATTERN ===\n")
##
## === P3 SIGN PATTERN ===
cat(" ℓ_S (securities) ℓ_L (loans) ℓ_BTFP (OMO)\n")
## ℓ_S (securities) ℓ_L (loans) ℓ_BTFP (OMO)
cat(" BTFP (par) + (par margin) 0 +\n")
## BTFP (par) + (par margin) 0 +
cat(" DW (market) 0 + 0\n")
## DW (market) 0 + 0
cat(" FHLB (market) 0 + 0\n\n")
## FHLB (market) 0 + 0
for (fac in c("BTFP (ℓ_S/ℓ_L)","DW (ℓ_S/ℓ_L)","FHLB (ℓ_S/ℓ_L)")) {
r <- reg_p3[[fac]]
b <- coef(r)["mtm_sec_z"]; se <- sqrt(vcov(r)["mtm_sec_z","mtm_sec_z"])
p <- 2*pnorm(-abs(b/se))
cat(sprintf(" %-20s ℓ_S = %+.4f (%.4f) p=%.3f\n", fac, b, se, p))
}
## BTFP (ℓ_S/ℓ_L) ℓ_S = +0.0228 (0.0076) p=0.003
## DW (ℓ_S/ℓ_L) ℓ_S = +0.0097 (0.0066) p=0.140
## FHLB (ℓ_S/ℓ_L) ℓ_S = -0.0074 (0.0057) p=0.195
for (fac in c("BTFP (OMO/nonOMO)","DW (OMO/nonOMO)","FHLB (OMO/nonOMO)")) {
r <- reg_p3[[fac]]
b <- coef(r)["mtm_btfp_z"]; se <- sqrt(vcov(r)["mtm_btfp_z","mtm_btfp_z"])
p <- 2*pnorm(-abs(b/se))
cat(sprintf(" %-20s ℓ_BTFP = %+.4f (%.4f) p=%.3f\n", fac, b, se, p))
}
## BTFP (OMO/nonOMO) ℓ_BTFP = +0.0156 (0.0060) p=0.009
## DW (OMO/nonOMO) ℓ_BTFP = +0.0073 (0.0053) p=0.171
## FHLB (OMO/nonOMO) ℓ_BTFP = -0.0060 (0.0039) p=0.123
9.2 Cross-Equation
Test: \(\ell_S^{BTFP} >
\ell_S^{DW}\) [Eq. 9]
df_stacked <- bind_rows(
df_m %>% mutate(y=btfp_crisis, facility="BTFP"),
df_m %>% mutate(y=dw_crisis, facility="DW")
) %>%
mutate(is_btfp=as.integer(facility=="BTFP"),
sec_x_btfp=mtm_sec_z*is_btfp,
loan_x_btfp=mtm_loan_z*is_btfp)
fml_stack <- as.formula(paste0(
"y ~ is_btfp + mtm_sec_z + mtm_loan_z + sec_x_btfp + loan_x_btfp + ",
"franchise_value + uninsured_lev + ", ctrl_str))
reg_stack <- feols(fml_stack, data=df_stacked, cluster=~idrssd)
cat("=== CROSS-EQUATION: l_S DIFFERENTIAL ===\n")
## === CROSS-EQUATION: l_S DIFFERENTIAL ===
b_s <- coef(reg_stack)["sec_x_btfp"]; se_s <- sqrt(vcov(reg_stack)["sec_x_btfp","sec_x_btfp"])
p_s <- 2*pnorm(-abs(b_s/se_s))
cat(sprintf(" l_S(BTFP) - l_S(DW) = %.4f (SE=%.4f, p=%.4f) %s\n",
b_s, se_s, p_s, ifelse(p_s<0.01,"***",ifelse(p_s<0.05,"**",ifelse(p_s<0.10,"*","")))))
## l_S(BTFP) - l_S(DW) = 0.0287 (SE=0.0064, p=0.0000) ***
cat(" Theory: > 0 (par margin is BTFP-specific)\n")
## Theory: > 0 (par margin is BTFP-specific)
b_l <- coef(reg_stack)["loan_x_btfp"]; se_l <- sqrt(vcov(reg_stack)["loan_x_btfp","loan_x_btfp"])
p_l <- 2*pnorm(-abs(b_l/se_l))
cat(sprintf(" l_L(BTFP) - l_L(DW) = %.4f (SE=%.4f, p=%.4f)\n", b_l, se_l, p_l))
## l_L(BTFP) - l_L(DW) = 0.0064 (SE=0.0062, p=0.3009)
cat(" Theory: ~ 0 (no facility-specific channel for loans)\n")
## Theory: ~ 0 (no facility-specific channel for loans)
9.3 Observable vs
Unobservable Signal: \(\ell_S \times
f\) vs \(\ell_L \times f\)
Theory: “Loan losses \(\ell_L\) predict borrowing through a
fundamental channel but do not generate coordination amplification.”
df_signal <- df_m %>%
mutate(sec_x_f = mtm_sec_z * franchise_value,
loan_x_f = mtm_loan_z * franchise_value)
fml_signal <- as.formula(paste0(
"any_fed ~ mtm_sec_z + mtm_loan_z + franchise_value + sec_x_f + loan_x_f + ", ctrl_str))
reg_signal <- list(
"Full" = feols(fml_signal, data=df_signal, vcov="hetero")
)
# Within panic zone
df_pz_sig <- df_signal %>% filter(model_region=="Panic Zone")
if (sum(df_pz_sig$any_fed)>=10 & sum(df_pz_sig$any_fed==0)>=10)
reg_signal[["Panic Zone"]] <- feols(fml_signal, data=df_pz_sig, vcov="hetero")
# Within fund.ins.
df_fi_sig <- df_signal %>% filter(model_region=="Fundamental Insolvency")
if (sum(df_fi_sig$any_fed)>=10 & sum(df_fi_sig$any_fed==0)>=10)
reg_signal[["Fund. Insolvency"]] <- feols(fml_signal, data=df_fi_sig, vcov="hetero")
msummary(reg_signal, stars=c("*"=.10,"**"=.05,"***"=.01), gof_omit="AIC|BIC|Log|RMSE",
add_rows=dv_summary_rows(reg_signal),
coef_rename=c("mtm_sec_z"="l_S","mtm_loan_z"="l_L","franchise_value"="f",
"sec_x_f"="l_S x f (OBSERVABLE signal; expect > 0)",
"loan_x_f"="l_L x f (UNOBSERVABLE; expect ~ 0)"),
title="P3: Observable vs Unobservable Signal — Coordination Amplification",
notes="Theory: l_S x f > 0 (observable, generates coordination); l_L x f ≈ 0 (not a coordination signal).")
P3: Observable vs Unobservable Signal — Coordination Amplification
| |
Full |
Panic Zone |
Fund. Insolvency |
| * p < 0.1, ** p < 0.05, *** p < 0.01 |
| Theory: l_S x f > 0 (observable, generates coordination); l_L x f ≈ 0 (not a coordination signal). |
| (Intercept) |
0.193*** |
0.197 |
0.137 |
|
(0.006) |
(0.348) |
(0.147) |
| l_S |
0.029*** |
0.064 |
-0.007 |
|
(0.009) |
(0.151) |
(0.065) |
| l_L |
0.020*** |
0.003 |
0.018 |
|
(0.007) |
(0.120) |
(0.050) |
| f |
0.015** |
0.049 |
-0.065 |
|
(0.007) |
(0.115) |
(0.107) |
| l_S x f (OBSERVABLE signal; expect > 0) |
0.012** |
0.027 |
0.057 |
|
(0.006) |
(0.076) |
(0.060) |
| l_L x f (UNOBSERVABLE; expect ~ 0) |
0.016*** |
0.038 |
0.076 |
|
(0.006) |
(0.045) |
(0.051) |
| ln_assets |
0.104*** |
0.157** |
0.108*** |
|
(0.007) |
(0.067) |
(0.033) |
| cash_ratio |
-0.017** |
-0.157* |
-0.058 |
|
(0.007) |
(0.091) |
(0.046) |
| loan_to_deposit |
0.002 |
0.074 |
-0.005 |
|
(0.010) |
(0.113) |
(0.056) |
| book_equity_ratio |
-0.017*** |
0.054 |
-0.105 |
|
(0.006) |
(0.222) |
(0.071) |
| wholesale |
0.028*** |
0.014 |
0.046* |
|
(0.007) |
(0.048) |
(0.024) |
| roa |
-0.002 |
-0.054 |
-0.018 |
|
(0.006) |
(0.061) |
(0.027) |
| Num.Obs. |
4226 |
102 |
335 |
| R2 |
0.110 |
0.243 |
0.099 |
| R2 Adj. |
0.108 |
0.150 |
0.068 |
| Std.Errors |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
| N (DV = 1) |
822 |
33 |
88 |
| Mean (DV) |
0.1945 |
0.3235 |
0.2627 |
# --- LaTeX export ---
save_reg_latex(reg_signal, "Table_P3_Signal",
add_rows=dv_summary_rows(reg_signal),
coef_rename=c("mtm_sec_z"="$\\ell_S$","mtm_loan_z"="$\\ell_L$","franchise_value"="$f$",
"sec_x_f"="$\\ell_S \\times f$ (observable)",
"loan_x_f"="$\\ell_L \\times f$ (unobservable)"),
title="Observable vs.\\ Unobservable Signal: Coordination Amplification",
notes="$\\ell_S \\times f > 0$ (observable); $\\ell_L \\times f \\approx 0$ (not a coordination signal).")
## Saved: Table_P3_Signal.tex
9.4 Facility Choice
Among Borrowers [P3]
Theory: “BTFP use should concentrate among banks
with large securities MTM losses, high uninsured leverage, and
going-concern solvency.”
df_borr <- df_crisis_model %>% filter(any_fed==1) %>%
mutate(chose_btfp=as.integer(btfp_crisis==1),
mtm_securities=mtm_sec) # already z-standardized
cat(sprintf("Borrowers: %d (BTFP: %d, DW: %d, Both: %d)\n",
nrow(df_borr), sum(df_borr$btfp_crisis), sum(df_borr$dw_crisis), sum(df_borr$both_fed)))
## Borrowers: 822 (BTFP: 496, DW: 430, Both: 104)
if (nrow(df_borr)>=30) {
fml_ch <- as.formula(paste0("chose_btfp ~ mtm_securities + franchise_value + uninsured_lev + ", ctrl_str))
reg_ch <- list("All Borrowers"=feols(fml_ch, data=df_borr, vcov="hetero"))
for (reg in c("Panic Zone","Fundamental Insolvency")) {
df_br <- df_borr %>% filter(model_region==reg)
if (nrow(df_br)>=15)
reg_ch[[gsub("[^A-Za-z]","",reg)]] <- feols(fml_ch, data=df_br, vcov="hetero")
}
msummary(reg_ch, stars=c("*"=.10,"**"=.05,"***"=.01), gof_omit="AIC|BIC|Log|RMSE",
add_rows=dv_summary_rows(reg_ch),
coef_rename=c("mtm_securities"="l_S (Securities MTM)","franchise_value"="f",
"uninsured_lev"="mu"),
title="P3: Facility Choice (Chose BTFP = 1) Among Borrowers",
notes="Theory: l_S > 0, mu > 0 (BTFP preferred by panic-zone banks with large securities losses).")
# --- LaTeX export ---
save_reg_latex(reg_ch, "Table_P3_FacilityChoice",
add_rows=dv_summary_rows(reg_ch),
coef_rename=c("mtm_securities"="$\\ell_S$","franchise_value"="$f$","uninsured_lev"="$\\mu$"),
title="Facility Choice Among Borrowers (Chose BTFP=1)")
}
## Saved: Table_P3_FacilityChoice.tex
FALSIFICATION TESTS
10.1 FHLB
Falsification
Theory footnote: “We address [confounds] with FHLB
borrowing […] tests.” \(\ell \times f\)
should NOT predict FHLB borrowing (FHLB = routine advance, not
run-motivated).
fml_fhlb <- as.formula(paste0("fhlb_user ~ mtm_total + franchise_value + mtm_x_franchise + ", ctrl_str))
reg_falsif <- list(
"Any Fed (crisis)" = feols(fml_main, data=df_crisis_model, vcov="hetero"),
"FHLB (crisis)" = feols(fml_fhlb, data=df_crisis_model, vcov="hetero")
)
# Within panic zone
if (sum(df_pz$any_fed)>=10) reg_falsif[["PZ: Any Fed"]] <- feols(fml_main, data=df_pz, vcov="hetero")
if (sum(df_pz$fhlb_user)>=10 & sum(df_pz$fhlb_user==0)>=10)
reg_falsif[["PZ: FHLB"]] <- feols(fml_fhlb, data=df_pz, vcov="hetero")
msummary(reg_falsif, stars=c("*"=.10,"**"=.05,"***"=.01), gof_omit="AIC|BIC|Log|RMSE",
add_rows=dv_summary_rows(reg_falsif), coef_rename=coef_map,
title="Falsification: FHLB Should NOT Show Coordination Signature",
notes="FHLB = routine advance lending. A significant l x f for FHLB would undermine the coordination interpretation.")
Falsification: FHLB Should NOT Show Coordination Signature
| |
Any Fed (crisis) |
FHLB (crisis) |
PZ: Any Fed |
| * p < 0.1, ** p < 0.05, *** p < 0.01 |
| FHLB = routine advance lending. A significant l x f for FHLB would undermine the coordination interpretation. |
| (Intercept) |
0.193*** |
0.070*** |
0.269 |
|
(0.006) |
(0.004) |
(0.450) |
| l (MTM) |
0.027*** |
0.000 |
-0.005 |
|
(0.006) |
(0.005) |
(0.180) |
| f (Franchise) |
0.014** |
-0.004 |
0.051 |
|
(0.007) |
(0.004) |
(0.068) |
| l x f |
0.017*** |
-0.006* |
0.044 |
|
(0.006) |
(0.003) |
(0.061) |
| Log(Assets) |
0.105*** |
0.016*** |
0.159** |
|
(0.007) |
(0.005) |
(0.065) |
| Cash/TA |
-0.021*** |
-0.016*** |
-0.181** |
|
(0.006) |
(0.004) |
(0.086) |
| Loan/Dep |
-0.005 |
0.021*** |
0.030 |
|
(0.007) |
(0.005) |
(0.064) |
| Equity/TA |
-0.018*** |
0.007* |
0.086 |
|
(0.006) |
(0.004) |
(0.290) |
| Wholesale |
0.029*** |
-0.002 |
0.022 |
|
(0.007) |
(0.004) |
(0.046) |
| ROA |
-0.002 |
-0.007* |
-0.052 |
|
(0.006) |
(0.004) |
(0.063) |
| Num.Obs. |
4226 |
4226 |
102 |
| R2 |
0.109 |
0.025 |
0.235 |
| R2 Adj. |
0.108 |
0.023 |
0.160 |
| Std.Errors |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
| N (DV = 1) |
822 |
299 |
33 |
| Mean (DV) |
0.1945 |
0.0708 |
0.3235 |
# --- LaTeX export ---
save_reg_latex(reg_falsif, "Table_Falsif_FHLB",
add_rows=dv_summary_rows(reg_falsif), coef_rename=coef_map,
title="Falsification: FHLB Should Not Show Coordination Signature")
## Saved: Table_Falsif_FHLB.tex
cat("\n=== FHLB FALSIFICATION: l x f COMPARISON ===\n")
##
## === FHLB FALSIFICATION: l x f COMPARISON ===
for (nm in names(reg_falsif)) {
m <- reg_falsif[[nm]]
b <- coef(m)["mtm_x_franchise"]; se <- sqrt(vcov(m)["mtm_x_franchise","mtm_x_franchise"])
p <- 2*pnorm(-abs(b/se))
cat(sprintf(" %-20s l x f = %+.4f (%.4f) p=%.3f %s\n", nm, b, se, p,
ifelse(p<0.01,"***",ifelse(p<0.05,"**",ifelse(p<0.10,"*","")))))
}
## Any Fed (crisis) l x f = +0.0175 (0.0055) p=0.002 ***
## FHLB (crisis) l x f = -0.0055 (0.0032) p=0.085 *
## PZ: Any Fed l x f = +0.0437 (0.0613) p=0.476
10.2 Temporal
Falsification: Arbitrage Period
Theory footnote: “the carry-trade opportunity that
emerged in late 2023 […] We address [this] with temporal falsification
tests.” In the arb period, BTFP borrowing was carry-trade motivated.
\(c_3 \approx 0\) predicted.
df_arb_model <- df_arb_clean %>% filter(!is.na(model_region))
cat(sprintf("Arb sample: %d banks. Any Fed: %d, BTFP: %d\n",
nrow(df_arb_model), sum(df_arb_model$any_fed),
sum(df_arb_model$btfp_arb, na.rm=TRUE)))
## Arb sample: 4151 banks. Any Fed: 998, BTFP: 746
# Arb-specific z-scores
df_arb_reg <- df_arb_model %>%
mutate(
mtm_total_a = standardize_z(winsorize(mtm_total_raw)),
franchise_value_a = standardize_z(winsorize(f_pp)),
mtm_x_franchise_a = mtm_total_a * franchise_value_a,
ln_assets_a=standardize_z(winsorize(ln_assets_raw)),
cash_ratio_a=standardize_z(winsorize(cash_ratio_raw)),
loan_to_deposit_a=standardize_z(winsorize(loan_to_deposit_raw)),
book_equity_ratio_a=standardize_z(winsorize(book_equity_ratio_raw)),
wholesale_a=standardize_z(winsorize(wholesale_raw)),
roa_a=standardize_z(winsorize(roa_raw))
)
arb_ctrl <- c("ln_assets_a","cash_ratio_a","loan_to_deposit_a","book_equity_ratio_a","wholesale_a","roa_a")
fml_arb <- as.formula(paste0("any_fed ~ mtm_total_a + franchise_value_a + mtm_x_franchise_a + ",
paste(arb_ctrl,collapse=" + ")))
fml_arb_btfp <- update(fml_arb, btfp_arb ~ .)
reg_arb <- list()
reg_arb[["Crisis: Any Fed"]] <- feols(fml_main, data=df_crisis_model, vcov="hetero")
if (sum(df_arb_reg$any_fed)>=10 & sum(df_arb_reg$any_fed==0)>=10)
reg_arb[["Arb: Any Fed"]] <- feols(fml_arb, data=df_arb_reg, vcov="hetero")
if (sum(df_arb_reg$btfp_arb, na.rm=TRUE)>=10)
reg_arb[["Arb: BTFP"]] <- feols(fml_arb_btfp, data=df_arb_reg, vcov="hetero")
arb_coef_map <- c("mtm_total"="l","franchise_value"="f","mtm_x_franchise"="l x f",
"mtm_total_a"="l","franchise_value_a"="f","mtm_x_franchise_a"="l x f",
"ln_assets"="Log(Assets)","ln_assets_a"="Log(Assets)",
"cash_ratio"="Cash/TA","cash_ratio_a"="Cash/TA",
"loan_to_deposit"="Loan/Dep","loan_to_deposit_a"="Loan/Dep",
"book_equity_ratio"="Equity/TA","book_equity_ratio_a"="Equity/TA",
"wholesale"="Wholesale","wholesale_a"="Wholesale",
"roa"="ROA","roa_a"="ROA")
msummary(reg_arb, stars=c("*"=.10,"**"=.05,"***"=.01), gof_omit="AIC|BIC|Log|RMSE",
coef_rename=arb_coef_map,
title="Temporal Falsification: Crisis vs Arb Period",
notes="Theory: l x f should be significant in crisis, ≈ 0 in arb period (carry-trade, not coordination).")
Temporal Falsification: Crisis vs Arb Period
| |
Crisis: Any Fed |
Arb: Any Fed |
Arb: BTFP |
| * p < 0.1, ** p < 0.05, *** p < 0.01 |
| Theory: l x f should be significant in crisis, ≈ 0 in arb period (carry-trade, not coordination). |
| (Intercept) |
0.193*** |
0.241*** |
0.180*** |
|
(0.006) |
(0.006) |
(0.006) |
| l |
0.027*** |
0.014* |
0.020*** |
|
(0.006) |
(0.007) |
(0.007) |
| f |
0.014** |
0.008 |
0.016*** |
|
(0.007) |
(0.007) |
(0.006) |
| l x f |
0.017*** |
0.006 |
0.004 |
|
(0.006) |
(0.006) |
(0.005) |
| Log(Assets) |
0.105*** |
0.092*** |
0.051*** |
|
(0.007) |
(0.007) |
(0.006) |
| Cash/TA |
-0.021*** |
-0.032*** |
-0.033*** |
|
(0.006) |
(0.007) |
(0.006) |
| Loan/Dep |
-0.005 |
-0.000 |
-0.003 |
|
(0.007) |
(0.007) |
(0.006) |
| Equity/TA |
-0.018*** |
-0.011* |
-0.009 |
|
(0.006) |
(0.006) |
(0.006) |
| Wholesale |
0.029*** |
0.099*** |
0.100*** |
|
(0.007) |
(0.008) |
(0.007) |
| ROA |
-0.002 |
-0.025*** |
-0.027*** |
|
(0.006) |
(0.006) |
(0.006) |
| Num.Obs. |
4226 |
4151 |
4151 |
| R2 |
0.109 |
0.150 |
0.139 |
| R2 Adj. |
0.108 |
0.148 |
0.137 |
| Std.Errors |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
# --- LaTeX export ---
save_reg_latex(reg_arb, "Table_Falsif_ArbPeriod",
coef_rename=arb_coef_map,
title="Temporal Falsification: Crisis vs.\\ Arbitrage Period",
notes="$\\ell \\times f$ significant in crisis, $\\approx 0$ in arbitrage period.")
## Saved: Table_Falsif_ArbPeriod.tex
cat("\n=== TEMPORAL FALSIFICATION: l x f ===\n")
##
## === TEMPORAL FALSIFICATION: l x f ===
for (nm in names(reg_arb)) {
m <- reg_arb[[nm]]
int_var <- if ("mtm_x_franchise_a" %in% names(coef(m))) "mtm_x_franchise_a" else "mtm_x_franchise"
b <- coef(m)[int_var]; se <- sqrt(vcov(m)[int_var,int_var])
p <- 2*pnorm(-abs(b/se))
cat(sprintf(" %-20s l x f = %+.4f (%.4f) p=%.3f %s\n", nm, b, se, p,
ifelse(p<0.01,"***",ifelse(p<0.05,"**",ifelse(p<0.10,"*","")))))
}
## Crisis: Any Fed l x f = +0.0175 (0.0055) p=0.002 ***
## Arb: Any Fed l x f = +0.0061 (0.0060) p=0.314
## Arb: BTFP l x f = +0.0039 (0.0052) p=0.447
ROBUSTNESS
11.1 Logit
Robustness
fml_logit <- as.formula(paste0("any_fed ~ mtm_total + franchise_value + mtm_x_franchise + ", ctrl_str))
logit_list <- list()
logit_list[["Full_Logit"]] <- feglm(fml_logit, data=df_crisis_model, family=binomial("logit"), vcov="hetero")
if (sum(df_pz$any_fed)>=10 & sum(df_pz$any_fed==0)>=10)
logit_list[["PZ_Logit"]] <- feglm(fml_logit, data=df_pz, family=binomial("logit"), vcov="hetero")
if (sum(df_fi$any_fed)>=10 & sum(df_fi$any_fed==0)>=10)
logit_list[["FI_Logit"]] <- feglm(fml_logit, data=df_fi, family=binomial("logit"), vcov="hetero")
# LPM comparisons
logit_list[["Full_LPM"]] <- feols(fml_main, data=df_crisis_model, vcov="hetero")
msummary(logit_list, stars=c("*"=.10,"**"=.05,"***"=.01), gof_omit="AIC|BIC|Log|RMSE",
coef_rename=coef_map,
title="Robustness: Logit vs LPM",
notes="LPM and Logit should agree on sign and significance of l x f.")
Robustness: Logit vs LPM
| |
Full_Logit |
PZ_Logit |
FI_Logit |
Full_LPM |
| * p < 0.1, ** p < 0.05, *** p < 0.01 |
| LPM and Logit should agree on sign and significance of l x f. |
| (Intercept) |
-1.690*** |
-1.210 |
-1.325 |
0.193*** |
|
(0.048) |
(3.144) |
(0.874) |
(0.006) |
| l (MTM) |
0.184*** |
0.033 |
-0.326 |
0.027*** |
|
(0.048) |
(1.291) |
(0.336) |
(0.006) |
| f (Franchise) |
0.101** |
0.600 |
-0.359 |
0.014** |
|
(0.046) |
(0.493) |
(0.538) |
(0.007) |
| l x f |
0.070* |
0.158 |
0.404 |
0.017*** |
|
(0.042) |
(0.436) |
(0.359) |
(0.006) |
| Log(Assets) |
0.662*** |
0.879** |
0.615*** |
0.105*** |
|
(0.047) |
(0.391) |
(0.172) |
(0.007) |
| Cash/TA |
-0.265*** |
-1.425** |
-0.535 |
-0.021*** |
|
(0.071) |
(0.632) |
(0.351) |
(0.006) |
| Loan/Dep |
0.026 |
0.211 |
0.039 |
-0.005 |
|
(0.055) |
(0.362) |
(0.211) |
(0.007) |
| Equity/TA |
-0.252*** |
0.891 |
-0.403 |
-0.018*** |
|
(0.062) |
(1.997) |
(0.504) |
(0.006) |
| Wholesale |
0.159*** |
0.100 |
0.217** |
0.029*** |
|
(0.039) |
(0.232) |
(0.111) |
(0.007) |
| ROA |
0.030 |
-0.190 |
-0.120 |
-0.002 |
|
(0.048) |
(0.392) |
(0.161) |
(0.006) |
| Num.Obs. |
4226 |
102 |
335 |
4226 |
| R2 |
0.116 |
0.218 |
0.091 |
0.109 |
| R2 Adj. |
0.112 |
0.078 |
0.044 |
0.108 |
| Std.Errors |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
# --- LaTeX export ---
save_reg_latex(logit_list, "Table_Robust_Logit",
coef_rename=coef_map,
title="Robustness: Logit vs.\\ LPM",
notes="LPM and Logit should agree on sign and significance of $\\ell \\times f$.")
## Saved: Table_Robust_Logit.tex
11.2 Beta Clipping
Sensitivity
df_unc <- df_crisis_clean %>% filter(!is.na(beta_uninsured)) %>%
mutate(
c_u_unc = ifelse(!is.na(cost_u_raw), cost_u_raw, 0),
f_unc = pmax(((1 - beta_uninsured) * y_10yr - c_u_unc) * cap_factor * mu_decimal, 0) * 100,
f_unc_w = winsorize(f_unc),
franchise_unc = standardize_z(f_unc_w),
mtm_x_fran_unc = mtm_total * franchise_unc
)
n_neg <- sum(df_unc$f_unc < 0, na.rm=TRUE)
cat(sprintf("Unclipped: %d banks with f < 0 (%.1f%% of theory sample).\n",
n_neg, 100*n_neg/nrow(df_unc)))
## Unclipped: 0 banks with f < 0 (0.0% of theory sample).
fml_unc <- as.formula(paste0("any_fed ~ mtm_total + franchise_unc + mtm_x_fran_unc + ", ctrl_str))
reg_clip <- list(
"Clipped (baseline)" = feols(fml_main, data=df_crisis_model, vcov="hetero"),
"Unclipped" = feols(fml_unc, data=df_unc, vcov="hetero")
)
msummary(reg_clip, stars=c("*"=.10,"**"=.05,"***"=.01), gof_omit="AIC|BIC|Log|RMSE",
coef_rename=c("mtm_total"="l","franchise_value"="f","franchise_unc"="f",
"mtm_x_franchise"="l x f","mtm_x_fran_unc"="l x f"),
title="Robustness: Beta^U Clipping")
Robustness: Beta^U Clipping
| |
Clipped (baseline) |
Unclipped |
| * p < 0.1, ** p < 0.05, *** p < 0.01 |
| (Intercept) |
0.193*** |
0.193*** |
|
(0.006) |
(0.006) |
| l |
0.027*** |
0.027*** |
|
(0.006) |
(0.006) |
| f |
0.014** |
0.014** |
|
(0.007) |
(0.007) |
| l x f |
0.017*** |
0.017*** |
|
(0.006) |
(0.006) |
| ln_assets |
0.105*** |
0.105*** |
|
(0.007) |
(0.007) |
| cash_ratio |
-0.021*** |
-0.021*** |
|
(0.006) |
(0.006) |
| loan_to_deposit |
-0.005 |
-0.005 |
|
(0.007) |
(0.007) |
| book_equity_ratio |
-0.018*** |
-0.018*** |
|
(0.006) |
(0.006) |
| wholesale |
0.029*** |
0.029*** |
|
(0.007) |
(0.007) |
| roa |
-0.002 |
-0.002 |
|
(0.006) |
(0.006) |
| Num.Obs. |
4226 |
4226 |
| R2 |
0.109 |
0.109 |
| R2 Adj. |
0.108 |
0.108 |
| Std.Errors |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
# --- LaTeX export ---
save_reg_latex(reg_clip, "Table_Robust_BetaClip",
coef_rename=c("mtm_total"="$\\ell$","franchise_value"="$f$","franchise_unc"="$f$",
"mtm_x_franchise"="$\\ell \\times f$","mtm_x_fran_unc"="$\\ell \\times f$"),
title="Robustness: $\\beta^U$ Clipping")
## Saved: Table_Robust_BetaClip.tex
11.3 Partial Run
Sensitivity (\(\phi\)) [Theory Eq.
10]
Theory (Eq. 10): \(\underline\ell(\phi) = e + [(D^I +
(1-\phi)D^U)/D]f\). Baseline uses \(\phi=1\) (full run). Lower \(\phi\) raises the liquidity threshold,
classifying fewer banks as panic-zone.
phi_grid <- c(0.25, 0.50, 0.75, 1.00)
phi_results <- map_dfr(phi_grid, function(phi) {
df_phi <- df_crisis_model %>%
mutate(
# Eq. 10: ℓ_(φ) = e + [(D^I + (1-φ)D^U)/D]f
th_liq_phi = book_equity_to_total_asset +
safe_div(insured_deposit + (1-phi)*uninsured_deposit,
insured_deposit + uninsured_deposit, NA_real_) * f_pp,
region_phi = case_when(
is.na(th_liq_phi) | is.na(threshold_solvency) ~ NA_character_,
mtm_total_raw <= th_liq_phi ~ "No-Run",
mtm_total_raw <= threshold_solvency ~ "Panic",
TRUE ~ "Fund.Ins."
)
)
tibble(
phi = phi,
NoRun = sum(df_phi$region_phi=="No-Run", na.rm=T),
Panic = sum(df_phi$region_phi=="Panic", na.rm=T),
FundIns = sum(df_phi$region_phi=="Fund.Ins.", na.rm=T),
N = sum(!is.na(df_phi$region_phi)),
Panic_Borr_Rate = if (sum(df_phi$region_phi=="Panic",na.rm=T)>0)
round(100*mean(df_phi$any_fed[df_phi$region_phi=="Panic"],na.rm=T),2) else NA_real_
)
})
phi_disp <- phi_results %>%
mutate(`No-Run`=sprintf("%d (%.1f%%)",NoRun,100*NoRun/N),
Panic=sprintf("%d (%.1f%%)",Panic,100*Panic/N),
`Fund.Ins.`=sprintf("%d (%.1f%%)",FundIns,100*FundIns/N),
`PZ Borrow Rate`=ifelse(is.na(Panic_Borr_Rate),"---",sprintf("%.2f%%",Panic_Borr_Rate))) %>%
select(phi, N, `No-Run`, Panic, `Fund.Ins.`, `PZ Borrow Rate`)
kbl(phi_disp, format="html", escape=FALSE,
caption="Eq. 10: Sensitivity to Partial Run Rate (phi)") %>%
kable_styling(bootstrap_options=c("striped","hover","condensed"), full_width=FALSE) %>%
row_spec(which(phi_results$phi==1), bold=TRUE, background="#e8f5e9") %>%
footnote(general="phi=1 (baseline): full uninsured-depositor run. Lower phi = milder run, fewer panic-zone banks.")
Eq. 10: Sensitivity to Partial Run Rate (phi)
|
phi
|
N
|
No-Run
|
Panic
|
Fund.Ins.
|
PZ Borrow Rate
|
|
0.25
|
4226
|
3877 (91.7%)
|
14 (0.3%)
|
335 (7.9%)
|
21.43%
|
|
0.50
|
4226
|
3852 (91.2%)
|
39 (0.9%)
|
335 (7.9%)
|
33.33%
|
|
0.75
|
4226
|
3822 (90.4%)
|
69 (1.6%)
|
335 (7.9%)
|
30.43%
|
|
1.00
|
4226
|
3789 (89.7%)
|
102 (2.4%)
|
335 (7.9%)
|
32.35%
|
|
Note:
|
|
phi=1 (baseline): full uninsured-depositor run. Lower phi =
milder run, fewer panic-zone banks.
|
# --- LaTeX export ---
save_kbl_latex(phi_disp, "Table_Robust_PhiSensitivity",
caption="Robustness: Sensitivity to Partial Run Rate ($\\phi$)")
## Saved: Table_Robust_PhiSensitivity.tex
11.4
Franchise-Horizon Sensitivity (\(T\))
DSSW baseline: δ = 10% (deposit decay rate). Lower δ
means slower deposit attrition, higher cap factor, higher f. We test
robustness of the main regression (Eq. 11) to δ ∈ {5%, 10%, 15%}.
delta_reg_grid <- c(0.05, 0.10, 0.15)
reg_delta <- list()
for (dd in delta_reg_grid) {
cf_dd <- 1 / (y_10yr + dd)
lab <- sprintf("δ=%.0f%% (1/(y+δ)=%.2f)%s", dd*100, cf_dd,
ifelse(dd == delta_decay, " [baseline]", ""))
df_reg_d <- df_crisis_model %>%
mutate(
c_u = ifelse(!is.na(cost_u_raw), cost_u_raw, 0),
f_d = pmax(((1 - beta_u_clipped) * y_10yr - c_u) * cf_dd * mu_decimal, 0) * 100,
f_d_w = winsorize(f_d),
fv_d = standardize_z(f_d_w),
mtm_x_fv_d = mtm_total * fv_d
)
fml_d <- as.formula(paste0("any_fed ~ mtm_total + fv_d + mtm_x_fv_d + ", ctrl_str))
reg_delta[[lab]] <- feols(fml_d, data=df_reg_d, vcov="hetero")
}
msummary(reg_delta, stars=c("*"=.10,"**"=.05,"***"=.01), gof_omit="AIC|BIC|Log|RMSE",
coef_rename=c("mtm_total"="l","fv_d"="f","mtm_x_fv_d"="l x f"),
title="Robustness: Eq. (11) Under Alternative Deposit Decay Rates",
notes="DSSW baseline: δ=10%. l x f should remain significant across decay rates.")
Robustness: Eq. (11) Under Alternative Deposit Decay Rates
| |
δ=5% (1/(y+δ)=11.49) |
δ=10% (1/(y+δ)=7.30) [baseline] |
δ=15% (1/(y+δ)=5.35) |
| * p < 0.1, ** p < 0.05, *** p < 0.01 |
| DSSW baseline: δ=10%. l x f should remain significant across decay rates. |
| (Intercept) |
0.193*** |
0.193*** |
0.193*** |
|
(0.006) |
(0.006) |
(0.006) |
| l |
0.027*** |
0.027*** |
0.027*** |
|
(0.006) |
(0.006) |
(0.006) |
| f |
0.014** |
0.014** |
0.014** |
|
(0.007) |
(0.007) |
(0.007) |
| l x f |
0.017*** |
0.017*** |
0.017*** |
|
(0.006) |
(0.006) |
(0.006) |
| ln_assets |
0.105*** |
0.105*** |
0.105*** |
|
(0.007) |
(0.007) |
(0.007) |
| cash_ratio |
-0.021*** |
-0.021*** |
-0.021*** |
|
(0.006) |
(0.006) |
(0.006) |
| loan_to_deposit |
-0.005 |
-0.005 |
-0.005 |
|
(0.007) |
(0.007) |
(0.007) |
| book_equity_ratio |
-0.018*** |
-0.018*** |
-0.018*** |
|
(0.006) |
(0.006) |
(0.006) |
| wholesale |
0.029*** |
0.029*** |
0.029*** |
|
(0.007) |
(0.007) |
(0.007) |
| roa |
-0.002 |
-0.002 |
-0.002 |
|
(0.006) |
(0.006) |
(0.006) |
| Num.Obs. |
4226 |
4226 |
4226 |
| R2 |
0.109 |
0.109 |
0.109 |
| R2 Adj. |
0.108 |
0.108 |
0.108 |
| Std.Errors |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
Heteroskedasticity-robust |
# --- LaTeX export ---
save_reg_latex(reg_delta, "Table_Robust_DecayRate_Reg",
coef_rename=c("mtm_total"="$\\ell$","fv_d"="$f$","mtm_x_fv_d"="$\\ell \\times f$"),
title="Robustness: Eq.\\ (11) Under Alternative Deposit Decay Rates",
notes="DSSW baseline: $\\delta=10\\%$.")
## Saved: Table_Robust_DecayRate_Reg.tex
PLOTS
gp_colors <- c("No-Run Region"="#1565C0","Panic Zone"="#FF8F00","Fundamental Insolvency"="#C62828")
borr_colors <- c("Fed Borrower"="#C62828","Non-Borrower"="#1565C0")
theme_gp <- theme_minimal(base_size=13) +
theme(plot.title=element_text(face="bold",size=14), plot.subtitle=element_text(color="grey40",size=11),
plot.caption=element_text(color="grey50",size=9,hjust=0), legend.position="bottom",
panel.grid.minor=element_blank(), strip.text=element_text(face="bold",size=11))
Scatter: Three
Regions
p1 <- ggplot(df_crisis_model, aes(x=mtm_total_raw, y=uninsured_lev_raw)) +
geom_point(aes(color=model_region, shape=ifelse(any_fed==1,"Borrower","Non-Borrower")),
alpha=0.5, size=1.8) +
scale_color_manual(values=gp_colors, name="Region") +
scale_shape_manual(values=c("Borrower"=17,"Non-Borrower"=1), name=NULL) +
labs(title="Three-Region Map: MTM Losses x Uninsured Leverage",
subtitle="Triangles = Fed borrowers",
x="MTM Loss / TA (pp)", y="Uninsured Deposits / TA (pp)") +
theme_gp + guides(color=guide_legend(override.aes=list(size=3,alpha=1)))
print(p1); save_figure(p1, "Fig_ThreeRegion_Scatter", 12, 7)

Borrowing Rates
bl <- region_borrow %>%
mutate(`Any Fed`=AnyFed_pct, BTFP=BTFP_pct, DW=DW_pct, FHLB=FHLB_pct) %>%
select(model_region,`Any Fed`,BTFP,DW,FHLB) %>%
pivot_longer(-model_region, names_to="Facility", values_to="Rate") %>%
mutate(Facility=factor(Facility, levels=c("Any Fed","BTFP","DW","FHLB")))
p2 <- ggplot(bl, aes(x=Facility, y=Rate, fill=model_region)) +
geom_col(position=position_dodge(0.75), width=0.65, alpha=0.9) +
geom_text(aes(label=sprintf("%.1f%%",Rate)), position=position_dodge(0.75), vjust=-0.4, size=2.8) +
scale_fill_manual(values=gp_colors) +
scale_y_continuous(labels=function(x) paste0(x,"%"), expand=expansion(mult=c(0,0.15))) +
labs(title="Crisis Borrowing by Theory Region", x=NULL, y="Borrowing Rate (%)") + theme_gp
print(p2); save_figure(p2, "Fig_BorrowRates", 11, 5)

MV Equity
Density
p3 <- ggplot(df_crisis_model %>% mutate(B=ifelse(any_fed==1,"Fed Borrower","Non-Borrower")),
aes(x=emv_pp, fill=B, color=B)) +
geom_density(alpha=0.3, linewidth=0.7) +
geom_vline(xintercept=0, linetype="dashed") +
scale_fill_manual(values=borr_colors) + scale_color_manual(values=borr_colors) +
labs(title="MV Equity: Borrowers vs Non-Borrowers",
subtitle="Dashed = E^MV=0 (solvency threshold)", x="E^MV / TA (pp)", y="Density") + theme_gp
print(p3); save_figure(p3, "Fig_EMV_Density", 12, 5)

Three-Region Number
Line (Theory Figure)
Empirical counterpart of Theory Figure 1. Banks
sorted by total MTM loss, with region shading determined by
bank-specific thresholds. Vertical lines show mean thresholds at each
φ.
# --- Build bank-level data with φ-varying thresholds ---
phi_plot_grid <- c(0.25, 0.50, 0.75, 1.00)
nl_data <- map_dfr(phi_plot_grid, function(phi) {
df_crisis_model %>%
mutate(
th_liq_phi = book_equity_to_total_asset +
safe_div(insured_deposit + (1 - phi) * uninsured_deposit,
insured_deposit + uninsured_deposit, NA_real_) * f_pp,
region_phi = case_when(
is.na(th_liq_phi) | is.na(threshold_solvency) ~ NA_character_,
mtm_total_raw <= th_liq_phi ~ "No-Run Region",
mtm_total_raw <= threshold_solvency ~ "Panic Zone",
TRUE ~ "Fundamental Insolvency"
),
region_phi = factor(region_phi, levels = c("No-Run Region","Panic Zone","Fundamental Insolvency")),
phi_label = sprintf("φ = %d%%", as.integer(phi * 100)),
is_borrower = factor(ifelse(any_fed == 1, "Fed Borrower", "Non-Borrower"),
levels = c("Non-Borrower","Fed Borrower"))
) %>%
filter(!is.na(region_phi)) %>%
select(idrssd, mtm_total_raw, th_liq_phi, threshold_solvency, region_phi, phi_label, is_borrower)
})
# Mean thresholds for vertical lines
thresh_means <- nl_data %>%
group_by(phi_label) %>%
summarise(mean_liq = mean(th_liq_phi, na.rm = TRUE),
mean_sol = mean(threshold_solvency, na.rm = TRUE), .groups = "drop")
p_nl <- ggplot(nl_data, aes(x = mtm_total_raw)) +
geom_histogram(aes(fill = region_phi), bins = 60, alpha = 0.75, color = "white", linewidth = 0.2) +
geom_vline(data = thresh_means, aes(xintercept = mean_liq),
color = "#1565C0", linewidth = 0.9, linetype = "dashed") +
geom_vline(data = thresh_means, aes(xintercept = mean_sol),
color = "#C62828", linewidth = 0.9, linetype = "dotdash") +
facet_wrap(~phi_label, ncol = 2, scales = "free_y") +
scale_fill_manual(values = gp_colors, name = "Region") +
labs(title = "Three-Region Classification Under Alternative Run Assumptions (φ)",
subtitle = "Dashed blue = mean liquidity threshold; dot-dash red = mean solvency threshold",
x = "Total MTM Loss / TA (pp)", y = "Number of Banks",
caption = "Each panel reclassifies all banks using the generalised liquidity threshold ℓ_(φ) = e + [(D^I + (1-φ)D^U)/D]f.") +
theme_gp +
theme(strip.text = element_text(face = "bold", size = 12))
print(p_nl); save_figure(p_nl, "Fig_ThreeRegion_NumberLine", 14, 7)

Region Proportions
by φ
How does the run assumption change the bank
partition?
phi_fine <- seq(0.10, 1.00, by = 0.05)
prop_data <- map_dfr(phi_fine, function(phi) {
df_phi <- df_crisis_model %>%
mutate(
th_liq_phi = book_equity_to_total_asset +
safe_div(insured_deposit + (1 - phi) * uninsured_deposit,
insured_deposit + uninsured_deposit, NA_real_) * f_pp,
region_phi = case_when(
is.na(th_liq_phi) | is.na(threshold_solvency) ~ NA_character_,
mtm_total_raw <= th_liq_phi ~ "No-Run Region",
mtm_total_raw <= threshold_solvency ~ "Panic Zone",
TRUE ~ "Fundamental Insolvency"
)
) %>% filter(!is.na(region_phi))
n_tot <- nrow(df_phi)
tibble(
phi = phi,
Region = c("No-Run Region", "Panic Zone", "Fundamental Insolvency"),
Count = c(sum(df_phi$region_phi == "No-Run Region"),
sum(df_phi$region_phi == "Panic Zone"),
sum(df_phi$region_phi == "Fundamental Insolvency")),
Pct = Count / n_tot * 100
)
}) %>% mutate(Region = factor(Region, levels = c("No-Run Region","Panic Zone","Fundamental Insolvency")))
p_prop <- ggplot(prop_data, aes(x = phi * 100, y = Pct, fill = Region)) +
geom_area(alpha = 0.8, color = "white", linewidth = 0.3) +
geom_vline(xintercept = 100, linetype = "dashed", color = "black", linewidth = 0.5) +
annotate("text", x = 98, y = 5, label = "Baseline (φ=100%)", hjust = 1, size = 3.2, fontface = "italic") +
scale_fill_manual(values = gp_colors) +
scale_x_continuous(breaks = seq(10, 100, 10), labels = function(x) paste0(x, "%")) +
scale_y_continuous(labels = function(y) paste0(y, "%"), expand = expansion(mult = c(0, 0.02))) +
labs(title = "Region Composition Under Alternative Run Assumptions",
subtitle = "Lower φ → milder run → fewer panic-zone banks",
x = "Run fraction (φ)", y = "Share of banks (%)",
caption = "ℓ_(φ) = e + [(D^I + (1-φ)D^U)/D]f. Solvency threshold unchanged.") +
theme_gp
print(p_prop); save_figure(p_prop, "Fig_RegionProportions_Phi", 11, 6)

Threshold Diagram:
Borrowers vs Non-Borrowers
Empirical number-line in the spirit of the theory TikZ
diagram. Each point is a bank plotted at its MTM loss, with
region boundaries shown for the baseline (φ=100%) and a partial-run
scenario (φ=50%).
# Compute bank-level data with both φ=1 and φ=0.50 thresholds
df_diag <- df_crisis_model %>%
mutate(
th_liq_100 = threshold_liquidity,
th_liq_50 = book_equity_to_total_asset +
safe_div(insured_deposit + 0.50 * uninsured_deposit,
insured_deposit + uninsured_deposit, NA_real_) * f_pp,
th_sol = threshold_solvency,
B = ifelse(any_fed == 1, "Fed Borrower", "Non-Borrower"),
rank_loss = rank(mtm_total_raw, ties.method = "first")
) %>% filter(!is.na(th_liq_100))
# Mean thresholds
mn_liq100 <- mean(df_diag$th_liq_100, na.rm = TRUE)
mn_liq50 <- mean(df_diag$th_liq_50, na.rm = TRUE)
mn_sol <- mean(df_diag$th_sol, na.rm = TRUE)
p_diag <- ggplot(df_diag, aes(x = rank_loss, y = mtm_total_raw)) +
geom_point(aes(color = model_region, shape = B), alpha = 0.55, size = 1.5) +
geom_hline(yintercept = mn_liq100, color = "#1565C0", linewidth = 0.8, linetype = "dashed") +
geom_hline(yintercept = mn_liq50, color = "#1565C0", linewidth = 0.8, linetype = "dotted") +
geom_hline(yintercept = mn_sol, color = "#C62828", linewidth = 0.8, linetype = "dotdash") +
annotate("text", x = max(df_diag$rank_loss) * 0.02, y = mn_liq100 + 0.15,
label = "ℓ_ (φ=100%)", color = "#1565C0", hjust = 0, size = 3, fontface = "bold") +
annotate("text", x = max(df_diag$rank_loss) * 0.02, y = mn_liq50 + 0.15,
label = "ℓ_ (φ=50%)", color = "#1565C0", hjust = 0, size = 3, fontface = "italic") +
annotate("text", x = max(df_diag$rank_loss) * 0.02, y = mn_sol + 0.15,
label = "ℓ̄ (solvency)", color = "#C62828", hjust = 0, size = 3, fontface = "bold") +
scale_color_manual(values = gp_colors, name = "Region (φ=100%)") +
scale_shape_manual(values = c("Fed Borrower" = 17, "Non-Borrower" = 1), name = NULL) +
labs(title = "Bank-Level MTM Losses with Theory Thresholds",
subtitle = "Banks ranked by total MTM loss; thresholds shown for φ=100% (baseline) and φ=50%",
x = "Banks (ranked by MTM loss, ascending)", y = "Total MTM Loss / TA (pp)",
caption = "Raising the liquidity threshold (lowering φ) reclassifies marginal panic-zone banks as no-run.") +
theme_gp +
guides(color = guide_legend(override.aes = list(size = 3, alpha = 1)))
print(p_diag); save_figure(p_diag, "Fig_ThresholdDiagram", 14, 5)

Within-Region
Coefficient Plot
pv <- c("ln_assets_raw","mtm_total_raw","mtm_sec_raw","mtm_loan_raw",
"mtm_btfp_raw","uninsured_lev_raw",
"cash_ratio_raw","book_equity_ratio_raw","f_pp","wholesale_raw")
pl <- c("Log(Assets)","MTM Loss (l)","Securities MTM (l_S)","Loan MTM (l_L)",
"BTFP-elig MTM (OMO)","Uninsured/TA",
"Cash/TA","Book Equity","Franchise (f)","Wholesale")
ci_data <- map_dfr(seq_along(pv), function(i) {
df_crisis_model %>% filter(!is.na(!!sym(pv[i]))) %>%
mutate(B=ifelse(any_fed==1,"Fed Borrower","Non-Borrower")) %>%
group_by(model_region, B) %>%
summarise(Mean=mean(!!sym(pv[i]),na.rm=T), SE=sd(!!sym(pv[i]),na.rm=T)/sqrt(n()), .groups="drop") %>%
mutate(lo=Mean-1.96*SE, hi=Mean+1.96*SE, Variable=pl[i])
}) %>% mutate(Variable=factor(Variable, levels=rev(pl)))
p4 <- ggplot(ci_data, aes(x=Mean, y=Variable, color=B, shape=B)) +
geom_pointrange(aes(xmin=lo,xmax=hi), position=position_dodge(0.5), size=0.5) +
facet_wrap(~model_region, scales="free_x") +
scale_color_manual(values=borr_colors) + scale_shape_manual(values=c("Fed Borrower"=17,"Non-Borrower"=16)) +
labs(title="Borrower vs Non-Borrower by Region", x="Mean (raw)", y=NULL) + theme_gp
print(p4); save_figure(p4, "Fig_CoefPlot_ByRegion", 13, 8)

Equity
Waterfall
wf <- df_crisis_model %>%
mutate(B=ifelse(any_fed==1,"Fed Borrower","Non-Borrower")) %>%
group_by(B) %>%
summarise(`Book Equity (e)`=mean(book_equity_ratio_raw,na.rm=T),
`- MTM Loss`=-mean(mtm_total_raw,na.rm=T),
`= Jiang AE`=mean(adjusted_equity_raw,na.rm=T),
`+ Franchise (f)`=mean(f_pp,na.rm=T),
`= E^MV`=mean(emv_pp,na.rm=T), .groups="drop") %>%
pivot_longer(-B, names_to="Component", values_to="Value") %>%
mutate(Component=factor(Component, levels=c("Book Equity (e)","- MTM Loss",
"= Jiang AE","+ Franchise (f)","= E^MV")),
Type=case_when(grepl("^=",Component)~"Sub", grepl("^-",Component)~"Neg", TRUE~"Pos"))
p5 <- ggplot(wf, aes(x=Component, y=Value, fill=Type)) +
geom_col(alpha=0.85, width=0.65) +
geom_hline(yintercept=0, linetype="dashed") +
geom_text(aes(label=sprintf("%.2f",Value)), vjust=ifelse(wf$Value>=0,-0.3,1.3), size=3) +
facet_wrap(~B) +
scale_fill_manual(values=c("Pos"="#43A047","Neg"="#E53935","Sub"="#37474F"), guide="none") +
scale_x_discrete(labels=function(x) str_wrap(x,12)) +
labs(title="Equity Waterfall: Book -> MTM -> Franchise -> MV Equity",
subtitle="Mean (pp of TA)", x=NULL, y="pp of TA") + theme_gp
print(p5); save_figure(p5, "Fig_EquityWaterfall", 12, 6)
