1 Executive Summary

This analysis addresses the research questions on bank borrowing behavior across Federal Reserve emergency facilities (BTFP and Discount Window) during the March 2023 banking crisis.

1.1 Key Additions Based on Advisor Feedback

  1. Jiang et al. Insolvency Link: Comprehensive analysis linking insolvent banks (by adjusted equity and IDCR measures) to LOLR borrowing by period
  2. GSIB-Specific Analysis: Separate analysis for G-SIBs (with custodial bank identification)
  3. MTM Total Only: Extensive margin models now use total MTM loss (not split by eligible/non-eligible)
  4. Acute Period Deep Dive: Descriptive stats by subsamples for the acute phase
  5. Run Risk Dummy: Added to temporal specifications
  6. Insolvency Specs for DW: Complete insolvency analysis for both BTFP and DW (without book equity)
  7. FHLB Borrowing Analysis: Analysis of banks with FHLB increases, focused on $10-100B banks
# ============================================================================
# LOAD PACKAGES
# ============================================================================
pkgs <- c("tidyverse", "data.table", "lubridate", "fixest", "sandwich", 
          "lmtest", "broom", "margins", "pROC", "sampleSelection",
          "modelsummary", "kableExtra", "patchwork", "scales", 
          "viridis", "psych")

invisible(lapply(pkgs, library, character.only = TRUE))
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)
}

# Significance stars
add_stars <- function(pval) {
  case_when(pval < 0.01 ~ "***", pval < 0.05 ~ "**", pval < 0.10 ~ "*", TRUE ~ "")
}

# 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 palettes
COLORS <- list(
  btfp = "#2E86AB", dw = "#A23B72", both = "#F18F01", neither = "gray70",
  acute = "#FC9272", post = "#A1D99B", arb = "#9ECAE1", winddown = "#DADAEB"
)

facility_colors <- c(
  "BTFP" = "#2E86AB", "DW" = "#A23B72", "Discount Window" = "#A23B72",
  "Both" = "#F18F01", "Neither" = "gray70"
)
# ============================================================================
# OUTPUT DIRECTORY SETUP
# ============================================================================
OUTPUT_PATH <- "C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/final_result_04"

if (!dir.exists(OUTPUT_PATH)) dir.create(OUTPUT_PATH, recursive = TRUE)

TABLE_PATH <- file.path(OUTPUT_PATH, "tables")
FIG_PATH <- file.path(OUTPUT_PATH, "figures")

if (!dir.exists(TABLE_PATH)) dir.create(TABLE_PATH)
if (!dir.exists(FIG_PATH)) dir.create(FIG_PATH)

cat("Output directories set up:\n")

Output directories set up:

cat("  Tables:", TABLE_PATH, "\n")

Tables: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/final_result_04/tables

cat("  Figures:", FIG_PATH, "\n")

Figures: C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/03_documentation/final_result_04/figures

# ============================================================================
# CANONICAL PERIOD DEFINITIONS
# ============================================================================
periods <- tribble(
  ~period_num, ~period_name, ~start_date, ~end_date, ~description,
  0, "Pre-BTFP",   "2023-03-01", "2023-03-10", "DW only available",
  1, "Acute",      "2023-03-13", "2023-05-01", "Peak crisis phase",
  2, "Post-Acute", "2023-05-02", "2023-10-31", "Stabilization",
  3, "Arbitrage",  "2023-11-01", "2024-01-24", "BTFP rate < IORB",
  4, "Wind-down",  "2024-01-25", "2024-03-11", "BTFP closing announced"
) %>% 
  mutate(across(c(start_date, end_date), as.Date))

# Key dates
BTFP_LAUNCH <- as.Date("2023-03-12")
BTFP_CLOSE <- as.Date("2024-03-11")
DW_DATA_END <- as.Date("2023-12-31")
BASELINE_DATE <- "2022Q4"

periods %>%
  kable(col.names = c("Period", "Name", "Start", "End", "Description"),
        caption = "Table 0: Analysis Period Definitions") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Table 0: Analysis Period Definitions
Period Name Start End Description
0 Pre-BTFP 2023-03-01 2023-03-10 DW only available
1 Acute 2023-03-13 2023-05-01 Peak crisis phase
2 Post-Acute 2023-05-02 2023-10-31 Stabilization
3 Arbitrage 2023-11-01 2024-01-24 BTFP rate < IORB
4 Wind-down 2024-01-25 2024-03-11 BTFP closing announced
# ============================================================================
# LOAD DATA
# ============================================================================
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")

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), "| BTFP Loans:", nrow(btfp_loans_raw), "| DW Loans:", nrow(dw_loans_raw), "\n")

Call Report: 61002 | BTFP Loans: 6734 | DW Loans: 10008

# ============================================================================
# ASSIGN PERIODS TO LOANS
# ============================================================================
assign_period <- function(date, periods_df) {
  sapply(date, function(d) {
    if (is.na(d)) return(NA_integer_)
    match <- periods_df %>% filter(d >= start_date & d <= end_date) %>% pull(period_num)
    if (length(match) == 0) NA_integer_ else match[1]
  })
}

btfp_loans_raw <- btfp_loans_raw %>% mutate(period = assign_period(btfp_loan_date, periods))
dw_loans_raw <- dw_loans_raw %>% mutate(period = assign_period(dw_loan_date, periods))
# ============================================================================
# DEFINE EXCLUDED BANKS AND GSIB IDENTIFICATION
# ============================================================================

# Identify GSIBs at baseline (KEEP SEPARATE FOR GSIB ANALYSIS)
gsib_banks <- call_q %>%
  filter(quarter == BASELINE_DATE, gsib == 1) %>%
  pull(idrssd)

# Identify failed banks
failed_banks <- call_q %>%
  filter(quarter == BASELINE_DATE, failed_bank == 1) %>%
  pull(idrssd)

# Exclude failed + GSIBs for main analysis
excluded_banks <- call_q %>%
  filter(quarter == BASELINE_DATE, failed_bank == 1 | gsib == 1) %>%
  pull(idrssd)

cat("=== EXCLUSIONS ===\n")

=== EXCLUSIONS ===

cat("G-SIBs:", length(gsib_banks), "\n")

G-SIBs: 33

cat("Failed banks:", length(failed_banks), "\n")

Failed banks: 8

cat("Total excluded:", length(excluded_banks), "\n")

Total excluded: 41

# ============================================================================
# IDENTIFY CUSTODIAL BANKS AMONG GSIBs
# Note: Custodial banks (State Street, BNY Mellon) may not have typical 
# uninsured deposit profiles - flag for separate analysis
# ============================================================================

# These are known custodial banks - verify RSSDIDs in your data
custodial_banks <- call_q %>%
  filter(quarter == BASELINE_DATE, gsib == 1) %>%
  filter(grepl("STATE STREET|MELLON|NORTHERN TRUST", toupper(bank_name), ignore.case = TRUE)) %>%
  pull(idrssd)

cat("Custodial banks identified:", length(custodial_banks), "\n")

Custodial banks identified: 6

# Non-custodial GSIBs
gsib_non_custodial <- setdiff(gsib_banks, custodial_banks)
cat("Non-custodial GSIBs:", length(gsib_non_custodial), "\n")

Non-custodial GSIBs: 27

# Create filtered loan datasets
btfp_loans <- btfp_loans_raw %>% filter(!rssd_id %in% excluded_banks)
dw_loans <- dw_loans_raw %>% filter(!rssd_id %in% excluded_banks)

# GSIB-only loan datasets
btfp_loans_gsib <- btfp_loans_raw %>% filter(rssd_id %in% gsib_banks)
dw_loans_gsib <- dw_loans_raw %>% filter(rssd_id %in% gsib_banks)
# ============================================================================
# AGGREGATE BORROWER-LEVEL DATA
# ============================================================================

# Helper function for aggregation
aggregate_btfp <- function(loans_df) {
  loans_df %>%
    filter(!is.na(period), period >= 1) %>%
    group_by(rssd_id) %>%
    summarise(
      btfp = 1L,
      btfp_amount = sum(btfp_loan_amount, na.rm = TRUE),
      btfp_first_date = min(btfp_loan_date),
      btfp_first_period = first(period[btfp_loan_date == min(btfp_loan_date)]),
      btfp_n_loans = n(),
      btfp_collateral = sum(btfp_total_collateral, na.rm = TRUE),
      .groups = "drop"
    ) %>% rename(idrssd = rssd_id)
}

aggregate_dw_post <- function(loans_df) {
  loans_df %>%
    filter(!is.na(period), period >= 1) %>%
    group_by(rssd_id) %>%
    summarise(
      dw = 1L,
      dw_amount = sum(dw_loan_amount, na.rm = TRUE),
      dw_first_date = min(dw_loan_date),
      dw_first_period = first(period[dw_loan_date == min(dw_loan_date)]),
      dw_omo_coll = sum(dw_omo_eligible, na.rm = TRUE),
      dw_non_omo_coll = sum(dw_non_omo_eligible, na.rm = TRUE),
      .groups = "drop"
    ) %>% rename(idrssd = rssd_id)
}

aggregate_dw_pre <- function(loans_df) {
  loans_df %>%
    filter(!is.na(period), period == 0) %>%
    group_by(rssd_id) %>%
    summarise(
      dw_pre = 1L,
      dw_pre_amount = sum(dw_loan_amount, na.rm = TRUE),
      dw_pre_first_date = min(dw_loan_date),
      .groups = "drop"
    ) %>% rename(idrssd = rssd_id)
}

# Period-specific aggregation for temporal analysis
aggregate_btfp_by_period <- function(loans_df, target_period) {
  loans_df %>%
    filter(!is.na(period), period == target_period) %>%
    group_by(rssd_id) %>%
    summarise(
      btfp_period = 1L,
      btfp_period_amount = sum(btfp_loan_amount, na.rm = TRUE),
      .groups = "drop"
    ) %>% rename(idrssd = rssd_id)
}

aggregate_dw_by_period <- function(loans_df, target_period) {
  loans_df %>%
    filter(!is.na(period), period == target_period) %>%
    group_by(rssd_id) %>%
    summarise(
      dw_period = 1L,
      dw_period_amount = sum(dw_loan_amount, na.rm = TRUE),
      .groups = "drop"
    ) %>% rename(idrssd = rssd_id)
}

# Create aggregations
btfp_agg_all <- aggregate_btfp(btfp_loans_raw)
btfp_agg <- aggregate_btfp(btfp_loans)
btfp_agg_gsib <- aggregate_btfp(btfp_loans_gsib)

dw_post_agg_all <- aggregate_dw_post(dw_loans_raw)
dw_post_agg <- aggregate_dw_post(dw_loans)
dw_post_agg_gsib <- aggregate_dw_post(dw_loans_gsib)

dw_pre_agg_all <- aggregate_dw_pre(dw_loans_raw)
dw_pre_agg <- aggregate_dw_pre(dw_loans)
dw_pre_agg_gsib <- aggregate_dw_pre(dw_loans_gsib)

# Acute period borrowers
btfp_acute_agg <- aggregate_btfp_by_period(btfp_loans, 1)
dw_acute_agg <- aggregate_dw_by_period(dw_loans, 1)

cat("\n=== AGGREGATED BORROWERS ===\n")

=== AGGREGATED BORROWERS ===

cat("Main sample - BTFP:", nrow(btfp_agg), "| DW post:", nrow(dw_post_agg), "| DW pre:", nrow(dw_pre_agg), "\n")

Main sample - BTFP: 1316 | DW post: 1389 | DW pre: 106

cat("GSIB sample - BTFP:", nrow(btfp_agg_gsib), "| DW post:", nrow(dw_post_agg_gsib), "| DW pre:", nrow(dw_pre_agg_gsib), "\n")

GSIB sample - BTFP: 10 | DW post: 17 | DW pre: 0

# ============================================================================
# CONSTRUCT ANALYSIS DATASETS
# ============================================================================

create_analysis_df <- function(baseline_data, btfp_agg, dw_post_agg, dw_pre_agg) {
  
  df_raw <- baseline_data %>%
    left_join(btfp_agg, by = "idrssd") %>%
    left_join(dw_post_agg, by = "idrssd") %>%
    left_join(dw_pre_agg, by = "idrssd") %>%
    mutate(
      btfp = replace_na(btfp, 0L),
      dw = replace_na(dw, 0L),
      dw_pre = replace_na(dw_pre, 0L),
      
      any_fed = as.integer(btfp == 1 | dw == 1),
      both = as.integer(btfp == 1 & dw == 1),
      btfp_only = as.integer(btfp == 1 & dw == 0),
      dw_only = as.integer(btfp == 0 & dw == 1),
      
      facility_choice = factor(
        case_when(both == 1 ~ "Both", btfp_only == 1 ~ "BTFP_Only",
                  dw_only == 1 ~ "DW_Only", TRUE ~ "Neither"),
        levels = c("Neither", "BTFP_Only", "DW_Only", "Both")
      ),
      
      # Period-specific entry indicators
      btfp_acute = as.integer(!is.na(btfp_first_period) & btfp_first_period == 1),
      btfp_post = as.integer(!is.na(btfp_first_period) & btfp_first_period == 2),
      btfp_arb = as.integer(!is.na(btfp_first_period) & btfp_first_period == 3),
      btfp_winddown = as.integer(!is.na(btfp_first_period) & btfp_first_period == 4),
      
      dw_acute = as.integer(!is.na(dw_first_period) & dw_first_period == 1),
      dw_post = as.integer(!is.na(dw_first_period) & dw_first_period == 2),
      dw_arb = as.integer(!is.na(dw_first_period) & dw_first_period == 3),
      
      # RAW KEY VARIABLES
      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,
      
      # Jiang et al. insolvency measures
      adjusted_equity_raw = book_equity_to_total_asset - mtm_loss_to_total_asset,
      mv_adjustment_raw = if_else(mv_asset == 0 | is.na(mv_asset), NA_real_, (total_asset / mv_asset) - 1),
      idcr_1_raw = safe_div(mv_asset - 0.5 * uninsured_deposit - insured_deposit, insured_deposit),
      idcr_2_raw = safe_div(mv_asset - 1.0 * uninsured_deposit - insured_deposit, insured_deposit),
      insolvency_1_raw = safe_div((total_asset - total_liability) - 0.5 * uninsured_deposit * mv_adjustment_raw, total_asset),
      insolvency_2_raw = safe_div((total_asset - total_liability) - 1.0 * uninsured_deposit * mv_adjustment_raw, total_asset),
      
      # 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,
      
      wholesale_raw = safe_div(
        fed_fund_purchase + repo + replace_na(other_borrowed_less_than_1yr, 0),
        total_liability, 0
      ) * 100,
      
      # Size categorization
      size_cat = factor(size_bin, levels = c("small", "large")),
      prior_dw = dw_pre,
      
      # State for clustering
      state = if("state" %in% names(.)) state else NA_character_
    )
  
  # WINSORIZE
  df <- df_raw %>%
    mutate(
      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),
      
      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),
      
      adjusted_equity = winsorize(adjusted_equity_raw),
      mv_adjustment = winsorize(mv_adjustment_raw),
      idcr_1 = winsorize(idcr_1_raw),
      idcr_2 = winsorize(idcr_2_raw),
      insolvency_1 = winsorize(insolvency_1_raw),
      insolvency_2 = winsorize(insolvency_2_raw),
      
      # Insolvency indicators
      mtm_insolvent = as.integer(adjusted_equity < 0),
      insolvent_idcr_s50 = as.integer(idcr_1 < 0),
      insolvent_idcr_s100 = as.integer(idcr_2 < 0),
      insolvent_cap_s50 = as.integer(insolvency_1 < 0),
      insolvent_cap_s100 = as.integer(insolvency_2 < 0),
      
      # Run risk measures (continuous and dummy)
      run_risk = uninsured_share * mtm_total,
      high_uninsured = as.integer(uninsured_lev > median(uninsured_lev, na.rm = TRUE)),
      high_mtm = as.integer(mtm_total > median(mtm_total, na.rm = TRUE))
    ) %>%
    # Calculate run risk dummy based on sample medians
    mutate(
      median_uninsured = median(uninsured_share, na.rm = TRUE),
      median_mtm = median(mtm_total, na.rm = TRUE),
      run_risk_dummy = as.integer(uninsured_share > median_uninsured & mtm_total > median_mtm)
    )
  
  # Intensive margin (BTFP only)
  df <- df %>%
    mutate(
      btfp_pct = ifelse(btfp == 1, 100 * btfp_amount / (total_asset * 1000), NA_real_),
      dw_pct = ifelse(dw == 1, 100 * dw_amount / (total_asset * 1000), NA_real_),
      btfp_util = ifelse(btfp == 1 & omo_eligible > 0, btfp_amount / (omo_eligible * 1000), NA_real_),
      maxed_out = as.integer(!is.na(btfp_util) & btfp_util > 0.90),
      total_fed_pct = ifelse(any_fed == 1, 
                             100 * (replace_na(btfp_amount, 0) + replace_na(dw_amount, 0)) / (total_asset * 1000), 
                             NA_real_)
    )
  
  # Remove raw variables
  df <- df %>% select(-ends_with("_raw"), -median_uninsured, -median_mtm)
  
  return(df)
}

# Create datasets
baseline_all <- call_q %>% filter(quarter == BASELINE_DATE)
baseline_excl <- call_q %>% filter(quarter == BASELINE_DATE, !idrssd %in% excluded_banks)
baseline_gsib <- call_q %>% filter(quarter == BASELINE_DATE, idrssd %in% gsib_banks)
baseline_gsib_nc <- call_q %>% filter(quarter == BASELINE_DATE, idrssd %in% gsib_non_custodial)

# Main analysis samples
df_all <- create_analysis_df(baseline_all, btfp_agg_all, dw_post_agg_all, dw_pre_agg_all)
df <- create_analysis_df(baseline_excl, btfp_agg, dw_post_agg, dw_pre_agg)
df_gsib <- create_analysis_df(baseline_gsib, btfp_agg_gsib, dw_post_agg_gsib, dw_pre_agg_gsib)
df_gsib_nc <- create_analysis_df(baseline_gsib_nc, btfp_agg_gsib, dw_post_agg_gsib, dw_pre_agg_gsib)

cat("\n=== ANALYSIS DATASETS CREATED ===\n")

=== ANALYSIS DATASETS CREATED ===

cat("df (main sample, excl. failed & GSIB):", nrow(df), "\n")

df (main sample, excl. failed & GSIB): 4696

cat("df_gsib (GSIBs only):", nrow(df_gsib), "\n")

df_gsib (GSIBs only): 33

cat("df_gsib_nc (non-custodial GSIBs):", nrow(df_gsib_nc), "\n")

df_gsib_nc (non-custodial GSIBs): 27

cat("  - BTFP users:", sum(df$btfp), "\n")
  • BTFP users: 1305
cat("  - DW users:", sum(df$dw), "\n")
  • DW users: 1377
cat("  - Both:", sum(df$both), "\n")
  • Both: 551
# ============================================================================
# GLOBAL MODEL SETTINGS
# ============================================================================

# Standard controls WITH book equity (for non-insolvency specs)
CONTROLS <- "ln_assets + cash_ratio + securities_ratio + loan_to_deposit + book_equity_ratio + wholesale + fhlb_ratio + roa"

# Controls WITHOUT book equity (for insolvency specs - per advisor feedback)
CONTROLS_INSOLVENCY <- "ln_assets + cash_ratio + securities_ratio + loan_to_deposit + wholesale + fhlb_ratio + roa"

# Cluster at bank level
CLUSTER_VAR <- "idrssd"

# Coefficient labels
COEF_MAP <- c(
  # Insolvency measures
  "adjusted_equity" = "Adjusted Equity",
  "mtm_insolvent" = "MTM Insolvent",
  "idcr_1" = "IDCR (s=0.5)",
  "idcr_2" = "IDCR (s=1.0)",
  "insolvent_idcr_s50" = "Insolvent IDCR (s=0.5)",
  "insolvent_idcr_s100" = "Insolvent IDCR (s=1.0)",
  "insolvency_1" = "Insolvency Cap (s=0.5)",
  "insolvency_2" = "Insolvency Cap (s=1.0)",
  "insolvent_cap_s50" = "Insolvent Cap (s=0.5)",
  "insolvent_cap_s100" = "Insolvent Cap (s=1.0)",
  
  # MTM measures - USE TOTAL ONLY FOR EXTENSIVE MARGIN
  "mtm_total" = "MTM Loss (Total)",
  "mtm_btfp" = "MTM Loss (BTFP-Eligible)",
  "mtm_other" = "MTM Loss (Non-BTFP)",
  "uninsured_lev" = "Uninsured Leverage",
  "borrowing_subsidy" = "Borrowing Subsidy",
  "run_risk" = "Run Risk (MTM × % Uninsured)",
  "run_risk_dummy" = "Run Risk Dummy",
  "I(mtm_total * uninsured_lev)" = "MTM × Uninsured",
  "fhlb_change" = "FHLB Change (fwd Q)",
  "fhlb_borrower" = "FHLB Borrower",
  
  # Controls
  "prior_dw" = "Prior DW User",
  "omo_ratio" = "BTFP-Eligible Ratio",
  "non_omo_ratio" = "DW-Only Eligible Ratio",
  "maxed_out" = "Maxed Out (>90%)",
  "high_uninsured" = "High Uninsured",
  "high_mtm" = "High MTM",
  "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"
)

cat("=== MODEL SETTINGS ===\n")

=== MODEL SETTINGS ===

cat("Main sample size:", nrow(df), "\n")

Main sample size: 4696

cat("GSIB sample size:", nrow(df_gsib), "\n")

GSIB sample size: 33

2 SECTION 1: Jiang et al. Insolvency and LOLR Borrowing

2.1 1.1 Insolvency Counts and LOLR Usage by Period

# ============================================================================
# TABLE: INSOLVENT BANKS AND LOLR USAGE BY PERIOD
# Key question: How many of the ~400 insolvent banks used LOLR?
# ============================================================================

# Count insolvent banks by measure (full sample including GSIBs)
insolvency_counts <- df_all %>%
  summarise(
    N_Total = n(),
    `MTM Insolvent (Adj Eq < 0)` = sum(mtm_insolvent == 1, na.rm = TRUE),
    `IDCR Insolvent (s=0.5)` = sum(insolvent_idcr_s50 == 1, na.rm = TRUE),
    `IDCR Insolvent (s=1.0)` = sum(insolvent_idcr_s100 == 1, na.rm = TRUE),
    `Cap Insolvent (s=0.5)` = sum(insolvent_cap_s50 == 1, na.rm = TRUE),
    `Cap Insolvent (s=1.0)` = sum(insolvent_cap_s100 == 1, na.rm = TRUE)
  )

cat("=== INSOLVENCY COUNTS (ALL BANKS) ===\n")

=== INSOLVENCY COUNTS (ALL BANKS) ===

print(insolvency_counts)

3 A tibble: 1 × 6

N_Total MTM Insolvent (Adj Eq < 0) IDCR Insolvent (s=0.5) IDCR Insolvent (s=1.0) Cap Insolvent (s=0.5) 1 4737 866 205 1303 0 # ℹ 1 more variable: Cap Insolvent (s=1.0)

# LOLR usage among insolvent banks by measure and facility
compute_insolvency_lolr <- function(data, insolvency_var) {
  data %>%
    filter(!!sym(insolvency_var) == 1) %>%
    summarise(
      N_Insolvent = n(),
      N_DW_Pre = sum(dw_pre == 1, na.rm = TRUE),
      N_DW_Acute = sum(dw_acute == 1, na.rm = TRUE),
      N_DW_Post = sum(dw_post == 1, na.rm = TRUE),
      N_DW_Any = sum(dw == 1, na.rm = TRUE),
      N_BTFP_Acute = sum(btfp_acute == 1, na.rm = TRUE),
      N_BTFP_Post = sum(btfp_post == 1, na.rm = TRUE),
      N_BTFP_Arb = sum(btfp_arb == 1, na.rm = TRUE),
      N_BTFP_Any = sum(btfp == 1, na.rm = TRUE),
      N_Any_Fed = sum(any_fed == 1, na.rm = TRUE),
      Pct_DW_Pre = N_DW_Pre / N_Insolvent * 100,
      Pct_DW_Acute = N_DW_Acute / N_Insolvent * 100,
      Pct_BTFP_Acute = N_BTFP_Acute / N_Insolvent * 100,
      Pct_Any = N_Any_Fed / N_Insolvent * 100
    ) %>%
    mutate(Measure = insolvency_var, .before = 1)
}

insolvency_measures <- c("mtm_insolvent", "insolvent_idcr_s50", "insolvent_idcr_s100", 
                         "insolvent_cap_s50", "insolvent_cap_s100")

insolvency_lolr_table <- map_dfr(insolvency_measures, ~compute_insolvency_lolr(df_all, .x))

# Display table
insolvency_lolr_table %>%
  select(Measure, N_Insolvent, N_DW_Pre, Pct_DW_Pre, N_DW_Acute, Pct_DW_Acute, 
         N_BTFP_Acute, Pct_BTFP_Acute, N_Any_Fed, Pct_Any) %>%
  mutate(
    Measure = case_when(
      Measure == "mtm_insolvent" ~ "Adjusted Equity < 0",
      Measure == "insolvent_idcr_s50" ~ "IDCR < 0 (s=0.5)",
      Measure == "insolvent_idcr_s100" ~ "IDCR < 0 (s=1.0)",
      Measure == "insolvent_cap_s50" ~ "Cap Ratio < 0 (s=0.5)",
      Measure == "insolvent_cap_s100" ~ "Cap Ratio < 0 (s=1.0)"
    )
  ) %>%
  kable(caption = "Table: Insolvent Banks (Jiang et al. Measures) and LOLR Usage",
        digits = 1,
        col.names = c("Insolvency Measure", "N Insolvent", "N DW Pre", "% DW Pre",
                      "N DW Acute", "% DW Acute", "N BTFP Acute", "% BTFP Acute",
                      "N Any Fed", "% Any Fed")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  footnote(general = "Pre-BTFP: Mar 1-10, 2023. Acute: Mar 13 - May 1, 2023. Insolvent banks per Jiang et al. (2023) definitions.")
Table: Insolvent Banks (Jiang et al. Measures) and LOLR Usage
Insolvency Measure N Insolvent N DW Pre % DW Pre N DW Acute % DW Acute N BTFP Acute % BTFP Acute N Any Fed % Any Fed
Adjusted Equity < 0 866 26 3.0 82 9.5 137 15.8 473 54.6
IDCR < 0 (s=0.5) 205 3 1.5 26 12.7 16 7.8 98 47.8
IDCR < 0 (s=1.0) 1303 31 2.4 129 9.9 133 10.2 665 51.0
Cap Ratio < 0 (s=0.5) 0 0 0 0 0
Cap Ratio < 0 (s=1.0) 363 13 3.6 51 14.0 50 13.8 201 55.4
Note:
Pre-BTFP: Mar 1-10, 2023. Acute: Mar 13 - May 1, 2023. Insolvent banks per Jiang et al. (2023) definitions.
# Save table
write_csv(insolvency_lolr_table, file.path(TABLE_PATH, "insolvency_lolr_usage.csv"))

3.1 1.2 Pre-BTFP DW Usage: Did Insolvent Banks Use DW? (Stigma Test)

# ============================================================================
# PRE-BTFP PERIOD: INSOLVENT BANKS AND DW USAGE
# Key question: If insolvent banks did NOT use DW pre-BTFP, suggests stigma
# 
# IMPORTANT: Each insolvency measure is an ALTERNATIVE way to capture insolvency
# They should be in SEPARATE models, not combined
# ============================================================================

cat("=== PRE-BTFP DW USAGE BY INSOLVENCY STATUS ===\n\n")

=== PRE-BTFP DW USAGE BY INSOLVENCY STATUS ===

# ------------------------------------------------------------------------------
# PART 1: SUMMARY STATISTICS BY EACH INSOLVENCY MEASURE
# ------------------------------------------------------------------------------

# Function to compute pre-BTFP stats by insolvency status
compute_pre_btfp_stats <- function(data, insolvency_var, label) {
  data %>%
    group_by(!!sym(insolvency_var)) %>%
    summarise(
      N = n(),
      N_DW_Pre = sum(dw_pre == 1, na.rm = TRUE),
      Pct_DW_Pre = N_DW_Pre / N * 100,
      Mean_MTM = mean(mtm_total, na.rm = TRUE),
      Mean_Uninsured = mean(uninsured_lev, na.rm = TRUE),
      Mean_Adj_Equity = mean(adjusted_equity, na.rm = TRUE),
      .groups = "drop"
    ) %>%
    mutate(
      Measure = label,
      Status = ifelse(!!sym(insolvency_var) == 1, "Insolvent", "Solvent")
    ) %>%
    select(Measure, Status, N, N_DW_Pre, Pct_DW_Pre, Mean_MTM, Mean_Uninsured, Mean_Adj_Equity)
}

# Compute for each insolvency measure
pre_btfp_mtm <- compute_pre_btfp_stats(df_all, "mtm_insolvent", "MTM Insolvent (Adj Eq < 0)")
pre_btfp_idcr50 <- compute_pre_btfp_stats(df_all, "insolvent_idcr_s50", "IDCR Insolvent (s=0.5)")
pre_btfp_idcr100 <- compute_pre_btfp_stats(df_all, "insolvent_idcr_s100", "IDCR Insolvent (s=1.0)")
pre_btfp_cap50 <- compute_pre_btfp_stats(df_all, "insolvent_cap_s50", "Capital Insolvent (s=0.5)")
pre_btfp_cap100 <- compute_pre_btfp_stats(df_all, "insolvent_cap_s100", "Capital Insolvent (s=1.0)")

# Combine all
pre_btfp_summary_all <- bind_rows(
  pre_btfp_mtm,
  pre_btfp_idcr50,
  pre_btfp_idcr100,
  pre_btfp_cap50,
  pre_btfp_cap100
)

# Display table
pre_btfp_summary_all %>%
  kable(caption = "Pre-BTFP DW Usage by Insolvency Status (All Measures)",
        digits = 2,
        col.names = c("Insolvency Measure", "Status", "N Banks", "N Used DW", 
                      "% Used DW", "Mean MTM", "Mean Unins Lev", "Mean Adj Eq")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Pre-BTFP DW Usage by Insolvency Status (All Measures)
Insolvency Measure Status N Banks N Used DW % Used DW Mean MTM Mean Unins Lev Mean Adj Eq
MTM Insolvent (Adj Eq < 0) Solvent 3853 82 2.13 4.85 22.98 5.90
MTM Insolvent (Adj Eq < 0) Insolvent 866 26 3.00 7.80 23.86 -2.17
MTM Insolvent (Adj Eq < 0) 18 0 0.00 22.12
IDCR Insolvent (s=0.5) Solvent 4464 105 2.35 5.48 23.41 4.20
IDCR Insolvent (s=0.5) Insolvent 205 3 1.46 4.30 22.44 4.96
IDCR Insolvent (s=0.5) 68 0 0.00 1.68 7.38 22.24
IDCR Insolvent (s=1.0) Solvent 3366 77 2.29 5.42 21.90 4.83
IDCR Insolvent (s=1.0) Insolvent 1303 31 2.38 5.45 27.17 2.68
IDCR Insolvent (s=1.0) 68 0 0.00 1.68 7.38 22.24
Capital Insolvent (s=0.5) Solvent 4719 108 2.29 5.39 23.14 4.42
Capital Insolvent (s=0.5) 18 0 0.00 22.12
Capital Insolvent (s=1.0) Solvent 4356 95 2.18 5.43 22.03 4.52
Capital Insolvent (s=1.0) Insolvent 363 13 3.58 4.85 36.51 3.30
Capital Insolvent (s=1.0) 18 0 0.00 22.12
# ------------------------------------------------------------------------------
# PART 2: T-TESTS FOR EACH INSOLVENCY MEASURE
# ------------------------------------------------------------------------------

 
cat("\n--- T-tests: Pre-BTFP DW Usage by Insolvency Status ---\n\n")

— T-tests: Pre-BTFP DW Usage by Insolvency Status —

# A helper list to loop through tests safely
test_vars <- list(
  "MTM Insolvent" = "mtm_insolvent",
  "IDCR (s=0.5)"  = "insolvent_idcr_s50",
  "IDCR (s=1.0)"  = "insolvent_idcr_s100",
  "Cap (s=1.0)"   = "insolvent_cap_s100"
)

for (label in names(test_vars)) {
  var_name <- test_vars[[label]]
  # Ensure we have at least 2 groups and some insolvent banks to test
  if(length(unique(df_all[[var_name]])) > 1 && sum(df_all[[var_name]], na.rm = TRUE) > 0) {
    t_res <- t.test(dw_pre ~ get(var_name), data = df_all)
    cat(sprintf("%s: Solvent %.2f%% vs Insolvent %.2f%%, p=%.4f\n",
                label, t_res$estimate[1]*100, t_res$estimate[2]*100, t_res$p.value))
  }
}

MTM Insolvent: Solvent 2.13% vs Insolvent 3.00%, p=0.1623 IDCR (s=0.5): Solvent 2.35% vs Insolvent 1.46%, p=0.3085 IDCR (s=1.0): Solvent 2.29% vs Insolvent 2.38%, p=0.8532 Cap (s=1.0): Solvent 2.18% vs Insolvent 3.58%, p=0.1628

# ------------------------------------------------------------------------------
# PART 3: LPM MODELS - ALL 8 SPECIFICATIONS
# ------------------------------------------------------------------------------
cat("\n--- LPM: Predicting Pre-BTFP DW Usage (8 Specifications) ---\n")

— LPM: Predicting Pre-BTFP DW Usage (8 Specifications) —

# Define Formulas
f_pre_1 <- as.formula(paste("dw_pre ~ mtm_insolvent + uninsured_lev +", CONTROLS_INSOLVENCY))
f_pre_2 <- as.formula(paste("dw_pre ~ adjusted_equity + uninsured_lev +", CONTROLS_INSOLVENCY))
f_pre_3 <- as.formula(paste("dw_pre ~ insolvent_idcr_s50 + uninsured_lev +", CONTROLS_INSOLVENCY))
f_pre_4 <- as.formula(paste("dw_pre ~ insolvent_idcr_s100 + uninsured_lev +", CONTROLS_INSOLVENCY))
f_pre_5 <- as.formula(paste("dw_pre ~ idcr_2 + uninsured_lev +", CONTROLS_INSOLVENCY))
f_pre_6 <- as.formula(paste("dw_pre ~ insolvent_cap_s50 + uninsured_lev +", CONTROLS_INSOLVENCY))
f_pre_7 <- as.formula(paste("dw_pre ~ insolvent_cap_s100 + uninsured_lev +", CONTROLS_INSOLVENCY))
f_pre_8 <- as.formula(paste("dw_pre ~ insolvency_2 + uninsured_lev +", CONTROLS_INSOLVENCY)) # Fixed typo: 'insolveny' -> 'insolvency'

# Estimate All Models
m_pre_1 <- feols(f_pre_1, data = df_all, cluster = ~idrssd)
m_pre_2 <- feols(f_pre_2, data = df_all, cluster = ~idrssd)
m_pre_3 <- feols(f_pre_3, data = df_all, cluster = ~idrssd)
m_pre_4 <- feols(f_pre_4, data = df_all, cluster = ~idrssd)
m_pre_5 <- feols(f_pre_5, data = df_all, cluster = ~idrssd)
m_pre_6 <- feols(f_pre_6, data = df_all, cluster = ~idrssd)
m_pre_7 <- feols(f_pre_7, data = df_all, cluster = ~idrssd)
m_pre_8 <- feols(f_pre_8, data = df_all, cluster = ~idrssd)

# Create Consolidated List
pre_btfp_list <- list(
  "(1) MTM Insol" = m_pre_1,
  "(2) Adj Eq"    = m_pre_2,
  "(3) IDCR s.5"  = m_pre_3,
  "(4) IDCR s1.0" = m_pre_4,
  "(5) IDCR Cont" = m_pre_5,
  "(6) Cap s.5"   = m_pre_6,
  "(7) Cap s1.0"  = m_pre_7,
  "(8) Cap Cont"  = m_pre_8
)

# Generate and display table
table_pre_btfp <- modelsummary(
  pre_btfp_list,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table: Pre-BTFP DW Usage and Insolvency (Alternative Specifications)",
  notes = c("Bank-clustered SEs. Sample covers pre-BTFP period.",
            "Each column tests a different proxy for insolvency/mark-to-market stress."),
  output = "kableExtra"
) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>%
  scroll_box(width = "100%")

table_pre_btfp
Table: Pre-BTFP DW Usage and Insolvency (Alternative Specifications)
&nbsp;(1) MTM Insol &nbsp;(2) Adj Eq &nbsp;(3) IDCR s.5 &nbsp;(4) IDCR s1.0 &nbsp;(5) IDCR Cont &nbsp;(6) Cap s.5 &nbsp;(7) Cap s1.0 &nbsp;(8) Cap Cont
Adjusted Equity 0.000
(0.000)
MTM Insolvent 0.004
(0.007)
IDCR (s=1.0) 0.006
(0.013)
Insolvent IDCR (s=0.5) −0.012
(0.009)
Insolvent IDCR (s=1.0) 0.002
(0.005)
Insolvency Cap (s=1.0) 0.048
(0.047)
Insolvent Cap (s=1.0) 0.002
(0.011)
Uninsured Leverage 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
(0.000) (0.000) (0.000) (0.000) (0.000) (0.000) (0.000) (0.000)
Log(Assets) 0.015*** 0.015*** 0.016*** 0.016*** 0.015*** 0.015*** 0.015*** 0.015***
(0.002) (0.003) (0.003) (0.003) (0.003) (0.002) (0.002) (0.002)
Cash Ratio −0.001** −0.001*** −0.001** −0.001** −0.001** −0.001*** −0.001*** −0.001***
(0.000) (0.000) (0.000) (0.000) (0.000) (0.000) (0.000) (0.000)
Securities Ratio −0.000 −0.000 −0.000 −0.000 −0.000 −0.000 −0.000 −0.000
(0.000) (0.000) (0.000) (0.000) (0.000) (0.000) (0.000) (0.000)
Loan/Deposit −0.000* −0.000** −0.000 −0.000 −0.000 −0.000** −0.000** −0.000**
(0.000) (0.000) (0.000) (0.000) (0.000) (0.000) (0.000) (0.000)
Wholesale Funding 0.007*** 0.007*** 0.007*** 0.007*** 0.007*** 0.007*** 0.007*** 0.007***
(0.002) (0.002) (0.002) (0.002) (0.002) (0.002) (0.002) (0.002)
FHLB Ratio 0.001 0.001 0.001 0.001 0.001 0.001 0.001 0.001*
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
ROA −0.004 −0.004 −0.005 −0.005 −0.005 −0.004 −0.004 −0.005
(0.003) (0.003) (0.003) (0.003) (0.004) (0.003) (0.003) (0.003)
Num.Obs. 4719 4719 4669 4669 4669 4719 4719 4719
R2 0.033 0.033 0.033 0.033 0.033 0.033 0.033 0.033
* p < 0.1, ** p < 0.05, *** p < 0.01
Bank-clustered SEs. Sample covers pre-BTFP period.
Each column tests a different proxy for insolvency/mark-to-market stress.
# Save ALL 8 models to the HTML file
modelsummary(
  pre_btfp_list,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table: Pre-BTFP DW Usage and Insolvency (Full Set)",
  output = file.path(TABLE_PATH, "table_pre_btfp_dw_insolvency_full.html")
)

3.2 1.3 LPM: Does Insolvency Predict Facility Usage?

# ============================================================================
# LPM: INSOLVENCY PREDICTING BTFP/DW USAGE
# All Jiang et al. insolvency measures - EACH IN SEPARATE SPECIFICATION
# Binary measures: MTM Insol, IDCR s=0.5, IDCR s=1.0, Cap s=0.5, Cap s=1.0
# Continuous measures: Adjusted Equity, IDCR
# ============================================================================

# --- BTFP SPECIFICATIONS ---

# Binary measures
f_btfp_mtm <- as.formula(paste("btfp ~ mtm_insolvent + uninsured_lev +", CONTROLS_INSOLVENCY))
f_btfp_idcr50 <- as.formula(paste("btfp ~ insolvent_idcr_s50 + uninsured_lev +", CONTROLS_INSOLVENCY))
f_btfp_idcr100 <- as.formula(paste("btfp ~ insolvent_idcr_s100 + uninsured_lev +", CONTROLS_INSOLVENCY))
f_btfp_cap50 <- as.formula(paste("btfp ~ insolvent_cap_s50 + uninsured_lev +", CONTROLS_INSOLVENCY))
f_btfp_cap100 <- as.formula(paste("btfp ~ insolvent_cap_s100 + uninsured_lev +", CONTROLS_INSOLVENCY))

# Continuous measures
f_btfp_adj <- as.formula(paste("btfp ~ adjusted_equity + uninsured_lev +", CONTROLS_INSOLVENCY))
f_btfp_idcr_cont <- as.formula(paste("btfp ~ idcr_2 + uninsured_lev +", CONTROLS_INSOLVENCY))

# --- DW SPECIFICATIONS ---

# Binary measures
f_dw_mtm <- as.formula(paste("dw ~ mtm_insolvent + uninsured_lev +", CONTROLS_INSOLVENCY))
f_dw_idcr50 <- as.formula(paste("dw ~ insolvent_idcr_s50 + uninsured_lev +", CONTROLS_INSOLVENCY))
f_dw_idcr100 <- as.formula(paste("dw ~ insolvent_idcr_s100 + uninsured_lev +", CONTROLS_INSOLVENCY))
f_dw_cap50 <- as.formula(paste("dw ~ insolvent_cap_s50 + uninsured_lev +", CONTROLS_INSOLVENCY))
f_dw_cap100 <- as.formula(paste("dw ~ insolvent_cap_s100 + uninsured_lev +", CONTROLS_INSOLVENCY))

# Continuous measures
f_dw_adj <- as.formula(paste("dw ~ adjusted_equity + uninsured_lev +", CONTROLS_INSOLVENCY))
f_dw_idcr_cont <- as.formula(paste("dw ~ idcr_2 + uninsured_lev +", CONTROLS_INSOLVENCY))

# --- ESTIMATE ALL BTFP MODELS ---
m_btfp_mtm <- feols(f_btfp_mtm, data = df_all, vcov = ~idrssd)
m_btfp_adj <- feols(f_btfp_adj, data = df_all, vcov = ~idrssd)
m_btfp_idcr50 <- feols(f_btfp_idcr50, data = df_all, vcov = ~idrssd)
m_btfp_idcr100 <- feols(f_btfp_idcr100, data = df_all, vcov = ~idrssd)
m_btfp_idcr_cont <- feols(f_btfp_idcr_cont, data = df_all, vcov = ~idrssd)
m_btfp_cap50 <- feols(f_btfp_cap50, data = df_all, vcov = ~idrssd)
m_btfp_cap100 <- feols(f_btfp_cap100, data = df_all, vcov = ~idrssd)

# --- ESTIMATE ALL DW MODELS ---
m_dw_mtm <- feols(f_dw_mtm, data = df_all, vcov = ~idrssd)
m_dw_adj <- feols(f_dw_adj, data = df_all, vcov = ~idrssd)
m_dw_idcr50 <- feols(f_dw_idcr50, data = df_all, vcov = ~idrssd)
m_dw_idcr100 <- feols(f_dw_idcr100, data = df_all, vcov = ~idrssd)
m_dw_idcr_cont <- feols(f_dw_idcr_cont, data = df_all, vcov = ~idrssd)
m_dw_cap50 <- feols(f_dw_cap50, data = df_all, vcov = ~idrssd)
m_dw_cap100 <- feols(f_dw_cap100, data = df_all, vcov = ~idrssd)

# --- PANEL A: BTFP - All insolvency measures ---
table_insol_btfp <- modelsummary(
  list("(1) MTM Insol" = m_btfp_mtm, 
       "(2) Adj Equity" = m_btfp_adj,
       "(3) IDCR s=0.5" = m_btfp_idcr50,
       "(4) IDCR s=1.0" = m_btfp_idcr100,
       "(5) IDCR Cont" = m_btfp_idcr_cont,
       "(6) Cap s=0.5" = m_btfp_cap50,
       "(7) Cap s=1.0" = m_btfp_cap100),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table: Insolvency and BTFP Access (All Jiang et al. Measures)",
  notes = c("Bank-clustered SEs. Book equity excluded. Full sample.",
            "Each column uses a DIFFERENT insolvency measure (alternatives, not combined)."),
  output = "kableExtra"
) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>%
  scroll_box(width = "100%")

table_insol_btfp
Table: Insolvency and BTFP Access (All Jiang et al. Measures)
&nbsp;(1) MTM Insol &nbsp;(2) Adj Equity &nbsp;(3) IDCR s=0.5 &nbsp;(4) IDCR s=1.0 &nbsp;(5) IDCR Cont &nbsp;(6) Cap s=0.5 &nbsp;(7) Cap s=1.0
Adjusted Equity −0.009***
(0.001)
MTM Insolvent 0.096***
(0.019)
IDCR (s=1.0) −0.144***
(0.031)
Insolvent IDCR (s=0.5) −0.003
(0.031)
Insolvent IDCR (s=1.0) 0.042***
(0.015)
Insolvent Cap (s=1.0) −0.007
(0.026)
Uninsured Leverage 0.002*** 0.002*** 0.002*** 0.002*** 0.002*** 0.002*** 0.002***
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Log(Assets) 0.061*** 0.059*** 0.061*** 0.061*** 0.061*** 0.061*** 0.061***
(0.006) (0.006) (0.006) (0.006) (0.006) (0.006) (0.006)
Cash Ratio −0.005*** −0.004*** −0.007*** −0.006*** −0.005*** −0.006*** −0.006***
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Securities Ratio 0.003*** 0.003*** 0.004*** 0.004*** 0.004*** 0.003*** 0.003***
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Loan/Deposit −0.000 −0.000 −0.000 −0.000 −0.000 −0.001 −0.001
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Wholesale Funding 0.011*** 0.010*** 0.011*** 0.012*** 0.013*** 0.011*** 0.011***
(0.004) (0.004) (0.004) (0.004) (0.004) (0.004) (0.004)
FHLB Ratio 0.006*** 0.006*** 0.007*** 0.007*** 0.008*** 0.007*** 0.007***
(0.002) (0.002) (0.002) (0.002) (0.002) (0.002) (0.002)
ROA −0.022** −0.009 −0.026** −0.024** −0.020* −0.026*** −0.027***
(0.010) (0.010) (0.011) (0.011) (0.011) (0.010) (0.010)
Num.Obs. 4719 4719 4669 4669 4669 4719 4719
R2 0.110 0.113 0.102 0.104 0.106 0.104 0.104
* p < 0.1, ** p < 0.05, *** p < 0.01
Bank-clustered SEs. Book equity excluded. Full sample.
Each column uses a DIFFERENT insolvency measure (alternatives, not combined).
# --- PANEL B: DW - All insolvency measures ---
table_insol_dw <- modelsummary(
  list("(1) MTM Insol" = m_dw_mtm, 
       "(2) Adj Equity" = m_dw_adj,
       "(3) IDCR s=0.5" = m_dw_idcr50,
       "(4) IDCR s=1.0" = m_dw_idcr100,
       "(5) IDCR Cont" = m_dw_idcr_cont,
       "(6) Cap s=0.5" = m_dw_cap50,
       "(7) Cap s=1.0" = m_dw_cap100),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table: Insolvency and DW Access (All Jiang et al. Measures)",
  notes = c("Bank-clustered SEs. Book equity excluded. Full sample.",
            "Each column uses a DIFFERENT insolvency measure (alternatives, not combined)."),
  output = "kableExtra"
) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>%
  scroll_box(width = "100%")

table_insol_dw
Table: Insolvency and DW Access (All Jiang et al. Measures)
&nbsp;(1) MTM Insol &nbsp;(2) Adj Equity &nbsp;(3) IDCR s=0.5 &nbsp;(4) IDCR s=1.0 &nbsp;(5) IDCR Cont &nbsp;(6) Cap s=0.5 &nbsp;(7) Cap s=1.0
Adjusted Equity −0.003**
(0.001)
MTM Insolvent 0.003
(0.017)
IDCR (s=1.0) −0.042
(0.032)
Insolvent IDCR (s=0.5) −0.014
(0.030)
Insolvent IDCR (s=1.0) 0.022
(0.015)
Insolvent Cap (s=1.0) −0.022
(0.026)
Uninsured Leverage 0.001 0.000 0.000 0.000 0.000 0.001 0.001
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Log(Assets) 0.129*** 0.128*** 0.131*** 0.131*** 0.131*** 0.129*** 0.129***
(0.005) (0.005) (0.006) (0.005) (0.005) (0.005) (0.005)
Cash Ratio 0.000 0.001 −0.000 0.000 0.000 0.000 −0.000
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Securities Ratio 0.001 0.001 0.000 0.000 0.000 0.001 0.001
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Loan/Deposit 0.001* 0.001** 0.001 0.001 0.001 0.001* 0.001*
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Wholesale Funding −0.003 −0.003 −0.003 −0.002 −0.002 −0.003 −0.003
(0.004) (0.004) (0.004) (0.004) (0.004) (0.004) (0.004)
FHLB Ratio 0.001 0.001 0.001 0.002 0.002 0.001 0.001
(0.002) (0.002) (0.002) (0.002) (0.002) (0.002) (0.002)
ROA −0.012 −0.008 −0.014 −0.013 −0.012 −0.013 −0.013
(0.010) (0.011) (0.011) (0.011) (0.011) (0.010) (0.010)
Num.Obs. 4719 4719 4669 4669 4669 4719 4719
R2 0.170 0.171 0.168 0.168 0.168 0.170 0.170
* p < 0.1, ** p < 0.05, *** p < 0.01
Bank-clustered SEs. Book equity excluded. Full sample.
Each column uses a DIFFERENT insolvency measure (alternatives, not combined).
# --- COMBINED TABLE FOR EXPORT (Key specifications only) ---
modelsummary(
  list("(1) BTFP: MTM" = m_btfp_mtm, 
       "(2) BTFP: Adj Eq" = m_btfp_adj,
       "(3) BTFP: IDCR" = m_btfp_idcr100,
       "(4) BTFP: Cap" = m_btfp_cap100,
       "(5) DW: MTM" = m_dw_mtm, 
       "(6) DW: Adj Eq" = m_dw_adj,
       "(7) DW: IDCR" = m_dw_idcr100,
       "(8) DW: Cap" = m_dw_cap100),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table: Insolvency and LOLR Access (Key Specifications)",
  output = file.path(TABLE_PATH, "table_insolvency_lolr_all.html")
)

4 SECTION 2: G-SIB Specific Analysis

Advisor Comment: “I also suspect that a lot of the GSIBs used the DW - there were 33 of them in the sample. So we need to run the analysis above for these banks. Be careful as to look at these banks individually, as some may be custodial banks.”

4.1 2.1 GSIB Overview and Individual Bank Analysis

# ============================================================================
# GSIB INDIVIDUAL BANK ANALYSIS
# ============================================================================

cat("=== G-SIB INDIVIDUAL ANALYSIS ===\n\n")

=== G-SIB INDIVIDUAL ANALYSIS ===

# List all GSIBs with their characteristics
gsib_detail <- df_gsib %>%
  select(idrssd, bank_name, total_asset, mtm_total, uninsured_lev, adjusted_equity,
         mtm_insolvent, dw_pre, dw, btfp, both) %>%
  mutate(
    Total_Assets_B = total_asset / 1e6,
    Custodial = ifelse(idrssd %in% custodial_banks, "Yes", "No")
  ) %>%
  arrange(desc(Total_Assets_B))

# Display GSIB table
gsib_detail %>%
  select(bank_name, Total_Assets_B, Custodial, mtm_total, uninsured_lev, 
         adjusted_equity, mtm_insolvent, dw_pre, dw, btfp) %>%
  kable(caption = "Table: G-SIB Individual Bank Characteristics and LOLR Usage",
        digits = 2,
        col.names = c("Bank Name", "Assets ($B)", "Custodial", "MTM Loss (%)", 
                      "Unins Lev (%)", "Adj Equity (%)", "Insolvent", 
                      "DW Pre", "DW Post", "BTFP")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), 
                full_width = FALSE, font_size = 10) %>%
  row_spec(which(gsib_detail$Custodial == "Yes"), background = "#ffffcc") %>%
  footnote(general = "Highlighted rows are custodial banks (State Street, BNY Mellon, etc.)")
Table: G-SIB Individual Bank Characteristics and LOLR Usage
Bank Name Assets ($B) Custodial MTM Loss (%) Unins Lev (%) Adj Equity (%) Insolvent DW Pre DW Post BTFP
JPMORGAN CHASE BANK, NATIONAL ASSOCIATION 3201.94 No 3.05 33.25 6.44 0 0 1 0
BANK OF AMERICA, NATIONAL ASSOCIATION 2418.51 No 6.48 39.30 2.85 0 0 1 1
CITIBANK, N.A. 1764.14 No 2.38 35.01 6.97 0 0 1 1
WELLS FARGO BANK, NATIONAL ASSOCIATION 1717.53 No 5.63 43.45 3.77 0 0 1 0
GOLDMAN SACHS BANK USA 485.55 No 0.78 42.98 9.18 0 0 1 1
TD BANK, N.A. 386.80 No 6.06 26.55 4.95 0 0 1 1
BANK OF NEW YORK MELLON, THE 324.65 Yes 3.65 54.96 4.59 0 0 1 0
STATE STREET BANK AND TRUST COMPANY 298.02 Yes 2.78 53.32 6.14 0 0 1 1
MORGAN STANLEY PRIVATE BANK, NATIONAL ASSOCIATION 209.66 No 3.63 37.94 3.99 0 0 1 1
MORGAN STANLEY BANK, N.A. 201.36 No 3.34 36.04 6.58 0 0 1 1
HSBC BANK USA, NATIONAL ASSOCIATION 162.44 No 3.34 58.78 6.44 0 0 1 1
UBS BANK USA 120.99 No 2.99 0.00 4.79 0 0 1 1
MUFG UNION BANK, NATIONAL ASSOCIATION 104.45 No 5.42 42.60 9.09 0 0 0 0
SANTANDER BANK, NATIONAL ASSOCIATION 99.11 No 4.13 27.89 7.15 0 0 1 1
DEUTSCHE BANK TRUST COMPANY AMERICAS 39.19 No 0.54 66.50 23.65 0 0 0 0
BARCLAYS BANK DELAWARE 37.46 No 0.05 3.25 14.89 0 0 1 0
TD BANK USA, NATIONAL ASSOCIATION 36.00 No 0.14 1.79 9.45 0 0 1 0
BANK OF AMERICA CALIFORNIA, NATIONAL ASSOCIATION 18.20 No 7.63 11.61 4.97 0 0 0 0
WELLS FARGO NATIONAL BANK WEST 17.66 No 8.02 7.25 0.74 0 0 0 0
MIZUHO BANK (USA) 7.00 No 1.08 36.90 20.47 0 0 1 0
RBC BANK (GEORGIA), NATIONAL ASSOCIATION 6.10 No 5.60 6.58 2.87 0 0 0 0
WELLS FARGO BANK SOUTH CENTRAL, NATIONAL ASSOCIATION 5.77 No 6.37 14.05 6.52 0 0 0 0
SUMITOMO MITSUI TRUST BANK (U.S.A.) LIMITED 3.38 No 0.00 69.16 11.86 0 0 0 0
INDUSTRIAL AND COMMERCIAL BANK OF CHINA (USA), NATIONAL ASSOCIATION 2.89 No 1.70 21.19 13.18 0 0 1 0
BANK OF NEW YORK MELLON TRUST COMPANY, NATIONAL ASSOCIATION, THE 1.50 Yes 0.00 0.02 81.98 0 0 0 0
DEUTSCHE BANK NATIONAL TRUST COMPANY 0.42 No 0.70 0.00 59.32 0 0 0 0
STATE STREET BANK AND TRUST COMPANY 0.30 Yes 5.95 11.17 0.88 0 0 0 0
DEUTSCHE BANK TRUST COMPANY, NATIONAL ASSOCIATION 0.15 No 2.02 0.00 86.52 0 0 0 0
DEUTSCHE BANK TRUST COMPANY DELAWARE 0.12 No 0.00 2.26 93.91 0 0 0 0
JPMORGAN CHASE BANK, DEARBORN 0.06 No 0.00 0.40 98.43 0 0 0 0
HSBC TRUST COMPANY (DELAWARE), NATIONAL ASSOCIATION 0.06 No 0.00 0.44 98.25 0 0 0 0
STATE STREET BANK AND TRUST COMPANY, NATIONAL ASSOCIATION 0.04 Yes 0.00 0.00 98.27 0 0 0 0
STATE STREET BANK AND TRUST COMPANY OF CALIFORNIA, NATIONAL ASSOCIATION 0.01 Yes 0.00 0.00 97.75 0 0 0 0
Note:
Highlighted rows are custodial banks (State Street, BNY Mellon, etc.)
# GSIB summary stats
cat("\n=== GSIB SUMMARY ===\n")

=== GSIB SUMMARY ===

cat("Total GSIBs:", nrow(df_gsib), "\n")

Total GSIBs: 33

cat("Custodial banks:", sum(df_gsib$idrssd %in% custodial_banks), "\n")

Custodial banks: 6

cat("Insolvent GSIBs (Adj Eq < 0):", sum(df_gsib$mtm_insolvent == 1, na.rm = TRUE), "\n")

Insolvent GSIBs (Adj Eq < 0): 0

cat("GSIBs using DW pre-BTFP:", sum(df_gsib$dw_pre == 1, na.rm = TRUE), "\n")

GSIBs using DW pre-BTFP: 0

cat("GSIBs using DW post-BTFP:", sum(df_gsib$dw == 1, na.rm = TRUE), "\n")

GSIBs using DW post-BTFP: 17

cat("GSIBs using BTFP:", sum(df_gsib$btfp == 1, na.rm = TRUE), "\n")

GSIBs using BTFP: 10

# ============================================================================
# GSIB INDIVIDUAL BANK ANALYSIS
# ============================================================================
 #insolvency_2, insolvent_cap_s100
cat("=== G-SIB INDIVIDUAL ANALYSIS ===\n\n")

=== G-SIB INDIVIDUAL ANALYSIS ===

# List all GSIBs with their characteristics
gsib_detail <- df_gsib %>%
  select(idrssd, bank_name, total_asset, mtm_total, uninsured_lev, idcr_2,insolvent_idcr_s100,
         dw_pre, dw, btfp, both) %>%
  mutate(
    Total_Assets_B = total_asset / 1e6,
    Custodial = ifelse(idrssd %in% custodial_banks, "Yes", "No")
  ) %>%
  arrange(desc(Total_Assets_B))

# Display GSIB table
gsib_detail %>%
  select(bank_name, Total_Assets_B, Custodial, mtm_total, uninsured_lev, 
         idcr_2,insolvent_idcr_s100, dw_pre, dw, btfp) %>%
  kable(caption = "Table: G-SIB Individual Bank Characteristics and LOLR Usage",
        digits = 2,
        col.names = c("Bank Name", "Assets ($B)", "Custodial", "MTM Loss (%)", 
                      "Unins Lev (%)", "IDCR (s= 1.0 %)", "IDCR Insolvent", 
                      "DW Pre", "DW Post", "BTFP")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), 
                full_width = FALSE, font_size = 10) %>%
  row_spec(which(gsib_detail$Custodial == "Yes"), background = "#ffffcc") %>%
  footnote(general = "Highlighted rows are custodial banks (State Street, BNY Mellon, etc.)")
Table: G-SIB Individual Bank Characteristics and LOLR Usage
Bank Name Assets ($B) Custodial MTM Loss (%) Unins Lev (%) IDCR (s= 1.0 %) IDCR Insolvent DW Pre DW Post BTFP
JPMORGAN CHASE BANK, NATIONAL ASSOCIATION 3201.94 No 3.05 33.25 1.14 0 0 1 0
BANK OF AMERICA, NATIONAL ASSOCIATION 2418.51 No 6.48 39.30 0.33 0 0 1 1
CITIBANK, N.A. 1764.14 No 2.38 35.01 3.01 0 0 1 1
WELLS FARGO BANK, NATIONAL ASSOCIATION 1717.53 No 5.63 43.45 0.12 0 0 1 0
GOLDMAN SACHS BANK USA 485.55 No 0.78 42.98 0.25 0 0 1 1
TD BANK, N.A. 386.80 No 6.06 26.55 0.27 0 0 1 1
BANK OF NEW YORK MELLON, THE 324.65 Yes 3.65 54.96 10.97 0 0 1 0
STATE STREET BANK AND TRUST COMPANY 298.02 Yes 2.78 53.32 9.22 0 0 1 1
MORGAN STANLEY PRIVATE BANK, NATIONAL ASSOCIATION 209.66 No 3.63 37.94 -0.18 1 0 1 1
MORGAN STANLEY BANK, N.A. 201.36 No 3.34 36.04 0.06 0 0 1 1
HSBC BANK USA, NATIONAL ASSOCIATION 162.44 No 3.34 58.78 1.23 0 0 1 1
UBS BANK USA 120.99 No 2.99 0.00 0.13 0 0 1 1
MUFG UNION BANK, NATIONAL ASSOCIATION 104.45 No 5.42 42.60 1.04 0 0 0 0
SANTANDER BANK, NATIONAL ASSOCIATION 99.11 No 4.13 27.89 0.39 0 0 1 1
DEUTSCHE BANK TRUST COMPANY AMERICAS 39.19 No 0.54 66.50 21.61 0 0 0 0
BARCLAYS BANK DELAWARE 37.46 No 0.05 3.25 0.03 0 0 1 0
TD BANK USA, NATIONAL ASSOCIATION 36.00 No 0.14 1.79 -0.09 1 0 1 0
BANK OF AMERICA CALIFORNIA, NATIONAL ASSOCIATION 18.20 No 7.63 11.61 1.14 0 0 0 0
WELLS FARGO NATIONAL BANK WEST 17.66 No 8.02 7.25 0.26 0 0 0 0
MIZUHO BANK (USA) 7.00 No 1.08 36.90 0.92 0 0 1 0
RBC BANK (GEORGIA), NATIONAL ASSOCIATION 6.10 No 5.60 6.58 0.03 0 0 0 0
WELLS FARGO BANK SOUTH CENTRAL, NATIONAL ASSOCIATION 5.77 No 6.37 14.05 0.07 0 0 0 0
SUMITOMO MITSUI TRUST BANK (U.S.A.) LIMITED 3.38 No 0.00 69.16 3.72 0 0 0 0
INDUSTRIAL AND COMMERCIAL BANK OF CHINA (USA), NATIONAL ASSOCIATION 2.89 No 1.70 21.19 0.52 0 0 1 0
BANK OF NEW YORK MELLON TRUST COMPANY, NATIONAL ASSOCIATION, THE 1.50 Yes 0.00 0.02 551.79 0 0 0 0
DEUTSCHE BANK NATIONAL TRUST COMPANY 0.42 No 0.70 0.00 0 0 0
STATE STREET BANK AND TRUST COMPANY 0.30 Yes 5.95 11.17 0.03 0 0 0 0
DEUTSCHE BANK TRUST COMPANY, NATIONAL ASSOCIATION 0.15 No 2.02 0.00 0 0 0
DEUTSCHE BANK TRUST COMPANY DELAWARE 0.12 No 0.00 2.26 459.43 0 0 0 0
JPMORGAN CHASE BANK, DEARBORN 0.06 No 0.00 0.40 245.40 0 0 0 0
HSBC TRUST COMPANY (DELAWARE), NATIONAL ASSOCIATION 0.06 No 0.00 0.44 218.43 0 0 0 0
STATE STREET BANK AND TRUST COMPANY, NATIONAL ASSOCIATION 0.04 Yes 0.00 0.00 0 0 0
STATE STREET BANK AND TRUST COMPANY OF CALIFORNIA, NATIONAL ASSOCIATION 0.01 Yes 0.00 0.00 0 0 0
Note:
Highlighted rows are custodial banks (State Street, BNY Mellon, etc.)
# GSIB summary stats
cat("\n=== GSIB SUMMARY ===\n")

=== GSIB SUMMARY ===

cat("Total GSIBs:", nrow(df_gsib), "\n")

Total GSIBs: 33

cat("Custodial banks:", sum(df_gsib$idrssd %in% custodial_banks), "\n")

Custodial banks: 6

cat("Insolvent GSIBs (IDCR Insolvent s=1.0 < 0):", sum(df_gsib$insolvent_idcr_s100 == 1, na.rm = TRUE), "\n")

Insolvent GSIBs (IDCR Insolvent s=1.0 < 0): 2

cat("GSIBs using DW pre-BTFP:", sum(df_gsib$dw_pre == 1, na.rm = TRUE), "\n")

GSIBs using DW pre-BTFP: 0

cat("GSIBs using DW post-BTFP:", sum(df_gsib$dw == 1, na.rm = TRUE), "\n")

GSIBs using DW post-BTFP: 17

cat("GSIBs using BTFP:", sum(df_gsib$btfp == 1, na.rm = TRUE), "\n")

GSIBs using BTFP: 10

# ============================================================================
# GSIB INDIVIDUAL BANK ANALYSIS
# ============================================================================
 
cat("=== G-SIB INDIVIDUAL ANALYSIS ===\n\n")

=== G-SIB INDIVIDUAL ANALYSIS ===

# List all GSIBs with their characteristics
gsib_detail <- df_gsib %>%
  select(idrssd, bank_name, total_asset, mtm_total, uninsured_lev, insolvency_2, insolvent_cap_s100,
         dw_pre, dw, btfp, both) %>%
  mutate(
    Total_Assets_B = total_asset / 1e6,
    Custodial = ifelse(idrssd %in% custodial_banks, "Yes", "No")
  ) %>%
  arrange(desc(Total_Assets_B))

# Display GSIB table
gsib_detail %>%
  select(bank_name, Total_Assets_B, Custodial, mtm_total, uninsured_lev, 
         insolvency_2, insolvent_cap_s100,, dw_pre, dw, btfp) %>%
  kable(caption = "Table: G-SIB Individual Bank Characteristics and LOLR Usage",
        digits = 2,
        col.names = c("Bank Name", "Assets ($B)", "Custodial", "MTM Loss (%)", 
                      "Unins Lev (%)", "Capital (s= 1.0 %)", "Capital Insolvent", 
                      "DW Pre", "DW Post", "BTFP")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), 
                full_width = FALSE, font_size = 10) %>%
  row_spec(which(gsib_detail$Custodial == "Yes"), background = "#ffffcc") %>%
  footnote(general = "Highlighted rows are custodial banks (State Street, BNY Mellon, etc.)")
Table: G-SIB Individual Bank Characteristics and LOLR Usage
Bank Name Assets ($B) Custodial MTM Loss (%) Unins Lev (%) Capital (s= 1.0 %) Capital Insolvent DW Pre DW Post BTFP
JPMORGAN CHASE BANK, NATIONAL ASSOCIATION 3201.94 No 3.05 33.25 0.10 0 0 1 0
BANK OF AMERICA, NATIONAL ASSOCIATION 2418.51 No 6.48 39.30 0.08 0 0 1 1
CITIBANK, N.A. 1764.14 No 2.38 35.01 0.06 0 0 1 1
WELLS FARGO BANK, NATIONAL ASSOCIATION 1717.53 No 5.63 43.45 0.09 0 0 1 0
GOLDMAN SACHS BANK USA 485.55 No 0.78 42.98 0.04 0 0 1 1
TD BANK, N.A. 386.80 No 6.06 26.55 0.12 0 0 1 1
BANK OF NEW YORK MELLON, THE 324.65 Yes 3.65 54.96 0.11 0 0 1 0
STATE STREET BANK AND TRUST COMPANY 298.02 Yes 2.78 53.32 0.10 0 0 1 1
MORGAN STANLEY PRIVATE BANK, NATIONAL ASSOCIATION 209.66 No 3.63 37.94 0.02 0 0 1 1
MORGAN STANLEY BANK, N.A. 201.36 No 3.34 36.04 0.07 0 0 1 1
HSBC BANK USA, NATIONAL ASSOCIATION 162.44 No 3.34 58.78 0.16 0 0 1 1
UBS BANK USA 120.99 No 2.99 0.00 0.08 0 0 1 1
MUFG UNION BANK, NATIONAL ASSOCIATION 104.45 No 5.42 42.60 0.21 0 0 0 0
SANTANDER BANK, NATIONAL ASSOCIATION 99.11 No 4.13 27.89 0.11 0 0 1 1
DEUTSCHE BANK TRUST COMPANY AMERICAS 39.19 No 0.54 66.50 0.37 0 0 0 0
BARCLAYS BANK DELAWARE 37.46 No 0.05 3.25 0.14 0 0 1 0
TD BANK USA, NATIONAL ASSOCIATION 36.00 No 0.14 1.79 0.09 0 0 1 0
BANK OF AMERICA CALIFORNIA, NATIONAL ASSOCIATION 18.20 No 7.63 11.61 0.17 0 0 0 0
WELLS FARGO NATIONAL BANK WEST 17.66 No 8.02 7.25 0.10 0 0 0 0
MIZUHO BANK (USA) 7.00 No 1.08 36.90 0.20 0 0 1 0
RBC BANK (GEORGIA), NATIONAL ASSOCIATION 6.10 No 5.60 6.58 0.08 0 0 0 0
WELLS FARGO BANK SOUTH CENTRAL, NATIONAL ASSOCIATION 5.77 No 6.37 14.05 0.11 0 0 0 0
SUMITOMO MITSUI TRUST BANK (U.S.A.) LIMITED 3.38 No 0.00 69.16 0.22 0 0 0 0
INDUSTRIAL AND COMMERCIAL BANK OF CHINA (USA), NATIONAL ASSOCIATION 2.89 No 1.70 21.19 0.15 0 0 1 0
BANK OF NEW YORK MELLON TRUST COMPANY, NATIONAL ASSOCIATION, THE 1.50 Yes 0.00 0.02 0.82 0 0 0 0
DEUTSCHE BANK NATIONAL TRUST COMPANY 0.42 No 0.70 0.00 0.60 0 0 0 0
STATE STREET BANK AND TRUST COMPANY 0.30 Yes 5.95 11.17 0.06 0 0 0 0
DEUTSCHE BANK TRUST COMPANY, NATIONAL ASSOCIATION 0.15 No 2.02 0.00 0.89 0 0 0 0
DEUTSCHE BANK TRUST COMPANY DELAWARE 0.12 No 0.00 2.26 0.94 0 0 0 0
JPMORGAN CHASE BANK, DEARBORN 0.06 No 0.00 0.40 0.98 0 0 0 0
HSBC TRUST COMPANY (DELAWARE), NATIONAL ASSOCIATION 0.06 No 0.00 0.44 0.98 0 0 0 0
STATE STREET BANK AND TRUST COMPANY, NATIONAL ASSOCIATION 0.04 Yes 0.00 0.00 0.98 0 0 0 0
STATE STREET BANK AND TRUST COMPANY OF CALIFORNIA, NATIONAL ASSOCIATION 0.01 Yes 0.00 0.00 0.98 0 0 0 0
Note:
Highlighted rows are custodial banks (State Street, BNY Mellon, etc.)
# GSIB summary stats
cat("\n=== GSIB SUMMARY ===\n")

=== GSIB SUMMARY ===

cat("Total GSIBs:", nrow(df_gsib), "\n")

Total GSIBs: 33

cat("Custodial banks:", sum(df_gsib$idrssd %in% custodial_banks), "\n")

Custodial banks: 6

cat("Insolvent GSIBs (Capital insolvent s=1.0 < 0):", sum(df_gsib$insolvent_cap_s100 == 1, na.rm = TRUE), "\n")

Insolvent GSIBs (Capital insolvent s=1.0 < 0): 0

cat("GSIBs using DW pre-BTFP:", sum(df_gsib$dw_pre == 1, na.rm = TRUE), "\n")

GSIBs using DW pre-BTFP: 0

cat("GSIBs using DW post-BTFP:", sum(df_gsib$dw == 1, na.rm = TRUE), "\n")

GSIBs using DW post-BTFP: 17

cat("GSIBs using BTFP:", sum(df_gsib$btfp == 1, na.rm = TRUE), "\n")

GSIBs using BTFP: 10

4.2 2.2 GSIB Descriptive Statistics Comparison

# ============================================================================
# GSIB DESCRIPTIVE STATISTICS
# ============================================================================
# Compare GSIBs vs Non-GSIBs with additional Insolvency measures
gsib_comparison <- bind_rows(
  df_gsib %>%
    summarise(
      Group = "GSIBs (All)",
      N = n(),
      Mean_Assets_B = mean(total_asset / 1e6, na.rm = TRUE),
      Mean_MTM = mean(mtm_total, na.rm = TRUE),
      Mean_Uninsured = mean(uninsured_lev, na.rm = TRUE),
      Mean_Adj_Equity = mean(adjusted_equity, na.rm = TRUE),
      Pct_Insolvent = mean(mtm_insolvent, na.rm = TRUE) * 100,
      # New variables added here
      Mean_IDCR = mean(idcr_2, na.rm = TRUE),
      Pct_IDCR_Insol = mean(insolvent_idcr_s100, na.rm = TRUE) * 100,
      Mean_Insolvency = mean(insolvency_2, na.rm = TRUE),
      Pct_Cap_Insol = mean(insolvent_cap_s100, na.rm = TRUE) * 100,
      # ---
      Pct_DW_Pre = mean(dw_pre, na.rm = TRUE) * 100,
      Pct_DW = mean(dw, na.rm = TRUE) * 100,
      Pct_BTFP = mean(btfp, na.rm = TRUE) * 100
    ),
  df_gsib %>%
    filter(!idrssd %in% custodial_banks) %>%
    summarise(
      Group = "GSIBs (Non-Custodial)",
      N = n(),
      Mean_Assets_B = mean(total_asset / 1e6, na.rm = TRUE),
      Mean_MTM = mean(mtm_total, na.rm = TRUE),
      Mean_Uninsured = mean(uninsured_lev, na.rm = TRUE),
      Mean_Adj_Equity = mean(adjusted_equity, na.rm = TRUE),
      Pct_Insolvent = mean(mtm_insolvent, na.rm = TRUE) * 100,
      Mean_IDCR = mean(idcr_2, na.rm = TRUE),
      Pct_IDCR_Insol = mean(insolvent_idcr_s100, na.rm = TRUE) * 100,
      Mean_Insolvency = mean(insolvency_2, na.rm = TRUE),
      Pct_Cap_Insol = mean(insolvent_cap_s100, na.rm = TRUE) * 100,
      Pct_DW_Pre = mean(dw_pre, na.rm = TRUE) * 100,
      Pct_DW = mean(dw, na.rm = TRUE) * 100,
      Pct_BTFP = mean(btfp, na.rm = TRUE) * 100
    ),
  df_gsib %>%
    filter(idrssd %in% custodial_banks) %>%
    summarise(
      Group = "GSIBs (Custodial)",
      N = n(),
      Mean_Assets_B = mean(total_asset / 1e6, na.rm = TRUE),
      Mean_MTM = mean(mtm_total, na.rm = TRUE),
      Mean_Uninsured = mean(uninsured_lev, na.rm = TRUE),
      Mean_Adj_Equity = mean(adjusted_equity, na.rm = TRUE),
      Pct_Insolvent = mean(mtm_insolvent, na.rm = TRUE) * 100,
      Mean_IDCR = mean(idcr_2, na.rm = TRUE),
      Pct_IDCR_Insol = mean(insolvent_idcr_s100, na.rm = TRUE) * 100,
      Mean_Insolvency = mean(insolvency_2, na.rm = TRUE),
      Pct_Cap_Insol = mean(insolvent_cap_s100, na.rm = TRUE) * 100,
      Pct_DW_Pre = mean(dw_pre, na.rm = TRUE) * 100,
      Pct_DW = mean(dw, na.rm = TRUE) * 100,
      Pct_BTFP = mean(btfp, na.rm = TRUE) * 100
    ),
  df %>%
    summarise(
      Group = "Non-GSIBs",
      N = n(),
      Mean_Assets_B = mean(total_asset / 1e6, na.rm = TRUE),
      Mean_MTM = mean(mtm_total, na.rm = TRUE),
      Mean_Uninsured = mean(uninsured_lev, na.rm = TRUE),
      Mean_Adj_Equity = mean(adjusted_equity, na.rm = TRUE),
      Pct_Insolvent = mean(mtm_insolvent, na.rm = TRUE) * 100,
      Mean_IDCR = mean(idcr_2, na.rm = TRUE),
      Pct_IDCR_Insol = mean(insolvent_idcr_s100, na.rm = TRUE) * 100,
      Mean_Insolvency = mean(insolvency_2, na.rm = TRUE),
      Pct_Cap_Insol = mean(insolvent_cap_s100, na.rm = TRUE) * 100,
      Pct_DW_Pre = mean(dw_pre, na.rm = TRUE) * 100,
      Pct_DW = mean(dw, na.rm = TRUE) * 100,
      Pct_BTFP = mean(btfp, na.rm = TRUE) * 100
    )
)

# Display updated Table
gsib_comparison %>%
  kable(caption = "Table: GSIB vs Non-GSIB Comparison (Extended Insolvency)",
        digits = 2,
        col.names = c("Group", "N", "Mean Assets ($B)", "MTM Loss", "Unins Lev",
                      "Adj Equity", "% MTM Insol", "Mean IDCR", "% IDCR Insol", 
                      "Mean Insolvency", "% Cap Insol", "% DW Pre", "% DW", "% BTFP")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  footnote(general = "IDCR and Insolvency measures calculated following Jiang et al. (2023) at s=1.0.")
Table: GSIB vs Non-GSIB Comparison (Extended Insolvency)
Group N Mean Assets ($B) MTM Loss Unins Lev Adj Equity % MTM Insol Mean IDCR % IDCR Insol Mean Insolvency % Cap Insol % DW Pre % DW % BTFP
GSIBs (All) 33 353.68 2.83 23.78 27.48 0.00 52.80 6.90 0.31 0.00 0.00 51.52 30.30
GSIBs (Non-Custodial) 27 409.15 3.00 24.64 22.86 0.00 38.37 8.00 0.26 0.00 0.00 55.56 33.33
GSIBs (Custodial) 6 104.09 2.06 19.91 48.27 0.00 143.00 0.00 0.51 0.00 0.00 33.33 16.67
Non-GSIBs 4696 2.42 5.41 23.09 4.35 18.47 0.09 28.04 0.07 7.72 2.24 29.32 27.79
Note:
IDCR and Insolvency measures calculated following Jiang et al. (2023) at s=1.0.
# Save updated CSV
write_csv(gsib_comparison, file.path(TABLE_PATH, "gsib_comparison_extended.csv"))

5 SECTION 3: Extensive Margin with MTM Total

5.1 3.1 Main Extensive Margin Specifications (MTM Total Only)

# ============================================================================
# TABLE 5: EXTENSIVE MARGIN - MTM TOTAL ONLY (per advisor feedback)
# ============================================================================

# Formulas using MTM TOTAL only (not split)
f_btfp_1 <- as.formula(paste("btfp ~ mtm_total +", CONTROLS))
f_btfp_2 <- as.formula(paste("btfp ~ mtm_total + uninsured_lev +", CONTROLS))
f_btfp_3 <- as.formula(paste("btfp ~ mtm_total + uninsured_lev + I(mtm_total * uninsured_lev) +", CONTROLS))

f_dw_1 <- as.formula(paste("dw ~ mtm_total +", CONTROLS))
f_dw_2 <- as.formula(paste("dw ~ mtm_total + uninsured_lev +", CONTROLS))
f_dw_3 <- as.formula(paste("dw ~ mtm_total + uninsured_lev + I(mtm_total * uninsured_lev) +", CONTROLS))

# Estimate models
m5_btfp_1 <- feols(f_btfp_1, data = df, vcov = ~idrssd)
m5_btfp_2 <- feols(f_btfp_2, data = df, vcov = ~idrssd)
m5_btfp_3 <- feols(f_btfp_3, data = df, vcov = ~idrssd)
m5_dw_1 <- feols(f_dw_1, data = df, vcov = ~idrssd)
m5_dw_2 <- feols(f_dw_2, data = df, vcov = ~idrssd)
m5_dw_3 <- feols(f_dw_3, data = df, vcov = ~idrssd)

table5 <- modelsummary(
  list("(1) BTFP" = m5_btfp_1, "(2) BTFP" = m5_btfp_2, "(3) BTFP" = m5_btfp_3,
       "(4) DW" = m5_dw_1, "(5) DW" = m5_dw_2, "(6) DW" = m5_dw_3),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 5: Extensive Margin - MTM Total Loss (Not Split)",
  notes = "Bank-clustered SEs. MTM Total = All securities, not split by BTFP eligibility.",
  output = "kableExtra"
)

table5
Table 5: Extensive Margin - MTM Total Loss (Not Split)
&nbsp;(1) BTFP &nbsp;(2) BTFP &nbsp;(3) BTFP &nbsp;(4) DW &nbsp;(5) DW &nbsp;(6) DW
MTM Loss (Total) 0.015*** 0.017*** −0.004 0.004 0.004 −0.015***
(0.003) (0.003) (0.005) (0.003) (0.003) (0.005)
Uninsured Leverage 0.002*** −0.003*** 0.000 −0.004***
(0.001) (0.001) (0.001) (0.001)
MTM × Uninsured 0.001*** 0.001***
(0.000) (0.000)
Log(Assets) 0.068*** 0.060*** 0.059*** 0.132*** 0.131*** 0.130***
(0.005) (0.006) (0.006) (0.005) (0.006) (0.006)
Cash Ratio −0.004*** −0.004*** −0.004*** 0.001 0.001 0.001
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Securities Ratio 0.003*** 0.003*** 0.003*** 0.000 0.000 0.000
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Loan/Deposit −0.000 −0.000 0.000 0.001 0.001 0.001**
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Book Equity −0.008*** −0.006*** −0.006*** −0.002* −0.002 −0.002
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Wholesale Funding 0.010** 0.011*** 0.010*** −0.003 −0.003 −0.004
(0.004) (0.004) (0.004) (0.004) (0.004) (0.004)
FHLB Ratio 0.005*** 0.006*** 0.006*** 0.001 0.001 0.001
(0.002) (0.002) (0.002) (0.002) (0.002) (0.002)
ROA −0.009 −0.010 −0.015 −0.010 −0.010 −0.015
(0.010) (0.010) (0.010) (0.011) (0.011) (0.011)
Num.Obs. 4678 4678 4678 4678 4678 4678
R2 0.112 0.114 0.118 0.167 0.167 0.170
* p < 0.1, ** p < 0.05, *** p < 0.01
Bank-clustered SEs. MTM Total = All securities, not split by BTFP eligibility.
# Save
modelsummary(
  list("(1) BTFP" = m5_btfp_1, "(2) BTFP" = m5_btfp_2, "(3) BTFP" = m5_btfp_3,
       "(4) DW" = m5_dw_1, "(5) DW" = m5_dw_2, "(6) DW" = m5_dw_3),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 5: Extensive Margin (MTM Total)",
  output = file.path(TABLE_PATH, "table5_extensive_mtm_total.html")
)

6 SECTION 4: Acute Period Analysis

6.1 4.1 Acute Period Descriptive Statistics by Facility

# ============================================================================
# ACUTE PERIOD DESCRIPTIVE STATISTICS
# ============================================================================

# Add acute period borrowing indicators
df <- df %>%
  mutate(
    borrowed_acute = case_when(
      btfp_acute == 1 & dw_acute == 1 ~ "Both",
      btfp_acute == 1 ~ "BTFP Only",
      dw_acute == 1 ~ "DW Only",
      TRUE ~ "Neither"
    )
  )

# Descriptive stats for acute period borrowers
acute_vars_full <- c(
  "ln_assets", "mtm_total", "uninsured_lev", "uninsured_share", "omo_ratio", 
  "non_omo_ratio", "fhlb_change", "cash_ratio", "securities_ratio", 
  "loan_ratio", "book_equity_ratio", "tier1_ratio", "roa", 
  "loan_to_deposit", "fhlb_ratio", "wholesale", "adjusted_equity", 
  "mv_adjustment", "idcr_1", "idcr_2", "insolvency_1", "insolvency_2",
  "mtm_insolvent", "insolvent_idcr_s50", "insolvent_idcr_s100", 
  "insolvent_cap_s50", "insolvent_cap_s100", "btfp_pct", "dw_pct", 
  "btfp_util", "run_risk"
)

# 1. Create a detailed summary table (Means only for readability)
acute_summary_expanded <- df %>%
  group_by(borrowed_acute) %>%
  summarise(
    N = n(),
    # Risk & Exposure
    `MTM Total`       = mean(mtm_total, na.rm = TRUE),
    `Unins Lev`       = mean(uninsured_lev, na.rm = TRUE),
    `Run Risk`        = mean(run_risk, na.rm = TRUE),
    `OMO Ratio`       = mean(omo_ratio, na.rm = TRUE),
    
    # Jiang et al. / Insolvency
    `Adj Equity`      = mean(adjusted_equity, na.rm = TRUE),
    `IDCR (s=1.0)`    = mean(idcr_2, na.rm = TRUE),
    `MTM Insolvent %` = mean(mtm_insolvent, na.rm = TRUE) * 100,
    `IDCR Insol %`    = mean(insolvent_idcr_s100, na.rm = TRUE) * 100,
    `Cap Insol %`     = mean(insolvent_cap_s100, na.rm = TRUE) * 100,
    
    # Controls
    `Log(Assets)`     = mean(ln_assets, na.rm = TRUE),
    `Cash Ratio`      = mean(cash_ratio, na.rm = TRUE),
    `Tier 1 Ratio`    = mean(tier1_ratio, na.rm = TRUE),
    `Loan/Deposit`    = mean(loan_to_deposit, na.rm = TRUE),
    `Wholesale`       = mean(wholesale, na.rm = TRUE),
    
    # Intensive Margin
    `BTFP % Assets`   = mean(btfp_pct, na.rm = TRUE),
    `DW % Assets`     = mean(dw_pct, na.rm = TRUE),
    .groups = "drop"
  )

# 2. Display the table

table_acute <- acute_summary_expanded %>%
  kable(caption = "Table 10: Expanded Characteristics by Acute Period Borrowing Status",
        digits = 2) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), 
                full_width = TRUE, 
                font_size = 12) %>%
  # Updated counts: 1 (borrowed_acute) + 1 (N) + 4 (Risk) + 5 (Insolv) + 5 (Controls) + 2 (Margins) = 18
  add_header_above(c(" " = 2, 
                     "Risk & Exposure" = 4, 
                     "Insolvency Measures" = 5, 
                     "Controls" = 5, 
                     "Margins" = 2)) %>%
  footnote(general = "Acute period: Mar 13 - May 1, 2023. Means displayed. Insolvency % represents percentage of banks in group.")

table_acute
Table 10: Expanded Characteristics by Acute Period Borrowing Status
Risk & Exposure
Insolvency Measures
Controls
Margins
borrowed_acute N MTM Total Unins Lev Run Risk OMO Ratio Adj Equity IDCR (s=1.0) MTM Insolvent % IDCR Insol % Cap Insol % Log(Assets) Cash Ratio Tier 1 Ratio Loan/Deposit Wholesale BTFP % Assets DW % Assets
BTFP Only 382 6.17 25.78 180.2 10.75 2.08 0.07 30.10 28.27 9.69 13.44 4.71 10.36 72.57 1.26 16.23 2.85
Both 96 5.95 31.58 217.6 10.85 2.34 0.11 22.92 25.00 12.50 14.35 4.72 9.67 73.41 1.63 20.40 33.14
DW Only 316 5.68 26.65 173.1 8.73 3.49 0.08 18.67 32.91 12.03 13.76 6.48 10.55 75.81 1.10 11.92 23.28
Neither 3902 5.30 22.33 134.2 9.50 4.69 0.09 17.20 27.70 7.05 12.62 8.93 11.72 70.75 0.69 10.49 1.60
Note:
Acute period: Mar 13 - May 1, 2023. Means displayed. Insolvency % represents percentage of banks in group.
# 3. Export full Mean/SD stats for all variables to CSV
acute_full_stats <- df %>%
  group_by(borrowed_acute) %>%
  summarise(across(all_of(acute_vars_full), 
                   list(mean = ~mean(., na.rm = TRUE), sd = ~sd(., na.rm = TRUE)),
                   .names = "{.col}_{.fn}"))

write_csv(acute_full_stats, file.path(TABLE_PATH, "acute_period_full_descriptives.csv"))

# 4. T-test loop for significant differences between 'BTFP Only' and 'DW Only'
cat("\n=== T-Tests: BTFP Only vs. DW Only (Key Metrics) ===\n")

=== T-Tests: BTFP Only vs. DW Only (Key Metrics) ===

compare_vars <- c("uninsured_lev", "mtm_total", "adjusted_equity", "run_risk")

for(v in compare_vars) {
  t_val <- t.test(as.formula(paste(v, "~ borrowed_acute")), 
                  data = df %>% filter(borrowed_acute %in% c("BTFP Only", "DW Only")))
  cat(sprintf("%s: BTFP Mean=%.2f, DW Mean=%.2f, p-val=%.4f\n", 
              v, t_val$estimate[1], t_val$estimate[2], t_val$p.value))
}

uninsured_lev: BTFP Mean=25.78, DW Mean=26.65, p-val=0.2964 mtm_total: BTFP Mean=6.17, DW Mean=5.68, p-val=0.0015 adjusted_equity: BTFP Mean=2.08, DW Mean=3.49, p-val=0.0000 run_risk: BTFP Mean=180.24, DW Mean=173.05, p-val=0.2763

# T-test: DW vs BTFP during acute for uninsured leverage
cat("\n=== T-TEST: DW vs BTFP Acute Period - Uninsured Leverage ===\n")

=== T-TEST: DW vs BTFP Acute Period - Uninsured Leverage ===

acute_btfp <- df %>% filter(btfp_acute == 1) %>% pull(uninsured_lev)
acute_dw <- df %>% filter(dw_acute == 1) %>% pull(uninsured_lev)
t.test(acute_btfp, acute_dw)
Welch Two Sample t-test

data: acute_btfp and acute_dw t = -1.1, df = 859, p-value = 0.3 alternative hypothesis: true difference in means is not equal to 0 95 percent confidence interval: -2.3663 0.6595 sample estimates: mean of x mean of y 26.95 27.80

6.2 4.2 Acute Period by Size Subsamples

# ============================================================================
# ACUTE PERIOD BY SIZE SUBSAMPLES
# ============================================================================

# Create size categories (10-100B is focus per advisor)
df <- df %>%
  mutate(
    size_group = case_when(
      total_asset < 1e6 ~ "< $1B",
      total_asset < 10e6 ~ "$1-10B",
      total_asset < 100e6 ~ "$10-100B",
      TRUE ~ "> $100B"
    ),
    size_group = factor(size_group, levels = c("< $1B", "$1-10B", "$10-100B", "> $100B"))
  )

# Acute borrowing by size
acute_by_size <- df %>%
  group_by(size_group) %>%
  summarise(
    N = n(),
    N_BTFP_Acute = sum(btfp_acute, na.rm = TRUE),
    N_DW_Acute   = sum(dw_acute, na.rm = TRUE),
    Pct_BTFP     = (N_BTFP_Acute / N) * 100,
    Pct_DW       = (N_DW_Acute / N) * 100,
    
    # Core Exposure
    Mean_MTM       = mean(mtm_total, na.rm = TRUE),
    Mean_Uninsured = mean(uninsured_lev, na.rm = TRUE),
    
    # Adjusted Equity & IDCR (Continuous)
    Mean_AdjEq     = mean(adjusted_equity, na.rm = TRUE),
    Mean_IDCR      = mean(idcr_2, na.rm = TRUE),
    Mean_Insolv    = mean(insolvency_2, na.rm = TRUE),
    
    # Insolvency Binary Indicators (% of banks in group)
    Pct_MTM_Insol  = mean(mtm_insolvent, na.rm = TRUE) * 100,
    Pct_IDCR_Insol = mean(insolvent_idcr_s100, na.rm = TRUE) * 100,
    Pct_Cap_Insol  = mean(insolvent_cap_s100, na.rm = TRUE) * 100,
    .groups = "drop"
  )

acute_by_size %>%
  kable(caption = "Table: Acute Period LOLR Usage and Insolvency by Bank Size",
        digits = 2,
        col.names = c("Size Group", "N Banks", "N BTFP", "N DW", 
                      "% BTFP", "% DW", "Mean MTM", "Mean Unins", 
                      "Adj Equity", "IDCR", "Insolvency", 
                      "% MTM Insol", "% IDCR Insol", "% Cap Insol")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE) %>%
  add_header_above(c(" " = 2, "Usage Counts" = 2, "Usage %" = 2, 
                     "Exposure" = 2, "Levels (Mean)" = 3, "Insolvent (% Banks)" = 3)) %>%
  footnote(general = "Insolvency measures calculated following Jiang et al. (2023) using s=1.0 (100% run scenario).")
Table: Acute Period LOLR Usage and Insolvency by Bank Size
Usage Counts
Usage %
Exposure
Levels (Mean)
Insolvent (% Banks)
Size Group N Banks N BTFP N DW % BTFP % DW Mean MTM Mean Unins Adj Equity IDCR Insolvency % MTM Insol % IDCR Insol % Cap Insol
< $1B 3743 296 216 7.91 5.77 5.41 21.34 4.39 0.09 0.08 19.48 27.95 6.20
$1-10B 818 142 152 17.36 18.58 5.47 29.35 4.05 0.09 0.06 15.54 29.06 13.46
$10-100B 117 35 39 29.91 33.33 5.00 34.42 5.30 0.17 0.06 6.84 23.93 15.38
> $100B 18 5 5 27.78 27.78 4.62 30.70 4.14 0.12 0.05 16.67 27.78 11.11
Note:
Insolvency measures calculated following Jiang et al. (2023) using s=1.0 (100% run scenario).

7 SECTION 5: Temporal Dynamics with Run Risk Dummy

Advisor Comment: “Table 8 - Run the specifications with run risk dummy - we should see significance in the acute phases and for DW as well, I think.”

7.1 5.1 Temporal Analysis with Run Risk Dummy

# ============================================================================
# TABLE 8: TEMPORAL DYNAMICS WITH RUN RISK DUMMY
# ============================================================================

# Formulas with run risk dummy
f_acute_rr <- as.formula(paste("btfp_acute ~ mtm_total + uninsured_lev + run_risk_dummy  +", CONTROLS))
f_post_rr <- as.formula(paste("btfp_post ~ mtm_total + uninsured_lev + run_risk_dummy  +", CONTROLS))
f_arb_rr <- as.formula(paste("btfp_arb ~ mtm_total + uninsured_lev + run_risk_dummy + +", CONTROLS))
f_wind_rr <- as.formula(paste("btfp_winddown ~ mtm_total + uninsured_lev + run_risk_dummy + +", CONTROLS))

f_dw_acute_rr <- as.formula(paste("dw_acute ~ mtm_total + uninsured_lev + run_risk_dummy  +", CONTROLS))
f_dw_post_rr <- as.formula(paste("dw_post ~ mtm_total + uninsured_lev + run_risk_dummy  +", CONTROLS))
f_dw_arb_rr <- as.formula(paste("dw_arb ~ mtm_total + uninsured_lev + run_risk_dummy  +", CONTROLS))

# Estimate
m8_acute <- feols(f_acute_rr, data = df, vcov = ~idrssd)
m8_post <- feols(f_post_rr, data = df, vcov = ~idrssd)
m8_arb <- feols(f_arb_rr, data = df, vcov = ~idrssd)
m8_wind <- feols(f_wind_rr, data = df, vcov = ~idrssd)
m8_dw_acute <- feols(f_dw_acute_rr, data = df, vcov = ~idrssd)
m8_dw_post <- feols(f_dw_post_rr, data = df, vcov = ~idrssd)
m8_dw_arb <- feols(f_dw_arb_rr, data = df, vcov = ~idrssd)

table8 <- modelsummary(
  list("(1) BTFP Acute" = m8_acute, "(2) BTFP Post" = m8_post, "(3) BTFP Arb" = m8_arb,
       "(4) BTFP Wind" = m8_wind, "(5) DW Acute" = m8_dw_acute, "(6) DW Post" = m8_dw_post,
       "(7) DW Arb" = m8_dw_arb),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 8: Temporal Dynamics with Run Risk Dummy",
  notes = c("Bank-clustered SEs. Run Risk Dummy = 1 if both uninsured share and MTM loss above median.",
            "Acute: Mar 13 - May 1 | Post-Acute: May 2 - Oct 31 | Arbitrage: Nov 1 - Jan 24"),
  output = "kableExtra"
)

table8
Table 8: Temporal Dynamics with Run Risk Dummy
&nbsp;(1) BTFP Acute &nbsp;(2) BTFP Post &nbsp;(3) BTFP Arb &nbsp;(4) BTFP Wind &nbsp;(5) DW Acute &nbsp;(6) DW Post &nbsp;(7) DW Arb
MTM Loss (Total) 0.004 0.004 0.006*** 0.000 0.001 −0.003 0.003
(0.002) (0.003) (0.002) (0.001) (0.002) (0.003) (0.002)
Uninsured Leverage 0.001 0.000 0.000 0.000 0.000 −0.000 −0.000
(0.000) (0.000) (0.000) (0.000) (0.000) (0.001) (0.000)
Run Risk Dummy 0.037** 0.013 −0.012 0.002 0.027** 0.021 −0.010
(0.015) (0.015) (0.010) (0.004) (0.013) (0.016) (0.009)
Log(Assets) 0.034*** 0.011*** 0.016*** −0.001 0.048*** 0.064*** 0.019***
(0.004) (0.004) (0.003) (0.001) (0.004) (0.005) (0.003)
Cash Ratio −0.002*** −0.002*** −0.001* 0.000 0.001 −0.000 0.000
(0.001) (0.001) (0.000) (0.000) (0.001) (0.001) (0.000)
Securities Ratio 0.001 0.002*** −0.000 0.000 0.001 0.000 −0.000
(0.000) (0.001) (0.000) (0.000) (0.000) (0.001) (0.000)
Loan/Deposit −0.001** 0.001 −0.000 0.000 0.000 0.001** 0.000
(0.000) (0.000) (0.000) (0.000) (0.000) (0.000) (0.000)
Book Equity −0.002*** −0.004*** −0.000 −0.000 −0.000 −0.001 −0.001
(0.001) (0.001) (0.001) (0.000) (0.001) (0.001) (0.001)
Wholesale Funding 0.012*** −0.002 −0.001 0.001 0.008*** −0.009*** −0.002
(0.003) (0.003) (0.002) (0.001) (0.003) (0.003) (0.002)
FHLB Ratio 0.007*** −0.002 0.000 −0.000 0.002 −0.001 0.001
(0.001) (0.001) (0.001) (0.000) (0.001) (0.002) (0.001)
ROA −0.000 0.001 −0.013** 0.001 0.004 −0.014 −0.002
(0.007) (0.008) (0.005) (0.002) (0.007) (0.009) (0.005)
Num.Obs. 4678 4678 4678 4678 4678 4678 4678
R2 0.073 0.026 0.016 0.002 0.068 0.063 0.017
* p < 0.1, ** p < 0.05, *** p < 0.01
Bank-clustered SEs. Run Risk Dummy = 1 if both uninsured share and MTM loss above median.
Acute: Mar 13 - May 1 | Post-Acute: May 2 - Oct 31 | Arbitrage: Nov 1 - Jan 24
# Save
modelsummary(
  list("(1) BTFP Acute" = m8_acute, "(2) BTFP Post" = m8_post, "(3) BTFP Arb" = m8_arb,
       "(4) BTFP Wind" = m8_wind, "(5) DW Acute" = m8_dw_acute, "(6) DW Post" = m8_dw_post,
       "(7) DW Arb" = m8_dw_arb),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 8: Temporal Dynamics with Run Risk Dummy",
  output = file.path(TABLE_PATH, "table8_temporal_run_risk.html")
)

8 SECTION 6: Insolvency Specifications for Both BTFP and DW

8.1 6.1 Complete Insolvency Analysis (BTFP and DW, No Book Equity)

# ============================================================================
# TABLE 9: COMPLETE INSOLVENCY ANALYSIS - BTFP AND DW
# ALL Jiang et al. measures: Adjusted Equity, IDCR, and Capital Ratio
# Using CONTROLS_INSOLVENCY (no book equity)
# ============================================================================

# --- BTFP Insolvency Specifications (10 total) ---
# Adjusted Equity measures
f_btfp_adj <- as.formula(paste("btfp ~ adjusted_equity  +", CONTROLS_INSOLVENCY))
f_btfp_mtm_ins <- as.formula(paste("btfp ~ mtm_insolvent  +", CONTROLS_INSOLVENCY))

# IDCR measures
f_btfp_idcr1 <- as.formula(paste("btfp ~ idcr_1  +", CONTROLS_INSOLVENCY))
f_btfp_idcr2 <- as.formula(paste("btfp ~ idcr_2  +", CONTROLS_INSOLVENCY))
f_btfp_insol_idcr50 <- as.formula(paste("btfp ~ insolvent_idcr_s50  +", CONTROLS_INSOLVENCY))
f_btfp_insol_idcr100 <- as.formula(paste("btfp ~ insolvent_idcr_s100  +", CONTROLS_INSOLVENCY))

# Capital Ratio measures (NEW - per feedback)
f_btfp_cap1 <- as.formula(paste("btfp ~ insolvency_1  +", CONTROLS_INSOLVENCY))
f_btfp_cap2 <- as.formula(paste("btfp ~ insolvency_2  +", CONTROLS_INSOLVENCY))
f_btfp_insol_cap50 <- as.formula(paste("btfp ~ insolvent_cap_s50  +", CONTROLS_INSOLVENCY))
f_btfp_insol_cap100 <- as.formula(paste("btfp ~ insolvent_cap_s100 +", CONTROLS_INSOLVENCY))

# --- DW Insolvency Specifications (10 total) ---
# Adjusted Equity measures
f_dw_adj <- as.formula(paste("dw ~ adjusted_equity  +", CONTROLS_INSOLVENCY))
f_dw_mtm_ins <- as.formula(paste("dw ~ mtm_insolvent +", CONTROLS_INSOLVENCY))

# IDCR measures
f_dw_idcr1 <- as.formula(paste("dw ~ idcr_1  +", CONTROLS_INSOLVENCY))
f_dw_idcr2 <- as.formula(paste("dw ~ idcr_2  +", CONTROLS_INSOLVENCY))
f_dw_insol_idcr50 <- as.formula(paste("dw ~ insolvent_idcr_s50  +", CONTROLS_INSOLVENCY))
f_dw_insol_idcr100 <- as.formula(paste("dw ~ insolvent_idcr_s100  +", CONTROLS_INSOLVENCY))

# Capital Ratio measures (NEW - per feedback)
f_dw_cap1 <- as.formula(paste("dw ~ insolvency_1  +", CONTROLS_INSOLVENCY))
f_dw_cap2 <- as.formula(paste("dw ~ insolvency_2  +", CONTROLS_INSOLVENCY))
f_dw_insol_cap50 <- as.formula(paste("dw ~ insolvent_cap_s50 +", CONTROLS_INSOLVENCY))
f_dw_insol_cap100 <- as.formula(paste("dw ~ insolvent_cap_s100 +", CONTROLS_INSOLVENCY))

# --- Estimate all BTFP models ---
m9_btfp_1 <- feols(f_btfp_adj, data = df, vcov = ~idrssd)
m9_btfp_2 <- feols(f_btfp_mtm_ins, data = df, vcov = ~idrssd)
m9_btfp_3 <- feols(f_btfp_idcr1, data = df, vcov = ~idrssd)
m9_btfp_4 <- feols(f_btfp_idcr2, data = df, vcov = ~idrssd)
m9_btfp_5 <- feols(f_btfp_insol_idcr50, data = df, vcov = ~idrssd)
m9_btfp_6 <- feols(f_btfp_insol_idcr100, data = df, vcov = ~idrssd)
m9_btfp_7 <- feols(f_btfp_cap1, data = df, vcov = ~idrssd)
m9_btfp_8 <- feols(f_btfp_cap2, data = df, vcov = ~idrssd)
m9_btfp_9 <- feols(f_btfp_insol_cap50, data = df, vcov = ~idrssd)
m9_btfp_10 <- feols(f_btfp_insol_cap100, data = df, vcov = ~idrssd)

# --- Estimate all DW models ---
m9_dw_1 <- feols(f_dw_adj, data = df, vcov = ~idrssd)
m9_dw_2 <- feols(f_dw_mtm_ins, data = df, vcov = ~idrssd)
m9_dw_3 <- feols(f_dw_idcr1, data = df, vcov = ~idrssd)
m9_dw_4 <- feols(f_dw_idcr2, data = df, vcov = ~idrssd)
m9_dw_5 <- feols(f_dw_insol_idcr50, data = df, vcov = ~idrssd)
m9_dw_6 <- feols(f_dw_insol_idcr100, data = df, vcov = ~idrssd)
m9_dw_7 <- feols(f_dw_cap1, data = df, vcov = ~idrssd)
m9_dw_8 <- feols(f_dw_cap2, data = df, vcov = ~idrssd)
m9_dw_9 <- feols(f_dw_insol_cap50, data = df, vcov = ~idrssd)
m9_dw_10 <- feols(f_dw_insol_cap100, data = df, vcov = ~idrssd)

# --- Panel A: BTFP - All 10 Specifications ---
table9a <- modelsummary(
  list("(1)" = m9_btfp_1, "(2)" = m9_btfp_2, "(3)" = m9_btfp_3, "(4)" = m9_btfp_4,
       "(5)" = m9_btfp_5, "(6)" = m9_btfp_6, "(7)" = m9_btfp_7, "(8)" = m9_btfp_8,
       "(9)" = m9_btfp_9, "(10)" = m9_btfp_10),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 9A: BTFP Insolvency Specifications (All Jiang et al. Measures, No Book Equity)",
  notes = c("Bank-clustered SEs. Book equity excluded per Jiang methodology.",
            "(1) Adj Eq, (2) MTM Insol, (3-4) IDCR continuous, (5-6) IDCR binary,",
            "(7-8) Capital continuous, (9-10) Capital binary"),
  output = "kableExtra"
) %>% 
  kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>%
  scroll_box(width = "100%")

table9a
Table 9A: BTFP Insolvency Specifications (All Jiang et al. Measures, No Book Equity)
&nbsp;(1) &nbsp;&nbsp;(2) &nbsp;&nbsp;(3) &nbsp;&nbsp;(4) &nbsp;&nbsp;(5) &nbsp;&nbsp;(6) &nbsp;&nbsp;(7) &nbsp;&nbsp;(8) &nbsp;&nbsp;(9) &nbsp;&nbsp;(10)
Adjusted Equity −0.010***
(0.001)
MTM Insolvent 0.099***
(0.019)
IDCR (s=0.5) −0.066***
(0.024)
IDCR (s=1.0) −0.156***
(0.032)
Insolvent IDCR (s=0.5) −0.009
(0.031)
Insolvent IDCR (s=1.0) 0.049***
(0.015)
Insolvency Cap (s=0.5) −0.913***
(0.119)
Insolvency Cap (s=1.0) −0.727***
(0.103)
Insolvent Cap (s=1.0) 0.016
(0.025)
Log(Assets) 0.067*** 0.072*** 0.076*** 0.071*** 0.073*** 0.071*** 0.065*** 0.064*** 0.073*** 0.073***
(0.005) (0.005) (0.005) (0.005) (0.005) (0.005) (0.005) (0.005) (0.005) (0.005)
Cash Ratio −0.004*** −0.005*** −0.006*** −0.005*** −0.007*** −0.006*** −0.005*** −0.005*** −0.006*** −0.006***
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Securities Ratio 0.003*** 0.003*** 0.004*** 0.004*** 0.003*** 0.004*** 0.004*** 0.004*** 0.004*** 0.004***
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Loan/Deposit 0.000 0.000 −0.001 −0.000 −0.001 −0.000 0.000 0.000 −0.001 −0.001
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Wholesale Funding 0.009** 0.010** 0.011*** 0.012*** 0.010** 0.011*** 0.009** 0.009** 0.010** 0.010**
(0.004) (0.004) (0.004) (0.004) (0.004) (0.004) (0.004) (0.004) (0.004) (0.004)
FHLB Ratio 0.005** 0.006*** 0.007*** 0.008*** 0.007*** 0.007*** 0.005*** 0.005*** 0.007*** 0.006***
(0.002) (0.002) (0.002) (0.002) (0.002) (0.002) (0.002) (0.002) (0.002) (0.002)
ROA −0.011 −0.023** −0.022** −0.020* −0.026** −0.024** −0.015 −0.015 −0.028*** −0.028***
(0.010) (0.010) (0.011) (0.011) (0.011) (0.011) (0.010) (0.010) (0.010) (0.010)
Num.Obs. 4678 4678 4632 4632 4632 4632 4678 4678 4678 4678
R2 0.113 0.108 0.102 0.104 0.100 0.103 0.110 0.110 0.102 0.102
* p < 0.1, ** p < 0.05, *** p < 0.01
Bank-clustered SEs. Book equity excluded per Jiang methodology.
(1) Adj Eq, (2) MTM Insol, (3-4) IDCR continuous, (5-6) IDCR binary,
(7-8) Capital continuous, (9-10) Capital binary
# --- Panel B: DW - All 10 Specifications ---
table9b <- modelsummary(
  list("(1)" = m9_dw_1, "(2)" = m9_dw_2, "(3)" = m9_dw_3, "(4)" = m9_dw_4,
       "(5)" = m9_dw_5, "(6)" = m9_dw_6, "(7)" = m9_dw_7, "(8)" = m9_dw_8,
       "(9)" = m9_dw_9, "(10)" = m9_dw_10),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 9B: DW Insolvency Specifications (All Jiang et al. Measures, No Book Equity)",
  notes = c("Bank-clustered SEs. Book equity excluded per Jiang methodology.",
            "(1) Adj Eq, (2) MTM Insol, (3-4) IDCR continuous, (5-6) IDCR binary,",
            "(7-8) Capital continuous, (9-10) Capital binary"),
  output = "kableExtra"
) %>% 
  kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>%
  scroll_box(width = "100%")

table9b
Table 9B: DW Insolvency Specifications (All Jiang et al. Measures, No Book Equity)
&nbsp;(1) &nbsp;&nbsp;(2) &nbsp;&nbsp;(3) &nbsp;&nbsp;(4) &nbsp;&nbsp;(5) &nbsp;&nbsp;(6) &nbsp;&nbsp;(7) &nbsp;&nbsp;(8) &nbsp;&nbsp;(9) &nbsp;&nbsp;(10)
Adjusted Equity −0.003**
(0.001)
MTM Insolvent 0.004
(0.017)
IDCR (s=0.5) −0.012
(0.025)
IDCR (s=1.0) −0.039
(0.034)
Insolvent IDCR (s=0.5) −0.020
(0.030)
Insolvent IDCR (s=1.0) 0.021
(0.014)
Insolvency Cap (s=0.5) −0.201
(0.124)
Insolvency Cap (s=1.0) −0.155
(0.107)
Insolvent Cap (s=1.0) −0.017
(0.025)
Log(Assets) 0.132*** 0.134*** 0.136*** 0.135*** 0.135*** 0.135*** 0.132*** 0.132*** 0.134*** 0.134***
(0.005) (0.005) (0.005) (0.005) (0.005) (0.005) (0.005) (0.005) (0.005) (0.005)
Cash Ratio 0.001 0.000 −0.000 0.000 −0.000 0.000 0.001 0.000 0.000 0.000
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Securities Ratio 0.000 0.000 −0.000 0.000 −0.000 0.000 0.001 0.001 0.000 0.000
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Loan/Deposit 0.001* 0.001 0.000 0.000 0.000 0.000 0.001 0.001 0.001 0.001
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Wholesale Funding −0.003 −0.003 −0.002 −0.002 −0.003 −0.002 −0.003 −0.003 −0.003 −0.003
(0.004) (0.004) (0.004) (0.004) (0.004) (0.004) (0.004) (0.004) (0.004) (0.004)
FHLB Ratio 0.001 0.001 0.002 0.002 0.002 0.002 0.001 0.001 0.002 0.002
(0.002) (0.002) (0.002) (0.002) (0.002) (0.002) (0.002) (0.002) (0.002) (0.002)
ROA −0.011 −0.015 −0.015 −0.014 −0.016 −0.015 −0.012 −0.012 −0.015 −0.016
(0.011) (0.010) (0.012) (0.012) (0.011) (0.011) (0.011) (0.011) (0.010) (0.010)
Num.Obs. 4678 4678 4632 4632 4632 4632 4678 4678 4678 4678
R2 0.167 0.167 0.165 0.165 0.165 0.165 0.167 0.167 0.166 0.167
* p < 0.1, ** p < 0.05, *** p < 0.01
Bank-clustered SEs. Book equity excluded per Jiang methodology.
(1) Adj Eq, (2) MTM Insol, (3-4) IDCR continuous, (5-6) IDCR binary,
(7-8) Capital continuous, (9-10) Capital binary
# --- Save all specifications to file ---
modelsummary(
  list("BTFP(1)" = m9_btfp_1, "BTFP(2)" = m9_btfp_2, "BTFP(7)" = m9_btfp_7, 
       "BTFP(9)" = m9_btfp_9, "BTFP(10)" = m9_btfp_10,
       "DW(1)" = m9_dw_1, "DW(2)" = m9_dw_2, "DW(7)" = m9_dw_7,
       "DW(9)" = m9_dw_9, "DW(10)" = m9_dw_10),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 9: Insolvency Analysis Summary (BTFP and DW, Key Specs)",
  output = file.path(TABLE_PATH, "table9_insolvency_complete.html")
)

# Save full BTFP table
modelsummary(
  list("(1)" = m9_btfp_1, "(2)" = m9_btfp_2, "(3)" = m9_btfp_3, "(4)" = m9_btfp_4,
       "(5)" = m9_btfp_5, "(6)" = m9_btfp_6, "(7)" = m9_btfp_7, "(8)" = m9_btfp_8,
       "(9)" = m9_btfp_9, "(10)" = m9_btfp_10),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 9A: BTFP Insolvency (Full)",
  output = file.path(TABLE_PATH, "table9a_btfp_insolvency_full.html")
)

# Save full DW table
modelsummary(
  list("(1)" = m9_dw_1, "(2)" = m9_dw_2, "(3)" = m9_dw_3, "(4)" = m9_dw_4,
       "(5)" = m9_dw_5, "(6)" = m9_dw_6, "(7)" = m9_dw_7, "(8)" = m9_dw_8,
       "(9)" = m9_dw_9, "(10)" = m9_dw_10),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 9B: DW Insolvency (Full)",
  output = file.path(TABLE_PATH, "table9b_dw_insolvency_full.html")
)

9 SECTION 7: FHLB Borrowing Analysis

9.1 7.1 FHLB Borrower Identification

# ============================================================================
# FHLB BORROWER IDENTIFICATION
# Using quarterly change in FHLB advances
# ============================================================================

# Create FHLB borrower indicator
df <- df %>%
  mutate(
    # FHLB borrower = positive change in FHLB advances
    fhlb_borrower = as.integer(!is.na(fhlb_change) & fhlb_change > 0),
    # Size filter: 10-100B (per advisor focus)
    in_size_range = total_asset >= 10e6 & total_asset <= 100e6
  )

# Summary of FHLB borrowers
fhlb_summary <- df %>%
  summarise(
    N_Total = n(),
    N_FHLB_Increase = sum(fhlb_borrower == 1, na.rm = TRUE),
    Pct_FHLB = N_FHLB_Increase / N_Total * 100,
    N_10_100B = sum(in_size_range, na.rm = TRUE),
    N_FHLB_10_100B = sum(fhlb_borrower == 1 & in_size_range, na.rm = TRUE),
    Pct_FHLB_10_100B = N_FHLB_10_100B / N_10_100B * 100
  )

cat("=== FHLB BORROWER SUMMARY ===\n")

=== FHLB BORROWER SUMMARY ===

cat("Total banks:", fhlb_summary$N_Total, "\n")

Total banks: 4696

cat("Banks with FHLB increase:", fhlb_summary$N_FHLB_Increase, 
    sprintf("(%.1f%%)", fhlb_summary$Pct_FHLB), "\n")

Banks with FHLB increase: 956 (20.4%)

cat("Banks in $10-100B range:", fhlb_summary$N_10_100B, "\n")

Banks in $10-100B range: 117

cat("FHLB borrowers in $10-100B:", fhlb_summary$N_FHLB_10_100B,
    sprintf("(%.1f%%)", fhlb_summary$Pct_FHLB_10_100B), "\n")

FHLB borrowers in $10-100B: 65 (55.6%)

# Cross-tab: FHLB and Fed facility usage
fhlb_crosstab <- df %>%
  filter(in_size_range) %>%
  group_by(fhlb_borrower, facility_choice) %>%
  summarise(N = n(), .groups = "drop") %>%
  pivot_wider(names_from = facility_choice, values_from = N, values_fill = 0)

cat("\n=== FHLB BORROWING VS FED FACILITY (10-100B Banks) ===\n")

=== FHLB BORROWING VS FED FACILITY (10-100B Banks) ===

print(fhlb_crosstab)

10 A tibble: 2 × 5

fhlb_borrower Neither BTFP_Only DW_Only Both 1 0 12 2 19 19 2 1 10 8 23 24

10.1 7.2 FHLB Specifications (10-100B Sample)

# ============================================================================
# FHLB BORROWING SPECIFICATIONS
# Focus on 10-100B banks per advisor
# ============================================================================

# Filter to 10-100B banks
df_fhlb <- df %>% filter(in_size_range)

cat("Sample size (10-100B banks):", nrow(df_fhlb), "\n")

Sample size (10-100B banks): 117

cat("FHLB borrowers in sample:", sum(df_fhlb$fhlb_borrower == 1, na.rm = TRUE), "\n")

FHLB borrowers in sample: 65

# Main FHLB specifications
f_fhlb_1 <- as.formula(paste("fhlb_borrower ~ mtm_total +", CONTROLS))
f_fhlb_2 <- as.formula(paste("fhlb_borrower ~ mtm_total + uninsured_lev +", CONTROLS))
f_fhlb_3 <- as.formula(paste("fhlb_borrower ~ mtm_total + uninsured_lev + I(mtm_total * uninsured_lev) +", CONTROLS))
f_fhlb_4 <- as.formula(paste("fhlb_borrower ~ mtm_total + uninsured_lev + run_risk_dummy +", CONTROLS))

# Insolvency specifications for FHLB
f_fhlb_ins <- as.formula(paste("fhlb_borrower ~ adjusted_equity +", CONTROLS_INSOLVENCY))
f_fhlb_insol <- as.formula(paste("fhlb_borrower ~ mtm_insolvent +", CONTROLS_INSOLVENCY))

# Estimate
m_fhlb_1 <- feols(f_fhlb_1, data = df_fhlb, vcov = ~idrssd)
m_fhlb_2 <- feols(f_fhlb_2, data = df_fhlb, vcov = ~idrssd)
m_fhlb_3 <- feols(f_fhlb_3, data = df_fhlb, vcov = ~idrssd)
m_fhlb_4 <- feols(f_fhlb_4, data = df_fhlb, vcov = ~idrssd)
m_fhlb_5 <- feols(f_fhlb_ins, data = df_fhlb, vcov = ~idrssd)
m_fhlb_6 <- feols(f_fhlb_insol, data = df_fhlb, vcov = ~idrssd)

table_fhlb <- modelsummary(
  list("(1) MTM Only" = m_fhlb_1, "(2) + Uninsured" = m_fhlb_2, 
       "(3) + Interaction" = m_fhlb_3, "(4) + Run Risk" = m_fhlb_4,
       "(5) Adj Equity" = m_fhlb_5, "(6) Insolvency" = m_fhlb_6),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table: FHLB Borrowing Analysis (Banks $10-100B)",
  notes = c("Bank-clustered SEs. Sample: Banks with total assets $10-100B.",
            "FHLB borrower = 1 if positive change in FHLB advances."),
  output = "kableExtra"
)

table_fhlb
Table: FHLB Borrowing Analysis (Banks $10-100B)
&nbsp;(1) MTM Only &nbsp;(2) + Uninsured &nbsp;(3) + Interaction &nbsp;(4) + Run Risk &nbsp;(5) Adj Equity &nbsp;(6) Insolvency
Adjusted Equity 0.008
(0.014)
MTM Insolvent 0.138
(0.189)
MTM Loss (Total) 0.022 0.024 0.084** 0.005
(0.027) (0.027) (0.036) (0.034)
Uninsured Leverage 0.003 0.012* 0.003
(0.003) (0.007) (0.003)
Run Risk Dummy 0.105
(0.116)
MTM × Uninsured −0.002
(0.001)
Log(Assets) −0.752 −0.646 −0.858 −0.609 −0.899 −1.000
(0.765) (0.809) (0.781) (0.831) (0.756) (0.783)
Cash Ratio −0.033** −0.030* −0.032** −0.029* −0.036** −0.037**
(0.015) (0.015) (0.015) (0.015) (0.015) (0.015)
Securities Ratio −0.020 −0.017 −0.018 −0.016 −0.017 −0.020
(0.013) (0.013) (0.013) (0.014) (0.013) (0.012)
Loan/Deposit −0.011 −0.009 −0.009 −0.009 −0.009 −0.009
(0.009) (0.009) (0.009) (0.009) (0.009) (0.008)
Book Equity 0.019 0.020 0.020 0.020
(0.018) (0.017) (0.017) (0.017)
Wholesale Funding −0.033 −0.035 −0.029 −0.038* −0.035 −0.033
(0.022) (0.022) (0.024) (0.023) (0.022) (0.022)
FHLB Ratio 0.031*** 0.031*** 0.031*** 0.030*** 0.029** 0.029***
(0.011) (0.011) (0.011) (0.011) (0.011) (0.011)
ROA −0.138 −0.124 −0.104 −0.141 −0.137 −0.106
(0.114) (0.114) (0.116) (0.117) (0.117) (0.115)
Num.Obs. 117 117 117 117 117 117
R2 0.257 0.264 0.275 0.269 0.246 0.248
* p < 0.1, ** p < 0.05, *** p < 0.01
Bank-clustered SEs. Sample: Banks with total assets $10-100B.
FHLB borrower = 1 if positive change in FHLB advances.
# Save
modelsummary(
  list("(1) MTM Only" = m_fhlb_1, "(2) + Uninsured" = m_fhlb_2, 
       "(3) + Interaction" = m_fhlb_3, "(4) + Run Risk" = m_fhlb_4,
       "(5) Adj Equity" = m_fhlb_5, "(6) Insolvency" = m_fhlb_6),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table: FHLB Borrowing Analysis",
  
  output = file.path(TABLE_PATH, "table_fhlb_analysis.html")
)

10.2 7.3 FHLB vs Fed Facility Comparison

# ============================================================================
# FHLB VS FED FACILITY COMPARISON
# ============================================================================

# Compare FHLB borrowers vs Fed borrowers (10-100B)
fhlb_fed_comparison <- df_fhlb %>%
  mutate(
    borrowing_type = case_when(
      fhlb_borrower == 1 & any_fed == 1 ~ "Both FHLB & Fed",
      fhlb_borrower == 1 ~ "FHLB Only",
      any_fed == 1 ~ "Fed Only",
      TRUE ~ "Neither"
    )
  ) %>%
  group_by(borrowing_type) %>%
  summarise(
    N = n(),
    Mean_MTM = mean(mtm_total, na.rm = TRUE),
    Mean_Uninsured = mean(uninsured_lev, na.rm = TRUE),
    Mean_AdjEq = mean(adjusted_equity, na.rm = TRUE),
    Pct_Insolvent = mean(mtm_insolvent, na.rm = TRUE) * 100,
    Mean_IDCR      = mean(idcr_2, na.rm = TRUE),
    Pct_IDCR_Insol = mean(insolvent_idcr_s100, na.rm = TRUE) * 100,
    Mean_Insolv_cap    = mean(insolvency_2, na.rm = TRUE),
    Pct_Cap_Insol  = mean(insolvent_cap_s100, na.rm = TRUE) * 100,
    Mean_FHLB_Change = mean(fhlb_change, na.rm = TRUE),
    .groups = "drop"
  )


fhlb_fed_comparison %>%
  kable(caption = "Table: FHLB vs Fed Borrowers (Banks $10-100B)",
        digits = 2,
        col.names = c("Borrowing Type", "N", "Mean MTM", "Mean Unins", 
                      "Mean Adj Eq", "% Insolvent mtm", "Mean IDCR", 
                      "% Insolvent IDCR", "Mean Cap", "% Insolvent Cap", "Mean FHLB Chg")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE) %>%
  footnote(general = "Insolvency measures calculated following Jiang et al. (2023) using s=1.0 (100% run scenario).")
Table: FHLB vs Fed Borrowers (Banks $10-100B)
Borrowing Type N Mean MTM Mean Unins Mean Adj Eq % Insolvent mtm Mean IDCR % Insolvent IDCR Mean Cap % Insolvent Cap Mean FHLB Chg
Both FHLB & Fed 55 5.19 36.80 5.45 5.45 0.13 27.27 0.05 23.64 125.29
FHLB Only 10 4.96 32.34 5.68 10.00 0.23 20.00 0.06 10.00 138.59
Fed Only 40 4.63 31.27 5.49 7.50 0.19 20.00 0.07 5.00 -35.51
Neither 12 5.37 35.77 3.67 8.33 0.23 25.00 0.05 16.67 -4.92
Note:
Insolvency measures calculated following Jiang et al. (2023) using s=1.0 (100% run scenario).
# ============================================================================
# FHLB VS FED FACILITY COMPARISON
# ============================================================================

# Compare FHLB borrowers vs Fed borrowers (10-100B)
fhlb_fed_comparison <- df %>%
  mutate(
    borrowing_type = case_when(
      fhlb_borrower == 1 & any_fed == 1 ~ "Both FHLB & Fed",
      fhlb_borrower == 1 ~ "FHLB Only",
      any_fed == 1 ~ "Fed Only",
      TRUE ~ "Neither"
    )
  ) %>%
  group_by(borrowing_type) %>%
  summarise(
    N = n(),
    Mean_MTM = mean(mtm_total, na.rm = TRUE),
    Mean_Uninsured = mean(uninsured_lev, na.rm = TRUE),
    Mean_AdjEq = mean(adjusted_equity, na.rm = TRUE),
    Pct_Insolvent = mean(mtm_insolvent, na.rm = TRUE) * 100,
    Mean_IDCR      = mean(idcr_2, na.rm = TRUE),
    Pct_IDCR_Insol = mean(insolvent_idcr_s100, na.rm = TRUE) * 100,
    Mean_Insolv_cap    = mean(insolvency_2, na.rm = TRUE),
    Pct_Cap_Insol  = mean(insolvent_cap_s100, na.rm = TRUE) * 100,
    Mean_FHLB_Change = mean(fhlb_change, na.rm = TRUE),
    .groups = "drop"
  )


fhlb_fed_comparison %>%
  kable(caption = "Table: FHLB vs Fed Borrowers (all)",
        digits = 2,
        col.names = c("Borrowing Type", "N", "Mean MTM", "Mean Unins", 
                      "Mean Adj Eq", "% Insolvent mtm", "Mean IDCR", 
                      "% Insolvent IDCR", "Mean Cap", "% Insolvent Cap", "Mean FHLB Chg")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE) %>%
  footnote(general = "Insolvency measures calculated following Jiang et al. (2023) using s=1.0 (100% run scenario).")
Table: FHLB vs Fed Borrowers (all)
Borrowing Type N Mean MTM Mean Unins Mean Adj Eq % Insolvent mtm Mean IDCR % Insolvent IDCR Mean Cap % Insolvent Cap Mean FHLB Chg
Both FHLB & Fed 541 6.06 26.09 3.24 21.26 0.08 30.68 0.06 12.38 105.86
FHLB Only 415 5.75 21.24 4.25 15.18 0.12 25.54 0.07 7.71 94.69
Fed Only 1590 5.64 25.29 3.24 22.45 0.07 31.19 0.06 8.30 -35.56
Neither 2150 5.00 21.08 5.48 15.43 0.10 25.46 0.08 6.10 -31.04
Note:
Insolvency measures calculated following Jiang et al. (2023) using s=1.0 (100% run scenario).
# ============================================================================
# FHLB BORROWING SPECIFICATIONS
# Focus on 10-100B banks per advisor
# ============================================================================

# all FHLB User


cat("Sample size (All Banks):", nrow(df), "\n")

Sample size (All Banks): 4696

cat("FHLB borrowers in sample:", sum(df$fhlb_borrower == 1, na.rm = TRUE), "\n")

FHLB borrowers in sample: 956

# 1. Baseline Specifications
f_fhlb_1 <- as.formula(paste("fhlb_borrower ~ mtm_total +", CONTROLS))
f_fhlb_2 <- as.formula(paste("fhlb_borrower ~ mtm_total + uninsured_lev +", CONTROLS))
f_fhlb_3 <- as.formula(paste("fhlb_borrower ~ mtm_total + uninsured_lev + I(mtm_total * uninsured_lev) +", CONTROLS))
f_fhlb_4 <- as.formula(paste("fhlb_borrower ~ mtm_total + uninsured_lev + run_risk_dummy +", CONTROLS))

# 2. Expanded Insolvency Specifications (Jiang et al. measures)
f_fhlb_5 <- as.formula(paste("fhlb_borrower ~ adjusted_equity +", CONTROLS_INSOLVENCY))
f_fhlb_6 <- as.formula(paste("fhlb_borrower ~ mtm_insolvent +", CONTROLS_INSOLVENCY))
f_fhlb_7 <- as.formula(paste("fhlb_borrower ~ idcr_2 +", CONTROLS_INSOLVENCY))
f_fhlb_8 <- as.formula(paste("fhlb_borrower ~ insolvent_idcr_s100 +", CONTROLS_INSOLVENCY))
f_fhlb_9 <- as.formula(paste("fhlb_borrower ~ insolvency_2 +", CONTROLS_INSOLVENCY))
f_fhlb_10 <- as.formula(paste("fhlb_borrower ~ insolvent_cap_s100 +", CONTROLS_INSOLVENCY))

# 3. Estimate All Models
# Using cluster = ~idrssd as preferred in fixest
m_fhlb_1  <- feols(f_fhlb_1,  data = df, cluster = ~idrssd)
m_fhlb_2  <- feols(f_fhlb_2,  data = df, cluster = ~idrssd)
m_fhlb_3  <- feols(f_fhlb_3,  data = df, cluster = ~idrssd)
m_fhlb_4  <- feols(f_fhlb_4,  data = df, cluster = ~idrssd)
m_fhlb_5  <- feols(f_fhlb_5,  data = df, cluster = ~idrssd)
m_fhlb_6  <- feols(f_fhlb_6,  data = df, cluster = ~idrssd)
m_fhlb_7  <- feols(f_fhlb_7,  data = df, cluster = ~idrssd)
m_fhlb_8  <- feols(f_fhlb_8,  data = df, cluster = ~idrssd)
m_fhlb_9  <- feols(f_fhlb_9,  data = df, cluster = ~idrssd)
m_fhlb_10 <- feols(f_fhlb_10, data = df, cluster = ~idrssd)

# 4. Create Model List
fhlb_full_list <- list(
  "(1) MTM"      = m_fhlb_1, 
  "(2) Unins"    = m_fhlb_2, 
  "(3) Int"      = m_fhlb_3, 
  "(4) RunR"     = m_fhlb_4,
  "(5) AdjEq"    = m_fhlb_5, 
  "(6) MTM Ins"  = m_fhlb_6,
  "(7) IDCR"     = m_fhlb_7, 
  "(8) IDCR Ins" = m_fhlb_8,
  "(9) Cap"      = m_fhlb_9, 
  "(10) Cap Ins" = m_fhlb_10
)

# 5. Generate and Display Table
table_fhlb_all <- modelsummary(
  fhlb_full_list,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table: FHLB Borrowing and Comprehensive Insolvency Analysis",
  notes = c("Bank-clustered SEs.",
            "Insolvency indicators following Jiang et al. (2023) at s=1.0.",
            "FHLB borrower = 1 if positive change in FHLB advances."),
  output = "kableExtra"
) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>%
  scroll_box(width = "100%")

table_fhlb_all
Table: FHLB Borrowing and Comprehensive Insolvency Analysis
&nbsp;(1) MTM &nbsp;(2) Unins &nbsp;(3) Int &nbsp;(4) RunR &nbsp;(5) AdjEq &nbsp;(6) MTM Ins &nbsp;(7) IDCR &nbsp;(8) IDCR Ins &nbsp;(9) Cap &nbsp;(10) Cap Ins
Adjusted Equity 0.000
(0.001)
MTM Insolvent 0.019
(0.016)
IDCR (s=1.0) 0.030
(0.030)
Insolvent IDCR (s=1.0) 0.004
(0.012)
Insolvency Cap (s=1.0) 0.235**
(0.092)
Insolvent Cap (s=1.0) 0.007
(0.023)
MTM Loss (Total) 0.012*** 0.012*** 0.012** 0.012***
(0.003) (0.003) (0.005) (0.003)
Uninsured Leverage 0.000 0.000 −0.000
(0.001) (0.001) (0.001)
Run Risk Dummy 0.008
(0.017)
MTM × Uninsured 0.000
(0.000)
Log(Assets) 0.053*** 0.053*** 0.053*** 0.053*** 0.051*** 0.051*** 0.053*** 0.053*** 0.054*** 0.051***
(0.005) (0.005) (0.005) (0.005) (0.005) (0.005) (0.005) (0.005) (0.005) (0.005)
Cash Ratio −0.006*** −0.006*** −0.006*** −0.006*** −0.007*** −0.006*** −0.007*** −0.006*** −0.007*** −0.006***
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Securities Ratio −0.004*** −0.004*** −0.004*** −0.004*** −0.003*** −0.003*** −0.003*** −0.003*** −0.003*** −0.003***
(0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001) (0.001)
Loan/Deposit −0.000 −0.000 −0.000 −0.000 0.000 0.000 0.000 0.000 −0.000 0.000
(0.000) (0.000) (0.000) (0.000) (0.000) (0.000) (0.001) (0.001) (0.000) (0.000)
Book Equity 0.004*** 0.004*** 0.004*** 0.004***
(0.001) (0.001) (0.001) (0.001)
Wholesale Funding −0.000 −0.000 −0.000 −0.000 −0.002 −0.002 −0.002 −0.002 −0.002 −0.002
(0.003) (0.003) (0.003) (0.003) (0.003) (0.003) (0.003) (0.003) (0.003) (0.003)
FHLB Ratio 0.024*** 0.024*** 0.024*** 0.024*** 0.024*** 0.024*** 0.023*** 0.023*** 0.024*** 0.024***
(0.002) (0.002) (0.002) (0.002) (0.002) (0.002) (0.002) (0.002) (0.002) (0.002)
ROA −0.056*** −0.056*** −0.056*** −0.056*** −0.058*** −0.057*** −0.067*** −0.066*** −0.062*** −0.057***
(0.009) (0.009) (0.009) (0.009) (0.009) (0.009) (0.010) (0.010) (0.009) (0.009)
Num.Obs. 4678 4678 4678 4678 4678 4678 4632 4632 4678 4678
R2 0.174 0.174 0.174 0.174 0.170 0.170 0.170 0.170 0.171 0.170
* p < 0.1, ** p < 0.05, *** p < 0.01
Bank-clustered SEs.
Insolvency indicators following Jiang et al. (2023) at s=1.0.
FHLB borrower = 1 if positive change in FHLB advances.
# 6. Save to HTML
modelsummary(
  fhlb_full_list,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table: FHLB Borrowing Analysis (Full Set)",
  output = file.path(TABLE_PATH, "table_fhlb_analysis_all.html")
)

11 SECTION 8: Eligible Securities Check (DW vs BTFP)

Advisor Comment: “I suspect that all banks that borrowed at the DW had eligible securities. Just check if there are differences across the DW and BTFP samples.”

# ============================================================================
# ELIGIBLE SECURITIES COMPARISON: DW VS BTFP BORROWERS
# ============================================================================

eligible_comparison <- df %>%
  filter(any_fed == 1) %>%
  group_by(facility_choice) %>%
  summarise(
    N = n(),
    Mean_OMO_Ratio = mean(omo_ratio, na.rm = TRUE),
    Mean_NonOMO_Ratio = mean(non_omo_ratio, na.rm = TRUE),
    Pct_Has_OMO = mean(omo_ratio > 0, na.rm = TRUE) * 100,
    Mean_OMO_If_Has = mean(ifelse(omo_ratio > 0, omo_ratio, NA), na.rm = TRUE),
    .groups = "drop"
  )

eligible_comparison %>%
  kable(caption = "Table: Eligible Securities by Facility Choice",
        digits = 2,
        col.names = c("Facility", "N", "Mean OMO Ratio", "Mean Non-OMO Ratio",
                      "% With OMO > 0", "Mean OMO (if > 0)")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  footnote(general = "OMO = BTFP-eligible (Open Market Operations eligible). All Fed borrowers shown.")
Table: Eligible Securities by Facility Choice
Facility N Mean OMO Ratio Mean Non-OMO Ratio % With OMO > 0 Mean OMO (if > 0)
BTFP_Only 754 10.85 77.70 95.76 11.33
DW_Only 826 8.00 78.25 92.13 8.68
Both 551 9.96 78.93 97.46 10.22
Note:
OMO = BTFP-eligible (Open Market Operations eligible). All Fed borrowers shown.
# T-test for OMO ratio differences
cat("\n=== T-TEST: OMO Ratio - BTFP vs DW Users ===\n")

=== T-TEST: OMO Ratio - BTFP vs DW Users ===

btfp_users <- df %>% filter(btfp == 1) %>% pull(omo_ratio)
dw_users <- df %>% filter(dw == 1) %>% pull(omo_ratio)
t.test(btfp_users, dw_users)
Welch Two Sample t-test

data: btfp_users and dw_users t = 5, df = 2628, p-value = 0.0000006 alternative hypothesis: true difference in means is not equal to 0 95 percent confidence interval: 1.029 2.355 sample estimates: mean of x mean of y 10.47 8.78

12 SECTION 9: Pre-BTFP DW Placebo Test

# ============================================================================
# TABLE 7: PRE-BTFP DW PLACEBO TEST
# ============================================================================

f_pre_1 <- as.formula(paste("dw_pre ~ mtm_total +", CONTROLS))
f_pre_2 <- as.formula(paste("dw_pre ~ mtm_total + uninsured_lev +", CONTROLS))
f_pre_3 <- as.formula(paste("dw_pre ~ mtm_total + uninsured_lev + I(mtm_total * uninsured_lev) +", CONTROLS))

m7_1 <- feols(f_pre_1, data = df, vcov = ~idrssd)
m7_2 <- feols(f_pre_2, data = df, vcov = ~idrssd)
m7_3 <- feols(f_pre_3, data = df, vcov = ~idrssd)

table7 <- modelsummary(
  list("(1) DW Pre" = m7_1, "(2) DW Pre" = m7_2, "(3) DW Pre" = m7_3),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 7: Pre-BTFP DW Usage (Placebo Test)",
  notes = "Bank-clustered SEs. Period: Mar 1-10, 2023 (before BTFP announcement).",
  output = "kableExtra"
)

table7
Table 7: Pre-BTFP DW Usage (Placebo Test)
&nbsp;(1) DW Pre &nbsp;(2) DW Pre &nbsp;(3) DW Pre
MTM Loss (Total) 0.000 0.000 −0.001
(0.001) (0.001) (0.002)
Uninsured Leverage 0.000 −0.000
(0.000) (0.000)
MTM × Uninsured 0.000
(0.000)
Log(Assets) 0.016*** 0.016*** 0.016***
(0.002) (0.003) (0.003)
Cash Ratio −0.001*** −0.001*** −0.001***
(0.000) (0.000) (0.000)
Securities Ratio −0.000 −0.000 −0.000
(0.000) (0.000) (0.000)
Loan/Deposit −0.000** −0.000** −0.000**
(0.000) (0.000) (0.000)
Book Equity 0.000 0.000 0.000
(0.000) (0.000) (0.000)
Wholesale Funding 0.007*** 0.007*** 0.007***
(0.002) (0.002) (0.002)
FHLB Ratio 0.001 0.001 0.001
(0.001) (0.001) (0.001)
ROA −0.005 −0.005 −0.005
(0.003) (0.003) (0.003)
Num.Obs. 4678 4678 4678
R2 0.032 0.032 0.032
* p < 0.1, ** p < 0.05, *** p < 0.01
Bank-clustered SEs. Period: Mar 1-10, 2023 (before BTFP announcement).
modelsummary(
  list("(1) DW Pre" = m7_1, "(2) DW Pre" = m7_2, "(3) DW Pre" = m7_3),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 7: Pre-BTFP Placebo",
  output = file.path(TABLE_PATH, "table7_placebo.html")
)

13 SECTION 10: Summary Statistics

# ============================================================================
# TABLE 1: FULL SAMPLE SUMMARY STATISTICS
# ============================================================================

summary_vars <- c(# --- Size & Core Controls ---
  "ln_assets", "total_asset", "cash_ratio", "securities_ratio", 
  "loan_ratio", "book_equity_ratio", "tier1_ratio", "roa", 
  "loan_to_deposit", "wholesale", "fhlb_ratio",

  # --- Exposure & Risk ---
  "mtm_total", "mtm_btfp", "mtm_other", "uninsured_lev", 
  "uninsured_share", "omo_ratio", "non_omo_ratio", 
  "run_risk", "run_risk_1_dummy", "borrowing_subsidy",

  # --- Jiang et al. (2023) Continuous Measures ---
  "adjusted_equity", "mv_adjustment", 
  "idcr_1", "idcr_2",               # Insured Deposit Coverage Ratio (s=0.5, 1.0)
  "insolvency_1", "insolvency_2",   # Capital-based Insolvency (s=0.5, 1.0)

  # --- Insolvency Binary Indicators (Dummies) ---
  "mtm_insolvent",                  # Adjusted Equity < 0
  "insolvent_idcr_s50",             # IDCR < 0 (s=0.5)
  "insolvent_idcr_s100",            # IDCR < 0 (s=1.0)
  "insolvent_cap_s50",              # Capital Insolvency (s=0.5)
  "insolvent_cap_s100",             # Capital Insolvency (s=1.0)

  # --- Borrowing & Facility Usage (LOLR) ---
  "dw_pre", "dw_acute", "btfp_acute", "fhlb_borrower", "fhlb_change",
  "btfp_pct", "dw_pct", "btfp_util")

compute_summary <- function(data, vars) {
  data %>%
    select(any_of(vars)) %>%
    pivot_longer(everything(), names_to = "variable", values_to = "value") %>%
    group_by(variable) %>%
    summarise(
      N = sum(!is.na(value)),
      Mean = mean(value, na.rm = TRUE),
      SD = sd(value, na.rm = TRUE),
      Min = min(value, na.rm = TRUE),
      P25 = quantile(value, 0.25, na.rm = TRUE),
      Median = median(value, na.rm = TRUE),
      P75 = quantile(value, 0.75, na.rm = TRUE),
      Max = max(value, na.rm = TRUE),
      .groups = "drop"
    )
}

summary_table <- compute_summary(df, summary_vars) %>%
  mutate(variable = factor(variable, levels = summary_vars)) %>%
  arrange(variable)

summary_table %>%
  kable(caption = "Table 1: Summary Statistics (Excluding Failed Banks & G-SIBs)",
        digits = 3,
        col.names = c("Variable", "N", "Mean", "SD", "Mean","P25", "Median", "P75", "Max")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), 
                full_width = FALSE, font_size = 11)
Table 1: Summary Statistics (Excluding Failed Banks & G-SIBs)
Variable N Mean SD Mean P25 Median P75 Max
ln_assets 4696 12.799 1.368 10.315 11.850 12.669 13.583 16.337
total_asset 4696 2423811.905 19465948.093 3043.000 140102.000 317794.000 792591.750 585135605.000
cash_ratio 4696 8.336 8.522 1.089 2.696 5.185 10.488 39.484
securities_ratio 4696 24.259 15.541 0.051 12.163 22.266 34.347 61.494
loan_ratio 4696 60.538 17.593 13.188 49.821 62.840 74.564 86.639
book_equity_ratio 4696 9.933 4.683 3.392 7.236 9.003 11.182 28.060
tier1_ratio 4696 11.488 4.122 7.382 9.114 10.264 12.179 28.257
roa 4696 1.038 0.576 -0.306 0.694 1.015 1.337 2.697
loan_to_deposit 4696 71.297 22.621 15.031 56.972 72.694 87.894 113.061
wholesale 4696 0.785 1.790 0.000 0.000 0.000 0.437 7.927
fhlb_ratio 4696 2.586 3.800 0.000 0.000 0.306 4.121 14.705
mtm_total 4678 5.408 2.183 1.112 3.804 5.272 6.961 9.961
mtm_btfp 4678 0.598 0.696 0.000 0.070 0.341 0.866 2.822
mtm_other 4678 4.579 2.039 0.781 3.079 4.382 5.974 9.159
uninsured_lev 4696 23.094 11.577 2.585 14.805 21.932 30.165 52.083
uninsured_share 4696 27.234 13.632 4.445 17.478 25.607 34.981 62.834
omo_ratio 4696 9.578 9.426 0.000 2.270 6.733 14.079 37.974
non_omo_ratio 4696 75.439 14.220 32.416 69.048 79.273 85.953 92.783
run_risk 4678 142.299 84.437 4.945 79.133 129.233 189.949 625.865
borrowing_subsidy 4282 6.876 4.724 0.000 3.205 6.537 9.849 18.801
adjusted_equity 4678 4.349 5.238 -4.552 0.972 3.808 6.729 21.135
mv_adjustment 4678 0.107 0.113 -0.077 0.036 0.088 0.152 0.480
idcr_1 4632 0.311 0.268 -0.067 0.148 0.248 0.392 1.293
idcr_2 4632 0.089 0.196 -0.370 -0.012 0.072 0.165 0.701
insolvency_1 4678 0.084 0.049 0.002 0.055 0.077 0.102 0.253
insolvency_2 4678 0.072 0.059 -0.062 0.039 0.067 0.097 0.258
mtm_insolvent 4678 0.185 0.388 0.000 0.000 0.000 0.000 1.000
insolvent_idcr_s50 4632 0.044 0.205 0.000 0.000 0.000 0.000 1.000
insolvent_idcr_s100 4632 0.280 0.449 0.000 0.000 0.000 1.000 1.000
insolvent_cap_s50 4678 0.000 0.000 0.000 0.000 0.000 0.000 0.000
insolvent_cap_s100 4678 0.077 0.267 0.000 0.000 0.000 0.000 1.000
dw_pre 4696 0.022 0.148 0.000 0.000 0.000 0.000 1.000
dw_acute 4696 0.088 0.283 0.000 0.000 0.000 0.000 1.000
btfp_acute 4696 0.102 0.302 0.000 0.000 0.000 0.000 1.000
fhlb_borrower 4696 0.204 0.403 0.000 0.000 0.000 0.000 1.000
fhlb_change 2469 18.634 104.880 -100.000 -30.508 0.000 30.368 437.150
btfp_pct 1305 12.997 19.667 0.000 1.936 6.155 15.582 157.646
dw_pct 1377 8.880 52.376 0.000 0.000 0.002 0.330 970.051
btfp_util 1259 9.102 91.658 0.000 0.216 0.789 2.259 1989.247

14 SECTION 11: Intensive Margin Analysis

# ============================================================================
# TABLE 10: INTENSIVE MARGIN WITH IPW SELECTION CORRECTION
# ============================================================================

# Create complete cases for propensity score
ps_vars <- c("btfp", "mtm_total", "uninsured_lev", "omo_ratio",
             "ln_assets", "cash_ratio", "securities_ratio", "loan_to_deposit",
             "book_equity_ratio", "wholesale", "fhlb_ratio", "roa", "idrssd",
             "btfp_pct", "btfp_util", "borrowing_subsidy")

df_ps <- df %>%
  select(any_of(ps_vars)) %>%
  drop_na()

cat("Complete cases for PS estimation:", nrow(df_ps), "\n")

Complete cases for PS estimation: 1259

# Propensity score model
ps_formula <- as.formula(paste("btfp ~ mtm_total + uninsured_lev + omo_ratio +", CONTROLS))
ps_model <- glm(ps_formula, data = df_ps, family = binomial(link = "logit"))
df_ps$ps_btfp <- predict(ps_model, type = "response")

# Calculate IPW
df_ps <- df_ps %>%
  mutate(
    ipw = ifelse(btfp == 1, 1 / ps_btfp, NA_real_),
    ipw_stab = ifelse(btfp == 1, mean(btfp) / ps_btfp, NA_real_)
  )

# Trim extreme weights
df_ps <- df_ps %>%
  mutate(
    ipw_trim = ifelse(!is.na(ipw), 
                      pmin(pmax(ipw, quantile(ipw[btfp==1], 0.01, na.rm=TRUE)),
                           quantile(ipw[btfp==1], 0.99, na.rm=TRUE)), NA_real_)
  )

# BTFP users subset
df_btfp <- df_ps %>% filter(btfp == 1, !is.na(btfp_pct))
cat("BTFP users for intensive margin:", nrow(df_btfp), "\n")

BTFP users for intensive margin: 1259

# Intensive margin models
f_int <- as.formula(paste("btfp_pct ~ mtm_total + uninsured_lev + omo_ratio +", CONTROLS))

m10_naive <- feols(f_int, data = df_btfp, vcov = ~idrssd)
m10_ipw <- feols(f_int, data = df_btfp, weights = ~ipw_trim, vcov = ~idrssd)

# Utilization models
f_util <- as.formula(paste("btfp_util ~ borrowing_subsidy + uninsured_lev + omo_ratio +", CONTROLS))
m10_util <- feols(f_util, data = df_btfp, vcov = ~idrssd)

table10 <- modelsummary(
  list("(1) Naive OLS" = m10_naive, "(2) IPW" = m10_ipw, "(3) Utilization" = m10_util),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 10: Intensive Margin Analysis",
  notes = "Sample: BTFP borrowers only. Bank-clustered SEs.",
  output = "kableExtra"
)

table10
Table 10: Intensive Margin Analysis
&nbsp;(1) Naive OLS &nbsp;(2) IPW &nbsp;(3) Utilization
MTM Loss (Total) −0.496 −0.496
(0.396) (0.396)
Uninsured Leverage 0.103 0.103 −0.287
(0.080) (0.080) (0.268)
Borrowing Subsidy 0.904
(0.745)
BTFP-Eligible Ratio 0.135 0.135 −1.020***
(0.092) (0.092) (0.292)
Log(Assets) 0.063 0.063 0.777
(0.628) (0.628) (2.334)
Cash Ratio −0.521** −0.521** −0.177
(0.215) (0.215) (0.327)
Securities Ratio −0.105 −0.105 0.186
(0.233) (0.233) (0.598)
Loan/Deposit −0.358* −0.358* 0.049
(0.213) (0.213) (0.413)
Book Equity 0.726** 0.726** −0.470
(0.321) (0.321) (0.502)
Wholesale Funding 0.751* 0.751* −0.676
(0.397) (0.397) (1.328)
FHLB Ratio 1.081*** 1.081*** 1.153
(0.335) (0.335) (0.775)
ROA 0.183 0.183 −0.450
(1.521) (1.521) (5.709)
Num.Obs. 1259 1259 1259
R2 0.071 0.071 0.019
* p < 0.1, ** p < 0.05, *** p < 0.01
Sample: BTFP borrowers only. Bank-clustered SEs.

15 SECTION 12: Both-Facility Mechanisms

# ============================================================================
# TABLE 11: WHY USE BOTH FACILITIES?
# ============================================================================

df_fed <- df %>% filter(any_fed == 1)

# Add maxed_out indicator if not present
if(!"maxed_out" %in% names(df_fed)) {
  df_fed <- df_fed %>%
    mutate(maxed_out = as.integer(!is.na(btfp_util) & btfp_util > 0.90))
}

f_maxout <- as.formula(paste("both ~ maxed_out + mtm_total + uninsured_lev +", CONTROLS))
f_oper <- as.formula(paste("both ~ prior_dw + mtm_total + uninsured_lev +", CONTROLS))
f_coll <- as.formula(paste("both ~ non_omo_ratio + omo_ratio + mtm_total + uninsured_lev +", CONTROLS))
f_full <- as.formula(paste("both ~ maxed_out + prior_dw + non_omo_ratio + mtm_total + uninsured_lev +", CONTROLS))

m11_1 <- feols(f_maxout, data = df_fed, vcov = ~idrssd)
m11_2 <- feols(f_oper, data = df_fed, vcov = ~idrssd)
m11_3 <- feols(f_coll, data = df_fed, vcov = ~idrssd)
m11_4 <- feols(f_full, data = df_fed, vcov = ~idrssd)

table11 <- modelsummary(
  list("(1) Max-Out" = m11_1, "(2) Operational" = m11_2, 
       "(3) Collateral" = m11_3, "(4) Combined" = m11_4),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 11: Why Use Both Facilities?",
  notes = c("Sample: Fed borrowers. Bank-clustered SEs.",
            "Max-Out: BTFP utilization > 90%. Prior DW: Used DW before BTFP."),
  output = "kableExtra"
)

table11
Table 11: Why Use Both Facilities?
&nbsp;(1) Max-Out &nbsp;(2) Operational &nbsp;(3) Collateral &nbsp;(4) Combined
MTM Loss (Total) 0.005 0.009 0.009* 0.008
(0.005) (0.005) (0.005) (0.005)
Uninsured Leverage 0.003*** 0.003*** 0.003*** 0.003***
(0.001) (0.001) (0.001) (0.001)
Prior DW User 0.200*** 0.185***
(0.051) (0.047)
BTFP-Eligible Ratio −0.000
(0.004)
DW-Only Eligible Ratio −0.001 −0.003**
(0.004) (0.001)
Maxed Out (&gt;90%) 0.205*** 0.211***
(0.022) (0.022)
Log(Assets) 0.065*** 0.057*** 0.059*** 0.060***
(0.008) (0.008) (0.008) (0.008)
Cash Ratio −0.004 −0.005** −0.006** −0.005*
(0.002) (0.002) (0.003) (0.002)
Securities Ratio −0.001 −0.001 −0.001 −0.000
(0.002) (0.002) (0.003) (0.002)
Loan/Deposit −0.002 −0.002 −0.002 −0.001
(0.002) (0.002) (0.003) (0.002)
Book Equity −0.004 −0.003 −0.004 −0.005*
(0.003) (0.003) (0.004) (0.003)
Wholesale Funding 0.011** 0.011** 0.013** 0.007
(0.005) (0.005) (0.006) (0.005)
FHLB Ratio 0.005* 0.008** 0.008** 0.004
(0.003) (0.003) (0.004) (0.003)
ROA −0.028 −0.026 −0.024 −0.019
(0.019) (0.019) (0.020) (0.019)
Num.Obs. 2131 2131 2131 2131
R2 0.114 0.080 0.072 0.124
* p < 0.1, ** p < 0.05, *** p < 0.01
Sample: Fed borrowers. Bank-clustered SEs.
Max-Out: BTFP utilization > 90%. Prior DW: Used DW before BTFP.
modelsummary(
  list("(1) Max-Out" = m11_1, "(2) Operational" = m11_2, 
       "(3) Collateral" = m11_3, "(4) Combined" = m11_4),
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared"),
  title = "Table 11: Both-Facility Mechanisms",
  output = file.path(TABLE_PATH, "table11_both_mechanisms.html")
)

16 SECTION 13: Summary Statistics by Facility Choice

# ============================================================================
# TABLE 2: COMPARISON BY FACILITY CHOICE
# ============================================================================


# Helper function for significance stars (defined here for safety)
add_stars <- function(p_vec) {
  # use case_when to handle the entire vector at once
  dplyr::case_when(
    is.na(p_vec) ~ "",
    p_vec < 0.01 ~ "***",
    p_vec < 0.05 ~ "**",
    p_vec < 0.10 ~ "*",
    TRUE         ~ ""
  )
}

# 1. Variable List
comparison_vars <- c(
  "ln_assets", "mtm_total", "mtm_btfp", "uninsured_lev", 
  "omo_ratio", "run_risk", "adjusted_equity", "idcr_2", "insolvency_2",
  "mtm_insolvent", "insolvent_idcr_s100", "insolvent_cap_s100",
  "cash_ratio", "book_equity_ratio", "wholesale", "fhlb_ratio", "roa"
)

# Define the master labeling vector (Keep name consistent)
summary_labels <- c(
  "ln_assets"           = "Log(Total Assets)", 
  "cash_ratio"          = "Cash / Assets (%)", 
  "securities_ratio"    = "Securities / Assets (%)", 
  "loan_ratio"          = "Loans / Assets (%)",
  "book_equity_ratio"   = "Book Equity / Assets (%)", 
  "tier1_ratio"         = "Tier 1 Capital Ratio (%)",
  "roa"                 = "ROA (%)",
  "loan_to_deposit"     = "Loans / Deposits", 
  "wholesale"           = "Wholesale Funding / Liabilities (%)", 
  "fhlb_ratio"          = "FHLB / Assets (%)",
  "mtm_total"           = "MTM Loss / Assets (%)", 
  "mtm_btfp"            = "MTM Loss (BTFP-Eligible) (%)", 
  "uninsured_lev"       = "Uninsured Deposits / Assets (%)", 
  "run_risk"            = "Run Risk (MTM * Unins Share)", 
  "run_risk_1_dummy"    = "Run Risk Dummy",
  "adjusted_equity"     = "Adjusted Equity (%)",
  "idcr_2"              = "IDCR (s=1.0)",
  "insolvency_2"        = "Insolvency (s=1.0)",
  "mtm_insolvent"       = "Insolvent: Adj. Equity < 0",
  "insolvent_idcr_s100" = "Insolvent: IDCR (s=1.0)",
  "insolvent_cap_s100"  = "Insolvent: Capital (s=1.0)",
  "btfp_pct"            = "BTFP Borrowing / Assets (%)",
  "dw_pct"              = "DW Borrowing / Assets (%)",
  "fhlb_change"         = "Change in FHLB Advances / Assets (%)"
)

# 2. Compute means by group
group_means <- df %>%
  group_by(facility_choice) %>%
  summarise(
    N = n(),
    across(all_of(comparison_vars), ~mean(., na.rm = TRUE)),
    .groups = "drop"
  )

# 3. Compute t-tests vs Neither
neither_data <- df %>% filter(facility_choice == "Neither")

ttest_results <- map_dfr(comparison_vars, function(v) {
  results <- tibble(variable = v)
  
  for (grp in c("BTFP_Only", "DW_Only", "Both")) {
    grp_data <- df %>% filter(facility_choice == grp) %>% pull(!!sym(v))
    neither_vals <- neither_data %>% pull(!!sym(v))
    
    if (sum(!is.na(grp_data)) > 2 & sum(!is.na(neither_vals)) > 2) {
      tt <- t.test(grp_data, neither_vals)
      results[[paste0(grp, "_mean")]] <- mean(grp_data, na.rm = TRUE)
      results[[paste0(grp, "_tstat")]] <- tt$statistic
      results[[paste0(grp, "_pval")]] <- tt$p.value
    } else {
      results[[paste0(grp, "_mean")]] <- mean(grp_data, na.rm = TRUE)
      results[[paste0(grp, "_tstat")]] <- NA
      results[[paste0(grp, "_pval")]] <- NA
    }
  }
  results
})

# 4. Format comparison table
comparison_table <- ttest_results %>%
  mutate(
    Variable = summary_labels[variable],
    
    # Extract Neither means
    Neither_val = map_dbl(variable, ~{
      group_means %>% filter(facility_choice == "Neither") %>% pull(.x)
    }),
    
    Neither = sprintf("%.3f", Neither_val),
    
    # apply vectorized stars
    BTFP_Only = sprintf("%.3f\n(%.2f%s)", 
                        BTFP_Only_mean, BTFP_Only_tstat, add_stars(BTFP_Only_pval)),
    DW_Only = sprintf("%.3f\n(%.2f%s)", 
                      DW_Only_mean, DW_Only_tstat, add_stars(DW_Only_pval)),
    Both = sprintf("%.3f\n(%.2f%s)", 
                   Both_mean, Both_tstat, add_stars(Both_pval))
  ) %>%
  select(Variable, Neither, BTFP_Only, DW_Only, Both)

# 5. Add N row
n_row <- tibble(
  Variable = "Observations (N)",
  Neither = as.character(group_means$N[group_means$facility_choice == "Neither"]),
  BTFP_Only = as.character(group_means$N[group_means$facility_choice == "BTFP_Only"]),
  DW_Only = as.character(group_means$N[group_means$facility_choice == "DW_Only"]),
  Both = as.character(group_means$N[group_means$facility_choice == "Both"])
)

# 6. Final Table Assembly and Output
bind_rows(n_row, comparison_table) %>%
  kable(
    caption = "**Table 2: Bank Characteristics by Facility Choice (2022Q4 Baseline)**",
    col.names = c("Variable", "Neither", "BTFP Only", "DW Only", "Both"),
    align = c("l", "c", "c", "c", "c"),
    escape = FALSE
  ) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  add_header_above(c(" " = 1, "Baseline" = 1, "Difference vs. Neither [Mean (T-stat)]" = 3)) %>%
  footnote(
    general = "T-statistics in parentheses test difference from Neither group. *** p<0.01, ** p<0.05, * p<0.10",
    footnote_as_chunk = TRUE
  )
Table 2: Bank Characteristics by Facility Choice (2022Q4 Baseline)
Baseline
Difference vs. Neither [Mean (T-stat)]
Variable Neither BTFP Only DW Only Both
Observations (N) 2565 754 826 551
Log(Total Assets) 12.300 12.918 (13.11***)
13.516
(22.83***
13.886
(25.75***
MTM Loss / Assets (%) 5.124 6.072 (11.43***)
 5.293
(1.96**)
 5.987
(9.04***)
MTM Loss (BTFP-Eligible) (%) 0.521 0.762 (7.82***)
  0.557
(1.39)
 0.788
(7.60***)
Uninsured Deposits / Assets (%) 21.105 23.842 (6.28***)
25.315
(8.97***)
28.005
(13.04***
9.632 10.849 (3.10***)
7.995
(-4.82***)
   9.956
(0.76)
Run Risk (MTM * Unins Share) 122.611 162.251 (12.20***)
151.946
(8.87***
191.539
(16.37**
Adjusted Equity (%) 5.279 2.407 (-14.94***)
4.440
(-4.29***)
2.571
(-13.20***
IDCR (s=1.0) 0.102 0.062 (-5.55***)
 0.087
(-1.81*)
 0.070
(-3.52***
Insolvency (s=1.0) 0.082 0.056 (-12.40***)
0.067
(-6.63***)
0.049
(-14.52***
Insolvent: Adj. Equity < 0 0.154 0.296 (7.84***)
  0.136
(-1.32)
 0.249
(4.79***)
Insolvent: IDCR (s=1.0) 0.255 0.312 (3.00***)
 0.300
(2.50**)
 0.325
(3.22***)
Insolvent: Capital (s=1.0) 0.064 0.086 (2.00**)
 0.090
(2.35**)
 0.109
(3.20***)
Cash / Assets (%) 10.063 5.769 (-15.53***)
7.562
(-7.63***)
4.970
(-18.35***
Book Equity / Assets (%) 10.704 8.498 (-13.45***)
9.770
(-5.62***)
8.553
(-13.12***
Wholesale Funding / Liabilities (%) 0.679 0.984 (3.85***)
  0.692
(0.19)
 1.141
(5.11***)
FHLB / Assets (%) 2.173 2.862 (4.30***)
 2.932
(4.98***)
 3.610
(7.81***)
ROA (%) 1.018 1.009 (-0.42)
 1.119
(4.48***)
   1.054
(1.50)
Note: T-statistics in parentheses test difference from Neither group. *** p<0.01, ** p<0.05, * p<0.10

17 SECTION 14: Key Visualizations

# ============================================================================
# FIGURE: DAILY BORROWING TIMELINE
# ============================================================================

# Daily BTFP borrowing
btfp_daily <- btfp_loans %>%
  filter(btfp_loan_date >= "2023-03-01", btfp_loan_date <= BTFP_CLOSE) %>%
  group_by(date = btfp_loan_date) %>%
  summarise(amount_B = sum(btfp_loan_amount, na.rm = TRUE) / 1e9, .groups = "drop") %>%
  mutate(facility = "BTFP")

# Daily DW borrowing
dw_daily <- dw_loans %>%
  filter(dw_loan_date >= "2023-03-01", dw_loan_date <= DW_DATA_END) %>%
  group_by(date = dw_loan_date) %>%
  summarise(amount_B = sum(dw_loan_amount, na.rm = TRUE) / 1e9, .groups = "drop") %>%
  mutate(facility = "Discount Window")

daily_combined <- bind_rows(btfp_daily, dw_daily)

fig_daily <- ggplot() +
  annotate("rect", xmin = periods$start_date, xmax = periods$end_date,
           ymin = -Inf, ymax = Inf,
           fill = c("#FFE5E5", "#FC9272", "#A1D99B", "#9ECAE1", "#DADAEB"),
           alpha = 0.4) +
  geom_col(data = daily_combined, 
           aes(x = date, y = amount_B, fill = facility),
           position = "dodge", width = 1) +
  scale_fill_manual(values = c("BTFP" = COLORS$btfp, "Discount Window" = COLORS$dw)) +
  scale_x_date(date_breaks = "1 month", date_labels = "%b %Y") +
  scale_y_continuous(labels = scales::dollar_format(suffix = "B")) +
  labs(
    title = "Figure: Daily Federal Reserve Emergency Lending During 2023 Banking Crisis",
    subtitle = "Periods: Pre-BTFP (pink) | Acute (red) | Post-Acute (green) | Arbitrage (blue) | Wind-down (purple)",
    x = NULL, y = "Daily Borrowing ($B)", fill = "Facility"
  ) +
  theme_pub() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

print(fig_daily)

ggsave(file.path(FIG_PATH, "fig_daily_borrowing.png"), fig_daily, width = 14, height = 8, dpi = 300, bg = "white")
# ============================================================================
# FIGURE: BANK CHARACTERISTICS BY FACILITY CHOICE
# ============================================================================

plot_df <- df %>%
  mutate(group = as.character(facility_choice))

# Panel A: Total MTM Loss
p2a <- ggplot(plot_df, aes(x = facility_choice, y = mtm_total, fill = facility_choice)) +
  geom_boxplot(outlier.shape = NA, alpha = 0.7) +
  coord_cartesian(ylim = c(0, quantile(plot_df$mtm_total, 0.95, na.rm = TRUE))) +
  scale_fill_manual(values = c("Neither" = COLORS$neither, "BTFP_Only" = COLORS$btfp,
                                "DW_Only" = COLORS$dw, "Both" = COLORS$both)) +
  labs(title = "A: Total MTM Loss / Assets", x = NULL, y = "%") +
  theme_pub() + theme(legend.position = "none")

# Panel B: Uninsured Leverage
p2b <- ggplot(plot_df, aes(x = facility_choice, y = uninsured_lev, fill = facility_choice)) +
  geom_boxplot(outlier.shape = NA, alpha = 0.7) +
  coord_cartesian(ylim = c(0, quantile(plot_df$uninsured_lev, 0.95, na.rm = TRUE))) +
  scale_fill_manual(values = c("Neither" = COLORS$neither, "BTFP_Only" = COLORS$btfp,
                                "DW_Only" = COLORS$dw, "Both" = COLORS$both)) +
  labs(title = "B: Uninsured Deposits / Assets", x = NULL, y = "%") +
  theme_pub() + theme(legend.position = "none")

# Panel C: Adjusted Equity
p2c <- ggplot(plot_df, aes(x = facility_choice, y = adjusted_equity, fill = facility_choice)) +
  geom_boxplot(outlier.shape = NA, alpha = 0.7) +
  coord_cartesian(ylim = quantile(plot_df$adjusted_equity, c(0.05, 0.95), na.rm = TRUE)) +
  scale_fill_manual(values = c("Neither" = COLORS$neither, "BTFP_Only" = COLORS$btfp,
                                "DW_Only" = COLORS$dw, "Both" = COLORS$both)) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
  labs(title = "C: Adjusted Equity (Jiang et al.)", x = NULL, y = "%") +
  theme_pub() + theme(legend.position = "none")

# Panel D: Run Risk
p2d <- ggplot(plot_df, aes(x = facility_choice, y = run_risk, fill = facility_choice)) +
  geom_boxplot(outlier.shape = NA, alpha = 0.7) +
  coord_cartesian(ylim = c(0, quantile(plot_df$run_risk, 0.95, na.rm = TRUE))) +
  scale_fill_manual(values = c("Neither" = COLORS$neither, "BTFP_Only" = COLORS$btfp,
                                "DW_Only" = COLORS$dw, "Both" = COLORS$both)) +
  labs(title = "D: Run Risk (MTM × Uninsured)", x = NULL, y = "") +
  theme_pub() + theme(legend.position = "none")

fig_char <- (p2a + p2b) / (p2c + p2d) +
  plot_annotation(
    title = "Figure: Bank Characteristics by Facility Choice",
    subtitle = "BTFP users have higher MTM losses; Both users show highest vulnerability"
  )

print(fig_char)

ggsave(file.path(FIG_PATH, "fig_characteristics_boxplot.png"), fig_char, width = 12, height = 10, dpi = 300, bg = "white")
# ============================================================================
# FIGURE: INSOLVENT BANKS AND LOLR USAGE
# ============================================================================

insolvency_viz <- df_all %>%
  summarise(
    `Insolvent (Adj Eq < 0)` = sum(mtm_insolvent == 1, na.rm = TRUE),
    `Of which: Used BTFP` = sum(mtm_insolvent == 1 & btfp == 1, na.rm = TRUE),
    `Of which: Used DW` = sum(mtm_insolvent == 1 & dw == 1, na.rm = TRUE),
    `Of which: Used Neither` = sum(mtm_insolvent == 1 & any_fed == 0, na.rm = TRUE)
  ) %>%
  pivot_longer(everything(), names_to = "Category", values_to = "Count") %>%
  mutate(
    Group = ifelse(Category == "Insolvent (Adj Eq < 0)", "Total", "Breakdown"),
    Category = factor(Category, levels = c("Insolvent (Adj Eq < 0)", "Of which: Used BTFP", 
                                            "Of which: Used DW", "Of which: Used Neither"))
  )

fig_insol <- ggplot(insolvency_viz, aes(x = Category, y = Count, fill = Group)) +
  geom_col() +
  geom_text(aes(label = Count), vjust = -0.5, size = 4) +
  scale_fill_manual(values = c("Total" = "#2E86AB", "Breakdown" = "#F18F01")) +
  labs(
    title = "Figure: Insolvent Banks (Jiang et al.) and LOLR Usage",
    subtitle = "Many insolvent banks did not use emergency facilities",
    x = NULL, y = "Number of Banks"
  ) +
  theme_pub() +
  theme(legend.position = "none", axis.text.x = element_text(angle = 15, hjust = 1))

print(fig_insol)

ggsave(file.path(FIG_PATH, "fig_insolvency_lolr.png"), fig_insol, width = 10, height = 6, dpi = 300, bg = "white")
# 1. Reshape data to include all three primary insolvency indicators

insolvency_all_viz <- df_all %>%
  summarise(
    # Adjusted Equity Scenario
    MTM_Total = sum(mtm_insolvent == 1, na.rm = TRUE),
    MTM_BTFP  = sum(mtm_insolvent == 1 & btfp == 1, na.rm = TRUE),
    MTM_DW    = sum(mtm_insolvent == 1 & dw == 1, na.rm = TRUE),
    MTM_None  = sum(mtm_insolvent == 1 & any_fed == 0, na.rm = TRUE),
    
    # IDCR Scenario (s=1.0)
    IDCR_Total = sum(insolvent_idcr_s100 == 1, na.rm = TRUE),
    IDCR_BTFP  = sum(insolvent_idcr_s100 == 1 & btfp == 1, na.rm = TRUE),
    IDCR_DW    = sum(insolvent_idcr_s100 == 1 & dw == 1, na.rm = TRUE),
    IDCR_None  = sum(insolvent_idcr_s100 == 1 & any_fed == 0, na.rm = TRUE),
    
    # Capital scenario (s=1.0)
    Cap_Total = sum(insolvent_cap_s100 == 1, na.rm = TRUE),
    Cap_BTFP  = sum(insolvent_cap_s100 == 1 & btfp == 1, na.rm = TRUE),
    Cap_DW    = sum(insolvent_cap_s100 == 1 & dw == 1, na.rm = TRUE),
    Cap_None  = sum(insolvent_cap_s100 == 1 & any_fed == 0, na.rm = TRUE)
  ) %>%
  pivot_longer(everything(), names_to = "Key", values_to = "Count") %>%
  # Separate by the underscore
  separate(Key, into = c("Measure", "Usage"), sep = "_") %>%
  mutate(
    # Convert clean names to pretty labels for the plot
    Measure = case_when(
      Measure == "MTM"  ~ "MTM Insolvent (AdjEq < 0)",
      Measure == "IDCR" ~ "IDCR Insolvent (s=1.0)",
      Measure == "Cap"  ~ "Cap. Insolvent (s=1.0)"
    ),
    Usage = factor(Usage, levels = c("Total", "BTFP", "DW", "None"),
                  labels = c("Total Insolvent", "Used BTFP", "Used DW", "Used Neither"))
  )

# 2. Create the Faceted Plot
fig_insol_all <- ggplot(insolvency_all_viz, aes(x = Usage, y = Count, fill = Usage)) +
  geom_col(alpha = 0.8, color = "white") +
  geom_text(aes(label = Count), vjust = -0.5, size = 3.5, fontface = "bold") +
  facet_wrap(~Measure, scales = "free_y") +
  scale_fill_manual(values = c(
    "Total Insolvent" = "#2E86AB", 
    "Used BTFP"       = "#F18F01", 
    "Used DW"         = "#C73E1D", 
    "Used Neither"    = "#3D3B30"  
  )) +
  labs(
    title = "Figure: Comparison of Insolvency Measures and LOLR Usage",
    subtitle = "Analysis of take-up across Jiang et al. (2023) insolvency definitions",
    x = NULL, y = "Number of Banks",
    fill = "Usage Category"
  ) +
  theme_minimal() +
  theme(
    legend.position = "bottom",
    axis.text.x = element_text(angle = 45, hjust = 1),
    strip.text = element_text(face = "bold", size = 11),
    panel.spacing = unit(2, "lines")
  )

# 3. Save
print(fig_insol_all)

ggsave(file.path(FIG_PATH, "fig_insolvency_comprehensive.png"), 
       fig_insol_all, width = 12, height = 7, dpi = 300, bg = "white")
# ============================================================================
# FIGURE: TEMPORAL COEFFICIENT EVOLUTION
# ============================================================================

coef_temporal <- bind_rows(
  tidy(m8_acute, conf.int = TRUE) %>% mutate(period = "Acute Crisis"),
  tidy(m8_post, conf.int = TRUE) %>% mutate(period = "Post-Acute"),
  tidy(m8_arb, conf.int = TRUE) %>% mutate(period = "Arbitrage"),
  tidy(m8_wind, conf.int = TRUE) %>% mutate(period = "Wind-down")
) %>%
  filter(term %in% c("mtm_total", "uninsured_lev", "run_risk_dummy")) %>%
  mutate(
    term = case_when(
      term == "mtm_total" ~ "MTM Loss (Total)",
      term == "uninsured_lev" ~ "Uninsured Leverage",
      term == "run_risk_dummy" ~ "Run Risk Dummy"
    ),
    period = factor(period, levels = c("Acute Crisis", "Post-Acute", "Arbitrage", "Wind-down"))
  )

fig_temporal <- ggplot(coef_temporal, aes(x = period, y = estimate, fill = period)) +
  geom_col(alpha = 0.8) +
  geom_errorbar(aes(ymin = conf.low, ymax = conf.high), width = 0.2) +
  geom_hline(yintercept = 0, linetype = "dashed") +
  facet_wrap(~term, scales = "free_y") +
  scale_fill_manual(values = c("Acute Crisis" = "#FFB3B3", "Post-Acute" = "#90EE90", 
                                "Arbitrage" = "#87CEEB", "Wind-down" = "#DADAEB")) +
  labs(
    title = "Figure: How BTFP Borrowing Determinants Changed Across Crisis Phases",
    subtitle = "Including Run Risk Dummy per advisor feedback",
    x = NULL, y = "Coefficient Estimate", fill = NULL
  ) +
  theme_pub() +
  theme(legend.position = "none", axis.text.x = element_text(angle = 45, hjust = 1))

print(fig_temporal)

ggsave(file.path(FIG_PATH, "fig_temporal_coefs.png"), fig_temporal, width = 12, height = 7, dpi = 300, bg = "white")
# Combine coefficients for DW
coef_temporal_dw <- bind_rows(

  tidy(m8_dw_acute, conf.int = TRUE) %>% mutate(period = "Acute Crisis"),
  tidy(m8_dw_post,  conf.int = TRUE) %>% mutate(period = "Post-Acute"),
  tidy(m8_dw_arb,   conf.int = TRUE) %>% mutate(period = "Arbitrage")

) %>%
  filter(term %in% c("mtm_total", "uninsured_lev", "run_risk_dummy")) %>%
  mutate(
    term = case_when(
      term == "mtm_total" ~ "MTM Loss (Total)",
      term == "uninsured_lev" ~ "Uninsured Leverage",
      term == "run_risk_dummy" ~ "Run Risk Dummy"
    ),
    period = factor(period, levels = c("Acute Crisis", "Post-Acute", "Arbitrage"))
  )

# Create the Plot
fig_temporal_dw <- ggplot(coef_temporal_dw, aes(x = period, y = estimate, fill = period)) +
  geom_col(alpha = 0.8, color = "white") +
  geom_errorbar(aes(ymin = conf.low, ymax = conf.high), width = 0.2, color = "grey30") +
  geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
  facet_wrap(~term, scales = "free_y") +
  scale_fill_manual(values = c("Acute Crisis" = "#D73027", "Post-Acute" = "#FC8D59", 
                                "Arbitrage" = "#91BFDB", "Wind-down" = "#4575B4")) +
  labs(
    title = "Figure: Evolution of Discount Window (DW) Borrowing Determinants",
    subtitle = "Coefficient estimates across crisis phases with 95% Confidence Intervals",
    x = NULL, y = "Coefficient Estimate", fill = NULL
  ) +
  theme_minimal() +
  theme(
    legend.position = "none", 
    axis.text.x = element_text(angle = 45, hjust = 1),
    strip.text = element_text(face = "bold", size = 11),
    panel.grid.minor = element_blank()
  )

# Print and Save
print(fig_temporal_dw)

ggsave(file.path(FIG_PATH, "fig_temporal_coefs_dw.png"), fig_temporal_dw, 
       width = 12, height = 7, dpi = 300, bg = "white")

Analysis completed: January 05, 2026 at 14:23

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] psych_2.5.6 viridis_0.6.5 viridisLite_0.4.2 scales_1.3.0 patchwork_1.3.0
[6] kableExtra_1.4.0 modelsummary_2.3.0 sampleSelection_1.2-12 maxLik_1.5-2.1 miscTools_0.6-28
[11] pROC_1.19.0.1 margins_0.3.28 broom_1.0.8 lmtest_0.9-40 zoo_1.8-12
[16] sandwich_3.1-1 fixest_0.12.1 data.table_1.17.0 lubridate_1.9.4 forcats_1.0.0
[21] stringr_1.5.1 dplyr_1.1.4 purrr_1.0.2 readr_2.1.5 tidyr_1.3.1
[26] tibble_3.2.1 ggplot2_3.5.1 tidyverse_2.0.0

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