Beta Summary by
Solvency
# ==============================================================================
# PANEL F: SIZE DISTRIBUTION BY SOLVENCY × BORROWER GROUP
# ==============================================================================
# Canonical column order
group_levels <- c("Insolvent_Borrower", "Insolvent_NonBorrower",
"Solvent_Borrower", "Solvent_NonBorrower")
build_size_table <- function(df, grp_var) {
df %>%
filter(!is.na(!!sym(grp_var))) %>%
# Force factor with all four levels so pivot always has 4 columns
mutate(Group = factor(!!sym(grp_var), levels = group_levels)) %>%
group_by(Group, size_cat, .drop = FALSE) %>%
summarise(N = n(), .groups = "drop") %>%
group_by(Group) %>%
mutate(Pct = ifelse(sum(N) > 0, round(100 * N / sum(N), 1), 0)) %>%
ungroup() %>%
mutate(Cell = sprintf("%d (%.1f%%)", N, Pct)) %>%
select(size_cat, Group, Cell) %>%
pivot_wider(names_from = Group, values_from = Cell, values_fill = "0 (0.0%)")
}
# Framework metadata: var name, display label, clean filename suffix
frameworks <- list(
list(var = "mtm_group", label = "Jiang MTM", suffix = "MTM"),
list(var = "idcr_group", label = "IDCR-100%", suffix = "IDCR100"),
list(var = "dssw_group", label = "DSSW Total Franchise", suffix = "DSSW_Total"),
list(var = "dssw_u_group", label = "DSSW Uninsured Franchise", suffix = "DSSW_Uninsured")
)
for (info in frameworks) {
size_tbl <- build_size_table(df_crisis_clean, info$var)
# Group totals for caption
totals <- df_crisis_clean %>%
filter(!is.na(!!sym(info$var))) %>%
count(!!sym(info$var)) %>%
mutate(lbl = sprintf("%s (N=%d)", !!sym(info$var), n)) %>%
pull(lbl) %>% paste(collapse = ", ")
# Clean column names for display
display_names <- c("Size Category",
"Ins. Borrower", "Ins. Non-Borrower",
"Sol. Borrower", "Sol. Non-Borrower")
# HTML display
print(
kbl(size_tbl, format = "html", escape = FALSE,
col.names = display_names,
caption = sprintf("Size Distribution: %s Solvency × Fed Borrower — %s",
info$label, totals)) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, position = "left") %>%
add_header_above(c(" " = 1, "Insolvent" = 2, "Solvent" = 2))
)
# LaTeX
tex_tbl <- size_tbl
names(tex_tbl) <- display_names
kbl_size_tex <- kbl(tex_tbl, format = "latex", booktabs = TRUE, escape = FALSE,
caption = sprintf("Size Distribution: %s Solvency $\\times$ Fed Borrower", info$label)) %>%
kable_styling(latex_options = c("hold_position", "scale_down")) %>%
add_header_above(c(" " = 1, "Insolvent" = 2, "Solvent" = 2), escape = FALSE)
tex_file <- file.path(TABLE_PATH, sprintf("Table_SizeDist_%s.tex", info$suffix))
writeLines(kbl_size_tex, tex_file)
cat(sprintf("Saved: %s\n", tex_file))
}
## <table class="table table-striped table-hover table-condensed" style="width: auto !important; ">
## <caption>Size Distribution: Jiang MTM Solvency × Fed Borrower — Insolvent_Borrower (N=205), Insolvent_NonBorrower (N=620), Solvent_Borrower (N=623), Solvent_NonBorrower (N=2803)</caption>
## <thead>
## <tr>
## <th style="empty-cells: hide;border-bottom:hidden;" colspan="1"></th>
## <th style="border-bottom:hidden;padding-bottom:0; padding-left:3px;padding-right:3px;text-align: center; " colspan="2"><div style="border-bottom: 1px solid #ddd; padding-bottom: 5px; ">Insolvent</div></th>
## <th style="border-bottom:hidden;padding-bottom:0; padding-left:3px;padding-right:3px;text-align: center; " colspan="2"><div style="border-bottom: 1px solid #ddd; padding-bottom: 5px; ">Solvent</div></th>
## </tr>
## <tr>
## <th style="text-align:left;"> Size Category </th>
## <th style="text-align:left;"> Ins. Borrower </th>
## <th style="text-align:left;"> Ins. Non-Borrower </th>
## <th style="text-align:left;"> Sol. Borrower </th>
## <th style="text-align:left;"> Sol. Non-Borrower </th>
## </tr>
## </thead>
## <tbody>
## <tr>
## <td style="text-align:left;"> Small (<$1B) </td>
## <td style="text-align:left;"> 143 (69.8%) </td>
## <td style="text-align:left;"> 545 (87.9%) </td>
## <td style="text-align:left;"> 344 (55.2%) </td>
## <td style="text-align:left;"> 2299 (82.0%) </td>
## </tr>
## <tr>
## <td style="text-align:left;"> Medium ($1B-$100B) </td>
## <td style="text-align:left;"> 61 (69.8%) </td>
## <td style="text-align:left;"> 73 (87.9%) </td>
## <td style="text-align:left;"> 272 (55.2%) </td>
## <td style="text-align:left;"> 496 (82.0%) </td>
## </tr>
## <tr>
## <td style="text-align:left;"> Large (>$100B) </td>
## <td style="text-align:left;"> 1 (69.8%) </td>
## <td style="text-align:left;"> 2 (87.9%) </td>
## <td style="text-align:left;"> 7 (55.2%) </td>
## <td style="text-align:left;"> 8 (82.0%) </td>
## </tr>
## </tbody>
## </table>Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_SizeDist_MTM.tex
## <table class="table table-striped table-hover table-condensed" style="width: auto !important; ">
## <caption>Size Distribution: IDCR-100% Solvency × Fed Borrower — Insolvent_Borrower (N=247), Insolvent_NonBorrower (N=963), Solvent_Borrower (N=581), Solvent_NonBorrower (N=2460)</caption>
## <thead>
## <tr>
## <th style="empty-cells: hide;border-bottom:hidden;" colspan="1"></th>
## <th style="border-bottom:hidden;padding-bottom:0; padding-left:3px;padding-right:3px;text-align: center; " colspan="2"><div style="border-bottom: 1px solid #ddd; padding-bottom: 5px; ">Insolvent</div></th>
## <th style="border-bottom:hidden;padding-bottom:0; padding-left:3px;padding-right:3px;text-align: center; " colspan="2"><div style="border-bottom: 1px solid #ddd; padding-bottom: 5px; ">Solvent</div></th>
## </tr>
## <tr>
## <th style="text-align:left;"> Size Category </th>
## <th style="text-align:left;"> Ins. Borrower </th>
## <th style="text-align:left;"> Ins. Non-Borrower </th>
## <th style="text-align:left;"> Sol. Borrower </th>
## <th style="text-align:left;"> Sol. Non-Borrower </th>
## </tr>
## </thead>
## <tbody>
## <tr>
## <td style="text-align:left;"> Small (<$1B) </td>
## <td style="text-align:left;"> 148 (59.9%) </td>
## <td style="text-align:left;"> 798 (82.9%) </td>
## <td style="text-align:left;"> 339 (58.3%) </td>
## <td style="text-align:left;"> 2046 (83.2%) </td>
## </tr>
## <tr>
## <td style="text-align:left;"> Medium ($1B-$100B) </td>
## <td style="text-align:left;"> 98 (59.9%) </td>
## <td style="text-align:left;"> 161 (82.9%) </td>
## <td style="text-align:left;"> 235 (58.3%) </td>
## <td style="text-align:left;"> 408 (83.2%) </td>
## </tr>
## <tr>
## <td style="text-align:left;"> Large (>$100B) </td>
## <td style="text-align:left;"> 1 (59.9%) </td>
## <td style="text-align:left;"> 4 (82.9%) </td>
## <td style="text-align:left;"> 7 (58.3%) </td>
## <td style="text-align:left;"> 6 (83.2%) </td>
## </tr>
## </tbody>
## </table>Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_SizeDist_IDCR100.tex
## <table class="table table-striped table-hover table-condensed" style="width: auto !important; ">
## <caption>Size Distribution: DSSW Total Franchise Solvency × Fed Borrower — Solvent_Borrower (N=822), Solvent_NonBorrower (N=3404)</caption>
## <thead>
## <tr>
## <th style="empty-cells: hide;border-bottom:hidden;" colspan="1"></th>
## <th style="border-bottom:hidden;padding-bottom:0; padding-left:3px;padding-right:3px;text-align: center; " colspan="2"><div style="border-bottom: 1px solid #ddd; padding-bottom: 5px; ">Insolvent</div></th>
## <th style="border-bottom:hidden;padding-bottom:0; padding-left:3px;padding-right:3px;text-align: center; " colspan="2"><div style="border-bottom: 1px solid #ddd; padding-bottom: 5px; ">Solvent</div></th>
## </tr>
## <tr>
## <th style="text-align:left;"> Size Category </th>
## <th style="text-align:left;"> Ins. Borrower </th>
## <th style="text-align:left;"> Ins. Non-Borrower </th>
## <th style="text-align:left;"> Sol. Borrower </th>
## <th style="text-align:left;"> Sol. Non-Borrower </th>
## </tr>
## </thead>
## <tbody>
## <tr>
## <td style="text-align:left;"> Small (<$1B) </td>
## <td style="text-align:left;"> 0 (0.0%) </td>
## <td style="text-align:left;"> 0 (0.0%) </td>
## <td style="text-align:left;"> 482 (58.6%) </td>
## <td style="text-align:left;"> 2827 (83.0%) </td>
## </tr>
## <tr>
## <td style="text-align:left;"> Medium ($1B-$100B) </td>
## <td style="text-align:left;"> 0 (0.0%) </td>
## <td style="text-align:left;"> 0 (0.0%) </td>
## <td style="text-align:left;"> 332 (58.6%) </td>
## <td style="text-align:left;"> 568 (83.0%) </td>
## </tr>
## <tr>
## <td style="text-align:left;"> Large (>$100B) </td>
## <td style="text-align:left;"> 0 (0.0%) </td>
## <td style="text-align:left;"> 0 (0.0%) </td>
## <td style="text-align:left;"> 8 (58.6%) </td>
## <td style="text-align:left;"> 9 (83.0%) </td>
## </tr>
## </tbody>
## </table>Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_SizeDist_DSSW_Total.tex
## <table class="table table-striped table-hover table-condensed" style="width: auto !important; ">
## <caption>Size Distribution: DSSW Uninsured Franchise Solvency × Fed Borrower — Insolvent_Borrower (N=3), Insolvent_NonBorrower (N=11), Solvent_Borrower (N=819), Solvent_NonBorrower (N=3393)</caption>
## <thead>
## <tr>
## <th style="empty-cells: hide;border-bottom:hidden;" colspan="1"></th>
## <th style="border-bottom:hidden;padding-bottom:0; padding-left:3px;padding-right:3px;text-align: center; " colspan="2"><div style="border-bottom: 1px solid #ddd; padding-bottom: 5px; ">Insolvent</div></th>
## <th style="border-bottom:hidden;padding-bottom:0; padding-left:3px;padding-right:3px;text-align: center; " colspan="2"><div style="border-bottom: 1px solid #ddd; padding-bottom: 5px; ">Solvent</div></th>
## </tr>
## <tr>
## <th style="text-align:left;"> Size Category </th>
## <th style="text-align:left;"> Ins. Borrower </th>
## <th style="text-align:left;"> Ins. Non-Borrower </th>
## <th style="text-align:left;"> Sol. Borrower </th>
## <th style="text-align:left;"> Sol. Non-Borrower </th>
## </tr>
## </thead>
## <tbody>
## <tr>
## <td style="text-align:left;"> Small (<$1B) </td>
## <td style="text-align:left;"> 1 (33.3%) </td>
## <td style="text-align:left;"> 9 (81.8%) </td>
## <td style="text-align:left;"> 481 (58.7%) </td>
## <td style="text-align:left;"> 2818 (83.1%) </td>
## </tr>
## <tr>
## <td style="text-align:left;"> Medium ($1B-$100B) </td>
## <td style="text-align:left;"> 1 (33.3%) </td>
## <td style="text-align:left;"> 2 (81.8%) </td>
## <td style="text-align:left;"> 331 (58.7%) </td>
## <td style="text-align:left;"> 566 (83.1%) </td>
## </tr>
## <tr>
## <td style="text-align:left;"> Large (>$100B) </td>
## <td style="text-align:left;"> 1 (33.3%) </td>
## <td style="text-align:left;"> 0 (81.8%) </td>
## <td style="text-align:left;"> 7 (58.7%) </td>
## <td style="text-align:left;"> 9 (83.1%) </td>
## </tr>
## </tbody>
## </table>Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_SizeDist_DSSW_Uninsured.tex
# ==============================================================================
# IDCR SENSITIVITY: PARTIAL UNINSURED RUNS (25%, 50%, 75%, 100%)
# ==============================================================================
# Jiang et al. (2024) IDCR at 100% assumes ALL uninsured depositors run.
# But Goldstein-Pauzner (2005) implies partial coordination:
# - In the "upper dominance" region, nobody runs regardless.
# - In the "lower dominance" region, everybody runs.
# - In between (panic zone), the fraction running depends on fundamentals.
#
# IDCR(θ) = [MV_Assets − θ × Uninsured_Dep − Insured_Dep] / Insured_Dep
# where θ ∈ {0.25, 0.50, 0.75, 1.00} is the run fraction.
#
# Lower θ = milder run → more banks remain solvent.
# The gap between θ=0.25 and θ=1.00 is the panic zone width.
# ==============================================================================
run_fractions <- c(0.25, 0.50, 0.75, 1.00)
# Compute IDCR at each θ for the crisis clean sample
df_idcr_sens <- df_crisis_clean %>%
mutate(
# MV assets already computed: mv_assets = total_asset * (1 - mtm_loss_to_total_asset / 100)
idcr_025 = safe_div(mv_assets - 0.25 * uninsured_deposit - insured_deposit,
insured_deposit, NA_real_),
idcr_050 = safe_div(mv_assets - 0.50 * uninsured_deposit - insured_deposit,
insured_deposit, NA_real_),
idcr_075 = safe_div(mv_assets - 0.75 * uninsured_deposit - insured_deposit,
insured_deposit, NA_real_),
idcr_100 = idcr_100, # already have this
# Solvency at each threshold
solvent_025 = as.integer(idcr_025 >= 0),
solvent_050 = as.integer(idcr_050 >= 0),
solvent_075 = as.integer(idcr_075 >= 0),
solvent_100 = as.integer(idcr_100 >= 0),
# Panic zone classification
# "Always solvent" = solvent even at 100% run
# "Always insolvent" = insolvent even at 25% run (lower dominance)
# "Panic zone" = solvent at some θ but insolvent at higher θ
zone_class = case_when(
solvent_100 == 1 ~ "Always Solvent (No-Run Region)",
solvent_025 == 0 ~ "Always Insolvent (Lower Dominance)",
TRUE ~ "Panic Zone (Partial Run Vulnerable)"
),
zone_class = factor(zone_class, levels = c(
"Always Solvent (No-Run Region)",
"Panic Zone (Partial Run Vulnerable)",
"Always Insolvent (Lower Dominance)"
)),
# Finer: at which threshold does the bank tip?
tipping_point = case_when(
solvent_100 == 1 ~ "> 100%",
solvent_075 == 1 & solvent_100 == 0 ~ "75%–100%",
solvent_050 == 1 & solvent_075 == 0 ~ "50%–75%",
solvent_025 == 1 & solvent_050 == 0 ~ "25%–50%",
TRUE ~ "< 25%"
),
tipping_point = factor(tipping_point, levels = c(
"< 25%", "25%–50%", "50%–75%", "75%–100%", "> 100%"
))
)
cat("=== IDCR RUN-FRACTION SENSITIVITY ===\n\n")
## === IDCR RUN-FRACTION SENSITIVITY ===
for (theta in c("025", "050", "075", "100")) {
sol_var <- paste0("solvent_", theta)
n_sol <- sum(df_idcr_sens[[sol_var]] == 1, na.rm = TRUE)
n_ins <- sum(df_idcr_sens[[sol_var]] == 0, na.rm = TRUE)
cat(sprintf(" θ = %s%%: Solvent = %d (%.1f%%), Insolvent = %d (%.1f%%)\n",
gsub("0", "", theta), n_sol, 100*n_sol/nrow(df_idcr_sens),
n_ins, 100*n_ins/nrow(df_idcr_sens)))
}
## θ = 25%: Solvent = 4157 (97.8%), Insolvent = 94 (2.2%)
## θ = 5%: Solvent = 4072 (95.8%), Insolvent = 179 (4.2%)
## θ = 75%: Solvent = 3809 (89.6%), Insolvent = 442 (10.4%)
## θ = 1%: Solvent = 3041 (71.5%), Insolvent = 1210 (28.5%)
cat("\n--- Zone Classification ---\n")
##
## --- Zone Classification ---
print(table(df_idcr_sens$zone_class))
##
## Always Solvent (No-Run Region) Panic Zone (Partial Run Vulnerable)
## 3041 1116
## Always Insolvent (Lower Dominance)
## 94
cat("\n--- Tipping Point Distribution ---\n")
##
## --- Tipping Point Distribution ---
print(table(df_idcr_sens$tipping_point))
##
## < 25% 25%–50% 50%–75% 75%–100% > 100%
## 94 85 263 768 3041
# ==============================================================================
# TABLE: Solvency Counts & Borrowing Rates at Each Run Fraction
# ==============================================================================
idcr_sens_summary <- map_dfr(run_fractions, function(theta) {
theta_label <- sprintf("%.0f%%", theta * 100)
sol_var <- sprintf("solvent_%03.0f", theta * 100)
df_idcr_sens %>%
mutate(Solvency = ifelse(!!sym(sol_var) == 1, "Solvent", "Insolvent")) %>%
group_by(Solvency) %>%
summarise(
N = n(),
AnyFed_N = sum(any_fed, na.rm = TRUE),
AnyFed_pct = round(100 * mean(any_fed, na.rm = TRUE), 2),
BTFP_N = sum(btfp_crisis, na.rm = TRUE),
BTFP_pct = round(100 * mean(btfp_crisis, na.rm = TRUE), 2),
DW_N = sum(dw_crisis, na.rm = TRUE),
DW_pct = round(100 * mean(dw_crisis, na.rm = TRUE), 2),
FHLB_N = sum(fhlb_user, na.rm = TRUE),
FHLB_pct = round(100 * mean(fhlb_user, na.rm = TRUE), 2),
.groups = "drop"
) %>%
mutate(Run_Fraction = theta_label)
})
idcr_sens_summary <- idcr_sens_summary %>%
mutate(Run_Fraction = factor(Run_Fraction, levels = c("25%", "50%", "75%", "100%"))) %>%
arrange(Run_Fraction, desc(Solvency))
# HTML display
disp_sens <- idcr_sens_summary %>%
mutate(
`Any Fed` = 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(Run_Fraction, Solvency, N, `Any Fed`, BTFP, DW, FHLB)
kbl(disp_sens, format = "html", escape = FALSE,
col.names = c("Run θ", "Solvency", "N", "Any Fed", "BTFP", "DW", "FHLB"),
caption = "IDCR Solvency & Borrowing Rates Under Partial Uninsured Runs") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, position = "left") %>%
collapse_rows(columns = 1, valign = "middle") %>%
footnote(general = "IDCR(θ) = [MV Assets − θ × Uninsured Dep − Insured Dep] / Insured Dep. Solvent ⟺ IDCR ≥ 0.",
general_title = "")
IDCR Solvency & Borrowing Rates Under Partial Uninsured Runs
|
Run θ
|
Solvency
|
N
|
Any Fed
|
BTFP
|
DW
|
FHLB
|
|
25%
|
Solvent
|
4157
|
806 (19.4%)
|
490 (11.8%)
|
419 (10.1%)
|
295 (7.1%)
|
|
Insolvent
|
94
|
22 (23.4%)
|
11 (11.7%)
|
14 (14.9%)
|
7 (7.4%)
|
|
50%
|
Solvent
|
4072
|
789 (19.4%)
|
485 (11.9%)
|
407 (10.0%)
|
288 (7.1%)
|
|
Insolvent
|
179
|
39 (21.8%)
|
16 (8.9%)
|
26 (14.5%)
|
14 (7.8%)
|
|
75%
|
Solvent
|
3809
|
743 (19.5%)
|
455 (11.9%)
|
386 (10.1%)
|
269 (7.1%)
|
|
Insolvent
|
442
|
85 (19.2%)
|
46 (10.4%)
|
47 (10.6%)
|
33 (7.5%)
|
|
100%
|
Solvent
|
3041
|
581 (19.1%)
|
361 (11.9%)
|
300 (9.9%)
|
224 (7.4%)
|
|
Insolvent
|
1210
|
247 (20.4%)
|
140 (11.6%)
|
133 (11.0%)
|
78 (6.4%)
|
|
IDCR(θ) = [MV Assets − θ × Uninsured Dep − Insured Dep] /
Insured Dep. Solvent ⟺ IDCR ≥ 0.
|
# LaTeX
tex_sens <- idcr_sens_summary %>%
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(Run_Fraction, Solvency, N, AnyFed, BTFP, DW, FHLB)
names(tex_sens) <- c("Run $\\theta$", "Solvency", "N", "Any Fed", "BTFP", "DW", "FHLB")
kbl_sens_tex <- kbl(tex_sens, format = "latex", booktabs = TRUE, escape = FALSE,
caption = "IDCR Solvency and Borrowing Rates Under Partial Uninsured Runs") %>%
kable_styling(latex_options = c("hold_position", "scale_down")) %>%
collapse_rows(columns = 1, valign = "middle")
tex_file_sens <- file.path(TABLE_PATH, "Table_IDCR_RunFraction_Sensitivity.tex")
writeLines(kbl_sens_tex, tex_file_sens)
cat(sprintf("Saved: %s\n", tex_file_sens))
## Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_IDCR_RunFraction_Sensitivity.tex
# ==============================================================================
# PLOT THEME & PALETTE
# ==============================================================================
# Goldstein-Pauzner inspired palette
gp_colors <- c(
"Insolvent_Borrower" = "#C62828", # deep red — run zone, sought help
"Insolvent_NonBorrower" = "#EF9A9A", # light red — run zone, stayed away
"Solvent_Borrower" = "#1565C0", # deep blue — safe zone, precautionary
"Solvent_NonBorrower" = "#90CAF9" # light blue — safe zone, no need
)
gp_labels <- c(
"Insolvent_Borrower" = "Insolvent · Borrower",
"Insolvent_NonBorrower" = "Insolvent · Non-Borrower",
"Solvent_Borrower" = "Solvent · Borrower",
"Solvent_NonBorrower" = "Solvent · Non-Borrower"
)
solvency_colors <- c("Solvent" = "#1565C0", "Insolvent" = "#C62828")
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)
)
# ==============================================================================
# PLOT 1: THE PANIC ZONE — MTM Losses × Uninsured Leverage
# ==============================================================================
# Goldstein-Pauzner (2005): the run equilibrium exists when fundamentals
# are bad enough. The "panic zone" is the region where a bank is solvent
# in the no-run equilibrium but insolvent if depositors run.
#
# Visual: scatter banks on (MTM Loss, Uninsured Deposits) plane.
# The solvency boundary (AE = 0) divides run zone from no-run zone.
# Color by borrower status → shows who accessed the Fed from each zone.
# ==============================================================================
p1 <- ggplot(df_crisis_clean,
aes(x = mtm_total_raw, y = uninsured_lev_raw)) +
# Solvency boundary: AE = 0 ⟹ BookEquity/TA = MTMLoss/TA
# Mark with a reference line (slope = 1 if both on same scale)
geom_hline(yintercept = 0, color = "grey70", linewidth = 0.3) +
geom_vline(xintercept = 0, color = "grey70", linewidth = 0.3) +
# Shade the "run zone" (AE < 0 region: high MTM loss relative to equity)
annotate("rect",
xmin = quantile(df_crisis_clean$mtm_total_raw, 0.75, na.rm = TRUE),
xmax = max(df_crisis_clean$mtm_total_raw, na.rm = TRUE) * 1.02,
ymin = quantile(df_crisis_clean$uninsured_lev_raw, 0.60, na.rm = TRUE),
ymax = max(df_crisis_clean$uninsured_lev_raw, na.rm = TRUE) * 1.02,
fill = "#C62828", alpha = 0.06
) +
annotate("text",
x = quantile(df_crisis_clean$mtm_total_raw, 0.88, na.rm = TRUE),
y = max(df_crisis_clean$uninsured_lev_raw, na.rm = TRUE) * 0.98,
label = "Panic Zone", fontface = "bold.italic", color = "#C62828",
size = 4, alpha = 0.7
) +
# Points
geom_point(aes(color = mtm_group, shape = mtm_group), alpha = 0.55, size = 1.8) +
# Scales
scale_color_manual(values = gp_colors, labels = gp_labels, name = NULL) +
scale_shape_manual(values = c(17, 2, 16, 1), labels = gp_labels, name = NULL) +
labs(
title = "The Panic Zone: Mark-to-Market Losses and Uninsured Deposit Exposure",
subtitle = "Each point is a bank (2022Q4). Solvency: Jiang MTM adjusted equity. Crisis-period Fed borrowing.",
x = "MTM Loss / Total Assets (pp)",
y = "Uninsured Deposits / Total Assets (pp)",
caption = "Filled markers = Fed borrowers. Open markers = non-borrowers.\nPanic zone (shaded): high MTM losses × high uninsured exposure → run-vulnerable."
) +
theme_gp +
guides(color = guide_legend(nrow = 1, override.aes = list(size = 3, alpha = 1)))
print(p1)

save_figure(p1, "Fig_PanicZone_Scatter_MTM", width = 12, height = 7)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## for 'Panic zone (shaded): high MTM losses × high uninsured exposure →
## run-vulnerable.' in 'mbcsToSbcs': -> substituted for → (U+2192)
## Saved: Fig_PanicZone_Scatter_MTM.pdf
# ==============================================================================
# PLOT 2: WHO BORROWS? — Facility Take-Up by Solvency Status
# ==============================================================================
# Grouped bar chart: borrowing rates (%) for each facility, split by
# solvent vs. insolvent, across all four solvency frameworks.
# ==============================================================================
# Build long-format borrowing rate data
build_borrow_rates <- function(df, sol_var, insol_var, framework_label) {
bind_rows(
df %>% filter(!!sym(sol_var) == 1) %>%
summarise(
BTFP = 100 * mean(btfp_crisis, na.rm = TRUE),
DW = 100 * mean(dw_crisis, na.rm = TRUE),
FHLB = 100 * mean(fhlb_user, na.rm = TRUE),
`Any Fed` = 100 * mean(any_fed, na.rm = TRUE),
N = n()
) %>% mutate(Solvency = "Solvent"),
df %>% filter(!!sym(insol_var) == 1) %>%
summarise(
BTFP = 100 * mean(btfp_crisis, na.rm = TRUE),
DW = 100 * mean(dw_crisis, na.rm = TRUE),
FHLB = 100 * mean(fhlb_user, na.rm = TRUE),
`Any Fed` = 100 * mean(any_fed, na.rm = TRUE),
N = n()
) %>% mutate(Solvency = "Insolvent")
) %>%
mutate(Framework = framework_label) %>%
pivot_longer(cols = c(BTFP, DW, FHLB, `Any Fed`),
names_to = "Facility", values_to = "Rate")
}
borrow_long <- bind_rows(
build_borrow_rates(df_crisis_clean, "mtm_solvent", "mtm_insolvent", "Jiang MTM"),
build_borrow_rates(df_crisis_clean, "solvent_idcr_100", "insolvent_idcr_100", "IDCR-100%"),
build_borrow_rates(df_crisis_clean %>% filter(!is.na(dssw_group)),
"dssw_solvent", "dssw_insolvent", "DSSW Total"),
build_borrow_rates(df_crisis_clean %>% filter(!is.na(dssw_u_group)),
"dssw_u_solvent", "dssw_u_insolvent", "DSSW Uninsured")
) %>%
mutate(
Framework = factor(Framework, levels = c("Jiang MTM", "IDCR-100%",
"DSSW Total", "DSSW Uninsured")),
Facility = factor(Facility, levels = c("Any Fed", "BTFP", "DW", "FHLB")),
Solvency = factor(Solvency, levels = c("Solvent", "Insolvent"))
)
p2 <- ggplot(borrow_long, aes(x = Facility, y = Rate, fill = Solvency)) +
geom_col(position = position_dodge(width = 0.75), width = 0.65, alpha = 0.9) +
geom_text(aes(label = sprintf("%.1f%%", Rate)),
position = position_dodge(width = 0.75),
vjust = -0.4, size = 2.8, color = "grey30") +
facet_wrap(~ Framework, nrow = 1) +
scale_fill_manual(values = solvency_colors, name = NULL) +
scale_y_continuous(labels = function(x) paste0(x, "%"), expand = expansion(mult = c(0, 0.15))) +
labs(
title = "Crisis Borrowing Rates: Solvent vs. Insolvent Banks",
subtitle = "Share of banks in each solvency group that borrowed during Mar 8 – May 4, 2023",
x = NULL, y = "Borrowing Rate (%)",
caption = "Solvency measured at 2022Q4 baseline. DSSW panels restricted to banks with deposit betas."
) +
theme_gp +
theme(strip.text = element_text(size = 10))
print(p2)
## Warning: Removed 4 rows containing missing values or values outside the scale range
## (`geom_col()`).
## Warning: Removed 4 rows containing missing values or values outside the scale range
## (`geom_text()`).

save_figure(p2, "Fig_BorrowingRates_BySolvency", width = 12, height = 6)
## Warning: Removed 4 rows containing missing values or values outside the scale range
## (`geom_col()`).
## Removed 4 rows containing missing values or values outside the scale range
## (`geom_text()`).
## Saved: Fig_BorrowingRates_BySolvency.pdf
# ==============================================================================
# PLOT 3: SOLVENCY DISTRIBUTIONS — Density by Borrower Status
# ==============================================================================
# For each solvency measure, overlay densities for Fed borrowers vs.
# non-borrowers. The vertical line at zero marks the run/no-run boundary.
# ==============================================================================
sol_measures <- tribble(
~var, ~label, ~framework,
"adjusted_equity_raw", "Jiang MTM Adj. Equity (pp)", "Jiang MTM",
"idcr_100", "IDCR-100% Ratio", "IDCR-100%",
"adjusted_equity_dssw_raw", "DSSW Total Adj. Equity (pp)", "DSSW Total",
"adjusted_equity_dssw_u_raw", "DSSW Uninsured Adj. Equity (pp)", "DSSW Uninsured"
)
density_data <- map_dfr(1:nrow(sol_measures), function(i) {
v <- sol_measures$var[i]
df_crisis_clean %>%
filter(!is.na(!!sym(v))) %>%
transmute(
value = !!sym(v),
Borrower = ifelse(any_fed == 1, "Fed Borrower", "Non-Borrower"),
Framework = sol_measures$framework[i],
x_label = sol_measures$label[i]
)
})
density_data$Framework <- factor(density_data$Framework,
levels = c("Jiang MTM", "IDCR-100%", "DSSW Total", "DSSW Uninsured"))
p3 <- ggplot(density_data, aes(x = value, fill = Borrower, color = Borrower)) +
geom_density(alpha = 0.3, linewidth = 0.7) +
geom_vline(xintercept = 0, linetype = "dashed", color = "grey30", linewidth = 0.6) +
annotate("text", x = 0, y = Inf, label = " AE = 0\n(Run Boundary)",
hjust = -0.05, vjust = 1.5, size = 3, color = "grey30", fontface = "italic") +
facet_wrap(~ Framework, scales = "free", nrow = 2) +
scale_fill_manual(values = c("Fed Borrower" = "#C62828", "Non-Borrower" = "#1565C0"), name = NULL) +
scale_color_manual(values = c("Fed Borrower" = "#C62828", "Non-Borrower" = "#1565C0"), name = NULL) +
labs(
title = "Solvency Distributions: Fed Borrowers vs. Non-Borrowers",
subtitle = "Dashed line = solvency boundary (AE = 0). Left of boundary = run zone.",
x = "Solvency Measure", y = "Density",
caption = "Crisis period borrowers. Each panel uses a different solvency definition."
) +
theme_gp
print(p3)

save_figure(p3, "Fig_SolvencyDensity_BorrowerVsNon", width = 12, height = 7)
## Saved: Fig_SolvencyDensity_BorrowerVsNon.pdf
# ==============================================================================
# PLOT 4: WHAT MAKES BORROWERS DIFFERENT WITHIN THE RUN ZONE?
# ==============================================================================
# Within-zone comparison (Jiang MTM): for the insolvent subsample and
# solvent subsample separately, show key characteristics of borrowers
# vs. non-borrowers. Uses coefficient-plot style (mean ± 95% CI).
# ==============================================================================
# Key variables that tell the GP story
plot_vars <- c("ln_assets_raw", "mtm_total_raw", "uninsured_lev_raw",
"cash_ratio_raw", "securities_ratio_raw", "book_equity_ratio_raw",
"fhlb_ratio_raw", "wholesale_raw", "collateral_capacity_raw")
plot_labels <- c("Log(Assets)", "MTM Loss / TA", "Uninsured Dep. / TA",
"Cash / TA", "Securities / TA", "Book Equity / TA",
"FHLB / TA", "Wholesale Funding (%)", "Collateral Capacity (%)")
# Build mean + CI for each group × variable
build_ci_data <- function(df, grp_var, vars, labels) {
map_dfr(seq_along(vars), function(i) {
df %>%
filter(!is.na(!!sym(grp_var)) & !is.na(!!sym(vars[i]))) %>%
group_by(Group = !!sym(grp_var)) %>%
summarise(
Mean = mean(!!sym(vars[i]), na.rm = TRUE),
SE = sd(!!sym(vars[i]), na.rm = TRUE) / sqrt(n()),
N = n(),
.groups = "drop"
) %>%
mutate(
CI_lo = Mean - 1.96 * SE,
CI_hi = Mean + 1.96 * SE,
Variable = labels[i]
)
})
}
ci_data <- build_ci_data(df_crisis_clean, "mtm_group", plot_vars, plot_labels) %>%
mutate(
Zone = ifelse(grepl("Insolvent", Group), "Run Zone (Insolvent)", "No-Run Zone (Solvent)"),
Borrower = ifelse(grepl("Borrower$", Group), "Fed Borrower", "Non-Borrower"),
# Standardize within each variable for comparability across panels
Variable = factor(Variable, levels = rev(plot_labels))
)
p4 <- ggplot(ci_data, aes(x = Mean, y = Variable, color = Borrower, shape = Borrower)) +
geom_pointrange(aes(xmin = CI_lo, xmax = CI_hi),
position = position_dodge(width = 0.5), size = 0.5, linewidth = 0.6) +
facet_wrap(~ Zone, scales = "free_x") +
scale_color_manual(values = c("Fed Borrower" = "#C62828", "Non-Borrower" = "#1565C0"), name = NULL) +
scale_shape_manual(values = c("Fed Borrower" = 17, "Non-Borrower" = 16), name = NULL) +
labs(
title = "What Distinguishes Borrowers Within Each Zone? (Jiang MTM)",
subtitle = "Mean ± 95% CI. Within-zone comparison: what drives a bank to access the Fed?",
x = "Mean (raw units)", y = NULL,
caption = "Crisis period (Mar–May 2023). Solvency at 2022Q4. Variables in raw (pre-winsorized) units."
) +
theme_gp +
theme(strip.text = element_text(size = 12))
print(p4)

save_figure(p4, "Fig_WithinZone_Characteristics_MTM", width = 13, height = 8)
## Saved: Fig_WithinZone_Characteristics_MTM.pdf
# ==============================================================================
# PLOT 5: EQUITY WATERFALL — How Franchise Value Shifts the Solvency Boundary
# ==============================================================================
# For each borrower group (Jiang MTM), show how the solvency measure
# changes as we add franchise value:
# Book Equity → minus MTM → plus DFV_Total → or plus DFV_Uninsured
# This shows how many banks "cross the boundary" when we account for
# franchise value — the DSSW insight.
# ==============================================================================
waterfall_data <- df_crisis_clean %>%
filter(!is.na(adjusted_equity_dssw_raw) & !is.na(adjusted_equity_dssw_u_raw)) %>%
mutate(Borrower = ifelse(any_fed == 1, "Fed Borrower", "Non-Borrower")) %>%
group_by(Borrower) %>%
summarise(
`Book Equity/TA` = mean(book_equity_ratio_raw, na.rm = TRUE),
`− MTM Loss/TA` = -mean(mtm_total_raw, na.rm = TRUE),
`= Jiang AE` = mean(adjusted_equity_raw, na.rm = TRUE),
`+ DFV Total` = mean(dfv_raw, na.rm = TRUE),
`= DSSW AE` = mean(adjusted_equity_dssw_raw, na.rm = TRUE),
`+ DFV Uninsured` = mean(dfv_uninsured_raw, na.rm = TRUE),
`= DSSW AE (Unins.)` = mean(adjusted_equity_dssw_u_raw, na.rm = TRUE),
N = n(),
.groups = "drop"
)
waterfall_long <- waterfall_data %>%
pivot_longer(cols = -c(Borrower, N), names_to = "Component", values_to = "Value") %>%
mutate(
Component = factor(Component, levels = c(
"Book Equity/TA", "− MTM Loss/TA", "= Jiang AE",
"+ DFV Total", "= DSSW AE",
"+ DFV Uninsured", "= DSSW AE (Unins.)"
)),
Type = case_when(
grepl("^=", Component) ~ "Subtotal",
grepl("^−", Component) ~ "Negative",
TRUE ~ "Positive"
)
)
p5 <- ggplot(waterfall_long, aes(x = Component, y = Value, fill = Type)) +
geom_col(alpha = 0.85, width = 0.65) +
geom_hline(yintercept = 0, color = "grey30", linewidth = 0.5, linetype = "dashed") +
geom_text(aes(label = sprintf("%.2f", Value)),
vjust = ifelse(waterfall_long$Value >= 0, -0.3, 1.3),
size = 3, color = "grey20") +
facet_wrap(~ Borrower) +
scale_fill_manual(values = c("Positive" = "#43A047", "Negative" = "#E53935",
"Subtotal" = "#37474F"), guide = "none") +
scale_x_discrete(labels = function(x) str_wrap(x, width = 12)) +
labs(
title = "Equity Waterfall: How Franchise Value Shifts the Solvency Boundary",
subtitle = "Mean values (pp of TA). Franchise value can move banks from insolvent → solvent.",
x = NULL, y = "Percentage Points of Total Assets",
caption = "Sample: banks with both β and β^U available. Green = adds equity; Red = subtracts."
) +
theme_gp +
theme(axis.text.x = element_text(size = 9, angle = 0, hjust = 0.5))
print(p5)

save_figure(p5, "Fig_EquityWaterfall_Franchise", width = 12, height = 6)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## for 'Mean values (pp of TA). Franchise value can move banks from insolvent →
## solvent.' in 'mbcsToSbcs': -> substituted for → (U+2192)
## Saved: Fig_EquityWaterfall_Franchise.pdf
# ==============================================================================
# PLOT 6: RECLASSIFICATION FLOWS — Who Crosses the Boundary?
# ==============================================================================
# Alluvial-style: how many banks change from Insolvent → Solvent (or vice
# versa) as we move across solvency definitions? Shows the "rescue" effect
# of franchise value and highlights which borrowers it saves.
# ==============================================================================
reclass <- df_crisis_clean %>%
filter(!is.na(adjusted_equity_dssw_raw)) %>%
mutate(
Jiang = ifelse(mtm_solvent == 1, "Solvent", "Insolvent"),
IDCR = ifelse(solvent_idcr_100 == 1, "Solvent", "Insolvent"),
DSSW_Tot = ifelse(dssw_solvent == 1, "Solvent", "Insolvent"),
Borrower = ifelse(any_fed == 1, "Fed Borrower", "Non-Borrower"),
# Reclassification: Jiang-Insolvent but DSSW-Solvent
Rescued_by_DFV = (Jiang == "Insolvent" & DSSW_Tot == "Solvent")
)
# Summary counts
reclass_summary <- reclass %>%
group_by(Jiang, DSSW_Tot, Borrower) %>%
summarise(N = n(), .groups = "drop") %>%
mutate(
Flow = paste0(Jiang, " → ", DSSW_Tot),
Flow = factor(Flow, levels = c("Insolvent → Solvent", "Insolvent → Insolvent",
"Solvent → Solvent", "Solvent → Insolvent"))
)
p6 <- ggplot(reclass_summary, aes(x = Flow, y = N, fill = Borrower)) +
geom_col(position = position_dodge(width = 0.7), width = 0.6, alpha = 0.9) +
geom_text(aes(label = N),
position = position_dodge(width = 0.7), vjust = -0.3, size = 3.2) +
scale_fill_manual(values = c("Fed Borrower" = "#C62828", "Non-Borrower" = "#1565C0"),
name = NULL) +
labs(
title = "Solvency Reclassification: Jiang MTM → DSSW Total Franchise",
subtitle = "How many banks cross the solvency boundary when franchise value is added?",
x = "Jiang MTM Classification → DSSW Total Classification",
y = "Number of Banks",
caption = "\"Insolvent → Solvent\" = banks rescued by deposit franchise value.\nRestricted to banks with deposit betas."
) +
theme_gp
print(p6)

save_figure(p6, "Fig_Reclassification_Jiang_to_DSSW", width = 10, height = 6)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## for 'Insolvent → Solvent' in 'mbcsToSbcs': -> substituted for → (U+2192)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## for 'Solvent → Solvent' in 'mbcsToSbcs': -> substituted for → (U+2192)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## for 'Jiang MTM Classification → DSSW Total Classification' in 'mbcsToSbcs': ->
## substituted for → (U+2192)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## for 'Solvency Reclassification: Jiang MTM → DSSW Total Franchise' in
## 'mbcsToSbcs': -> substituted for → (U+2192)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## for '"Insolvent → Solvent" = banks rescued by deposit franchise value.' in
## 'mbcsToSbcs': -> substituted for → (U+2192)
## Saved: Fig_Reclassification_Jiang_to_DSSW.pdf
# Print key stat
n_rescued <- sum(reclass$Rescued_by_DFV)
n_rescued_borr <- sum(reclass$Rescued_by_DFV & reclass$Borrower == "Fed Borrower")
cat(sprintf("\n--- Reclassification Summary ---\n"))
##
## --- Reclassification Summary ---
cat(sprintf(" Jiang-Insolvent → DSSW-Solvent (rescued by DFV): %d banks\n", n_rescued))
## Jiang-Insolvent → DSSW-Solvent (rescued by DFV): 813 banks
cat(sprintf(" Of which Fed Borrowers: %d\n", n_rescued_borr))
## Of which Fed Borrowers: 200
cat(sprintf(" Of which Non-Borrowers: %d\n", n_rescued - n_rescued_borr))
## Of which Non-Borrowers: 613
# ==============================================================================
# PLOT 7: STACKED BAR — How Many Banks Become Insolvent as θ Rises?
# ==============================================================================
# As the assumed run fraction increases, more banks cross the IDCR = 0
# boundary. The stacked bar shows Solvent/Insolvent counts at each θ,
# with borrower composition overlaid.
# ==============================================================================
stacked_data <- map_dfr(run_fractions, function(theta) {
sol_var <- sprintf("solvent_%03.0f", theta * 100)
df_idcr_sens %>%
mutate(
Solvency = ifelse(!!sym(sol_var) == 1, "Solvent", "Insolvent"),
Borrower = ifelse(any_fed == 1, "Borrower", "Non-Borrower"),
Group = paste(Solvency, Borrower, sep = " · "),
Run_Pct = sprintf("θ = %d%%", as.integer(theta * 100))
) %>%
count(Run_Pct, Group, Solvency, Borrower, name = "N")
}) %>%
mutate(
Run_Pct = factor(Run_Pct, levels = paste0("θ = ", c(25, 50, 75, 100), "%")),
Group = factor(Group, levels = c(
"Insolvent · Borrower", "Insolvent · Non-Borrower",
"Solvent · Borrower", "Solvent · Non-Borrower"
))
)
p7 <- ggplot(stacked_data, aes(x = Run_Pct, y = N, fill = Group)) +
geom_col(width = 0.65, alpha = 0.9) +
geom_text(aes(label = N), position = position_stack(vjust = 0.5),
size = 3, color = "white", fontface = "bold") +
scale_fill_manual(
values = gp_colors,
labels = gp_labels,
name = NULL
) +
labs(
title = "IDCR Solvency Under Partial Uninsured Runs",
subtitle = "As the assumed run fraction (θ) rises, more banks cross into insolvency",
x = "Assumed Run Fraction", y = "Number of Banks",
caption = "IDCR(θ) = [MV Assets − θ·Uninsured Dep − Insured Dep] / Insured Dep.\nBorrower = used BTFP or DW during crisis period."
) +
theme_gp +
guides(fill = guide_legend(nrow = 2))
print(p7)
## Warning: No shared levels found between `names(values)` of the manual scale and the
## data's fill values.
## No shared levels found between `names(values)` of the manual scale and the
## data's fill values.
## No shared levels found between `names(values)` of the manual scale and the
## data's fill values.

save_figure(p7, "Fig_IDCR_RunFraction_StackedBar", width = 11, height = 6)
## Warning: No shared levels found between `names(values)` of the manual scale and the
## data's fill values.
## No shared levels found between `names(values)` of the manual scale and the
## data's fill values.
## No shared levels found between `names(values)` of the manual scale and the
## data's fill values.
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'θ = 25%' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'θ = 50%' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'θ = 75%' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'θ = 100%' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'As the assumed run fraction (θ) rises, more banks cross
## into insolvency' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'IDCR(θ) = [MV Assets − θ·Uninsured Dep − Insured Dep] /
## Insured Dep.' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'IDCR(θ) = [MV Assets − θ·Uninsured Dep − Insured Dep] /
## Insured Dep.' in 'mbcsToSbcs': for θ (U+03B8)
## Saved: Fig_IDCR_RunFraction_StackedBar.pdf
# ==============================================================================
# PLOT 8: TIPPING POINT — At What Run Fraction Does Each Bank Fail?
# ==============================================================================
# Distribution of tipping points: the θ range at which a bank goes from
# solvent → insolvent. Banks in "< 25%" are fragile under even mild runs.
# Banks in "> 100%" survive full runs. The middle is the panic zone.
# ==============================================================================
tipping_data <- df_idcr_sens %>%
mutate(Borrower = ifelse(any_fed == 1, "Fed Borrower", "Non-Borrower")) %>%
count(tipping_point, Borrower, name = "N") %>%
group_by(Borrower) %>%
mutate(Pct = round(100 * N / sum(N), 1)) %>%
ungroup()
p8 <- ggplot(tipping_data, aes(x = tipping_point, y = N, fill = Borrower)) +
geom_col(position = position_dodge(width = 0.7), width = 0.6, alpha = 0.9) +
geom_text(aes(label = sprintf("%d\n(%.1f%%)", N, Pct)),
position = position_dodge(width = 0.7), vjust = -0.2,
size = 2.8, lineheight = 0.85) +
scale_fill_manual(values = c("Fed Borrower" = "#C62828", "Non-Borrower" = "#1565C0"),
name = NULL) +
scale_y_continuous(expand = expansion(mult = c(0, 0.18))) +
labs(
title = "Tipping Point: At What Run Fraction Does Each Bank Become Insolvent?",
subtitle = "Banks in '< 25%' fail under mild runs. Banks in '> 100%' survive full runs. Middle = panic zone.",
x = "Run Fraction (θ) at Which Bank Tips to Insolvent",
y = "Number of Banks",
caption = "Based on IDCR at θ ∈ {25%, 50%, 75%, 100%}. Tipping = smallest θ where IDCR < 0."
) +
theme_gp
print(p8)

save_figure(p8, "Fig_IDCR_TippingPoint_Distribution", width = 11, height = 6)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'Run Fraction (θ) at Which Bank Tips to Insolvent' in
## 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'Based on IDCR at θ ∈ {25%, 50%, 75%, 100%}. Tipping =
## smallest θ where IDCR < 0.' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'Based on IDCR at θ ∈ {25%, 50%, 75%, 100%}. Tipping =
## smallest θ where IDCR < 0.' in 'mbcsToSbcs': for ∈ (U+2208)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'Based on IDCR at θ ∈ {25%, 50%, 75%, 100%}. Tipping =
## smallest θ where IDCR < 0.' in 'mbcsToSbcs': for θ (U+03B8)
## Saved: Fig_IDCR_TippingPoint_Distribution.pdf
# ==============================================================================
# PLOT 9: BORROWING RATES by θ — How Does the Participation Puzzle Change?
# ==============================================================================
# Line plot: as θ increases, track the borrowing rate separately for
# Solvent and Insolvent groups. If the GP model holds, the insolvent
# borrowing rate should be consistently higher — and the gap should
# widen as θ rises (more marginal banks pushed into insolvency).
# ==============================================================================
rate_by_theta <- map_dfr(run_fractions, function(theta) {
sol_var <- sprintf("solvent_%03.0f", theta * 100)
df_idcr_sens %>%
mutate(Solvency = ifelse(!!sym(sol_var) == 1, "Solvent", "Insolvent")) %>%
group_by(Solvency) %>%
summarise(
AnyFed = 100 * mean(any_fed, na.rm = TRUE),
BTFP = 100 * mean(btfp_crisis, na.rm = TRUE),
DW = 100 * mean(dw_crisis, na.rm = TRUE),
N = n(),
.groups = "drop"
) %>%
mutate(Theta = theta * 100)
}) %>%
pivot_longer(cols = c(AnyFed, BTFP, DW), names_to = "Facility", values_to = "Rate") %>%
mutate(
Facility = factor(Facility, levels = c("AnyFed", "BTFP", "DW"),
labels = c("Any Fed", "BTFP", "DW")),
Solvency = factor(Solvency, levels = c("Solvent", "Insolvent"))
)
p9 <- ggplot(rate_by_theta, aes(x = Theta, y = Rate, color = Solvency,
linetype = Solvency, shape = Solvency)) +
geom_line(linewidth = 0.9) +
geom_point(size = 3) +
facet_wrap(~ Facility) +
scale_color_manual(values = solvency_colors, name = NULL) +
scale_linetype_manual(values = c("Solvent" = "solid", "Insolvent" = "dashed"), name = NULL) +
scale_shape_manual(values = c("Solvent" = 16, "Insolvent" = 17), name = NULL) +
scale_x_continuous(breaks = c(25, 50, 75, 100), labels = function(x) paste0(x, "%")) +
scale_y_continuous(labels = function(x) paste0(round(x, 1), "%")) +
labs(
title = "Borrowing Rates as Run Severity Increases",
subtitle = "At each θ, banks are reclassified as solvent/insolvent via IDCR(θ). Rates track each group.",
x = "Assumed Uninsured Run Fraction (θ)",
y = "Crisis Borrowing Rate (%)",
caption = "As θ rises, the 'insolvent' group grows (more marginal banks enter), potentially diluting the rate."
) +
theme_gp +
theme(strip.text = element_text(size = 12))
print(p9)

save_figure(p9, "Fig_IDCR_BorrowingRate_ByTheta", width = 11, height = 5)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'Assumed Uninsured Run Fraction (θ)' in 'mbcsToSbcs': for
## θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'At each θ, banks are reclassified as solvent/insolvent
## via IDCR(θ). Rates track each group.' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'At each θ, banks are reclassified as solvent/insolvent
## via IDCR(θ). Rates track each group.' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'As θ rises, the 'insolvent' group grows (more marginal
## banks enter), potentially diluting the rate.' in 'mbcsToSbcs': for θ (U+03B8)
## Saved: Fig_IDCR_BorrowingRate_ByTheta.pdf
# ==============================================================================
# PLOT 10: PANIC ZONE MAP — IDCR Edition
# ==============================================================================
# Scatter on (MTM Loss, Uninsured Dep.) plane, colored by zone classification:
# Always Solvent / Panic Zone / Always Insolvent
# Borrowers marked with filled shapes.
# ==============================================================================
p10 <- ggplot(df_idcr_sens,
aes(x = mtm_total_raw, y = uninsured_lev_raw)) +
geom_point(aes(color = zone_class,
shape = ifelse(any_fed == 1, "Borrower", "Non-Borrower")),
alpha = 0.5, size = 1.8) +
scale_color_manual(
values = c(
"Always Solvent (No-Run Region)" = "#1565C0",
"Panic Zone (Partial Run Vulnerable)" = "#FF8F00",
"Always Insolvent (Lower Dominance)" = "#C62828"
),
name = "Zone"
) +
scale_shape_manual(values = c("Borrower" = 17, "Non-Borrower" = 1), name = NULL) +
labs(
title = "IDCR Panic Zone Map: Solvent at 25% Run but Insolvent at 100%",
subtitle = "Orange = panic zone (GP coordination region). Red = insolvent even under mild runs.",
x = "MTM Loss / Total Assets (pp)",
y = "Uninsured Deposits / Total Assets (pp)",
caption = "Zone: Always Solvent (IDCR≥0 at θ=100%), Panic (IDCR≥0 at θ=25% but <0 at θ=100%),\nAlways Insolvent (IDCR<0 even at θ=25%). Triangles = Fed borrowers."
) +
theme_gp +
guides(color = guide_legend(override.aes = list(size = 3, alpha = 1)),
shape = guide_legend(override.aes = list(size = 3, alpha = 1)))
print(p10)

save_figure(p10, "Fig_IDCR_PanicZoneMap", width = 12, height = 7)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'Zone: Always Solvent (IDCR≥0 at θ=100%), Panic (IDCR≥0
## at θ=25% but <0 at θ=100%),' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'Zone: Always Solvent (IDCR≥0 at θ=100%), Panic (IDCR≥0
## at θ=25% but <0 at θ=100%),' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'Zone: Always Solvent (IDCR≥0 at θ=100%), Panic (IDCR≥0
## at θ=25% but <0 at θ=100%),' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'Always Insolvent (IDCR<0 even at θ=25%). Triangles = Fed
## borrowers.' in 'mbcsToSbcs': for θ (U+03B8)
## Saved: Fig_IDCR_PanicZoneMap.pdf
# ==============================================================================
# PLOT 11: IDCR DENSITY ACROSS RUN FRACTIONS
# ==============================================================================
# Overlapping densities of IDCR at each θ, with borrowers vs. non-borrowers.
# Shows how the distribution shifts left as θ increases and more mass
# crosses below zero.
# ==============================================================================
idcr_density_data <- map_dfr(run_fractions, function(theta) {
idcr_var <- sprintf("idcr_%03.0f", theta * 100)
df_idcr_sens %>%
filter(!is.na(!!sym(idcr_var))) %>%
transmute(
IDCR = !!sym(idcr_var),
Theta = sprintf("θ = %d%%", as.integer(theta * 100)),
Borrower = ifelse(any_fed == 1, "Fed Borrower", "Non-Borrower")
)
}) %>%
mutate(Theta = factor(Theta, levels = paste0("θ = ", c(25, 50, 75, 100), "%")))
p11 <- ggplot(idcr_density_data, aes(x = IDCR, fill = Borrower, color = Borrower)) +
geom_density(alpha = 0.25, linewidth = 0.6) +
geom_vline(xintercept = 0, linetype = "dashed", color = "grey30", linewidth = 0.5) +
facet_wrap(~ Theta, nrow = 2) +
scale_fill_manual(values = c("Fed Borrower" = "#C62828", "Non-Borrower" = "#1565C0"), name = NULL) +
scale_color_manual(values = c("Fed Borrower" = "#C62828", "Non-Borrower" = "#1565C0"), name = NULL) +
coord_cartesian(xlim = c(
quantile(df_idcr_sens$idcr_025, 0.01, na.rm = TRUE),
quantile(df_idcr_sens$idcr_025, 0.99, na.rm = TRUE)
)) +
labs(
title = "IDCR Distributions Under Partial Uninsured Runs",
subtitle = "As θ rises, the distribution shifts left → more banks fall below the IDCR = 0 boundary",
x = "IDCR Value", y = "Density",
caption = "Dashed line = solvency boundary (IDCR = 0). Left of line = insolvent."
) +
theme_gp
print(p11)

save_figure(p11, "Fig_IDCR_Density_ByTheta", width = 12, height = 7)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'θ = 75%' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'θ = 100%' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'θ = 25%' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'θ = 50%' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'As θ rises, the distribution shifts left → more banks
## fall below the IDCR = 0 boundary' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## for 'As θ rises, the distribution shifts left → more banks fall below the IDCR
## = 0 boundary' in 'mbcsToSbcs': -> substituted for → (U+2192)
## Saved: Fig_IDCR_Density_ByTheta.pdf
# ==============================================================================
# PLOT 12: PANIC ZONE BORROWING — Who Borrows from Each Zone?
# ==============================================================================
# For the three zone classifications (Always Solvent, Panic Zone,
# Always Insolvent), show facility-level borrowing rates.
# The panic zone is the GP region of interest.
# ==============================================================================
zone_borrow <- df_idcr_sens %>%
group_by(zone_class) %>%
summarise(
N = n(),
`Any Fed` = 100 * mean(any_fed, na.rm = TRUE),
BTFP = 100 * mean(btfp_crisis, na.rm = TRUE),
DW = 100 * mean(dw_crisis, na.rm = TRUE),
FHLB = 100 * mean(fhlb_user, na.rm = TRUE),
.groups = "drop"
) %>%
pivot_longer(cols = c(`Any Fed`, BTFP, DW, FHLB),
names_to = "Facility", values_to = "Rate") %>%
mutate(Facility = factor(Facility, levels = c("Any Fed", "BTFP", "DW", "FHLB")))
# Add N labels for caption
zone_n <- df_idcr_sens %>% count(zone_class) %>%
mutate(lbl = sprintf("%s (N=%d)", zone_class, n)) %>% pull(lbl) %>% paste(collapse = " | ")
p12 <- ggplot(zone_borrow, aes(x = Facility, y = Rate, fill = zone_class)) +
geom_col(position = position_dodge(width = 0.75), width = 0.65, alpha = 0.9) +
geom_text(aes(label = sprintf("%.1f%%", Rate)),
position = position_dodge(width = 0.75), vjust = -0.3, size = 3) +
scale_fill_manual(
values = c(
"Always Solvent (No-Run Region)" = "#1565C0",
"Panic Zone (Partial Run Vulnerable)" = "#FF8F00",
"Always Insolvent (Lower Dominance)" = "#C62828"
),
name = NULL
) +
scale_y_continuous(labels = function(x) paste0(x, "%"), expand = expansion(mult = c(0, 0.15))) +
labs(
title = "Borrowing Rates by IDCR Zone Classification",
subtitle = "Panic zone = solvent at θ=25% but insolvent at θ=100% (GP coordination region)",
x = NULL, y = "Borrowing Rate (%)",
caption = zone_n
) +
theme_gp
print(p12)

save_figure(p12, "Fig_IDCR_ZoneBorrowingRates", width = 10, height = 6)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'Panic zone = solvent at θ=25% but insolvent at θ=100%
## (GP coordination region)' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'Panic zone = solvent at θ=25% but insolvent at θ=100%
## (GP coordination region)' in 'mbcsToSbcs': for θ (U+03B8)
## Saved: Fig_IDCR_ZoneBorrowingRates.pdf
# ==============================================================================
# DESCRIPTIVE STATS: THREE-ZONE COMPARISON (Always Sol / Panic / Always Ins)
# ==============================================================================
cat("================================================================\n")
## ================================================================
cat(" IDCR ZONE DESCRIPTIVE STATISTICS\n")
## IDCR ZONE DESCRIPTIVE STATISTICS
cat("================================================================\n\n")
## ================================================================
df_always_sol <- df_idcr_sens %>% filter(zone_class == "Always Solvent (No-Run Region)")
df_panic <- df_idcr_sens %>% filter(zone_class == "Panic Zone (Partial Run Vulnerable)")
df_always_ins <- df_idcr_sens %>% filter(zone_class == "Always Insolvent (Lower Dominance)")
cat(sprintf("Always Solvent: N = %d (%.1f%%)\n", nrow(df_always_sol),
100 * nrow(df_always_sol) / nrow(df_idcr_sens)))
## Always Solvent: N = 3041 (71.5%)
cat(sprintf("Panic Zone: N = %d (%.1f%%)\n", nrow(df_panic),
100 * nrow(df_panic) / nrow(df_idcr_sens)))
## Panic Zone: N = 1116 (26.3%)
cat(sprintf("Always Insolvent: N = %d (%.1f%%)\n\n", nrow(df_always_ins),
100 * nrow(df_always_ins) / nrow(df_idcr_sens)))
## Always Insolvent: N = 94 (2.2%)
# (a) Panic Zone vs. Always Solvent
desc_panic_vs_sol <- build_desc_table(df_panic, df_always_sol,
"PanicZone", "AlwaysSolvent",
desc_vars, desc_labels)
display_desc_table(desc_panic_vs_sol, "PanicZone", "AlwaysSolvent",
nrow(df_panic), nrow(df_always_sol),
"IDCR Panic Zone vs. Always Solvent")
IDCR Panic Zone vs. Always Solvent: PanicZone (N=1116) vs. AlwaysSolvent
(N=3041)
|
Variable
|
PanicZone Mean (SD)
|
AlwaysSolvent Mean (SD)
|
Difference
|
t-stat
|
|
Log(Assets)
|
12.970 (1.322)
|
12.866 (1.501)
|
0.104**
|
2.17
|
|
Cash / TA
|
7.794 (8.139)
|
8.033 (8.832)
|
-0.239
|
-0.82
|
|
Securities / TA
|
24.960 (15.660)
|
25.780 (15.307)
|
-0.820
|
-1.51
|
|
Loans / TA
|
61.393 (17.485)
|
60.113 (16.539)
|
1.280**
|
2.12
|
|
Book Equity / TA
|
7.830 (2.979)
|
10.252 (6.324)
|
-2.422***
|
-16.67
|
|
ROA
|
1.012 (0.559)
|
1.094 (2.006)
|
-0.082**
|
-2.05
|
|
FHLB / TA
|
2.057 (3.244)
|
2.890 (4.510)
|
-0.833***
|
-6.56
|
|
Loan-to-Deposit
|
69.700 (21.937)
|
71.715 (24.261)
|
-2.015**
|
-2.55
|
|
Wholesale Funding (%)
|
0.400 (1.256)
|
1.234 (3.574)
|
-0.834***
|
-11.12
|
|
MTM Loss / TA
|
5.645 (2.097)
|
5.488 (2.213)
|
0.157**
|
2.11
|
|
Uninsured Dep. / TA
|
28.149 (12.014)
|
22.341 (11.670)
|
5.808***
|
13.92
|
|
Insured Dep. / TA
|
61.242 (12.884)
|
62.484 (13.314)
|
-1.242***
|
-2.73
|
|
Uninsured / Total Dep.
|
31.722 (13.754)
|
26.485 (13.917)
|
5.237***
|
10.84
|
|
Adj. Equity (Jiang MTM)
|
2.185 (4.345)
|
4.764 (7.026)
|
-2.579***
|
-14.16
|
|
IDCR-100%
|
-0.107 (0.154)
|
1.909 (42.930)
|
-2.016***
|
-2.59
|
|
DFV Total (DSSW)
|
78.017 (12.405)
|
77.019 (12.780)
|
0.998**
|
2.27
|
|
Adj. Equity (DSSW Total)
|
80.268 (11.235)
|
81.771 (11.153)
|
-1.503***
|
-3.82
|
|
DFV Uninsured (DSSW)
|
18.498 (8.933)
|
15.210 (8.453)
|
3.288***
|
10.63
|
|
Adj. Equity (DSSW Uninsured)
|
20.750 (10.365)
|
19.962 (10.119)
|
0.788**
|
2.18
|
|
Uninsured Deposit β
|
0.343 (0.108)
|
0.323 (0.100)
|
0.020***
|
5.44
|
|
Collateral Capacity (%)
|
0.009 (0.009)
|
0.011 (0.010)
|
-0.002***
|
-5.53
|
|
Par Benefit
|
0.911 (0.287)
|
0.911 (0.337)
|
0.000
|
0.00
|
|
Stars: *** p<0.01, ** p<0.05, * p<0.10 (Welch
t-test).
|
save_desc_latex(desc_panic_vs_sol, "PanicZone", "AlwaysSolvent",
nrow(df_panic), nrow(df_always_sol),
"IDCR Panic Zone vs.\\ Always Solvent",
"Table_DescStats_IDCR_PanicVsSolvent")
## Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_DescStats_IDCR_PanicVsSolvent.tex
# (b) Panic Zone vs. Always Insolvent
desc_panic_vs_ins <- build_desc_table(df_panic, df_always_ins,
"PanicZone", "AlwaysInsolvent",
desc_vars, desc_labels)
display_desc_table(desc_panic_vs_ins, "PanicZone", "AlwaysInsolvent",
nrow(df_panic), nrow(df_always_ins),
"IDCR Panic Zone vs. Always Insolvent")
IDCR Panic Zone vs. Always Insolvent: PanicZone (N=1116)
vs. AlwaysInsolvent (N=94)
|
Variable
|
PanicZone Mean (SD)
|
AlwaysInsolvent Mean (SD)
|
Difference
|
t-stat
|
|
Log(Assets)
|
12.970 (1.322)
|
13.411 (1.729)
|
-0.441**
|
-2.41
|
|
Cash / TA
|
7.794 (8.139)
|
7.897 (9.506)
|
-0.103
|
-0.10
|
|
Securities / TA
|
24.960 (15.660)
|
22.671 (16.872)
|
2.289
|
1.27
|
|
Loans / TA
|
61.393 (17.485)
|
62.558 (19.782)
|
-1.165
|
-0.55
|
|
Book Equity / TA
|
7.830 (2.979)
|
9.445 (4.597)
|
-1.615***
|
-3.35
|
|
ROA
|
1.012 (0.559)
|
0.737 (1.591)
|
0.275*
|
1.67
|
|
FHLB / TA
|
2.057 (3.244)
|
2.897 (4.418)
|
-0.840*
|
-1.80
|
|
Loan-to-Deposit
|
69.700 (21.937)
|
73.288 (24.933)
|
-3.588
|
-1.35
|
|
Wholesale Funding (%)
|
0.400 (1.256)
|
0.562 (1.347)
|
-0.162
|
-1.12
|
|
MTM Loss / TA
|
5.645 (2.097)
|
4.003 (2.230)
|
1.642***
|
6.89
|
|
Uninsured Dep. / TA
|
28.149 (12.014)
|
18.118 (9.685)
|
10.031***
|
9.45
|
|
Insured Dep. / TA
|
61.242 (12.884)
|
68.579 (11.810)
|
-7.337***
|
-5.74
|
|
Uninsured / Total Dep.
|
31.722 (13.754)
|
21.115 (11.390)
|
10.607***
|
8.52
|
|
Adj. Equity (Jiang MTM)
|
2.185 (4.345)
|
5.442 (5.761)
|
-3.257***
|
-5.35
|
|
IDCR-100%
|
-0.107 (0.154)
|
-0.407 (0.282)
|
0.300***
|
10.20
|
|
DFV Total (DSSW)
|
78.017 (12.405)
|
68.036 (18.268)
|
9.981***
|
5.20
|
|
Adj. Equity (DSSW Total)
|
80.268 (11.235)
|
73.478 (16.795)
|
6.790***
|
3.85
|
|
DFV Uninsured (DSSW)
|
18.498 (8.933)
|
10.310 (6.665)
|
8.188***
|
11.10
|
|
Adj. Equity (DSSW Uninsured)
|
20.750 (10.365)
|
15.752 (9.130)
|
4.998***
|
5.04
|
|
Uninsured Deposit β
|
0.343 (0.108)
|
0.452 (0.179)
|
-0.109***
|
-5.79
|
|
Collateral Capacity (%)
|
0.009 (0.009)
|
0.010 (0.013)
|
-0.001
|
-0.69
|
|
Par Benefit
|
0.911 (0.287)
|
0.809 (0.395)
|
0.102**
|
2.46
|
|
Stars: *** p<0.01, ** p<0.05, * p<0.10 (Welch
t-test).
|
save_desc_latex(desc_panic_vs_ins, "PanicZone", "AlwaysInsolvent",
nrow(df_panic), nrow(df_always_ins),
"IDCR Panic Zone vs.\\ Always Insolvent",
"Table_DescStats_IDCR_PanicVsInsolvent")
## Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_DescStats_IDCR_PanicVsInsolvent.tex
# (c) Within Panic Zone: Borrower vs. Non-Borrower
df_panic_borr <- df_panic %>% filter(any_fed == 1)
df_panic_non <- df_panic %>% filter(any_fed == 0)
desc_panic_bvn <- build_desc_table(df_panic_borr, df_panic_non,
"Borrower", "NonBorrower",
desc_vars, desc_labels)
display_desc_table(desc_panic_bvn, "Borrower", "NonBorrower",
nrow(df_panic_borr), nrow(df_panic_non),
"Within IDCR Panic Zone: Borrower vs. Non-Borrower")
Within IDCR Panic Zone: Borrower vs. Non-Borrower: Borrower (N=225)
vs. NonBorrower (N=891)
|
Variable
|
Borrower Mean (SD)
|
NonBorrower Mean (SD)
|
Difference
|
t-stat
|
|
Log(Assets)
|
13.638 (1.353)
|
12.801 (1.260)
|
0.837***
|
8.40
|
|
Cash / TA
|
6.035 (6.668)
|
8.239 (8.416)
|
-2.204***
|
-4.19
|
|
Securities / TA
|
25.940 (15.661)
|
24.712 (15.659)
|
1.228
|
1.05
|
|
Loans / TA
|
62.729 (16.924)
|
61.055 (17.617)
|
1.674
|
1.31
|
|
Book Equity / TA
|
7.673 (2.757)
|
7.870 (3.032)
|
-0.197
|
-0.93
|
|
ROA
|
1.078 (0.630)
|
0.995 (0.539)
|
0.083*
|
1.81
|
|
FHLB / TA
|
2.862 (3.564)
|
1.854 (3.128)
|
1.008***
|
3.88
|
|
Loan-to-Deposit
|
72.183 (21.994)
|
69.073 (21.890)
|
3.110*
|
1.90
|
|
Wholesale Funding (%)
|
0.602 (1.722)
|
0.350 (1.103)
|
0.252**
|
2.09
|
|
MTM Loss / TA
|
5.975 (2.061)
|
5.561 (2.099)
|
0.414***
|
2.68
|
|
Uninsured Dep. / TA
|
30.053 (12.441)
|
27.668 (11.863)
|
2.385***
|
2.59
|
|
Insured Dep. / TA
|
58.762 (13.143)
|
61.868 (12.749)
|
-3.106***
|
-3.19
|
|
Uninsured / Total Dep.
|
34.325 (14.689)
|
31.064 (13.437)
|
3.261***
|
3.03
|
|
Adj. Equity (Jiang MTM)
|
1.698 (4.222)
|
2.308 (4.369)
|
-0.610*
|
-1.92
|
|
IDCR-100%
|
-0.107 (0.138)
|
-0.107 (0.158)
|
0.000
|
-0.06
|
|
DFV Total (DSSW)
|
75.361 (13.371)
|
78.679 (12.070)
|
-3.318***
|
-3.36
|
|
Adj. Equity (DSSW Total)
|
77.228 (11.778)
|
81.026 (10.972)
|
-3.798***
|
-4.35
|
|
DFV Uninsured (DSSW)
|
19.409 (9.375)
|
18.271 (8.810)
|
1.138
|
1.63
|
|
Adj. Equity (DSSW Uninsured)
|
21.276 (10.668)
|
20.618 (10.290)
|
0.658
|
0.83
|
|
Uninsured Deposit β
|
0.352 (0.116)
|
0.341 (0.105)
|
0.011
|
1.34
|
|
Collateral Capacity (%)
|
0.009 (0.008)
|
0.009 (0.009)
|
0.000
|
-0.63
|
|
Par Benefit
|
0.954 (0.150)
|
0.900 (0.312)
|
0.054***
|
3.75
|
|
Stars: *** p<0.01, ** p<0.05, * p<0.10 (Welch
t-test).
|
save_desc_latex(desc_panic_bvn, "Borrower", "NonBorrower",
nrow(df_panic_borr), nrow(df_panic_non),
"Within IDCR Panic Zone",
"Table_DescStats_IDCR_WithinPanicZone_BorrVsNon")
## Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_DescStats_IDCR_WithinPanicZone_BorrVsNon.tex
# ==============================================================================
# PLOT 13: WITHIN-ZONE CHARACTERISTICS — IDCR Three Zones
# ==============================================================================
# Coefficient-plot style: for each of the three zones, compare
# borrower vs. non-borrower means with 95% CIs.
# ==============================================================================
plot_vars_idcr <- c("ln_assets_raw", "mtm_total_raw", "uninsured_lev_raw",
"cash_ratio_raw", "securities_ratio_raw", "book_equity_ratio_raw",
"fhlb_ratio_raw", "wholesale_raw", "collateral_capacity_raw",
"idcr_100")
plot_labels_idcr <- c("Log(Assets)", "MTM Loss / TA", "Uninsured Dep. / TA",
"Cash / TA", "Securities / TA", "Book Equity / TA",
"FHLB / TA", "Wholesale (%)", "Collateral Cap. (%)",
"IDCR-100%")
ci_zone <- map_dfr(seq_along(plot_vars_idcr), function(i) {
df_idcr_sens %>%
filter(!is.na(!!sym(plot_vars_idcr[i]))) %>%
mutate(Borrower = ifelse(any_fed == 1, "Fed Borrower", "Non-Borrower")) %>%
group_by(zone_class, Borrower) %>%
summarise(
Mean = mean(!!sym(plot_vars_idcr[i]), na.rm = TRUE),
SE = sd(!!sym(plot_vars_idcr[i]), na.rm = TRUE) / sqrt(n()),
N = n(),
.groups = "drop"
) %>%
mutate(
CI_lo = Mean - 1.96 * SE,
CI_hi = Mean + 1.96 * SE,
Variable = plot_labels_idcr[i]
)
}) %>%
mutate(Variable = factor(Variable, levels = rev(plot_labels_idcr)))
# Shorten zone labels for facets
ci_zone <- ci_zone %>%
mutate(Zone = case_when(
grepl("Always Solvent", zone_class) ~ "Always Solvent",
grepl("Panic", zone_class) ~ "Panic Zone",
grepl("Always Insolvent", zone_class) ~ "Always Insolvent"
),
Zone = factor(Zone, levels = c("Always Solvent", "Panic Zone", "Always Insolvent")))
p13 <- ggplot(ci_zone, aes(x = Mean, y = Variable, color = Borrower, shape = Borrower)) +
geom_pointrange(aes(xmin = CI_lo, xmax = CI_hi),
position = position_dodge(width = 0.5), size = 0.5, linewidth = 0.6) +
facet_wrap(~ Zone, scales = "free_x") +
scale_color_manual(values = c("Fed Borrower" = "#C62828", "Non-Borrower" = "#1565C0"), name = NULL) +
scale_shape_manual(values = c("Fed Borrower" = 17, "Non-Borrower" = 16), name = NULL) +
labs(
title = "Bank Characteristics by IDCR Zone: Borrowers vs. Non-Borrowers",
subtitle = "Mean ± 95% CI. Panic zone = solvent at θ=25% but insolvent at θ=100%.",
x = "Mean (raw units)", y = NULL,
caption = "Crisis period. Zones based on IDCR evaluated at θ ∈ {25%, 100%}."
) +
theme_gp +
theme(strip.text = element_text(size = 12))
print(p13)

save_figure(p13, "Fig_IDCR_WithinZone_Characteristics", width = 13, height = 8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'Mean ± 95% CI. Panic zone = solvent at θ=25% but
## insolvent at θ=100%.' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'Mean ± 95% CI. Panic zone = solvent at θ=25% but
## insolvent at θ=100%.' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'Crisis period. Zones based on IDCR evaluated at θ ∈
## {25%, 100%}.' in 'mbcsToSbcs': for θ (U+03B8)
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## conversion failure on 'Crisis period. Zones based on IDCR evaluated at θ ∈
## {25%, 100%}.' in 'mbcsToSbcs': for ∈ (U+2208)
## Saved: Fig_IDCR_WithinZone_Characteristics.pdf