1 Analysis Map

1.1 Main Results: Extensive Margin (Crisis Period)

Five solvency frameworks × four borrower types:

Framework Solvency Definition Columns
A. Full Sample No split (all banks) 4 columns: AnyFed, BTFP, DW, FHLB
B. Jiang MTM AE = Book Equity/TA − MTM/TA ≥ 0 8 columns: 4 × {Solvent, Insolvent}
C. Jiang IDCR-100% (MV Assets − Uninsured − Insured) / Insured ≥ 0 8 columns: 4 × {Solvent, Insolvent}
D. DSSW Franchise AE + DFV ≥ 0, where DFV = (1−β)×D/TA 8 columns: 4 × {Solvent, Insolvent}
E. DSSW Uninsured Franchise AE + DFV_U ≥ 0, where DFV_U = (1−β^U)×Uninsured D/TA 8 columns: 4 × {Solvent, Insolvent}

1.2 Additional Tests

Test Description Solvency Splits
Temporal BTFP & DW: Crisis vs. Arbitrage Full + Jiang + IDCR-100% + DSSW Franchise + DSSW Uninsured Franchise
Deposit Beta Triple interaction: MTM × Uninsured × β^U Full + Jiang + IDCR-100% + DSSW Franchise + DSSW Uninsured Franchise
Facility Complementarity Multinomial choice, DW→BTFP complement, substitution Full sample

1.3 DSSW Theoretical Note

The DSSW (2023/2025) deposit franchise value for bank \(i\) in cross-section: \[\text{DFV}_i = (1 - \beta_i) \times \frac{D_i}{TA_i}\]

Full formula: \(F = D \times [(1-\beta) - c/r] \times [1 - 1/(1+r)^T]\) where \(c \approx 2\%\), \(r\) = long rate, \(T\) = deposit runoff horizon. Since \(c\), \(r\), \(T\) are constant cross-sectionally, all bank-level variation comes from \(\beta_i\) and \(D_i/TA_i\).

Key insight: DFV exists only in the no-run equilibrium. If depositors run, the uninsured franchise is destroyed. A bank that is DSSW-solvent absent a run can become insolvent during one — this is the panic region.

Uninsured Franchise (Framework E): The uninsured deposit franchise is: \[\text{DFV}^U_i = (1 - \beta^U_i) \times \frac{D^U_i}{TA_i}\] This captures the component destroyed by an uninsured depositor run specifically — the relevant margin for Goldstein–Pauzner (2005) style panic equilibria where only uninsured depositors coordinate.


2 SETUP

2.1 Packages

rm(list = ls())

library(data.table)
library(dplyr)
library(tidyr)
library(stringr)
library(lubridate)
library(purrr)
library(tibble)
library(ggrepel)
library(fixest)
library(marginaleffects)
library(nnet)
library(broom)
library(modelsummary)
library(knitr)
library(kableExtra)
library(DescTools)
library(ggplot2)
library(gridExtra)
library(scales)
library(patchwork)
library(readr)
library(readxl)

cat("All packages loaded.\n")
## All packages loaded.

2.2 Helper Functions

winsorize <- function(x, probs = c(0.025, 0.975)) {

if (all(is.na(x))) return(x)
  q <- quantile(x, probs = probs, na.rm = TRUE, names = FALSE)
  pmax(pmin(x, q[2]), q[1])
}

standardize_z <- function(x) {
  if (all(is.na(x))) return(x)
  (x - mean(x, na.rm = TRUE)) / sd(x, na.rm = TRUE)
}

safe_div <- function(num, denom, default = NA_real_) {
  ifelse(is.na(denom) | denom == 0, default, num / denom)
}

create_size_category_3 <- function(assets_thousands) {
  assets_millions <- assets_thousands / 1000
  case_when(
    assets_millions >= 100000 ~ "Large (>$100B)",
    assets_millions >= 1000   ~ "Medium ($1B-$100B)",
    TRUE                      ~ "Small (<$1B)"
  )
}
size_levels_3 <- c("Small (<$1B)", "Medium ($1B-$100B)", "Large (>$100B)")

format_pval <- function(p) {
  case_when(is.na(p) ~ "", p < 0.01 ~ "***", p < 0.05 ~ "**", p < 0.10 ~ "*", TRUE ~ "")
}

safe_writeLines <- function(text, con, max_retries = 5, wait_sec = 2) {
  for (i in seq_len(max_retries)) {
    result <- tryCatch(
      { writeLines(text, con); TRUE },
      error = function(e) {
        if (i < max_retries) Sys.sleep(wait_sec)
        FALSE
      }
    )
    if (isTRUE(result)) return(invisible(NULL))
  }
  warning("Failed to write ", con, " after ", max_retries, " attempts.")
}

save_figure <- function(plot_obj, filename, width = 12, height = 8) {
  ggsave(
    file.path(FIG_PATH, paste0(filename, ".pdf")),
    plot = plot_obj, width = width, height = height, device = "pdf"
  )
  message("Saved: ", filename, ".pdf")
}

2.3 Paths

BASE_PATH   <- "C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025"
DATA_PROC   <- file.path(BASE_PATH, "01_data/processed")
OUTPUT_PATH <- file.path(BASE_PATH, "03_documentation/run_equilibrium_Analysis")
TABLE_PATH  <- file.path(OUTPUT_PATH, "tables")
FIG_PATH    <- file.path(OUTPUT_PATH, "figures")

for (path in c(TABLE_PATH, FIG_PATH)) {
  if (!dir.exists(path)) dir.create(path, recursive = TRUE)
}

2.4 Key Dates

BASELINE_MAIN <- "2022Q4"
BASELINE_ARB  <- "2023Q3"

CRISIS_START     <- as.Date("2023-03-08")
CRISIS_END       <- as.Date("2023-05-04")
ARB_START        <- as.Date("2023-11-15")
ARB_END          <- as.Date("2024-01-24")
DW_DATA_END      <- as.Date("2023-12-31")

cat("Crisis:", format(CRISIS_START), "to", format(CRISIS_END), "\n")
## Crisis: 2023-03-08 to 2023-05-04
cat("Arbitrage:", format(ARB_START), "to", format(ARB_END), "\n")
## Arbitrage: 2023-11-15 to 2024-01-24

3 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 bank flag (from NY Fed CRSP-FRB link via NIC hierarchy)
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)

cat("=== DATA LOADED ===\n")
## === DATA LOADED ===
cat("Call Report:", nrow(call_q), "obs |", n_distinct(call_q$idrssd), "banks\n")
## Call Report: 75989 obs | 5074 banks
cat("BTFP Loans:", nrow(btfp_loans_raw), "\n")
## BTFP Loans: 6734
cat("DW Loans:", nrow(dw_loans_raw), "\n")
## DW Loans: 10008
cat("Public flag:", n_distinct(public_flag$idrssd[public_flag$is_public == 1]), "public banks\n")
## Public flag: 427 public banks

3.1 Exclusions

excluded_banks <- call_q %>%
  filter(period == BASELINE_MAIN, failed_bank == 1 | gsib == 1) %>%
  pull(idrssd)

btfp_loans <- btfp_loans_raw %>% filter(!rssd_id %in% excluded_banks)
dw_loans   <- dw_loans_raw   %>% filter(!rssd_id %in% excluded_banks)

cat("Excluded banks:", length(excluded_banks), "\n")
## Excluded banks: 41

4 BORROWER INDICATORS

create_borrower_indicator <- function(loans_df, date_col, id_col, amount_col,
                                      start_date, end_date, prefix) {
  loans_df %>%
    filter(!!sym(date_col) >= start_date, !!sym(date_col) <= end_date) %>%
    group_by(!!sym(id_col)) %>%
    summarise(
      "{prefix}"       := 1L,
      "{prefix}_amt"   := sum(!!sym(amount_col), na.rm = TRUE),
      "{prefix}_first" := min(!!sym(date_col)),
      .groups = "drop"
    ) %>%
    rename(idrssd = !!sym(id_col))
}

btfp_crisis <- create_borrower_indicator(btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount", CRISIS_START, CRISIS_END, "btfp_crisis")
btfp_arb    <- create_borrower_indicator(btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount", ARB_START, ARB_END, "btfp_arb")
dw_crisis   <- create_borrower_indicator(dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount", CRISIS_START, min(CRISIS_END, DW_DATA_END), "dw_crisis")
dw_arb      <- create_borrower_indicator(dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount", ARB_START, DW_DATA_END, "dw_arb")

cat("BTFP Crisis:", nrow(btfp_crisis), "| Arb:", nrow(btfp_arb), "\n")
## BTFP Crisis: 526 | Arb: 780
cat("DW Crisis:", nrow(dw_crisis), "| Arb:", nrow(dw_arb), "\n")
## DW Crisis: 459 | Arb: 389

5 VARIABLE CONSTRUCTION

# ==============================================================================
# SOLVENCY AUDIT LOG (reviewed against Jiang et al. 2024 & DSSW 2023/2025)
# ==============================================================================
#
# FRAMEWORK A — Jiang MTM Adjusted Equity:
#   AE_i = BookEquity_i/TA_i  −  MTMLoss_i/TA_i
#   Solvent ⟺ AE_i ≥ 0
#   ✓ CORRECT: Both ratios are in percentage points (pp) from Call Report.
#     Subtraction is valid because both share the same TA denominator.
#
# FRAMEWORK B — Jiang IDCR (100% uninsured run):
#   MV_Assets = TA × (1 − MTMLoss/TA / 100)
#   IDCR = (MV_Assets − UninsuredDep − InsuredDep) / InsuredDep
#   Solvent ⟺ IDCR ≥ 0
#   ✓ CORRECT: MTMLoss/TA is in pp → dividing by 100 converts to decimal for
#     the multiplier. MV_Assets, UninsuredDep, InsuredDep are all in the same
#     raw dollar units (thousands, from Call Report). The ratio tells us the
#     cushion available to insured depositors after a 100% uninsured run.
#
# FRAMEWORK C — DSSW Total Deposit Franchise Value:
#   DFV_i = (1 − β_i) × D_i / TA_i × 100   [converted to pp]
#   AdjEquity_DSSW = BookEquity/TA − MTMLoss/TA + DFV
#   Solvent ⟺ AdjEquity_DSSW ≥ 0
#   ✓ CORRECT: β_overall is the total deposit beta from DSSW time-series
#     estimation. D/TA × 100 converts the ratio to pp, matching the equity
#     and MTM terms. Uses dom_deposit (domestic), appropriate for Call Report
#     universe where foreign deposits are negligible for most banks.
#
# FRAMEWORK D — DSSW Uninsured Deposit Franchise Value:  [**NEW — was missing**]
#   DFV^U_i = (1 − β^U_i) × UninsuredDep_i / TA_i × 100
#   AdjEquity_DSSW_U = BookEquity/TA − MTMLoss/TA + DFV^U
#   Solvent ⟺ AdjEquity_DSSW_U ≥ 0
#   This captures the franchise value at risk from an uninsured run only —
#   the margin relevant for Goldstein–Pauzner (2005) panic equilibria.
#   Insured depositors retain FDIC backing, so their franchise is not
#   destroyed by an uninsured run; only the uninsured franchise matters.
# ==============================================================================

construct_analysis_vars <- function(baseline_data) {
  baseline_data %>%
    mutate(
      # === RAW VARIABLES ===
      mtm_total_raw         = mtm_loss_to_total_asset,
      mtm_btfp_raw          = mtm_loss_omo_eligible_to_total_asset,
      mtm_other_raw         = mtm_loss_non_omo_eligible_to_total_asset,
      uninsured_lev_raw     = uninsured_deposit_to_total_asset,
      insured_lev_raw       = r_insured_deposit,
      uninsured_share_raw   = uninsured_to_deposit,
      ln_assets_raw         = log(total_asset),
      cash_ratio_raw        = cash_to_total_asset,
      securities_ratio_raw  = security_to_total_asset,
      loan_ratio_raw        = total_loan_to_total_asset,
      book_equity_ratio_raw = book_equity_to_total_asset,
      roa_raw               = roa,
      fhlb_ratio_raw        = fhlb_to_total_asset,
      loan_to_deposit_raw   = loan_to_deposit,
      wholesale_raw = safe_div(
        fed_fund_purchase + repo + replace_na(other_borrowed_less_than_1yr, 0),
        total_liability, 0) * 100,

      # === SOLVENCY FRAMEWORK A: Jiang MTM Adjusted Equity ===
      # Jiang et al. (2024, JFE): AE = Book Equity/TA − MTM Loss/TA
      # Both terms in percentage points from Call Report ratios.
      adjusted_equity_raw = book_equity_to_total_asset - mtm_loss_to_total_asset,
      mtm_insolvent       = as.integer(adjusted_equity_raw < 0),
      mtm_solvent         = as.integer(adjusted_equity_raw >= 0),

      # === SOLVENCY FRAMEWORK B: Jiang IDCR (100% uninsured run) ===
      # Jiang et al. (2024, JFE): Insured Depositor Coverage Ratio
      # MV(Assets) = TA × (1 − MTM_Loss_pct / 100)
      # IDCR = [MV(Assets) − Uninsured_Dep − Insured_Dep] / Insured_Dep
      # Interpretation: cushion remaining for insured depositors if ALL
      # uninsured depositors withdraw.  IDCR ≥ 0 ⟹ solvent.
      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),

      # === SOLVENCY FRAMEWORK C: DSSW Total Deposit Franchise Value ===
      # DSSW (2023/2026): DFV = (1 − β) × D/TA
      # In no-run equilibrium, the deposit franchise adds value above
      # mark-to-market equity. β_overall = pass-through of market rates
      # to total deposit rates. × 100 to convert to pp matching equity terms.
      dfv_raw = ifelse(!is.na(beta_overall),
        (1 - beta_overall) * (dom_deposit / total_asset) * 100, NA_real_),
      adjusted_equity_dssw_raw = ifelse(!is.na(dfv_raw),
        book_equity_to_total_asset - mtm_loss_to_total_asset + dfv_raw, NA_real_),
      dssw_insolvent = as.integer(!is.na(adjusted_equity_dssw_raw) & adjusted_equity_dssw_raw < 0),
      dssw_solvent   = as.integer(!is.na(adjusted_equity_dssw_raw) & adjusted_equity_dssw_raw >= 0),

      # === SOLVENCY FRAMEWORK D: DSSW Uninsured Deposit Franchise Value ===
      # DFV^U = (1 − β^U) × UninsuredDep/TA
      # This is the franchise component destroyed by an uninsured run.
      # β^U = pass-through of market rates to uninsured deposit rates.
      # Goldstein–Pauzner (2005) relevance: in the panic region, only uninsured
      # depositors coordinate a run.  Insured depositors retain FDIC coverage,
      # so the insured franchise survives. Hence the correct solvency test for
      # the run/no-run boundary uses only the UNINSURED franchise.
      dfv_uninsured_raw = ifelse(!is.na(beta_uninsured),
        (1 - beta_uninsured) * (uninsured_deposit / total_asset) * 100, NA_real_),
      adjusted_equity_dssw_u_raw = ifelse(!is.na(dfv_uninsured_raw),
        book_equity_to_total_asset - mtm_loss_to_total_asset + dfv_uninsured_raw, NA_real_),
      dssw_u_insolvent = as.integer(!is.na(adjusted_equity_dssw_u_raw) & adjusted_equity_dssw_u_raw < 0),
      dssw_u_solvent   = as.integer(!is.na(adjusted_equity_dssw_u_raw) & adjusted_equity_dssw_u_raw >= 0),

      # === UNINSURED DEPOSIT BETA (for triple interaction) ===
      uninsured_beta_raw = ifelse(!is.na(beta_uninsured), beta_uninsured, NA_real_),

      # === WINSORIZED ===
      mtm_total_w         = winsorize(mtm_total_raw),
      mtm_btfp_w          = winsorize(mtm_btfp_raw),
      mtm_other_w         = winsorize(mtm_other_raw),
      uninsured_lev_w     = winsorize(uninsured_lev_raw),
      insured_lev_w       = winsorize(r_insured_deposit),
      adjusted_equity_w   = winsorize(adjusted_equity_raw),
      dfv_w               = winsorize(dfv_raw),
      dfv_uninsured_w     = winsorize(dfv_uninsured_raw),
      adjusted_equity_dssw_w   = winsorize(adjusted_equity_dssw_raw),
      adjusted_equity_dssw_u_w = winsorize(adjusted_equity_dssw_u_raw),
      uninsured_beta_w    = winsorize(uninsured_beta_raw),
      ln_assets_w         = winsorize(ln_assets_raw),
      cash_ratio_w        = winsorize(cash_ratio_raw),
      book_equity_ratio_w = winsorize(book_equity_ratio_raw),
      roa_w               = winsorize(roa_raw),
      loan_to_deposit_w   = winsorize(loan_to_deposit_raw),
      wholesale_w         = winsorize(wholesale_raw),

      # === Z-STANDARDIZED ===
      mtm_total       = standardize_z(mtm_total_w),
      uninsured_lev   = standardize_z(uninsured_lev_w),
      insured_lev     = standardize_z(insured_lev_w),
      adjusted_equity = standardize_z(adjusted_equity_w),
      dfv             = standardize_z(dfv_w),
      dfv_uninsured   = standardize_z(dfv_uninsured_w),
      adjusted_equity_dssw   = standardize_z(adjusted_equity_dssw_w),
      adjusted_equity_dssw_u = standardize_z(adjusted_equity_dssw_u_w),
      uninsured_beta  = standardize_z(uninsured_beta_w),
      ln_assets       = standardize_z(ln_assets_w),
      cash_ratio      = standardize_z(cash_ratio_w),
      book_equity_ratio = standardize_z(book_equity_ratio_w),
      roa             = standardize_z(roa_w),
      loan_to_deposit = standardize_z(loan_to_deposit_w),
      wholesale       = standardize_z(wholesale_w),

      # === INTERACTIONS ===
      mtm_x_uninsured     = mtm_total * uninsured_lev,
      mtm_x_insured       = mtm_total * insured_lev,
      mtm_x_unins_beta    = mtm_total * uninsured_beta,
      unins_x_unins_beta  = uninsured_lev * uninsured_beta,
      mtm_x_unins_x_beta  = mtm_total * uninsured_lev * uninsured_beta,

      # === PAR BENEFIT & COLLATERAL ===
      par_benefit_raw = safe_div(mtm_btfp_raw,
        mtm_btfp_raw + 100 * safe_div(omo_eligible, total_asset * 1000, 0), NA_real_),
      collateral_capacity_raw = safe_div(omo_eligible, total_asset * 1000, 0) * 100,

      size_cat = factor(create_size_category_3(total_asset), levels = size_levels_3)
    ) %>%
    mutate(
      par_benefit_w       = winsorize(par_benefit_raw),
      par_benefit         = standardize_z(par_benefit_w),
      collateral_capacity_w = winsorize(collateral_capacity_raw),
      collateral_capacity = standardize_z(collateral_capacity_w),
      par_x_uninsured     = par_benefit * uninsured_lev,

      # === PAR RECAPITALIZATION (BTFP par lending − market value) / TA ===
      par_recap           = standardize_z(mtm_btfp_w),
      par_recap_x_unins   = par_recap * uninsured_lev,
      par_recap_x_mtm_ui  = par_recap * mtm_x_uninsured
    )
}

6 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") %>%
  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") %>%
  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("=== BASELINES ===\n")
## === BASELINES ===
cat("2022Q4:", nrow(df_2022q4), "banks\n")
## 2022Q4: 4292 banks
cat("  MTM Insolvent:", sum(df_2022q4$mtm_insolvent, na.rm=TRUE), "\n")
##   MTM Insolvent: 825
cat("  IDCR-100% Insolvent:", sum(df_2022q4$insolvent_idcr_100, na.rm=TRUE), "\n")
##   IDCR-100% Insolvent: 1210
cat("  DSSW (Total) Insolvent:", sum(df_2022q4$dssw_insolvent, na.rm=TRUE), "\n")
##   DSSW (Total) Insolvent: 0
cat("  DSSW (Uninsured) Insolvent:", sum(df_2022q4$dssw_u_insolvent, na.rm=TRUE), "\n")
##   DSSW (Uninsured) Insolvent: 14
cat("  Beta coverage:", sum(!is.na(df_2022q4$beta_overall)), "\n")
##   Beta coverage: 4226
cat("  Public banks:", sum(df_2022q4$is_public, na.rm=TRUE), "\n")
##   Public banks: 311
cat("  Non-public banks:", sum(df_2022q4$is_public == 0, na.rm=TRUE), "\n")
##   Non-public banks: 3981
join_all_borrowers <- function(df_base, btfp_df, dw_df, btfp_var, dw_var) {
  df_base %>%
    left_join(btfp_df %>% select(idrssd, starts_with(btfp_var)), by = "idrssd") %>%
    left_join(dw_df   %>% select(idrssd, starts_with(dw_var)),   by = "idrssd") %>%
    mutate(
      "{btfp_var}" := replace_na(!!sym(btfp_var), 0L),
      "{dw_var}"   := replace_na(!!sym(dw_var),   0L),
      fhlb_user = as.integer(abnormal_fhlb_borrowing_10pct == 1),
      any_fed  = as.integer(!!sym(btfp_var) == 1 | !!sym(dw_var) == 1),
      both_fed = as.integer(!!sym(btfp_var) == 1 & !!sym(dw_var) == 1),
      user_group = factor(case_when(
        both_fed == 1 ~ "Both",
        !!sym(btfp_var) == 1 ~ "BTFP_Only",
        !!sym(dw_var)   == 1 ~ "DW_Only",
        TRUE ~ "Neither"
      ), levels = c("Neither", "BTFP_Only", "DW_Only", "Both")),
      non_user = as.integer(any_fed == 0 & fhlb_user == 0)
    )
}

# Crisis
df_crisis <- join_all_borrowers(df_2022q4, btfp_crisis, dw_crisis, "btfp_crisis", "dw_crisis") %>%
  mutate(
    btfp_pct = ifelse(btfp_crisis == 1 & btfp_crisis_amt > 0,
      100 * btfp_crisis_amt / (total_asset * 1000), NA_real_),
    dw_pct = ifelse(dw_crisis == 1 & dw_crisis_amt > 0,
      100 * dw_crisis_amt / (total_asset * 1000), NA_real_)
  )

# Arbitrage
df_arb <- df_2023q3 %>%
  left_join(btfp_arb %>% select(idrssd, btfp_arb, btfp_arb_amt), by = "idrssd") %>%
  left_join(dw_arb   %>% select(idrssd, dw_arb),                 by = "idrssd") %>%
  mutate(
    btfp_arb  = replace_na(btfp_arb, 0L),
    dw_arb    = replace_na(dw_arb, 0L),
    fhlb_user = as.integer(abnormal_fhlb_borrowing_10pct == 1),
    any_fed   = as.integer(btfp_arb == 1 | dw_arb == 1),
    non_user  = as.integer(any_fed == 0 & fhlb_user == 0),
    user_group = factor(case_when(
      btfp_arb == 1 & dw_arb == 1 ~ "Both",
      btfp_arb == 1 ~ "BTFP_Only",
      dw_arb   == 1 ~ "DW_Only",
      TRUE ~ "Neither"
    ), levels = c("Neither", "BTFP_Only", "DW_Only", "Both"))
  )

cat("=== PERIOD COUNTS ===\n")
## === PERIOD COUNTS ===
cat("Crisis:", nrow(df_crisis), "| BTFP:", sum(df_crisis$btfp_crisis),
    "| DW:", sum(df_crisis$dw_crisis), "| Any:", sum(df_crisis$any_fed), "\n")
## Crisis: 4292 | BTFP: 501 | DW: 433 | Any: 828
cat("Arb:", nrow(df_arb), "| BTFP:", sum(df_arb$btfp_arb),
    "| DW:", sum(df_arb$dw_arb), "\n")
## Arb: 4214 | BTFP: 749 | DW: 362

7 SAMPLE CLEANING & FINAL ANALYSIS SAMPLE

7.1 Cleaning Logic

# ==============================================================================
# SAMPLE CLEANING
# ==============================================================================
# Goal: Ensure every observation in the final analysis sample has valid values
# for all key variables so that solvency classifications, descriptive stats,
# and regressions are computed on a consistent sample.
#
# Pre-existing restrictions already applied:
#   - omo_eligible > 0  (BTFP collateral requirement)
#   - G-SIB exclusion   (too-big-to-fail banks)
#   - Failed bank exclusion
#
# Additional cleaning below drops rows with missing/invalid entries in the
# variables needed for solvency classification and regression.
# ==============================================================================

# --- Define required variables for each analysis component ---

# Core balance sheet variables (needed for all specifications)
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")

# Solvency variables
solvency_vars <- c("mtm_loss_to_total_asset",                          # Frameworks A, B
                    "uninsured_deposit", "insured_deposit",             # Framework B (IDCR)
                    "uninsured_deposit_to_total_asset")                 # Regressions

# FHLB indicator
fhlb_vars <- c("abnormal_fhlb_borrowing_10pct")

# All required for the core sample (excludes DSSW betas — those reduce sample)
required_core <- c(core_vars, solvency_vars, fhlb_vars)

# --- Build cleaning summary ---
cleaning_log <- tibble(
  Step = character(),
  Description = character(),
  N_before = integer(),
  N_dropped = integer(),
  N_after = integer()
)

add_log <- function(log, step, desc, n_before, n_after) {
  bind_rows(log, tibble(
    Step = step, Description = desc,
    N_before = n_before, N_dropped = n_before - n_after, N_after = n_after
  ))
}

# ---- CRISIS SAMPLE CLEANING ----
n0 <- nrow(df_crisis)
cleaning_log <- add_log(cleaning_log, "0", "Starting crisis sample (post exclusions, omo_eligible > 0)", n0, n0)

# Step 1: Drop if missing MTM losses (required for all solvency frameworks)
df_crisis_clean <- df_crisis %>% filter(!is.na(mtm_loss_to_total_asset))
n1 <- nrow(df_crisis_clean)
cleaning_log <- add_log(cleaning_log, "1", "Drop missing MTM losses", n0, n1)

# Step 2: Drop if missing core balance sheet ratios
df_crisis_clean <- df_crisis_clean %>%
  filter(if_all(all_of(core_vars), ~ !is.na(.)))
n2 <- nrow(df_crisis_clean)
cleaning_log <- add_log(cleaning_log, "2", "Drop missing core balance sheet vars", n1, n2)

# Step 3: Drop if missing deposit data (needed for IDCR and uninsured leverage)
df_crisis_clean <- df_crisis_clean %>%
  filter(!is.na(uninsured_deposit) & !is.na(insured_deposit) &
         insured_deposit > 0 &  # needed as IDCR denominator
         !is.na(uninsured_deposit_to_total_asset))
n3 <- nrow(df_crisis_clean)
cleaning_log <- add_log(cleaning_log, "3", "Drop missing/zero deposit data (IDCR denom > 0)", n2, n3)

# Step 4: Drop if missing FHLB indicator
df_crisis_clean <- df_crisis_clean %>%
  filter(!is.na(abnormal_fhlb_borrowing_10pct))
n4 <- nrow(df_crisis_clean)
cleaning_log <- add_log(cleaning_log, "4", "Drop missing FHLB borrowing indicator", n3, n4)

# Step 5: Drop if total_asset ≤ 0 or infinite/NaN in log
df_crisis_clean <- df_crisis_clean %>%
  filter(total_asset > 0 & is.finite(ln_assets_raw))
n5 <- nrow(df_crisis_clean)
cleaning_log <- add_log(cleaning_log, "5", "Drop non-positive total assets", n4, n5)

# Step 6: Verify solvency indicators are well-defined
# (Adjusted equity and IDCR should now be non-missing for all obs)
df_crisis_clean <- df_crisis_clean %>%
  filter(!is.na(adjusted_equity_raw) & !is.na(idcr_100))
n6 <- nrow(df_crisis_clean)
cleaning_log <- add_log(cleaning_log, "6", "Verify solvency indicators defined (AE, IDCR)", n5, n6)

# Note: DSSW requires beta_overall / beta_uninsured which reduces the sample.
# We keep these banks in the core sample; DSSW analyses will subset further.
n_dssw_total <- sum(!is.na(df_crisis_clean$adjusted_equity_dssw_raw))
n_dssw_unins <- sum(!is.na(df_crisis_clean$adjusted_equity_dssw_u_raw))
cleaning_log <- add_log(cleaning_log, "6a",
  sprintf("  └ Of which: DSSW total franchise available (beta_overall non-missing)"),
  n6, n_dssw_total)
cleaning_log <- add_log(cleaning_log, "6b",
  sprintf("  └ Of which: DSSW uninsured franchise available (beta_uninsured non-missing)"),
  n6, n_dssw_unins)

cat("=== CRISIS SAMPLE CLEANING COMPLETE ===\n")
## === CRISIS SAMPLE CLEANING COMPLETE ===
cat(sprintf("Final clean crisis sample: %d banks (dropped %d from %d)\n",
    n6, n0 - n6, n0))
## Final clean crisis sample: 4251 banks (dropped 41 from 4292)
# ---- ARBITRAGE SAMPLE CLEANING (parallel) ----
arb_n0 <- nrow(df_arb)
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 & !is.na(uninsured_deposit_to_total_asset)) %>%
  filter(!is.na(abnormal_fhlb_borrowing_10pct)) %>%
  filter(total_asset > 0 & is.finite(log(total_asset))) %>%
  filter(!is.na(adjusted_equity_raw) & !is.na(idcr_100))
arb_n_final <- nrow(df_arb_clean)

cleaning_log <- add_log(cleaning_log, "ARB-0", "Starting arbitrage sample", arb_n0, arb_n0)
cleaning_log <- add_log(cleaning_log, "ARB-final",
  "Final clean arbitrage sample (all steps)", arb_n0, arb_n_final)

cat(sprintf("Final clean arbitrage sample: %d banks (dropped %d from %d)\n",
    arb_n_final, arb_n0 - arb_n_final, arb_n0))
## Final clean arbitrage sample: 4168 banks (dropped 46 from 4214)

7.2 Cleaning Summary Table

# --- Display cleaning log ---
cleaning_log <- cleaning_log %>%
  mutate(Pct_of_start = sprintf("%.1f%%", 100 * N_after / first(N_after[Step == "0"])))

kbl(cleaning_log, format = "html", escape = FALSE,
    caption = "Sample Cleaning Summary",
    col.names = c("Step", "Description", "N Before", "N Dropped", "N After", "% of Start")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE, position = "left") %>%
  row_spec(which(cleaning_log$Step %in% c("6", "ARB-final")),
           bold = TRUE, background = "#e8f5e9")
Sample Cleaning Summary
Step Description N Before N Dropped N After % of Start
0 Starting crisis sample (post exclusions, omo_eligible > 0) 4292 0 4292 100.0%
1 Drop missing MTM losses 4292 10 4282 99.8%
2 Drop missing core balance sheet vars 4282 0 4282 99.8%
3 Drop missing/zero deposit data (IDCR denom > 0) 4282 31 4251 99.0%
4 Drop missing FHLB borrowing indicator 4251 0 4251 99.0%
5 Drop non-positive total assets 4251 0 4251 99.0%
6 Verify solvency indicators defined (AE, IDCR) 4251 0 4251 99.0%
6a └ Of which: DSSW total franchise available (beta_overall non-missing) 4251 25 4226 98.5%
6b └ Of which: DSSW uninsured franchise available (beta_uninsured non-missing) 4251 25 4226 98.5%
ARB-0 Starting arbitrage sample 4214 0 4214 98.2%
ARB-final Final clean arbitrage sample (all steps) 4214 46 4168 97.1%
# --- Save LaTeX ---
tex_clean <- cleaning_log %>%
  select(Step, Description, N_before, N_dropped, N_after)
names(tex_clean) <- c("Step", "Description", "N Before", "Dropped", "N After")

kbl_clean_tex <- kbl(tex_clean, format = "latex", booktabs = TRUE, escape = FALSE,
  caption = "Sample Cleaning Summary") %>%
  kable_styling(latex_options = c("hold_position", "scale_down"))

tex_file_clean <- file.path(TABLE_PATH, "Table_SampleCleaning.tex")
writeLines(kbl_clean_tex, tex_file_clean)
cat(sprintf("Saved: %s\n", tex_file_clean))
## Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_SampleCleaning.tex

7.3 Solvency Cross-Tabulation Diagnostic

# ==============================================================================
# DIAGNOSTIC: How do the five solvency definitions relate to each other?
# ==============================================================================

cat("=== SOLVENCY CROSS-TABULATION (Crisis Clean Sample) ===\n\n")
## === SOLVENCY CROSS-TABULATION (Crisis Clean Sample) ===
# Jiang MTM vs IDCR
cat("--- Jiang MTM × IDCR-100% ---\n")
## --- Jiang MTM × IDCR-100% ---
print(table(MTM = ifelse(df_crisis_clean$mtm_solvent == 1, "MTM_Solvent", "MTM_Insolvent"),
            IDCR = ifelse(df_crisis_clean$solvent_idcr_100 == 1, "IDCR_Solvent", "IDCR_Insolvent")))
##                IDCR
## MTM             IDCR_Insolvent IDCR_Solvent
##   MTM_Insolvent            364          461
##   MTM_Solvent              846         2580
# Among DSSW-available banks: MTM vs DSSW Total
df_dssw_avail <- df_crisis_clean %>% filter(!is.na(adjusted_equity_dssw_raw))
cat("\n--- Jiang MTM × DSSW Total Franchise (N =", nrow(df_dssw_avail), ") ---\n")
## 
## --- Jiang MTM × DSSW Total Franchise (N = 4226 ) ---
print(table(MTM  = ifelse(df_dssw_avail$mtm_solvent == 1, "MTM_Solvent", "MTM_Insolvent"),
            DSSW = ifelse(df_dssw_avail$dssw_solvent == 1, "DSSW_Solvent", "DSSW_Insolvent")))
##                DSSW
## MTM             DSSW_Solvent
##   MTM_Insolvent          813
##   MTM_Solvent           3413
# Among DSSW-U available banks: MTM vs DSSW Uninsured
df_dssw_u_avail <- df_crisis_clean %>% filter(!is.na(adjusted_equity_dssw_u_raw))
cat("\n--- Jiang MTM × DSSW Uninsured Franchise (N =", nrow(df_dssw_u_avail), ") ---\n")
## 
## --- Jiang MTM × DSSW Uninsured Franchise (N = 4226 ) ---
print(table(MTM    = ifelse(df_dssw_u_avail$mtm_solvent == 1, "MTM_Solvent", "MTM_Insolvent"),
            DSSW_U = ifelse(df_dssw_u_avail$dssw_u_solvent == 1, "DSSW_U_Solvent", "DSSW_U_Insolvent")))
##                DSSW_U
## MTM             DSSW_U_Insolvent DSSW_U_Solvent
##   MTM_Insolvent               14            799
##   MTM_Solvent                  0           3413
# DSSW Total vs DSSW Uninsured
df_both_dssw <- df_crisis_clean %>%
  filter(!is.na(adjusted_equity_dssw_raw) & !is.na(adjusted_equity_dssw_u_raw))
cat("\n--- DSSW Total × DSSW Uninsured (N =", nrow(df_both_dssw), ") ---\n")
## 
## --- DSSW Total × DSSW Uninsured (N = 4226 ) ---
print(table(DSSW_Total = ifelse(df_both_dssw$dssw_solvent == 1, "Tot_Solvent", "Tot_Insolvent"),
            DSSW_Unins = ifelse(df_both_dssw$dssw_u_solvent == 1, "Unins_Solvent", "Unins_Insolvent")))
##              DSSW_Unins
## DSSW_Total    Unins_Insolvent Unins_Solvent
##   Tot_Solvent              14          4212
cat("\n--- Summary counts ---\n")
## 
## --- Summary counts ---
cat(sprintf("  Clean crisis N = %d\n", nrow(df_crisis_clean)))
##   Clean crisis N = 4251
cat(sprintf("  MTM insolvent:         %d (%.1f%%)\n",
    sum(df_crisis_clean$mtm_insolvent), 100*mean(df_crisis_clean$mtm_insolvent)))
##   MTM insolvent:         825 (19.4%)
cat(sprintf("  IDCR insolvent:        %d (%.1f%%)\n",
    sum(df_crisis_clean$insolvent_idcr_100), 100*mean(df_crisis_clean$insolvent_idcr_100)))
##   IDCR insolvent:        1210 (28.5%)
cat(sprintf("  DSSW-Total insolvent:  %d / %d (%.1f%%)\n",
    sum(df_crisis_clean$dssw_insolvent, na.rm=TRUE), n_dssw_total,
    100*mean(df_crisis_clean$dssw_insolvent[!is.na(df_crisis_clean$adjusted_equity_dssw_raw)])))
##   DSSW-Total insolvent:  0 / 4226 (0.0%)
cat(sprintf("  DSSW-Unins insolvent:  %d / %d (%.1f%%)\n",
    sum(df_crisis_clean$dssw_u_insolvent, na.rm=TRUE), n_dssw_unins,
    100*mean(df_crisis_clean$dssw_u_insolvent[!is.na(df_crisis_clean$adjusted_equity_dssw_u_raw)])))
##   DSSW-Unins insolvent:  14 / 4226 (0.3%)

8 SOLVENCY-BASED PARTICIPATION: Borrowing Rates

8.1 Panel A: Jiang MTM Solvency — Borrowing by Facility & Period

# ==============================================================================
# PARTICIPATION PUZZLE BY SOLVENCY STATUS
# ==============================================================================

cat("================================================================\n")
## ================================================================
cat("  PANEL A: JIANG MTM SOLVENCY — BORROWING BY FACILITY & PERIOD\n")
##   PANEL A: JIANG MTM SOLVENCY — BORROWING BY FACILITY & PERIOD
cat("================================================================\n\n")
## ================================================================
# --- Crisis Period ---
cat("--- CRISIS PERIOD (Mar 8 – May 4, 2023) ---\n")
## --- CRISIS PERIOD (Mar 8 – May 4, 2023) ---
cat("Solvency measured at 2022Q4 baseline\n\n")
## Solvency measured at 2022Q4 baseline
crisis_mtm <- df_crisis_clean %>%
  mutate(Solvency = ifelse(mtm_solvent == 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"
  )

print(crisis_mtm, width = Inf)
## # A tibble: 2 × 10
##   Solvency      N AnyFed_N AnyFed_pct BTFP_N BTFP_pct  DW_N DW_pct FHLB_N
##   <chr>     <int>    <int>      <dbl>  <int>    <dbl> <int>  <dbl>  <int>
## 1 Insolvent   825      205       24.8    144     17.4    85   10.3     45
## 2 Solvent    3426      623       18.2    357     10.4   348   10.2    257
##   FHLB_pct
##      <dbl>
## 1     5.45
## 2     7.5
cat("\nCrisis Borrowing Rates (Jiang MTM):\n")
## 
## Crisis Borrowing Rates (Jiang MTM):
for (i in 1:nrow(crisis_mtm)) {
  cat(sprintf("  %s (N=%d):\n", crisis_mtm$Solvency[i], crisis_mtm$N[i]))
  cat(sprintf("    Any Fed: %d (%.2f%%)\n", crisis_mtm$AnyFed_N[i], crisis_mtm$AnyFed_pct[i]))
  cat(sprintf("    BTFP:    %d (%.2f%%)\n", crisis_mtm$BTFP_N[i], crisis_mtm$BTFP_pct[i]))
  cat(sprintf("    DW:      %d (%.2f%%)\n", crisis_mtm$DW_N[i], crisis_mtm$DW_pct[i]))
  cat(sprintf("    FHLB:    %d (%.2f%%)\n", crisis_mtm$FHLB_N[i], crisis_mtm$FHLB_pct[i]))
}
##   Insolvent (N=825):
##     Any Fed: 205 (24.85%)
##     BTFP:    144 (17.45%)
##     DW:      85 (10.30%)
##     FHLB:    45 (5.45%)
##   Solvent (N=3426):
##     Any Fed: 623 (18.18%)
##     BTFP:    357 (10.42%)
##     DW:      348 (10.16%)
##     FHLB:    257 (7.50%)
# Chi-squared tests
cat("\n--- Chi-squared Tests: Solvent vs. Insolvent (Crisis) ---\n")
## 
## --- Chi-squared Tests: Solvent vs. Insolvent (Crisis) ---
for (fac in c("any_fed", "btfp_crisis", "dw_crisis", "fhlb_user")) {
  tbl <- table(df_crisis_clean$mtm_solvent, df_crisis_clean[[fac]])
  chi <- tryCatch(chisq.test(tbl, correct = FALSE), error = function(e) NULL)
  if (!is.null(chi)) {
    stars <- case_when(chi$p.value < 0.01 ~ "***", chi$p.value < 0.05 ~ "**",
                       chi$p.value < 0.10 ~ "*", TRUE ~ "")
    cat(sprintf("  %-15s χ²=%.2f p=%.4f %s\n", fac, chi$statistic, chi$p.value, stars))
  }
}
##   any_fed         χ²=18.83 p=0.0000 ***
##   btfp_crisis     χ²=31.64 p=0.0000 ***
##   dw_crisis       χ²=0.02 p=0.9013 
##   fhlb_user       χ²=4.22 p=0.0399 **
# --- Arbitrage Period ---
cat("\n\n--- ARBITRAGE PERIOD (Nov 15, 2023 – Jan 24, 2024) ---\n")
## 
## 
## --- ARBITRAGE PERIOD (Nov 15, 2023 – Jan 24, 2024) ---
cat("Solvency measured at 2023Q3 baseline\n\n")
## Solvency measured at 2023Q3 baseline
arb_mtm <- df_arb_clean %>%
  mutate(Solvency = ifelse(mtm_solvent == 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_arb, na.rm = TRUE),
    BTFP_pct   = round(100 * mean(btfp_arb, na.rm = TRUE), 2),
    DW_N       = sum(dw_arb, na.rm = TRUE),
    DW_pct     = round(100 * mean(dw_arb, 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"
  )

print(arb_mtm, width = Inf)
## # A tibble: 2 × 10
##   Solvency      N AnyFed_N AnyFed_pct BTFP_N BTFP_pct  DW_N DW_pct FHLB_N
##   <chr>     <int>    <int>      <dbl>  <int>    <dbl> <int>  <dbl>  <int>
## 1 Insolvent   801      244       30.5    208     26.0    70   8.74     31
## 2 Solvent    3367      757       22.5    541     16.1   292   8.67    165
##   FHLB_pct
##      <dbl>
## 1     3.87
## 2     4.9
# Combined table
crisis_mtm_long <- crisis_mtm %>% mutate(Period = "Crisis")
arb_mtm_long    <- arb_mtm %>% mutate(Period = "Arbitrage")
combined_mtm    <- bind_rows(crisis_mtm_long, arb_mtm_long)

disp_mtm <- combined_mtm %>%
  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(Period, Solvency, N, `Any Fed`, BTFP, DW, FHLB)

kbl(disp_mtm, format = "html", escape = FALSE,
    caption = "Borrowing Rates by Jiang MTM Solvency Status and Period") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE, position = "left") %>%
  collapse_rows(columns = 1, valign = "middle")
Borrowing Rates by Jiang MTM Solvency Status and Period
Period Solvency N Any Fed BTFP DW FHLB
Crisis Insolvent 825 205 (24.9%) 144 (17.4%) 85 (10.3%) 45 (5.4%)
Solvent 3426 623 (18.2%) 357 (10.4%) 348 (10.2%) 257 (7.5%)
Arbitrage Insolvent 801 244 (30.5%) 208 (26.0%) 70 (8.7%) 31 (3.9%)
Solvent 3367 757 (22.5%) 541 (16.1%) 292 (8.7%) 165 (4.9%)
# Save LaTeX
tex_mtm <- combined_mtm %>%
  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(Period, Solvency, N, AnyFed, BTFP, DW, FHLB)

kbl_mtm_tex <- kbl(tex_mtm, format = "latex", booktabs = TRUE, escape = FALSE,
  caption = "Borrowing Rates by Jiang MTM Solvency Status and Period") %>%
  kable_styling(latex_options = c("hold_position")) %>%
  collapse_rows(columns = 1, valign = "middle")

tex_file_mtm <- file.path(TABLE_PATH, "Table_Participation_ByFacility_MTM.tex")
writeLines(kbl_mtm_tex, tex_file_mtm)
cat(sprintf("Saved: %s\n", tex_file_mtm))
## Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_Participation_ByFacility_MTM.tex

8.2 Panel B: IDCR-100% Solvency — Borrowing by Facility & Period

cat("================================================================\n")
## ================================================================
cat("  PANEL B: IDCR-100%% SOLVENCY — BORROWING BY FACILITY & PERIOD\n")
##   PANEL B: IDCR-100%% SOLVENCY — BORROWING BY FACILITY & PERIOD
cat("================================================================\n\n")
## ================================================================
# --- Crisis Period ---
crisis_idcr <- df_crisis_clean %>%
  mutate(Solvency = ifelse(solvent_idcr_100 == 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"
  )

# --- Arbitrage Period ---
arb_idcr <- df_arb_clean %>%
  mutate(Solvency = ifelse(solvent_idcr_100 == 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_arb, na.rm = TRUE),
    BTFP_pct   = round(100 * mean(btfp_arb, na.rm = TRUE), 2),
    DW_N       = sum(dw_arb, na.rm = TRUE),
    DW_pct     = round(100 * mean(dw_arb, 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"
  )

# Combined + display
crisis_idcr_long <- crisis_idcr %>% mutate(Period = "Crisis")
arb_idcr_long    <- arb_idcr %>% mutate(Period = "Arbitrage")
combined_idcr    <- bind_rows(crisis_idcr_long, arb_idcr_long)

disp_idcr <- combined_idcr %>%
  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(Period, Solvency, N, `Any Fed`, BTFP, DW, FHLB)

kbl(disp_idcr, format = "html", escape = FALSE,
    caption = "Borrowing Rates by IDCR-100% Solvency Status and Period") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE, position = "left") %>%
  collapse_rows(columns = 1, valign = "middle")
Borrowing Rates by IDCR-100% Solvency Status and Period
Period Solvency N Any Fed BTFP DW FHLB
Crisis Insolvent 1210 247 (20.4%) 140 (11.6%) 133 (11.0%) 78 (6.4%)
Solvent 3041 581 (19.1%) 361 (11.9%) 300 (9.9%) 224 (7.4%)
Arbitrage Insolvent 1359 340 (25.0%) 254 (18.7%) 130 (9.6%) 73 (5.4%)
Solvent 2809 661 (23.5%) 495 (17.6%) 232 (8.3%) 123 (4.4%)
# Chi-squared (Crisis)
cat("\n--- Chi-squared Tests: Solvent vs. Insolvent (Crisis, IDCR) ---\n")
## 
## --- Chi-squared Tests: Solvent vs. Insolvent (Crisis, IDCR) ---
for (fac in c("any_fed", "btfp_crisis", "dw_crisis", "fhlb_user")) {
  tbl <- table(df_crisis_clean$solvent_idcr_100, df_crisis_clean[[fac]])
  chi <- tryCatch(chisq.test(tbl, correct = FALSE), error = function(e) NULL)
  if (!is.null(chi)) {
    stars <- case_when(chi$p.value < 0.01 ~ "***", chi$p.value < 0.05 ~ "**",
                       chi$p.value < 0.10 ~ "*", TRUE ~ "")
    cat(sprintf("  %-15s χ²=%.2f p=%.4f %s\n", fac, chi$statistic, chi$p.value, stars))
  }
}
##   any_fed         χ²=0.94 p=0.3313 
##   btfp_crisis     χ²=0.08 p=0.7837 
##   dw_crisis       χ²=1.20 p=0.2732 
##   fhlb_user       χ²=1.11 p=0.2922
# Save LaTeX
tex_idcr <- combined_idcr %>%
  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(Period, Solvency, N, AnyFed, BTFP, DW, FHLB)

kbl_idcr_tex <- kbl(tex_idcr, format = "latex", booktabs = TRUE, escape = FALSE,
  caption = "Borrowing Rates by IDCR-100\\% Solvency Status and Period") %>%
  kable_styling(latex_options = c("hold_position")) %>%
  collapse_rows(columns = 1, valign = "middle")

tex_file_idcr <- file.path(TABLE_PATH, "Table_Participation_ByFacility_IDCR.tex")
writeLines(kbl_idcr_tex, tex_file_idcr)
cat(sprintf("Saved: %s\n", tex_file_idcr))
## Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_Participation_ByFacility_IDCR.tex

8.3 Panel B2: DSSW Franchise — Borrowing by Facility & Period

cat("================================================================\n")
## ================================================================
cat("  PANEL B2: DSSW TOTAL FRANCHISE — BORROWING BY FACILITY & PERIOD\n")
##   PANEL B2: DSSW TOTAL FRANCHISE — BORROWING BY FACILITY & PERIOD
cat("================================================================\n\n")
## ================================================================
# DSSW Total: subset to banks with beta_overall available
df_crisis_dssw <- df_crisis_clean %>% filter(!is.na(adjusted_equity_dssw_raw))
df_arb_dssw    <- df_arb_clean %>% filter(!is.na(adjusted_equity_dssw_raw))

crisis_dssw <- df_crisis_dssw %>%
  mutate(Solvency = ifelse(dssw_solvent == 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(Period = "Crisis")

arb_dssw <- df_arb_dssw %>%
  mutate(Solvency = ifelse(dssw_solvent == 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_arb, na.rm = TRUE),
    BTFP_pct = round(100 * mean(btfp_arb, na.rm = TRUE), 2),
    DW_N = sum(dw_arb, na.rm = TRUE),
    DW_pct = round(100 * mean(dw_arb, 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(Period = "Arbitrage")

combined_dssw <- bind_rows(crisis_dssw, arb_dssw)

disp_dssw <- combined_dssw %>%
  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(Period, Solvency, N, `Any Fed`, BTFP, DW, FHLB)

kbl(disp_dssw, format = "html", escape = FALSE,
    caption = sprintf("Borrowing Rates by DSSW Total Franchise Solvency (N=%d with betas)",
                      nrow(df_crisis_dssw))) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE, position = "left") %>%
  collapse_rows(columns = 1, valign = "middle")
Borrowing Rates by DSSW Total Franchise Solvency (N=4226 with betas)
Period Solvency N Any Fed BTFP DW FHLB
Crisis Solvent 4226 822 (19.4%) 496 (11.7%) 430 (10.2%) 299 (7.1%)
Arbitrage Solvent 4151 998 (24.0%) 746 (18.0%) 362 (8.7%) 196 (4.7%)
# Save LaTeX
tex_dssw <- combined_dssw %>%
  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(Period, Solvency, N, AnyFed, BTFP, DW, FHLB)

kbl_dssw_tex <- kbl(tex_dssw, format = "latex", booktabs = TRUE, escape = FALSE,
  caption = "Borrowing Rates by DSSW Total Franchise Solvency") %>%
  kable_styling(latex_options = c("hold_position")) %>%
  collapse_rows(columns = 1, valign = "middle")

writeLines(kbl_dssw_tex, file.path(TABLE_PATH, "Table_Participation_ByFacility_DSSW_Total.tex"))

8.4 Panel B3: DSSW Uninsured Franchise — Borrowing by Facility & Period

cat("================================================================\n")
## ================================================================
cat("  PANEL B3: DSSW UNINSURED FRANCHISE — BORROWING BY FACILITY\n")
##   PANEL B3: DSSW UNINSURED FRANCHISE — BORROWING BY FACILITY
cat("================================================================\n\n")
## ================================================================
df_crisis_dssw_u <- df_crisis_clean %>% filter(!is.na(adjusted_equity_dssw_u_raw))
df_arb_dssw_u    <- df_arb_clean %>% filter(!is.na(adjusted_equity_dssw_u_raw))

crisis_dssw_u <- df_crisis_dssw_u %>%
  mutate(Solvency = ifelse(dssw_u_solvent == 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(Period = "Crisis")

arb_dssw_u <- df_arb_dssw_u %>%
  mutate(Solvency = ifelse(dssw_u_solvent == 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_arb, na.rm = TRUE),
    BTFP_pct = round(100 * mean(btfp_arb, na.rm = TRUE), 2),
    DW_N = sum(dw_arb, na.rm = TRUE),
    DW_pct = round(100 * mean(dw_arb, 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(Period = "Arbitrage")

combined_dssw_u <- bind_rows(crisis_dssw_u, arb_dssw_u)

disp_dssw_u <- combined_dssw_u %>%
  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(Period, Solvency, N, `Any Fed`, BTFP, DW, FHLB)

kbl(disp_dssw_u, format = "html", escape = FALSE,
    caption = sprintf("Borrowing Rates by DSSW Uninsured Franchise Solvency (N=%d with β^U)",
                      nrow(df_crisis_dssw_u))) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE, position = "left") %>%
  collapse_rows(columns = 1, valign = "middle")
Borrowing Rates by DSSW Uninsured Franchise Solvency (N=4226 with β^U)
Period Solvency N Any Fed BTFP DW FHLB
Crisis Insolvent 14 3 (21.4%) 2 (14.3%) 1 (7.1%) 0 (0.0%)
Solvent 4212 819 (19.4%) 494 (11.7%) 429 (10.2%) 299 (7.1%)
Arbitrage Insolvent 19 6 (31.6%) 4 (21.1%) 3 (15.8%) 0 (0.0%)
Solvent 4132 992 (24.0%) 742 (18.0%) 359 (8.7%) 196 (4.7%)
# Save LaTeX
tex_dssw_u <- combined_dssw_u %>%
  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(Period, Solvency, N, AnyFed, BTFP, DW, FHLB)

kbl_dssw_u_tex <- kbl(tex_dssw_u, format = "latex", booktabs = TRUE, escape = FALSE,
  caption = "Borrowing Rates by DSSW Uninsured Franchise Solvency") %>%
  kable_styling(latex_options = c("hold_position")) %>%
  collapse_rows(columns = 1, valign = "middle")

writeLines(kbl_dssw_u_tex, file.path(TABLE_PATH, "Table_Participation_ByFacility_DSSW_Uninsured.tex"))

9 DESCRIPTIVE STATISTICS

9.1 Reusable Summary Function

# ==============================================================================
# COMPREHENSIVE DESCRIPTIVE STATISTICS FUNCTION
# ==============================================================================
# Produces summary tables for any pair of subgroups with:
#   - Mean, SD, Median, N_obs for each group
#   - Welch t-test for difference in means
#   - Wilcoxon rank-sum test for difference in medians (robustness)
# ==============================================================================

# Variables to summarize across all panels
desc_vars <- c("ln_assets_raw", "cash_ratio_raw", "securities_ratio_raw",
  "loan_ratio_raw", "book_equity_ratio_raw", "roa_raw", "fhlb_ratio_raw",
  "loan_to_deposit_raw", "wholesale_raw", "mtm_total_raw", "uninsured_lev_raw",
  "insured_lev_raw", "uninsured_share_raw",
  "adjusted_equity_raw", "idcr_100",
  "dfv_raw", "adjusted_equity_dssw_raw",
  "dfv_uninsured_raw", "adjusted_equity_dssw_u_raw",
  "uninsured_beta_raw", "collateral_capacity_raw", "par_benefit_raw")

desc_labels <- c("Log(Assets)", "Cash / TA", "Securities / TA",
  "Loans / TA", "Book Equity / TA", "ROA", "FHLB / TA",
  "Loan-to-Deposit", "Wholesale Funding (%)", "MTM Loss / TA",
  "Uninsured Dep. / TA", "Insured Dep. / TA", "Uninsured / Total Dep.",
  "Adj. Equity (Jiang MTM)", "IDCR-100%",
  "DFV Total (DSSW)", "Adj. Equity (DSSW Total)",
  "DFV Uninsured (DSSW)", "Adj. Equity (DSSW Uninsured)",
  "Uninsured Deposit β", "Collateral Capacity (%)", "Par Benefit")

build_desc_table <- function(df_a, df_b, label_a, label_b, vars, labels) {
  # Filter to variables that exist in both dataframes
  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, function(p) {
    if (is.na(p)) "" else if (p < 0.01) "***" else if (p < 0.05) "**" else if (p < 0.10) "*" else ""
  })

  # Wilcoxon rank-sum for median comparison
  results$wilcox_p <- sapply(avail, function(v) {
    wt <- tryCatch(wilcox.test(df_a[[v]], df_b[[v]]), error = function(e) NULL)
    if (!is.null(wt)) round(wt$p.value, 4) else NA_real_
  })

  return(results)
}

# --- Display helper ---
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 = "Stars: *** p<0.01, ** p<0.05, * p<0.10 (Welch t-test).",
             general_title = "")
}

# --- LaTeX save helper ---
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 = "Stars: *** p$<$0.01, ** p$<$0.05, * p$<$0.10 (Welch t-test).",
             escape = FALSE, general_title = "")

  tex_file <- file.path(TABLE_PATH, paste0(filename, ".tex"))
  writeLines(kbl_tex, tex_file)
  cat(sprintf("Saved: %s\n", tex_file))
}

9.2 Panel C1: Desc Stats — Jiang MTM Solvent vs. Insolvent

cat("================================================================\n")
## ================================================================
cat("  PANEL C1: DESCRIPTIVE STATS BY JIANG MTM SOLVENCY (2022Q4)\n")
##   PANEL C1: DESCRIPTIVE STATS BY JIANG MTM SOLVENCY (2022Q4)
cat("================================================================\n\n")
## ================================================================
df_sol_mtm <- df_crisis_clean %>% filter(mtm_solvent == 1)
df_ins_mtm <- df_crisis_clean %>% filter(mtm_insolvent == 1)

cat(sprintf("MTM Solvent: N = %d (%.1f%%)\n", nrow(df_sol_mtm),
    100 * nrow(df_sol_mtm) / nrow(df_crisis_clean)))
## MTM Solvent: N = 3426 (80.6%)
cat(sprintf("MTM Insolvent: N = %d (%.1f%%)\n\n", nrow(df_ins_mtm),
    100 * nrow(df_ins_mtm) / nrow(df_crisis_clean)))
## MTM Insolvent: N = 825 (19.4%)
desc_mtm <- build_desc_table(df_sol_mtm, df_ins_mtm, "Solvent", "Insolvent",
                              desc_vars, desc_labels)
display_desc_table(desc_mtm, "Solvent", "Insolvent",
                   nrow(df_sol_mtm), nrow(df_ins_mtm),
                   "Jiang MTM Solvency")
Jiang MTM Solvency: Solvent (N=3426) vs. Insolvent (N=825)
Variable Solvent Mean (SD) Insolvent Mean (SD) Difference t-stat
Log(Assets) 12.929 (1.520) 12.804 (1.204) 0.125** 2.55
Cash / TA 8.616 (9.271) 5.274 (4.629) 3.342*** 14.79
Securities / TA 22.681 (14.100) 37.185 (15.293) -14.504*** -24.82
Loans / TA 62.526 (16.430) 52.103 (16.108) 10.423*** 16.62
Book Equity / TA 10.593 (5.867) 5.468 (1.871) 5.125*** 42.87
ROA 1.104 (1.919) 0.901 (0.485) 0.203*** 5.51
FHLB / TA 2.681 (4.286) 2.631 (3.979) 0.050 0.32
Loan-to-Deposit 74.316 (23.557) 58.367 (19.636) 15.949*** 20.10
Wholesale Funding (%) 0.976 (3.235) 1.101 (2.581) -0.125 -1.18
MTM Loss / TA 4.936 (1.961) 7.822 (1.472) -2.886*** -47.16
Uninsured Dep. / TA 23.644 (12.227) 24.307 (11.126) -0.663 -1.51
Insured Dep. / TA 61.386 (13.481) 66.060 (11.294) -4.674*** -10.26
Uninsured / Total Dep. 27.948 (14.490) 26.883 (11.994) 1.065** 2.19
Adj. Equity (Jiang MTM) 5.657 (6.256) -2.354 (1.945) 8.011*** 63.32
IDCR-100% 1.617 (40.436) 0.131 (2.381) 1.486** 2.14
DFV Total (DSSW) 75.667 (13.216) 83.015 (9.443) -7.348*** -18.32
Adj. Equity (DSSW Total) 81.298 (11.839) 80.751 (9.356) 0.547 1.42
DFV Uninsured (DSSW) 15.757 (8.815) 16.821 (8.189) -1.064*** -3.28
Adj. Equity (DSSW Uninsured) 21.389 (10.144) 14.558 (8.362) 6.831*** 20.05
Uninsured Deposit β 0.337 (0.111) 0.309 (0.080) 0.028*** 8.26
Collateral Capacity (%) 0.010 (0.010) 0.012 (0.011) -0.002*** -3.29
Par Benefit 0.900 (0.350) 0.945 (0.195) -0.045*** -4.98
Stars: *** p<0.01, ** p<0.05, * p<0.10 (Welch t-test).
save_desc_latex(desc_mtm, "Solvent", "Insolvent",
                nrow(df_sol_mtm), nrow(df_ins_mtm),
                "Jiang MTM Solvency", "Table_DescStats_MTM_Solvency")
## Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_DescStats_MTM_Solvency.tex

9.3 Panel C2: Desc Stats — IDCR-100% Solvent vs. Insolvent

cat("================================================================\n")
## ================================================================
cat("  PANEL C2: DESCRIPTIVE STATS BY IDCR-100%% SOLVENCY (2022Q4)\n")
##   PANEL C2: DESCRIPTIVE STATS BY IDCR-100%% SOLVENCY (2022Q4)
cat("================================================================\n\n")
## ================================================================
df_sol_idcr <- df_crisis_clean %>% filter(solvent_idcr_100 == 1)
df_ins_idcr <- df_crisis_clean %>% filter(insolvent_idcr_100 == 1)

desc_idcr <- build_desc_table(df_sol_idcr, df_ins_idcr, "Solvent", "Insolvent",
                               desc_vars, desc_labels)
display_desc_table(desc_idcr, "Solvent", "Insolvent",
                   nrow(df_sol_idcr), nrow(df_ins_idcr),
                   "IDCR-100% Solvency")
IDCR-100% Solvency: Solvent (N=3041) vs. Insolvent (N=1210)
Variable Solvent Mean (SD) Insolvent Mean (SD) Difference t-stat
Log(Assets) 12.866 (1.501) 13.004 (1.362) -0.138*** -2.91
Cash / TA 8.033 (8.832) 7.802 (8.249) 0.231 0.81
Securities / TA 25.780 (15.307) 24.782 (15.762) 0.998* 1.88
Loans / TA 60.113 (16.539) 61.483 (17.668) -1.370** -2.32
Book Equity / TA 10.252 (6.324) 7.956 (3.161) 2.296*** 15.69
ROA 1.094 (2.006) 0.991 (0.699) 0.103** 2.49
FHLB / TA 2.890 (4.510) 2.123 (3.355) 0.767*** 6.07
Loan-to-Deposit 71.715 (24.261) 69.979 (22.193) 1.736** 2.24
Wholesale Funding (%) 1.234 (3.574) 0.413 (1.264) 0.821*** 11.05
MTM Loss / TA 5.488 (2.213) 5.517 (2.152) -0.029 -0.40
Uninsured Dep. / TA 22.341 (11.670) 27.370 (12.147) -5.029*** -12.32
Insured Dep. / TA 62.484 (13.314) 61.812 (12.949) 0.672 1.52
Uninsured / Total Dep. 26.485 (13.917) 30.898 (13.875) -4.413*** -9.35
Adj. Equity (Jiang MTM) 4.764 (7.026) 2.438 (4.552) 2.326*** 12.73
IDCR-100% 1.909 (42.930) -0.130 (0.186) 2.039*** 2.62
DFV Total (DSSW) 77.019 (12.780) 77.236 (13.225) -0.217 -0.49
Adj. Equity (DSSW Total) 81.771 (11.153) 79.737 (11.896) 2.034*** 5.10
DFV Uninsured (DSSW) 15.210 (8.453) 17.857 (9.046) -2.647*** -8.74
Adj. Equity (DSSW Uninsured) 19.962 (10.119) 20.358 (10.358) -0.396 -1.13
Uninsured Deposit β 0.323 (0.100) 0.352 (0.118) -0.029*** -7.41
Collateral Capacity (%) 0.011 (0.010) 0.009 (0.010) 0.002*** 5.32
Par Benefit 0.911 (0.337) 0.903 (0.298) 0.008 0.75
Stars: *** p<0.01, ** p<0.05, * p<0.10 (Welch t-test).
save_desc_latex(desc_idcr, "Solvent", "Insolvent",
                nrow(df_sol_idcr), nrow(df_ins_idcr),
                "IDCR-100\\% Solvency", "Table_DescStats_IDCR_Solvency")
## 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_Solvency.tex

9.4 Panel C3: Desc Stats — DSSW Total Franchise Solvent vs. Insolvent

cat("================================================================\n")
## ================================================================
cat("  PANEL C3: DESCRIPTIVE STATS BY DSSW TOTAL FRANCHISE SOLVENCY\n")
##   PANEL C3: DESCRIPTIVE STATS BY DSSW TOTAL FRANCHISE SOLVENCY
cat("================================================================\n\n")
## ================================================================
df_sol_dssw <- df_crisis_clean %>% filter(dssw_solvent == 1)
df_ins_dssw <- df_crisis_clean %>% filter(dssw_insolvent == 1)

desc_dssw <- build_desc_table(df_sol_dssw, df_ins_dssw, "Solvent", "Insolvent",
                               desc_vars, desc_labels)
display_desc_table(desc_dssw, "Solvent", "Insolvent",
                   nrow(df_sol_dssw), nrow(df_ins_dssw),
                   "DSSW Total Franchise Solvency")
DSSW Total Franchise Solvency: Solvent (N=4226) vs. Insolvent (N=0)
Variable Solvent Mean (SD) Insolvent Mean (SD) Difference t-stat
Log(Assets) 12.909 (1.461) NaN (NA) NaN NA
Cash / TA 7.964 (8.677) NaN (NA) NaN NA
Securities / TA 25.415 (15.323) NaN (NA) NaN NA
Loans / TA 60.609 (16.772) NaN (NA) NaN NA
Book Equity / TA 9.604 (5.663) NaN (NA) NaN NA
ROA 1.067 (1.742) NaN (NA) NaN NA
FHLB / TA 2.675 (4.229) NaN (NA) NaN NA
Loan-to-Deposit 71.339 (23.602) NaN (NA) NaN NA
Wholesale Funding (%) 0.997 (3.113) NaN (NA) NaN NA
MTM Loss / TA 5.492 (2.188) NaN (NA) NaN NA
Uninsured Dep. / TA 23.758 (11.982) NaN (NA) NaN NA
Insured Dep. / TA 62.309 (13.176) NaN (NA) NaN NA
Uninsured / Total Dep. 27.736 (14.026) NaN (NA) NaN NA
Adj. Equity (Jiang MTM) 4.113 (6.449) NaN (NA) NaN NA
IDCR-100% 1.333 (36.427) NaN (NA) NaN NA
DFV Total (DSSW) 77.080 (12.907) NaN (NA) NaN NA
Adj. Equity (DSSW Total) 81.193 (11.405) NaN (NA) NaN NA
DFV Uninsured (DSSW) 15.962 (8.707) NaN (NA) NaN NA
Adj. Equity (DSSW Uninsured) 20.075 (10.188) NaN (NA) NaN NA
Uninsured Deposit β 0.331 (0.106) NaN (NA) NaN NA
Collateral Capacity (%) 0.011 (0.010) NaN (NA) NaN NA
Par Benefit 0.909 (0.326) NaN (NA) NaN NA
Stars: *** p<0.01, ** p<0.05, * p<0.10 (Welch t-test).
save_desc_latex(desc_dssw, "Solvent", "Insolvent",
                nrow(df_sol_dssw), nrow(df_ins_dssw),
                "DSSW Total Franchise Solvency", "Table_DescStats_DSSW_Total_Solvency")
## Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_DescStats_DSSW_Total_Solvency.tex

9.5 Panel C4: Desc Stats — DSSW Uninsured Franchise Solvent vs. Insolvent

cat("================================================================\n")
## ================================================================
cat("  PANEL C4: DESCRIPTIVE STATS BY DSSW UNINSURED FRANCHISE SOLVENCY\n")
##   PANEL C4: DESCRIPTIVE STATS BY DSSW UNINSURED FRANCHISE SOLVENCY
cat("================================================================\n\n")
## ================================================================
df_sol_dssw_u <- df_crisis_clean %>% filter(dssw_u_solvent == 1)
df_ins_dssw_u <- df_crisis_clean %>% filter(dssw_u_insolvent == 1)

desc_dssw_u <- build_desc_table(df_sol_dssw_u, df_ins_dssw_u, "Solvent", "Insolvent",
                                 desc_vars, desc_labels)
display_desc_table(desc_dssw_u, "Solvent", "Insolvent",
                   nrow(df_sol_dssw_u), nrow(df_ins_dssw_u),
                   "DSSW Uninsured Franchise Solvency")
DSSW Uninsured Franchise Solvency: Solvent (N=4212) vs. Insolvent (N=14)
Variable Solvent Mean (SD) Insolvent Mean (SD) Difference t-stat
Log(Assets) 12.909 (1.455) 12.941 (2.844) -0.032 -0.04
Cash / TA 7.967 (8.685) 6.840 (6.162) 1.127 0.68
Securities / TA 25.349 (15.235) 45.110 (26.551) -19.761** -2.78
Loans / TA 60.671 (16.714) 42.079 (23.594) 18.592** 2.95
Book Equity / TA 9.622 (5.662) 4.207 (2.100) 5.415*** 9.53
ROA 1.068 (1.744) 0.566 (0.511) 0.502*** 3.61
FHLB / TA 2.675 (4.227) 2.755 (4.931) -0.080 -0.06
Loan-to-Deposit 71.424 (23.554) 45.781 (25.139) 25.643*** 3.81
Wholesale Funding (%) 0.999 (3.118) 0.306 (0.754) 0.693*** 3.34
MTM Loss / TA 5.479 (2.177) 9.137 (2.497) -3.658*** -5.47
Uninsured Dep. / TA 23.823 (11.948) 4.425 (3.021) 19.398*** 23.42
Insured Dep. / TA 62.225 (13.114) 87.393 (5.916) -25.168*** -15.79
Uninsured / Total Dep. 27.812 (13.985) 4.798 (3.218) 23.014*** 25.96
Adj. Equity (Jiang MTM) 4.143 (6.437) -4.930 (2.251) 9.073*** 14.88
IDCR-100% 1.337 (36.487) 0.052 (0.172) 1.285** 2.28
DFV Total (DSSW) 77.042 (12.904) 88.720 (7.214) -11.678*** -6.03
Adj. Equity (DSSW Total) 81.184 (11.415) 83.790 (7.553) -2.606 -1.29
DFV Uninsured (DSSW) 16.005 (8.689) 3.048 (2.100) 12.957*** 22.45
Adj. Equity (DSSW Uninsured) 20.148 (10.125) -1.882 (1.721) 22.030*** 45.36
Uninsured Deposit β 0.331 (0.106) 0.329 (0.054) 0.002 0.15
Collateral Capacity (%) 0.011 (0.010) 0.015 (0.015) -0.004 -1.21
Par Benefit 0.909 (0.326) 0.888 (0.280) 0.021 0.29
Stars: *** p<0.01, ** p<0.05, * p<0.10 (Welch t-test).
save_desc_latex(desc_dssw_u, "Solvent", "Insolvent",
                nrow(df_sol_dssw_u), nrow(df_ins_dssw_u),
                "DSSW Uninsured Franchise Solvency", "Table_DescStats_DSSW_Uninsured_Solvency")
## Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_DescStats_DSSW_Uninsured_Solvency.tex

10 DESCRIPTIVE STATISTICS BY BORROWER GROUP × SOLVENCY

10.1 Borrower Group Definitions

# ==============================================================================
# GOLDSTEIN-PAUZNER (2005) MOTIVATION:
# ==============================================================================
# In the GP model, the run/no-run equilibrium depends on fundamentals.
# Banks in the "run zone" (insolvent) face coordination failure among
# depositors. The key empirical question is: among insolvent banks,
# who borrows from the lender of last resort (and via which facility)?
#
# We split each solvency subsample into four borrower groups:
#   (1) Insolvent × Borrower      (run zone + chose to borrow)
#   (2) Insolvent × Non-Borrower  (run zone + did NOT borrow)
#   (3) Solvent × Borrower        (no-run zone + chose to borrow)
#   (4) Solvent × Non-Borrower    (no-run zone + did NOT borrow)
#
# "Borrower" = any_fed == 1 (used BTFP or DW during crisis period)
# ==============================================================================

cat("=== BORROWER GROUP × SOLVENCY SPLITS (CRISIS CLEAN SAMPLE) ===\n\n")
## === BORROWER GROUP × SOLVENCY SPLITS (CRISIS CLEAN SAMPLE) ===
# Create the 4 groups for each solvency framework
df_crisis_clean <- df_crisis_clean %>%
  mutate(
    # Jiang MTM
    mtm_group = case_when(
      mtm_insolvent == 1 & any_fed == 1 ~ "Insolvent_Borrower",
      mtm_insolvent == 1 & any_fed == 0 ~ "Insolvent_NonBorrower",
      mtm_solvent == 1   & any_fed == 1 ~ "Solvent_Borrower",
      mtm_solvent == 1   & any_fed == 0 ~ "Solvent_NonBorrower"
    ),
    mtm_group = factor(mtm_group, levels = c("Insolvent_Borrower", "Insolvent_NonBorrower",
                                              "Solvent_Borrower", "Solvent_NonBorrower")),

    # IDCR-100%
    idcr_group = case_when(
      insolvent_idcr_100 == 1 & any_fed == 1 ~ "Insolvent_Borrower",
      insolvent_idcr_100 == 1 & any_fed == 0 ~ "Insolvent_NonBorrower",
      solvent_idcr_100 == 1   & any_fed == 1 ~ "Solvent_Borrower",
      solvent_idcr_100 == 1   & any_fed == 0 ~ "Solvent_NonBorrower"
    ),
    idcr_group = factor(idcr_group, levels = c("Insolvent_Borrower", "Insolvent_NonBorrower",
                                                "Solvent_Borrower", "Solvent_NonBorrower")),

    # DSSW Total
    dssw_group = case_when(
      dssw_insolvent == 1 & any_fed == 1 ~ "Insolvent_Borrower",
      dssw_insolvent == 1 & any_fed == 0 ~ "Insolvent_NonBorrower",
      dssw_solvent == 1   & any_fed == 1 ~ "Solvent_Borrower",
      dssw_solvent == 1   & any_fed == 0 ~ "Solvent_NonBorrower",
      TRUE ~ NA_character_  # banks without beta
    ),
    dssw_group = factor(dssw_group, levels = c("Insolvent_Borrower", "Insolvent_NonBorrower",
                                                "Solvent_Borrower", "Solvent_NonBorrower")),

    # DSSW Uninsured
    dssw_u_group = case_when(
      dssw_u_insolvent == 1 & any_fed == 1 ~ "Insolvent_Borrower",
      dssw_u_insolvent == 1 & any_fed == 0 ~ "Insolvent_NonBorrower",
      dssw_u_solvent == 1   & any_fed == 1 ~ "Solvent_Borrower",
      dssw_u_solvent == 1   & any_fed == 0 ~ "Solvent_NonBorrower",
      TRUE ~ NA_character_
    ),
    dssw_u_group = factor(dssw_u_group, levels = c("Insolvent_Borrower", "Insolvent_NonBorrower",
                                                    "Solvent_Borrower", "Solvent_NonBorrower"))
  )

# Print group counts for each framework
for (grp_var in c("mtm_group", "idcr_group", "dssw_group", "dssw_u_group")) {
  cat(sprintf("--- %s ---\n", grp_var))
  print(table(df_crisis_clean[[grp_var]], useNA = "ifany"))
  cat("\n")
}
## --- mtm_group ---
## 
##    Insolvent_Borrower Insolvent_NonBorrower      Solvent_Borrower 
##                   205                   620                   623 
##   Solvent_NonBorrower 
##                  2803 
## 
## --- idcr_group ---
## 
##    Insolvent_Borrower Insolvent_NonBorrower      Solvent_Borrower 
##                   247                   963                   581 
##   Solvent_NonBorrower 
##                  2460 
## 
## --- dssw_group ---
## 
##    Insolvent_Borrower Insolvent_NonBorrower      Solvent_Borrower 
##                     0                     0                   822 
##   Solvent_NonBorrower                  <NA> 
##                  3404                    25 
## 
## --- dssw_u_group ---
## 
##    Insolvent_Borrower Insolvent_NonBorrower      Solvent_Borrower 
##                     3                    11                   819 
##   Solvent_NonBorrower                  <NA> 
##                  3393                    25

10.2 Four-Way Desc Stats Function

# ==============================================================================
# FOUR-WAY DESCRIPTIVE STATISTICS
# ==============================================================================
# For a given solvency framework, produce a single table with four columns:
#   Insolvent Borrower | Insolvent Non-Borrower | Solvent Borrower | Solvent Non-Borrower
# Plus t-tests for the key comparisons:
#   (a) Insolvent Borrower vs. Insolvent Non-Borrower (within run-zone)
#   (b) Solvent Borrower vs. Solvent Non-Borrower (within no-run-zone)
#   (c) Insolvent Borrower vs. Solvent Borrower (across zones, borrowers only)
# ==============================================================================

build_four_way_desc <- function(df, group_var, vars, labels) {

  groups <- c("Insolvent_Borrower", "Insolvent_NonBorrower",
              "Solvent_Borrower", "Solvent_NonBorrower")

  # Filter to available vars
  avail <- vars[vars %in% names(df)]
  avail_labels <- labels[vars %in% names(df)]

  # Compute means and SDs for each group
  results <- data.frame(Variable = avail_labels, stringsAsFactors = FALSE)

  for (g in groups) {
    sub <- df %>% filter(!!sym(group_var) == g)
    g_short <- gsub("_", " ", g)
    results[[paste0("N_", g)]]    <- sapply(avail, function(v) sum(!is.na(sub[[v]])))
    results[[paste0("Mean_", g)]] <- sapply(avail, function(v) round(mean(sub[[v]], na.rm = TRUE), 3))
    results[[paste0("SD_", g)]]   <- sapply(avail, function(v) round(sd(sub[[v]], na.rm = TRUE), 3))
  }

  # T-test: Insolvent Borrower vs Insolvent Non-Borrower
  df_ib  <- df %>% filter(!!sym(group_var) == "Insolvent_Borrower")
  df_inb <- df %>% filter(!!sym(group_var) == "Insolvent_NonBorrower")
  df_sb  <- df %>% filter(!!sym(group_var) == "Solvent_Borrower")
  df_snb <- df %>% filter(!!sym(group_var) == "Solvent_NonBorrower")

  # (a) Within insolvent: Borrower vs Non-Borrower
  results$Diff_Ins <- results$Mean_Insolvent_Borrower - results$Mean_Insolvent_NonBorrower
  results$t_Ins <- sapply(avail, function(v) {
    tt <- tryCatch(t.test(df_ib[[v]], df_inb[[v]]), error = function(e) NULL)
    if (!is.null(tt)) round(tt$statistic, 2) else NA_real_
  })
  results$p_Ins <- sapply(avail, function(v) {
    tt <- tryCatch(t.test(df_ib[[v]], df_inb[[v]]), error = function(e) NULL)
    if (!is.null(tt)) round(tt$p.value, 4) else NA_real_
  })
  results$Stars_Ins <- sapply(results$p_Ins, format_pval)

  # (b) Within solvent: Borrower vs Non-Borrower
  results$Diff_Sol <- results$Mean_Solvent_Borrower - results$Mean_Solvent_NonBorrower
  results$t_Sol <- sapply(avail, function(v) {
    tt <- tryCatch(t.test(df_sb[[v]], df_snb[[v]]), error = function(e) NULL)
    if (!is.null(tt)) round(tt$statistic, 2) else NA_real_
  })
  results$p_Sol <- sapply(avail, function(v) {
    tt <- tryCatch(t.test(df_sb[[v]], df_snb[[v]]), error = function(e) NULL)
    if (!is.null(tt)) round(tt$p.value, 4) else NA_real_
  })
  results$Stars_Sol <- sapply(results$p_Sol, format_pval)

  # (c) Across zones: Insolvent Borrower vs Solvent Borrower
  results$Diff_Cross <- results$Mean_Insolvent_Borrower - results$Mean_Solvent_Borrower
  results$t_Cross <- sapply(avail, function(v) {
    tt <- tryCatch(t.test(df_ib[[v]], df_sb[[v]]), error = function(e) NULL)
    if (!is.null(tt)) round(tt$statistic, 2) else NA_real_
  })
  results$p_Cross <- sapply(avail, function(v) {
    tt <- tryCatch(t.test(df_ib[[v]], df_sb[[v]]), error = function(e) NULL)
    if (!is.null(tt)) round(tt$p.value, 4) else NA_real_
  })
  results$Stars_Cross <- sapply(results$p_Cross, format_pval)

  return(list(
    results = results,
    n_ib = nrow(df_ib), n_inb = nrow(df_inb),
    n_sb = nrow(df_sb), n_snb = nrow(df_snb)
  ))
}

# --- Display helper for four-way table ---
display_four_way <- function(fw, caption_prefix) {
  r <- fw$results

  disp <- r %>%
    mutate(
      `Ins. Borrower` = sprintf("%.3f (%.3f)", Mean_Insolvent_Borrower, SD_Insolvent_Borrower),
      `Ins. Non-Borr.` = sprintf("%.3f (%.3f)", Mean_Insolvent_NonBorrower, SD_Insolvent_NonBorrower),
      `Diff (Ins.)` = sprintf("%.3f%s", Diff_Ins, Stars_Ins),
      `Sol. Borrower` = sprintf("%.3f (%.3f)", Mean_Solvent_Borrower, SD_Solvent_Borrower),
      `Sol. Non-Borr.` = sprintf("%.3f (%.3f)", Mean_Solvent_NonBorrower, SD_Solvent_NonBorrower),
      `Diff (Sol.)` = sprintf("%.3f%s", Diff_Sol, Stars_Sol),
      `Diff (IB−SB)` = sprintf("%.3f%s", Diff_Cross, Stars_Cross)
    ) %>%
    select(Variable, `Ins. Borrower`, `Ins. Non-Borr.`, `Diff (Ins.)`,
           `Sol. Borrower`, `Sol. Non-Borr.`, `Diff (Sol.)`, `Diff (IB−SB)`)

  kbl(disp, format = "html", escape = FALSE,
      caption = sprintf("%s — Ins.Borr (N=%d), Ins.NonBorr (N=%d), Sol.Borr (N=%d), Sol.NonBorr (N=%d)",
                        caption_prefix, fw$n_ib, fw$n_inb, fw$n_sb, fw$n_snb)) %>%
    kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                  full_width = FALSE, position = "left", font_size = 11) %>%
    add_header_above(c(" " = 1, "Insolvent" = 3, "Solvent" = 3, "Cross" = 1)) %>%
    footnote(general = "Mean (SD). Diff = Borrower − Non-Borrower. Stars: *** p<0.01, ** p<0.05, * p<0.10.",
             general_title = "")
}

# --- LaTeX save helper for four-way table ---
save_four_way_latex <- function(fw, caption_prefix, filename) {
  r <- fw$results

  tex <- r %>%
    mutate(
      IB  = sprintf("%.3f (%.3f)", Mean_Insolvent_Borrower, SD_Insolvent_Borrower),
      INB = sprintf("%.3f (%.3f)", Mean_Insolvent_NonBorrower, SD_Insolvent_NonBorrower),
      DI  = sprintf("%.3f%s", Diff_Ins, Stars_Ins),
      SB  = sprintf("%.3f (%.3f)", Mean_Solvent_Borrower, SD_Solvent_Borrower),
      SNB = sprintf("%.3f (%.3f)", Mean_Solvent_NonBorrower, SD_Solvent_NonBorrower),
      DS  = sprintf("%.3f%s", Diff_Sol, Stars_Sol),
      DC  = sprintf("%.3f%s", Diff_Cross, Stars_Cross)
    ) %>%
    select(Variable, IB, INB, DI, SB, SNB, DS, DC)

  names(tex) <- c("Variable", "Ins. Borr.", "Ins. Non-B.",
                   "$\\Delta$ Ins.", "Sol. Borr.", "Sol. Non-B.",
                   "$\\Delta$ Sol.", "$\\Delta$ Cross")

  kbl_tex <- kbl(tex, format = "latex", booktabs = TRUE, escape = FALSE,
    caption = sprintf("%s (Ins.B=%d, Ins.NB=%d, Sol.B=%d, Sol.NB=%d)",
                      caption_prefix, fw$n_ib, fw$n_inb, fw$n_sb, fw$n_snb)) %>%
    kable_styling(latex_options = c("hold_position", "scale_down")) %>%
    add_header_above(c(" " = 1, "Insolvent" = 3, "Solvent" = 3, "Cross" = 1), escape = FALSE) %>%
    footnote(general = "Mean (SD). $\\\\Delta$ = Borrower $-$ Non-Borrower. *** p$<$0.01, ** p$<$0.05, * p$<$0.10.",
             escape = FALSE, general_title = "")

  tex_file <- file.path(TABLE_PATH, paste0(filename, ".tex"))
  writeLines(kbl_tex, tex_file)
  cat(sprintf("Saved: %s\n", tex_file))
}

10.3 Panel D1: Jiang MTM — Borrower Group Summary

cat("================================================================\n")
## ================================================================
cat("  PANEL D1: FOUR-WAY DESC STATS — JIANG MTM × BORROWER\n")
##   PANEL D1: FOUR-WAY DESC STATS — JIANG MTM × BORROWER
cat("================================================================\n\n")
## ================================================================
fw_mtm <- build_four_way_desc(df_crisis_clean, "mtm_group", desc_vars, desc_labels)
display_four_way(fw_mtm, "Jiang MTM Solvency × Fed Borrower")
Jiang MTM Solvency × Fed Borrower — Ins.Borr (N=205), Ins.NonBorr (N=620), Sol.Borr (N=623), Sol.NonBorr (N=2803)
Insolvent
Solvent
Cross
Variable Ins. Borrower Ins. Non-Borr. Diff (Ins.) Sol. Borrower Sol. Non-Borr. Diff (Sol.) Diff (IB−SB)
Log(Assets) 13.369 (1.266) 12.617 (1.122) 0.752*** 13.892 (1.580) 12.715 (1.420) 1.177*** -0.523***
Cash / TA 4.068 (3.576) 5.673 (4.865) -1.605*** 5.647 (5.966) 9.276 (9.735) -3.629*** -1.579***
Securities / TA 37.614 (13.995) 37.043 (15.707) 0.571 22.806 (12.329) 22.653 (14.466) 0.153 14.808***
Loans / TA 53.092 (15.019) 51.776 (16.451) 1.316 65.556 (13.319) 61.853 (16.973) 3.703*** -12.464***
Book Equity / TA 5.207 (1.878) 5.554 (1.862) -0.347** 9.637 (2.690) 10.805 (6.342) -1.168*** -4.430***
ROA 0.928 (0.360) 0.892 (0.519) 0.036 1.146 (0.633) 1.095 (2.101) 0.051 -0.218***
FHLB / TA 3.109 (3.774) 2.474 (4.036) 0.635** 3.794 (4.937) 2.434 (4.087) 1.360*** -0.685**
Loan-to-Deposit 60.140 (18.884) 57.780 (19.858) 2.360 78.508 (18.232) 73.384 (24.490) 5.124*** -18.368***
Wholesale Funding (%) 1.635 (3.435) 0.924 (2.204) 0.711*** 1.495 (3.487) 0.861 (3.165) 0.634*** 0.140
MTM Loss / TA 7.863 (1.334) 7.809 (1.515) 0.054 5.321 (1.746) 4.850 (1.995) 0.471*** 2.542***
Uninsured Dep. / TA 26.068 (10.602) 23.725 (11.241) 2.343*** 27.504 (12.461) 22.785 (12.009) 4.719*** -1.436
Insured Dep. / TA 63.448 (11.054) 66.924 (11.248) -3.476*** 57.120 (12.949) 62.334 (13.415) -5.214*** 6.328***
Uninsured / Total Dep. 29.152 (11.751) 26.133 (11.989) 3.019*** 32.683 (14.780) 26.895 (14.215) 5.788*** -3.531***
Adj. Equity (Jiang MTM) -2.656 (1.959) -2.255 (1.931) -0.401** 4.316 (3.207) 5.955 (6.713) -1.639*** -6.972***
IDCR-100% 0.036 (0.124) 0.162 (2.745) -0.126 0.098 (0.386) 1.955 (44.699) -1.857** -0.062***
DFV Total (DSSW) 81.115 (9.487) 83.634 (9.353) -2.519*** 72.382 (13.650) 76.399 (13.007) -4.017*** 8.733***
Adj. Equity (DSSW Total) 78.590 (9.423) 81.456 (9.233) -2.866*** 76.695 (12.872) 82.324 (11.347) -5.629*** 1.895**
DFV Uninsured (DSSW) 17.983 (8.230) 16.443 (8.146) 1.540** 17.890 (9.015) 15.282 (8.700) 2.608*** 0.093
Adj. Equity (DSSW Uninsured) 15.458 (8.337) 14.264 (8.355) 1.194* 22.203 (9.430) 21.208 (10.289) 0.995** -6.745***
Uninsured Deposit β 0.312 (0.082) 0.307 (0.080) 0.005 0.352 (0.122) 0.333 (0.108) 0.019*** -0.040***
Collateral Capacity (%) 0.013 (0.012) 0.011 (0.011) 0.002* 0.010 (0.009) 0.010 (0.010) 0.000 0.003***
Par Benefit 0.962 (0.153) 0.940 (0.206) 0.022* 0.946 (0.179) 0.890 (0.377) 0.056*** 0.016
Mean (SD). Diff = Borrower − Non-Borrower. Stars: *** p<0.01, ** p<0.05, * p<0.10.
save_four_way_latex(fw_mtm, "Jiang MTM Solvency $\\times$ Fed Borrower",
                    "Table_FourWay_MTM")
## Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_FourWay_MTM.tex

10.4 Panel D2: IDCR-100% — Borrower Group Summary

cat("================================================================\n")
## ================================================================
cat("  PANEL D2: FOUR-WAY DESC STATS — IDCR-100%% × BORROWER\n")
##   PANEL D2: FOUR-WAY DESC STATS — IDCR-100%% × BORROWER
cat("================================================================\n\n")
## ================================================================
fw_idcr <- build_four_way_desc(df_crisis_clean, "idcr_group", desc_vars, desc_labels)
display_four_way(fw_idcr, "IDCR-100% Solvency × Fed Borrower")
IDCR-100% Solvency × Fed Borrower — Ins.Borr (N=247), Ins.NonBorr (N=963), Sol.Borr (N=581), Sol.NonBorr (N=2460)
Insolvent
Solvent
Cross
Variable Ins. Borrower Ins. Non-Borr. Diff (Ins.) Sol. Borrower Sol. Non-Borr. Diff (Sol.) Diff (IB−SB)
Log(Assets) 13.712 (1.377) 12.823 (1.298) 0.889*** 13.784 (1.584) 12.649 (1.396) 1.135*** -0.072
Cash / TA 5.923 (6.776) 8.284 (8.523) -2.361*** 4.972 (4.857) 8.756 (9.388) -3.784*** 0.951**
Securities / TA 25.904 (15.774) 24.494 (15.755) 1.410 26.714 (13.583) 25.559 (15.681) 1.155* -0.810
Loans / TA 62.702 (17.542) 61.171 (17.695) 1.531 62.371 (13.429) 59.580 (17.151) 2.791*** 0.331
Book Equity / TA 7.791 (2.806) 7.998 (3.246) -0.207 8.859 (3.247) 10.581 (6.811) -1.722*** -1.068***
ROA 1.113 (0.736) 0.959 (0.686) 0.154*** 1.084 (0.508) 1.097 (2.217) -0.013 0.029
FHLB / TA 2.991 (3.682) 1.900 (3.231) 1.091*** 3.893 (5.028) 2.653 (4.346) 1.240*** -0.902***
Loan-to-Deposit 72.286 (22.484) 69.387 (22.091) 2.899* 74.672 (18.855) 71.016 (25.323) 3.656*** -2.386
Wholesale Funding (%) 0.651 (1.737) 0.352 (1.103) 0.299** 1.903 (3.930) 1.076 (3.466) 0.827*** -1.252***
MTM Loss / TA 5.824 (2.118) 5.439 (2.155) 0.385** 6.004 (1.924) 5.366 (2.259) 0.638*** -0.180
Uninsured Dep. / TA 28.982 (12.765) 26.956 (11.955) 2.026** 26.369 (11.638) 21.390 (11.476) 4.979*** 2.613***
Insured Dep. / TA 59.527 (13.161) 62.398 (12.836) -2.871*** 58.329 (12.631) 63.466 (13.285) -5.137*** 1.198
Uninsured / Total Dep. 33.161 (14.981) 30.317 (13.524) 2.844*** 31.234 (13.779) 25.364 (13.714) 5.870*** 1.927*
Adj. Equity (Jiang MTM) 1.967 (4.299) 2.559 (4.609) -0.592* 2.855 (4.151) 5.215 (7.477) -2.360*** -0.888***
IDCR-100% -0.136 (0.188) -0.129 (0.185) -0.007 0.176 (0.349) 2.318 (47.723) -2.142** -0.312***
DFV Total (DSSW) 73.887 (14.657) 78.085 (12.705) -4.198*** 74.767 (12.687) 77.552 (12.746) -2.785*** -0.880
Adj. Equity (DSSW Total) 76.012 (12.992) 80.682 (11.417) -4.670*** 77.636 (11.751) 82.750 (10.779) -5.114*** -1.624*
DFV Uninsured (DSSW) 18.514 (9.713) 17.691 (8.866) 0.823 17.660 (8.421) 14.630 (8.358) 3.030*** 0.854
Adj. Equity (DSSW Uninsured) 20.639 (10.736) 20.287 (10.264) 0.352 20.529 (9.117) 19.828 (10.339) 0.701 0.110
Uninsured Deposit β 0.369 (0.137) 0.347 (0.113) 0.022** 0.331 (0.102) 0.321 (0.099) 0.010** 0.038***
Collateral Capacity (%) 0.009 (0.009) 0.009 (0.010) 0.000 0.011 (0.010) 0.011 (0.010) 0.000 -0.002**
Par Benefit 0.949 (0.190) 0.892 (0.319) 0.057*** 0.951 (0.165) 0.902 (0.365) 0.049*** -0.002
Mean (SD). Diff = Borrower − Non-Borrower. Stars: *** p<0.01, ** p<0.05, * p<0.10.
save_four_way_latex(fw_idcr, "IDCR-100\\% Solvency $\\times$ Fed Borrower",
                    "Table_FourWay_IDCR")
## Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_FourWay_IDCR.tex

10.5 Panel D3: DSSW Total Franchise — Borrower Group Summary

cat("================================================================\n")
## ================================================================
cat("  PANEL D3: FOUR-WAY DESC STATS — DSSW TOTAL × BORROWER\n")
##   PANEL D3: FOUR-WAY DESC STATS — DSSW TOTAL × BORROWER
cat("================================================================\n\n")
## ================================================================
# Restrict to banks with DSSW betas
df_dssw_4way <- df_crisis_clean %>% filter(!is.na(dssw_group))
fw_dssw <- build_four_way_desc(df_dssw_4way, "dssw_group", desc_vars, desc_labels)
display_four_way(fw_dssw, "DSSW Total Franchise × Fed Borrower")
DSSW Total Franchise × Fed Borrower — Ins.Borr (N=0), Ins.NonBorr (N=0), Sol.Borr (N=822), Sol.NonBorr (N=3404)
Insolvent
Solvent
Cross
Variable Ins. Borrower Ins. Non-Borr. Diff (Ins.) Sol. Borrower Sol. Non-Borr. Diff (Sol.) Diff (IB−SB)
Log(Assets) NaN (NA) NaN (NA) NaN 13.771 (1.525) 12.701 (1.367) 1.070*** NaN
Cash / TA NaN (NA) NaN (NA) NaN 5.251 (5.521) 8.619 (9.161) -3.368*** NaN
Securities / TA NaN (NA) NaN (NA) NaN 26.270 (14.049) 25.208 (15.611) 1.062* NaN
Loans / TA NaN (NA) NaN (NA) NaN 62.660 (14.591) 60.114 (17.223) 2.546*** NaN
Book Equity / TA NaN (NA) NaN (NA) NaN 8.585 (3.110) 9.851 (6.096) -1.266*** NaN
ROA NaN (NA) NaN (NA) NaN 1.094 (0.586) 1.060 (1.919) 0.034 NaN
FHLB / TA NaN (NA) NaN (NA) NaN 3.648 (4.692) 2.440 (4.076) 1.208*** NaN
Loan-to-Deposit NaN (NA) NaN (NA) NaN 74.216 (19.811) 70.645 (24.382) 3.571*** NaN
Wholesale Funding (%) NaN (NA) NaN (NA) NaN 1.529 (3.480) 0.868 (3.004) 0.661*** NaN
MTM Loss / TA NaN (NA) NaN (NA) NaN 5.936 (1.978) 5.384 (2.223) 0.552*** NaN
Uninsured Dep. / TA NaN (NA) NaN (NA) NaN 27.133 (12.061) 22.944 (11.821) 4.189*** NaN
Insured Dep. / TA NaN (NA) NaN (NA) NaN 58.636 (12.814) 63.195 (13.111) -4.559*** NaN
Uninsured / Total Dep. NaN (NA) NaN (NA) NaN 31.816 (14.201) 26.750 (13.805) 5.066*** NaN
Adj. Equity (Jiang MTM) NaN (NA) NaN (NA) NaN 2.649 (4.146) 4.466 (6.844) -1.817*** NaN
IDCR-100% NaN (NA) NaN (NA) NaN 0.083 (0.342) 1.634 (40.582) -1.551** NaN
DFV Total (DSSW) NaN (NA) NaN (NA) NaN 74.507 (13.297) 77.702 (12.735) -3.195*** NaN
Adj. Equity (DSSW Total) NaN (NA) NaN (NA) NaN 77.156 (12.146) 82.168 (11.000) -5.012*** NaN
DFV Uninsured (DSSW) NaN (NA) NaN (NA) NaN 17.913 (8.825) 15.491 (8.613) 2.422*** NaN
Adj. Equity (DSSW Uninsured) NaN (NA) NaN (NA) NaN 20.562 (9.618) 19.957 (10.319) 0.605 NaN
Uninsured Deposit β NaN (NA) NaN (NA) NaN 0.342 (0.115) 0.329 (0.104) 0.013*** NaN
Collateral Capacity (%) NaN (NA) NaN (NA) NaN 0.011 (0.010) 0.011 (0.010) 0.000 NaN
Par Benefit NaN (NA) NaN (NA) NaN 0.950 (0.173) 0.899 (0.352) 0.051*** NaN
Mean (SD). Diff = Borrower − Non-Borrower. Stars: *** p<0.01, ** p<0.05, * p<0.10.
save_four_way_latex(fw_dssw, "DSSW Total Franchise $\\times$ Fed Borrower",
                    "Table_FourWay_DSSW_Total")
## Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_FourWay_DSSW_Total.tex

10.6 Panel D4: DSSW Uninsured Franchise — Borrower Group Summary

cat("================================================================\n")
## ================================================================
cat("  PANEL D4: FOUR-WAY DESC STATS — DSSW UNINSURED × BORROWER\n")
##   PANEL D4: FOUR-WAY DESC STATS — DSSW UNINSURED × BORROWER
cat("================================================================\n\n")
## ================================================================
df_dssw_u_4way <- df_crisis_clean %>% filter(!is.na(dssw_u_group))
fw_dssw_u <- build_four_way_desc(df_dssw_u_4way, "dssw_u_group", desc_vars, desc_labels)
display_four_way(fw_dssw_u, "DSSW Uninsured Franchise × Fed Borrower")
DSSW Uninsured Franchise × Fed Borrower — Ins.Borr (N=3), Ins.NonBorr (N=11), Sol.Borr (N=819), Sol.NonBorr (N=3393)
Insolvent
Solvent
Cross
Variable Ins. Borrower Ins. Non-Borr. Diff (Ins.) Sol. Borrower Sol. Non-Borr. Diff (Sol.) Diff (IB−SB)
Log(Assets) 15.241 (3.256) 12.314 (2.525) 2.927 13.766 (1.517) 12.702 (1.362) 1.064*** 1.475
Cash / TA 8.018 (0.561) 6.518 (6.983) 1.500 5.241 (5.528) 8.625 (9.167) -3.384*** 2.777***
Securities / TA 41.653 (11.221) 46.052 (29.778) -4.399 26.213 (14.032) 25.141 (15.507) 1.072* 15.440
Loans / TA 43.106 (7.483) 41.799 (26.685) 1.307 62.732 (14.565) 60.173 (17.158) 2.559*** -19.626**
Book Equity / TA 2.346 (0.885) 4.715 (2.062) -2.369** 8.608 (3.093) 9.867 (6.098) -1.259*** -6.262***
ROA 0.644 (0.574) 0.545 (0.521) 0.099 1.096 (0.586) 1.062 (1.922) 0.034 -0.452
FHLB / TA 4.680 (2.519) 2.230 (5.378) 2.450 3.644 (4.699) 2.441 (4.072) 1.203*** 1.036
Loan-to-Deposit 48.039 (8.408) 45.165 (28.381) 2.874 74.312 (19.780) 70.727 (24.329) 3.585*** -26.273**
Wholesale Funding (%) 0.881 (1.526) 0.149 (0.384) 0.732 1.531 (3.485) 0.871 (3.009) 0.660*** -0.650
MTM Loss / TA 8.112 (0.574) 9.417 (2.764) -1.305 5.928 (1.977) 5.371 (2.209) 0.557*** 2.184**
Uninsured Dep. / TA 6.590 (1.640) 3.834 (3.088) 2.756* 27.208 (12.019) 23.006 (11.788) 4.202*** -20.618***
Insured Dep. / TA 83.194 (1.302) 88.538 (6.200) -5.344** 58.546 (12.750) 63.113 (13.048) -4.567*** 24.648***
Uninsured / Total Dep. 7.303 (1.621) 4.114 (3.247) 3.189* 31.906 (14.149) 26.824 (13.766) 5.082*** -24.603***
Adj. Equity (Jiang MTM) -5.767 (0.784) -4.702 (2.490) -1.065 2.680 (4.121) 4.496 (6.834) -1.816*** -8.447***
IDCR-100% 0.028 (0.037) 0.058 (0.195) -0.030 0.084 (0.343) 1.640 (40.648) -1.556** -0.056*
DFV Total (DSSW) 86.690 (1.820) 89.274 (8.088) -2.584 74.462 (13.300) 77.664 (12.731) -3.202*** 12.228***
Adj. Equity (DSSW Total) 80.923 (1.094) 84.572 (8.413) -3.649 77.142 (12.166) 82.160 (11.008) -5.018*** 3.781***
DFV Uninsured (DSSW) 4.476 (1.096) 2.658 (2.171) 1.818* 17.962 (8.804) 15.533 (8.595) 2.429*** -13.486***
Adj. Equity (DSSW Uninsured) -1.290 (0.332) -2.044 (1.922) 0.754 20.642 (9.544) 20.029 (10.258) 0.613 -21.932***
Uninsured Deposit β 0.320 (0.006) 0.332 (0.061) -0.012 0.342 (0.115) 0.329 (0.104) 0.013*** -0.022***
Collateral Capacity (%) 0.022 (0.019) 0.014 (0.014) 0.008 0.011 (0.010) 0.011 (0.010) 0.000 0.011
Par Benefit 0.991 (0.002) 0.860 (0.313) 0.131 0.950 (0.173) 0.900 (0.353) 0.050*** 0.041***
Mean (SD). Diff = Borrower − Non-Borrower. Stars: *** p<0.01, ** p<0.05, * p<0.10.
save_four_way_latex(fw_dssw_u, "DSSW Uninsured Franchise $\\times$ Fed Borrower",
                    "Table_FourWay_DSSW_Uninsured")
## Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_FourWay_DSSW_Uninsured.tex

10.7 Panel E: Overall Summary — Full Clean Sample

cat("================================================================\n")
## ================================================================
cat("  PANEL E: FULL SAMPLE DESCRIPTIVE STATISTICS (CRISIS CLEAN)\n")
##   PANEL E: FULL SAMPLE DESCRIPTIVE STATISTICS (CRISIS CLEAN)
cat("================================================================\n\n")
## ================================================================
# Full sample summary: Borrower vs Non-Borrower (no solvency split)
df_fed_borr <- df_crisis_clean %>% filter(any_fed == 1)
df_fed_non  <- df_crisis_clean %>% filter(any_fed == 0)

desc_full <- build_desc_table(df_fed_borr, df_fed_non, "Fed_Borrower", "Non_Borrower",
                               desc_vars, desc_labels)
display_desc_table(desc_full, "Fed_Borrower", "Non_Borrower",
                   nrow(df_fed_borr), nrow(df_fed_non),
                   "Full Sample (No Solvency Split)")
Full Sample (No Solvency Split): Fed_Borrower (N=828) vs. Non_Borrower (N=3423)
Variable Fed_Borrower Mean (SD) Non_Borrower 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 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
Loan-to-Deposit 73.960 (20.022) 70.558 (24.465) 3.402*** 4.19
Wholesale Funding (%) 1.530 (3.473) 0.872 (3.013) 0.658*** 5.01
MTM Loss / TA 5.951 (1.985) 5.386 (2.230) 0.565*** 7.16
Uninsured Dep. / TA 27.149 (12.037) 22.956 (11.878) 4.193*** 9.02
Insured Dep. / TA 58.687 (12.795) 63.165 (13.167) -4.478*** -8.99
Uninsured / Total Dep. 31.809 (14.167) 26.757 (13.839) 5.052*** 9.25
Adj. Equity (Jiang MTM) 2.590 (4.213) 4.468 (6.897) -1.878*** -9.99
IDCR-100% 0.083 (0.341) 1.630 (40.470) -1.547** -2.24
DFV Total (DSSW) 74.507 (13.297) 77.702 (12.735) -3.195*** -6.23
Adj. Equity (DSSW Total) 77.156 (12.146) 82.168 (11.000) -5.012*** -10.81
DFV Uninsured (DSSW) 17.913 (8.825) 15.491 (8.613) 2.422*** 7.09
Adj. Equity (DSSW Uninsured) 20.562 (9.618) 19.957 (10.319) 0.605 1.59
Uninsured Deposit β 0.342 (0.115) 0.329 (0.104) 0.013*** 3.15
Collateral Capacity (%) 0.011 (0.010) 0.011 (0.010) 0.000 0.02
Par Benefit 0.950 (0.173) 0.899 (0.353) 0.051*** 6.01
Stars: *** p<0.01, ** p<0.05, * p<0.10 (Welch t-test).
save_desc_latex(desc_full, "Fed_Borrower", "Non_Borrower",
                nrow(df_fed_borr), nrow(df_fed_non),
                "Full Sample", "Table_DescStats_FullSample_BorrowerVsNon")
## Saved: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables/Table_DescStats_FullSample_BorrowerVsNon.tex
# Also: by facility type
cat("\n--- By Facility: BTFP vs DW vs Both ---\n")
## 
## --- By Facility: BTFP vs DW vs Both ---
for (ug in c("BTFP_Only", "DW_Only", "Both", "Neither")) {
  n <- sum(df_crisis_clean$user_group == ug, na.rm = TRUE)
  cat(sprintf("  %s: N = %d (%.1f%%)\n", ug, n, 100*n/nrow(df_crisis_clean)))
}
##   BTFP_Only: N = 395 (9.3%)
##   DW_Only: N = 327 (7.7%)
##   Both: N = 106 (2.5%)
##   Neither: N = 3423 (80.5%)

10.8 Panel F: Size Distribution by Solvency × Borrower Group

# ==============================================================================
# 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) {

  # Compute counts
  tbl <- df %>%
    filter(!is.na(!!sym(grp_var))) %>%
    mutate(Group = as.character(!!sym(grp_var))) %>%
    group_by(Group, size_cat) %>%
    summarise(N = n(), .groups = "drop") %>%
    group_by(Group) %>%
    mutate(Pct = round(100 * N / sum(N), 1)) %>%
    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%)")

  # Force all four group columns to exist in canonical order
  for (g in group_levels) {
    if (!g %in% names(tbl)) tbl[[g]] <- "0 (0.0%)"
  }
  tbl <- tbl %>% select(size_cat, all_of(group_levels))
  return(tbl)
}

# Framework metadata
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")
)

display_names <- c("Size Category",
                   "Ins. Borrower", "Ins. Non-Borrower",
                   "Sol. Borrower", "Sol. Non-Borrower")

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 = ", ")

  # Verify column count before adding header
  ncol_data <- ncol(size_tbl)  # should be 5 (size_cat + 4 groups)

  # HTML display
  print(
    kbl(size_tbl, format = "html", escape = FALSE,
        col.names = display_names[1:ncol_data],
        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") %>%
      {if (ncol_data == 5) add_header_above(., c(" " = 1, "Insolvent" = 2, "Solvent" = 2)) else .}
  )

  # LaTeX
  tex_tbl <- size_tbl
  names(tex_tbl) <- display_names[1:ncol_data]

  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")) %>%
    {if (ncol_data == 5) add_header_above(., c(" " = 1, "Insolvent" = 2, "Solvent" = 2), escape = FALSE) else .}

  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 (&lt;$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 (29.8%) </td>
##    <td style="text-align:left;"> 73 (11.8%) </td>
##    <td style="text-align:left;"> 272 (43.7%) </td>
##    <td style="text-align:left;"> 496 (17.7%) </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Large (&gt;$100B) </td>
##    <td style="text-align:left;"> 1 (0.5%) </td>
##    <td style="text-align:left;"> 2 (0.3%) </td>
##    <td style="text-align:left;"> 7 (1.1%) </td>
##    <td style="text-align:left;"> 8 (0.3%) </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 (&lt;$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 (39.7%) </td>
##    <td style="text-align:left;"> 161 (16.7%) </td>
##    <td style="text-align:left;"> 235 (40.4%) </td>
##    <td style="text-align:left;"> 408 (16.6%) </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Large (&gt;$100B) </td>
##    <td style="text-align:left;"> 1 (0.4%) </td>
##    <td style="text-align:left;"> 4 (0.4%) </td>
##    <td style="text-align:left;"> 7 (1.2%) </td>
##    <td style="text-align:left;"> 6 (0.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 (&lt;$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 (40.4%) </td>
##    <td style="text-align:left;"> 568 (16.7%) </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Large (&gt;$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 (1.0%) </td>
##    <td style="text-align:left;"> 9 (0.3%) </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 (&lt;$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 (18.2%) </td>
##    <td style="text-align:left;"> 331 (40.4%) </td>
##    <td style="text-align:left;"> 566 (16.7%) </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Large (&gt;$100B) </td>
##    <td style="text-align:left;"> 1 (33.3%) </td>
##    <td style="text-align:left;"> 0 (0.0%) </td>
##    <td style="text-align:left;"> 7 (0.9%) </td>
##    <td style="text-align:left;"> 9 (0.3%) </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

11 DEPOSIT BETA DIAGNOSTICS

11.1 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 (&lt;$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 (&gt;$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 (&lt;$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 (&gt;$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 (&lt;$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 (&gt;$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 (&lt;$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 (&gt;$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

12 SESSION INFO

cat("=== Analysis complete ===\n")
## === Analysis complete ===
cat("Clean crisis sample N:", nrow(df_crisis_clean), "\n")
## Clean crisis sample N: 4251
cat("Clean arbitrage sample N:", nrow(df_arb_clean), "\n")
## Clean arbitrage sample N: 4168
cat("Tables saved to:", TABLE_PATH, "\n")
## Tables saved to: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/run_equilibrium_Analysis/tables
sessionInfo()
## R version 4.5.2 (2025-10-31 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26200)
## 
## Matrix products: default
##   LAPACK version 3.12.1
## 
## locale:
## [1] LC_COLLATE=English_United States.utf8 
## [2] LC_CTYPE=English_United States.utf8   
## [3] LC_MONETARY=English_United States.utf8
## [4] LC_NUMERIC=C                          
## [5] LC_TIME=English_United States.utf8    
## 
## time zone: America/Chicago
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] readxl_1.4.5           readr_2.1.6            patchwork_1.3.2       
##  [4] scales_1.4.0           gridExtra_2.3          DescTools_0.99.60     
##  [7] kableExtra_1.4.0       knitr_1.51             modelsummary_2.6.0    
## [10] broom_1.0.12           nnet_7.3-20            marginaleffects_0.31.0
## [13] fixest_0.13.2          ggrepel_0.9.6          ggplot2_4.0.2         
## [16] tibble_3.3.1           purrr_1.2.1            lubridate_1.9.5       
## [19] stringr_1.6.0          tidyr_1.3.2            dplyr_1.2.0           
## [22] data.table_1.18.2.1   
## 
## loaded via a namespace (and not attached):
##  [1] tidyselect_1.2.1    Exact_3.3           viridisLite_0.4.3  
##  [4] rootSolve_1.8.2.4   farver_2.1.2        S7_0.2.1           
##  [7] fastmap_1.2.0       digest_0.6.39       timechange_0.4.0   
## [10] lifecycle_1.0.5     dreamerr_1.5.0      lmom_3.2           
## [13] magrittr_2.0.4      compiler_4.5.2      rlang_1.1.7        
## [16] sass_0.4.10         tools_4.5.2         utf8_1.2.6         
## [19] yaml_2.3.12         labeling_0.4.3      bit_4.6.0          
## [22] xml2_1.5.2          RColorBrewer_1.1-3  expm_1.0-0         
## [25] withr_3.0.2         numDeriv_2016.8-1.1 grid_4.5.2         
## [28] e1071_1.7-17        MASS_7.3-65         cli_3.6.5          
## [31] mvtnorm_1.3-3       crayon_1.5.3        rmarkdown_2.30     
## [34] generics_0.1.4      rstudioapi_0.18.0   httr_1.4.7         
## [37] tzdb_0.5.0          gld_2.6.8           cachem_1.1.0       
## [40] proxy_0.4-29        parallel_4.5.2      cellranger_1.1.0   
## [43] stringmagic_1.2.0   vctrs_0.7.1         boot_1.3-32        
## [46] Matrix_1.7-4        sandwich_3.1-1      jsonlite_2.0.0     
## [49] hms_1.1.4           bit64_4.6.0-1       Formula_1.2-5      
## [52] systemfonts_1.3.1   jquerylib_0.1.4     glue_1.8.0         
## [55] stringi_1.8.7       gtable_0.3.6        tables_0.9.33      
## [58] pillar_1.11.1       htmltools_0.5.9     R6_2.6.1           
## [61] textshaping_1.0.4   vroom_1.7.0         evaluate_1.0.5     
## [64] lattice_0.22-7      haven_2.5.5         backports_1.5.0    
## [67] bslib_0.10.0        class_7.3-23        Rcpp_1.1.1         
## [70] svglite_2.2.2       nlme_3.1-168        xfun_0.56          
## [73] fs_1.6.6            zoo_1.8-15          forcats_1.0.1      
## [76] pkgconfig_2.0.3