1 Setup

# ============================================================================
# LOAD PACKAGES
# ============================================================================
library(tidyverse)
library(data.table)
library(lubridate)
library(fixest)
library(modelsummary)
library(kableExtra)
library(patchwork)
library(scales)

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

All packages loaded successfully.

# ============================================================================
# HELPER FUNCTIONS
# ============================================================================

# Winsorization function
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])
}

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

# Size category function (as specified)
create_size_category <- function(assets_thousands) {
  assets_millions <- assets_thousands / 1000
  
  case_when(
    assets_millions >= 700000 ~ "$700B and Above",
    assets_millions >= 250000 ~ "$250B-$700B",
    assets_millions >= 100000 ~ "$100B-$250B",
    assets_millions >= 50000  ~ "$50B-$100B",
    assets_millions >= 20000  ~ "$20B-$50B",
    assets_millions >= 10000  ~ "$10B-$20B",
    assets_millions >= 5000   ~ "$5B-$10B",
    assets_millions >= 3000   ~ "$3B-$5B",
    assets_millions >= 1000   ~ "$1B-$3B",
    assets_millions >= 500    ~ "$500M-$1B",
    TRUE                      ~ "Below $500M"
  )
}

# Ordered factor for size categories
size_levels <- c(
  "Below $500M", "$500M-$1B", "$1B-$3B", "$3B-$5B", "$5B-$10B",
  "$10B-$20B", "$20B-$50B", "$50B-$100B", "$100B-$250B", "$250B-$700B", "$700B and Above"
)

# Publication theme
theme_pub <- function(base_size = 12) {
  theme_minimal(base_size = base_size) +
    theme(
      plot.title = element_text(face = "bold", size = base_size + 2),
      plot.subtitle = element_text(color = "gray40"),
      legend.position = "bottom",
      panel.grid.minor = element_blank(),
      axis.title = element_text(face = "bold")
    )
}

# Color palette
COLORS <- list(
  btfp = "#2E86AB", 
  dw = "#A23B72", 
  fhlb = "#F4A261",
  both = "#F18F01", 
  neither = "gray70"
)
# ============================================================================
# PATHS AND KEY DATES
# ============================================================================

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

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

# Key dates
BASELINE_DATE <- "2022Q4"

# Period definitions with specific date ranges
periods <- tribble(
  ~period_num, ~period_name, ~start_date, ~end_date,
  0, "Pre-BTFP",        "2023-03-01", "2023-03-10",
  1, "BTFP_Launch",     "2023-03-10", "2023-03-13",  # Mar 10-13
  2, "Early_Acute",     "2023-03-09", "2023-03-14",  # Mar 9-14
  3, "Acute",           "2023-03-13", "2023-05-01",
  4, "Post-Acute",      "2023-05-02", "2023-10-31",
  5, "Arbitrage",       "2023-11-01", "2024-01-24",
  6, "Wind-down",       "2024-01-25", "2024-03-11"
) %>% mutate(across(c(start_date, end_date), as.Date))

# Specific date ranges for temporal analysis
DATE_MAR10 <- as.Date("2023-03-10")
DATE_MAR13 <- as.Date("2023-03-13")
DATE_MAR09 <- as.Date("2023-03-09")
DATE_MAR14 <- as.Date("2023-03-14")
ACUTE_START <- as.Date("2023-03-13")
ACUTE_END <- as.Date("2023-05-01")
POST_ACUTE_END <- as.Date("2023-10-31")
ARB_START <- as.Date("2023-11-01")
ARB_END <- as.Date("2024-01-24")
WIND_START <- as.Date("2024-01-25")
WIND_END <- as.Date("2024-03-11")
DW_DATA_END <- as.Date("2023-09-30")
# ============================================================================
# LOAD DATA
# ============================================================================

call_q <- read_csv(file.path(DATA_PROC, "final_call_gsib.csv"), show_col_types = FALSE) %>%
  mutate(idrssd = as.character(idrssd))

btfp_loans_raw <- read_csv(file.path(DATA_PROC, "btfp_loan_bank_only.csv"), show_col_types = FALSE) %>%
  mutate(rssd_id = as.character(rssd_id), btfp_loan_date = mdy(btfp_loan_date))

dw_loans_raw <- read_csv(file.path(DATA_PROC, "dw_loan_bank_2023.csv"), show_col_types = FALSE) %>%
  mutate(rssd_id = as.character(rssd_id), dw_loan_date = ymd(dw_loan_date))

cat("=== DATA LOADED ===\n")

=== DATA LOADED ===

cat("Call Report:", nrow(call_q), "obs |", n_distinct(call_q$idrssd), "banks\n")

Call Report: 75989 obs | 5074 banks

cat("BTFP Loans:", nrow(btfp_loans_raw), "| DW Loans:", nrow(dw_loans_raw), "\n")

BTFP Loans: 6734 | DW Loans: 10008

# ============================================================================
# EXCLUDE FAILED BANKS AND GSIBs
# ============================================================================

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

cat("Excluded banks (failed + G-SIBs):", length(excluded_banks), "\n")

Excluded banks (failed + G-SIBs): 41

btfp_loans <- btfp_loans_raw %>% filter(!rssd_id %in% excluded_banks)
dw_loans <- dw_loans_raw %>% filter(!rssd_id %in% excluded_banks)
# ============================================================================
# AGGREGATE BORROWERS BY TIME PERIOD
# ============================================================================

# --- BTFP by period ---
# Pre-Mar 10 (should be none)
btfp_pre_mar10 <- btfp_loans %>%
  filter(btfp_loan_date < DATE_MAR10) %>%
  group_by(rssd_id) %>%
  summarise(btfp_pre_mar10 = 1L, .groups = "drop") %>%
  rename(idrssd = rssd_id)

# Mar 10-13
btfp_mar10_13 <- btfp_loans %>%
  filter(btfp_loan_date >= DATE_MAR10, btfp_loan_date <= DATE_MAR13) %>%
  group_by(rssd_id) %>%
  summarise(btfp_mar10_13 = 1L, .groups = "drop") %>%
  rename(idrssd = rssd_id)

# Mar 9-14
btfp_mar9_14 <- btfp_loans %>%
  filter(btfp_loan_date >= DATE_MAR09, btfp_loan_date <= DATE_MAR14) %>%
  group_by(rssd_id) %>%
  summarise(btfp_mar9_14 = 1L, .groups = "drop") %>%
  rename(idrssd = rssd_id)

# Acute period
btfp_acute <- btfp_loans %>%
  filter(btfp_loan_date >= ACUTE_START, btfp_loan_date <= ACUTE_END) %>%
  group_by(rssd_id) %>%
  summarise(
    btfp_acute = 1L,
    btfp_acute_amount = sum(btfp_loan_amount, na.rm = TRUE),
    btfp_acute_first = min(btfp_loan_date),
    .groups = "drop"
  ) %>%
  rename(idrssd = rssd_id)

# Post-Acute period
btfp_post <- btfp_loans %>%
  filter(btfp_loan_date > ACUTE_END, btfp_loan_date <= POST_ACUTE_END) %>%
  group_by(rssd_id) %>%
  summarise(btfp_post = 1L, .groups = "drop") %>%
  rename(idrssd = rssd_id)

# Arbitrage period
btfp_arb <- btfp_loans %>%
  filter(btfp_loan_date >= ARB_START, btfp_loan_date <= ARB_END) %>%
  group_by(rssd_id) %>%
  summarise(btfp_arb = 1L, .groups = "drop") %>%
  rename(idrssd = rssd_id)

# Wind-down period
btfp_wind <- btfp_loans %>%
  filter(btfp_loan_date >= WIND_START, btfp_loan_date <= WIND_END) %>%
  group_by(rssd_id) %>%
  summarise(btfp_wind = 1L, .groups = "drop") %>%
  rename(idrssd = rssd_id)

# Any BTFP
btfp_any <- btfp_loans %>%
  filter(btfp_loan_date >= ACUTE_START) %>%
  group_by(rssd_id) %>%
  summarise(
    btfp = 1L,
    btfp_amount = sum(btfp_loan_amount, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  rename(idrssd = rssd_id)

# --- DW by period ---
# Pre-BTFP (Mar 1-10)
dw_pre <- dw_loans %>%
  filter(dw_loan_date >= as.Date("2023-03-01"), dw_loan_date < DATE_MAR10) %>%
  group_by(rssd_id) %>%
  summarise(dw_pre = 1L, .groups = "drop") %>%
  rename(idrssd = rssd_id)

# Mar 10 only
dw_mar10 <- dw_loans %>%
  filter(dw_loan_date == DATE_MAR10) %>%
  group_by(rssd_id) %>%
  summarise(dw_mar10 = 1L, .groups = "drop") %>%
  rename(idrssd = rssd_id)

# Mar 10-13
dw_mar10_13 <- dw_loans %>%
  filter(dw_loan_date >= DATE_MAR10, dw_loan_date <= DATE_MAR13) %>%
  group_by(rssd_id) %>%
  summarise(dw_mar10_13 = 1L, .groups = "drop") %>%
  rename(idrssd = rssd_id)

# Mar 9-14
dw_mar9_14 <- dw_loans %>%
  filter(dw_loan_date >= DATE_MAR09, dw_loan_date <= DATE_MAR14) %>%
  group_by(rssd_id) %>%
  summarise(dw_mar9_14 = 1L, .groups = "drop") %>%
  rename(idrssd = rssd_id)

# Acute (post-BTFP launch)
dw_acute <- dw_loans %>%
  filter(dw_loan_date >= ACUTE_START, dw_loan_date <= min(ACUTE_END, DW_DATA_END)) %>%
  group_by(rssd_id) %>%
  summarise(
    dw_acute = 1L,
    dw_acute_amount = sum(dw_loan_amount, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  rename(idrssd = rssd_id)

# Any DW post-BTFP
dw_any <- dw_loans %>%
  filter(dw_loan_date >= ACUTE_START, dw_loan_date <= DW_DATA_END) %>%
  group_by(rssd_id) %>%
  summarise(
    dw = 1L,
    dw_amount = sum(dw_loan_amount, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  rename(idrssd = rssd_id)

cat("\n=== BORROWER COUNTS BY PERIOD ===\n")

=== BORROWER COUNTS BY PERIOD ===

cat("BTFP Acute:", nrow(btfp_acute), "| Post:", nrow(btfp_post), "| Arb:", nrow(btfp_arb), "| Wind:", nrow(btfp_wind), "\n")

BTFP Acute: 485 | Post: 811 | Arb: 797 | Wind: 237

cat("DW Pre:", nrow(dw_pre), "| Mar10:", nrow(dw_mar10), "| Acute:", nrow(dw_acute), "\n")

DW Pre: 89 | Mar10: 50 | Acute: 417

# ============================================================================
# CONSTRUCT ANALYSIS DATASET
# ============================================================================

baseline_excl <- call_q %>% 
  filter(period == BASELINE_DATE, !idrssd %in% excluded_banks)

# Calculate medians for run risk classification
medians <- baseline_excl %>%
  summarise(
    median_uninsured = median(uninsured_deposit_to_total_asset, na.rm = TRUE),
    median_mtm = median(mtm_loss_to_total_asset, na.rm = TRUE)
  )

cat("\n=== MEDIANS FOR CLASSIFICATION ===\n")

=== MEDIANS FOR CLASSIFICATION ===

cat("Median Uninsured Leverage:", medians$median_uninsured, "%\n")

Median Uninsured Leverage: 21.93 %

cat("Median MTM Loss:", medians$median_mtm, "%\n")

Median MTM Loss: 5.272 %

# Build analysis dataset
df <- baseline_excl %>%
  # Join all borrower indicators
  left_join(btfp_any, by = "idrssd") %>%
  left_join(btfp_acute, by = "idrssd") %>%
  left_join(btfp_post, by = "idrssd") %>%
  left_join(btfp_arb, by = "idrssd") %>%
  left_join(btfp_wind, by = "idrssd") %>%
  left_join(btfp_mar10_13, by = "idrssd") %>%
  left_join(btfp_mar9_14, by = "idrssd") %>%
  left_join(dw_any, by = "idrssd") %>%
  left_join(dw_acute, by = "idrssd") %>%
  left_join(dw_pre, by = "idrssd") %>%
  left_join(dw_mar10, by = "idrssd") %>%
  left_join(dw_mar10_13, by = "idrssd") %>%
  left_join(dw_mar9_14, by = "idrssd") %>%
  mutate(

      # Fill NAs with 0 (excluding date and amount columns)
    across(starts_with("btfp") & where(is.integer), ~replace_na(., 0L)),
    across(starts_with("dw") & where(is.integer), ~replace_na(., 0L)),
        
    # ================================================================
    # DEPENDENT VARIABLES
    # ================================================================
    
    # Fed borrowers
    any_fed_acute = as.integer(btfp_acute == 1 | dw_acute == 1),
    both_acute = as.integer(btfp_acute == 1 & dw_acute == 1),
    btfp_only_acute = as.integer(btfp_acute == 1 & dw_acute == 0),
    dw_only_acute = as.integer(btfp_acute == 0 & dw_acute == 1),
    
    # FHLB borrowers (using abnormal borrowing indicators from call report)
    fhlb_borrower_10pct = as.integer(abnormal_fhlb_borrowing_10pct == 1),
    fhlb_borrower_5pct = as.integer(abnormal_fhlb_borrowing_5pct == 1),
    
    # ALL crisis borrowers (DW + BTFP + FHLB)
    all_borrower_acute = as.integer(any_fed_acute == 1 | fhlb_borrower_10pct == 1),
    
    # Facility choice for tables
    facility_choice = factor(
      case_when(
        btfp_acute == 1 & dw_acute == 1 ~ "Both",
        btfp_acute == 1 & dw_acute == 0 ~ "BTFP_Only",
        btfp_acute == 0 & dw_acute == 1 ~ "DW_Only",
        TRUE ~ "Neither"
      ),
      levels = c("BTFP_Only", "DW_Only", "Both", "Neither")
    ),
    
    # ================================================================
    # RUN RISK DUMMIES (2x2 Classification)
    # ================================================================
    
    # a. Solvent and Liquid (both uninsured and MTM below median)
    run_risk_1 = as.integer(
      uninsured_deposit_to_total_asset < medians$median_uninsured & 
        mtm_loss_to_total_asset < medians$median_mtm
    ),
    
    # b. Solvent and Illiquid (low MTM, high uninsured)
    run_risk_2 = as.integer(
      uninsured_deposit_to_total_asset > medians$median_uninsured & 
        mtm_loss_to_total_asset < medians$median_mtm
    ),
    
    # c. Insolvent and Liquid (high MTM, low uninsured)
    run_risk_3 = as.integer(
      uninsured_deposit_to_total_asset < medians$median_uninsured & 
        mtm_loss_to_total_asset > medians$median_mtm
    ),
    
    # d. Insolvent and Illiquid (both above median) - HIGHEST RUN RISK
    run_risk_4 = as.integer(
      uninsured_deposit_to_total_asset > medians$median_uninsured & 
        mtm_loss_to_total_asset > medians$median_mtm
    ),
    
    # Run risk category for tables
    run_risk_category = case_when(
      run_risk_1 == 1 ~ "1: Solvent & Liquid",
      run_risk_2 == 1 ~ "2: Solvent & Illiquid",
      run_risk_3 == 1 ~ "3: Insolvent & Liquid",
      run_risk_4 == 1 ~ "4: Insolvent & Illiquid",
      TRUE ~ NA_character_
    ),
    run_risk_category = factor(run_risk_category, levels = c(
      "1: Solvent & Liquid", "2: Solvent & Illiquid",
      "3: Insolvent & Liquid", "4: Insolvent & Illiquid"
    )),

      # ================================================================
      # RAW KEY VARIABLES (before winsorization)
      # ================================================================
      mtm_btfp_raw = mtm_loss_omo_eligible_to_total_asset,
      mtm_other_raw = mtm_loss_non_omo_eligible_to_total_asset,
      mtm_total_raw = mtm_loss_to_total_asset,
      borrowing_subsidy_raw = mtm_loss_omo_eligible_to_omo_eligible,
      uninsured_lev_raw = uninsured_deposit_to_total_asset,
      uninsured_share_raw = uninsured_to_deposit,
      omo_ratio_raw = omo_eligible_to_total_asset,
      non_omo_ratio_raw = non_omo_eligible_to_total_asset,
      mv_asset = mm_asset,
      change_fhlb_fwd_q_raw = change_fhlb_adv_fwd_q,
          
    
    # Controls
      ln_assets_raw = log(total_asset),
      cash_ratio_raw = cash_to_total_asset,
      securities_ratio_raw = security_to_total_asset,
      loan_ratio_raw = total_loan_to_total_asset,
      book_equity_ratio_raw = book_equity_to_total_asset,
      tier1_ratio_raw = tier1cap_to_total_asset,
      roa_raw = roa,
      loan_to_deposit_raw = loan_to_deposit,
      fhlb_ratio_raw = fhlb_to_total_asset,
      loan_ratio_raw = total_loan_to_total_asset,
      
      wholesale_raw = safe_div(
        fed_fund_purchase + repo + replace_na(other_borrowed_less_than_1yr, 0),
        total_liability, 0
      ) * 100,
    
    
    
        
    # ================================================================
    # KEY VARIABLES (winsorized)
    # ================================================================
      # Key explanatory
      mtm_btfp = winsorize(mtm_btfp_raw),
      mtm_other = winsorize(mtm_other_raw),
      mtm_total = winsorize(mtm_total_raw),
      borrowing_subsidy = winsorize(borrowing_subsidy_raw),
      uninsured_lev = winsorize(uninsured_lev_raw),
      uninsured_share = winsorize(uninsured_share_raw),
      omo_ratio = winsorize(omo_ratio_raw),
      non_omo_ratio = winsorize(non_omo_ratio_raw),
      fhlb_change = winsorize(change_fhlb_fwd_q_raw),
    
    # Interaction term
    mtm_x_uninsured = mtm_total * uninsured_lev,
    
      # Controls
      ln_assets = winsorize(ln_assets_raw),
      cash_ratio = winsorize(cash_ratio_raw),
      securities_ratio = winsorize(securities_ratio_raw),
      loan_ratio = winsorize(loan_ratio_raw),
      book_equity_ratio = winsorize(book_equity_ratio_raw),
      tier1_ratio = winsorize(tier1_ratio_raw),
      roa = winsorize(roa_raw),
      loan_to_deposit = winsorize(loan_to_deposit_raw),
      fhlb_ratio = winsorize(fhlb_ratio_raw),
      wholesale = winsorize(wholesale_raw),
    
    # Deposit outflow indicators (from call report)
    insured_outflow_10pct = as.integer(abnormal_insured_outflow_10pct == 1),
    insured_outflow_5pct = as.integer(abnormal_insured_outflow_5pct == 1),
    uninsured_outflow_10pct = as.integer(abnormal_uninsured_outflow_10pct == 1),
    uninsured_outflow_5pct = as.integer(abnormal_uninsured_outflow_5pct == 1),
    
    # Size category
    size_category = factor(create_size_category(total_asset), levels = size_levels),
    
    # Prior DW (operational readiness)
    prior_dw = dw_pre
  )

cat("\n=== ANALYSIS DATASET ===\n")

=== ANALYSIS DATASET ===

cat("Total banks:", nrow(df), "\n")

Total banks: 4696

cat("BTFP acute:", sum(df$btfp_acute), "\n")

BTFP acute: 478

cat("DW acute:", sum(df$dw_acute), "\n")

DW acute: 412

cat("Any Fed acute:", sum(df$any_fed_acute), "\n")

Any Fed acute: 794

cat("FHLB abnormal (10%):", sum(df$fhlb_borrower_10pct, na.rm = TRUE), "\n")

FHLB abnormal (10%): 325

cat("All crisis borrowers:", sum(df$all_borrower_acute, na.rm = TRUE), "\n")

All crisis borrowers: 1062

cat("\n--- Run Risk Distribution ---\n")

— Run Risk Distribution —

cat("Run Risk 1 (Solvent & Liquid):", sum(df$run_risk_1, na.rm = TRUE), "\n")

Run Risk 1 (Solvent & Liquid): 1097

cat("Run Risk 2 (Solvent & Illiquid):", sum(df$run_risk_2, na.rm = TRUE), "\n")

Run Risk 2 (Solvent & Illiquid): 1242

cat("Run Risk 3 (Insolvent & Liquid):", sum(df$run_risk_3, na.rm = TRUE), "\n")

Run Risk 3 (Insolvent & Liquid): 1241

cat("Run Risk 4 (Insolvent & Illiquid):", sum(df$run_risk_4, na.rm = TRUE), "\n")

Run Risk 4 (Insolvent & Illiquid): 1098


2 Summary Statistics

2.1 Table 1: Summary by Run Risk Category

# ============================================================================
# SUMMARY BY RUN RISK CATEGORY
# ============================================================================

run_risk_summary <- df %>%
  filter(!is.na(run_risk_category)) %>%
  group_by(run_risk_category) %>%
  summarise(
    N = n(),
    `% Sample` = n() / nrow(df) * 100,
    `BTFP (%)` = mean(btfp_acute, na.rm = TRUE) * 100,
    `DW (%)` = mean(dw_acute, na.rm = TRUE) * 100,
    `Any Fed (%)` = mean(any_fed_acute, na.rm = TRUE) * 100,
    `FHLB (%)` = mean(fhlb_borrower_10pct, na.rm = TRUE) * 100,
    `All Crisis (%)` = mean(all_borrower_acute, na.rm = TRUE) * 100,
    `MTM (%)` = mean(mtm_total, na.rm = TRUE),
    `Uninsured (%)` = mean(uninsured_lev, na.rm = TRUE),
    .groups = "drop"
  )

run_risk_summary %>%
  kable(
    caption = "Table 1: Crisis Borrowing by Run Risk Category",
    digits = 2,
    format = "html"
  ) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  row_spec(4, bold = TRUE, background = "#FFE4E1")
Table 1: Crisis Borrowing by Run Risk Category
run_risk_category N % Sample BTFP (%) DW (%) Any Fed (%) FHLB (%) All Crisis (%) MTM (%) Uninsured (%)
1: Solvent & Liquid 1097 23.36 4.65 4.28 8.30 5.65 13.31 3.60 13.58
2: Solvent & Illiquid 1242 26.45 9.34 9.74 16.75 8.05 23.11 3.64 33.24
3: Insolvent & Liquid 1241 26.43 9.02 7.25 15.23 6.69 21.19 7.39 14.27
4: Insolvent & Illiquid 1098 23.38 18.12 14.03 27.87 7.29 33.33 6.99 31.10

2.2 Table 2: Summary by Asset Size and Facility Choice (Acute Period)

# ============================================================================
# SUMMARY TABLE BY ASSET SIZE AND FACILITY CHOICE
# ============================================================================

# First, count of banks by size and facility
count_table <- df %>%
  group_by(size_category, facility_choice) %>%
  summarise(n = n(), .groups = "drop") %>%
  pivot_wider(names_from = facility_choice, values_from = n, values_fill = 0) %>%
  mutate(Total = BTFP_Only + DW_Only + Both + Neither) %>%
  arrange(factor(size_category, levels = size_levels))

# Characteristics by size and facility choice
char_by_size <- df %>%
  group_by(size_category, facility_choice) %>%
  summarise(
    N = n(),
    MTM_Total = mean(mtm_total, na.rm = TRUE),
    Uninsured_Lev = mean(uninsured_lev, na.rm = TRUE),
    Uninsured_Share = mean(uninsured_share, na.rm = TRUE),
    Loan_Ratio = mean(loan_ratio, na.rm = TRUE),
    Securities_Ratio = mean(securities_ratio, na.rm = TRUE),
    Cash_Ratio = mean(cash_ratio, na.rm = TRUE),
    Insured_Outflow_10 = mean(insured_outflow_10pct, na.rm = TRUE) * 100,
    Uninsured_Outflow_10 = mean(uninsured_outflow_10pct, na.rm = TRUE) * 100,
    .groups = "drop"
  ) %>%
  arrange(factor(size_category, levels = size_levels))

# --- Create formatted output ---
cat("\n\n### Panel A: Number of Banks by Asset Size and Facility Choice\n\n")

2.2.1 Panel A: Number of Banks by Asset Size and Facility Choice

count_table %>%
  kable(
    caption = "Number of Banks by Asset Size and Facility Choice (Acute Period)",
    format = "html",
    col.names = c("Asset Size", "BTFP Only", "DW Only", "Both", "Neither", "Total")
  ) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
Number of Banks by Asset Size and Facility Choice (Acute Period)
Asset Size BTFP Only DW Only Both Neither Total
Below $500M 173 112 25 2669 2979
$500M-$1B 84 65 14 601 764
$1B-$3B 78 73 27 407 585
$3B-$5B 13 25 3 80 121
$5B-$10B 14 17 7 74 112
$10B-$20B 6 16 9 26 57
$20B-$50B 7 4 7 27 45
$50B-$100B 4 1 2 8 15
$100B-$250B 2 2 2 7 13
$250B-$700B 1 1 0 3 5
# ============================================================================
# DETAILED CHARACTERISTICS BY SIZE BUCKET
# ============================================================================

create_size_panel <- function(data, size_cat) {
  
  size_data <- data %>% filter(size_category == size_cat)
  
  if (nrow(size_data) == 0) return(NULL)
  
  # Counts
  counts <- size_data %>%
    group_by(facility_choice) %>%
    summarise(N = n(), .groups = "drop") %>%
    pivot_wider(names_from = facility_choice, values_from = N, values_fill = 0)
  
  # Characteristics by facility
  chars <- size_data %>%
    group_by(facility_choice) %>%
    summarise(
      N = n(),
      `MTM Total (%)` = mean(mtm_total, na.rm = TRUE),
      `Uninsured Lev (%)` = mean(uninsured_lev, na.rm = TRUE),
      `Uninsured Share (%)` = mean(uninsured_share, na.rm = TRUE),
      `Loan Ratio (%)` = mean(loan_ratio, na.rm = TRUE),
      `Securities Ratio (%)` = mean(securities_ratio, na.rm = TRUE),
      `Cash Ratio (%)` = mean(cash_ratio, na.rm = TRUE),
      `10% Insured Outflow` = mean(insured_outflow_10pct, na.rm = TRUE) * 100,
      `10% Uninsured Outflow` = mean(uninsured_outflow_10pct, na.rm = TRUE) * 100,
      .groups = "drop"
    )
  
  return(chars)
}

# Generate panels for each size category
for (sz in size_levels) {
  panel <- create_size_panel(df, sz)
  if (!is.null(panel) && nrow(panel) > 0) {
    cat(sprintf("\n\n### Panel: %s\n\n", sz))
    print(
      panel %>%
        kable(
          digits = 2,
          format = "html"
        ) %>%
        kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
    )
  }
}

2.2.2 Panel: Below $500M

facility_choice N MTM Total (%) Uninsured Lev (%) Uninsured Share (%) Loan Ratio (%) Securities Ratio (%) Cash Ratio (%) 10% Insured Outflow 10% Uninsured Outflow
BTFP_Only 173 6.23 22.96 26.49 57.86 30.89 5.65 15.03 20.23
DW_Only 112 5.72 22.31 26.11 59.49 25.59 8.50 7.14 23.21
Both 25 6.42 24.44 28.16 54.34 33.38 6.00 8.00 24.00
Neither 2669 5.25 19.73 23.31 57.20 25.45 10.01 8.21 15.40

2.2.3 Panel: $500M-$1B

facility_choice N MTM Total (%) Uninsured Lev (%) Uninsured Share (%) Loan Ratio (%) Securities Ratio (%) Cash Ratio (%) 10% Insured Outflow 10% Uninsured Outflow
BTFP_Only 84 6.22 25.39 29.39 64.41 25.99 3.93 9.52 29.76
DW_Only 65 5.96 24.77 29.26 64.35 23.39 6.44 3.08 35.38
Both 14 6.01 33.02 37.60 56.28 34.45 4.93 14.29 21.43
Neither 601 5.59 26.47 30.59 65.35 21.79 7.09 6.82 26.29

2.2.4 Panel: $1B-$3B

facility_choice N MTM Total (%) Uninsured Lev (%) Uninsured Share (%) Loan Ratio (%) Securities Ratio (%) Cash Ratio (%) 10% Insured Outflow 10% Uninsured Outflow
BTFP_Only 78 6.33 27.40 32.59 66.56 24.19 3.52 11.54 29.49
DW_Only 73 5.71 27.61 32.90 68.53 20.37 5.43 5.48 23.29
Both 27 5.82 33.23 38.86 65.71 23.63 5.17 7.41 44.44
Neither 407 5.38 28.71 34.00 67.42 19.63 6.50 2.95 29.48

2.2.5 Panel: $3B-$5B

facility_choice N MTM Total (%) Uninsured Lev (%) Uninsured Share (%) Loan Ratio (%) Securities Ratio (%) Cash Ratio (%) 10% Insured Outflow 10% Uninsured Outflow
BTFP_Only 13 6.74 34.40 39.93 58.33 31.97 3.98 0.00 53.85
DW_Only 25 5.44 31.22 36.07 65.03 23.34 5.47 0.00 20.00
Both 3 6.21 30.46 36.63 65.85 26.10 1.56 0.00 0.00
Neither 80 5.08 30.64 36.97 70.54 15.99 5.56 1.25 32.50

2.2.6 Panel: $5B-$10B

facility_choice N MTM Total (%) Uninsured Lev (%) Uninsured Share (%) Loan Ratio (%) Securities Ratio (%) Cash Ratio (%) 10% Insured Outflow 10% Uninsured Outflow
BTFP_Only 14 5.64 28.96 35.92 59.98 27.19 5.82 14.29 21.43
DW_Only 17 4.96 37.96 44.72 66.30 23.20 3.59 0.00 52.94
Both 7 6.79 26.00 31.81 63.96 27.93 1.52 0.00 42.86
Neither 74 4.80 30.67 37.17 69.32 19.01 4.91 5.41 29.73

2.2.7 Panel: $10B-$20B

facility_choice N MTM Total (%) Uninsured Lev (%) Uninsured Share (%) Loan Ratio (%) Securities Ratio (%) Cash Ratio (%) 10% Insured Outflow 10% Uninsured Outflow
BTFP_Only 6 4.46 39.57 49.86 73.63 15.58 2.75 0 66.67
DW_Only 16 5.05 35.78 44.15 71.07 17.51 2.88 0 18.75
Both 9 5.89 42.07 51.60 65.92 25.08 3.09 0 33.33
Neither 26 4.87 27.28 32.73 63.66 22.67 5.97 0 38.46

2.2.8 Panel: $20B-$50B

facility_choice N MTM Total (%) Uninsured Lev (%) Uninsured Share (%) Loan Ratio (%) Securities Ratio (%) Cash Ratio (%) 10% Insured Outflow 10% Uninsured Outflow
BTFP_Only 7 5.25 29.22 35.68 59.56 28.65 3.97 0.00 42.86
DW_Only 4 5.92 38.66 47.10 61.41 28.64 2.06 0.00 50.00
Both 7 4.96 36.07 43.78 64.83 23.49 2.86 0.00 57.14
Neither 27 5.04 33.03 41.18 65.20 20.75 4.85 11.11 29.63

2.2.9 Panel: $50B-$100B

facility_choice N MTM Total (%) Uninsured Lev (%) Uninsured Share (%) Loan Ratio (%) Securities Ratio (%) Cash Ratio (%) 10% Insured Outflow 10% Uninsured Outflow
BTFP_Only 4 4.29 45.36 55.66 66.08 21.07 4.35 0 50
DW_Only 1 6.37 50.12 52.78 38.74 45.84 10.75 0 0
Both 2 4.82 52.08 62.70 76.53 13.03 3.59 0 100
Neither 8 4.15 36.30 45.73 67.76 17.16 7.02 0 25

2.2.10 Panel: $100B-$250B

facility_choice N MTM Total (%) Uninsured Lev (%) Uninsured Share (%) Loan Ratio (%) Securities Ratio (%) Cash Ratio (%) 10% Insured Outflow 10% Uninsured Outflow
BTFP_Only 2 3.62 45.72 56.38 57.40 24.97 8.28 0 0.00
DW_Only 2 7.21 29.82 35.52 50.64 37.47 5.61 0 50.00
Both 2 2.92 26.15 31.59 71.31 10.10 12.04 0 0.00
Neither 7 3.71 26.80 33.22 63.24 18.07 9.18 0 57.14

2.2.11 Panel: $250B-$700B

facility_choice N MTM Total (%) Uninsured Lev (%) Uninsured Share (%) Loan Ratio (%) Securities Ratio (%) Cash Ratio (%) 10% Insured Outflow 10% Uninsured Outflow
BTFP_Only 1 6.48 40.48 50.96 56.56 25.72 7.77 0 0.00
DW_Only 1 4.28 34.59 43.23 58.36 25.39 6.21 0 0.00
Neither 3 6.31 28.82 34.97 46.11 33.73 5.85 0 66.67

2.3 Table 3: Comprehensive Summary by Asset Size

# ============================================================================
# COMPREHENSIVE TABLE: SIZE × FACILITY WITH ALL CHARACTERISTICS
# ============================================================================

comprehensive_table <- df %>%
  group_by(size_category) %>%
  summarise(
    # Counts
    Total = n(),
    BTFP_Only = sum(btfp_only_acute),
    DW_Only = sum(dw_only_acute),
    Both = sum(both_acute),
    Neither = sum(facility_choice == "Neither"),
    
    # Percentages
    BTFP_Pct = mean(btfp_acute) * 100,
    DW_Pct = mean(dw_acute) * 100,
    Any_Fed_Pct = mean(any_fed_acute) * 100,
    FHLB_Pct = mean(fhlb_borrower_10pct, na.rm = TRUE) * 100,
    All_Crisis_Pct = mean(all_borrower_acute, na.rm = TRUE) * 100,
    
    # Fundamentals
    MTM_Total = mean(mtm_total, na.rm = TRUE),
    MTM_BTFP = mean(mtm_btfp, na.rm = TRUE),
    
    # Deposit structure
    Uninsured_Lev = mean(uninsured_lev, na.rm = TRUE),
    Uninsured_Share = mean(uninsured_share, na.rm = TRUE),
    
    # Asset composition
    Loan_Ratio = mean(loan_ratio, na.rm = TRUE),
    Securities_Ratio = mean(securities_ratio, na.rm = TRUE),
    Cash_Ratio = mean(cash_ratio, na.rm = TRUE),
    
    # Outflows
    Insured_Out_10 = mean(insured_outflow_10pct, na.rm = TRUE) * 100,
    Insured_Out_5 = mean(insured_outflow_5pct, na.rm = TRUE) * 100,
    Uninsured_Out_10 = mean(uninsured_outflow_10pct, na.rm = TRUE) * 100,
    Uninsured_Out_5 = mean(uninsured_outflow_5pct, na.rm = TRUE) * 100,
    
    .groups = "drop"
  ) %>%
  arrange(factor(size_category, levels = size_levels))

# Display counts
cat("\n\n### Panel A: Bank Counts by Facility\n\n")

2.3.1 Panel A: Bank Counts by Facility

comprehensive_table %>%
  select(size_category, Total, BTFP_Only, DW_Only, Both, Neither) %>%
  kable(
    col.names = c("Asset Size", "Total", "BTFP Only", "DW Only", "Both", "Neither"),
    format = "html"
  ) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Asset Size Total BTFP Only DW Only Both Neither
Below $500M 2979 173 112 25 2669
$500M-$1B 764 84 65 14 601
$1B-$3B 585 78 73 27 407
$3B-$5B 121 13 25 3 80
$5B-$10B 112 14 17 7 74
$10B-$20B 57 6 16 9 26
$20B-$50B 45 7 4 7 27
$50B-$100B 15 4 1 2 8
$100B-$250B 13 2 2 2 7
$250B-$700B 5 1 1 0 3
# Display percentages
cat("\n\n### Panel B: Borrowing Rates (%)\n\n")

2.3.2 Panel B: Borrowing Rates (%)

comprehensive_table %>%
  select(size_category, Total, BTFP_Pct, DW_Pct, Any_Fed_Pct, FHLB_Pct, All_Crisis_Pct) %>%
  kable(
    col.names = c("Asset Size", "N", "BTFP", "DW", "Any Fed", "FHLB", "All Crisis"),
    digits = 1,
    format = "html"
  ) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Asset Size N BTFP DW Any Fed FHLB All Crisis
Below $500M 2979 6.6 4.6 10.4 5.6 15.5
$500M-$1B 764 12.8 10.3 21.3 7.7 28.1
$1B-$3B 585 17.9 17.1 30.4 9.2 36.9
$3B-$5B 121 13.2 23.1 33.9 8.3 36.4
$5B-$10B 112 18.8 21.4 33.9 12.5 44.6
$10B-$20B 57 26.3 43.9 54.4 15.8 63.2
$20B-$50B 45 31.1 24.4 40.0 17.8 51.1
$50B-$100B 15 40.0 20.0 46.7 13.3 60.0
$100B-$250B 13 30.8 30.8 46.2 7.7 46.2
$250B-$700B 5 20.0 20.0 40.0 0.0 40.0
# Display fundamentals
cat("\n\n### Panel C: Bank Fundamentals\n\n")

2.3.3 Panel C: Bank Fundamentals

comprehensive_table %>%
  select(size_category, Total, MTM_Total, Uninsured_Lev, Uninsured_Share, Loan_Ratio, Securities_Ratio, Cash_Ratio) %>%
  kable(
    col.names = c("Asset Size", "N", "MTM Total (%)", "Uninsured Lev (%)", "Uninsured Share (%)", 
                  "Loan Ratio (%)", "Securities (%)", "Cash (%)"),
    digits = 2,
    format = "html"
  ) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Asset Size N MTM Total (%) Uninsured Lev (%) Uninsured Share (%) Loan Ratio (%) Securities (%) Cash (%)
Below $500M 2979 5.34 20.06 23.64 57.30 25.83 9.66
$500M-$1B 764 5.70 26.32 30.47 64.99 22.62 6.65
$1B-$3B 585 5.57 28.61 33.90 67.37 20.52 5.90
$3B-$5B 121 5.36 31.16 37.09 67.98 19.47 5.27
$5B-$10B 112 5.06 31.27 37.83 67.36 21.22 4.61
$10B-$20B 57 5.04 33.30 40.71 67.15 20.86 4.31
$20B-$50B 45 5.14 33.41 41.26 63.93 23.10 4.16
$50B-$100B 15 4.43 41.75 51.11 66.55 19.56 6.10
$100B-$250B 13 4.11 30.08 36.88 61.64 20.89 8.93
$250B-$700B 5 5.94 32.31 39.82 50.65 30.46 6.31
# Display outflows
cat("\n\n### Panel D: Deposit Outflows\n\n")

2.3.4 Panel D: Deposit Outflows

comprehensive_table %>%
  select(size_category, Total, Insured_Out_10, Insured_Out_5, Uninsured_Out_10, Uninsured_Out_5) %>%
  kable(
    col.names = c("Asset Size", "N", "Insured 10%", "Insured 5%", "Uninsured 10%", "Uninsured 5%"),
    digits = 1,
    format = "html"
  ) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Asset Size N Insured 10% Insured 5% Uninsured 10% Uninsured 5%
Below $500M 2979 8.6 3.1 16.0 6.5
$500M-$1B 764 6.9 2.0 27.4 11.1
$1B-$3B 585 4.6 2.4 29.4 13.2
$3B-$5B 121 0.8 0.0 31.4 17.4
$5B-$10B 112 5.4 1.8 33.0 17.0
$10B-$20B 57 0.0 0.0 35.1 26.3
$20B-$50B 45 6.7 2.2 37.8 15.6
$50B-$100B 15 0.0 0.0 40.0 20.0
$100B-$250B 13 0.0 0.0 38.5 15.4
$250B-$700B 5 0.0 0.0 40.0 20.0

3 Regression Models

3.1 Model Settings

# ============================================================================
# MODEL SETTINGS
# ============================================================================

# Standard controls
CONTROLS <- "ln_assets + cash_ratio + securities_ratio + book_equity_ratio + fhlb_ratio + roa"

# Coefficient labels
COEF_MAP <- c(
  "mtm_total" = "MTM Loss (Total)",
  "mtm_btfp" = "MTM Loss (BTFP-Eligible)",
  "uninsured_lev" = "Uninsured Leverage",
  "loan_ratio" = "Loan Ratio",
  "I(mtm_total * uninsured_lev)" = "MTM × Uninsured",
  "run_risk_1" = "Run Risk 1: Solvent & Liquid",
  "run_risk_2" = "Run Risk 2: Solvent & Illiquid",
  "run_risk_3" = "Run Risk 3: Insolvent & Liquid",
  "run_risk_4" = "Run Risk 4: Insolvent & Illiquid",
  "ln_assets"            = "Log(Assets)",
  "cash_ratio"           = "Cash Ratio",
  "securities_ratio"     = "Securities Ratio",
  "loan_to_deposit"      = "Loan/Deposit",
  "book_equity_ratio"    = "Book Equity",
  "wholesale"            = "Wholesale Funding",
  "fhlb_ratio"           = "FHLB Ratio",
  "roa"                  = "ROA",
  "prior_dw" = "Prior DW User"
)

3.2 4.1 First Models: All Borrowers (DW + BTFP + FHLB) - Acute Period

# ============================================================================
# TABLE: ALL BORROWERS (DW + BTFP + FHLB) - ACUTE PERIOD
# Each run risk dummy added separately
# ============================================================================

# Define Controls (Source of truth)
CONTROLS <- "ln_assets + cash_ratio + securities_ratio + loan_ratio + book_equity_ratio + wholesale + fhlb_ratio + roa"

# Base covariates present in all models
BASE_VARS <- "all_borrower_acute ~ mtm_total + uninsured_lev"

# Helper function to construct formulas dynamically

get_fml <- function(add_var = NULL) {
  if (is.null(add_var)) {
    as.formula(paste(BASE_VARS, "+", CONTROLS))
  } else {
    as.formula(paste(BASE_VARS, "+", add_var, "+", CONTROLS))
  }
}

# Model 1: Base model with interaction
m1_all <- feols(get_fml("I(mtm_total * uninsured_lev)"), 
                data = df, vcov = "hetero")

# Model 2: Add Run Risk 1 (Solvent & Liquid)
m2_all <- feols(get_fml("run_risk_1"), 
                data = df, vcov = "hetero")

# Model 3: Add Run Risk 2 (Solvent & Illiquid)
m3_all <- feols(get_fml("run_risk_2"), 
                data = df, vcov = "hetero")

# Model 4: Add Run Risk 3 (Insolvent & Liquid)
m4_all <- feols(get_fml("run_risk_3"), 
                data = df, vcov = "hetero")

# Model 5: Add Run Risk 4 (Insolvent & Illiquid) - HIGHEST RISK
m5_all <- feols(get_fml("run_risk_4"), 
                data = df, vcov = "hetero")

# Create List for Modelsummary
models_all <- list(
  "Base + Interaction"    = m1_all,
  "+ RR1 (Sol & Liq)"     = m2_all,
  "+ RR2 (Sol & Illiq)"   = m3_all,
  "+ RR3 (Insol & Liq)"   = m4_all,
  "+ RR4 (Insol & Illiq)" = m5_all
)

# Output Table
modelsummary(
  models_all,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared", "adj.r.squared"),
  title = "Table 4: All Crisis Borrowers (DW + BTFP + FHLB) - Acute Period",
  notes = list(
    "DV: 1 if bank borrowed from DW, BTFP, or had abnormal FHLB increase during acute period.",
    "Each run risk dummy added separately (not together).",
    "Run Risk categories based on median splits of MTM loss and uninsured deposits.",
    "RR1: Solvent & Liquid (low MTM, low uninsured) | RR2: Solvent & Illiquid (low MTM, high uninsured)",
    "RR3: Insolvent & Liquid (high MTM, low uninsured) | RR4: Insolvent & Illiquid (high MTM, high uninsured)"
  ),
  output = "kableExtra"
) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), font_size = 9) %>%
  add_header_above(c(" " = 1, "DV: All Crisis Borrowers (Acute) = 1" = 5))
Table 4: All Crisis Borrowers (DW + BTFP + FHLB) - Acute Period
DV: All Crisis Borrowers (Acute) = 1
&nbsp;Base + Interaction
  • RR1 (Sol & Liq)
&nbsp;+ RR2 (Sol & Illiq) &nbsp;+ RR3 (Insol & Liq) &nbsp;+ RR4 (Insol & Illiq)
MTM Loss (Total) −0.000 0.014*** 0.009*** 0.017*** 0.007**
(0.005) (0.004) (0.003) (0.004) (0.003)
Uninsured Leverage −0.001 0.002** 0.002*** 0.001 0.001
(0.001) (0.001) (0.001) (0.001) (0.001)
Loan Ratio 0.001 0.000 0.000 0.000 0.001
(0.001) (0.001) (0.001) (0.001) (0.001)
MTM × Uninsured 0.001***
(0.000)
Run Risk 1: Solvent & Liquid 0.019
(0.018)
Run Risk 2: Solvent & Illiquid −0.026
(0.018)
Run Risk 3: Insolvent & Liquid −0.050***
(0.018)
Run Risk 4: Insolvent & Illiquid 0.054***
(0.019)
Log(Assets) 0.069*** 0.070*** 0.070*** 0.070*** 0.070***
(0.006) (0.006) (0.006) (0.006) (0.006)
Cash Ratio −0.002*** −0.003*** −0.003*** −0.003*** −0.003***
(0.001) (0.001) (0.001) (0.001) (0.001)
Securities Ratio 0.001 0.001 0.001 0.001 0.001
(0.001) (0.001) (0.001) (0.001) (0.001)
Book Equity −0.001 −0.001 −0.001 −0.001 −0.001
(0.001) (0.001) (0.001) (0.001) (0.001)
Wholesale Funding 0.015*** 0.015*** 0.015*** 0.015*** 0.015***
(0.004) (0.004) (0.004) (0.004) (0.004)
FHLB Ratio 0.008*** 0.008*** 0.008*** 0.008*** 0.008***
(0.002) (0.002) (0.002) (0.002) (0.002)
ROA −0.002 0.001 0.000 0.000 −0.001
(0.010) (0.010) (0.010) (0.010) (0.010)
Num.Obs. 4678 4678 4678 4678 4678
R2 0.108 0.107 0.107 0.108 0.108
R2 Adj. 0.105 0.104 0.105 0.106 0.106
* p < 0.1, ** p < 0.05, *** p < 0.01
DV: 1 if bank borrowed from DW, BTFP, or had abnormal FHLB increase during acute period.
Each run risk dummy added separately (not together).
Run Risk categories based on median splits of MTM loss and uninsured deposits.
RR1: Solvent & Liquid (low MTM, low uninsured) | RR2: Solvent & Illiquid (low MTM, high uninsured)
RR3: Insolvent & Liquid (high MTM, low uninsured) | RR4: Insolvent & Illiquid (high MTM, high uninsured)

3.3 4.2 Second Models: BTFP Users Only

# ============================================================================
# TABLE: BTFP USERS ONLY - ACUTE PERIOD
# Structure: Base Vars (MTM + Uninsured) + Specific Explanatory Var + Controls
# ============================================================================

# 1. Define Controls
CONTROLS <- "ln_assets + cash_ratio + securities_ratio + loan_ratio + book_equity_ratio + wholesale + fhlb_ratio + roa"

# 2. Define Base Variables (Present in ALL models)
BASE_VARS <- "mtm_total + uninsured_lev"

# 3. Helper function to construct formulas
# Adds the specific "variable_of_interest" to the base variables and controls
get_btfp_fml <- function(variable_of_interest) {
  as.formula(paste("btfp_acute ~", BASE_VARS, "+", variable_of_interest, "+", CONTROLS))
}

# Model 1: Explanatory Variable = Interaction Term
m1_btfp <- feols(get_btfp_fml("I(mtm_total * uninsured_lev)"), 
                 data = df, vcov = "hetero")

# Model 2: Explanatory Variable = Run Risk 1 (Solvent & Liquid)
m2_btfp <- feols(get_btfp_fml("run_risk_1"), 
                 data = df, vcov = "hetero")

# Model 3: Explanatory Variable = Run Risk 2 (Solvent & Illiquid)
m3_btfp <- feols(get_btfp_fml("run_risk_2"), 
                 data = df, vcov = "hetero")

# Model 4: Explanatory Variable = Run Risk 3 (Insolvent & Liquid)
m4_btfp <- feols(get_btfp_fml("run_risk_3"), 
                 data = df, vcov = "hetero")

# Model 5: Explanatory Variable = Run Risk 4 (Insolvent & Illiquid)
m5_btfp <- feols(get_btfp_fml("run_risk_4"), 
                 data = df, vcov = "hetero")

# Create List for Modelsummary
models_btfp <- list(
  "Model 1 (Interaction)" = m1_btfp,
  "Model 2 (+ RR1)"       = m2_btfp,
  "Model 3 (+ RR2)"       = m3_btfp,
  "Model 4 (+ RR3)"       = m4_btfp,
  "Model 5 (+ RR4)"       = m5_btfp
)

# Output Table
modelsummary(
  models_btfp,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared", "adj.r.squared"),
  title = "Table 5: BTFP Borrowers Only - Acute Period",
  notes = list(
    "DV: 1 if bank borrowed from BTFP during acute period.",
    "Models share common controls and base variables (MTM + Uninsured).",
    "Model 1 tests the interaction term specifically.",
    "Models 2-5 test specific Run Risk categories separately."
  ),
  output = "kableExtra"
) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), font_size = 9) %>%
  add_header_above(c(" " = 1, "DV: BTFP (Acute) = 1" = 5))
Table 5: BTFP Borrowers Only - Acute Period
DV: BTFP (Acute) = 1
&nbsp;Model 1 (Interaction) &nbsp;Model 2 (+ RR1) &nbsp;Model 3 (+ RR2) &nbsp;Model 4 (+ RR3) &nbsp;Model 5 (+ RR4)
MTM Loss (Total) −0.005 0.009*** 0.004* 0.011*** 0.003
(0.003) (0.003) (0.002) (0.003) (0.002)
Uninsured Leverage −0.002** 0.002*** 0.002*** 0.001 0.001
(0.001) (0.001) (0.001) (0.000) (0.000)
Loan Ratio −0.000 −0.001* −0.001 −0.001 −0.001
(0.000) (0.000) (0.000) (0.000) (0.000)
MTM × Uninsured 0.001***
(0.000)
Run Risk 1: Solvent & Liquid 0.024*
(0.013)
Run Risk 2: Solvent & Illiquid −0.029**
(0.014)
Run Risk 3: Insolvent & Liquid −0.041***
(0.013)
Run Risk 4: Insolvent & Illiquid 0.045***
(0.015)
Log(Assets) 0.033*** 0.034*** 0.034*** 0.034*** 0.034***
(0.004) (0.004) (0.004) (0.004) (0.004)
Cash Ratio −0.001** −0.002*** −0.002*** −0.002*** −0.002**
(0.001) (0.001) (0.001) (0.001) (0.001)
Securities Ratio 0.001* 0.001 0.001 0.001 0.001*
(0.000) (0.001) (0.000) (0.000) (0.000)
Book Equity −0.002*** −0.002*** −0.002*** −0.002*** −0.002***
(0.001) (0.001) (0.001) (0.001) (0.001)
Wholesale Funding 0.012*** 0.012*** 0.012*** 0.012*** 0.012***
(0.003) (0.003) (0.003) (0.003) (0.003)
FHLB Ratio 0.007*** 0.007*** 0.007*** 0.007*** 0.007***
(0.001) (0.001) (0.001) (0.001) (0.001)
ROA −0.001 0.002 0.002 0.002 0.000
(0.007) (0.007) (0.007) (0.007) (0.007)
Num.Obs. 4678 4678 4678 4678 4678
R2 0.074 0.072 0.073 0.073 0.074
R2 Adj. 0.072 0.070 0.070 0.071 0.072
* p < 0.1, ** p < 0.05, *** p < 0.01
DV: 1 if bank borrowed from BTFP during acute period.
Models share common controls and base variables (MTM + Uninsured).
Model 1 tests the interaction term specifically.
Models 2-5 test specific Run Risk categories separately.

3.4 4.3 Temporal Models: DW

# ============================================================================
# TABLE: DW TEMPORAL ANALYSIS
# Specification: Base + I(mtm_total * uninsured_lev) + Controls
# Comparing coefficients across different time windows
# ============================================================================

# 1. Define Controls (Standardized with Tables 4 & 5)
CONTROLS <- "ln_assets + cash_ratio + securities_ratio + loan_ratio + book_equity_ratio + wholesale + fhlb_ratio + roa"

# 2. Define Base Variables
BASE_VARS <- "mtm_total + uninsured_lev"

# 3. Define the Main Explanatory Variable of Interest
# (Testing the "Insolvent & Illiquid" hypothesis across time)
MAIN_VAR <- "I(mtm_total * uninsured_lev)"

# 4. Helper function to construct formulas dynamically
# Structure: DV ~ Base + I(mtm_total * uninsured_lev) + Controls
get_dw_fml <- function(dv_name) {
  as.formula(paste(dv_name, "~", BASE_VARS, "+", MAIN_VAR, "+", CONTROLS))
}

# Model 1: DW Mar 10 only (pre-BTFP announcement)
m_dw_mar10 <- feols(get_dw_fml("dw_mar10"), 
                    data = df, vcov = "hetero")

# Model 2: DW Mar 10-13 (The panic weekend)
m_dw_mar10_13 <- feols(get_dw_fml("dw_mar10_13"), 
                       data = df, vcov = "hetero")

# Model 3: DW Mar 9-14 (Extended window)
m_dw_mar9_14 <- feols(get_dw_fml("dw_mar9_14"), 
                      data = df, vcov = "hetero")

# Model 4: DW Acute Period (Full Crisis)
m_dw_acute <- feols(get_dw_fml("dw_acute"), 
                    data = df, vcov = "hetero")

models_dw_temp <- list(
  "Mar 10"      = m_dw_mar10,
  "Mar 10-13"   = m_dw_mar10_13,
  "Mar 9-14"    = m_dw_mar9_14,
  "Acute"       = m_dw_acute
)

modelsummary(
  models_dw_temp,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 6: Discount Window - Temporal Analysis",
  notes = list(
    "DV: 1 if bank borrowed from DW during specified period.",
    "Specification tests Run Risk = I(mtm_total * uninsured_lev) across all time windows.",
    "Mar 10 = day of SVB closure | Mar 13 = BTFP launch | Acute = Mar 13 - May 1"
  ),
  output = "kableExtra"
) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), font_size = 9) %>%
  add_header_above(c(" " = 1, "DW Borrowing by Period" = 4))
Table 6: Discount Window - Temporal Analysis
DW Borrowing by Period
&nbsp;Mar 10 &nbsp;Mar 10-13 &nbsp;Mar 9-14 Acute
MTM Loss (Total) −0.001 −0.005*** −0.005** −0.006*
(0.001) (0.002) (0.002) (0.003)
Uninsured Leverage 0.000 −0.000 −0.000 −0.002*
(0.000) (0.000) (0.001) (0.001)
Loan Ratio −0.000* −0.000 0.000 −0.000
(0.000) (0.000) (0.000) (0.000)
MTM × Uninsured 0.000 0.000* 0.000* 0.000***
(0.000) (0.000) (0.000) (0.000)
Log(Assets) 0.008*** 0.017*** 0.018*** 0.048***
(0.002) (0.003) (0.003) (0.004)
Cash Ratio −0.000*** −0.000 −0.000 0.001
(0.000) (0.000) (0.000) (0.001)
Securities Ratio −0.000 0.000 0.000 0.000
(0.000) (0.000) (0.000) (0.001)
Book Equity −0.000 0.000 0.001 −0.000
(0.000) (0.000) (0.001) (0.001)
Wholesale Funding 0.004*** 0.004*** 0.004*** 0.008***
(0.001) (0.002) (0.002) (0.003)
FHLB Ratio 0.001* 0.001** 0.002** 0.002*
(0.001) (0.001) (0.001) (0.001)
ROA −0.005** −0.004 −0.001 0.003
(0.002) (0.003) (0.004) (0.007)
Num.Obs. 4678 4678 4678 4678
R2 0.022 0.042 0.038 0.069
* p < 0.1, ** p < 0.05, *** p < 0.01
DV: 1 if bank borrowed from DW during specified period.
Specification tests Run Risk = I(mtm_total * uninsured_lev) across all time windows.
Mar 10 = day of SVB closure | Mar 13 = BTFP launch | Acute = Mar 13 - May 1
# ============================================================================
# TABLE: DW TEMPORAL ANALYSIS
# Specification: Base + Run Risk 4 (Insolvent/Illiquid) + Controls
# Comparing coefficients across different time windows
# ============================================================================

# 1. Define Controls (Standardized with Tables 4 & 5)
CONTROLS <- "ln_assets + cash_ratio + securities_ratio + loan_ratio + book_equity_ratio + wholesale + fhlb_ratio + roa"

# 2. Define Base Variables
BASE_VARS <- "mtm_total + uninsured_lev"

# 3. Define the Main Explanatory Variable of Interest
# (Testing the "Insolvent & Illiquid" hypothesis across time)
MAIN_VAR <- "run_risk_4"

# 4. Helper function to construct formulas dynamically
# Structure: DV ~ Base + RunRisk4 + Controls
get_dw_fml <- function(dv_name) {
  as.formula(paste(dv_name, "~", BASE_VARS, "+", MAIN_VAR, "+", CONTROLS))
}

# Model 1: DW Mar 10 only (pre-BTFP announcement)
m_dw_mar10 <- feols(get_dw_fml("dw_mar10"), 
                    data = df, vcov = "hetero")

# Model 2: DW Mar 10-13 (The panic weekend)
m_dw_mar10_13 <- feols(get_dw_fml("dw_mar10_13"), 
                       data = df, vcov = "hetero")

# Model 3: DW Mar 9-14 (Extended window)
m_dw_mar9_14 <- feols(get_dw_fml("dw_mar9_14"), 
                      data = df, vcov = "hetero")

# Model 4: DW Acute Period (Full Crisis)
m_dw_acute <- feols(get_dw_fml("dw_acute"), 
                    data = df, vcov = "hetero")

models_dw_temp <- list(
  "Mar 10"      = m_dw_mar10,
  "Mar 10-13"   = m_dw_mar10_13,
  "Mar 9-14"    = m_dw_mar9_14,
  "Acute"       = m_dw_acute
)

modelsummary(
  models_dw_temp,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 7: Discount Window - Temporal Analysis",
  notes = list(
    "DV: 1 if bank borrowed from DW during specified period.",
    "Specification tests Run Risk 4 (Insolvent & Illiquid) across all time windows.",
    "Mar 10 = day of SVB closure | Mar 13 = BTFP launch | Acute = Mar 13 - May 1"
  ),
  output = "kableExtra"
) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), font_size = 9) %>%
  add_header_above(c(" " = 1, "DW Borrowing by Period" = 4))
Table 7: Discount Window - Temporal Analysis
DW Borrowing by Period
&nbsp;Mar 10 &nbsp;Mar 10-13 &nbsp;Mar 9-14 Acute
MTM Loss (Total) −0.001 −0.002* −0.001 0.002
(0.001) (0.001) (0.001) (0.002)
Uninsured Leverage 0.000 0.000 0.001* 0.000
(0.000) (0.000) (0.000) (0.000)
Loan Ratio −0.000 −0.000 0.000 −0.000
(0.000) (0.000) (0.000) (0.000)
Run Risk 4: Insolvent & Illiquid 0.002 0.007 0.009 0.025*
(0.005) (0.007) (0.008) (0.013)
Log(Assets) 0.008*** 0.017*** 0.018*** 0.048***
(0.002) (0.003) (0.003) (0.004)
Cash Ratio −0.000*** −0.000 −0.000 0.000
(0.000) (0.000) (0.000) (0.001)
Securities Ratio −0.000 0.000 0.000 0.000
(0.000) (0.000) (0.000) (0.001)
Book Equity −0.000 0.000 0.001 −0.000
(0.000) (0.000) (0.001) (0.001)
Wholesale Funding 0.004*** 0.004*** 0.004*** 0.008***
(0.001) (0.002) (0.002) (0.003)
FHLB Ratio 0.001* 0.001** 0.002** 0.002*
(0.001) (0.001) (0.001) (0.001)
ROA −0.005** −0.003 −0.001 0.004
(0.002) (0.003) (0.004) (0.007)
Num.Obs. 4678 4678 4678 4678
R2 0.022 0.042 0.037 0.068
* p < 0.1, ** p < 0.05, *** p < 0.01
DV: 1 if bank borrowed from DW during specified period.
Specification tests Run Risk 4 (Insolvent & Illiquid) across all time windows.
Mar 10 = day of SVB closure | Mar 13 = BTFP launch | Acute = Mar 13 - May 1

3.5 4.4 Temporal Models: BTFP

# ============================================================================
# TABLE: BTFP TEMPORAL ANALYSIS
# Specification: Base + Interaction + Controls
# Comparing "Interaction (MTM * Uninsured)" usage across program phases
# ============================================================================

# 1. Define Controls
CONTROLS <- "ln_assets + cash_ratio + securities_ratio + loan_ratio + book_equity_ratio + wholesale + fhlb_ratio + roa"

# 2. Define Base Variables
BASE_VARS <- "mtm_total + uninsured_lev"

# 3. Define Main Explanatory Variable (Fixed Syntax)
MAIN_VAR <- "I(mtm_total * uninsured_lev)"

# 4. Helper function to construct formulas
# Structure: DV ~ Base + Interaction + Controls
get_btfp_temp_fml <- function(dv_name) {
  as.formula(paste(dv_name, "~", BASE_VARS, "+", MAIN_VAR, "+", CONTROLS))
}

# Model 1: Acute Phase (Mar 13 - May 1)
m_btfp_acute <- feols(get_btfp_temp_fml("btfp_acute"), 
                      data = df, vcov = "hetero")

# Model 2: Post-Acute Phase (May 2 - Oct 31)
m_btfp_post <- feols(get_btfp_temp_fml("btfp_post"), 
                     data = df, vcov = "hetero")

# Model 3: Arbitrage Phase (Nov 1 - Jan 24)
m_btfp_arb <- feols(get_btfp_temp_fml("btfp_arb"), 
                    data = df, vcov = "hetero")

# Model 4: Wind-down Phase (Jan 25 - Mar 11)
m_btfp_wind <- feols(get_btfp_temp_fml("btfp_wind"), 
                     data = df, vcov = "hetero")

models_btfp_temp <- list(
  "Acute"      = m_btfp_acute,
  "Post-Acute" = m_btfp_post,
  "Arbitrage"  = m_btfp_arb,
  "Wind-down"  = m_btfp_wind
)

modelsummary(
  models_btfp_temp,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 8: BTFP - Temporal Analysis (First Entry)",
  notes = list(
    "DV: 1 if bank's first BTFP borrowing occurred in the specified period.",
    "Specification tests the interaction of MTM losses and Uninsured Leverage across all phases.",
    "Acute: Mar 13 - May 1 | Post-Acute: May 2 - Oct 31 | Arbitrage: Nov 1 - Jan 24 | Wind-down: Jan 25 - Mar 11"
  ),
  output = "kableExtra"
) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), font_size = 9) %>%
  add_header_above(c(" " = 1, "BTFP First Entry by Period" = 4))
Table 8: BTFP - Temporal Analysis (First Entry)
BTFP First Entry by Period
Acute Post-Acute Arbitrage Wind-down
MTM Loss (Total) −0.005 −0.005 −0.001 −0.000
(0.003) (0.004) (0.004) (0.003)
Uninsured Leverage −0.002** −0.002** −0.002** 0.000
(0.001) (0.001) (0.001) (0.001)
Loan Ratio −0.000 0.001 0.000 −0.000
(0.000) (0.001) (0.001) (0.000)
MTM × Uninsured 0.001*** 0.001*** 0.001*** 0.000
(0.000) (0.000) (0.000) (0.000)
Log(Assets) 0.033*** 0.027*** 0.044*** 0.003
(0.004) (0.005) (0.005) (0.003)
Cash Ratio −0.001** −0.002*** −0.003*** −0.001*
(0.001) (0.001) (0.001) (0.000)
Securities Ratio 0.001* 0.003*** 0.002*** 0.001**
(0.000) (0.001) (0.001) (0.000)
Book Equity −0.002*** −0.004*** −0.002 −0.002**
(0.001) (0.001) (0.001) (0.001)
Wholesale Funding 0.012*** 0.006* 0.003 0.003
(0.003) (0.003) (0.003) (0.002)
FHLB Ratio 0.007*** 0.002 0.005*** 0.002*
(0.001) (0.002) (0.002) (0.001)
ROA −0.001 −0.007 −0.015* −0.007
(0.007) (0.009) (0.009) (0.005)
Num.Obs. 4678 4678 4678 4678
R2 0.074 0.064 0.082 0.017
* p < 0.1, ** p < 0.05, *** p < 0.01
DV: 1 if bank’s first BTFP borrowing occurred in the specified period.
Specification tests the interaction of MTM losses and Uninsured Leverage across all phases.
Acute: Mar 13 - May 1 | Post-Acute: May 2 - Oct 31 | Arbitrage: Nov 1 - Jan 24 | Wind-down: Jan 25 - Mar 11
# ============================================================================
# TABLE: BTFP TEMPORAL ANALYSIS
# Specification: Base + Run Risk 4 (Insolvent & Illiquid) + Controls
# Comparing "Insolvent & Illiquid" usage across program phases
# ============================================================================

# 1. Define Controls
CONTROLS <- "ln_assets + cash_ratio + securities_ratio + loan_ratio + book_equity_ratio + wholesale + fhlb_ratio + roa"

# 2. Define Base Variables
BASE_VARS <- "mtm_total + uninsured_lev"

# 3. Define Main Explanatory Variable (Consistent across all columns)
MAIN_VAR <- "run_risk_4"

# 4. Helper function to construct formulas
# Structure: DV ~ Base + RunRisk4 + Controls
get_btfp_temp_fml <- function(dv_name) {
  as.formula(paste(dv_name, "~", BASE_VARS, "+", MAIN_VAR, "+", CONTROLS))
}

# Model 1: Acute Phase (Mar 13 - May 1)
m_btfp_acute <- feols(get_btfp_temp_fml("btfp_acute"), 
                      data = df, vcov = "hetero")

# Model 2: Post-Acute Phase (May 2 - Oct 31)
m_btfp_post <- feols(get_btfp_temp_fml("btfp_post"), 
                     data = df, vcov = "hetero")

# Model 3: Arbitrage Phase (Nov 1 - Jan 24)
m_btfp_arb <- feols(get_btfp_temp_fml("btfp_arb"), 
                    data = df, vcov = "hetero")

# Model 4: Wind-down Phase (Jan 25 - Mar 11)
m_btfp_wind <- feols(get_btfp_temp_fml("btfp_wind"), 
                     data = df, vcov = "hetero")

models_btfp_temp <- list(
  "Acute"      = m_btfp_acute,
  "Post-Acute" = m_btfp_post,
  "Arbitrage"  = m_btfp_arb,
  "Wind-down"  = m_btfp_wind
)

modelsummary(
  models_btfp_temp,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 9: BTFP - Temporal Analysis (First Entry)",
  notes = list(
    "DV: 1 if bank's first BTFP borrowing occurred in the specified period.",
    "Specification tests Run Risk 4 (Insolvent & Illiquid) across all phases.",
    "Acute: Mar 13 - May 1 | Post-Acute: May 2 - Oct 31 | Arbitrage: Nov 1 - Jan 24 | Wind-down: Jan 25 - Mar 11"
  ),
  output = "kableExtra"
) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), font_size = 9) %>%
  add_header_above(c(" " = 1, "BTFP First Entry by Period" = 4))
Table 9: BTFP - Temporal Analysis (First Entry)
BTFP First Entry by Period
Acute Post-Acute Arbitrage Wind-down
MTM Loss (Total) 0.003 0.005 0.010*** 0.001
(0.002) (0.003) (0.003) (0.002)
Uninsured Leverage 0.001 0.001 0.001* 0.001*
(0.000) (0.001) (0.001) (0.000)
Loan Ratio −0.001 0.001 0.000 −0.000
(0.000) (0.001) (0.001) (0.000)
Run Risk 4: Insolvent & Illiquid 0.045*** 0.039** 0.023 0.009
(0.015) (0.017) (0.017) (0.011)
Log(Assets) 0.034*** 0.027*** 0.044*** 0.004
(0.004) (0.005) (0.005) (0.003)
Cash Ratio −0.002** −0.003*** −0.003*** −0.001*
(0.001) (0.001) (0.001) (0.000)
Securities Ratio 0.001* 0.003*** 0.002*** 0.001**
(0.000) (0.001) (0.001) (0.000)
Book Equity −0.002*** −0.004*** −0.002* −0.002**
(0.001) (0.001) (0.001) (0.001)
Wholesale Funding 0.012*** 0.006* 0.003 0.003
(0.003) (0.003) (0.003) (0.002)
FHLB Ratio 0.007*** 0.002 0.005*** 0.002*
(0.001) (0.002) (0.002) (0.001)
ROA 0.000 −0.005 −0.013 −0.007
(0.007) (0.009) (0.009) (0.005)
Num.Obs. 4678 4678 4678 4678
R2 0.074 0.063 0.080 0.017
* p < 0.1, ** p < 0.05, *** p < 0.01
DV: 1 if bank’s first BTFP borrowing occurred in the specified period.
Specification tests Run Risk 4 (Insolvent & Illiquid) across all phases.
Acute: Mar 13 - May 1 | Post-Acute: May 2 - Oct 31 | Arbitrage: Nov 1 - Jan 24 | Wind-down: Jan 25 - Mar 11

3.6 4.5 Alternative Specifications: Using MTM × Uninsured Only (No Run Risk Dummies)

# ============================================================================
# TABLE: INTERACTION MODELS ONLY (FOR COMPARISON)
# Tests the "Panic Signature" (MTM * Uninsured) across all facilities
# ============================================================================

# 1. Define Standard Controls (includes fhlb_ratio)
CONTROLS <- "ln_assets + cash_ratio + securities_ratio + loan_ratio + book_equity_ratio + wholesale + fhlb_ratio + roa"

# 2. Define FHLB Controls (Standard minus fhlb_ratio to avoid circularity)
CONTROLS_FHLB <- "ln_assets + cash_ratio + securities_ratio + loan_ratio + book_equity_ratio + wholesale + roa"

# 3. Define Base Variables + Interaction
BASE_VARS <- "mtm_total + uninsured_lev"
INTERACTION <- "I(mtm_total * uninsured_lev)"

# 4. Helper function to construct formulas
# Logic: If dv_name contains "fhlb", use restricted controls; otherwise use standard.
get_int_fml <- function(dv_name) {
  if (grepl("fhlb", dv_name)) {
    as.formula(paste(dv_name, "~", BASE_VARS, "+", INTERACTION, "+", CONTROLS_FHLB))
  } else {
    as.formula(paste(dv_name, "~", BASE_VARS, "+", INTERACTION, "+", CONTROLS))
  }
}

# Model 1: All Borrowers (Acute)
m_int_all <- feols(get_int_fml("all_borrower_acute"), 
                   data = df, vcov = "hetero")

# Model 2: BTFP Only
m_int_btfp <- feols(get_int_fml("btfp_acute"), 
                    data = df, vcov = "hetero")

# Model 3: Discount Window Only
m_int_dw <- feols(get_int_fml("dw_acute"), 
                  data = df, vcov = "hetero")

# Model 4: FHLB Only (Uses restricted controls)
m_int_fhlb <- feols(get_int_fml("fhlb_borrower_10pct"), 
                    data = df, vcov = "hetero")

models_int <- list(
  "All Crisis" = m_int_all,
  "BTFP"       = m_int_btfp,
  "DW"         = m_int_dw,
  "FHLB"       = m_int_fhlb
)

modelsummary(
  models_int,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 8: Crisis Borrowing - Interaction Model by Facility",
  notes = list(
    "All models cover the Acute Period (Mar 13 - May 1).",
    "Interaction (MTM * Uninsured) tests the 'Panic Signature' (Chen-Goldstein).",
    "Controls include wholesale funding, liquidity ratios, and size.",
    "FHLB model excludes FHLB ratio from controls to avoid mechanical correlation."
  ),
  output = "kableExtra"
) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), font_size = 9) %>%
  add_header_above(c(" " = 1, "Acute Period Borrowing" = 4))
Table 8: Crisis Borrowing - Interaction Model by Facility
Acute Period Borrowing
All Crisis BTFP DW FHLB
MTM Loss (Total) −0.000 −0.005 −0.006* 0.006**
(0.005) (0.003) (0.003) (0.003)
Uninsured Leverage −0.001 −0.002** −0.002* 0.001**
(0.001) (0.001) (0.001) (0.001)
Loan Ratio 0.001 −0.000 −0.000 0.001***
(0.001) (0.000) (0.000) (0.000)
MTM × Uninsured 0.001*** 0.001*** 0.000*** −0.000
(0.000) (0.000) (0.000) (0.000)
Log(Assets) 0.069*** 0.033*** 0.048*** 0.010***
(0.006) (0.004) (0.004) (0.003)
Cash Ratio −0.002*** −0.001** 0.001 −0.002***
(0.001) (0.001) (0.001) (0.001)
Securities Ratio 0.001 0.001* 0.000 −0.000
(0.001) (0.000) (0.001) (0.000)
Book Equity −0.001 −0.002*** −0.000 0.003***
(0.001) (0.001) (0.001) (0.001)
Wholesale Funding 0.015*** 0.012*** 0.008*** −0.000
(0.004) (0.003) (0.003) (0.002)
FHLB Ratio 0.008*** 0.007*** 0.002*
(0.002) (0.001) (0.001)
ROA −0.002 −0.001 0.003 −0.014**
(0.010) (0.007) (0.007) (0.006)
Num.Obs. 4678 4678 4678 4678
R2 0.108 0.074 0.069 0.026
* p < 0.1, ** p < 0.05, *** p < 0.01
All models cover the Acute Period (Mar 13 - May 1).
Interaction (MTM * Uninsured) tests the ‘Panic Signature’ (Chen-Goldstein).
Controls include wholesale funding, liquidity ratios, and size.
FHLB model excludes FHLB ratio from controls to avoid mechanical correlation.

4 Variable Definitions

# ============================================================================
# VARIABLE DEFINITIONS TABLE
# ============================================================================

var_defs <- tribble(
  ~Variable, ~Definition,
  # Dependent Variables
  "all_borrower_acute", "1 if bank borrowed from DW OR BTFP OR had abnormal FHLB increase (>10th percentile) during acute period",
  "btfp_acute", "1 if bank borrowed from BTFP during acute period (Mar 13 - May 1, 2023)",
  "dw_acute", "1 if bank borrowed from DW during acute period",
  "fhlb_borrower_10pct", "1 if bank had abnormal FHLB borrowing increase (above 10th percentile z-score)",
  "btfp_post", "1 if bank's first BTFP borrowing during post-acute (May 2 - Oct 31, 2023)",
  "btfp_arb", "1 if bank's first BTFP borrowing during arbitrage (Nov 1, 2023 - Jan 24, 2024)",
  "btfp_wind", "1 if bank's first BTFP borrowing during wind-down (Jan 25 - Mar 11, 2024)",
  # Run Risk Dummies
  "run_risk_1", "Solvent & Liquid: MTM loss < median AND uninsured leverage < median",
  "run_risk_2", "Solvent & Illiquid: MTM loss < median AND uninsured leverage > median",
  "run_risk_3", "Insolvent & Liquid: MTM loss > median AND uninsured leverage < median",
  "run_risk_4", "Insolvent & Illiquid: MTM loss > median AND uninsured leverage > median (HIGHEST RUN RISK)",
  # Key Explanatory
  "mtm_total", "Total MTM loss / Total Assets (%), winsorized 2.5/97.5",
  "uninsured_lev", "Uninsured deposits / Total Assets (%), winsorized",
  "loan_ratio", "Total Loans / Total Assets (%), structural illiquidity control",
  "MTM × Uninsured", "Interaction term: mtm_total × uninsured_lev",
  # Outflow Indicators
  "insured_outflow_10pct", "1 if bank had abnormal insured deposit outflow (above 10th percentile z-score)",
  "uninsured_outflow_10pct", "1 if bank had abnormal uninsured deposit outflow (above 10th percentile z-score)",
  # Controls
  "ln_assets", "Log(Total Assets in $000s)",
  "cash_ratio", "Cash / Total Assets (%)",
  "securities_ratio", "Securities / Total Assets (%)",
  "book_equity_ratio", "Book Equity / Total Assets (%)",
  "fhlb_ratio", "FHLB Advances / Total Assets (%)",
  "roa", "Return on Assets (%), annualized"
)

var_defs %>%
  kable(
    caption = "Table 9: Variable Definitions",
    format = "html"
  ) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE, font_size = 10) %>%
  pack_rows("Dependent Variables", 1, 7) %>%
  pack_rows("Run Risk Categories (2×2 Classification)", 8, 11) %>%
  pack_rows("Key Explanatory Variables", 12, 15) %>%
  pack_rows("Deposit Outflow Indicators", 16, 17) %>%
  pack_rows("Control Variables", 18, 23)
Table 9: Variable Definitions
Variable Definition
Dependent Variables
all_borrower_acute 1 if bank borrowed from DW OR BTFP OR had abnormal FHLB increase (>10th percentile) during acute period
btfp_acute 1 if bank borrowed from BTFP during acute period (Mar 13 - May 1, 2023)
dw_acute 1 if bank borrowed from DW during acute period
fhlb_borrower_10pct 1 if bank had abnormal FHLB borrowing increase (above 10th percentile z-score)
btfp_post 1 if bank’s first BTFP borrowing during post-acute (May 2 - Oct 31, 2023)
btfp_arb 1 if bank’s first BTFP borrowing during arbitrage (Nov 1, 2023 - Jan 24, 2024)
btfp_wind 1 if bank’s first BTFP borrowing during wind-down (Jan 25 - Mar 11, 2024)
Run Risk Categories (2×2 Classification)
run_risk_1 Solvent & Liquid: MTM loss < median AND uninsured leverage < median
run_risk_2 Solvent & Illiquid: MTM loss < median AND uninsured leverage > median
run_risk_3 Insolvent & Liquid: MTM loss > median AND uninsured leverage < median
run_risk_4 Insolvent & Illiquid: MTM loss > median AND uninsured leverage > median (HIGHEST RUN RISK)
Key Explanatory Variables
mtm_total Total MTM loss / Total Assets (%), winsorized 2.5/97.5
uninsured_lev Uninsured deposits / Total Assets (%), winsorized
loan_ratio Total Loans / Total Assets (%), structural illiquidity control
MTM × Uninsured Interaction term: mtm_total × uninsured_lev
Deposit Outflow Indicators
insured_outflow_10pct 1 if bank had abnormal insured deposit outflow (above 10th percentile z-score)
uninsured_outflow_10pct 1 if bank had abnormal uninsured deposit outflow (above 10th percentile z-score)
Control Variables
ln_assets Log(Total Assets in $000s)
cash_ratio Cash / Total Assets (%)
securities_ratio Securities / Total Assets (%)
book_equity_ratio Book Equity / Total Assets (%)
fhlb_ratio FHLB Advances / Total Assets (%)
roa Return on Assets (%), annualized

5 Summary

5.1 Key Findings

  1. Run Risk 4 (Insolvent & Illiquid) banks - those with both high MTM losses AND high uninsured deposits - had the highest rates of crisis borrowing

  2. MTM × Uninsured interaction is positive and significant for BTFP, supporting the Chen-Goldstein panic amplification hypothesis

  3. Temporal dynamics: The run risk effect is strongest during the acute crisis period

  4. Size patterns: Larger banks accessed BTFP at higher rates, while smaller banks relied more on FHLB


Analysis completed: January 14, 2026 at 12:42

sessionInfo()

R version 4.3.1 (2023-06-16 ucrt) Platform: x86_64-w64-mingw32/x64 (64-bit) Running under: Windows 11 x64 (build 26100)

Matrix products: default

locale: [1] LC_COLLATE=English_United States.utf8 LC_CTYPE=English_United States.utf8 LC_MONETARY=English_United States.utf8 [4] LC_NUMERIC=C 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] scales_1.3.0 patchwork_1.3.0 kableExtra_1.4.0 modelsummary_2.3.0 fixest_0.12.1 data.table_1.17.0 [7] lubridate_1.9.4 forcats_1.0.0 stringr_1.5.1 dplyr_1.1.4 purrr_1.0.2 readr_2.1.5
[13] tidyr_1.3.1 tibble_3.2.1 ggplot2_3.5.1 tidyverse_2.0.0

loaded via a namespace (and not attached): [1] gtable_0.3.6 bayestestR_0.17.0 xfun_0.54 bslib_0.5.1 insight_1.4.3
[6] lattice_0.21-8 tzdb_0.4.0 numDeriv_2016.8-1.1 vctrs_0.6.4 tools_4.3.1
[11] generics_0.1.3 datawizard_1.3.0 parallel_4.3.1 sandwich_3.1-1 fansi_1.0.5
[16] pkgconfig_2.0.3 checkmate_2.3.1 stringmagic_1.1.2 lifecycle_1.0.4 compiler_4.3.1
[21] farver_2.1.1 munsell_0.5.0 htmltools_0.5.7 sass_0.4.9 yaml_2.3.7
[26] Formula_1.2-5 pillar_1.9.0 crayon_1.5.2 jquerylib_0.1.4 cachem_1.0.8
[31] nlme_3.1-162 tidyselect_1.2.1 digest_0.6.33 performance_0.15.3 stringi_1.7.12
[36] fastmap_1.1.1 grid_4.3.1 colorspace_2.1-0 cli_3.6.5 magrittr_2.0.3
[41] utf8_1.2.4 withr_2.5.2 dreamerr_1.4.0 backports_1.4.1 bit64_4.0.5
[46] timechange_0.3.0 rmarkdown_2.29 bit_4.0.5 zoo_1.8-12 hms_1.1.3
[51] evaluate_0.23 knitr_1.50 parameters_0.28.3 viridisLite_0.4.2 rlang_1.1.6
[56] Rcpp_1.0.12 glue_1.8.0 xml2_1.3.6 svglite_2.1.3 rstudioapi_0.16.0
[61] vroom_1.6.5 jsonlite_1.8.7 R6_2.5.1 tables_0.9.31 systemfonts_1.0.6