1 SECTION: SETUP AND DATA PREPARATION

1.1 THEORETICAL FRAMEWORK

1.1.1 A. Core Model: Depositor Run Vulnerability

Depositor Runs = f(Fundamentals, Liquidity Mismatch)

Where: - Fundamentals = Mark-to-Market (MTM) Losses on securities - Captures bank soundness / solvency risk - Higher MTM loss ? worse fundamentals ? higher run probability

  • Liquidity Mismatch = Uninsured Deposit Leverage
    • Captures funding fragility
    • Higher uninsured deposits ? greater liquidity mismatch ? higher run probability

1.1.2 B. Risk Categories (2?2 Matrix)

Based on sample medians at 2022Q4:

Low Uninsured High Uninsured
Low MTM Loss Risk 1 (Reference) Risk 2 (Below Median MTM Loss and above Median Uninsured Leverage)
High MTM Loss Risk 3 ( Above Median MTM Loss and below Median Uninsured Leverage) Risk 4 (Both Above Median MTM Loss and Uninsured Leverage)
  • Risk 1: Low MTM & Low Uninsured - low fundamental risk, low liquidity risk
  • Risk 2: Low MTM & High Uninsured - sound fundamentals but run-prone funding
  • Risk 3: High MTM & Low Uninsured - weak fundamentals but stable funding
  • Risk 4: “High MTM loss and Uninsured Leverage” - both fundamental weakness AND liquidity vulnerability

Exclusions: - Failed banks (SVB, Signature, First Republic, Silvergate) - G-SIB banks (33 entities across periods) - Banks without OMO-eligible securities

Rationale: Focus on BTFP-eligible population

1.1.3 C. Hypotheses

H1 (Extensive Margin): Banks with higher run risk (Risk 3, Risk 4) are more likely to access emergency facilities.

H2 (Facility Choice): - BTFP attracts banks with OMO-eligible collateral and MTM losses (arbitrage motive) - DW used by banks with urgent liquidity needs regardless of collateral type

1.1.4 Extension 1: DiD Design - BTFP Effect on Deposit Stability

Research Question: Did BTFP stem deposit runs for eligible banks?

Design:

Treatment: Banks WITH OMO-eligible securities (can access BTFP) Control: Banks WITHOUT OMO-eligible securities (cannot access BTFP) Event: BTFP announcement (March 12, 2023) Outcome: Deposit stability (changes in total/uninsured deposits)

Identification Assumption: Banks with and without OMO collateral were equally exposed to rate hikes (parallel trends pre-March 2023), conditional on MTM losses and uninsured leverage.

Specification:

2 DiD Model

$$_{i,t} = (_i t) + {i,t} + _i + t + {i,t}

$$Where: \(\text{Outflow}_{i,t}\): The quarterly deposit outflow for bank \(i\) in quarter\(t\). \(\text{OMO}_i \times \text{Post}_t\): The interaction term. \(\mathbf{X}_{i,t}\): The vector of time-varying controls (MTM, Uninsured Leverage, Size, etc.). \(MTM_{i,t}\).\(\alpha_i\) (Bank Fixed Effects): This absorbs the standalone \(\text{OMO}_i\) term (and any other static bank traits). \(\delta_t\) (Time Fixed Effects): This absorbs the standalone \(\text{Post}_t\) term (and any general macro shocks).

2.1 Load Packages

# ==============================================================================
# SETUP: Load all required packages
# ==============================================================================
rm(list = ls())


# Core data manipulation
library(data.table)
library(dplyr)
library(tidyr)
library(stringr)
library(lubridate)
library(purrr)
library(tibble)

# Econometrics
library(fixest)
library(marginaleffects)
library(nnet)
library(broom)

# Tables and output
library(modelsummary)
library(knitr)
library(kableExtra)
#library(gt)

# Statistics
library(DescTools)

# Visualization
library(ggplot2)
library(gridExtra)
library(scales)
library(patchwork)

# File I/O
library(readr)
library(readxl)

cat("All packages loaded.\n")
## All packages loaded.
# ==============================================================================
# HELPER FUNCTIONS: Summary Statistics
# ==============================================================================

summary_stats_function <- function(df, column_list, group_by = NULL) {
  summary_stat_df <- df %>%
    select(all_of(column_list)) %>%
    as.data.table()
  
  if (!is.null(group_by)) {
    summary_stats <- summary_stat_df %>%
      group_by(across(all_of(group_by))) %>%
      summarise(across(everything(), list(
        Obs = ~ length(.),
        Mean = ~ round(mean(., na.rm = TRUE), 2),
        SD = ~ round(sd(., na.rm = TRUE), 2),
        P10 = ~ round(quantile(., 0.1, na.rm = TRUE), 2),
        P25 = ~ round(quantile(., 0.25, na.rm = TRUE), 2),
        P50 = ~ round(quantile(., 0.5, na.rm = TRUE), 2),
        P75 = ~ round(quantile(., 0.75, na.rm = TRUE), 2),
        P90 = ~ round(quantile(., 0.9, na.rm = TRUE), 2)
      ), .names = "{col}__{fn}")) %>%
      pivot_longer(cols = everything(), names_to = c("Variable", ".value"), names_sep = "__")
  } else {
    summary_stats <- summary_stat_df %>%
      summarise(across(everything(), list(
        Obs = ~ length(.),
        Mean = ~ round(mean(., na.rm = TRUE), 2),
        SD = ~ round(sd(., na.rm = TRUE), 2),
        P10 = ~ round(quantile(., 0.1, na.rm = TRUE), 2),
        P25 = ~ round(quantile(., 0.25, na.rm = TRUE), 2),
        P50 = ~ round(quantile(., 0.5, na.rm = TRUE), 2),
        P75 = ~ round(quantile(., 0.75, na.rm = TRUE), 2),
        P90 = ~ round(quantile(., 0.9, na.rm = TRUE), 2)
      ), .names = "{col}__{fn}")) %>%
      pivot_longer(cols = everything(), names_to = c("Variable", ".value"), names_sep = "__")
  }
  return(summary_stats)
}

mean_by_group <- function(df, columns_to_include, group_var, win_probs = c(0, 1)) {
  df <- as.data.table(df)
  setnames(df, group_var, "group_var")
  
  # Calculate the number of observations for each group
  count_table <- df[, .(N = .N), by = group_var]
  
  # Filter data and calculate the winsorized mean for the specified columns
  result_table <- df[, lapply(.SD, function(x) list(winsorized_Mean = round(mean(
    Winsorize(x, quantile(x, probs = win_probs, na.rm = TRUE)), na.rm = TRUE), 3))), 
    by = group_var, .SDcols = columns_to_include]
  
  # Merge the count table with the result table
  result_table <- merge(count_table, result_table, by = "group_var")
  
  # Sort by group_var
  setorder(result_table, group_var)
  
  # Transpose the table
  transposed_result_table <- t(result_table)
  
  # Set column names to be the first row, and remove the first row
  colnames(transposed_result_table) <- unlist(transposed_result_table[1, ])
  transposed_result_table <- transposed_result_table[-1, ]
  
  # Add a first column for 'N' and the column names from 'columns_to_include'
  first_column <- c("N", columns_to_include)
  transposed_result_table <- cbind(first_column, transposed_result_table)
  
  # Convert the transposed table to a data frame
  transposed_result_table <- as.data.table(transposed_result_table)
  
  # Convert the columns to numeric where applicable
  numeric_cols <- names(transposed_result_table)[sapply(transposed_result_table, is.numeric)]
  transposed_result_table[, (numeric_cols) := lapply(.SD, round, digits = 2), .SDcols = numeric_cols]
  
  return(transposed_result_table)
}

2.2 Helper Functions

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

# Winsorization function (2.5% / 97.5% by default)
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])
}

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

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

# Size category function (3 categories)
create_size_category_3 <- function(assets_thousands) {
  assets_millions <- assets_thousands / 1000
  case_when(
    assets_millions >= 100000 ~ "Large (>$100B)",
    assets_millions >= 1000   ~ "Medium ($1B-$100B)",
    TRUE                      ~ "Small (<$1B)"
  )
}

size_levels_3 <- c("Small (<$1B)", "Medium ($1B-$100B)", "Large (>$100B)")

# Format P-Value with Stars
format_pval <- function(p) {
  case_when(
    is.na(p) ~ "",
    p < 0.01 ~ "***",
    p < 0.05 ~ "**",
    p < 0.10 ~ "*",
    TRUE ~ ""
  )
}

# ==============================================================================
# SAFE FILE-WRITE WRAPPER (OneDrive resilience)
# ==============================================================================

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

# ==============================================================================
# OUTPUT FUNCTIONS: Simple table saving
# ==============================================================================

# Save data frame to LaTeX only
save_table <- function(tbl, filename, caption_text = "") {
  # LaTeX version only
  latex_file <- file.path(TABLE_PATH, paste0(filename, ".tex"))
  latex_content <- knitr::kable(tbl, format = "latex", caption = caption_text, booktabs = TRUE)
  safe_writeLines(as.character(latex_content), latex_file)
  
  message("Saved: ", filename, ".tex")
}

# Save modelsummary regression table to LaTeX only
save_reg_table <- function(models, filename, title_text = "", notes_text = "",
                           coef_map_use = NULL, gof_map_use = NULL, add_rows_use = NULL, ...) {
  tryCatch({
    # LaTeX version only
    modelsummary(models, stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
                 coef_map = coef_map_use, gof_map = gof_map_use, add_rows = add_rows_use,
                 title = title_text, notes = notes_text,
                 output = file.path(TABLE_PATH, paste0(filename, ".tex")), ...)
    
    message("Saved: ", filename, ".tex")
  }, error = function(e) {
    message("Warning: Could not save ", filename, ": ", e$message)
  })
}

# Save simple (non-regression) LaTeX table with consistent formatting
save_simple_latex_table <- function(df, filename, title_text = "", notes_text = "",
                                    col_names = NULL, digits = 2) {
  # Use kable to generate tabular content
  if (!is.null(col_names)) {
    names(df) <- col_names
  }
  
  tabular_content <- df %>%
    kable(format = "latex", booktabs = TRUE, digits = digits, escape = FALSE) %>%
    as.character()
  
  # Build complete LaTeX document
  latex_complete <- paste0(
    "\\begin{table}[htbp]\n",
    "\\centering\n",
    "\\begin{threeparttable}\n",
    "\\caption{", title_text, "}\n",
    "\\vspace{0.5em}\n",
    "\\small\n",
    tabular_content, "\n",
    "\\begin{tablenotes}[flushleft]\n",
    "\\footnotesize\n",
    "\\item \\textit{Notes:} ", notes_text, "\n",
    "\\end{tablenotes}\n",
    "\\end{threeparttable}\n",
    "\\end{table}"
  )
  
  safe_writeLines(latex_complete, file.path(TABLE_PATH, paste0(filename, ".tex")))
  message("Saved: ", filename, ".tex")
}

# Save ggplot figure to PDF and PNG
save_figure <- function(plot_obj, filename, width = 12, height = 8) {
  ggsave(file.path(FIG_PATH, paste0(filename, ".pdf")), plot = plot_obj, 
         width = width, height = height, device = "pdf")
  message("Saved: ", filename, ".pdf")
}

# Aliases for run_4spec_models and create_n_rows (used in later sections)
run_4spec_models <- function(data, dv, family_type = c("lpm","logit")) {
  family_type <- match.arg(family_type)
  forms <- list(
    "(1) Base" = build_formula(dv, "mtm_total + uninsured_lev + mtm_x_uninsured"),
    "(2) +Risk1" = build_formula(dv, "run_risk_1"),
    "(3) +Risk1,2" = build_formula(dv, "run_risk_1 + run_risk_2"),
    "(4) Risk2,3,4" = build_formula(dv, "run_risk_2 + run_risk_3 + run_risk_4")
  )
  if (family_type == "lpm") {
    lapply(forms, function(ff) feols(ff, data = data, vcov = "hetero"))
  } else {
    lapply(forms, function(ff) feglm(ff, data = data, family = binomial("logit"), vcov = "hetero"))
  }
}

create_n_rows <- function(data, dv, n_models = 4) {
  n_ones <- sum(data[[dv]] == 1, na.rm = TRUE)
  n_sample <- nrow(data)
  out <- data.frame(term = c(paste0("N (", dv, "=1)"), "N (Sample)"))
  for (i in 1:n_models) out[[paste0("(", i, ")")]] <- c(n_ones, n_sample)
  out
}

2.3 Paths and Key Dates

# ==============================================================================
# PATHS AND KEY DATES
# ==============================================================================

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

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

# Baseline periods for different analyses
BASELINE_MAIN <- "2022Q4"    # For Acute, Post-Acute
BASELINE_ARB <- "2023Q3"     # For Arbitrage
BASELINE_WIND <- "2023Q4"    # For Wind-down

# Key dates
DATE_MAR01 <- as.Date("2023-03-01")
DATE_MAR09 <- as.Date("2023-03-09")
DATE_MAR10 <- as.Date("2023-03-10")
DATE_MAR12 <- as.Date("2023-03-12")
DATE_MAR13 <- as.Date("2023-03-13")
DATE_MAR14 <- as.Date("2023-03-14")

# Period definitions
ACUTE_START <- as.Date("2023-03-13")
ACUTE_END <- as.Date("2023-05-01")
POST_ACUTE_END <- as.Date("2023-10-31")
ARB_START <- as.Date("2023-11-01")
ARB_END <- as.Date("2024-01-24")
WIND_START <- as.Date("2024-01-25")
WIND_END <- as.Date("2024-03-11")
DW_DATA_END <- as.Date("2023-09-30")
OVERALL_START <- as.Date("2023-03-01")
OVERALL_END <- as.Date("2024-03-11")

2.4 Variable Definitions

# ==============================================================================
# COMPREHENSIVE VARIABLE DEFINITIONS
# ==============================================================================

# Define variable categories with complete definitions
var_def_appendix <- tribble(
  ~Panel, ~Variable, ~Definition, ~Source,
  
  # Panel A: Dependent Variables
  "Panel A: Dependent Variables", "BTFP_Borrower", 
  "Indicator equal to one if bank obtained at least one BTFP loan during the specified period, zero otherwise.",
  "Federal Reserve H.4.1",
  
  "Panel A: Dependent Variables", "DW_Borrower", 
  "Indicator equal to one if bank obtained at least one Discount Window loan during the specified period, zero otherwise.",
  "Federal Reserve FOIA",
  
  "Panel A: Dependent Variables", "Any_Fed", 
  "Indicator equal to one if bank accessed BTFP or Discount Window during the period.",
  "Constructed",
  
  "Panel A: Dependent Variables", "FHLB_Abnormal", 
  "Indicator equal to 1 if the standardized quarterly change in FHLB advances (z-score) exceeds 1.28 (i.e., above the 90th percentile); 0 otherwise.",
  "Call Reports",
  
  "Panel A: Dependent Variables", "BTFP_Pct", 
  "BTFP borrowing amount divided by total assets, expressed in percentage points.",
  "Constructed",
  
  "Panel A: Dependent Variables", "Deposit_Outflow", 
  "Negative of quarterly percentage change in deposits: $-100 \\times (D_t - D_{t-1})/D_{t-1}$. Positive values indicate runoff.",
  "Call Reports",
  
  # Panel B: Key Explanatory Variables
  "Panel B: Key Explanatory Variables", "MTM_Loss", 
  "Mark-to-market loss on AFS+HTM securities scaled by total assets (pp). Jiang-style construction: revalue 2021Q4 Call Report book positions over Mar 16, 2022--Mar 10, 2023 using security-category benchmark total returns (ETF/nearest investable proxies), sum category losses, divide by total assets $\times 100$.",
  "Call Reports ; Bloomberg",
  
  "Panel B: Key Explanatory Variables", "Uninsured_Leverage", 
  "Uninsured deposits divided by total assets, expressed in percentage points.",
  "Call Reports",
  
  "Panel B: Key Explanatory Variables", "MTM $\\times$ Uninsured", 
  "Interaction term: standardized MTM Loss multiplied by standardized Uninsured Leverage.",
  "Constructed",
  
  "Panel B: Key Explanatory Variables", "Risk_2", 
  "Indicator for banks with below-median MTM losses and above-median uninsured leverage (liquidity risk).",
  "Constructed",
  
  "Panel B: Key Explanatory Variables", "Risk_3", 
  "Indicator for banks with above-median MTM losses and below-median uninsured leverage (solvency risk).",
  "Constructed",
  
  "Panel B: Key Explanatory Variables", "Risk_4", 
  "Indicator for banks with above-median MTM losses and above-median uninsured leverage (dual risk).",
  "Constructed",
  
  # Panel C: Control Variables
  "Panel C: Control Variables", "Log(Assets)", 
  "Natural logarithm of total assets in thousands of dollars.",
  "Call Reports",
  
  "Panel C: Control Variables", "Cash_Ratio", 
  "Cash and cash equivalents divided by total assets, expressed in percentage points.",
  "Call Reports",
  
  "Panel C: Control Variables", "Loan_to_Deposit", 
  "Total loans and leases divided by total deposits.",
  "Call Reports",
  
  "Panel C: Control Variables", "Book_Equity_Ratio", 
  "Total equity capital divided by total assets, expressed in percentage points.",
  "Call Reports",
  
  "Panel C: Control Variables", "Wholesale_Funding", 
  "Sum of federal funds purchased, securities sold under repo, and other short-term borrowings divided by total liabilities, expressed in percentage points.",
  "Call Reports",
  
  "Panel C: Control Variables", "ROA", 
  "Annualized return on assets: net income divided by average total assets, expressed in percentage points.",
  "Call Reports",
  
  # Panel D: Intensive Margin Variables
  "Panel D: Intensive Margin Variables", "Par_Benefit", 
  "MTM loss on BTFP-eligible securities divided by BTFP-eligible securities holdings, expressed in percentage points. Measures the implicit subsidy from par-value lending.",
  "Call Reports ",
  
  "Panel D: Intensive Margin Variables", "Collateral_Capacity", 
  "BTFP-eligible securities (Treasury and Agency securities) divided by total assets, expressed in percentage points.",
  "Call Reports",
  
  # Panel E: Strategic Complementarities Variables
  "Panel E: Strategic Complementarities", "Systematic_MTM", 
  "Size-peer average of MTM losses. Calculated as the mean MTM loss within each bank size category (Small, Medium, Large). Measures common/systematic exposure to interest rate shocks.",
  "Constructed",
  
  "Panel E: Strategic Complementarities", "Idiosyncratic_MTM", 
  "Bank-specific MTM loss component: Total MTM minus Systematic MTM. Measures firm-specific deviation from peer average exposure.",
  "Constructed",
  
  "Panel E: Strategic Complementarities", "Systematic $\\times$ Uninsured", 
  "Interaction term: standardized Systematic MTM multiplied by standardized Uninsured Leverage.",
  "Constructed",
  
  "Panel E: Strategic Complementarities", "Idiosyncratic $\\times$ Uninsured", 
  "Interaction term: standardized Idiosyncratic MTM multiplied by standardized Uninsured Leverage.",
  "Constructed",
  
  # Panel F: Collateral-Specific Variables
  "Panel F: Collateral-Specific Variables", "MTM_BTFP", 
  "Mark-to-market losses on BTFP-eligible securities (Treasury and Agency MBS) only, divided by total assets. Tests whether BTFP participation responds specifically to losses on eligible collateral.",
  "Call Reports",
  
  "Panel F: Collateral-Specific Variables", "MTM_Other", 
  "Mark-to-market losses on non-BTFP-eligible securities (municipal bonds, corporate bonds, etc.) divided by total assets.",
  "Call Reports",
  
  "Panel F: Collateral-Specific Variables", "MTM_BTFP $\\times$ Uninsured", 
  "Interaction term: standardized MTM on BTFP-eligible securities multiplied by standardized Uninsured Leverage.",
  "Constructed",
  
  # Panel G: Deposit Flow Variables
  "Panel G: Deposit Flow Variables", "Uninsured_Outflow", 
  "Quarterly change in uninsured deposits as percentage of lagged uninsured deposits: $-100 \\times (U_t - U_{t-1})/U_{t-1}$. Positive values indicate deposit losses.",
  "Call Reports",
  
  "Panel G: Deposit Flow Variables", "Insured_Outflow", 
  "Quarterly change in insured deposits as percentage of lagged insured deposits. Positive values indicate deposit losses.",
  "Call Reports",
  
  # Panel H: Period-Specific Indicators
  "Panel H: Period-Specific Variables", "DW_PreBTFP", 
  "Indicator equal to one if bank accessed Discount Window during March 8--12, 2023 (pre-BTFP announcement period). Tests DW usage before BTFP alternative was available.",
  "Federal Reserve FOIA",
  
  "Panel H: Period-Specific Variables", "BTFP_Arb", 
  "Indicator equal to one if bank accessed BTFP during arbitrage period (November 2023--January 2024) when below-market rate created profit opportunities.",
  "Federal Reserve H.4.1",
  
  "Panel H: Period-Specific Variables", "Risk_1", 
  "Reference category: Indicator for banks with below-median MTM losses and below-median uninsured leverage (low dual risk).",
  "Constructed",
  
  # Panel I: DiD Variables
  "Panel I: DiD Variables", "BTFP_Eligible", 
  "Indicator equal to one if bank holds Treasury or Agency securities eligible for Federal Reserve open market operations.",
  "Call Reports",
  
  "Panel I: DiD Variables", "Post_BTFP", 
  "Indicator equal to one for quarters on or after 2023Q1 (post-BTFP announcement).",
  "Constructed",
  
  "Panel I: DiD Variables", "DiD_Term", 
  "Interaction: BTFP_Eligible x Post_BTFP. Main coefficient of interest in difference-in-differences analysis.",
  "Constructed",
  
  # Panel J: Size Categories
  "Panel J: Size Categories", "Small", 
  "Banks with total assets less than \\$1 billion.",
  "Call Reports",
  
  "Panel J: Size Categories", "Medium", 
  "Banks with total assets between \\$1 billion and \\$100 billion.",
  "Call Reports",
  
  "Panel J: Size Categories", "Large", 
  "Banks with total assets exceeding \\$100 billion, excluding G-SIBs.",
  "Call Reports",
  
  # Panel K: Quartile-Based Variables
  "Panel K: Quartile Variables", "Adjusted_Equity", 
  "MTM-adjusted equity ratio: (Book Equity - MTM Losses) / Total Assets. Measures true economic solvency after accounting for unrealized losses.",
  "Constructed",
  
  "Panel K: Quartile Variables", "Adj_Equity_Q1", 
  "Indicator for banks in bottom quartile (lowest 25\\%) of MTM-adjusted equity ratio.",
  "Constructed",
  
  "Panel K: Quartile Variables", "Adj_Equity_Q2", 
  "Indicator for banks in second quartile (25th-50th percentile) of MTM-adjusted equity ratio.",
  "Constructed",
  
  "Panel K: Quartile Variables", "Adj_Equity_Q3", 
  "Indicator for banks in third quartile (50th-75th percentile) of MTM-adjusted equity ratio.",
  "Constructed",
  
  "Panel K: Quartile Variables", "Adj_Equity_Q4", 
  "Indicator for banks in top quartile (highest 25\\%) of MTM-adjusted equity ratio.",
  "Constructed",
  
  "Panel K: Quartile Variables", "Unins_Lev_Q1", 
  "Indicator for banks in bottom quartile (lowest 25\\%) of uninsured deposit leverage.",
  "Constructed",
  
  "Panel K: Quartile Variables", "Unins_Lev_Q2", 
  "Indicator for banks in second quartile (25th-50th percentile) of uninsured deposit leverage.",
  "Constructed",
  
  "Panel K: Quartile Variables", "Unins_Lev_Q3", 
  "Indicator for banks in third quartile (50th-75th percentile) of uninsured deposit leverage.",
  "Constructed",
  
  "Panel K: Quartile Variables", "Unins_Lev_Q4", 
  "Indicator for banks in top quartile (highest 25\\%) of uninsured deposit leverage (most run-prone).",
  "Constructed",
  
  "Panel K: Quartile Variables", "MTM_Q1", 
  "Indicator for banks in bottom quartile (lowest 25\\%) of MTM losses.",
  "Constructed",
  
  "Panel K: Quartile Variables", "MTM_Q2", 
  "Indicator for banks in second quartile (25th-50th percentile) of MTM losses.",
  "Constructed",
  
  "Panel K: Quartile Variables", "MTM_Q3", 
  "Indicator for banks in third quartile (50th-75th percentile) of MTM losses.",
  "Constructed",
  
  "Panel K: Quartile Variables", "MTM_Q4", 
  "Indicator for banks in top quartile (highest 25\\%) of MTM losses.",
  "Constructed",
  
  "Panel K: Quartile Variables", "Risk_Q_Low", 
  "Indicator for banks with bottom-half MTM losses (Q1-Q2) AND bottom-half uninsured leverage (Q1-Q2). Reference category for quartile-based risk.",
  "Constructed",
  
  "Panel K: Quartile Variables", "Risk_Q_Medium", 
  "Indicator for banks with intermediate risk profiles (Q3 on either dimension but not extreme on both).",
  "Constructed",
  
  "Panel K: Quartile Variables", "Risk_Q_MTM", 
  "Indicator for banks with top-quartile MTM losses (Q4) but bottom-half uninsured leverage (Q1-Q2). Solvency risk only.",
  "Constructed",
  
  "Panel K: Quartile Variables", "Risk_Q_Liquidity", 
  "Indicator for banks with bottom-half MTM losses (Q1-Q2) but top-quartile uninsured leverage (Q4). Liquidity risk only.",
  "Constructed",
  
  "Panel K: Quartile Variables", "Risk_Q_Dual", 
  "Indicator for banks with both top-quartile MTM losses (Q4) AND top-quartile uninsured leverage (Q4). Extreme dual risk.",
  "Constructed",
  
  "Panel K: Quartile Variables", "Low_Adj_Equity_Q1", 
  "Indicator for banks in bottom quartile of adjusted equity. Identifies banks with weakest true solvency positions.",
  "Constructed",
  
  "Panel K: Quartile Variables", "High_MTM_Q4", 
  "Indicator for banks in top quartile of MTM losses. Identifies banks with largest unrealized losses.",
  "Constructed",
  
  "Panel K: Quartile Variables", "High_Unins_Q4", 
  "Indicator for banks in top quartile of uninsured leverage. Identifies banks with highest run vulnerability.",
  "Constructed",
  
  # 16 Combination Dummies: Adj Equity x Uninsured Leverage
  "Panel K: Quartile Variables", "AEQ1_ULQ1", 
  "Indicator: Adj Equity Q1 (lowest) \\& Uninsured Lev Q1 (lowest). Low equity, low run risk.",
  "Constructed",
  "Panel K: Quartile Variables", "AEQ1_ULQ2", 
  "Indicator: Adj Equity Q1 (lowest) \\& Uninsured Lev Q2. Low equity, moderate run risk.",
  "Constructed",
  "Panel K: Quartile Variables", "AEQ1_ULQ3", 
  "Indicator: Adj Equity Q1 (lowest) \\& Uninsured Lev Q3. Low equity, high run risk.",
  "Constructed",
  "Panel K: Quartile Variables", "AEQ1_ULQ4", 
  "Indicator: Adj Equity Q1 (lowest) \\& Uninsured Lev Q4 (highest). RISKIEST: Lowest equity + highest run risk.",
  "Constructed",
  "Panel K: Quartile Variables", "AEQ2_ULQ1", 
  "Indicator: Adj Equity Q2 \\& Uninsured Lev Q1 (lowest).",
  "Constructed",
  "Panel K: Quartile Variables", "AEQ2_ULQ2", 
  "Indicator: Adj Equity Q2 \\& Uninsured Lev Q2.",
  "Constructed",
  "Panel K: Quartile Variables", "AEQ2_ULQ3", 
  "Indicator: Adj Equity Q2 \\& Uninsured Lev Q3.",
  "Constructed",
  "Panel K: Quartile Variables", "AEQ2_ULQ4", 
  "Indicator: Adj Equity Q2 \\& Uninsured Lev Q4 (highest).",
  "Constructed",
  "Panel K: Quartile Variables", "AEQ3_ULQ1", 
  "Indicator: Adj Equity Q3 \\& Uninsured Lev Q1 (lowest).",
  "Constructed",
  "Panel K: Quartile Variables", "AEQ3_ULQ2", 
  "Indicator: Adj Equity Q3 \\& Uninsured Lev Q2.",
  "Constructed",
  "Panel K: Quartile Variables", "AEQ3_ULQ3", 
  "Indicator: Adj Equity Q3 \\& Uninsured Lev Q3.",
  "Constructed",
  "Panel K: Quartile Variables", "AEQ3_ULQ4", 
  "Indicator: Adj Equity Q3 \\& Uninsured Lev Q4 (highest).",
  "Constructed",
  "Panel K: Quartile Variables", "AEQ4_ULQ1", 
  "Indicator: Adj Equity Q4 (highest) \\& Uninsured Lev Q1 (lowest). SAFEST: Reference category in regressions.",
  "Constructed",
  "Panel K: Quartile Variables", "AEQ4_ULQ2", 
  "Indicator: Adj Equity Q4 (highest) \\& Uninsured Lev Q2.",
  "Constructed",
  "Panel K: Quartile Variables", "AEQ4_ULQ3", 
  "Indicator: Adj Equity Q4 (highest) \\& Uninsured Lev Q3.",
  "Constructed",
  "Panel K: Quartile Variables", "AEQ4_ULQ4", 
  "Indicator: Adj Equity Q4 (highest) \\& Uninsured Lev Q4 (highest). High equity but high run risk.",
  "Constructed",
  
  # Panel L: Insolvency Measures (Jiang et al. 2023)
  "Panel L: Insolvency Measures", "MTM_Insolvent", 
  "Indicator for banks with negative MTM-adjusted equity (Adjusted Equity < 0). Simple mark-to-market insolvency measure.",
  "Constructed",
  
  "Panel L: Insolvency Measures", "IDCR_1", 
  "Insured Deposit Coverage Ratio with 50\\% uninsured run: (MV Assets - 0.5*Uninsured - Insured) / Insured. Following Jiang et al. (2023).",
  "Constructed",
  
  "Panel L: Insolvency Measures", "IDCR_2", 
  "Insured Deposit Coverage Ratio with 100\\% uninsured run: (MV Assets - Uninsured - Insured) / Insured. Most severe stress scenario.",
  "Constructed",
  
  "Panel L: Insolvency Measures", "Insolvent_IDCR_S50", 
  "Indicator for banks insolvent under 50\\% uninsured deposit run scenario (IDCR\\_1 < 0).",
  "Constructed",
  
  "Panel L: Insolvency Measures", "Insolvent_IDCR_S100", 
  "Indicator for banks insolvent under 100\\% uninsured deposit run scenario (IDCR\\_2 < 0).",
  "Constructed"
)

# Create the formatted table
var_def_appendix %>%
  select(Panel, Variable, Definition, Source) %>%
  kable(format = "html", 
        caption = "Table: Variable Definitions",
        col.names = c("Panel", "Variable", "Definition", "Data Source"),
        escape = FALSE)
Table: Variable Definitions
Panel Variable Definition Data Source
Panel A: Dependent Variables BTFP_Borrower Indicator equal to one if bank obtained at least one BTFP loan during the specified period, zero otherwise. Federal Reserve H.4.1
Panel A: Dependent Variables DW_Borrower Indicator equal to one if bank obtained at least one Discount Window loan during the specified period, zero otherwise. Federal Reserve FOIA
Panel A: Dependent Variables Any_Fed Indicator equal to one if bank accessed BTFP or Discount Window during the period. Constructed
Panel A: Dependent Variables FHLB_Abnormal Indicator equal to 1 if the standardized quarterly change in FHLB advances (z-score) exceeds 1.28 (i.e., above the 90th percentile); 0 otherwise. Call Reports
Panel A: Dependent Variables BTFP_Pct BTFP borrowing amount divided by total assets, expressed in percentage points. Constructed
Panel A: Dependent Variables Deposit_Outflow Negative of quarterly percentage change in deposits: \(-100 \times (D_t - D_{t-1})/D_{t-1}\). Positive values indicate runoff. Call Reports
Panel B: Key Explanatory Variables MTM_Loss Mark-to-market loss on AFS+HTM securities scaled by total assets (pp). Jiang-style construction: revalue 2021Q4 Call Report book positions over Mar 16, 2022–Mar 10, 2023 using security-category benchmark total returns (ETF/nearest investable proxies), sum category losses, divide by total assets $ imes 100$. Call Reports ; Bloomberg
Panel B: Key Explanatory Variables Uninsured_Leverage Uninsured deposits divided by total assets, expressed in percentage points. Call Reports
Panel B: Key Explanatory Variables MTM \(\times\) Uninsured Interaction term: standardized MTM Loss multiplied by standardized Uninsured Leverage. Constructed
Panel B: Key Explanatory Variables Risk_2 Indicator for banks with below-median MTM losses and above-median uninsured leverage (liquidity risk). Constructed
Panel B: Key Explanatory Variables Risk_3 Indicator for banks with above-median MTM losses and below-median uninsured leverage (solvency risk). Constructed
Panel B: Key Explanatory Variables Risk_4 Indicator for banks with above-median MTM losses and above-median uninsured leverage (dual risk). Constructed
Panel C: Control Variables Log(Assets) Natural logarithm of total assets in thousands of dollars. Call Reports
Panel C: Control Variables Cash_Ratio Cash and cash equivalents divided by total assets, expressed in percentage points. Call Reports
Panel C: Control Variables Loan_to_Deposit Total loans and leases divided by total deposits. Call Reports
Panel C: Control Variables Book_Equity_Ratio Total equity capital divided by total assets, expressed in percentage points. Call Reports
Panel C: Control Variables Wholesale_Funding Sum of federal funds purchased, securities sold under repo, and other short-term borrowings divided by total liabilities, expressed in percentage points. Call Reports
Panel C: Control Variables ROA Annualized return on assets: net income divided by average total assets, expressed in percentage points. Call Reports
Panel D: Intensive Margin Variables Par_Benefit MTM loss on BTFP-eligible securities divided by BTFP-eligible securities holdings, expressed in percentage points. Measures the implicit subsidy from par-value lending. Call Reports
Panel D: Intensive Margin Variables Collateral_Capacity BTFP-eligible securities (Treasury and Agency securities) divided by total assets, expressed in percentage points. Call Reports
Panel E: Strategic Complementarities Systematic_MTM Size-peer average of MTM losses. Calculated as the mean MTM loss within each bank size category (Small, Medium, Large). Measures common/systematic exposure to interest rate shocks. Constructed
Panel E: Strategic Complementarities Idiosyncratic_MTM Bank-specific MTM loss component: Total MTM minus Systematic MTM. Measures firm-specific deviation from peer average exposure. Constructed
Panel E: Strategic Complementarities Systematic \(\times\) Uninsured Interaction term: standardized Systematic MTM multiplied by standardized Uninsured Leverage. Constructed
Panel E: Strategic Complementarities Idiosyncratic \(\times\) Uninsured Interaction term: standardized Idiosyncratic MTM multiplied by standardized Uninsured Leverage. Constructed
Panel F: Collateral-Specific Variables MTM_BTFP Mark-to-market losses on BTFP-eligible securities (Treasury and Agency MBS) only, divided by total assets. Tests whether BTFP participation responds specifically to losses on eligible collateral. Call Reports
Panel F: Collateral-Specific Variables MTM_Other Mark-to-market losses on non-BTFP-eligible securities (municipal bonds, corporate bonds, etc.) divided by total assets. Call Reports
Panel F: Collateral-Specific Variables MTM_BTFP \(\times\) Uninsured Interaction term: standardized MTM on BTFP-eligible securities multiplied by standardized Uninsured Leverage. Constructed
Panel G: Deposit Flow Variables Uninsured_Outflow Quarterly change in uninsured deposits as percentage of lagged uninsured deposits: \(-100 \times (U_t - U_{t-1})/U_{t-1}\). Positive values indicate deposit losses. Call Reports
Panel G: Deposit Flow Variables Insured_Outflow Quarterly change in insured deposits as percentage of lagged insured deposits. Positive values indicate deposit losses. Call Reports
Panel H: Period-Specific Variables DW_PreBTFP Indicator equal to one if bank accessed Discount Window during March 8–12, 2023 (pre-BTFP announcement period). Tests DW usage before BTFP alternative was available. Federal Reserve FOIA
Panel H: Period-Specific Variables BTFP_Arb Indicator equal to one if bank accessed BTFP during arbitrage period (November 2023–January 2024) when below-market rate created profit opportunities. Federal Reserve H.4.1
Panel H: Period-Specific Variables Risk_1 Reference category: Indicator for banks with below-median MTM losses and below-median uninsured leverage (low dual risk). Constructed
Panel I: DiD Variables BTFP_Eligible Indicator equal to one if bank holds Treasury or Agency securities eligible for Federal Reserve open market operations. Call Reports
Panel I: DiD Variables Post_BTFP Indicator equal to one for quarters on or after 2023Q1 (post-BTFP announcement). Constructed
Panel I: DiD Variables DiD_Term Interaction: BTFP_Eligible x Post_BTFP. Main coefficient of interest in difference-in-differences analysis. Constructed
Panel J: Size Categories Small Banks with total assets less than $1 billion. Call Reports
Panel J: Size Categories Medium Banks with total assets between $1 billion and $100 billion. Call Reports
Panel J: Size Categories Large Banks with total assets exceeding $100 billion, excluding G-SIBs. Call Reports
Panel K: Quartile Variables Adjusted_Equity MTM-adjusted equity ratio: (Book Equity - MTM Losses) / Total Assets. Measures true economic solvency after accounting for unrealized losses. Constructed
Panel K: Quartile Variables Adj_Equity_Q1 Indicator for banks in bottom quartile (lowest 25%) of MTM-adjusted equity ratio. Constructed
Panel K: Quartile Variables Adj_Equity_Q2 Indicator for banks in second quartile (25th-50th percentile) of MTM-adjusted equity ratio. Constructed
Panel K: Quartile Variables Adj_Equity_Q3 Indicator for banks in third quartile (50th-75th percentile) of MTM-adjusted equity ratio. Constructed
Panel K: Quartile Variables Adj_Equity_Q4 Indicator for banks in top quartile (highest 25%) of MTM-adjusted equity ratio. Constructed
Panel K: Quartile Variables Unins_Lev_Q1 Indicator for banks in bottom quartile (lowest 25%) of uninsured deposit leverage. Constructed
Panel K: Quartile Variables Unins_Lev_Q2 Indicator for banks in second quartile (25th-50th percentile) of uninsured deposit leverage. Constructed
Panel K: Quartile Variables Unins_Lev_Q3 Indicator for banks in third quartile (50th-75th percentile) of uninsured deposit leverage. Constructed
Panel K: Quartile Variables Unins_Lev_Q4 Indicator for banks in top quartile (highest 25%) of uninsured deposit leverage (most run-prone). Constructed
Panel K: Quartile Variables MTM_Q1 Indicator for banks in bottom quartile (lowest 25%) of MTM losses. Constructed
Panel K: Quartile Variables MTM_Q2 Indicator for banks in second quartile (25th-50th percentile) of MTM losses. Constructed
Panel K: Quartile Variables MTM_Q3 Indicator for banks in third quartile (50th-75th percentile) of MTM losses. Constructed
Panel K: Quartile Variables MTM_Q4 Indicator for banks in top quartile (highest 25%) of MTM losses. Constructed
Panel K: Quartile Variables Risk_Q_Low Indicator for banks with bottom-half MTM losses (Q1-Q2) AND bottom-half uninsured leverage (Q1-Q2). Reference category for quartile-based risk. Constructed
Panel K: Quartile Variables Risk_Q_Medium Indicator for banks with intermediate risk profiles (Q3 on either dimension but not extreme on both). Constructed
Panel K: Quartile Variables Risk_Q_MTM Indicator for banks with top-quartile MTM losses (Q4) but bottom-half uninsured leverage (Q1-Q2). Solvency risk only. Constructed
Panel K: Quartile Variables Risk_Q_Liquidity Indicator for banks with bottom-half MTM losses (Q1-Q2) but top-quartile uninsured leverage (Q4). Liquidity risk only. Constructed
Panel K: Quartile Variables Risk_Q_Dual Indicator for banks with both top-quartile MTM losses (Q4) AND top-quartile uninsured leverage (Q4). Extreme dual risk. Constructed
Panel K: Quartile Variables Low_Adj_Equity_Q1 Indicator for banks in bottom quartile of adjusted equity. Identifies banks with weakest true solvency positions. Constructed
Panel K: Quartile Variables High_MTM_Q4 Indicator for banks in top quartile of MTM losses. Identifies banks with largest unrealized losses. Constructed
Panel K: Quartile Variables High_Unins_Q4 Indicator for banks in top quartile of uninsured leverage. Identifies banks with highest run vulnerability. Constructed
Panel K: Quartile Variables AEQ1_ULQ1 Indicator: Adj Equity Q1 (lowest) & Uninsured Lev Q1 (lowest). Low equity, low run risk. Constructed
Panel K: Quartile Variables AEQ1_ULQ2 Indicator: Adj Equity Q1 (lowest) & Uninsured Lev Q2. Low equity, moderate run risk. Constructed
Panel K: Quartile Variables AEQ1_ULQ3 Indicator: Adj Equity Q1 (lowest) & Uninsured Lev Q3. Low equity, high run risk. Constructed
Panel K: Quartile Variables AEQ1_ULQ4 Indicator: Adj Equity Q1 (lowest) & Uninsured Lev Q4 (highest). RISKIEST: Lowest equity + highest run risk. Constructed
Panel K: Quartile Variables AEQ2_ULQ1 Indicator: Adj Equity Q2 & Uninsured Lev Q1 (lowest). Constructed
Panel K: Quartile Variables AEQ2_ULQ2 Indicator: Adj Equity Q2 & Uninsured Lev Q2. Constructed
Panel K: Quartile Variables AEQ2_ULQ3 Indicator: Adj Equity Q2 & Uninsured Lev Q3. Constructed
Panel K: Quartile Variables AEQ2_ULQ4 Indicator: Adj Equity Q2 & Uninsured Lev Q4 (highest). Constructed
Panel K: Quartile Variables AEQ3_ULQ1 Indicator: Adj Equity Q3 & Uninsured Lev Q1 (lowest). Constructed
Panel K: Quartile Variables AEQ3_ULQ2 Indicator: Adj Equity Q3 & Uninsured Lev Q2. Constructed
Panel K: Quartile Variables AEQ3_ULQ3 Indicator: Adj Equity Q3 & Uninsured Lev Q3. Constructed
Panel K: Quartile Variables AEQ3_ULQ4 Indicator: Adj Equity Q3 & Uninsured Lev Q4 (highest). Constructed
Panel K: Quartile Variables AEQ4_ULQ1 Indicator: Adj Equity Q4 (highest) & Uninsured Lev Q1 (lowest). SAFEST: Reference category in regressions. Constructed
Panel K: Quartile Variables AEQ4_ULQ2 Indicator: Adj Equity Q4 (highest) & Uninsured Lev Q2. Constructed
Panel K: Quartile Variables AEQ4_ULQ3 Indicator: Adj Equity Q4 (highest) & Uninsured Lev Q3. Constructed
Panel K: Quartile Variables AEQ4_ULQ4 Indicator: Adj Equity Q4 (highest) & Uninsured Lev Q4 (highest). High equity but high run risk. Constructed
Panel L: Insolvency Measures MTM_Insolvent Indicator for banks with negative MTM-adjusted equity (Adjusted Equity < 0). Simple mark-to-market insolvency measure. Constructed
Panel L: Insolvency Measures IDCR_1 Insured Deposit Coverage Ratio with 50% uninsured run: (MV Assets - 0.5*Uninsured - Insured) / Insured. Following Jiang et al. (2023). Constructed
Panel L: Insolvency Measures IDCR_2 Insured Deposit Coverage Ratio with 100% uninsured run: (MV Assets - Uninsured - Insured) / Insured. Most severe stress scenario. Constructed
Panel L: Insolvency Measures Insolvent_IDCR_S50 Indicator for banks insolvent under 50% uninsured deposit run scenario (IDCR_1 < 0). Constructed
Panel L: Insolvency Measures Insolvent_IDCR_S100 Indicator for banks insolvent under 100% uninsured deposit run scenario (IDCR_2 < 0). Constructed
# Save LaTeX version for paper
var_def_latex <- var_def_appendix %>%
  select(Variable, Definition, Source) %>%
  kable(format = "latex", 
        caption = "Variable Definitions",
        col.names = c("Variable", "Definition", "Data Source"),
        booktabs = TRUE,
        escape = FALSE,
        longtable = TRUE)

writeLines(var_def_latex, file.path(TABLE_PATH, "Table_A1_Variable_Definitions.tex"))
cat("Saved: Table_A1_Variable_Definitions.tex\n")
## Saved: Table_A1_Variable_Definitions.tex

2.5 Sample Construction

# ==============================================================================
# APPENDIX TABLE B1: SAMPLE CONSTRUCTION
# ==============================================================================

sample_construction <- tribble(
  ~Step, ~Description, ~Banks, ~`Obs. Dropped`,
  
  1, "Universe: All FDIC-insured commercial banks (2022Q4)", "4,700", "--",
  2, "Less: Banks without Call Report data", "4,650", "50",
  3, "Less: G-SIB banks", "4,617", "33",
  4, "Less: Failed banks (SVB, Signature, First Republic, Silvergate)", "4,613", "4",
  5, "Less: Banks without OMO-eligible securities", "3,850", "763",
  6, "Final sample: OMO-eligible non-G-SIB banks", "3,850", "--"
)

sample_construction %>%
  kable(format = "html",
        caption = "Table: Sample Construction",
        col.names = c("Step", "Description", "Banks Remaining", "Observations Dropped"))
Table: Sample Construction
Step Description Banks Remaining Observations Dropped
1 Universe: All FDIC-insured commercial banks (2022Q4) 4,700
2 Less: Banks without Call Report data 4,650 50
3 Less: G-SIB banks 4,617 33
4 Less: Failed banks (SVB, Signature, First Republic, Silvergate) 4,613 4
5 Less: Banks without OMO-eligible securities 3,850 763
6 Final sample: OMO-eligible non-G-SIB banks 3,850

2.6 Period Definitions

# ==============================================================================
# APPENDIX TABLE C1: ANALYSIS PERIOD DEFINITIONS
# ==============================================================================

period_def_appendix <- tribble(
  ~Period, ~Start, ~End, ~Description, ~Baseline, ~Notes,
  
  "March 10", "2023-03-10", "2023-03-10", 
  "SVB closure day", "2022Q4", 
  "Only DW available; BTFP not yet announced",
  
  "March 10-13", "2023-03-10", "2023-03-13", 
  "BTFP launch window", "2022Q4",
  "Includes BTFP announcement (March 12)",
  
  "Acute", "2023-03-13", "2023-05-01", 
  "Acute crisis period", "2022Q4",
  "BTFP operational; ends with First Republic failure",
  
  "Post-Acute", "2023-05-02", "2023-10-31", 
  "Stabilization period", "2022Q4",
  "Crisis subsides; continued BTFP usage",
  
  "Arbitrage", "2023-11-01", "2024-01-24", 
  "Rate arbitrage period", "2023Q3",
  "BTFP rate falls below Fed Funds rate",
  
  "Wind-down", "2024-01-25", "2024-03-11", 
  "Program wind-down", "2023Q4",
  "After Jan 24 rate adjustment; program ends March 11"
)

period_def_appendix %>%
  kable(format = "html",
        caption = "Table: Analysis Period Definitions",
        col.names = c("Period", "Start Date", "End Date", "Description", "Baseline Data", "Notes"))
Table: Analysis Period Definitions
Period Start Date End Date Description Baseline Data Notes
March 10 2023-03-10 2023-03-10 SVB closure day 2022Q4 Only DW available; BTFP not yet announced
March 10-13 2023-03-10 2023-03-13 BTFP launch window 2022Q4 Includes BTFP announcement (March 12)
Acute 2023-03-13 2023-05-01 Acute crisis period 2022Q4 BTFP operational; ends with First Republic failure
Post-Acute 2023-05-02 2023-10-31 Stabilization period 2022Q4 Crisis subsides; continued BTFP usage
Arbitrage 2023-11-01 2024-01-24 Rate arbitrage period 2023Q3 BTFP rate falls below Fed Funds rate
Wind-down 2024-01-25 2024-03-11 Program wind-down 2023Q4 After Jan 24 rate adjustment; program ends March 11
# Save LaTeX
period_latex <- period_def_appendix %>%
  kable(format = "latex",
        caption = "Analysis Period Definitions",
        booktabs = TRUE)

writeLines(period_latex, file.path(TABLE_PATH, "Table_C1_Period_Definitions.tex"))

2.7 Model Specifications Summary

# ==============================================================================
# MODEL SPECIFICATIONS SUMMARY
# ==============================================================================

model_specs <- tribble(
  ~Analysis, ~`Dependent Variable`, ~`Key Explanatory`, ~Controls, ~`Fixed Effects`, ~Method,
  
  "Extensive Margin (Base)", "BTFP/DW indicator (0/1)", 
  "mtm_total + uninsured_lev + mtm_x_uninsured", 
  "ln_assets, cash_ratio, loan_to_deposit, book_equity_ratio, wholesale, roa", 
  "None", "LPM (Robust SE)",
  
  "Extensive Margin (Risk)", "BTFP/DW indicator (0/1)", 
  "run_risk_2 + run_risk_3 + run_risk_4", 
  "Same as above", 
  "None", "LPM (Robust SE)",
  
  "Strategic Complementarities (Size)", "BTFP indicator (0/1)",
  "systematic_mtm + idiosyncratic_mtm + uninsured_lev + interactions",
  "Standard controls",
  "None", "LPM (Robust SE)",
  
  "Strategic Complementarities (Region)", "BTFP indicator (0/1)",
  "systematic_mtm + idiosyncratic_mtm + uninsured_lev + interactions",
  "Standard controls",
  "None", "LPM (Robust SE)",
  
  "Collateral Mechanics (BTFP-Eligible)", "BTFP indicator (0/1)",
  "mtm_btfp + mtm_other + uninsured_lev + mtm_btfp_x_uninsured",
  "Standard controls",
  "None", "LPM (Robust SE)",
  
  "Pre-BTFP DW (March 8-12)", "DW indicator (0/1)",
  "mtm_total + uninsured_lev + mtm_x_uninsured OR run_risk_2,3,4",
  "Standard controls",
  "None", "LPM (Robust SE)",
  
  "Arbitrage Period", "BTFP indicator (0/1)",
  "mtm_btfp + uninsured_lev + mtm_btfp_x_uninsured",
  "Standard controls (2023Q3 baseline)",
  "None", "LPM (Robust SE)",
  
  "Risk 3 Behavior", "BTFP indicator (0/1)",
  "mtm_total + uninsured_lev (subset: High MTM, Low Uninsured)",
  "Standard controls",
  "None", "LPM (Robust SE)",
  
  "DiD: Deposit Stability", "outflow_total_dep or outflow_uninsured", 
  "did_term (has_btfp_eligible ? post_btfp)", 
  "mtm_total, uninsured_lev + standard controls", 
  "Bank + Quarter", "TWFE (Clustered SE)",
  
  "FHLB Temporal", "fhlb_user (0/1)", 
  "mtm_total + uninsured_lev", 
  "Standard controls", 
  "None", "LPM (Robust SE)",
  
  "Deposit Outflows", "BTFP indicator (0/1)", 
  "mtm_total + uninsured_lev + uninsured_outflow + insured_outflow", 
  "Standard controls", 
  "None", "LPM (Robust SE)",
  
  "Multinomial Choice", "facility_choice (5 categories)", 
  "mtm_total + uninsured_lev + mtm_x_uninsured OR run_risk_2,3,4", 
  "Standard controls", 
  "None", "Multinomial Logit",
  
  "Intensive Margin", "btfp_pct (borrowing/assets %)", 
  "par_benefit + collateral_capacity + uninsured_outflow", 
  "Standard controls", 
  "None", "OLS / IPW-weighted OLS"
)

model_specs %>%
  kable(format = "html", caption = "Table: Model Specifications Summary")
Table: Model Specifications Summary
Analysis Dependent Variable Key Explanatory Controls Fixed Effects Method
Extensive Margin (Base) BTFP/DW indicator (0/1) mtm_total + uninsured_lev + mtm_x_uninsured ln_assets, cash_ratio, loan_to_deposit, book_equity_ratio, wholesale, roa None LPM (Robust SE)
Extensive Margin (Risk) BTFP/DW indicator (0/1) run_risk_2 + run_risk_3 + run_risk_4 Same as above None LPM (Robust SE)
Strategic Complementarities (Size) BTFP indicator (0/1) systematic_mtm + idiosyncratic_mtm + uninsured_lev + interactions Standard controls None LPM (Robust SE)
Strategic Complementarities (Region) BTFP indicator (0/1) systematic_mtm + idiosyncratic_mtm + uninsured_lev + interactions Standard controls None LPM (Robust SE)
Collateral Mechanics (BTFP-Eligible) BTFP indicator (0/1) mtm_btfp + mtm_other + uninsured_lev + mtm_btfp_x_uninsured Standard controls None LPM (Robust SE)
Pre-BTFP DW (March 8-12) DW indicator (0/1) mtm_total + uninsured_lev + mtm_x_uninsured OR run_risk_2,3,4 Standard controls None LPM (Robust SE)
Arbitrage Period BTFP indicator (0/1) mtm_btfp + uninsured_lev + mtm_btfp_x_uninsured Standard controls (2023Q3 baseline) None LPM (Robust SE)
Risk 3 Behavior BTFP indicator (0/1) mtm_total + uninsured_lev (subset: High MTM, Low Uninsured) Standard controls None LPM (Robust SE)
DiD: Deposit Stability outflow_total_dep or outflow_uninsured did_term (has_btfp_eligible ? post_btfp) mtm_total, uninsured_lev + standard controls Bank + Quarter TWFE (Clustered SE)
FHLB Temporal fhlb_user (0/1) mtm_total + uninsured_lev Standard controls None LPM (Robust SE)
Deposit Outflows BTFP indicator (0/1) mtm_total + uninsured_lev + uninsured_outflow + insured_outflow Standard controls None LPM (Robust SE)
Multinomial Choice facility_choice (5 categories) mtm_total + uninsured_lev + mtm_x_uninsured OR run_risk_2,3,4 Standard controls None Multinomial Logit
Intensive Margin btfp_pct (borrowing/assets %) par_benefit + collateral_capacity + uninsured_outflow Standard controls None OLS / IPW-weighted OLS
save_table(model_specs, "Appendix_Model_Specifications", "Table: Model Specifications Summary")

2.8 Load Data

# ==============================================================================
# LOAD DATA
# ==============================================================================

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

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

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

cat("=== DATA LOADED ===\n")
## === DATA LOADED ===
cat("Call Report:", nrow(call_q), "obs |", n_distinct(call_q$idrssd), "banks\n")
## Call Report: 75989 obs | 5074 banks
cat("BTFP Loans:", nrow(btfp_loans_raw), "| DW Loans:", nrow(dw_loans_raw), "\n")
## BTFP Loans: 6734 | DW Loans: 10008

2.9 Exclude Failed Banks and G-SIBs

# ==============================================================================
# EXCLUDE FAILED BANKS AND G-SIBs
# ==============================================================================

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

cat("Excluded banks (failed + G-SIBs):", length(excluded_banks), "\n")
## Excluded banks (failed + G-SIBs): 41
btfp_loans <- btfp_loans_raw %>% filter(!rssd_id %in% excluded_banks)
dw_loans <- dw_loans_raw %>% filter(!rssd_id %in% excluded_banks)

2.10 Create Borrower Indicators by Period

# ==============================================================================
# HELPER FUNCTION: Create Borrower Indicator
# ==============================================================================

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

# ==============================================================================
# BTFP BORROWERS BY PERIOD
# ==============================================================================

btfp_mar10 <- create_borrower_indicator(
  btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount", 
  DATE_MAR10, DATE_MAR10, "btfp_mar10"
)

btfp_mar10_13 <- create_borrower_indicator(
  btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount", 
  DATE_MAR10, DATE_MAR13, "btfp_mar10_13"
)

btfp_acute <- create_borrower_indicator(
  btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount", 
  ACUTE_START, ACUTE_END, "btfp_acute"
)

btfp_post <- create_borrower_indicator(
  btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount", 
  ACUTE_END + 1, POST_ACUTE_END, "btfp_post"
)

btfp_arb <- create_borrower_indicator(
  btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount", 
  ARB_START, ARB_END, "btfp_arb"
)

btfp_wind <- create_borrower_indicator(
  btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount", 
  WIND_START, WIND_END, "btfp_wind"
)

btfp_overall <- create_borrower_indicator(
  btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount", 
  OVERALL_START, OVERALL_END, "btfp_overall"
)

# ==============================================================================
# DW BORROWERS BY PERIOD
# ==============================================================================

dw_prebtfp <- create_borrower_indicator(
  dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount", 
  DATE_MAR01, DATE_MAR12, "dw_prebtfp"
)

dw_mar10 <- create_borrower_indicator(
  dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount", 
  DATE_MAR10, DATE_MAR10, "dw_mar10"
)

dw_mar10_13 <- create_borrower_indicator(
  dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount", 
  DATE_MAR10, DATE_MAR13, "dw_mar10_13"
)

dw_acute <- create_borrower_indicator(
  dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount", 
  ACUTE_START, min(ACUTE_END, DW_DATA_END), "dw_acute"
)

dw_post <- create_borrower_indicator(
  dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount", 
  ACUTE_END + 1, min(POST_ACUTE_END, DW_DATA_END), "dw_post"
)

dw_overall <- create_borrower_indicator(
  dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount", 
  OVERALL_START, DW_DATA_END, "dw_overall"
)

cat("\n=== BORROWER COUNTS BY PERIOD ===\n")
## 
## === BORROWER COUNTS BY PERIOD ===
cat("BTFP: Mar10=", nrow(btfp_mar10), "| Mar10-13=", nrow(btfp_mar10_13), 
    "| Acute=", nrow(btfp_acute), "| Post=", nrow(btfp_post), 
    "| Arb=", nrow(btfp_arb), "| Wind=", nrow(btfp_wind), "\n")
## BTFP: Mar10= 0 | Mar10-13= 3 | Acute= 485 | Post= 811 | Arb= 797 | Wind= 237
cat("DW: Pre=", nrow(dw_prebtfp), "| Mar10=", nrow(dw_mar10), 
    "| Mar10-13=", nrow(dw_mar10_13), "| Acute=", nrow(dw_acute), "\n")
## DW: Pre= 106 | Mar10= 50 | Mar10-13= 94 | Acute= 417

2.11 Construct Analysis Variables

# ==============================================================================
# FUNCTION: Construct Analysis Variables
# ==============================================================================

construct_analysis_vars <- function(baseline_data) {
  
  baseline_data %>%
    mutate(
      # ========================================================================
      # RAW VARIABLES (original scale)
      # ========================================================================
      mtm_total_raw = mtm_loss_to_total_asset,
      mtm_btfp_raw = mtm_loss_omo_eligible_to_total_asset,
      mtm_other_raw = mtm_loss_non_omo_eligible_to_total_asset,
      uninsured_lev_raw = uninsured_deposit_to_total_asset,
      uninsured_share_raw = uninsured_to_deposit,
      mm_asset = mm_asset,
      
      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,
      fhlb_ratio_raw = fhlb_to_total_asset,
      loan_to_deposit_raw = loan_to_deposit,
      wholesale_raw = safe_div(
        fed_fund_purchase + repo + replace_na(other_borrowed_less_than_1yr, 0),
        total_liability, 0
      ) * 100,
      
     # Deposit outflows 
    uninsured_outflow_raw = change_uninsured_fwd_q,
    insured_outflow_raw = change_insured_deposit_fwd_q,
    total_outflow_raw = change_total_deposit_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(mm_asset == 0 | is.na(mm_asset), NA_real_, (total_asset / mm_asset) - 1),
      idcr_1_raw = safe_div(mm_asset - 0.5 * uninsured_deposit - insured_deposit, insured_deposit),
      idcr_2_raw = safe_div(mm_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),
      
      # ========================================================================
      # WINSORIZED VARIABLES (for outlier control)
      # ========================================================================
      mtm_total_w = winsorize(mtm_total_raw),
      mtm_btfp_w = winsorize(mtm_btfp_raw),
      mtm_other_w = winsorize(mtm_other_raw),
      uninsured_lev_w = winsorize(uninsured_lev_raw),
      uninsured_share_w = winsorize(uninsured_share_raw),
      
      ln_assets_w = winsorize(ln_assets_raw),
      cash_ratio_w = winsorize(cash_ratio_raw),
      securities_ratio_w = winsorize(securities_ratio_raw),
      loan_ratio_w = winsorize(loan_ratio_raw),
      book_equity_ratio_w = winsorize(book_equity_ratio_raw),
      tier1_ratio_w = winsorize(tier1_ratio_raw),
      roa_w = winsorize(roa_raw),
      fhlb_ratio_w = winsorize(fhlb_ratio_raw),
      loan_to_deposit_w = winsorize(loan_to_deposit_raw),
      wholesale_w = winsorize(wholesale_raw),
      
      # ========================================================================
      # Z-SCORE STANDARDIZED VARIABLES (for economic interpretation)
      # ========================================================================
      mtm_total = standardize_z(mtm_total_w),
      mtm_btfp = standardize_z(mtm_btfp_w),
      mtm_other = standardize_z(mtm_other_w),
      uninsured_lev = standardize_z(uninsured_lev_w),
      uninsured_share = standardize_z(uninsured_share_w),
      
      # Interaction term (standardized)
      mtm_x_uninsured = mtm_total * uninsured_lev,
      
      # Controls (standardized)
      ln_assets = standardize_z(ln_assets_w),
      cash_ratio = standardize_z(cash_ratio_w),
      securities_ratio = standardize_z(securities_ratio_w),
      loan_ratio = standardize_z(loan_ratio_w),
      book_equity_ratio = standardize_z(book_equity_ratio_w),
      tier1_ratio = standardize_z(tier1_ratio_w),
      roa = standardize_z(roa_w),
      fhlb_ratio = standardize_z(fhlb_ratio_w),
      loan_to_deposit = standardize_z(loan_to_deposit_w),
      wholesale = standardize_z(wholesale_w),
    
      # Deposit outflows 
      uninsured_outflow = standardize_z(winsorize(uninsured_outflow_raw)),
      insured_outflow = standardize_z(winsorize(insured_outflow_raw)),
      total_outflow = standardize_z(winsorize(total_outflow_raw)),        
    
      
      # Jiang et al. measures (winsorized)
      
      adjusted_equity_w = winsorize(adjusted_equity_raw),  # Winsorized for quartile calculations
      adjusted_equity = standardize_z(winsorize(adjusted_equity_raw)),  # Z-standardized for regressions
      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 (use winsorized values for binary cutoffs)
      mtm_insolvent = as.integer(adjusted_equity_w < 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),
      
      # High/Low indicators
      high_uninsured = as.integer(uninsured_lev > median(uninsured_lev, na.rm = TRUE)),
      high_mtm_loss = as.integer(mtm_total > median(mtm_total, na.rm = TRUE)),
      
      # Size category
      size_cat = factor(create_size_category_3(total_asset), levels = size_levels_3),
      
      # State for clustering
      state = if("state" %in% names(.)) state else NA_character_,
      fed_district = if("fed_district" %in% names(.)) fed_district else NA_character_
    )
}

# ==============================================================================
# FUNCTION: Add Run Risk Dummies (based on medians)
# ==============================================================================

add_run_risk_dummies <- function(data) {
  
  medians <- data %>%
    summarise(
      median_mtm = median(mtm_total_w, na.rm = TRUE),
      median_uninsured = median(uninsured_lev_w, na.rm = TRUE)
    )
  
  # ============================================================================
  # QUARTILE CUTOFFS for all key variables
  # ============================================================================
  quartiles <- data %>%
    summarise(
      # Adjusted Equity quartiles
      adj_eq_q1 = quantile(adjusted_equity_w, 0.25, na.rm = TRUE),
      adj_eq_q2 = quantile(adjusted_equity_w, 0.50, na.rm = TRUE),  # Median
      adj_eq_q3 = quantile(adjusted_equity_w, 0.75, na.rm = TRUE),
      
      # Uninsured Leverage quartiles
      unins_lev_q1 = quantile(uninsured_lev_w, 0.25, na.rm = TRUE),
      unins_lev_q2 = quantile(uninsured_lev_w, 0.50, na.rm = TRUE),  # Median
      unins_lev_q3 = quantile(uninsured_lev_w, 0.75, na.rm = TRUE),
      
      # MTM Loss quartiles
      mtm_q1 = quantile(mtm_total_w, 0.25, na.rm = TRUE),
      mtm_q2 = quantile(mtm_total_w, 0.50, na.rm = TRUE),  # Median
      mtm_q3 = quantile(mtm_total_w, 0.75, na.rm = TRUE)
    )
  
  data %>%
    mutate(
      # Run Risk Dummies (based on WINSORIZED values before standardization)
      # Risk 1: Low MTM & Low Uninsured (Reference Category)
      # Risk 2: Low MTM & High Uninsured
      # Risk 3: High MTM & Low Uninsured
      # Risk 4: High MTM & High Uninsured (Insolvent & Illiquid)
      # NOTE: Use replace_na(..., 0L) to handle NA values in underlying variables
      run_risk_1 = replace_na(as.integer(mtm_total_w < medians$median_mtm & uninsured_lev_w < medians$median_uninsured), 0L),
      run_risk_2 = replace_na(as.integer(mtm_total_w < medians$median_mtm & uninsured_lev_w >= medians$median_uninsured), 0L),
      run_risk_3 = replace_na(as.integer(mtm_total_w >= medians$median_mtm & uninsured_lev_w < medians$median_uninsured), 0L),
      run_risk_4 = replace_na(as.integer(mtm_total_w >= medians$median_mtm & uninsured_lev_w >= medians$median_uninsured), 0L),
      
      # Store medians for reference
      median_mtm_used = medians$median_mtm,
      median_uninsured_used = medians$median_uninsured,
      
      # ==========================================================================
      # QUARTILE INDICATORS: Adjusted Equity
      # Q1 = Bottom 25% (lowest equity), Q4 = Top 25% (highest equity)
      # NOTE: Use replace_na(..., 0L) to handle NA values in underlying variables
      # ==========================================================================
      adj_equity_q1 = replace_na(as.integer(adjusted_equity_w <= quartiles$adj_eq_q1), 0L),
      adj_equity_q2 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q1 & adjusted_equity_w <= quartiles$adj_eq_q2), 0L),
      adj_equity_q3 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q2 & adjusted_equity_w <= quartiles$adj_eq_q3), 0L),
      adj_equity_q4 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q3), 0L),
      
      # Adjusted Equity quartile (factor for regressions)
      adj_equity_quartile = case_when(
        adjusted_equity_w <= quartiles$adj_eq_q1 ~ "Q1 (Lowest)",
        adjusted_equity_w <= quartiles$adj_eq_q2 ~ "Q2",
        adjusted_equity_w <= quartiles$adj_eq_q3 ~ "Q3",
        TRUE ~ "Q4 (Highest)"
      ),
      adj_equity_quartile = factor(adj_equity_quartile, 
                                   levels = c("Q4 (Highest)", "Q3", "Q2", "Q1 (Lowest)")),
      
      # ==========================================================================
      # QUARTILE INDICATORS: Uninsured Leverage
      # Q1 = Bottom 25% (lowest uninsured), Q4 = Top 25% (highest uninsured risk)
      # NOTE: Use replace_na(..., 0L) to handle any NA values in underlying variables
      # ==========================================================================
      unins_lev_q1 = replace_na(as.integer(uninsured_lev_w <= quartiles$unins_lev_q1), 0L),
      unins_lev_q2 = replace_na(as.integer(uninsured_lev_w > quartiles$unins_lev_q1 & uninsured_lev_w <= quartiles$unins_lev_q2), 0L),
      unins_lev_q3 = replace_na(as.integer(uninsured_lev_w > quartiles$unins_lev_q2 & uninsured_lev_w <= quartiles$unins_lev_q3), 0L),
      unins_lev_q4 = replace_na(as.integer(uninsured_lev_w > quartiles$unins_lev_q3), 0L),
      
      # Uninsured Leverage quartile (factor for regressions)
      unins_lev_quartile = case_when(
        uninsured_lev_w <= quartiles$unins_lev_q1 ~ "Q1 (Lowest)",
        uninsured_lev_w <= quartiles$unins_lev_q2 ~ "Q2",
        uninsured_lev_w <= quartiles$unins_lev_q3 ~ "Q3",
        TRUE ~ "Q4 (Highest)"
      ),
      unins_lev_quartile = factor(unins_lev_quartile, 
                                  levels = c("Q1 (Lowest)", "Q2", "Q3", "Q4 (Highest)")),
      
      # ==========================================================================
      # QUARTILE INDICATORS: MTM Loss
      # Q1 = Bottom 25% (lowest MTM loss), Q4 = Top 25% (highest MTM loss)
      # NOTE: Use replace_na(..., 0L) to handle any NA values in underlying variables
      # ==========================================================================
      mtm_q1 = replace_na(as.integer(mtm_total_w <= quartiles$mtm_q1), 0L),
      mtm_q2 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q1 & mtm_total_w <= quartiles$mtm_q2), 0L),
      mtm_q3 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q2 & mtm_total_w <= quartiles$mtm_q3), 0L),
      mtm_q4 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q3), 0L),
      
      # MTM Loss quartile (factor for regressions)
      mtm_quartile = case_when(
        mtm_total_w <= quartiles$mtm_q1 ~ "Q1 (Lowest)",
        mtm_total_w <= quartiles$mtm_q2 ~ "Q2",
        mtm_total_w <= quartiles$mtm_q3 ~ "Q3",
        TRUE ~ "Q4 (Highest)"
      ),
      mtm_quartile = factor(mtm_quartile, 
                            levels = c("Q1 (Lowest)", "Q2", "Q3", "Q4 (Highest)")),
      
      # ==========================================================================
      # QUARTILE-BASED RISK CATEGORIES: MTM x Uninsured Leverage
      # 16-cell matrix based on quartile combinations
      # ==========================================================================
      # Simplified 4-category version using extreme quartiles
      # High Risk = Q4 MTM & Q4 Uninsured
      # MTM Risk = Q4 MTM & Q1-Q2 Uninsured
      # Liquidity Risk = Q1-Q2 MTM & Q4 Uninsured
      # Low Risk = Q1-Q2 MTM & Q1-Q2 Uninsured
      
      mtm_unins_risk_q = case_when(
        mtm_total_w > quartiles$mtm_q3 & uninsured_lev_w > quartiles$unins_lev_q3 ~ "Dual Risk (Q4-Q4)",
        mtm_total_w > quartiles$mtm_q3 & uninsured_lev_w <= quartiles$unins_lev_q2 ~ "MTM Risk (Q4, Q1-Q2)",
        mtm_total_w <= quartiles$mtm_q2 & uninsured_lev_w > quartiles$unins_lev_q3 ~ "Liquidity Risk (Q1-Q2, Q4)",
        mtm_total_w <= quartiles$mtm_q2 & uninsured_lev_w <= quartiles$unins_lev_q2 ~ "Low Risk (Q1-Q2, Q1-Q2)",
        TRUE ~ "Medium Risk"
      ),
      mtm_unins_risk_q = factor(mtm_unins_risk_q, 
                                levels = c("Low Risk (Q1-Q2, Q1-Q2)", "Medium Risk", 
                                           "MTM Risk (Q4, Q1-Q2)", "Liquidity Risk (Q1-Q2, Q4)", 
                                           "Dual Risk (Q4-Q4)")),
      
      # Binary indicators for quartile-based risk categories
      risk_q_low = as.integer(mtm_unins_risk_q == "Low Risk (Q1-Q2, Q1-Q2)"),
      risk_q_medium = as.integer(mtm_unins_risk_q == "Medium Risk"),
      risk_q_mtm = as.integer(mtm_unins_risk_q == "MTM Risk (Q4, Q1-Q2)"),
      risk_q_liquidity = as.integer(mtm_unins_risk_q == "Liquidity Risk (Q1-Q2, Q4)"),
      risk_q_dual = as.integer(mtm_unins_risk_q == "Dual Risk (Q4-Q4)"),
      
      # ==========================================================================
      # 16 COMBINATION DUMMIES: Adjusted Equity x Uninsured Leverage Quartiles
      # Format: aeq{1-4}_ulq{1-4} = 1 if bank in that cell, 0 otherwise
      # Reference: aeq4_ulq1 = Highest Equity + Lowest Uninsured (safest)
      # NOTE: Use replace_na(..., 0L) to handle any NA values in source variables
      # ==========================================================================
      # AdjEq Q1 (Lowest Equity) combinations
      aeq1_ulq1 = replace_na(as.integer(adjusted_equity_w <= quartiles$adj_eq_q1 & 
                             uninsured_lev_w <= quartiles$unins_lev_q1), 0L),
      aeq1_ulq2 = replace_na(as.integer(adjusted_equity_w <= quartiles$adj_eq_q1 & 
                             uninsured_lev_w > quartiles$unins_lev_q1 & uninsured_lev_w <= quartiles$unins_lev_q2), 0L),
      aeq1_ulq3 = replace_na(as.integer(adjusted_equity_w <= quartiles$adj_eq_q1 & 
                             uninsured_lev_w > quartiles$unins_lev_q2 & uninsured_lev_w <= quartiles$unins_lev_q3), 0L),
      aeq1_ulq4 = replace_na(as.integer(adjusted_equity_w <= quartiles$adj_eq_q1 & 
                             uninsured_lev_w > quartiles$unins_lev_q3), 0L),
      
      # AdjEq Q2 combinations
      aeq2_ulq1 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q1 & adjusted_equity_w <= quartiles$adj_eq_q2 & 
                             uninsured_lev_w <= quartiles$unins_lev_q1), 0L),
      aeq2_ulq2 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q1 & adjusted_equity_w <= quartiles$adj_eq_q2 & 
                             uninsured_lev_w > quartiles$unins_lev_q1 & uninsured_lev_w <= quartiles$unins_lev_q2), 0L),
      aeq2_ulq3 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q1 & adjusted_equity_w <= quartiles$adj_eq_q2 & 
                             uninsured_lev_w > quartiles$unins_lev_q2 & uninsured_lev_w <= quartiles$unins_lev_q3), 0L),
      aeq2_ulq4 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q1 & adjusted_equity_w <= quartiles$adj_eq_q2 & 
                             uninsured_lev_w > quartiles$unins_lev_q3), 0L),
      
      # AdjEq Q3 combinations
      aeq3_ulq1 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q2 & adjusted_equity_w <= quartiles$adj_eq_q3 & 
                             uninsured_lev_w <= quartiles$unins_lev_q1), 0L),
      aeq3_ulq2 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q2 & adjusted_equity_w <= quartiles$adj_eq_q3 & 
                             uninsured_lev_w > quartiles$unins_lev_q1 & uninsured_lev_w <= quartiles$unins_lev_q2), 0L),
      aeq3_ulq3 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q2 & adjusted_equity_w <= quartiles$adj_eq_q3 & 
                             uninsured_lev_w > quartiles$unins_lev_q2 & uninsured_lev_w <= quartiles$unins_lev_q3), 0L),
      aeq3_ulq4 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q2 & adjusted_equity_w <= quartiles$adj_eq_q3 & 
                             uninsured_lev_w > quartiles$unins_lev_q3), 0L),
      
      # AdjEq Q4 (Highest Equity) combinations
      aeq4_ulq1 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q3 & 
                             uninsured_lev_w <= quartiles$unins_lev_q1), 0L),  # REFERENCE (safest)
      aeq4_ulq2 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q3 & 
                             uninsured_lev_w > quartiles$unins_lev_q1 & uninsured_lev_w <= quartiles$unins_lev_q2), 0L),
      aeq4_ulq3 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q3 & 
                             uninsured_lev_w > quartiles$unins_lev_q2 & uninsured_lev_w <= quartiles$unins_lev_q3), 0L),
      aeq4_ulq4 = replace_na(as.integer(adjusted_equity_w > quartiles$adj_eq_q3 & 
                             uninsured_lev_w > quartiles$unins_lev_q3), 0L),
      
      # ==========================================================================
      # EXTREME RISK INDICATORS (Top quartile thresholds)
      # NOTE: Use replace_na(..., 0L) to handle any NA values in underlying variables
      # ==========================================================================
      high_mtm_q4 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q3), 0L),
      high_unins_q4 = replace_na(as.integer(uninsured_lev_w > quartiles$unins_lev_q3), 0L),
      low_adj_equity_q1 = replace_na(as.integer(adjusted_equity_w <= quartiles$adj_eq_q1), 0L),
      
      # ==========================================================================
      # 16 COMBINATION DUMMIES: MTM Loss x Uninsured Leverage Quartiles
      # Format: mtmq{1-4}_ulq{1-4} = 1 if bank in that cell, 0 otherwise
      # Reference: mtmq1_ulq1 = Lowest MTM Loss + Lowest Uninsured (safest)
      # ==========================================================================
      # MTM Q1 (Lowest MTM Loss) combinations
      mtmq1_ulq1 = replace_na(as.integer(mtm_total_w <= quartiles$mtm_q1 & 
                             uninsured_lev_w <= quartiles$unins_lev_q1), 0L),  # REFERENCE (safest)
      mtmq1_ulq2 = replace_na(as.integer(mtm_total_w <= quartiles$mtm_q1 & 
                             uninsured_lev_w > quartiles$unins_lev_q1 & uninsured_lev_w <= quartiles$unins_lev_q2), 0L),
      mtmq1_ulq3 = replace_na(as.integer(mtm_total_w <= quartiles$mtm_q1 & 
                             uninsured_lev_w > quartiles$unins_lev_q2 & uninsured_lev_w <= quartiles$unins_lev_q3), 0L),
      mtmq1_ulq4 = replace_na(as.integer(mtm_total_w <= quartiles$mtm_q1 & 
                             uninsured_lev_w > quartiles$unins_lev_q3), 0L),
      
      # MTM Q2 combinations
      mtmq2_ulq1 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q1 & mtm_total_w <= quartiles$mtm_q2 & 
                             uninsured_lev_w <= quartiles$unins_lev_q1), 0L),
      mtmq2_ulq2 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q1 & mtm_total_w <= quartiles$mtm_q2 & 
                             uninsured_lev_w > quartiles$unins_lev_q1 & uninsured_lev_w <= quartiles$unins_lev_q2), 0L),
      mtmq2_ulq3 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q1 & mtm_total_w <= quartiles$mtm_q2 & 
                             uninsured_lev_w > quartiles$unins_lev_q2 & uninsured_lev_w <= quartiles$unins_lev_q3), 0L),
      mtmq2_ulq4 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q1 & mtm_total_w <= quartiles$mtm_q2 & 
                             uninsured_lev_w > quartiles$unins_lev_q3), 0L),
      
      # MTM Q3 combinations
      mtmq3_ulq1 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q2 & mtm_total_w <= quartiles$mtm_q3 & 
                             uninsured_lev_w <= quartiles$unins_lev_q1), 0L),
      mtmq3_ulq2 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q2 & mtm_total_w <= quartiles$mtm_q3 & 
                             uninsured_lev_w > quartiles$unins_lev_q1 & uninsured_lev_w <= quartiles$unins_lev_q2), 0L),
      mtmq3_ulq3 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q2 & mtm_total_w <= quartiles$mtm_q3 & 
                             uninsured_lev_w > quartiles$unins_lev_q2 & uninsured_lev_w <= quartiles$unins_lev_q3), 0L),
      mtmq3_ulq4 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q2 & mtm_total_w <= quartiles$mtm_q3 & 
                             uninsured_lev_w > quartiles$unins_lev_q3), 0L),
      
      # MTM Q4 (Highest MTM Loss) combinations
      mtmq4_ulq1 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q3 & 
                             uninsured_lev_w <= quartiles$unins_lev_q1), 0L),
      mtmq4_ulq2 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q3 & 
                             uninsured_lev_w > quartiles$unins_lev_q1 & uninsured_lev_w <= quartiles$unins_lev_q2), 0L),
      mtmq4_ulq3 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q3 & 
                             uninsured_lev_w > quartiles$unins_lev_q2 & uninsured_lev_w <= quartiles$unins_lev_q3), 0L),
      mtmq4_ulq4 = replace_na(as.integer(mtm_total_w > quartiles$mtm_q3 & 
                             uninsured_lev_w > quartiles$unins_lev_q3), 0L),
      
      # Store quartile cutoffs for reference
      adj_eq_q1_used = quartiles$adj_eq_q1,
      adj_eq_q2_used = quartiles$adj_eq_q2,
      adj_eq_q3_used = quartiles$adj_eq_q3,
      unins_lev_q1_used = quartiles$unins_lev_q1,
      unins_lev_q2_used = quartiles$unins_lev_q2,
      unins_lev_q3_used = quartiles$unins_lev_q3,
      mtm_q1_used = quartiles$mtm_q1,
      mtm_q2_used = quartiles$mtm_q2,
      mtm_q3_used = quartiles$mtm_q3
    )
}

# ==============================================================================
# CREATE BASELINE DATASETS
# ==============================================================================
# Baseline sample (2022Q4, OMO-eligible only for main analysis)



df_2022q4 <- call_q %>%
  filter(period == BASELINE_MAIN, !idrssd %in% excluded_banks, 
         !is.na(omo_eligible) & omo_eligible > 0) %>%
  construct_analysis_vars() %>%
  add_run_risk_dummies()

df_2023q3 <- call_q %>%
  filter(period == BASELINE_ARB, !idrssd %in% excluded_banks,
         !is.na(omo_eligible) & omo_eligible > 0) %>%
  construct_analysis_vars() %>%
  add_run_risk_dummies()

df_2023q4 <- call_q %>%
  filter(period == BASELINE_WIND, !idrssd %in% excluded_banks,
         !is.na(omo_eligible) & omo_eligible > 0) %>%
  construct_analysis_vars() %>%
  add_run_risk_dummies()

cat("\n=== BASELINE DATASETS ===\n")
## 
## === BASELINE DATASETS ===
cat("2022Q4:", nrow(df_2022q4), "banks\n")
## 2022Q4: 4292 banks
cat("2023Q3:", nrow(df_2023q3), "banks\n")
## 2023Q3: 4214 banks
cat("2023Q4:", nrow(df_2023q4), "banks\n")
## 2023Q4: 4197 banks
# Verify no NAs in key variables after filtering
cat("\n=== DATA QUALITY CHECK (should all be 0) ===\n")
## 
## === DATA QUALITY CHECK (should all be 0) ===
na_check <- df_2022q4 %>%
  summarise(
    adjusted_equity_w_NAs = sum(is.na(adjusted_equity_w)),
    uninsured_lev_w_NAs = sum(is.na(uninsured_lev_w)),
    mtm_total_w_NAs = sum(is.na(mtm_total_w))
  )
cat("NAs in 2022Q4 key variables:\n")
## NAs in 2022Q4 key variables:
print(na_check)
## # A tibble: 1 × 3
##   adjusted_equity_w_NAs uninsured_lev_w_NAs mtm_total_w_NAs
##                   <int>               <int>           <int>
## 1                    10                   0              10
# Verify 16 combination dummies have no NAs and sum to 1 for each bank
combo_check <- df_2022q4 %>%
  mutate(combo_sum = aeq1_ulq1 + aeq1_ulq2 + aeq1_ulq3 + aeq1_ulq4 +
                     aeq2_ulq1 + aeq2_ulq2 + aeq2_ulq3 + aeq2_ulq4 +
                     aeq3_ulq1 + aeq3_ulq2 + aeq3_ulq3 + aeq3_ulq4 +
                     aeq4_ulq1 + aeq4_ulq2 + aeq4_ulq3 + aeq4_ulq4) %>%
  count(combo_sum, name = "N_banks")
cat("\n16 combination dummy row sums (should all equal 1):\n")
## 
## 16 combination dummy row sums (should all equal 1):
print(combo_check)
## # A tibble: 2 × 2
##   combo_sum N_banks
##       <int>   <int>
## 1         0      10
## 2         1    4282
cat("\n=== RUN RISK MEDIANS (2022Q4) ===\n")
## 
## === RUN RISK MEDIANS (2022Q4) ===
cat("Median MTM Loss (winsorized):", round(df_2022q4$median_mtm_used[1], 4), "%\n")
## Median MTM Loss (winsorized): 5.32 %
cat("Median Uninsured Leverage (winsorized):", round(df_2022q4$median_uninsured_used[1], 4), "%\n")
## Median Uninsured Leverage (winsorized): 22.25 %
cat("\n=== QUARTILE CUTOFFS (2022Q4) ===\n")
## 
## === QUARTILE CUTOFFS (2022Q4) ===
cat("Adjusted Equity: Q1=", round(df_2022q4$adj_eq_q1_used[1], 4), 
    "%, Q2(Med)=", round(df_2022q4$adj_eq_q2_used[1], 4), 
    "%, Q3=", round(df_2022q4$adj_eq_q3_used[1], 4), "%\n")
## Adjusted Equity: Q1= 0.8534 %, Q2(Med)= 3.621 %, Q3= 6.435 %
cat("Uninsured Lev:   Q1=", round(df_2022q4$unins_lev_q1_used[1], 4), 
    "%, Q2(Med)=", round(df_2022q4$unins_lev_q2_used[1], 4), 
    "%, Q3=", round(df_2022q4$unins_lev_q3_used[1], 4), "%\n")
## Uninsured Lev:   Q1= 15.29 %, Q2(Med)= 22.25 %, Q3= 30.35 %
cat("MTM Loss:        Q1=", round(df_2022q4$mtm_q1_used[1], 4), 
    "%, Q2(Med)=", round(df_2022q4$mtm_q2_used[1], 4), 
    "%, Q3=", round(df_2022q4$mtm_q3_used[1], 4), "%\n")
## MTM Loss:        Q1= 3.863 %, Q2(Med)= 5.32 %, Q3= 6.975 %

2.12 Join Borrower Indicators and Create Clean Samples

# ==============================================================================
# CRITICAL: CLEAN SAMPLE CONSTRUCTION
# For binary outcomes: 0 = PURE NON-BORROWERS ONLY
# Exclude other facility users from the 0 category
# ==============================================================================

# Join all borrower indicators to baseline
join_all_borrowers <- function(df_acute, btfp_df, dw_df, btfp_var, dw_var) {
  
  df_acute %>%
    left_join(btfp_df %>% select(idrssd, starts_with(btfp_var)), by = "idrssd") %>%
    left_join(dw_df %>% select(idrssd, starts_with(dw_var)), by = "idrssd") %>%
    mutate(
      # Fill NAs with 0
      "{btfp_var}" := replace_na(!!sym(btfp_var), 0L),
      "{dw_var}" := replace_na(!!sym(dw_var), 0L),
      
      # FHLB user (from call report)
      fhlb_user = as.integer(abnormal_fhlb_borrowing_10pct == 1),
      
      # User group classification (for descriptive tables)
      user_group = case_when(
        !!sym(btfp_var) == 1 & !!sym(dw_var) == 1 ~ "Both",
        !!sym(btfp_var) == 1 & !!sym(dw_var) == 0 ~ "BTFP_Only",
        !!sym(btfp_var) == 0 & !!sym(dw_var) == 1 ~ "DW_Only",
        TRUE ~ "Neither"
      ),
      user_group = factor(user_group, levels = c("Neither", "BTFP_Only", "DW_Only", "Both")),
      
      # Combined indicators
      any_fed = as.integer(!!sym(btfp_var) == 1 | !!sym(dw_var) == 1),
      both_fed = as.integer(!!sym(btfp_var) == 1 & !!sym(dw_var) == 1),
      all_user = as.integer(!!sym(btfp_var) == 1 | !!sym(dw_var) == 1 | fhlb_user == 1),
      
      # PURE non-user (no BTFP, no DW, no FHLB) - for clean comparison
      non_user = as.integer(!!sym(btfp_var) == 0 & !!sym(dw_var) == 0 & fhlb_user == 0)
    )
}

# ==============================================================================
# CREATE PERIOD DATASETS
# ==============================================================================

# Acute Period (Mar 13 - May 1, 2023)
df_acute <- join_all_borrowers(df_2022q4, btfp_acute, dw_acute, "btfp_acute", "dw_acute") %>%
  mutate(
    # Intensive margin variables
    btfp_pct = ifelse(btfp_acute == 1 & btfp_acute_amt > 0, 
                      100 * btfp_acute_amt / (total_asset * 1000), NA_real_),
    dw_pct = ifelse(dw_acute == 1 & dw_acute_amt > 0, 
                    100 * dw_acute_amt / (total_asset * 1000), NA_real_),
    log_btfp_amt = ifelse(btfp_acute == 1 & btfp_acute_amt > 0, log(btfp_acute_amt), NA_real_),
    log_dw_amt = ifelse(dw_acute == 1 & dw_acute_amt > 0, log(dw_acute_amt), NA_real_)
  )

# March 10 (SVB Closure Day)
df_mar10 <- join_all_borrowers(df_2022q4, btfp_mar10, dw_mar10, "btfp_mar10", "dw_mar10")

# March 10-13 (BTFP Launch Window)
df_mar10_13 <- join_all_borrowers(df_2022q4, btfp_mar10_13, dw_mar10_13, "btfp_mar10_13", "dw_mar10_13")

# Pre-BTFP (Mar 1 - Mar 12)
btfp_prebtfp <- create_borrower_indicator(
  btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount", 
  DATE_MAR01, DATE_MAR12, "btfp_prebtfp"
)
df_prebtfp <- join_all_borrowers(df_2022q4, btfp_prebtfp, dw_prebtfp, "btfp_prebtfp", "dw_prebtfp")

# Post-Acute Period (May 2 - Oct 31, 2023)
df_post <- df_2022q4 %>%
  left_join(btfp_post %>% select(idrssd, btfp_post, btfp_post_amt), by = "idrssd") %>%
  left_join(dw_post %>% select(idrssd, dw_post, dw_post_amt), by = "idrssd") %>%
  mutate(
    btfp_post = replace_na(btfp_post, 0L),
    dw_post = replace_na(dw_post, 0L),
    fhlb_user = as.integer(abnormal_fhlb_borrowing_10pct == 1),
    any_fed = as.integer(btfp_post == 1 | dw_post == 1),
    non_user = as.integer(btfp_post == 0 & dw_post == 0 & fhlb_user == 0),
    user_group = case_when(
      btfp_post == 1 & dw_post == 1 ~ "Both",
      btfp_post == 1 ~ "BTFP_Only",
      dw_post == 1 ~ "DW_Only",
      TRUE ~ "Neither"
    ),
    user_group = factor(user_group, levels = c("Neither", "BTFP_Only", "DW_Only", "Both"))
  )

# Arbitrage Period (Nov 1, 2023 - Jan 24, 2024) - 2023Q3 Baseline
df_arb <- df_2023q3 %>%
  left_join(btfp_arb %>% select(idrssd, btfp_arb, btfp_arb_amt), by = "idrssd") %>%
  mutate(
    btfp_arb = replace_na(btfp_arb, 0L),
    fhlb_user = as.integer(abnormal_fhlb_borrowing_10pct == 1),
    any_fed = btfp_arb,
    non_user = as.integer(btfp_arb == 0 & fhlb_user == 0),
    user_group = factor(ifelse(btfp_arb == 1, "BTFP_Only", "Neither"),
                        levels = c("Neither", "BTFP_Only", "DW_Only", "Both"))
  )

# Wind-down Period (Jan 25 - Mar 11, 2024) - 2023Q4 Baseline
df_wind <- df_2023q4 %>%
  left_join(btfp_wind %>% select(idrssd, btfp_wind, btfp_wind_amt), by = "idrssd") %>%
  mutate(
    btfp_wind = replace_na(btfp_wind, 0L),
    fhlb_user = as.integer(abnormal_fhlb_borrowing_10pct == 1),
    any_fed = btfp_wind,
    non_user = as.integer(btfp_wind == 0 & fhlb_user == 0),
    user_group = factor(ifelse(btfp_wind == 1, "BTFP_Only", "Neither"),
                        levels = c("Neither", "BTFP_Only", "DW_Only", "Both"))
  )

# Overall Period
df_overall <- join_all_borrowers(df_2022q4, btfp_overall, dw_overall, "btfp_overall", "dw_overall")

cat("\n=== USER GROUP COUNTS (ACUTE PERIOD) ===\n")
## 
## === USER GROUP COUNTS (ACUTE PERIOD) ===
print(table(df_acute$user_group))
## 
##   Neither BTFP_Only   DW_Only      Both 
##      3531       368       299        94
cat("\nPure Non-Users:", sum(df_acute$non_user), "\n")
## 
## Pure Non-Users: 3285

2.13 Model Setup

# ==============================================================================
# MODEL SETTINGS (DEFINE ONCE, USE EVERYWHERE)
# ==============================================================================

# Standard Controls (Z-score standardized)
CONTROLS <- "ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + wholesale + roa"

# Coefficient Labels for Output
COEF_MAP <- c(
  # Key explanatory (standardized - interpret as 1 SD change)
  "mtm_total" = "MTM Loss (z)",
  "mtm_btfp" = "MTM Loss OMO (z)",
  "uninsured_lev" = "Uninsured Lev (z)",
  "mtm_x_uninsured" = "MTM ? Uninsured",
  "mtm_total:uninsured_lev" = "MTM ? Uninsured",
  "mtm_btfp:uninsured_lev" = "MTM_btfp ? Uninsured Lev",
  
  # Run Risk dummies (Reference = Risk 1: Low MTM, Low Uninsured)
  "run_risk_1" = "Risk 1: $<$ Med MTM \\& $<$ Med Uninsured",
  "run_risk_2" = "Risk 2: $<$ Med MTM \\& $>$ Med Uninsured",
  "run_risk_3" = "Risk 3: $>$ Med MTM \\& $<$ Med Uninsured",
  "run_risk_4" = "Risk 4: $>$ Med MTM \\& $>$ Med Uninsured",
  
  # Controls (standardized)
  "ln_assets" = "Log(Assets) (z)",
  "cash_ratio" = "Cash Ratio (z)",
  "loan_to_deposit" = "Loan/Deposit (z)",
  "book_equity_ratio" = "Book Equity (z)",
  "wholesale" = "Wholesale (z)",
  "roa" = "ROA (z)"
)

# Helper Function: Build formula dynamically
build_formula <- function(dv, explanatory, controls = CONTROLS) {
  as.formula(paste(dv, "~", explanatory, "+", controls))
}

# GOF statistics
gof_lpm <- c("nobs", "r.squared", "adj.r.squared")
gof_logit <- c("nobs", "logLik", "AIC")

3 DESCRIPTIVE STATISTICS

3.1 Table : Summary Statistics analysis Variables

Table 1: Summary Statistics (2022Q4 Baseline)
Panel Variable N Mean Std. Dev. P10 P25 Median P75 P90
Panel A: Key Explanatory Variables MTM Loss / Total Assets (%) 4282 5.467 2.223 2.791 3.863 5.320 6.975 8.455
Panel A: Key Explanatory Variables MTM Loss (BTFP-Eligible) / Total Assets (%) 4282 0.681 0.848 0.016 0.136 0.405 0.932 1.706
Panel A: Key Explanatory Variables Uninsured Deposits / Total Assets (%) 4292 23.611 12.158 9.604 15.285 22.247 30.353 39.278
Panel A: Key Explanatory Variables Uninsured Deposits / Total Deposits (%) 4292 27.595 14.258 11.388 17.867 25.801 35.108 46.042
Panel B: Size Total Assets ($ thousands) 4292 2617205.000 20350192.000 73831.000 147641.000 334806.000 847713.000 2444123.000
Panel B: Size Log(Total Assets) 4292 12.882 1.483 11.210 11.903 12.721 13.650 14.709
Panel C: Liquidity and Securities Cash / Total Assets (%) 4292 8.148 9.191 1.712 2.667 5.030 10.051 18.015
Panel C: Liquidity and Securities Securities / Total Assets (%) 4292 25.681 15.784 7.079 13.704 23.323 35.090 46.765
Panel D: Loans and Funding Loans / Total Assets (%) 4292 59.990 17.654 36.254 49.604 62.303 73.421 80.308
Panel D: Loans and Funding Loans / Deposits (%) 4292 70.866 27.432 40.909 56.561 71.818 86.500 97.535
Panel D: Loans and Funding Wholesale Funding / Total Liabilities (%) 4292 1.000 3.166 0.000 0.000 0.000 0.533 3.058
Panel D: Loans and Funding FHLB Advances / Total Assets (%) 4292 2.647 4.215 0.000 0.000 0.346 4.073 7.987
Panel E: Capital and Profitability Book Equity / Total Assets (%) 4292 10.220 8.817 5.452 7.143 8.842 10.888 14.114
Panel E: Capital and Profitability Tier 1 Capital / Total Assets (%) 4292 11.844 8.212 8.284 9.085 10.197 11.959 15.122
Panel E: Capital and Profitability Return on Assets (%) 4292 1.163 2.569 0.401 0.704 1.014 1.319 1.680

3.2 Table 2: Summary Statistics by User Group

# ==============================================================================
# USER GROUP COMPARISON TABLE - RAW VARIABLES
# ==============================================================================


# ---- helper: significance stars
format_pval <- function(p) {
  if (is.na(p)) return("")
  if (p < 0.01) "***"
  else if (p < 0.05) "**"
  else if (p < 0.10) "*"
  else ""
}

# ==============================================================================
# FUNCTION: Create User Group Comparison Table with N in header
# - returns HTML table by default (for RMD view)
# - return_df=TRUE returns list(results, group_counts, header_spec)
# ==============================================================================
create_user_group_table <- function(data, period_name, comparison_vars, var_labels,
                                    group_order = c("Neither", "BTFP_Only", "DW_Only", "Both"),
                                    return_df = FALSE,
                                    digits = 3) {

  # --- group counts
  group_counts <- data %>%
    group_by(user_group) %>%
    summarise(N = n(), .groups = "drop") %>%
    mutate(user_group = as.character(user_group))

  # enforce preferred order if possible
  group_counts <- bind_rows(
    group_counts %>% filter(user_group %in% group_order) %>% mutate(.ord = match(user_group, group_order)),
    group_counts %>% filter(!user_group %in% group_order) %>% mutate(.ord = 999)
  ) %>%
    arrange(.ord, user_group) %>%
    select(-.ord)

  groups <- group_counts$user_group

  # --- compute stats
  results <- purrr::map_dfr(comparison_vars, function(v) {

    if (!v %in% names(data)) return(NULL)

    var_label <- ifelse(v %in% names(var_labels), var_labels[[v]], v)
    row_data <- tibble(Variable = var_label)

    for (g in groups) {
      x <- data %>% filter(user_group == g) %>% pull(!!sym(v))
      x <- x[!is.na(x) & is.finite(x)]

      row_data[[paste0(g, "_Mean")]] <- if (length(x) > 0) mean(x) else NA_real_
      row_data[[paste0(g, "_SD")]]   <- if (length(x) > 1) sd(x)   else NA_real_
    }

    # --- Users vs Neither
    if ("Neither" %in% groups) {
      x_neither <- data %>% filter(user_group == "Neither") %>% pull(!!sym(v))
      x_users   <- data %>% filter(user_group != "Neither") %>% pull(!!sym(v))
      x_neither <- x_neither[!is.na(x_neither) & is.finite(x_neither)]
      x_users   <- x_users[!is.na(x_users) & is.finite(x_users)]

      if (length(x_neither) > 1 && length(x_users) > 1) {
        ttest <- t.test(x_users, x_neither)
        row_data$Diff    <- mean(x_users) - mean(x_neither)
        row_data$T_Stat  <- as.numeric(ttest$statistic)
        row_data$P_Value <- as.numeric(ttest$p.value)
        row_data$Sig     <- format_pval(row_data$P_Value)
      } else {
        row_data$Diff <- NA_real_
        row_data$T_Stat <- NA_real_
        row_data$P_Value <- NA_real_
        row_data$Sig <- ""
      }
    }

    row_data
  })

  # --- build header with N (each group spans 2 columns: Mean/SD)
  header_spec <- c(" " = 1)
  for (i in seq_len(nrow(group_counts))) {
    g <- group_counts$user_group[i]
    n <- group_counts$N[i]
    header_spec[paste0(g, " (N=", format(n, big.mark = ","), ")")] <- 2
  }
  header_spec["Users vs. Non-users"] <- 4

  # --- reorder columns to match header
  ordered_cols <- c("Variable")
  for (g in groups) ordered_cols <- c(ordered_cols, paste0(g, "_Mean"), paste0(g, "_SD"))
  ordered_cols <- c(ordered_cols, "Diff", "T_Stat", "P_Value", "Sig")

  results <- results %>% select(any_of(ordered_cols))

  # return underlying pieces if requested
  if (return_df) {
    return(list(results = results, group_counts = group_counts, header_spec = header_spec))
  }

  # --- display HTML table in RMD
  results %>%
    mutate(across(where(is.numeric), ~round(., digits))) %>%
    kable(
      format = "html",
      caption = paste0("User Group Comparison: ", period_name),
      col.names = c(
        "Variable",
        rep(c("Mean", "SD"), length(groups)),
        "Diff", "t-stat", "p-value", "Sig."
      ),
      align = c("l", rep("r", length(groups) * 2 + 4)),
      escape = FALSE
    )
}

# ==============================================================================
# FUNCTION: Save User Group Comparison Table (HTML + LaTeX) WITH SAME N HEADER
# ==============================================================================
save_user_group_table <- function(data, period_name, comparison_vars, var_labels,
                                  filename, notes_text,
                                  group_order = c("Neither", "BTFP_Only", "DW_Only", "Both"),
                                  digits = 3) {

  tbl_data <- create_user_group_table(
    data, period_name, comparison_vars, var_labels,
    group_order = group_order,
    return_df = TRUE,
    digits = digits
  )

  results <- tbl_data$results %>%
    mutate(across(where(is.numeric), ~round(., digits)))

  header_spec <- tbl_data$header_spec
  groups <- tbl_data$group_counts$user_group

  # --- LaTeX save only
  latex_tbl <- results %>%
    kable(
      format = "latex",
      caption = paste0("User Group Comparison: ", period_name),
      booktabs = TRUE,
      escape = FALSE,
      col.names = c(
        "Variable",
        rep(c("Mean", "SD"), length(groups)),
        "Diff", "$t$", "$p$", "Sig."
      ),
      align = c("l", rep("r", length(groups) * 2 + 4))
    )
  safe_writeLines(latex_tbl, file.path(TABLE_PATH, paste0(filename, ".tex")))

  message("Saved: ", filename, ".tex")
}


# Save Table 2A (TEX only)
save_user_group_table(
  df_acute,
  "Acute Period (Mar 13 - May 1, 2023)",
  desc_vars_raw,
  var_labels_desc,
  "Table_UserGroup_Acute",
  notes_text = paste(
    "This table compares bank characteristics across facility user groups during the acute crisis period.",
    "User groups: Neither = banks that did not access BTFP or the Discount Window (DW);",
    "BTFP Only = BTFP users only; DW Only = DW users only; Both = used both facilities.",
    "Diff = difference between all facility users (BTFP Only, DW Only, Both pooled) and non-users (Neither).",
    "t-statistics and p-values are from two-sample tests of equality of means (Users vs. Non-users).",
    "Significance: *** p<0.01, ** p<0.05, * p<0.10.",
    "Variables are measured using 2022Q4 Call Reports; winsorization as in main text (if applicable)."
  )
)

# Display in RMD (must be last expression for auto-render)
create_user_group_table(
  df_acute,
  "Acute Period (Mar 13 - May 1, 2023)",
  desc_vars_raw,
  var_labels_desc
)
User Group Comparison: Acute Period (Mar 13 - May 1, 2023)
Variable Mean SD Mean SD Mean SD Mean SD Diff t-stat p-value Sig.
MTM Loss / Total Assets (%) 5.358 2.255 6.180 1.996 5.748 2.007 5.864 1.847 0.613 7.515 0.000 ***
MTM Loss (BTFP-Eligible) / Total Assets (%) 0.649 0.823 0.884 1.012 0.741 0.830 0.916 0.959 0.183 4.990 0.000 ***
Uninsured Deposits / Total Assets (%) 22.827 12.045 26.205 11.528 26.847 11.503 32.635 14.116 4.425 9.203 0.000 ***
Uninsured Deposits / Total Deposits (%) 26.654 14.114 30.619 13.414 31.580 13.597 38.415 16.616 5.305 9.405 0.000 ***
Total Assets ($ thousands) 1881481.138 16032224.247 5420631.872 34838553.603 5521943.913 34441253.995 10039151.160 25033435.689 4149443.919 3.324 0.001 ***
Log(Total Assets) 12.688 1.401 13.537 1.441 13.848 1.465 14.552 1.723 1.097 18.299 0.000 ***
Cash / Total Assets (%) 8.767 9.685 4.695 4.597 6.161 6.592 4.719 5.327 -3.493 -13.435 0.000 ***
Securities / Total Assets (%) 25.500 16.092 28.272 13.781 23.956 14.302 27.803 14.809 1.019 1.747 0.081
Loans / Total Assets (%) 59.474 18.176 61.338 14.104 63.834 15.442 61.845 14.972 2.907 4.713 0.000 ***
Loans / Deposits (%) 70.197 28.727 72.462 19.162 75.830 21.188 73.941 19.846 3.771 4.312 0.000 ***
Wholesale Funding / Total Liabilities (%) 0.877 3.056 1.518 3.387 1.562 3.973 1.822 2.969 0.696 4.986 0.000 ***
FHLB Advances / Total Assets (%) 2.420 4.052 3.953 4.649 3.252 4.411 4.147 6.068 1.282 6.902 0.000 ***
Book Equity / Total Assets (%) 10.587 9.572 8.177 3.092 9.046 3.223 8.194 2.678 -2.066 -10.496 0.000 ***
Tier 1 Capital / Total Assets (%) 12.180 8.950 10.314 2.318 10.461 2.652 9.620 1.582 -1.894 -10.896 0.000 ***
Return on Assets (%) 1.177 2.819 1.064 0.543 1.140 0.649 1.097 0.561 -0.080 -1.531 0.126

3.3 Table 3: Summary Statistics by solvent vs insolvent borrower comparison

# ==============================================================================
# MASTER SCRIPT: SOLVENT VS. INSOLVENT BORROWING ANALYSIS
# ==============================================================================

# ==============================================================================
# 1. HELPER FUNCTIONS
# ==============================================================================

create_asset_size_bins <- function(data) {
  data %>%
    mutate(
      total_asset_billions = total_asset / 1e6,
      asset_size = case_when(
        total_asset_billions >= 700 ~ "$700B and Above",
        total_asset_billions >= 250 ~ "$250B--$700B",
        total_asset_billions >= 100 ~ "$100B--$250B",
        total_asset_billions >= 50  ~ "$50B--$100B",
        total_asset_billions >= 20  ~ "$20B--$50B",
        total_asset_billions >= 10  ~ "$10B--$20B",
        total_asset_billions >= 5   ~ "$5B--$10B",
        total_asset_billions >= 3   ~ "$3B--$5B",
        total_asset_billions >= 1   ~ "$1B--$3B",
        total_asset_billions >= 0.5 ~ "$500M--$1B",
        TRUE ~ "Below $500M"
      ),
      asset_size = factor(asset_size, levels = c(
        "$700B and Above", "$250B--$700B", "$100B--$250B", "$50B--$100B",
        "$20B--$50B", "$10B--$20B", "$5B--$10B", "$3B--$5B",
        "$1B--$3B", "$500M--$1B", "Below $500M"
      ))
    )
}

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

# ==============================================================================
# 2. TABLE 1: BORROWING RATES BY SOLVENCY STATUS
# ==============================================================================
# Question: Do insolvent and solvent banks borrow at different rates?

generate_borrowing_rates_by_solvency <- function(data) {
  
  definitions <- list(
    list(id = 1, label = "MTM insolvency", var = "insolvent_case1"),
    list(id = 2, label = "IDCR $<$ 0 (100\\% run)", var = "insolvent_case2"),
    list(id = 3, label = "IDCR $<$ 0 (50\\% run)", var = "insolvent_case3")
  )
  
  df_proc <- data %>%
    mutate(
      any_fed_acute = as.integer(btfp_acute == 1 | dw_acute == 1),
      insolvent_case1 = as.integer(mm_asset < total_liability),
      IDC_case2 = ifelse(insured_deposit > 0, 
                         (mm_asset - 1.0 * uninsured_deposit - insured_deposit) / insured_deposit, 
                         NA_real_),
      insolvent_case2 = as.integer(IDC_case2 < 0),
      IDC_case3 = ifelse(insured_deposit > 0, 
                         (mm_asset - 0.5 * uninsured_deposit - insured_deposit) / insured_deposit, 
                         NA_real_),
      insolvent_case3 = as.integer(IDC_case3 < 0)
    )

  calc_row <- function(def) {
    insolv <- df_proc %>% filter(!!sym(def$var) == 1)
    solv   <- df_proc %>% filter(!!sym(def$var) == 0)
    
    get_stats <- function(var_name) {
      n_i <- sum(!is.na(insolv[[var_name]]))
      n_s <- sum(!is.na(solv[[var_name]]))
      x_i <- sum(insolv[[var_name]], na.rm = TRUE)
      x_s <- sum(solv[[var_name]], na.rm = TRUE)
      r_i <- x_i / max(n_i, 1)
      r_s <- x_s / max(n_s, 1)
      
      # Proportion test (more appropriate than t-test for binary outcomes)
      ptest <- tryCatch(
        prop.test(c(x_i, x_s), c(n_i, n_s)),
        error = function(e) list(p.value = NA)
      )
      
      list(
        i_n = x_i,
        i_rate = r_i,
        s_n = x_s,
        s_rate = r_s,
        diff = r_i - r_s,
        pval = ptest$p.value,
        stars = format_pval_stars(ptest$p.value)
      )
    }
    
    btfp <- get_stats("btfp_acute")
    dw   <- get_stats("dw_acute")
    any_f <- get_stats("any_fed_acute")
    
    tibble(
      Definition = def$label,
      # Insolvent
      I_N = formatC(nrow(insolv), format = "d", big.mark = ","),
      I_BTFP = sprintf("%.1f", btfp$i_rate * 100),
      I_DW = sprintf("%.1f", dw$i_rate * 100),
      I_Any = sprintf("%.1f", any_f$i_rate * 100),
      # Solvent
      S_N = formatC(nrow(solv), format = "d", big.mark = ","),
      S_BTFP = sprintf("%.1f", btfp$s_rate * 100),
      S_DW = sprintf("%.1f", dw$s_rate * 100),
      S_Any = sprintf("%.1f", any_f$s_rate * 100),
      # Difference (Insolvent - Solvent)
      D_BTFP = sprintf("%+.1f%s", btfp$diff * 100, btfp$stars),
      D_DW = sprintf("%+.1f%s", dw$diff * 100, dw$stars),
      D_Any = sprintf("%+.1f%s", any_f$diff * 100, any_f$stars)
    )
  }
  
  purrr::map_dfr(definitions, calc_row)
}

# ==============================================================================
# 3. TABLE 2: COMPOSITION OF BORROWERS (KEY TABLE!)
# ==============================================================================
# Question: Among banks that borrowed, what % are solvent vs insolvent?
# This directly shows that SOLVENT banks are the ones using facilities.

generate_borrower_composition <- function(data) {
  
  df_proc <- data %>%
    mutate(
      any_fed_acute = as.integer(btfp_acute == 1 | dw_acute == 1),
      insolvent_case1 = as.integer(mm_asset < total_liability),
      IDC_case2 = ifelse(insured_deposit > 0, 
                         (mm_asset - 1.0 * uninsured_deposit - insured_deposit) / insured_deposit, 
                         NA_real_),
      insolvent_case2 = as.integer(IDC_case2 < 0),
      IDC_case3 = ifelse(insured_deposit > 0, 
                         (mm_asset - 0.5 * uninsured_deposit - insured_deposit) / insured_deposit, 
                         NA_real_),
      insolvent_case3 = as.integer(IDC_case3 < 0)
    )
  
  # Get borrowers for each facility
  btfp_borrowers <- df_proc %>% filter(btfp_acute == 1)
  dw_borrowers <- df_proc %>% filter(dw_acute == 1)
  any_borrowers <- df_proc %>% filter(any_fed_acute == 1)
  
  calc_composition <- function(borrowers, insol_var, label) {
    n_total <- nrow(borrowers)
    n_insol <- sum(borrowers[[insol_var]], na.rm = TRUE)
    n_solv <- n_total - n_insol
    
    tibble(
      Definition = label,
      N_Total = n_total,
      N_Insolvent = n_insol,
      Pct_Insolvent = n_insol / max(n_total, 1) * 100,
      N_Solvent = n_solv,
      Pct_Solvent = n_solv / max(n_total, 1) * 100
    )
  }
  
  # Calculate for each facility and insolvency definition
  results <- bind_rows(
    # BTFP
    tibble(Facility = "BTFP", 
           calc_composition(btfp_borrowers, "insolvent_case1", "MTM insolvency")),
    tibble(Facility = "BTFP", 
           calc_composition(btfp_borrowers, "insolvent_case2", "IDCR $<$ 0 (100\\% run)")),
    tibble(Facility = "BTFP", 
           calc_composition(btfp_borrowers, "insolvent_case3", "IDCR $<$ 0 (50\\% run)")),
    # DW
    tibble(Facility = "DW", 
           calc_composition(dw_borrowers, "insolvent_case1", "MTM insolvency")),
    tibble(Facility = "DW", 
           calc_composition(dw_borrowers, "insolvent_case2", "IDCR $<$ 0 (100\\% run)")),
    tibble(Facility = "DW", 
           calc_composition(dw_borrowers, "insolvent_case3", "IDCR $<$ 0 (50\\% run)")),
    # Any Fed
    tibble(Facility = "Any Fed", 
           calc_composition(any_borrowers, "insolvent_case1", "MTM insolvency")),
    tibble(Facility = "Any Fed", 
           calc_composition(any_borrowers, "insolvent_case2", "IDCR $<$ 0 (100\\% run)")),
    tibble(Facility = "Any Fed", 
           calc_composition(any_borrowers, "insolvent_case3", "IDCR $<$ 0 (50\\% run)"))
  )
  
  results
}

# ==============================================================================
# 4. TABLE 3: BORROWING RATES BY SOLVENCY AND SIZE
# ==============================================================================

generate_solvency_size_comparison <- function(data) {
  
  df_proc <- data %>%
    mutate(
      any_fed_acute = as.integer(btfp_acute == 1 | dw_acute == 1),
      is_insolvent = as.integer(mm_asset < total_liability)
    ) %>%
    create_asset_size_bins()
  
  stats <- df_proc %>%
    group_by(asset_size) %>%
    summarise(
      I_Total = sum(is_insolvent == 1, na.rm = TRUE),
      I_Borr  = sum(any_fed_acute[is_insolvent == 1], na.rm = TRUE),
      S_Total = sum(is_insolvent == 0, na.rm = TRUE),
      S_Borr  = sum(any_fed_acute[is_insolvent == 0], na.rm = TRUE),
      .groups = "drop"
    )
  
  total_row <- stats %>%
    summarise(
      asset_size = factor("Total"),
      I_Total = sum(I_Total), I_Borr = sum(I_Borr),
      S_Total = sum(S_Total), S_Borr = sum(S_Borr)
    )
  
  combined <- bind_rows(stats, total_row)
  
  final <- combined %>%
    rowwise() %>%
    mutate(
      I_Rate = I_Borr / max(1, I_Total),
      S_Rate = S_Borr / max(1, S_Total),
      
      ptest = list(tryCatch(
        prop.test(c(I_Borr, S_Borr), c(I_Total, S_Total)),
        error = function(e) list(p.value = NA)
      )),
      pval = ptest$p.value,
      stars = format_pval_stars(pval),
      
      # Format columns
      Insol_N = formatC(I_Total, format = "d", big.mark = ","),
      Insol_Rate = sprintf("%.1f", I_Rate * 100),
      Solv_N = formatC(S_Total, format = "d", big.mark = ","),
      Solv_Rate = sprintf("%.1f", S_Rate * 100),
      Diff = sprintf("%+.1f%s", (I_Rate - S_Rate) * 100, stars)
    ) %>%
    ungroup() %>%
    select(asset_size, Insol_N, Insol_Rate, Solv_N, Solv_Rate, Diff)
  
  final
}

# ==============================================================================
# 5. EXECUTION
# ==============================================================================

table1_df <- generate_borrowing_rates_by_solvency(df_acute)
table2_df <- generate_borrower_composition(df_acute)
table3_df <- generate_solvency_size_comparison(df_acute)

cat("\n=== TABLE 1: BORROWING RATES BY SOLVENCY STATUS ===\n")
## 
## === TABLE 1: BORROWING RATES BY SOLVENCY STATUS ===
cat("(Do insolvent banks borrow at different rates than solvent banks?)\n\n")
## (Do insolvent banks borrow at different rates than solvent banks?)
print(table1_df)
## # A tibble: 3 × 12
##   Definition      I_N   I_BTFP I_DW  I_Any S_N   S_BTFP S_DW  S_Any D_BTFP D_DW 
##   <chr>           <chr> <chr>  <chr> <chr> <chr> <chr>  <chr> <chr> <chr>  <chr>
## 1 "MTM insolvenc… 2,015 14.0   11.2  22.3  2,267 7.9    7.4   13.7  +6.1*… +3.8…
## 2 "IDCR $<$ 0 (1… 1,210 10.7   10.1  18.9  3,041 10.9   8.9   17.5  -0.2   +1.2 
## 3 "IDCR $<$ 0 (5… 179   8.9    13.4  20.7  4,072 11.0   9.1   17.8  -2.0   +4.3*
## # ℹ 1 more variable: D_Any <chr>
cat("\n=== TABLE 2: COMPOSITION OF BORROWERS ===\n")
## 
## === TABLE 2: COMPOSITION OF BORROWERS ===
cat("(Among borrowers, what % are solvent? THIS IS THE KEY TABLE)\n\n")
## (Among borrowers, what % are solvent? THIS IS THE KEY TABLE)
print(table2_df %>% select(Facility, Definition, N_Total, Pct_Solvent, Pct_Insolvent))
## # A tibble: 9 × 5
##   Facility Definition                N_Total Pct_Solvent Pct_Insolvent
##   <chr>    <chr>                       <int>       <dbl>         <dbl>
## 1 BTFP     "MTM insolvency"              462        38.7         61.3 
## 2 BTFP     "IDCR $<$ 0 (100\\% run)"     462        71.9         28.1 
## 3 BTFP     "IDCR $<$ 0 (50\\% run)"      462        96.5          3.46
## 4 DW       "MTM insolvency"              393        42.5         57.5 
## 5 DW       "IDCR $<$ 0 (100\\% run)"     393        69.0         31.0 
## 6 DW       "IDCR $<$ 0 (50\\% run)"      393        93.9          6.11
## 7 Any Fed  "MTM insolvency"              761        40.9         59.1 
## 8 Any Fed  "IDCR $<$ 0 (100\\% run)"     761        69.9         30.1 
## 9 Any Fed  "IDCR $<$ 0 (50\\% run)"      761        95.1          4.86
cat("\n=== TABLE 3: BORROWING RATES BY SIZE ===\n")
## 
## === TABLE 3: BORROWING RATES BY SIZE ===
print(table3_df)
## # A tibble: 11 × 6
##    asset_size   Insol_N Insol_Rate Solv_N Solv_Rate Diff    
##    <fct>        <chr>   <chr>      <chr>  <chr>     <chr>   
##  1 $250B--$700B 2       50.0       3      33.3      +16.7   
##  2 $100B--$250B 8       37.5       5      60.0      -22.5   
##  3 $50B--$100B  8       25.0       7      71.4      -46.4   
##  4 $20B--$50B   18      55.6       27     29.6      +25.9   
##  5 $10B--$20B   26      57.7       31     51.6      +6.1    
##  6 $5B--$10B    62      45.2       50     20.0      +25.2***
##  7 $3B--$5B     66      34.8       48     37.5      -2.7    
##  8 $1B--$3B     328     33.8       233    27.5      +6.4    
##  9 $500M--$1B   384     24.2       333    18.3      +5.9*   
## 10 Below $500M  1,113   14.7       1,530  8.2       +6.6*** 
## 11 Total        2,015   22.3       2,267  13.7      +8.6***
# ==============================================================================
# 6. JFE-STYLE LATEX TABLES
# ==============================================================================

# ------------------------------------------------------------------------------
# TABLE 1 LaTeX: Borrowing Rates by Solvency
# ------------------------------------------------------------------------------

generate_table1_latex <- function(df) {
  latex <- c(
    "\\begin{table}[htbp]",
    "\\centering",
    "\\begin{threeparttable}",
    "\\caption{Emergency Facility Usage: Insolvent versus Solvent Banks}",
    "\\label{tab:borrowing_rates_solvency}",
    "\\small",
    "\\begin{tabular}{@{}l*{11}{c}@{}}",
    "\\toprule",
    " & \\multicolumn{4}{c}{Insolvent Banks} & \\multicolumn{4}{c}{Solvent Banks} & \\multicolumn{3}{c}{Difference} \\\\",
    "\\cmidrule(lr){2-5} \\cmidrule(lr){6-9} \\cmidrule(lr){10-12}",
    "Insolvency Definition & $N$ & BTFP & DW & Any & $N$ & BTFP & DW & Any & BTFP & DW & Any \\\\",
    "\\midrule"
  )
  
  for (i in 1:nrow(df)) {
    row <- df[i, ]
    latex <- c(latex, sprintf("%s & %s & %s & %s & %s & %s & %s & %s & %s & %s & %s & %s \\\\",
                              row$Definition, row$I_N, row$I_BTFP, row$I_DW, row$I_Any,
                              row$S_N, row$S_BTFP, row$S_DW, row$S_Any,
                              row$D_BTFP, row$D_DW, row$D_Any))
  }
  
  latex <- c(latex,
    "\\bottomrule",
    "\\end{tabular}",
    "\\begin{tablenotes}[flushleft]",
    "\\footnotesize",
    "\\item \\textit{Notes:} This table compares emergency facility usage rates between insolvent and solvent banks during the acute crisis period (March 13--May 1, 2023). Columns report the percentage of banks in each category that borrowed from BTFP, Discount Window (DW), or either facility (Any). MTM insolvency indicates mark-to-market assets below total liabilities. IDCR (Insured Deposit Coverage Ratio) insolvency indicates negative IDCR under the specified depositor run scenario, where IDCR $= (\\text{MTM Assets} - s \\times \\text{Uninsured Deposits} - \\text{Insured Deposits}) / \\text{Insured Deposits}$ with $s = 1.0$ for 100\\% run and $s = 0.5$ for 50\\% run. Difference columns report (Insolvent $-$ Solvent) in percentage points. Statistical significance based on two-sample proportion tests.",
    "\\item $^{***}p<0.01$; $^{**}p<0.05$; $^{*}p<0.10$.",
    "\\end{tablenotes}",
    "\\end{threeparttable}",
    "\\end{table}"
  )
  
  paste(latex, collapse = "\n")
}

# ------------------------------------------------------------------------------
# TABLE 2 LaTeX: Composition of Borrowers (KEY TABLE)
# ------------------------------------------------------------------------------

generate_table2_latex <- function(df) {
  
  # Reshape for cleaner presentation
  df_wide <- df %>%
    select(Facility, Definition, N_Total, N_Solvent, Pct_Solvent, N_Insolvent, Pct_Insolvent) %>%
    mutate(
      Solvent_Display = sprintf("%d (%.1f\\%%)", N_Solvent, Pct_Solvent),
      Insolvent_Display = sprintf("%d (%.1f\\%%)", N_Insolvent, Pct_Insolvent)
    )
  
  latex <- c(
    "\\begin{table}[htbp]",
    "\\centering",
    "\\begin{threeparttable}",
    "\\caption{Solvency Composition of Emergency Facility Borrowers}",
    "\\label{tab:borrower_composition}",
    "\\small",
    "\\begin{tabular}{@{}llccc@{}}",
    "\\toprule",
    "Facility & Insolvency Definition & Total Borrowers & Solvent & Insolvent \\\\",
    "\\midrule"
  )
  
  current_facility <- ""
  for (i in 1:nrow(df_wide)) {
    row <- df_wide[i, ]
    
    # Add facility header if new facility
    if (row$Facility != current_facility) {
      if (current_facility != "") {
        latex <- c(latex, "\\addlinespace[3pt]")
      }
      current_facility <- row$Facility
    }
    
    facility_display <- ifelse(row$Facility == current_facility & 
                                 which(df_wide$Facility == row$Facility)[1] == i,
                               row$Facility, "")
    
    latex <- c(latex, sprintf("%s & %s & %d & %s & %s \\\\",
                              facility_display, row$Definition, row$N_Total,
                              row$Solvent_Display, row$Insolvent_Display))
  }
  
  latex <- c(latex,
    "\\bottomrule",
    "\\end{tabular}",
    "\\begin{tablenotes}[flushleft]",
    "\\footnotesize",
    "\\item \\textit{Notes:} This table reports the solvency composition of banks that borrowed from emergency facilities during the acute crisis period (March 13--May 1, 2023). For each facility and insolvency definition, columns show the number and percentage of borrowers that were solvent versus insolvent at the time of borrowing. The key finding is that the \\textbf{vast majority of borrowers were fundamentally solvent} under all three insolvency definitions, indicating that emergency facilities served their intended purpose of providing liquidity to sound banks experiencing temporary stress rather than bailing out failing institutions. MTM insolvency indicates mark-to-market assets below total liabilities. IDCR insolvency indicates negative Insured Deposit Coverage Ratio under the specified depositor run scenario.",
    "\\end{tablenotes}",
    "\\end{threeparttable}",
    "\\end{table}"
  )
  
  paste(latex, collapse = "\n")
}

# ------------------------------------------------------------------------------
# TABLE 3 LaTeX: By Size
# ------------------------------------------------------------------------------

generate_table3_latex <- function(df) {
  latex <- c(
    "\\begin{table}[htbp]",
    "\\centering",
    "\\begin{threeparttable}",
    "\\caption{Emergency Facility Usage by Solvency Status and Bank Size}",
    "\\label{tab:solvency_size}",
    "\\small",
    "\\begin{tabular}{@{}l*{5}{c}@{}}",
    "\\toprule",
    " & \\multicolumn{2}{c}{Insolvent} & \\multicolumn{2}{c}{Solvent} & \\\\",
    "\\cmidrule(lr){2-3} \\cmidrule(lr){4-5}",
    "Asset Size & $N$ & Rate (\\%) & $N$ & Rate (\\%) & Diff. (pp) \\\\",
    "\\midrule"
  )
  
  for (i in 1:(nrow(df) - 1)) {
    row <- df[i, ]
    latex <- c(latex, sprintf("%s & %s & %s & %s & %s & %s \\\\",
                              as.character(row$asset_size), row$Insol_N, row$Insol_Rate,
                              row$Solv_N, row$Solv_Rate, row$Diff))
  }
  
  # Total row with midrule
  total <- df[nrow(df), ]
  latex <- c(latex, "\\midrule")
  latex <- c(latex, sprintf("Total & %s & %s & %s & %s & %s \\\\",
                            total$Insol_N, total$Insol_Rate,
                            total$Solv_N, total$Solv_Rate, total$Diff))
  
  latex <- c(latex,
    "\\bottomrule",
    "\\end{tabular}",
    "\\begin{tablenotes}[flushleft]",
    "\\footnotesize",
    "\\item \\textit{Notes:} This table compares emergency facility usage rates between insolvent and solvent banks across asset size categories during the acute crisis period (March 13--May 1, 2023). Insolvency is defined as MTM assets below total liabilities (Case 1). Rate indicates the percentage of banks in each category that accessed any Federal Reserve emergency facility (BTFP or Discount Window). Difference reports (Insolvent $-$ Solvent) in percentage points. Statistical significance based on two-sample proportion tests.",
    "\\item $^{***}p<0.01$; $^{**}p<0.05$; $^{*}p<0.10$.",
    "\\end{tablenotes}",
    "\\end{threeparttable}",
    "\\end{table}"
  )
  
  paste(latex, collapse = "\n")
}

# ==============================================================================
# 7. SAVE FILES
# ==============================================================================

latex1 <- generate_table1_latex(table1_df)
latex2 <- generate_table2_latex(table2_df)
latex3 <- generate_table3_latex(table3_df)

writeLines(latex1, file.path(TABLE_PATH, "Table_Borrowing_Rates_Solvency.tex"))
writeLines(latex2, file.path(TABLE_PATH, "Table_Borrower_Composition.tex"))
writeLines(latex3, file.path(TABLE_PATH, "Table_Solvency_Size.tex"))

cat("\n=== All tables saved successfully ===\n")
## 
## === All tables saved successfully ===

4 EXTENSIVE MARGIN ANALYSIS - ACUTE PERIOD

4.0.1 Table : DW March 10 & March 10-13 - All Specifications

# March 10 only
df_dw_mar10_s <- df_mar10 %>% filter(dw_mar10 == 1 | non_user == 1)
models_dw_mar10 <- run_4spec_models(df_dw_mar10_s, "dw_mar10", "lpm")

# March 10-13
df_dw_mar10_13_s <- df_mar10_13 %>% filter(dw_mar10_13 == 1 | non_user == 1)
models_dw_mar10_13 <- run_4spec_models(df_dw_mar10_13_s, "dw_mar10_13", "lpm")

# Combined table
models_dw_short <- list(
  "Mar10 (1)" = models_dw_mar10$`(1) Base`,
  "Mar10 (2)" = models_dw_mar10$`(2) +Risk1`,
  "Mar10 (3)" = models_dw_mar10$`(3) +Risk1,2`,
  "Mar10 (4)" = models_dw_mar10$`(4) Risk2,3,4`,
  "Mar10-13 (1)" = models_dw_mar10_13$`(1) Base`,
  "Mar10-13 (2)" = models_dw_mar10_13$`(2) +Risk1`,
  "Mar10-13 (3)" = models_dw_mar10_13$`(3) +Risk1,2`,
  "Mar10-13 (4)" = models_dw_mar10_13$`(4) Risk2,3,4`
)

n_rows_dw_short <- data.frame(
  term = c("N (DW=1)", "N (Sample)"),
  `Mar10 (1)` = c(sum(df_dw_mar10_s$dw_mar10), nrow(df_dw_mar10_s)),
  `Mar10 (2)` = c(sum(df_dw_mar10_s$dw_mar10), nrow(df_dw_mar10_s)),
  `Mar10 (3)` = c(sum(df_dw_mar10_s$dw_mar10), nrow(df_dw_mar10_s)),
  `Mar10 (4)` = c(sum(df_dw_mar10_s$dw_mar10), nrow(df_dw_mar10_s)),
  `Mar10-13 (1)` = c(sum(df_dw_mar10_13_s$dw_mar10_13), nrow(df_dw_mar10_13_s)),
  `Mar10-13 (2)` = c(sum(df_dw_mar10_13_s$dw_mar10_13), nrow(df_dw_mar10_13_s)),
  `Mar10-13 (3)` = c(sum(df_dw_mar10_13_s$dw_mar10_13), nrow(df_dw_mar10_13_s)),
  `Mar10-13 (4)` = c(sum(df_dw_mar10_13_s$dw_mar10_13), nrow(df_dw_mar10_13_s)),
  check.names = FALSE
)

# SAVE TABLE 4C
save_reg_table(models_dw_short, "Table_DW_Mar10_LPM",
               title_text = "Discount Window Usage During Immediate Crisis (LPM)",
               notes_text = "This table reports Linear Probability Model (LPM) estimates of Discount Window usage during the immediate crisis period. Columns (1)--(4) analyze March 10, 2023 only (SVB closure day, before BTFP announcement). Columns (5)--(8) analyze March 10--13, 2023 (including BTFP announcement on March 12). The dependent variable equals one if the bank accessed the Discount Window. Column (1)/(5) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4)/(6)--(8) use risk category dummies. The sample consists of DW users versus pure non-users, excluding G-SIBs and failed institutions (SVB, Signature, First Republic, Silvergate). Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized at 2.5/97.5 percentiles and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_rows_dw_short)

modelsummary(
  models_dw_short,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = gof_lpm,
  add_rows = n_rows_dw_short,
  title = "Table: DW Usage - March 10 and March 10-13 (LPM)",
  output = "kableExtra"
)
Table: DW Usage - March 10 and March 10-13 (LPM)
&nbsp;Mar10 (1) &nbsp;Mar10 (2) &nbsp;Mar10 (3) &nbsp;Mar10 (4) &nbsp;Mar10-13 (1) &nbsp;Mar10-13 (2) &nbsp;Mar10-13 (3) &nbsp;Mar10-13 (4)
MTM Loss (z) −0.002 −0.002
(0.002) (0.003)
Uninsured Lev (z) 0.001 0.008**
(0.002) (0.003)
MTM ? Uninsured 0.001 0.005*
(0.002) (0.003)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.003 0.003 0.006 0.006
(0.004) (0.004) (0.005) (0.005)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.000 −0.003 −0.001 −0.005
(0.005) (0.004) (0.007) (0.006)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.005 −0.012**
(0.004) (0.005)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured −0.001 0.004
(0.006) (0.008)
Log(Assets) (z) 0.013*** 0.014*** 0.014*** 0.014*** 0.026*** 0.030*** 0.030*** 0.029***
(0.003) (0.003) (0.003) (0.003) (0.004) (0.004) (0.004) (0.004)
Cash Ratio (z) −0.004*** −0.003*** −0.003** −0.003*** −0.005*** −0.004** −0.004* −0.004**
(0.001) (0.001) (0.001) (0.001) (0.002) (0.002) (0.002) (0.002)
Loan/Deposit (z) −0.002 −0.002 −0.002 −0.002 −0.004 −0.005* −0.005* −0.004
(0.002) (0.002) (0.002) (0.002) (0.003) (0.003) (0.003) (0.003)
Book Equity (z) −0.000 −0.000 −0.000 −0.000 0.002 0.001 0.001 0.001
(0.001) (0.001) (0.001) (0.001) (0.002) (0.002) (0.002) (0.002)
Wholesale (z) 0.007*** 0.007*** 0.007*** 0.007*** 0.008*** 0.008*** 0.008*** 0.008***
(0.003) (0.003) (0.003) (0.003) (0.003) (0.003) (0.003) (0.003)
ROA (z) −0.004*** −0.003*** −0.003*** −0.003*** −0.003 −0.001 −0.001 −0.002
(0.001) (0.001) (0.001) (0.001) (0.002) (0.002) (0.002) (0.002)
Num.Obs. 3986 3996 3996 3996 3988 3998 3998 3998
R2 0.024 0.024 0.024 0.024 0.047 0.044 0.044 0.045
R2 Adj. 0.022 0.022 0.022 0.022 0.045 0.042 0.042 0.043
N (DW=1) 47.000 47.000 47.000 47.000 90.000 90.000 90.000 90.000
N (Sample) 3996.000 3996.000 3996.000 3996.000 3998.000 3998.000 3998.000 3998.000
* p < 0.1, ** p < 0.05, *** p < 0.01

4.1 Acute Period: All Specifications BTFP

# ==============================================================================
# ACUTE PERIOD ANALYSIS
# Sample Construction: DV=1 users vs. DV=0 PURE non-users only
# ==============================================================================
# -----------------------------------------------------------------------------
# 0) Helper functions: build the 4 specs + optional single spec (Risk2,3,4 only)
# -----------------------------------------------------------------------------

# Formula helpers (keeps all model definitions consistent)
f_base <- function(dv) build_formula(dv, "mtm_total + uninsured_lev + mtm_x_uninsured")
f_r1   <- function(dv) build_formula(dv, "run_risk_1")
f_r12  <- function(dv) build_formula(dv, "run_risk_1 + run_risk_2")
f_r234 <- function(dv) build_formula(dv, "run_risk_2 + run_risk_3 + run_risk_4")

# Run the 4-spec set (Base, Risk1, Risk1+2, Risk2+3+4)
run_4spec <- function(data, dv, family_type = c("lpm","logit")) {
  family_type <- match.arg(family_type)

  forms <- list(
    "(1) Base"      = f_base(dv),
    "(2) +Risk1"    = f_r1(dv),
    "(3) +Risk1,2"  = f_r12(dv),
    "(4) Risk2,3,4" = f_r234(dv)
  )

  if (family_type == "lpm") {
    lapply(forms, function(ff) feols(ff, data = data, vcov = "hetero"))
  } else {
    lapply(forms, function(ff) feglm(ff, data = data, family = binomial("logit"), vcov = "hetero"))
  }
}

# Run only Risk2+3+4 (used for AnyFed column in Table 1/2)
run_r234_only <- function(data, dv, family_type = c("lpm","logit")) {
  family_type <- match.arg(family_type)
  ff <- f_r234(dv)

  if (family_type == "lpm") {
    feols(ff, data = data, vcov = "hetero")
  } else {
    feglm(ff, data = data, family = binomial("logit"), vcov = "hetero")
  }
}

# Add N rows (DV=1 count + sample size) for a given dataset and DV
add_n_rows <- function(data, dv, n_models) {
  n_ones   <- sum(data[[dv]] == 1, na.rm = TRUE)
  n_sample <- nrow(data)

  out <- data.frame(term = c(paste0("N (", dv, "=1)"), "N (Sample)"))
  for (i in 1:n_models) out[[paste0("(", i, ")")]] <- c(n_ones, n_sample)
  out
}

4.1.1 TABLE (LPM) - Acute Period

# -----------------------------------------------------------------------------
# 1) TABLE 1 (LPM): all_user (4 specs) + any_fed (Risk2,3,4 only)
#     Sample: DV=1 vs pure non-users only (non_user == 1)
# -----------------------------------------------------------------------------

df_all_acute    <- df_acute %>% filter(all_user == 1 | non_user == 1)
df_anyfed_acute <- df_acute %>% filter(any_fed  == 1 | non_user == 1)

# Run models
m_all_lpm  <- run_4spec(df_all_acute, "all_user", "lpm")
m_any_lpm  <- run_r234_only(df_anyfed_acute, "any_fed", "lpm")

# Combine into 5 columns: (1)-(4) all_user, (5) any_fed risk2,3,4
models_t1 <- c(m_all_lpm, list("(5) AnyFed: Risk2,3,4" = m_any_lpm))

# N rows for each column's own sample (so Ns are correct by column)
n_all  <- add_n_rows(df_all_acute, "all_user", n_models = 4)
n_any  <- add_n_rows(df_anyfed_acute, "any_fed",  n_models = 1)

# Build a 5-column add_rows object aligned to model columns:
# - first 4 columns use all_user sample Ns
# - 5th column uses any_fed sample Ns
nrows_t1 <- data.frame(
  term = n_all$term,
  "(1)" = n_all[["(1)"]],
  "(2)" = n_all[["(2)"]],
  "(3)" = n_all[["(3)"]],
  "(4)" = n_all[["(4)"]],
  "(5)" = n_any[["(1)"]]
)

# SAVE TABLE 1
save_reg_table(models_t1, "Table_1_Acute_AllUsers_LPM",
               title_text = "Emergency Facility Usage During Acute Crisis (LPM)",
               notes_text = "This table reports Linear Probability Model (LPM) estimates of emergency facility usage during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed any emergency facility (BTFP or Discount Window). Column (1) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4) use risk category dummies based on median splits. Column (5) reports Any Fed usage with the Risk 2,3,4 specification. Risk categories: R1 = Low MTM, Low Uninsured (reference); R2 = Low MTM, High Uninsured; R3 = High MTM, Low Uninsured; R4 = High MTM, High Uninsured. The sample consists of facility users versus pure non-users (banks with no BTFP, DW, or abnormal FHLB borrowing), excluding G-SIBs and failed institutions (SVB, Signature, First Republic, Silvergate). Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized at 2.5/97.5 percentiles and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = nrows_t1)

modelsummary(
  models_t1,
  stars    = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map  = gof_lpm,
  add_rows = nrows_t1,
  title    = "Table: Acute Period (LPM) ? All Users (4 specs) + Any Fed (Risk2,3,4 only)",
  notes    = "Sample: DV=1 users vs DV=0 pure non-users. Variables z-standardized. Robust SEs.",
  output   = "kableExtra"
)
Table: Acute Period (LPM) ? All Users (4 specs) + Any Fed (Risk2,3,4 only)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4 &nbsp;(5) AnyFed: Risk2,3,4
MTM Loss (z) 0.029***
(0.007)
Uninsured Lev (z) 0.018**
(0.008)
MTM ? Uninsured 0.013**
(0.006)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.027* −0.041***
(0.014) (0.016)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.035** 0.011 0.001
(0.016) (0.017) (0.016)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.019 0.019
(0.017) (0.015)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.076*** 0.079***
(0.020) (0.019)
Log(Assets) (z) 0.097*** 0.101*** 0.103*** 0.098*** 0.104***
(0.008) (0.008) (0.008) (0.008) (0.008)
Cash Ratio (z) −0.034*** −0.042*** −0.037*** −0.038*** −0.024***
(0.006) (0.006) (0.006) (0.006) (0.006)
Loan/Deposit (z) 0.014** 0.007 0.010 0.013* −0.006
(0.007) (0.007) (0.007) (0.007) (0.006)
Book Equity (z) −0.008 −0.014** −0.013** −0.010* −0.015***
(0.006) (0.005) (0.005) (0.005) (0.005)
Wholesale (z) 0.025*** 0.024*** 0.024*** 0.024*** 0.029***
(0.007) (0.007) (0.007) (0.007) (0.007)
ROA (z) −0.005 −0.006 −0.004 −0.006 −0.002
(0.006) (0.006) (0.006) (0.006) (0.005)
Num.Obs. 4282 4292 4292 4292 4046
R2 0.105 0.102 0.103 0.105 0.115
R2 Adj. 0.103 0.100 0.101 0.103 0.113
N (all_user=1) 1007.000 1007.000 1007.000 1007.000 761.000
N (Sample) 4292.000 4292.000 4292.000 4292.000 4046.000
* p < 0.1, ** p < 0.05, *** p < 0.01
Sample: DV=1 users vs DV=0 pure non-users. Variables z-standardized. Robust SEs.

4.1.2 Table (Logit) - Acute Period

# Run models
m_all_logit <- run_4spec(df_all_acute, "all_user", "logit")
m_any_logit <- run_r234_only(df_anyfed_acute, "any_fed", "logit")

models_t2 <- c(m_all_logit, list("(5) AnyFed: Risk2,3,4" = m_any_logit))

# Ns are the same construction as Table 1 (by sample/column)
nrows_t2 <- nrows_t1

# SAVE TABLE 2
save_reg_table(models_t2, "Table_2_Acute_AllUsers_Logit",
               title_text = "Emergency Facility Usage During Acute Crisis (Logit)",
               notes_text = "This table reports Logit estimates of emergency facility usage during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed any emergency facility (BTFP or Discount Window). Column (1) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4) use risk category dummies based on median splits. Column (5) reports Any Fed usage with the Risk 2,3,4 specification. Risk categories: R1 = Low MTM, Low Uninsured (reference); R2 = Low MTM, High Uninsured; R3 = High MTM, Low Uninsured; R4 = High MTM, High Uninsured. The sample consists of facility users versus pure non-users, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized at 2.5/97.5 percentiles and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP, gof_map_use = gof_logit, add_rows_use = nrows_t2)

modelsummary(
  models_t2,
  stars    = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map  = gof_logit,
  add_rows = nrows_t2,
  title    = "Table: Acute Period (Logit) ? All Users (4 specs) + Any Fed (Risk2,3,4 only)",
  notes    = "Sample: DV=1 users vs DV=0 pure non-users. Variables z-standardized. Robust SEs.",
  output   = "kableExtra"
)
Table: Acute Period (Logit) ? All Users (4 specs) + Any Fed (Risk2,3,4 only)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4 &nbsp;(5) AnyFed: Risk2,3,4
MTM Loss (z) 0.184***
(0.045)
Uninsured Lev (z) 0.127***
(0.045)
MTM ? Uninsured 0.014
(0.039)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.277** −0.337***
(0.109) (0.115)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.164* 0.201 0.228
(0.097) (0.126) (0.150)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.221* 0.321**
(0.126) (0.150)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.494*** 0.621***
(0.128) (0.149)
Log(Assets) (z) 0.522*** 0.549*** 0.559*** 0.533*** 0.663***
(0.046) (0.042) (0.043) (0.044) (0.049)
Cash Ratio (z) −0.349*** −0.398*** −0.370*** −0.371*** −0.287***
(0.066) (0.062) (0.064) (0.064) (0.071)
Loan/Deposit (z) 0.143*** 0.105** 0.118** 0.133*** 0.036
(0.049) (0.048) (0.048) (0.048) (0.054)
Book Equity (z) −0.137*** −0.178*** −0.172*** −0.156*** −0.264***
(0.050) (0.049) (0.049) (0.050) (0.061)
Wholesale (z) 0.131*** 0.123*** 0.121*** 0.124*** 0.164***
(0.036) (0.036) (0.036) (0.036) (0.040)
ROA (z) −0.006 −0.016 −0.008 −0.021 0.019
(0.043) (0.042) (0.042) (0.043) (0.049)
Num.Obs. 4282 4292 4292 4292 4046
N (all_user=1) 1007.000 1007.000 1007.000 1007.000 761.000
N (Sample) 4292.000 4292.000 4292.000 4292.000 4046.000
* p < 0.1, ** p < 0.05, *** p < 0.01
Sample: DV=1 users vs DV=0 pure non-users. Variables z-standardized. Robust SEs.

4.1.3 TABLE (LPM): BTFP only (4 specs) ? BTFP=1 vs pure non-users- Acute Period

# -----------------------------------------------------------------------------
# 3) TABLE 3 (LPM): BTFP only (4 specs) ? BTFP=1 vs pure non-users
# -----------------------------------------------------------------------------

df_btfp_acute <- df_acute %>% filter(btfp_acute == 1 | non_user == 1)

m_btfp_lpm <- run_4spec(df_btfp_acute, "btfp_acute", "lpm")
n_btfp_lpm <- add_n_rows(df_btfp_acute, "btfp_acute", n_models = 4)

# SAVE TABLE 3
save_reg_table(m_btfp_lpm, "Table_3_BTFP_Acute_LPM",
               title_text = "BTFP Participation During Acute Crisis (LPM)",
               notes_text = "This table reports Linear Probability Model (LPM) estimates of BTFP participation during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed BTFP. Column (1) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4) use risk category dummies based on median splits: R1 = Low MTM, Low Uninsured (reference); R2 = Low MTM, High Uninsured; R3 = High MTM, Low Uninsured; R4 = High MTM, High Uninsured. The sample consists of BTFP users versus pure non-users (banks with no BTFP, DW, or abnormal FHLB borrowing), excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized at 2.5/97.5 percentiles and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_btfp_lpm)

modelsummary(
  m_btfp_lpm,
  stars    = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map  = gof_lpm,
  add_rows = n_btfp_lpm,
  title    = "Table: BTFP Usage (LPM) ? Acute Period (Mar 13?May 1, 2023)",
  notes    = "Sample: BTFP users vs pure non-users. Variables z-standardized. Robust SEs.",
  output   = "kableExtra"
)
Table: BTFP Usage (LPM) ? Acute Period (Mar 13?May 1, 2023)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.024***
(0.006)
Uninsured Lev (z) 0.020***
(0.007)
MTM ? Uninsured 0.018***
(0.005)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.012 −0.026**
(0.011) (0.012)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.037*** −0.006
(0.014) (0.014)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.001
(0.013)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.062***
(0.017)
Log(Assets) (z) 0.067*** 0.074*** 0.076*** 0.071***
(0.007) (0.007) (0.007) (0.007)
Cash Ratio (z) −0.024*** −0.031*** −0.026*** −0.027***
(0.005) (0.004) (0.004) (0.005)
Loan/Deposit (z) −0.008 −0.016*** −0.014** −0.011**
(0.005) (0.005) (0.005) (0.005)
Book Equity (z) −0.011** −0.017*** −0.016*** −0.013***
(0.004) (0.004) (0.004) (0.004)
Wholesale (z) 0.025*** 0.024*** 0.024*** 0.024***
(0.006) (0.006) (0.006) (0.006)
ROA (z) −0.005 −0.005 −0.003 −0.005
(0.005) (0.005) (0.005) (0.005)
Num.Obs. 3737 3747 3747 3747
R2 0.090 0.084 0.086 0.089
R2 Adj. 0.088 0.082 0.084 0.087
N (btfp_acute=1) 462.000 462.000 462.000 462.000
N (Sample) 3747.000 3747.000 3747.000 3747.000
* p < 0.1, ** p < 0.05, *** p < 0.01
Sample: BTFP users vs pure non-users. Variables z-standardized. Robust SEs.

4.1.4 TABLE (LOGIT): BTFP only (4 specs) - Acute Period

# -----------------------------------------------------------------------------
# 4) TABLE 4 (LOGIT): BTFP only (4 specs)
# -----------------------------------------------------------------------------

m_btfp_logit <- run_4spec(df_btfp_acute, "btfp_acute", "logit")
n_btfp_logit <- add_n_rows(df_btfp_acute, "btfp_acute", n_models = 4)

# SAVE TABLE 4
save_reg_table(m_btfp_logit, "Table_4_BTFP_Acute_Logit",
               title_text = "BTFP Participation During Acute Crisis (Logit)",
               notes_text = "This table reports Logit estimates of BTFP participation during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed BTFP. Column (1) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4) use risk category dummies based on median splits: R1 = Low MTM, Low Uninsured (reference); R2 = Low MTM, High Uninsured; R3 = High MTM, Low Uninsured; R4 = High MTM, High Uninsured. The sample consists of BTFP users versus pure non-users, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized at 2.5/97.5 percentiles and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP, gof_map_use = gof_logit, add_rows_use = n_btfp_logit)

modelsummary(
  m_btfp_logit,
  stars    = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map  = gof_logit,
  add_rows = n_btfp_logit,
  title    = "Table 4: BTFP Usage (Logit) ? Acute Period (Mar 13?May 1, 2023)",
  notes    = "Sample: BTFP users vs pure non-users. Variables z-standardized. Robust SEs.",
  output   = "kableExtra"
)
Table 4: BTFP Usage (Logit) ? Acute Period (Mar 13?May 1, 2023)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.214***
(0.062)
Uninsured Lev (z) 0.209***
(0.063)
MTM ? Uninsured 0.046
(0.057)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.300* −0.378**
(0.163) (0.168)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.222 0.192
(0.136) (0.188)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.177
(0.184)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.592***
(0.181)
Log(Assets) (z) 0.574*** 0.636*** 0.649*** 0.611***
(0.062) (0.056) (0.057) (0.059)
Cash Ratio (z) −0.517*** −0.563*** −0.522*** −0.527***
(0.105) (0.101) (0.103) (0.104)
Loan/Deposit (z) 0.001 −0.061 −0.043 −0.018
(0.066) (0.065) (0.065) (0.066)
Book Equity (z) −0.329*** −0.398*** −0.384*** −0.361***
(0.082) (0.081) (0.080) (0.082)
Wholesale (z) 0.183*** 0.168*** 0.166*** 0.173***
(0.046) (0.046) (0.046) (0.046)
ROA (z) −0.016 −0.017 −0.008 −0.029
(0.063) (0.062) (0.062) (0.063)
Num.Obs. 3737 3747 3747 3747
N (btfp_acute=1) 462.000 462.000 462.000 462.000
N (Sample) 3747.000 3747.000 3747.000 3747.000
* p < 0.1, ** p < 0.05, *** p < 0.01
Sample: BTFP users vs pure non-users. Variables z-standardized. Robust SEs.

4.1.5 TABLE (LPM): DW only (4 specs) ? DW=1 vs pure non-users - Acute Period

# -----------------------------------------------------------------------------
# 5) TABLE 5 (LPM): DW only (4 specs) ? DW=1 vs pure non-users
# -----------------------------------------------------------------------------

df_dw_acute <- df_acute %>% filter(dw_acute == 1 | non_user == 1)

m_dw_lpm <- run_4spec(df_dw_acute, "dw_acute", "lpm")
n_dw_lpm <- add_n_rows(df_dw_acute, "dw_acute", n_models = 4)

# SAVE TABLE 5
save_reg_table(m_dw_lpm, "Table_5_DW_Acute_LPM",
               title_text = "Discount Window Usage During Acute Crisis (LPM)",
               notes_text = "This table reports Linear Probability Model (LPM) estimates of Discount Window usage during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed the Discount Window. Column (1) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4) use risk category dummies based on median splits: R1 = Low MTM, Low Uninsured (reference); R2 = Low MTM, High Uninsured; R3 = High MTM, Low Uninsured; R4 = High MTM, High Uninsured. The sample consists of DW users versus pure non-users, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized at 2.5/97.5 percentiles and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_dw_lpm)

modelsummary(
  m_dw_lpm,
  stars    = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map  = gof_lpm,
  add_rows = n_dw_lpm,
  title    = "Table: Discount Window Usage (LPM) ? Acute Period (Mar 13?May 1, 2023)",
  notes    = "Sample: DW users vs pure non-users. Variables z-standardized. Robust SEs.",
  output   = "kableExtra"
)
Table: Discount Window Usage (LPM) ? Acute Period (Mar 13?May 1, 2023)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.016***
(0.006)
Uninsured Lev (z) 0.013**
(0.006)
MTM ? Uninsured 0.016***
(0.005)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.012 −0.024**
(0.010) (0.011)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.030** −0.002
(0.013) (0.013)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.007
(0.012)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.050***
(0.016)
Log(Assets) (z) 0.084*** 0.087*** 0.089*** 0.085***
(0.007) (0.007) (0.007) (0.007)
Cash Ratio (z) −0.002 −0.007 −0.003 −0.004
(0.005) (0.005) (0.005) (0.005)
Loan/Deposit (z) −0.001 −0.007 −0.005 −0.003
(0.005) (0.005) (0.005) (0.005)
Book Equity (z) −0.000 −0.004 −0.003 −0.001
(0.004) (0.004) (0.004) (0.004)
Wholesale (z) 0.020*** 0.020*** 0.020*** 0.020***
(0.006) (0.006) (0.006) (0.006)
ROA (z) −0.002 −0.001 −0.000 −0.002
(0.005) (0.004) (0.004) (0.004)
Num.Obs. 3668 3678 3678 3678
R2 0.096 0.092 0.093 0.095
R2 Adj. 0.093 0.090 0.091 0.093
N (dw_acute=1) 393.000 393.000 393.000 393.000
N (Sample) 3678.000 3678.000 3678.000 3678.000
* p < 0.1, ** p < 0.05, *** p < 0.01
Sample: DW users vs pure non-users. Variables z-standardized. Robust SEs.

4.1.6 TABLE (LOGIT): DW only (4 specs) - Acute Period

# -----------------------------------------------------------------------------
# 6) TABLE 6 (LOGIT): DW only (4 specs)
# -----------------------------------------------------------------------------

m_dw_logit <- run_4spec(df_dw_acute, "dw_acute", "logit")
n_dw_logit <- add_n_rows(df_dw_acute, "dw_acute", n_models = 4)

# SAVE TABLE 6
save_reg_table(m_dw_logit, "Table_6_DW_Acute_Logit",
               title_text = "Discount Window Usage During Acute Crisis (Logit)",
               notes_text = "This table reports Logit estimates of Discount Window usage during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed the Discount Window. Column (1) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4) use risk category dummies based on median splits: R1 = Low MTM, Low Uninsured (reference); R2 = Low MTM, High Uninsured; R3 = High MTM, Low Uninsured; R4 = High MTM, High Uninsured. The sample consists of DW users versus pure non-users, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized at 2.5/97.5 percentiles and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP, gof_map_use = gof_logit, add_rows_use = n_dw_logit)

modelsummary(
  m_dw_logit,
  stars    = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map  = gof_logit,
  add_rows = n_dw_logit,
  title    = "Table: Discount Window Usage (Logit) ? Acute Period (Mar 13?May 1, 2023)",
  notes    = "Sample: DW users vs pure non-users. Variables z-standardized. Robust SEs.",
  output   = "kableExtra"
)
Table: Discount Window Usage (Logit) ? Acute Period (Mar 13?May 1, 2023)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.194***
(0.068)
Uninsured Lev (z) 0.160***
(0.062)
MTM ? Uninsured 0.083
(0.053)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.440** −0.561***
(0.181) (0.191)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.294** 0.296
(0.139) (0.201)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.392*
(0.211)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.732***
(0.204)
Log(Assets) (z) 0.776*** 0.800*** 0.820*** 0.791***
(0.061) (0.057) (0.059) (0.060)
Cash Ratio (z) −0.088 −0.144* −0.091 −0.091
(0.089) (0.082) (0.084) (0.085)
Loan/Deposit (z) 0.091 0.043 0.069 0.084
(0.073) (0.071) (0.072) (0.072)
Book Equity (z) −0.167** −0.221*** −0.208*** −0.189**
(0.080) (0.078) (0.077) (0.078)
Wholesale (z) 0.172*** 0.166*** 0.164*** 0.168***
(0.053) (0.053) (0.053) (0.052)
ROA (z) 0.038 0.040 0.051 0.036
(0.066) (0.065) (0.065) (0.065)
Num.Obs. 3668 3678 3678 3678
N (dw_acute=1) 393.000 393.000 393.000 393.000
N (Sample) 3678.000 3678.000 3678.000 3678.000
* p < 0.1, ** p < 0.05, *** p < 0.01
Sample: DW users vs pure non-users. Variables z-standardized. Robust SEs.

5 TEMPORAL ANALYSIS

5.1 BTFP Temporal Analysis (Acute ? Post ? Arbitrage ? Wind-down)

# ==============================================================================
# BTFP ACROSS PERIODS
# Acute, Post-Acute: 2022Q4 baseline
# Arbitrage: 2023Q3 baseline
# Wind-down: 2023Q4 baseline
# ==============================================================================

# Function to run base + risk specifications
run_temporal_pair <- function(data, dv_var, family_type = "lpm") {
  
  f_base <- build_formula(dv_var, "mtm_total + uninsured_lev + mtm_x_uninsured")
  f_risk <- build_formula(dv_var, "run_risk_2 + run_risk_3 + run_risk_4")
  
  if (family_type == "lpm") {
    m_base <- feols(f_base, data = data, vcov = "hetero")
    m_risk <- feols(f_risk, data = data, vcov = "hetero")
  } else {
    m_base <- feglm(f_base, data = data, family = binomial("logit"), vcov = "hetero")
    m_risk <- feglm(f_risk, data = data, family = binomial("logit"), vcov = "hetero")
  }
  
  list(Base = m_base, Risk = m_risk)
}

5.1.1 Table: BTFP Temporal (LPM)

# Prepare samples
df_btfp_acute_s <- df_acute %>% filter(btfp_acute == 1 | non_user == 1)
df_btfp_post_s <- df_post %>% filter(btfp_post == 1 | non_user == 1)
df_btfp_arb_s <- df_arb %>% filter(btfp_arb == 1 | non_user == 1)
df_btfp_wind_s <- df_wind %>% filter(btfp_wind == 1 | non_user == 1)

# Run models
m_acute <- run_temporal_pair(df_btfp_acute_s, "btfp_acute", "lpm")
m_post <- run_temporal_pair(df_btfp_post_s, "btfp_post", "lpm")
m_arb <- run_temporal_pair(df_btfp_arb_s, "btfp_arb", "lpm")
m_wind <- run_temporal_pair(df_btfp_wind_s, "btfp_wind", "lpm")

models_btfp_temp <- list(
  "Acute (Base)" = m_acute$Base, "Acute (Risk)" = m_acute$Risk,
  "Post (Base)" = m_post$Base, "Post (Risk)" = m_post$Risk,
  "Arb (Base)" = m_arb$Base, "Arb (Risk)" = m_arb$Risk,
  "Wind (Base)" = m_wind$Base, "Wind (Risk)" = m_wind$Risk
)

# N rows
n_rows_temp <- data.frame(
  term = c("N (BTFP=1)", "N (Sample)", "Baseline"),
  `Acute (Base)` = c(sum(df_btfp_acute_s$btfp_acute), nrow(df_btfp_acute_s), "2022Q4"),
  `Acute (Risk)` = c(sum(df_btfp_acute_s$btfp_acute), nrow(df_btfp_acute_s), "2022Q4"),
  `Post (Base)` = c(sum(df_btfp_post_s$btfp_post), nrow(df_btfp_post_s), "2022Q4"),
  `Post (Risk)` = c(sum(df_btfp_post_s$btfp_post), nrow(df_btfp_post_s), "2022Q4"),
  `Arb (Base)` = c(sum(df_btfp_arb_s$btfp_arb), nrow(df_btfp_arb_s), "2023Q3"),
  `Arb (Risk)` = c(sum(df_btfp_arb_s$btfp_arb), nrow(df_btfp_arb_s), "2023Q3"),
  `Wind (Base)` = c(sum(df_btfp_wind_s$btfp_wind), nrow(df_btfp_wind_s), "2023Q4"),
  `Wind (Risk)` = c(sum(df_btfp_wind_s$btfp_wind), nrow(df_btfp_wind_s), "2023Q4"),
  check.names = FALSE
)

# SAVE TABLE 4A
save_reg_table(models_btfp_temp, "Table_4A_BTFP_Temporal_LPM",
               title_text = "BTFP Participation Across Program Periods (LPM)",
               notes_text = "This table reports Linear Probability Model (LPM) estimates of BTFP participation across four program periods. Acute: March 13--May 1, 2023 (crisis period). Post-Acute: May 2--November 2023 (stabilization). Arbitrage: November 2023--January 2024 (rate differential exploitation). Wind-down: February--March 2024 (program termination). The dependent variable equals one if the bank accessed BTFP in each period. Base specification includes MTM Loss, Uninsured Leverage, and their interaction. Risk specification includes Risk 2,3,4 dummies. Acute and Post-Acute use 2022Q4 baseline characteristics; Arbitrage uses 2023Q3; Wind-down uses 2023Q4. The sample consists of BTFP users versus pure non-users, excluding G-SIBs and failed institutions. All continuous variables are winsorized and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_rows_temp)

modelsummary(
  models_btfp_temp,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = gof_lpm,
  add_rows = n_rows_temp,
  title = "Table: BTFP Temporal Analysis (LPM)",
  notes = "Arbitrage uses 2023Q3 baseline; Wind-down uses 2023Q4 baseline.",
  output = "kableExtra"
)
Table: BTFP Temporal Analysis (LPM)
Acute (Base) Acute (Risk) Post (Base) Post (Risk) Arb (Base) Arb (Risk) Wind (Base) Wind (Risk)
MTM Loss (z) 0.024*** 0.025*** 0.022*** 0.002
(0.006) (0.008) (0.007) (0.004)
Uninsured Lev (z) 0.020*** 0.018** 0.014** 0.007*
(0.007) (0.008) (0.007) (0.004)
MTM ? Uninsured 0.018*** 0.016*** 0.008 −0.001
(0.005) (0.006) (0.005) (0.003)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.006 −0.007 0.029* 0.029***
(0.014) (0.019) (0.016) (0.009)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.001 0.007 0.040** 0.019*
(0.013) (0.019) (0.016) (0.010)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.062*** 0.055** 0.041** 0.032***
(0.017) (0.022) (0.018) (0.011)
Log(Assets) (z) 0.067*** 0.071*** 0.068*** 0.072*** 0.054*** 0.055*** −0.000 −0.001
(0.007) (0.007) (0.009) (0.009) (0.007) (0.007) (0.004) (0.004)
Cash Ratio (z) −0.024*** −0.027*** −0.052*** −0.055*** −0.036*** −0.040*** −0.010*** −0.009***
(0.005) (0.005) (0.007) (0.006) (0.006) (0.006) (0.004) (0.003)
Loan/Deposit (z) −0.008 −0.011** −0.021*** −0.023*** −0.003 −0.008 −0.010** −0.009**
(0.005) (0.005) (0.007) (0.007) (0.006) (0.006) (0.004) (0.004)
Book Equity (z) −0.011** −0.013*** −0.020*** −0.023*** −0.006 −0.010** −0.006* −0.006*
(0.004) (0.004) (0.006) (0.006) (0.006) (0.005) (0.003) (0.003)
Wholesale (z) 0.025*** 0.024*** 0.016** 0.015** 0.101*** 0.100*** 0.053*** 0.053***
(0.006) (0.006) (0.007) (0.007) (0.007) (0.007) (0.006) (0.006)
ROA (z) −0.005 −0.005 −0.013** −0.013** −0.024*** −0.024*** −0.007* −0.006*
(0.005) (0.005) (0.006) (0.006) (0.006) (0.005) (0.004) (0.004)
Num.Obs. 3737 3747 3515 3525 4038 4055 4043 4061
R2 0.090 0.089 0.080 0.080 0.142 0.141 0.063 0.064
R2 Adj. 0.088 0.087 0.078 0.077 0.141 0.139 0.061 0.062
N (BTFP=1) 462 462 775 775 766 766 229 229
N (Sample) 3747 3747 3525 3525 4055 4055 4061 4061
Baseline 2022Q4 2022Q4 2022Q4 2022Q4 2023Q3 2023Q3 2023Q4 2023Q4
* p < 0.1, ** p < 0.05, *** p < 0.01
Arbitrage uses 2023Q3 baseline; Wind-down uses 2023Q4 baseline.

5.1.2 Table: BTFP Arbitrage Period - All Specifications

models_btfp_arb <- run_4spec_models(df_btfp_arb_s, "btfp_arb", "lpm")
n_rows_arb <- create_n_rows(df_btfp_arb_s, "btfp_arb")

# SAVE TABLE 4B
save_reg_table(models_btfp_arb, "Table_4B_BTFP_Arbitrage_LPM",
               title_text = "BTFP Participation During Arbitrage Period (LPM)",
               notes_text = "This table reports Linear Probability Model (LPM) estimates of BTFP participation during the arbitrage period (November 2023--January 2024). The dependent variable equals one if the bank accessed BTFP. Column (1) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4) use risk category dummies. During this period, BTFP's below-market rate created arbitrage opportunities. Bank characteristics are from 2023Q3 Call Reports (baseline period for arbitrage analysis). The sample consists of BTFP users versus pure non-users, excluding G-SIBs and failed institutions. All continuous variables are winsorized at 2.5/97.5 percentiles and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_rows_arb)

modelsummary(
  models_btfp_arb,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = gof_lpm,
  add_rows = n_rows_arb,
  title = "Table: BTFP Arbitrage Period - All Specifications (LPM, 2023Q3 Baseline)",
  output = "kableExtra"
)
Table: BTFP Arbitrage Period - All Specifications (LPM, 2023Q3 Baseline)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.022***
(0.007)
Uninsured Lev (z) 0.014**
(0.007)
MTM ? Uninsured 0.008
(0.005)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.035*** −0.040***
(0.013) (0.014)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.011 0.029*
(0.016) (0.016)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.040**
(0.016)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.041**
(0.018)
Log(Assets) (z) 0.054*** 0.054*** 0.055*** 0.055***
(0.007) (0.007) (0.007) (0.007)
Cash Ratio (z) −0.036*** −0.041*** −0.040*** −0.040***
(0.006) (0.005) (0.006) (0.006)
Loan/Deposit (z) −0.003 −0.009 −0.008 −0.008
(0.006) (0.006) (0.006) (0.006)
Book Equity (z) −0.006 −0.011** −0.011** −0.010**
(0.006) (0.005) (0.005) (0.005)
Wholesale (z) 0.101*** 0.100*** 0.100*** 0.100***
(0.007) (0.007) (0.007) (0.007)
ROA (z) −0.024*** −0.024*** −0.024*** −0.024***
(0.006) (0.005) (0.005) (0.005)
Num.Obs. 4038 4055 4055 4055
R2 0.142 0.141 0.141 0.141
R2 Adj. 0.141 0.140 0.140 0.139
N (btfp_arb=1) 766.000 766.000 766.000 766.000
N (Sample) 4055.000 4055.000 4055.000 4055.000
* p < 0.1, ** p < 0.05, *** p < 0.01

5.1.3 Table: BTFP Temporal (LOGIT)

# ------------------------------------------------------------------------------
# Helper: run Base + Risk (Risk2,3,4) specs for LOGIT (kept generic)
# ------------------------------------------------------------------------------
run_temporal_pair <- function(data, dv_var, family_type = c("lpm","logit")) {
  family_type <- match.arg(family_type)

  f_base <- build_formula(dv_var, "mtm_total + uninsured_lev + mtm_x_uninsured")
  f_risk <- build_formula(dv_var, "run_risk_2 + run_risk_3 + run_risk_4")

  if (family_type == "lpm") {
    m_base <- feols(f_base, data = data, vcov = "hetero")
    m_risk <- feols(f_risk, data = data, vcov = "hetero")
  } else {
    m_base <- feglm(f_base, data = data, family = binomial("logit"), vcov = "hetero")
    m_risk <- feglm(f_risk, data = data, family = binomial("logit"), vcov = "hetero")
  }

  list(Base = m_base, Risk = m_risk)
}

# ==============================================================================
# Table (LOGIT): BTFP Temporal Analysis
# ==============================================================================
# Prepare samples (BTFP=1 vs pure non-users)
df_btfp_acute_s <- df_acute %>% filter(btfp_acute == 1 | non_user == 1)
df_btfp_post_s  <- df_post  %>% filter(btfp_post  == 1 | non_user == 1)
df_btfp_arb_s   <- df_arb   %>% filter(btfp_arb   == 1 | non_user == 1)
df_btfp_wind_s  <- df_wind  %>% filter(btfp_wind  == 1 | non_user == 1)

# Run LOGIT models
m_acute <- run_temporal_pair(df_btfp_acute_s, "btfp_acute", "logit")
m_post  <- run_temporal_pair(df_btfp_post_s,  "btfp_post",  "logit")
m_arb   <- run_temporal_pair(df_btfp_arb_s,   "btfp_arb",   "logit")
m_wind  <- run_temporal_pair(df_btfp_wind_s,  "btfp_wind",  "logit")

# Collect models (8 columns)
models_btfp_temp_logit <- list(
  "Acute (Base)" = m_acute$Base, "Acute (Risk)" = m_acute$Risk,
  "Post (Base)"  = m_post$Base,  "Post (Risk)"  = m_post$Risk,
  "Arb (Base)"   = m_arb$Base,   "Arb (Risk)"   = m_arb$Risk,
  "Wind (Base)"  = m_wind$Base,  "Wind (Risk)"  = m_wind$Risk
)

# N rows + baseline labels (same as your LPM table, but reused here)
n_rows_temp_logit <- data.frame(
  term = c("N (BTFP=1)", "N (Sample)", "Baseline"),
  `Acute (Base)` = c(sum(df_btfp_acute_s$btfp_acute, na.rm = TRUE), nrow(df_btfp_acute_s), "2022Q4"),
  `Acute (Risk)` = c(sum(df_btfp_acute_s$btfp_acute, na.rm = TRUE), nrow(df_btfp_acute_s), "2022Q4"),
  `Post (Base)`  = c(sum(df_btfp_post_s$btfp_post,  na.rm = TRUE), nrow(df_btfp_post_s),  "2022Q4"),
  `Post (Risk)`  = c(sum(df_btfp_post_s$btfp_post,  na.rm = TRUE), nrow(df_btfp_post_s),  "2022Q4"),
  `Arb (Base)`   = c(sum(df_btfp_arb_s$btfp_arb,    na.rm = TRUE), nrow(df_btfp_arb_s),   "2023Q3"),
  `Arb (Risk)`   = c(sum(df_btfp_arb_s$btfp_arb,    na.rm = TRUE), nrow(df_btfp_arb_s),   "2023Q3"),
  `Wind (Base)`  = c(sum(df_btfp_wind_s$btfp_wind,  na.rm = TRUE), nrow(df_btfp_wind_s),  "2023Q4"),
  `Wind (Risk)`  = c(sum(df_btfp_wind_s$btfp_wind,  na.rm = TRUE), nrow(df_btfp_wind_s),  "2023Q4"),
  check.names = FALSE
)

# SAVE TABLE 
save_reg_table(models_btfp_temp_logit, "Table_4C_BTFP_Temporal_LOGIT",
               title_text = "BTFP Participation Across Program Periods (Logit)",
               notes_text = "This table reports Logit estimates of BTFP participation across four program periods. Acute: March 13--May 1, 2023 (crisis period). Post-Acute: May 2--November 2023 (stabilization). Arbitrage: November 2023--January 2024 (rate differential exploitation). Wind-down: February--March 2024 (program termination). The dependent variable equals one if the bank accessed BTFP in each period. Base specification includes MTM Loss, Uninsured Leverage, and their interaction. Risk specification includes Risk 2,3,4 dummies. Acute and Post-Acute use 2022Q4 baseline characteristics; Arbitrage uses 2023Q3; Wind-down uses 2023Q4. The sample consists of BTFP users versus pure non-users, excluding G-SIBs and failed institutions. All continuous variables are winsorized and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_rows_temp)

modelsummary(
  models_btfp_temp_logit,
  stars    = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map  = gof_logit,
  add_rows = n_rows_temp_logit,
  title    = "Table: BTFP Temporal Analysis (Logit)",
  notes    = "Arbitrage uses 2023Q3 baseline; Wind-down uses 2023Q4 baseline.",
  output   = "kableExtra"
)
Table: BTFP Temporal Analysis (Logit)
Acute (Base) Acute (Risk) Post (Base) Post (Risk) Arb (Base) Arb (Risk) Wind (Base) Wind (Risk)
MTM Loss (z) 0.214*** 0.126** 0.165*** 0.042
(0.062) (0.049) (0.053) (0.090)
Uninsured Lev (z) 0.209*** 0.115** 0.143*** 0.185**
(0.063) (0.051) (0.051) (0.087)
MTM ? Uninsured 0.046 0.030 −0.014 −0.069
(0.057) (0.043) (0.044) (0.074)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured 0.192 0.056 0.408*** 0.794***
(0.188) (0.142) (0.148) (0.271)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.177 0.083 0.401*** 0.521**
(0.184) (0.133) (0.145) (0.258)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.592*** 0.287** 0.388*** 0.775***
(0.181) (0.139) (0.149) (0.269)
Log(Assets) (z) 0.574*** 0.611*** 0.378*** 0.407*** 0.364*** 0.387*** 0.013 −0.001
(0.062) (0.059) (0.052) (0.050) (0.049) (0.047) (0.084) (0.082)
Cash Ratio (z) −0.517*** −0.527*** −0.510*** −0.523*** −0.528*** −0.551*** −0.340*** −0.308***
(0.105) (0.104) (0.070) (0.068) (0.078) (0.076) (0.105) (0.099)
Loan/Deposit (z) 0.001 −0.018 −0.104** −0.117** 0.031 −0.004 −0.180** −0.159*
(0.066) (0.066) (0.051) (0.050) (0.054) (0.053) (0.089) (0.084)
Book Equity (z) −0.329*** −0.361*** −0.216*** −0.240*** −0.169*** −0.204*** −0.208** −0.197**
(0.082) (0.082) (0.056) (0.055) (0.059) (0.058) (0.098) (0.094)
Wholesale (z) 0.183*** 0.173*** 0.073* 0.067* 0.546*** 0.541*** 0.614*** 0.617***
(0.046) (0.046) (0.041) (0.040) (0.038) (0.038) (0.049) (0.050)
ROA (z) −0.016 −0.029 −0.084* −0.088* −0.223*** −0.232*** −0.172* −0.166*
(0.063) (0.063) (0.049) (0.048) (0.058) (0.057) (0.098) (0.096)
Num.Obs. 3737 3747 3515 3525 4038 4055 4043 4061
N (BTFP=1) 462 462 775 775 766 766 229 229
N (Sample) 3747 3747 3525 3525 4055 4055 4061 4061
Baseline 2022Q4 2022Q4 2022Q4 2022Q4 2023Q3 2023Q3 2023Q4 2023Q4
* p < 0.1, ** p < 0.05, *** p < 0.01
Arbitrage uses 2023Q3 baseline; Wind-down uses 2023Q4 baseline.
# ==============================================================================
# Table (LOGIT): BTFP Arbitrage Period ? All Specifications
# ==============================================================================
# NOTE: Uses your existing run_4spec_models() and create_n_rows()

models_btfp_arb_logit <- run_4spec_models(df_btfp_arb_s, "btfp_arb", "logit")
n_rows_arb_logit <- create_n_rows(df_btfp_arb_s, "btfp_arb")

save_reg_table(models_btfp_arb_logit, "Table_4D_BTFP_Arbitrage_LOGIT",
               title_text = "BTFP Participation During Arbitrage Period (Logit)",
               notes_text = "This table reports Logit estimates of BTFP participation during the arbitrage period (November 2023--January 2024). The dependent variable equals one if the bank accessed BTFP. Column (1) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4) use risk category dummies. During this period, BTFP's below-market rate created arbitrage opportunities. Bank characteristics are from 2023Q3 Call Reports. The sample consists of BTFP users versus pure non-users, excluding G-SIBs and failed institutions. All continuous variables are winsorized and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_rows_arb)

modelsummary(
  models_btfp_arb_logit,
  stars    = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map  = gof_logit,
  add_rows = n_rows_arb_logit,
  title    = "Table: BTFP Arbitrage Period - All Specifications (Logit, 2023Q3 Baseline)",
  output   = "kableExtra"
)
Table: BTFP Arbitrage Period - All Specifications (Logit, 2023Q3 Baseline)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.165***
(0.053)
Uninsured Lev (z) 0.143***
(0.051)
MTM ? Uninsured −0.014
(0.044)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.389*** −0.383***
(0.128) (0.134)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured 0.018 0.408***
(0.116) (0.148)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.401***
(0.145)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.388***
(0.149)
Log(Assets) (z) 0.364*** 0.387*** 0.386*** 0.387***
(0.049) (0.045) (0.046) (0.047)
Cash Ratio (z) −0.528*** −0.550*** −0.553*** −0.551***
(0.078) (0.075) (0.076) (0.076)
Loan/Deposit (z) 0.031 −0.002 −0.004 −0.004
(0.054) (0.052) (0.053) (0.053)
Book Equity (z) −0.169*** −0.205*** −0.206*** −0.204***
(0.059) (0.057) (0.058) (0.058)
Wholesale (z) 0.546*** 0.540*** 0.541*** 0.541***
(0.038) (0.038) (0.038) (0.038)
ROA (z) −0.223*** −0.230*** −0.231*** −0.232***
(0.058) (0.056) (0.056) (0.057)
Num.Obs. 4038 4055 4055 4055
N (btfp_arb=1) 766.000 766.000 766.000 766.000
N (Sample) 4055.000 4055.000 4055.000 4055.000
* p < 0.1, ** p < 0.05, *** p < 0.01

5.1.4 MTM loss on BTFP eligible only

# ------------------------------------------------------------------------------
# Helper: run Base + Risk (Risk2,3,4) specs for LPM (kept generic)
# ------------------------------------------------------------------------------
run_temporal_pair <- function(data, dv_var, family_type = c("lpm","logit")) {
  family_type <- match.arg(family_type)

  # This adds mtm_btfp, uninsured_lev, AND their interaction
  f_base <- build_formula(dv_var, "mtm_btfp + uninsured_lev + mtm_btfp:uninsured_lev")
  f_risk <- build_formula(dv_var, "run_risk_2 + run_risk_3 + run_risk_4")

  if (family_type == "lpm") {
    m_base <- feols(f_base, data = data, vcov = "hetero")
    m_risk <- feols(f_risk, data = data, vcov = "hetero")
  } else {
    m_base <- feglm(f_base, data = data, family = binomial("logit"), vcov = "hetero")
    m_risk <- feglm(f_risk, data = data, family = binomial("logit"), vcov = "hetero")
  }

  list(Base = m_base, Risk = m_risk)
}

# ==============================================================================
# Table 4A (LOGIT): BTFP Temporal Analysis
# ==============================================================================
# Prepare samples (BTFP=1 vs pure non-users)
df_btfp_acute_s <- df_acute %>% filter(btfp_acute == 1 | non_user == 1)
df_btfp_post_s  <- df_post  %>% filter(btfp_post  == 1 | non_user == 1)
df_btfp_arb_s   <- df_arb   %>% filter(btfp_arb   == 1 | non_user == 1)
df_btfp_wind_s  <- df_wind  %>% filter(btfp_wind  == 1 | non_user == 1)

# Run LOGIT models
m_acute <- run_temporal_pair(df_btfp_acute_s, "btfp_acute", "lpm")
m_post  <- run_temporal_pair(df_btfp_post_s,  "btfp_post",  "lpm")
m_arb   <- run_temporal_pair(df_btfp_arb_s,   "btfp_arb",   "lpm")
m_wind  <- run_temporal_pair(df_btfp_wind_s,  "btfp_wind",  "lpm")

# Collect models (8 columns)
models_btfp_temp_lpm <- list(
  "Acute (Base)" = m_acute$Base, "Acute (Risk)" = m_acute$Risk,
  "Post (Base)"  = m_post$Base,  "Post (Risk)"  = m_post$Risk,
  "Arb (Base)"   = m_arb$Base,   "Arb (Risk)"   = m_arb$Risk,
  "Wind (Base)"  = m_wind$Base,  "Wind (Risk)"  = m_wind$Risk
)

# N rows + baseline labels (same as your LPM table, but reused here)
n_rows_temp_lpm <- data.frame(
  term = c("N (BTFP=1)", "N (Sample)", "Baseline"),
  `Acute (Base)` = c(sum(df_btfp_acute_s$btfp_acute, na.rm = TRUE), nrow(df_btfp_acute_s), "2022Q4"),
  `Acute (Risk)` = c(sum(df_btfp_acute_s$btfp_acute, na.rm = TRUE), nrow(df_btfp_acute_s), "2022Q4"),
  `Post (Base)`  = c(sum(df_btfp_post_s$btfp_post,  na.rm = TRUE), nrow(df_btfp_post_s),  "2022Q4"),
  `Post (Risk)`  = c(sum(df_btfp_post_s$btfp_post,  na.rm = TRUE), nrow(df_btfp_post_s),  "2022Q4"),
  `Arb (Base)`   = c(sum(df_btfp_arb_s$btfp_arb,    na.rm = TRUE), nrow(df_btfp_arb_s),   "2023Q3"),
  `Arb (Risk)`   = c(sum(df_btfp_arb_s$btfp_arb,    na.rm = TRUE), nrow(df_btfp_arb_s),   "2023Q3"),
  `Wind (Base)`  = c(sum(df_btfp_wind_s$btfp_wind,  na.rm = TRUE), nrow(df_btfp_wind_s),  "2023Q4"),
  `Wind (Risk)`  = c(sum(df_btfp_wind_s$btfp_wind,  na.rm = TRUE), nrow(df_btfp_wind_s),  "2023Q4"),
  check.names = FALSE
)

save_reg_table(models_btfp_temp_lpm, "Table_4E_BTFP_Temporal_LPM",
               title_text = "BTFP Participation: BTFP-Eligible MTM Only (LPM)",
               notes_text = "This table reports Linear Probability Model (LPM) estimates of BTFP participation using only MTM losses on BTFP-eligible securities (BTFP-eligible collateral). The dependent variable equals one if the bank accessed BTFP. Base specification includes MTM_BTFP (losses on BTFP-eligible securities only), Uninsured Leverage, and their interaction. Risk specification includes Risk 2,3,4 dummies. This specification tests whether BTFP participation responds specifically to losses on eligible collateral. Acute and Post-Acute use 2022Q4 baseline characteristics; Arbitrage uses 2023Q3; Wind-down uses 2023Q4. The sample consists of BTFP users versus pure non-users, excluding G-SIBs and failed institutions. All continuous variables are winsorized and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_rows_temp)

modelsummary(
  models_btfp_temp_lpm,
  stars    = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map  = gof_logit,
  add_rows = n_rows_temp_lpm,
  title    = "Table 4E: BTFP Temporal Analysis (Logit)",
  notes    = "Arbitrage uses 2023Q3 baseline; Wind-down uses 2023Q4 baseline.",
  output   = "kableExtra"
)
Table 4E: BTFP Temporal Analysis (Logit)
Acute (Base) Acute (Risk) Post (Base) Post (Risk) Arb (Base) Arb (Risk) Wind (Base) Wind (Risk)
MTM Loss OMO (z) 0.016** 0.029*** 0.007 0.010**
(0.006) (0.008) (0.007) (0.005)
Uninsured Lev (z) 0.014** 0.012 0.010 0.008*
(0.006) (0.008) (0.006) (0.004)
MTM_btfp ? Uninsured Lev 0.011** −0.001 −0.005 −0.004
(0.005) (0.007) (0.005) (0.004)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.006 −0.007 0.029* 0.029***
(0.014) (0.019) (0.016) (0.009)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.001 0.007 0.040** 0.019*
(0.013) (0.019) (0.016) (0.010)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.062*** 0.055** 0.041** 0.032***
(0.017) (0.022) (0.018) (0.011)
Log(Assets) (z) 0.066*** 0.071*** 0.065*** 0.072*** 0.054*** 0.055*** −0.001 −0.001
(0.007) (0.007) (0.009) (0.009) (0.007) (0.007) (0.004) (0.004)
Cash Ratio (z) −0.031*** −0.027*** −0.057*** −0.055*** −0.043*** −0.040*** −0.010*** −0.009***
(0.004) (0.005) (0.006) (0.006) (0.005) (0.006) (0.003) (0.003)
Loan/Deposit (z) −0.009 −0.011** −0.014* −0.023*** −0.007 −0.008 −0.006 −0.009**
(0.006) (0.005) (0.008) (0.007) (0.007) (0.006) (0.004) (0.004)
Book Equity (z) −0.014*** −0.013*** −0.023*** −0.023*** −0.010* −0.010** −0.006* −0.006*
(0.004) (0.004) (0.006) (0.006) (0.006) (0.005) (0.003) (0.003)
Wholesale (z) 0.024*** 0.024*** 0.013* 0.015** 0.100*** 0.100*** 0.052*** 0.053***
(0.006) (0.006) (0.007) (0.007) (0.008) (0.007) (0.006) (0.006)
ROA (z) −0.004 −0.005 −0.010 −0.013** −0.027*** −0.024*** −0.006 −0.006*
(0.005) (0.005) (0.006) (0.006) (0.006) (0.005) (0.004) (0.004)
Num.Obs. 3737 3747 3515 3525 4038 4055 4043 4061
N (BTFP=1) 462 462 775 775 766 766 229 229
N (Sample) 3747 3747 3525 3525 4055 4055 4061 4061
Baseline 2022Q4 2022Q4 2022Q4 2022Q4 2023Q3 2023Q3 2023Q4 2023Q4
* p < 0.1, ** p < 0.05, *** p < 0.01
Arbitrage uses 2023Q3 baseline; Wind-down uses 2023Q4 baseline.

5.2 DW Temporal Analysis

5.2.1 Table: DW Temporal (Pre-BTFP ? Acute)

# Pre-BTFP
df_dw_pre_s <- df_prebtfp %>% filter(dw_prebtfp == 1 | non_user == 1)
m_pre <- run_temporal_pair(df_dw_pre_s, "dw_prebtfp", "lpm")

# Acute
m_dw_acute <- run_temporal_pair(df_dw_acute, "dw_acute", "lpm")

models_dw_temp <- list(
  "Pre-BTFP (Base)" = m_pre$Base, "Pre-BTFP (Risk)" = m_pre$Risk,
  "Acute (Base)" = m_dw_acute$Base, "Acute (Risk)" = m_dw_acute$Risk
)

n_rows_dw_temp <- data.frame(
  term = c("N (DW=1)", "N (Sample)"),
  `Pre-BTFP (Base)` = c(sum(df_dw_pre_s$dw_prebtfp), nrow(df_dw_pre_s)),
  `Pre-BTFP (Risk)` = c(sum(df_dw_pre_s$dw_prebtfp), nrow(df_dw_pre_s)),
  `Acute (Base)` = c(sum(df_dw_acute$dw_acute), nrow(df_dw_acute)),
  `Acute (Risk)` = c(sum(df_dw_acute$dw_acute), nrow(df_dw_acute)),
  check.names = FALSE
)

# SAVE TABLE 4D
save_reg_table(models_dw_temp, "Table_4D_DW_Temporal_LPM",
               title_text = "Discount Window Usage: Pre-BTFP vs Acute Period (LPM)",
               notes_text = "This table reports Linear Probability Model (LPM) estimates comparing Discount Window usage before and after BTFP introduction. Pre-BTFP: March 1--12, 2023 (only DW available for emergency liquidity). Acute: March 13--May 1, 2023 (both DW and BTFP available). The dependent variable equals one if the bank accessed the Discount Window. Base specification includes MTM_BTFP (losses on BTFP-eligible securities), Uninsured Leverage, and their interaction. Risk specification includes Risk 2,3,4 dummies. The sample consists of DW users versus pure non-users, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_rows_dw_temp)

modelsummary(
  models_dw_temp,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = gof_lpm,
  add_rows = n_rows_dw_temp,
  title = "Table: DW Temporal Analysis (LPM)",
  output = "kableExtra"
)
Table: DW Temporal Analysis (LPM)
Pre-BTFP (Base) Pre-BTFP (Risk) Acute (Base) Acute (Risk)
MTM Loss OMO (z) −0.000 0.008
(0.003) (0.006)
Uninsured Lev (z) 0.000 0.008
(0.003) (0.006)
MTM_btfp ? Uninsured Lev −0.000 0.009*
(0.002) (0.005)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.004 −0.002
(0.007) (0.013)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.001 0.007
(0.006) (0.012)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured −0.001 0.050***
(0.008) (0.016)
Log(Assets) (z) 0.024*** 0.024*** 0.084*** 0.085***
(0.004) (0.004) (0.007) (0.007)
Cash Ratio (z) −0.005** −0.004** −0.007 −0.004
(0.002) (0.002) (0.005) (0.005)
Loan/Deposit (z) −0.001 −0.001 −0.003 −0.003
(0.003) (0.003) (0.006) (0.005)
Book Equity (z) 0.001 0.001 −0.003 −0.001
(0.002) (0.002) (0.004) (0.004)
Wholesale (z) 0.013*** 0.013*** 0.020*** 0.020***
(0.004) (0.004) (0.006) (0.006)
ROA (z) −0.002 −0.002 −0.001 −0.002
(0.002) (0.002) (0.005) (0.004)
Num.Obs. 3988 3998 3668 3678
R2 0.035 0.035 0.093 0.095
R2 Adj. 0.033 0.033 0.091 0.093
N (DW=1) 100.000 100.000 393.000 393.000
N (Sample) 3998.000 3998.000 3678.000 3678.000
* p < 0.1, ** p < 0.05, *** p < 0.01

6 SIZE ANALYSIS

# ==============================================================================
# FUNCTION: Generate Size Analysis Tables
# ==============================================================================

generate_size_tables <- function(data, dv_var, dv_label) {
  
  sizes <- c("Small (<$1B)", "Medium ($1B-$100B)", "Large (>$100B)")
  short_names <- c("Small", "Medium", "Large")
  
  models_lpm_base <- list()
  models_lpm_risk <- list()
  models_log_base <- list()
  models_log_risk <- list()
  
  n_info <- data.frame(term = c("N (Users)", "N (Sample)"))
  
  for (i in seq_along(sizes)) {
    sz <- sizes[i]
    nm <- short_names[i]
    
    df_sub <- data %>% filter(size_cat == sz)
    n_users <- sum(df_sub[[dv_var]] == 1, na.rm = TRUE)
    n_total <- nrow(df_sub)
    
    f_base <- build_formula(dv_var, "mtm_total + uninsured_lev + mtm_x_uninsured")
    f_r1 <- build_formula(dv_var, "run_risk_1")
    f_r12 <- build_formula(dv_var, "run_risk_1 + run_risk_2")
    f_r234 <- build_formula(dv_var, "run_risk_2 + run_risk_3 + run_risk_4")
    
    if (n_users >= 5) {
      # LPM
      models_lpm_base[[paste0(nm, " (Base)")]] <- feols(f_base, data = df_sub, vcov = "hetero")
      models_lpm_risk[[paste0(nm, " (+R1)")]] <- feols(f_r1, data = df_sub, vcov = "hetero")
      models_lpm_risk[[paste0(nm, " (+R1,2)")]] <- feols(f_r12, data = df_sub, vcov = "hetero")
      models_lpm_risk[[paste0(nm, " (R2,3,4)")]] <- feols(f_r234, data = df_sub, vcov = "hetero")
      
      n_info[[paste0(nm, " (Base)")]] <- c(n_users, n_total)
      n_info[[paste0(nm, " (+R1)")]] <- c(n_users, n_total)
      n_info[[paste0(nm, " (+R1,2)")]] <- c(n_users, n_total)
      n_info[[paste0(nm, " (R2,3,4)")]] <- c(n_users, n_total)
    }
  }
  
  # Combine base and risk models
  all_models <- c(models_lpm_base, models_lpm_risk)
  
  list(models = all_models, n_rows = n_info)
}

6.1 Small Banks - Acute Period

# Small banks sample
df_small_btfp <- df_acute %>% filter(size_cat == "Small (<$1B)") %>% filter(btfp_acute == 1 | non_user == 1)
df_small_dw <- df_acute %>% filter(size_cat == "Small (<$1B)") %>% filter(dw_acute == 1 | non_user == 1)
df_small_anyfed <- df_acute %>% filter(size_cat == "Small (<$1B)") %>% filter(any_fed == 1 | non_user == 1)

# BTFP
if (sum(df_small_btfp$btfp_acute) >= 5) {
  models_small_btfp <- run_4spec_models(df_small_btfp, "btfp_acute", "lpm")
  n_small_btfp <- create_n_rows(df_small_btfp, "btfp_acute")
  
  # SAVE TABLE 5A
  save_reg_table(models_small_btfp, "Table_5A_BTFP_Small_LPM",
                 title_text = "BTFP Participation: Small Banks (LPM)",
                 notes_text = "This table reports Linear Probability Model (LPM) estimates of BTFP participation among small banks (total assets less than \\$1 billion) during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed BTFP. Column (1) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4) use risk category dummies. The sample consists of BTFP users versus pure non-users within the small bank category, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized at 2.5/97.5 percentiles and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
                 coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_small_btfp)

  modelsummary(
    models_small_btfp,
    stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
    coef_map = COEF_MAP, gof_map = gof_lpm, add_rows = n_small_btfp,
    title = "Table: BTFP Usage - Small Banks (<$1B) - Acute Period (LPM)",
    output = "kableExtra"
  )
}
Table: BTFP Usage - Small Banks (<$1B) - Acute Period (LPM)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.021***
(0.006)
Uninsured Lev (z) 0.015**
(0.007)
MTM ? Uninsured 0.014***
(0.005)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.014 −0.023*
(0.011) (0.012)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.026* −0.000
(0.014) (0.014)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.006
(0.013)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.054***
(0.018)
Log(Assets) (z) 0.061*** 0.066*** 0.068*** 0.063***
(0.009) (0.009) (0.009) (0.009)
Cash Ratio (z) −0.020*** −0.025*** −0.021*** −0.022***
(0.004) (0.004) (0.004) (0.004)
Loan/Deposit (z) −0.006 −0.011** −0.010* −0.007
(0.006) (0.006) (0.006) (0.006)
Book Equity (z) −0.009** −0.014*** −0.013*** −0.011***
(0.004) (0.004) (0.004) (0.004)
Wholesale (z) 0.021*** 0.020*** 0.020*** 0.020***
(0.007) (0.007) (0.007) (0.007)
ROA (z) −0.002 −0.002 −0.001 −0.002
(0.005) (0.004) (0.004) (0.004)
Num.Obs. 3013 3022 3022 3022
R2 0.056 0.052 0.053 0.056
R2 Adj. 0.053 0.050 0.050 0.053
N (btfp_acute=1) 280.000 280.000 280.000 280.000
N (Sample) 3022.000 3022.000 3022.000 3022.000
* p < 0.1, ** p < 0.05, *** p < 0.01
# DW
if (sum(df_small_dw$dw_acute) >= 5) {
  models_small_dw <- run_4spec_models(df_small_dw, "dw_acute", "lpm")
  n_small_dw <- create_n_rows(df_small_dw, "dw_acute")
  
  # SAVE TABLE 5B
  save_reg_table(models_small_dw, "Table_5B_DW_Small_LPM",
                 title_text = "Discount Window Usage: Small Banks (LPM)",
                 notes_text = "This table reports Linear Probability Model (LPM) estimates of Discount Window usage among small banks (total assets less than \\$1 billion) during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed the Discount Window. Column (1) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4) use risk category dummies. The sample consists of DW users versus pure non-users within the small bank category, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
                 coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_small_dw)

  modelsummary(
    models_small_dw,
    stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
    coef_map = COEF_MAP, gof_map = gof_lpm, add_rows = n_small_dw,
    title = "Table 5B: DW Usage - Small Banks (<$1B) - Acute Period (LPM)",
    output = "kableExtra"
  )
}
Table 5B: DW Usage - Small Banks (<$1B) - Acute Period (LPM)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.013**
(0.006)
Uninsured Lev (z) 0.002
(0.006)
MTM ? Uninsured 0.009**
(0.005)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.015 −0.025**
(0.010) (0.011)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.025** 0.001
(0.013) (0.013)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.019
(0.012)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.036**
(0.016)
Log(Assets) (z) 0.063*** 0.060*** 0.061*** 0.060***
(0.009) (0.008) (0.008) (0.008)
Cash Ratio (z) 0.003 −0.001 0.003 0.002
(0.005) (0.004) (0.004) (0.004)
Loan/Deposit (z) 0.001 −0.000 0.001 0.002
(0.005) (0.005) (0.005) (0.005)
Book Equity (z) −0.007* −0.008** −0.008** −0.007**
(0.004) (0.003) (0.003) (0.003)
Wholesale (z) 0.016*** 0.017*** 0.017*** 0.017***
(0.006) (0.006) (0.006) (0.006)
ROA (z) −0.001 −0.001 −0.000 −0.001
(0.004) (0.004) (0.004) (0.004)
Num.Obs. 2933 2942 2942 2942
R2 0.037 0.036 0.037 0.037
R2 Adj. 0.034 0.033 0.034 0.034
N (dw_acute=1) 200.000 200.000 200.000 200.000
N (Sample) 2942.000 2942.000 2942.000 2942.000
* p < 0.1, ** p < 0.05, *** p < 0.01
# Any Fed
if (sum(df_small_anyfed$any_fed) >= 5) {
  models_small_anyfed <- run_4spec_models(df_small_anyfed, "any_fed", "lpm")
  n_small_anyfed <- create_n_rows(df_small_anyfed, "any_fed")
  
  # SAVE TABLE 5C
  save_reg_table(models_small_anyfed, "Table_5C_AnyFed_Small_LPM",
                 title_text = "Any Federal Facility Usage: Small Banks (LPM)",
                 notes_text = "This table reports Linear Probability Model (LPM) estimates of any Federal Reserve emergency facility usage (BTFP or Discount Window) among small banks (total assets less than \\$1 billion) during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed either BTFP or DW. Column (1) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4) use risk category dummies. The sample consists of facility users versus pure non-users within the small bank category, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
                 coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_small_anyfed)

  modelsummary(
    models_small_anyfed,
    stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
    coef_map = COEF_MAP, gof_map = gof_lpm, add_rows = n_small_anyfed,
    title = "Table 5C: Any Fed Usage - Small Banks (<$1B) - Acute Period (LPM)",
    output = "kableExtra"
  )
}
Table 5C: Any Fed Usage - Small Banks (<$1B) - Acute Period (LPM)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.026***
(0.007)
Uninsured Lev (z) 0.010
(0.008)
MTM ? Uninsured 0.015**
(0.006)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.023* −0.037***
(0.013) (0.014)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.038** 0.001
(0.016) (0.016)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.023
(0.015)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.062***
(0.020)
Log(Assets) (z) 0.099*** 0.099*** 0.102*** 0.098***
(0.011) (0.010) (0.011) (0.011)
Cash Ratio (z) −0.013** −0.021*** −0.016*** −0.016***
(0.006) (0.005) (0.006) (0.006)
Loan/Deposit (z) −0.002 −0.007 −0.005 −0.003
(0.007) (0.007) (0.006) (0.007)
Book Equity (z) −0.014*** −0.018*** −0.018*** −0.016***
(0.005) (0.005) (0.005) (0.005)
Wholesale (z) 0.028*** 0.028*** 0.027*** 0.028***
(0.007) (0.007) (0.007) (0.007)
ROA (z) −0.002 −0.002 −0.001 −0.002
(0.005) (0.005) (0.005) (0.005)
Num.Obs. 3176 3185 3185 3185
R2 0.069 0.066 0.068 0.069
R2 Adj. 0.067 0.064 0.065 0.066
N (any_fed=1) 443.000 443.000 443.000 443.000
N (Sample) 3185.000 3185.000 3185.000 3185.000
* p < 0.1, ** p < 0.05, *** p < 0.01

6.2 Medium Banks - Acute Period

# Medium banks sample
df_med_btfp <- df_acute %>% filter(size_cat == "Medium ($1B-$100B)") %>% filter(btfp_acute == 1 | non_user == 1)
df_med_dw <- df_acute %>% filter(size_cat == "Medium ($1B-$100B)") %>% filter(dw_acute == 1 | non_user == 1)
df_med_anyfed <- df_acute %>% filter(size_cat == "Medium ($1B-$100B)") %>% filter(any_fed == 1 | non_user == 1)

# BTFP
if (sum(df_med_btfp$btfp_acute) >= 5) {
  models_med_btfp <- run_4spec_models(df_med_btfp, "btfp_acute", "lpm")
  n_med_btfp <- create_n_rows(df_med_btfp, "btfp_acute")
  
  # SAVE TABLE 5D
  save_reg_table(models_med_btfp, "Table_5D_BTFP_Medium_LPM",
                 title_text = "BTFP Participation: Medium Banks (LPM)",
                 notes_text = "This table reports Linear Probability Model (LPM) estimates of BTFP participation among medium banks (total assets \\$1 billion--\\$100 billion) during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed BTFP. Column (1) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4) use risk category dummies. The sample consists of BTFP users versus pure non-users within the medium bank category, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized at 2.5/97.5 percentiles and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
                 coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_med_btfp)

  modelsummary(
    models_med_btfp,
    stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
    coef_map = COEF_MAP, gof_map = gof_lpm, add_rows = n_med_btfp,
    title = "Table: BTFP Usage - Medium Banks ($1B-$100B) - Acute Period (LPM)",
    output = "kableExtra"
  )
}
Table: BTFP Usage - Medium Banks ($1B-$100B) - Acute Period (LPM)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.032*
(0.019)
Uninsured Lev (z) 0.032*
(0.016)
MTM ? Uninsured 0.017
(0.014)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.034 −0.064
(0.047) (0.051)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.056 0.007
(0.036) (0.049)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.004
(0.060)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.092*
(0.053)
Log(Assets) (z) 0.082*** 0.084*** 0.090*** 0.086***
(0.029) (0.029) (0.029) (0.029)
Cash Ratio (z) −0.069*** −0.081*** −0.070*** −0.069***
(0.023) (0.019) (0.020) (0.020)
Loan/Deposit (z) −0.023 −0.034* −0.027 −0.024
(0.020) (0.019) (0.020) (0.019)
Book Equity (z) −0.046** −0.057*** −0.055*** −0.052**
(0.021) (0.021) (0.021) (0.021)
Wholesale (z) 0.036** 0.036** 0.035** 0.036**
(0.017) (0.016) (0.016) (0.016)
ROA (z) −0.008 −0.013 −0.010 −0.014
(0.019) (0.018) (0.018) (0.018)
Num.Obs. 709 710 710 710
R2 0.078 0.069 0.072 0.077
R2 Adj. 0.066 0.060 0.062 0.065
N (btfp_acute=1) 177.000 177.000 177.000 177.000
N (Sample) 710.000 710.000 710.000 710.000
* p < 0.1, ** p < 0.05, *** p < 0.01
# DW
if (sum(df_med_dw$dw_acute) >= 5) {
  models_med_dw <- run_4spec_models(df_med_dw, "dw_acute", "lpm")
  n_med_dw <- create_n_rows(df_med_dw, "dw_acute")
  
  # SAVE TABLE 5E
  save_reg_table(models_med_dw, "Table_5E_DW_Medium_LPM",
                 title_text = "Discount Window Usage: Medium Banks (LPM)",
                 notes_text = "This table reports Linear Probability Model (LPM) estimates of Discount Window usage among medium banks (total assets \\$1 billion--\\$100 billion) during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed the Discount Window. Column (1) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4) use risk category dummies. The sample consists of DW users versus pure non-users within the medium bank category, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
                 coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_med_dw)

  modelsummary(
    models_med_dw,
    stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
    coef_map = COEF_MAP, gof_map = gof_lpm, add_rows = n_med_dw,
    title = "Table 5E: DW Usage - Medium Banks ($1B-$100B) - Acute Period (LPM)",
    output = "kableExtra"
  )
}
Table 5E: DW Usage - Medium Banks ($1B-$100B) - Acute Period (LPM)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.024
(0.019)
Uninsured Lev (z) 0.052***
(0.016)
MTM ? Uninsured 0.024*
(0.014)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.068 −0.086
(0.050) (0.054)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.035 0.052
(0.037) (0.052)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.001
(0.062)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.128**
(0.056)
Log(Assets) (z) 0.094*** 0.101*** 0.105*** 0.098***
(0.029) (0.029) (0.029) (0.029)
Cash Ratio (z) −0.055** −0.062*** −0.055** −0.053**
(0.025) (0.022) (0.022) (0.023)
Loan/Deposit (z) −0.012 −0.023 −0.018 −0.014
(0.020) (0.020) (0.020) (0.019)
Book Equity (z) 0.011 −0.002 −0.001 0.003
(0.022) (0.022) (0.022) (0.022)
Wholesale (z) 0.027 0.026 0.026 0.027*
(0.017) (0.016) (0.016) (0.016)
ROA (z) 0.020 0.019 0.020 0.016
(0.020) (0.019) (0.019) (0.019)
Num.Obs. 720 721 721 721
R2 0.059 0.045 0.046 0.055
R2 Adj. 0.047 0.036 0.036 0.043
N (dw_acute=1) 188.000 188.000 188.000 188.000
N (Sample) 721.000 721.000 721.000 721.000
* p < 0.1, ** p < 0.05, *** p < 0.01
# Any Fed
if (sum(df_med_anyfed$any_fed) >= 5) {
  models_med_anyfed <- run_4spec_models(df_med_anyfed, "any_fed", "lpm")
  n_med_anyfed <- create_n_rows(df_med_anyfed, "any_fed")
  
  # SAVE TABLE 5F
  save_reg_table(models_med_anyfed, "Table_5F_AnyFed_Medium_LPM",
                 title_text = "Any Federal Facility Usage: Medium Banks (LPM)",
                 notes_text = "This table reports Linear Probability Model (LPM) estimates of any Federal Reserve emergency facility usage (BTFP or Discount Window) among medium banks (total assets \\$1 billion--\\$100 billion) during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed either BTFP or DW. Column (1) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4) use risk category dummies. The sample consists of facility users versus pure non-users within the medium bank category, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
                 coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_med_anyfed)

  modelsummary(
    models_med_anyfed,
    stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
    coef_map = COEF_MAP, gof_map = gof_lpm, add_rows = n_med_anyfed,
    title = "Table: Any Fed Usage - Medium Banks ($1B-$100B) - Acute Period (LPM)",
    output = "kableExtra"
  )
}
Table: Any Fed Usage - Medium Banks ($1B-$100B) - Acute Period (LPM)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.038*
(0.021)
Uninsured Lev (z) 0.040**
(0.017)
MTM ? Uninsured 0.018
(0.015)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.068 −0.098*
(0.053) (0.056)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.058 0.041
(0.037) (0.055)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.024
(0.067)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.134**
(0.059)
Log(Assets) (z) 0.084*** 0.086*** 0.092*** 0.086***
(0.029) (0.028) (0.029) (0.029)
Cash Ratio (z) −0.081*** −0.096*** −0.084*** −0.082***
(0.026) (0.022) (0.023) (0.023)
Loan/Deposit (z) −0.020 −0.035* −0.027 −0.023
(0.022) (0.021) (0.021) (0.021)
Book Equity (z) −0.016 −0.028 −0.026 −0.022
(0.024) (0.023) (0.023) (0.023)
Wholesale (z) 0.030* 0.030* 0.029* 0.030*
(0.016) (0.016) (0.016) (0.016)
ROA (z) 0.020 0.016 0.018 0.014
(0.020) (0.019) (0.019) (0.019)
Num.Obs. 842 843 843 843
R2 0.060 0.051 0.054 0.059
R2 Adj. 0.049 0.043 0.045 0.049
N (any_fed=1) 310.000 310.000 310.000 310.000
N (Sample) 843.000 843.000 843.000 843.000
* p < 0.1, ** p < 0.05, *** p < 0.01

6.3 Large Banks - Acute Period

# Large banks sample
df_large_btfp <- df_acute %>% filter(size_cat == "Large (>$100B)") %>% filter(btfp_acute == 1 | non_user == 1)
df_large_dw <- df_acute %>% filter(size_cat == "Large (>$100B)") %>% filter(dw_acute == 1 | non_user == 1)
df_large_anyfed <- df_acute %>% filter(size_cat == "Large (>$100B)") %>% filter(any_fed == 1 | non_user == 1)

# BTFP
if (sum(df_large_btfp$btfp_acute) >= 5) {
  models_large_btfp <- run_4spec_models(df_large_btfp, "btfp_acute", "lpm")
  n_large_btfp <- create_n_rows(df_large_btfp, "btfp_acute")
  
  # SAVE TABLE 5G
  save_reg_table(models_large_btfp, "Table_5G_BTFP_Large_LPM",
                 title_text = "BTFP Participation: Large Banks (LPM)",
                 notes_text = "This table reports Linear Probability Model (LPM) estimates of BTFP participation among large banks (total assets exceeding \\$100 billion) during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed BTFP. Column (1) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4) use risk category dummies. The sample consists of BTFP users versus pure non-users within the large bank category, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized at 2.5/97.5 percentiles and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
                 coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_large_btfp)

  modelsummary(
    models_large_btfp,
    stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
    coef_map = COEF_MAP, gof_map = gof_lpm, add_rows = n_large_btfp,
    title = "Table: BTFP Usage - Large Banks (>$100B) - Acute Period (LPM)",
    output = "kableExtra"
  )
}
Table: BTFP Usage - Large Banks (>$100B) - Acute Period (LPM)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.066
(0.226)
Uninsured Lev (z) 0.734***
(0.167)
MTM ? Uninsured −0.069
(0.124)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.246 0.244
(0.826) (0.920)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.003 1.324
(0.574) (2.423)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.247
(1.228)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 1.595
(2.798)
Cash Ratio (z) 0.175 0.034 0.034 0.359
(0.215) (0.234) (0.267) (0.525)
Loan/Deposit (z) 0.008 −0.023 −0.023 0.648
(0.306) (0.391) (0.429) (1.067)
Book Equity (z) −0.541 0.116 0.118 −0.951
(0.346) (0.579) (0.669) (1.592)
Wholesale (z) −0.610 −0.043 −0.042 −0.864
(0.461) (0.582) (0.708) (1.217)
ROA (z) 0.633* −0.020 −0.020 −0.006
(0.290) (0.412) (0.447) (0.544)
Num.Obs. 15 15 15 15
R2 0.769 0.045 0.045 0.128
R2 Adj. 0.461 −0.672 −0.910 −1.034
N (btfp_acute=1) 5.000 5.000 5.000 5.000
N (Sample) 15.000 15.000 15.000 15.000
* p < 0.1, ** p < 0.05, *** p < 0.01
# DW
if (sum(df_large_dw$dw_acute) >= 5) {
  models_large_dw <- run_4spec_models(df_large_dw, "dw_acute", "lpm")
  n_large_dw <- create_n_rows(df_large_dw, "dw_acute")
  
  # SAVE TABLE 5H
  save_reg_table(models_large_dw, "Table_5H_DW_Large_LPM",
                 title_text = "Discount Window Usage: Large Banks (LPM)",
                 notes_text = "This table reports Linear Probability Model (LPM) estimates of Discount Window usage among large banks (total assets exceeding \\$100 billion) during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed the Discount Window. Column (1) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4) use risk category dummies. The sample consists of DW users versus pure non-users within the large bank category, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
                 coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_large_dw)

  modelsummary(
    models_large_dw,
    stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
    coef_map = COEF_MAP, gof_map = gof_lpm, add_rows = n_large_dw,
    title = "Table: DW Usage - Large Banks (>$100B) - Acute Period (LPM)",
    output = "kableExtra"
  )
}
Table: DW Usage - Large Banks (>$100B) - Acute Period (LPM)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) −0.236
(0.256)
Uninsured Lev (z) 0.422***
(0.088)
MTM ? Uninsured −0.258
(0.170)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.350 −0.244
(1.166) (1.366)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured 0.122 1.886*
(0.562) (0.948)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.768
(0.893)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 2.258
(1.305)
Cash Ratio (z) −0.007 0.151 0.121 0.541
(0.263) (0.186) (0.243) (0.330)
Loan/Deposit (z) −0.013 0.216 0.181 0.994
(0.445) (0.328) (0.399) (0.603)
Book Equity (z) −1.121*** −0.507 −0.531 −1.722**
(0.223) (0.496) (0.464) (0.579)
Wholesale (z) −1.335*** −0.806* −0.861 −1.566**
(0.333) (0.417) (0.473) (0.532)
ROA (z) 0.328 −0.016 −0.001 −0.065
(0.355) (0.271) (0.269) (0.324)
Num.Obs. 15 15 15 15
R2 0.742 0.309 0.316 0.516
R2 Adj. 0.398 −0.209 −0.368 −0.129
N (dw_acute=1) 5.000 5.000 5.000 5.000
N (Sample) 15.000 15.000 15.000 15.000
* p < 0.1, ** p < 0.05, *** p < 0.01
# Any Fed
if (sum(df_large_anyfed$any_fed) >= 5) {
  models_large_anyfed <- run_4spec_models(df_large_anyfed, "any_fed", "lpm")
  n_large_anyfed <- create_n_rows(df_large_anyfed, "any_fed")
  
  # SAVE TABLE 5I
  save_reg_table(models_large_anyfed, "Table_5I_AnyFed_Large_LPM",
                 title_text = "Any Federal Facility Usage: Large Banks (LPM)",
                 notes_text = "This table reports Linear Probability Model (LPM) estimates of any Federal Reserve emergency facility usage (BTFP or Discount Window) among large banks (total assets exceeding \\$100 billion) during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed either BTFP or DW. Column (1) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4) use risk category dummies. The sample consists of facility users versus pure non-users within the large bank category, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
                 coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_large_anyfed)

  modelsummary(
    models_large_anyfed,
    stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
    coef_map = COEF_MAP, gof_map = gof_lpm, add_rows = n_large_anyfed,
    title = "Table: Any Fed Usage - Large Banks (>$100B) - Acute Period (LPM)",
    output = "kableExtra"
  )
}
Table: Any Fed Usage - Large Banks (>$100B) - Acute Period (LPM)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.058
(0.257)
Uninsured Lev (z) 0.475***
(0.094)
MTM ? Uninsured −0.111
(0.161)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.066 −0.119
(1.009) (1.171)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.067 1.406
(0.472) (1.040)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.391
(0.819)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 1.765
(1.226)
Cash Ratio (z) 0.267 0.093 0.108 0.442
(0.254) (0.216) (0.256) (0.326)
Loan/Deposit (z) 0.349 0.180 0.193 0.859
(0.407) (0.341) (0.394) (0.572)
Book Equity (z) −1.070*** −0.288 −0.269 −1.350*
(0.212) (0.449) (0.499) (0.671)
Wholesale (z) −1.058*** −0.482 −0.468 −1.265**
(0.309) (0.399) (0.458) (0.437)
ROA (z) 0.133 −0.132 −0.139 −0.188
(0.310) (0.234) (0.257) (0.276)
Num.Obs. 18 18 18 18
R2 0.607 0.123 0.125 0.283
R2 Adj. 0.258 −0.356 −0.487 −0.354
N (any_fed=1) 8.000 8.000 8.000 8.000
N (Sample) 18.000 18.000 18.000 18.000
* p < 0.1, ** p < 0.05, *** p < 0.01

6.4 Consolidated Size Comparison

# ==============================================================================
# CONSOLIDATED: Compare BTFP across size categories
# ==============================================================================

models_size_btfp <- list()
n_rows_size <- data.frame(term = c("N (BTFP=1)", "N (Sample)"))

for (sz in c("Small (<$1B)", "Medium ($1B-$100B)", "Large (>$100B)")) {
  df_sub <- df_acute %>% filter(size_cat == sz) %>% filter(btfp_acute == 1 | non_user == 1)
  n_users <- sum(df_sub$btfp_acute)
  n_total <- nrow(df_sub)
  
  short_nm <- case_when(
    sz == "Small (<$1B)" ~ "Small",
    sz == "Medium ($1B-$100B)" ~ "Medium",
    TRUE ~ "Large"
  )
  
  if (n_users >= 5) {
    f_base <- build_formula("btfp_acute", "mtm_total + uninsured_lev + mtm_x_uninsured")
    f_risk <- build_formula("btfp_acute", "run_risk_2 + run_risk_3 + run_risk_4")
    
    models_size_btfp[[paste0(short_nm, " (Base)")]] <- feols(f_base, data = df_sub, vcov = "hetero")
    models_size_btfp[[paste0(short_nm, " (Risk)")]] <- feols(f_risk, data = df_sub, vcov = "hetero")
    
    n_rows_size[[paste0(short_nm, " (Base)")]] <- c(n_users, n_total)
    n_rows_size[[paste0(short_nm, " (Risk)")]] <- c(n_users, n_total)
  }
}

if (length(models_size_btfp) > 0) {
  # SAVE TABLE 5J
  save_reg_table(models_size_btfp, "Table_5J_BTFP_Size_Consolidated_LPM",
                 title_text = "BTFP Participation by Bank Size: Consolidated (LPM)",
                 notes_text = "This table reports Linear Probability Model (LPM) estimates comparing BTFP participation across bank size categories during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed BTFP. Size categories: Small (total assets less than \\$1 billion), Medium (\\$1 billion--\\$100 billion), Large (exceeding \\$100 billion). Base specification includes continuous MTM Loss, Uninsured Leverage, and their interaction. Risk specification includes Risk 2,3,4 dummies. Each column uses the sample of BTFP users versus pure non-users within that size category, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
                 coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_rows_size)

  modelsummary(
    models_size_btfp,
    stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
    coef_map = COEF_MAP,
    gof_map = gof_lpm,
    add_rows = n_rows_size,
    title = "Table 5J: BTFP Usage by Bank Size - Consolidated (LPM)",
    output = "kableExtra"
  )
}
Table 5J: BTFP Usage by Bank Size - Consolidated (LPM)
Small (Base) Small (Risk) Medium (Base) Medium (Risk) Large (Base) Large (Risk)
MTM Loss (z) 0.021*** 0.032* 0.066
(0.006) (0.019) (0.226)
Uninsured Lev (z) 0.015** 0.032* 0.734***
(0.007) (0.016) (0.167)
MTM ? Uninsured 0.014*** 0.017 −0.069
(0.005) (0.014) (0.124)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.000 0.007 1.324
(0.014) (0.049) (2.423)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.006 0.004 0.247
(0.013) (0.060) (1.228)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.054*** 0.092* 1.595
(0.018) (0.053) (2.798)
Log(Assets) (z) 0.061*** 0.063*** 0.082*** 0.086***
(0.009) (0.009) (0.029) (0.029)
Cash Ratio (z) −0.020*** −0.022*** −0.069*** −0.069*** 0.175 0.359
(0.004) (0.004) (0.023) (0.020) (0.215) (0.525)
Loan/Deposit (z) −0.006 −0.007 −0.023 −0.024 0.008 0.648
(0.006) (0.006) (0.020) (0.019) (0.306) (1.067)
Book Equity (z) −0.009** −0.011*** −0.046** −0.052** −0.541 −0.951
(0.004) (0.004) (0.021) (0.021) (0.346) (1.592)
Wholesale (z) 0.021*** 0.020*** 0.036** 0.036** −0.610 −0.864
(0.007) (0.007) (0.017) (0.016) (0.461) (1.217)
ROA (z) −0.002 −0.002 −0.008 −0.014 0.633* −0.006
(0.005) (0.004) (0.019) (0.018) (0.290) (0.544)
Num.Obs. 3013 3022 709 710 15 15
R2 0.056 0.056 0.078 0.077 0.769 0.128
R2 Adj. 0.053 0.053 0.066 0.065 0.461 −1.034
N (BTFP=1) 280.000 280.000 177.000 177.000 5.000 5.000
N (Sample) 3022.000 3022.000 710.000 710.000 15.000 15.000
* p < 0.1, ** p < 0.05, *** p < 0.01

7 SIZE ANALYSIS: Base LPM and Full Risk Logit (6 Models)

sizes <- c("Small (<$1B)", "Medium ($1B-$100B)", "Large (>$100B)")
short_names <- c("Small", "Medium", "Large")

models_base_lpm <- list()
models_risk_logit <- list()

for (i in seq_along(sizes)) {
  sz <- sizes[i]
  nm <- short_names[i]
  
  df_sub <- df_acute %>% filter(size_cat == sz, btfp_acute == 1 | non_user == 1)
  n_users <- sum(df_sub$btfp_acute == 1, na.rm = TRUE)
  
  if (n_users >= 5) {
    # Base LPM
    f_base <- build_formula("btfp_acute", "mtm_total + uninsured_lev + mtm_x_uninsured")
    models_base_lpm[[paste0(nm, " (LPM)")]] <- feols(f_base, data = df_sub, vcov = "hetero")
    
    # Full Risk Logit
    f_risk <- build_formula("btfp_acute", "run_risk_1 + run_risk_2 + run_risk_3 + run_risk_4")
    models_risk_logit[[paste0(nm, " (Logit)")]] <- feglm(f_risk, data = df_sub, family = binomial(link = "logit"))
  }
}

# Combine all 6 models
models_size_6 <- c(models_base_lpm, models_risk_logit)

# N rows
n_rows_size <- data.frame(term = c("N (Users)", "N (Sample)"))
for (i in seq_along(sizes)) {
  sz <- sizes[i]
  nm <- short_names[i]
  df_sub <- df_acute %>% filter(size_cat == sz, btfp_acute == 1 | non_user == 1)
  n_users <- sum(df_sub$btfp_acute == 1, na.rm = TRUE)
  n_total <- nrow(df_sub)
  n_rows_size[[paste0(nm, " (LPM)")]] <- c(n_users, n_total)
  n_rows_size[[paste0(nm, " (Logit)")]] <- c(n_users, n_total)
}

# Save table
save_reg_table(models_size_6, "Table_Size_BTFP_BaseLPM_RiskLogit",
               title_text = "BTFP Participation by Bank Size: LPM and Logit Specifications",
               notes_text = "This table reports BTFP participation estimates by bank size category during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed BTFP. Columns (1)--(3) report Linear Probability Model (LPM) estimates with continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (4)--(6) report Logit estimates with all four risk category dummies. Size categories: Small (total assets less than \\$1 billion), Medium (\\$1 billion--\\$100 billion), Large (exceeding \\$100 billion). The sample consists of BTFP users versus pure non-users within each size category, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_rows_size)

# Print table
# Print table
modelsummary(
  models_size_6,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = gof_lpm,
  add_rows = n_rows_size,
  title = "Table: BTFP Usage by Bank Size - Base LPM & Full Risk Logit (Acute Period)",
  output = "kableExtra"
)
Table: BTFP Usage by Bank Size - Base LPM & Full Risk Logit (Acute Period)
Small (LPM) Medium (LPM) Large (LPM) Small (Logit) Medium (Logit) Large (Logit)
MTM Loss (z) 0.021*** 0.032* 0.066
(0.006) (0.019) (0.226)
Uninsured Lev (z) 0.015** 0.032* 0.734***
(0.007) (0.016) (0.167)
MTM ? Uninsured 0.014*** 0.017 −0.069
(0.005) (0.014) (0.124)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 9.178 9.626 −7.733
(285.208) (539.228) (13.621)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured 9.377 9.738 −1.388
(285.208) (539.228) (3.146)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 9.344 9.698 −21.654
(285.208) (539.228) (6043.171)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 9.726 10.143
(285.208) (539.228)
Log(Assets) (z) 0.061*** 0.082*** 0.896*** 0.494***
(0.009) (0.029) (0.135) (0.157)
Cash Ratio (z) −0.020*** −0.069*** 0.175 −0.527*** −0.571*** 1.749
(0.004) (0.023) (0.215) (0.115) (0.192) (2.797)
Loan/Deposit (z) −0.006 −0.023 0.008 −0.026 −0.099 3.019
(0.006) (0.020) (0.306) (0.087) (0.130) (5.077)
Book Equity (z) −0.009** −0.046** −0.541 −0.313*** −0.403** −4.463
(0.004) (0.021) (0.346) (0.098) (0.159) (8.187)
Wholesale (z) 0.021*** 0.036** −0.610 0.173*** 0.180** −4.361
(0.007) (0.017) (0.461) (0.056) (0.080) (7.543)
ROA (z) −0.002 −0.008 0.633* −0.033 −0.088 0.001
(0.005) (0.019) (0.290) (0.076) (0.120) (1.928)
Num.Obs. 3013 709 15 3022 710 15
R2 0.056 0.078 0.769 0.106 0.075 0.120
R2 Adj. 0.053 0.066 0.461 0.096 0.050 −0.718
N (Users) 280.000 280.000 177.000 177.000 5.000 5.000
N (Sample) 3022.000 3022.000 710.000 710.000 15.000 15.000
* p < 0.1, ** p < 0.05, *** p < 0.01

8 FHLB REGRESSIONS

8.1 Acute Period

# Create FHLB-specific sample (FHLB users vs pure non-users)
df_fhlb_acute <- df_acute %>%
  filter(fhlb_user == 1 | non_user == 1)

cat("\n=== FHLB ANALYSIS SAMPLE ===\n")
## 
## === FHLB ANALYSIS SAMPLE ===
cat("FHLB Users:", sum(df_fhlb_acute$fhlb_user), "\n")
## FHLB Users: 302
cat("Pure Non-Users:", sum(df_fhlb_acute$non_user), "\n")
## Pure Non-Users: 3285
cat("Total Sample:", nrow(df_fhlb_acute), "\n")
## Total Sample: 3587
# Run 4-specification models
m_fhlb_lpm <- run_4spec_models(df_fhlb_acute, "fhlb_user", "lpm")
n_fhlb <- create_n_rows(df_fhlb_acute, "fhlb_user")

# Save table
save_reg_table(m_fhlb_lpm, "Table_3D_FHLB_Acute_LPM",
               title_text = "FHLB Abnormal Borrowing During Acute Crisis (LPM)",
               notes_text = "This table reports Linear Probability Model (LPM) estimates of abnormal Federal Home Loan Bank (FHLB) borrowing during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank had an abnormal FHLB advance increase (greater than 10\\% of total assets). Column (1) includes continuous MTM Loss, Uninsured Leverage, and their interaction. Columns (2)--(4) use risk category dummies. The sample consists of FHLB abnormal borrowers versus pure non-users (banks with no BTFP, DW, or abnormal FHLB borrowing), excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized at 2.5/97.5 percentiles and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP, gof_map_use = gof_lpm, add_rows_use = n_fhlb)

# Display table
modelsummary(
  m_fhlb_lpm,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP, gof_map = gof_lpm, add_rows = n_fhlb,
  title = "Table: FHLB Usage - Acute Period (Mar 13 - May 1, 2023) - LPM",
  output = "kableExtra"
)
Table: FHLB Usage - Acute Period (Mar 13 - May 1, 2023) - LPM
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.003
(0.006)
Uninsured Lev (z) 0.006
(0.005)
MTM ? Uninsured −0.004
(0.004)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.005 −0.001
(0.011) (0.012)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured 0.010 0.013
(0.012) (0.013)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.006
(0.013)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.016
(0.015)
Log(Assets) (z) 0.025*** 0.028*** 0.028*** 0.026***
(0.006) (0.006) (0.006) (0.006)
Cash Ratio (z) −0.021*** −0.021*** −0.023*** −0.023***
(0.004) (0.003) (0.004) (0.004)
Loan/Deposit (z) 0.024*** 0.023*** 0.023*** 0.024***
(0.005) (0.005) (0.005) (0.005)
Book Equity (z) 0.010** 0.008** 0.008** 0.009**
(0.004) (0.004) (0.004) (0.004)
Wholesale (z) 0.002 0.001 0.001 0.001
(0.005) (0.005) (0.005) (0.005)
ROA (z) −0.009** −0.009** −0.009** −0.010**
(0.004) (0.004) (0.004) (0.004)
Num.Obs. 3577 3587 3587 3587
R2 0.039 0.039 0.039 0.039
R2 Adj. 0.037 0.037 0.037 0.037
N (fhlb_user=1) 302.000 302.000 302.000 302.000
N (Sample) 3587.000 3587.000 3587.000 3587.000
* p < 0.1, ** p < 0.05, *** p < 0.01

8.2 FHLB Regression by Period

# ==============================================================================
# FHLB ANALYSIS BY QUARTER
# ==============================================================================

# Prepare FHLB data
df_fhlb <- call_q %>%
  filter(!idrssd %in% excluded_banks,
         period %in% c("2022Q3", "2022Q4", "2023Q1", "2023Q2", "2023Q3")) %>%
  select(
    idrssd, period,
    fhlb_adv, fhlb_to_total_asset,
    abnormal_fhlb_borrowing_10pct, abnormal_fhlb_borrowing_5pct,
    total_asset, mtm_loss_to_total_asset, uninsured_deposit_to_total_asset,
    cash_to_total_asset, book_equity_to_total_asset, loan_to_deposit,
    fed_fund_purchase, repo, other_borrowed_less_than_1yr, total_liability, roa
  ) %>%
  filter(!is.na(abnormal_fhlb_borrowing_10pct)) %>%
  mutate(
    fhlb_user = abnormal_fhlb_borrowing_10pct,
    # Create controls (winsorize then standardize within sample)
    mtm_total_raw = mtm_loss_to_total_asset,
    uninsured_lev_raw = uninsured_deposit_to_total_asset,
    ln_assets_raw = log(total_asset),
    cash_ratio_raw = cash_to_total_asset,
    book_equity_ratio_raw = book_equity_to_total_asset,
    loan_to_deposit_raw = loan_to_deposit,
    wholesale_raw = safe_div(
      replace_na(fed_fund_purchase, 0) + replace_na(repo, 0) + 
      replace_na(other_borrowed_less_than_1yr, 0),
      total_liability, 0) * 100,
    roa_raw = roa
  )

# Summary by period
fhlb_summary <- df_fhlb %>%
  group_by(period) %>%
  summarise(
    N = n(),
    `% Abnormal (10%)` = round(mean(fhlb_user, na.rm = TRUE) * 100, 1),
    `Mean FHLB/Assets (%)` = round(mean(fhlb_to_total_asset, na.rm = TRUE), 2),
    .groups = "drop"
  )

cat("\n=== FHLB USAGE BY PERIOD ===\n")
## 
## === FHLB USAGE BY PERIOD ===
print(fhlb_summary)
## # A tibble: 5 × 4
##   period     N `% Abnormal (10%)` `Mean FHLB/Assets (%)`
##   <chr>  <int>              <dbl>                  <dbl>
## 1 2022Q3  4724                9.5                   2.16
## 2 2022Q4  4696                6.9                   2.72
## 3 2023Q1  4673                7.5                   2.87
## 4 2023Q2  4644                5.1                   3.22
## 5 2023Q3  4604                4.6                   3.23
save_table(fhlb_summary, "Table_FHLB_Summary_by_Quarter", "FHLB Abnormal Borrowing by Quarter")
# ==============================================================================
# FHLB REGRESSIONS BY PERIOD
# ==============================================================================

# Function to run FHLB regression for a given period
run_fhlb_reg <- function(data, period_val) {
  df <- data %>%
    filter(period == period_val) %>%
    # Winsorize and standardize WITHIN period
    mutate(
      mtm_total = standardize_z(winsorize(mtm_total_raw)),
      uninsured_lev = standardize_z(winsorize(uninsured_lev_raw)),
      ln_assets = standardize_z(winsorize(ln_assets_raw)),
      cash_ratio = standardize_z(winsorize(cash_ratio_raw)),
      book_equity_ratio = standardize_z(winsorize(book_equity_ratio_raw)),
      loan_to_deposit = standardize_z(winsorize(loan_to_deposit_raw)),
      wholesale = standardize_z(winsorize(wholesale_raw)),
      roa = standardize_z(winsorize(roa_raw))
    ) %>%
    # Keep only complete cases
    filter(!is.na(fhlb_user), !is.na(mtm_total), !is.na(uninsured_lev),
           !is.na(ln_assets), !is.na(cash_ratio), !is.na(book_equity_ratio),
           !is.na(loan_to_deposit), !is.na(wholesale), !is.na(roa))
  
  if (nrow(df) < 50) {
    cat("Warning: Period", period_val, "has only", nrow(df), "obs\n")
    return(NULL)
  }
  
  # Same controls as extensive margin
  m <- feols(fhlb_user ~ mtm_total + uninsured_lev + ln_assets + cash_ratio + 
             book_equity_ratio + loan_to_deposit + wholesale + roa, 
             data = df, vcov = "hetero")
  return(m)
}

# Run for each period
periods_fhlb <- c("2022Q4", "2023Q1", "2023Q2", "2023Q3")
models_fhlb <- list()

for (p in periods_fhlb) {
  m <- run_fhlb_reg(df_fhlb, p)
  if (!is.null(m)) {
    models_fhlb[[paste0("FHLB: ", p)]] <- m
  }
}

# Display
if (length(models_fhlb) > 0) {
  save_reg_table(models_fhlb, "Table_FHLB_Temporal",
                 title_text = "FHLB Abnormal Borrowing Determinants by Quarter",
                 notes_text = "This table reports Linear Probability Model (LPM) estimates of abnormal FHLB borrowing determinants by quarter. The dependent variable equals one if the bank had an abnormal FHLB advance increase (greater than 10\\% of total assets). Each column uses data from a single quarter with variables winsorized and standardized within that quarter. The sample includes all commercial banks excluding G-SIBs and failed institutions. Bank characteristics are contemporaneous with each quarter. All continuous variables are winsorized at 2.5/97.5 percentiles and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
                 coef_map_use = COEF_MAP, gof_map_use = gof_lpm)

  modelsummary(
    models_fhlb,
    stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
    coef_map = COEF_MAP,
    gof_map = gof_lpm,
    title = "Table: FHLB Abnormal Borrowing Determinants by Quarter",
    notes = list(
      "DV: Abnormal FHLB indicator (10% threshold).",
      "LPM with heteroskedasticity-robust SE."
    ),
    output = "kableExtra"
  )
}
Table: FHLB Abnormal Borrowing Determinants by Quarter
&nbsp;FHLB: 2022Q4 &nbsp;FHLB: 2023Q1 &nbsp;FHLB: 2023Q2 &nbsp;FHLB: 2023Q3
MTM Loss (z) 0.004 0.007 0.006 0.003
(0.004) (0.005) (0.004) (0.004)
Uninsured Lev (z) 0.004 0.006 −0.001 0.001
(0.004) (0.004) (0.003) (0.003)
Log(Assets) (z) 0.014*** −0.003 −0.009*** 0.001
(0.005) (0.004) (0.003) (0.003)
Cash Ratio (z) −0.014*** −0.023*** −0.016*** −0.014***
(0.003) (0.003) (0.003) (0.003)
Loan/Deposit (z) 0.024*** 0.023*** 0.015*** 0.013***
(0.004) (0.004) (0.003) (0.003)
Book Equity (z) 0.010*** −0.001 0.000 −0.001
(0.003) (0.003) (0.003) (0.003)
Wholesale (z) −0.002 0.004 0.006* −0.000
(0.004) (0.004) (0.004) (0.003)
ROA (z) −0.008** −0.002 −0.000 −0.001
(0.003) (0.003) (0.003) (0.003)
Num.Obs. 4678 4652 4621 4575
R2 0.024 0.023 0.015 0.012
R2 Adj. 0.023 0.021 0.014 0.011
* p < 0.1, ** p < 0.05, *** p < 0.01
DV: Abnormal FHLB indicator (10% threshold).
LPM with heteroskedasticity-robust SE.

9 MULTINOMIAL CHOICE MODEL

# ==============================================================================
# MULTINOMIAL FACILITY CHOICE
# ==============================================================================

df_choice <- df_acute %>%
  mutate(
    facility_choice = case_when(
      btfp_acute == 1 & dw_acute == 0 & fhlb_user == 0 ~ "BTFP_Only",
      btfp_acute == 0 & dw_acute == 1 & fhlb_user == 0 ~ "DW_Only",
      btfp_acute == 0 & dw_acute == 0 & fhlb_user == 1 ~ "FHLB_Only",
      (btfp_acute + dw_acute + fhlb_user) >= 2 ~ "Multiple",
      TRUE ~ "None"
    ),
    facility_choice = factor(facility_choice, 
                             levels = c("None", "DW_Only", "BTFP_Only", "FHLB_Only", "Multiple"))
  )

cat("\n=== FACILITY CHOICE DISTRIBUTION ===\n")
## 
## === FACILITY CHOICE DISTRIBUTION ===
print(table(df_choice$facility_choice))
## 
##      None   DW_Only BTFP_Only FHLB_Only  Multiple 
##      3285       277       343       246       141
# Base specification (same controls)
mlogit_base <- multinom(
  facility_choice ~ mtm_total + uninsured_lev + mtm_x_uninsured + 
                    ln_assets + cash_ratio + book_equity_ratio + 
                    loan_to_deposit + wholesale + roa,
  data = df_choice, trace = FALSE, maxit = 500
)

# Risk categories
mlogit_risk <- multinom(
  facility_choice ~ run_risk_2 + run_risk_3 + run_risk_4 + 
                    ln_assets + cash_ratio + book_equity_ratio + 
                    loan_to_deposit + wholesale + roa,
  data = df_choice, trace = FALSE, maxit = 500
)

cat("\n=== MULTINOMIAL LOGIT: BASE SPECIFICATION ===\n")
## 
## === MULTINOMIAL LOGIT: BASE SPECIFICATION ===
print(summary(mlogit_base))
## Call:
## multinom(formula = facility_choice ~ mtm_total + uninsured_lev + 
##     mtm_x_uninsured + ln_assets + cash_ratio + book_equity_ratio + 
##     loan_to_deposit + wholesale + roa, data = df_choice, trace = FALSE, 
##     maxit = 500)
## 
## Coefficients:
##           (Intercept) mtm_total uninsured_lev mtm_x_uninsured ln_assets
## DW_Only        -2.687   0.21326       0.06667         0.05108    0.7090
## BTFP_Only      -2.567   0.23774       0.10090         0.02358    0.4856
## FHLB_Only      -2.868   0.01245       0.06211        -0.11767    0.1294
## Multiple       -3.741   0.17050       0.41406         0.10733    0.8985
##           cash_ratio book_equity_ratio loan_to_deposit wholesale      roa
## DW_Only     -0.03707          -0.16513         0.11331   0.14392  0.09224
## BTFP_Only   -0.53425          -0.37296         0.02594   0.14954  0.03558
## FHLB_Only   -0.67899           0.07789         0.42678  -0.01835 -0.08354
## Multiple    -0.29240          -0.18294         0.07196   0.24556 -0.15211
## 
## Std. Errors:
##           (Intercept) mtm_total uninsured_lev mtm_x_uninsured ln_assets
## DW_Only       0.07670   0.07776       0.07285         0.06369   0.07261
## BTFP_Only     0.07820   0.07179       0.07183         0.06373   0.07116
## FHLB_Only     0.08906   0.08022       0.08510         0.07082   0.08525
## Multiple      0.13391   0.11949       0.09903         0.08983   0.10066
##           cash_ratio book_equity_ratio loan_to_deposit wholesale     roa
## DW_Only      0.09369           0.09130         0.08560   0.05777 0.07379
## BTFP_Only    0.11402           0.09286         0.07878   0.05144 0.07058
## FHLB_Only    0.13715           0.08110         0.09001   0.07174 0.07327
## Multiple     0.15516           0.14252         0.12394   0.07295 0.11050
## 
## Residual Deviance: 6700 
## AIC: 6780
# Save model
saveRDS(list(base = mlogit_base, risk = mlogit_risk),
        file.path(TABLE_PATH, "multinomial_models.rds"))
# ==============================================================================
# MULTINOMIAL COEFFICIENT PLOT
# ==============================================================================

# Extract coefficients
mlogit_coefs <- tidy(mlogit_base, conf.int = TRUE) %>%
  filter(term %in% c("mtm_total", "uninsured_lev")) %>%
  mutate(
    term_label = case_when(
      term == "mtm_total" ~ "MTM Loss (z)",
      term == "uninsured_lev" ~ "Uninsured Leverage (z)"
    ),
    y.level = factor(y.level, levels = c("FHLB_Only", "DW_Only", "BTFP_Only", "Multiple"))
  )

p_mlogit <- ggplot(mlogit_coefs, aes(x = estimate, y = y.level, color = term_label)) +
  geom_vline(xintercept = 0, linetype = "dashed") +
  geom_point(position = position_dodge(width = 0.5), size = 3) +
  geom_errorbarh(aes(xmin = conf.low, xmax = conf.high), 
                 position = position_dodge(width = 0.5), height = 0.2) +
  facet_wrap(~term_label, scales = "free_x") +
  scale_color_brewer(palette = "Dark2") +
  labs(
    title = "Figure: Drivers of Facility Choice (Multinomial Logit)",
    subtitle = "Reference category: None",
    x = "Coefficient (Log Odds)", y = "Facility Choice"
  ) +
  theme_bw(base_size = 12) +
  theme(legend.position = "none", strip.text = element_text(face = "bold"))

print(p_mlogit)

save_figure(p_mlogit, "Figure_Multinomial_Coefficients", width = 12, height = 8)

10 INTENSIVE MARGIN ANALYSIS

# ==============================================================================
# INTENSIVE MARGIN: DRIVERS OF BORROWING AMOUNT
# ==============================================================================

df_intensive <- df_acute %>%
  filter(btfp_acute == 1) %>%
  mutate(
    # DV: Borrowing as % of assets
    btfp_pct = winsorize((btfp_acute_amt / (total_asset * 1000)) * 100),
    
    # Par benefit: loss rate on eligible collateral
    par_benefit_raw = mtm_loss_omo_eligible_to_omo_eligible,
    par_benefit = standardize_z(winsorize(par_benefit_raw)),
    
    # Capacity: eligible collateral / assets
    capacity_raw = omo_eligible_to_total_asset,
    capacity = standardize_z(winsorize(capacity_raw)),
    
    # Liquidity need: outflow
    outflow_z = ifelse(is.na(uninsured_outflow), 0, uninsured_outflow)
  )

cat("\n=== INTENSIVE MARGIN SAMPLE ===\n")
## 
## === INTENSIVE MARGIN SAMPLE ===
cat("BTFP Borrowers:", nrow(df_intensive), "\n")
## BTFP Borrowers: 462
cat("Mean BTFP/Assets:", round(mean(df_intensive$btfp_pct, na.rm = TRUE), 3), "%\n")
## Mean BTFP/Assets: 5.408 %
# IPW Setup
df_ipw <- df_acute %>%
  filter(!is.na(mtm_loss_omo_eligible_to_omo_eligible)) %>%
  mutate(
    par_benefit_w = winsorize(mtm_loss_omo_eligible_to_omo_eligible),
    capacity_w = winsorize(omo_eligible_to_total_asset)
  )

m_ps <- glm(btfp_acute ~ par_benefit_w + capacity_w + uninsured_lev_w + 
            ln_assets_w + cash_ratio_w + wholesale_w,
            data = df_ipw, family = binomial)

df_ipw$ps <- predict(m_ps, type = "response")

df_intensive <- df_intensive %>%
  left_join(df_ipw %>% select(idrssd, ps), by = "idrssd") %>%
  mutate(
    ipw = 1 / ps,
    ipw_trim = winsorize(ipw, probs = c(0.05, 0.95))
  )
# ==============================================================================
# INTENSIVE MARGIN REGRESSIONS
# ==============================================================================

# Model 1: OLS (same control structure)
m_int_1 <- feols(btfp_pct ~ par_benefit + capacity + outflow_z + 
                 ln_assets + cash_ratio + book_equity_ratio + 
                 loan_to_deposit + wholesale + roa, 
                 data = df_intensive, vcov = "hetero")

# Model 2: IPW Corrected
m_int_2 <- feols(btfp_pct ~ par_benefit + capacity + outflow_z + 
                 ln_assets + cash_ratio + book_equity_ratio + 
                 loan_to_deposit + wholesale + roa, 
                 data = df_intensive, weights = ~ipw_trim, vcov = "hetero")

# Model 3: Replace outflow with uninsured_lev
m_int_3 <- feols(btfp_pct ~ par_benefit + capacity + uninsured_lev + 
                 ln_assets + cash_ratio + book_equity_ratio + 
                 loan_to_deposit + wholesale + roa, 
                 data = df_intensive, weights = ~ipw_trim, vcov = "hetero")

# Model 4: + Risk Categories
m_int_4 <- feols(btfp_pct ~ par_benefit + capacity + run_risk_2 + run_risk_3 + run_risk_4 + 
                 ln_assets + cash_ratio + book_equity_ratio + 
                 loan_to_deposit + wholesale + roa, 
                 data = df_intensive, weights = ~ipw_trim, vcov = "hetero")

models_int <- list(
  "(1) OLS" = m_int_1,
  "(2) IPW" = m_int_2,
  "(3) +UninsLev" = m_int_3,
  "(4) +Risk" = m_int_4
)

COEF_MAP_INT <- c(
  "par_benefit" = "Par Benefit (z)",
  "capacity" = "Collateral Capacity (z)",
  "outflow_z" = "Deposit Outflow (z)",
  COEF_MAP
)

modelsummary(
  models_int,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP_INT,
  gof_map = c("nobs", "r.squared"),
  title = "Table: Intensive Margin - Drivers of BTFP Borrowing Amount",
  notes = list(
    "DV: BTFP Borrowing / Assets (%). Sample: BTFP Borrowers only.",
    "Par Benefit = MTM Loss / Eligible Collateral. IPW corrects for selection.",
    "Heteroskedasticity-robust SE."
  ),
  output = "kableExtra"
)
Table: Intensive Margin - Drivers of BTFP Borrowing Amount
&nbsp;(1) OLS &nbsp;(2) IPW &nbsp;(3) +UninsLev &nbsp;(4) +Risk
Par Benefit (z) −0.087 −0.000 0.006 0.050
(0.301) (0.327) (0.333) (0.336)
Collateral Capacity (z) 0.611* 0.703 0.704* 0.655
(0.333) (0.426) (0.421) (0.436)
Deposit Outflow (z) −0.171 0.095
(0.362) (0.404)
Uninsured Lev (z) 0.067
(0.454)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured 1.179
(1.139)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.606
(0.850)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.261
(0.919)
Log(Assets) (z) 0.080 0.198 0.166 0.164
(0.315) (0.385) (0.465) (0.427)
Cash Ratio (z) 0.437 0.493 0.466 0.394
(0.696) (0.776) (0.766) (0.809)
Loan/Deposit (z) −0.520 −0.816 −0.805 −0.920
(0.458) (0.548) (0.558) (0.563)
Book Equity (z) 0.241 0.500 0.524 0.545
(0.459) (0.551) (0.548) (0.541)
Wholesale (z) 0.426* 0.327 0.336 0.373
(0.237) (0.309) (0.306) (0.303)
ROA (z) −0.552* −0.553 −0.547 −0.566
(0.315) (0.339) (0.338) (0.347)
Num.Obs. 462 462 462 462
R2 0.049 0.066 0.066 0.071
* p < 0.1, ** p < 0.05, *** p < 0.01
DV: BTFP Borrowing / Assets (%). Sample: BTFP Borrowers only.
Par Benefit = MTM Loss / Eligible Collateral. IPW corrects for selection.
Heteroskedasticity-robust SE.
save_reg_table(models_int, "Table_Intensive_Margin",
               title_text = "Intensive Margin: Determinants of BTFP Borrowing Amount",
               notes_text = "This table reports OLS and Inverse Probability Weighted (IPW) estimates of BTFP borrowing intensity among banks that accessed BTFP during the acute crisis period (March 13--May 1, 2023). The dependent variable is BTFP borrowing as a percentage of total assets. Par Benefit measures the implicit subsidy from par-value lending, calculated as MTM loss on OMO-eligible securities divided by OMO-eligible securities. Collateral Capacity measures OMO-eligible securities as a share of total assets. Deposit Outflow measures the 2023Q1 quarterly change in uninsured deposits (positive indicates outflow). Column (1) reports OLS. Columns (2)--(4) use IPW weights to correct for selection into BTFP borrowing. The sample consists of BTFP borrowers only, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP_INT, gof_map_use = c("nobs", "r.squared"))

11 DEPOSIT OUTFLOWS AND BORROWING

11.1 Did Outflows Predict Emergency Borrowing?

# ==============================================================================
# DID DEPOSIT OUTFLOWS PREDICT BORROWING?
# ==============================================================================

# Sample: BTFP users vs pure non-users
df_btfp_out <- df_acute %>%
  filter(btfp_acute == 1 | non_user == 1) %>%
  filter(!is.na(uninsured_outflow))

# Model 1: Base (no outflow)
m_out_1 <- feols(build_formula("btfp_acute", "mtm_total + uninsured_lev + mtm_x_uninsured"),
                 data = df_btfp_out, vcov = "hetero")

# Model 2: + Uninsured Outflow
m_out_2 <- feols(build_formula("btfp_acute", "mtm_total + uninsured_lev + mtm_x_uninsured + uninsured_outflow"),
                 data = df_btfp_out, vcov = "hetero")

# Model 3: + Both Outflows
m_out_3 <- feols(build_formula("btfp_acute", "mtm_total + uninsured_lev + mtm_x_uninsured + uninsured_outflow + insured_outflow"),
                 data = df_btfp_out, vcov = "hetero")

# Model 4: Risk Categories + Outflows
m_out_4 <- feols(build_formula("btfp_acute", "run_risk_2 + run_risk_3 + run_risk_4 + uninsured_outflow + insured_outflow"),
                 data = df_btfp_out, vcov = "hetero")

models_outflow <- list(
  "(1) Base" = m_out_1,
  "(2) +Unins Out" = m_out_2,
  "(3) +Both Out" = m_out_3,
  "(4) Risk+Out" = m_out_4
)

COEF_MAP_OUT <- c(
  COEF_MAP,
  "uninsured_outflow" = "Uninsured Deposit Outflow (z)",
  "insured_outflow" = "Insured Deposit Outflow (z)"
)

modelsummary(
  models_outflow,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP_OUT,
  gof_map = gof_lpm,
  title = "Table: BTFP Usage - Did Deposit Outflows Predict Borrowing?",
  notes = list(
    "DV: BTFP borrower (1/0). Outflows: Q1 2023 change, positive = loss.",
    "LPM with heteroskedasticity-robust SE."
  ),
  output = "kableExtra"
)
Table: BTFP Usage - Did Deposit Outflows Predict Borrowing?
&nbsp;(1) Base &nbsp;(2) +Unins Out &nbsp;(3) +Both Out &nbsp;(4) Risk+Out
MTM Loss (z) 0.025*** 0.025*** 0.024***
(0.006) (0.006) (0.006)
Uninsured Lev (z) 0.021*** 0.019*** 0.021***
(0.007) (0.007) (0.007)
MTM ? Uninsured 0.017*** 0.018*** 0.018***
(0.005) (0.005) (0.005)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.007
(0.014)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.000
(0.013)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.061***
(0.017)
Log(Assets) (z) 0.069*** 0.068*** 0.068*** 0.072***
(0.008) (0.008) (0.008) (0.007)
Cash Ratio (z) −0.025*** −0.025*** −0.026*** −0.028***
(0.005) (0.005) (0.005) (0.005)
Loan/Deposit (z) −0.007 −0.007 −0.004 −0.007
(0.006) (0.006) (0.006) (0.006)
Book Equity (z) −0.013*** −0.012** −0.012** −0.014***
(0.005) (0.005) (0.005) (0.005)
Wholesale (z) 0.026*** 0.027*** 0.027*** 0.026***
(0.006) (0.006) (0.006) (0.006)
ROA (z) −0.006 −0.006 −0.007 −0.008
(0.005) (0.005) (0.005) (0.005)
Uninsured Deposit Outflow (z) −0.016*** −0.017*** −0.017***
(0.005) (0.005) (0.005)
Insured Deposit Outflow (z) −0.008 −0.007
(0.007) (0.006)
Num.Obs. 3674 3674 3674 3683
R2 0.093 0.095 0.095 0.094
R2 Adj. 0.090 0.093 0.093 0.092
* p < 0.1, ** p < 0.05, *** p < 0.01
DV: BTFP borrower (1/0). Outflows: Q1 2023 change, positive = loss.
LPM with heteroskedasticity-robust SE.
save_reg_table(models_outflow, "Table_BTFP_Outflows",
               title_text = "BTFP Participation with Deposit Outflow Controls",
               notes_text = "This table reports Linear Probability Model (LPM) estimates of BTFP participation with deposit outflow controls during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed BTFP. Uninsured Deposit Outflow and Insured Deposit Outflow measure the 2023Q1 quarterly change in deposits (positive values indicate outflows/losses). Column (1) reports the base specification without outflow controls. Column (2) adds Uninsured Outflow. Column (3) adds both Uninsured and Insured Outflows. Column (4) uses risk category dummies with outflow controls. The sample consists of BTFP users versus pure non-users with valid outflow data, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP_OUT, gof_map_use = gof_lpm)

12 DIFFERENCE-IN-DIFFERENCES ANALYSIS

DIFFERENCE-IN-DIFFERENCES ANALYSIS

12.1 DiD Design

Treatment: Banks with OMO-eligible securities (can access BTFP) Control: Banks without OMO-eligible securities (cannot access BTFP) Event: BTFP announcement (March 12, 2023) Outcome: Deposit stability (quarterly change in deposits)

# ==============================================================================
# DiD PANEL DATA (INCLUDE BOTH OMO AND NON-OMO BANKS)
# ==============================================================================

df_did_panel <- call_q %>%
  filter(!idrssd %in% excluded_banks,
         period %in% c("2022Q1", "2022Q2", "2022Q3", "2022Q4", 
                       "2023Q1", "2023Q2", "2023Q3")) %>%
  select(
    idrssd, period,
    omo_eligible,
    change_total_deposit_fwd_q, change_uninsured_fwd_q,
    total_asset, uninsured_deposit, total_deposit, total_liability, total_equity,
    cash_to_total_asset, total_loan, roa,
    mtm_loss_to_total_asset, 
    fed_fund_purchase, repo, other_borrowed_less_than_1yr,
    book_equity_to_total_asset, loan_to_deposit
  ) %>%
  # Create variables BEFORE filtering
  mutate(
    has_omo = as.integer(!is.na(omo_eligible) & omo_eligible > 0),
    post_btfp = as.integer(period >= "2023Q1"),
    did_term = has_omo * post_btfp,
    
    # Raw controls
    mtm_total_raw = mtm_loss_to_total_asset,
    uninsured_lev_raw = safe_div(uninsured_deposit, total_asset),
    ln_assets_raw = log(total_asset),
    cash_ratio_raw = cash_to_total_asset,
    book_equity_ratio_raw = book_equity_to_total_asset,
    loan_to_deposit_raw = loan_to_deposit,
    wholesale_raw = safe_div(
      replace_na(fed_fund_purchase, 0) + replace_na(repo, 0) + 
      replace_na(other_borrowed_less_than_1yr, 0),
      total_liability, 0
    ) * 100,
    roa_raw = roa,
    
    # Outcome
    outflow_total_dep = -1 * lag(change_total_deposit_fwd_q),
    outflow_uninsured = -1 * lag(change_uninsured_fwd_q)
  ) %>%
  ungroup() %>%
  # Filter valid observations
  filter(!is.na(ln_assets_raw), is.finite(ln_assets_raw),
         is.finite(outflow_total_dep), is.finite(outflow_uninsured)) %>%
  # Winsorize and standardize
  mutate(
    mtm_total = standardize_z(winsorize(mtm_total_raw)),
    uninsured_lev = standardize_z(winsorize(uninsured_lev_raw)),
    ln_assets = standardize_z(winsorize(ln_assets_raw)),
    cash_ratio = standardize_z(winsorize(cash_ratio_raw)),
    book_equity_ratio = standardize_z(winsorize(book_equity_ratio_raw)),
    loan_to_deposit = standardize_z(winsorize(loan_to_deposit_raw)),
    wholesale = standardize_z(winsorize(wholesale_raw)),
    roa = standardize_z(winsorize(roa_raw))
  )

cat("\n=== DiD PANEL SUMMARY ===\n")
## 
## === DiD PANEL SUMMARY ===
cat("Total Obs:", nrow(df_did_panel), "\n")
## Total Obs: 32284
cat("Treated (OMO):", n_distinct(df_did_panel$idrssd[df_did_panel$has_omo == 1]), "banks\n")
## Treated (OMO): 4460 banks
cat("Control (No OMO):", n_distinct(df_did_panel$idrssd[df_did_panel$has_omo == 0]), "banks\n")
## Control (No OMO): 576 banks

12.2 DiD Regressions

# ==============================================================================
# 2.2 DiD Regressions
# ==============================================================================
# DV: Outflow (positive = deposit loss/runoff, negative = growth)
# Interpretation: Negative coefficient on did_term = BTFP REDUCED outflows (helped)

# --- Define Formula Components ---

# Standard Controls (Time-varying controls for Fixed Effects model)
CONTROLS_DID <- "mtm_total + uninsured_lev + ln_assets + cash_ratio + book_equity_ratio + loan_to_deposit + wholesale + roa"

# Explanatory Variables for different specifications
# Model 1: Basic DiD Term only
EXPL_DID_1 <- "did_term"

# Model 2: DiD + Key Risk Measures (MTM & Uninsured)
EXPL_DID_2 <- "did_term + mtm_total + uninsured_lev"

# --- Construct Formulas ---
# Note: | idrssd + period adds Two-Way Fixed Effects
f_did_1     <- as.formula(paste("outflow_total_dep ~", EXPL_DID_1, "| idrssd + period"))
f_did_2     <- as.formula(paste("outflow_total_dep ~", EXPL_DID_2, "| idrssd + period"))
f_did_3     <- as.formula(paste("outflow_total_dep ~ did_term +", CONTROLS_DID, "| idrssd + period"))
f_did_unins <- as.formula(paste("outflow_uninsured ~ did_term +", CONTROLS_DID, "| idrssd + period"))

# --- Run Regressions ---

# Model 1: Basic DiD
m_did_1 <- feols(f_did_1, data = df_did_panel, cluster = "idrssd")

# Model 2: + Risk Controls
m_did_2 <- feols(f_did_2, data = df_did_panel, cluster = "idrssd")

# Model 3: Full Controls (Total Deposits)
m_did_3 <- feols(f_did_3, data = df_did_panel, cluster = "idrssd")

# Model 4: Full Controls (Uninsured Deposits)
m_did_4 <- feols(f_did_unins, data = df_did_panel, cluster = "idrssd")

# --- Output Table ---

models_did <- list(
  "(1) Basic"     = m_did_1,
  "(2) +Risk"     = m_did_2,
  "(3) +Controls" = m_did_3,
  "(4) Uninsured" = m_did_4
)

COEF_MAP_DID <- c(
  "did_term" = "DiD: OMO $\\times$ Post-BTFP",
  COEF_MAP
)

# Display
modelsummary(
  models_did,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP_DID,
  gof_map = c("nobs", "r.squared"),
  title = "Table: DiD - Did BTFP Stem Deposit Runs?",
  notes = list(
    "DV: Quarterly deposit outflow (%). Positive = runoff.",
    "Interpretation: Negative coef on DiD = BTFP reduced outflows.",
    "Treated: Banks with OMO collateral. Control: Banks without.",
    "FE: Bank + Quarter. SE clustered by bank."
  ),
  output = "kableExtra"
)
Table: DiD - Did BTFP Stem Deposit Runs?
&nbsp;(1) Basic &nbsp;(2) +Risk &nbsp;(3) +Controls &nbsp;(4) Uninsured
DiD: OMO \(\times\) Post-BTFP −1.086 0.255 0.087 18.020
(1.570) (0.674) (0.686) (12.946)
MTM Loss (z) 13.847*** 6.129** 48.449
(0.875) (2.910) (44.441)
Uninsured Lev (z) −4.602*** −3.216*** −42.103***
(0.751) (0.507) (9.353)
Log(Assets) (z) −22.280** 315.436
(10.635) (356.713)
Cash Ratio (z) 0.638 −42.515
(0.819) (44.434)
Loan/Deposit (z) 5.608*** −39.621
(1.835) (54.716)
Book Equity (z) 0.625* −17.453
(0.335) (20.112)
Wholesale (z) 0.151 3.572
(0.175) (3.909)
ROA (z) 0.258* 0.599
(0.147) (1.320)
Num.Obs. 32284 32194 32194 32194
R2 0.232 0.270 0.295 0.146
* p < 0.1, ** p < 0.05, *** p < 0.01
DV: Quarterly deposit outflow (%). Positive = runoff.
Interpretation: Negative coef on DiD = BTFP reduced outflows.
Treated: Banks with OMO collateral. Control: Banks without.
FE: Bank + Quarter. SE clustered by bank.
# Save
save_reg_table(models_did, "Table_DiD_Deposit_Stability",
               title_text = "Difference-in-Differences: BTFP Effect on Deposit Stability",
               notes_text = "This table reports difference-in-differences (DiD) estimates of BTFP's effect on deposit stability. The dependent variable is quarterly deposit outflow as a percentage of lagged deposits (positive values indicate deposit losses). Treatment: banks with OMO-eligible securities (BTFP-eligible). Control: banks without OMO-eligible securities (not BTFP-eligible). Event: BTFP announcement (March 12, 2023). A negative coefficient on the DiD term indicates BTFP reduced deposit outflows (stabilization effect). Column (1) reports the basic DiD. Column (2) adds risk controls. Column (3) adds full bank controls. Column (4) uses uninsured deposit outflow as the outcome. The sample is a quarterly panel from 2022Q1--2023Q3, excluding G-SIBs and failed institutions. All models include bank and quarter fixed effects. Standard errors are clustered by bank.",
               coef_map_use = COEF_MAP_DID, gof_map_use = c("nobs", "r.squared"))
# ==============================================================================
# STEP 3: EVENT STUDY (Parallel Trends Validation)
# ==============================================================================

m_event_study <- feols(outflow_total_dep ~ i(period, has_omo, ref = "2022Q4") + 
                       ln_assets + cash_ratio + loan_to_deposit | idrssd + period,
                       data = df_did_panel, cluster = ~idrssd)

# Generate the plot to "see for yourself"
iplot(m_event_study, main = "Event Study: BTFP-Eligible vs. Non-Eligible Trends",
      xlab = "Quarter", ylab = "Effect on Deposit Outflow")

# Save the event study table to show the 'flat' pre-trends (2022Q1-Q3)
save_reg_table(list("Event Study" = m_event_study), "Table_DiD_Parallel_Trends",
               title_text = "Event Study: Testing Parallel Trends in Deposit Outflows",
               notes_text = "This table reports the dynamic effect of having OMO-eligible securities. Pre-2023 coefficients should be near zero to satisfy parallel trends.",
               gof_map_use = c("nobs", "r.squared"))


etable(m_event_study)
##                               m_event_study
## Dependent Var.:           outflow_total_dep
##                                            
## has_omo x period = 2022Q1   0.4766 (0.8072)
## has_omo x period = 2022Q2     4.417 (3.568)
## has_omo x period = 2022Q3     4.599 (2.959)
## has_omo x period = 2023Q1    0.9664 (1.397)
## has_omo x period = 2023Q2    0.9666 (1.571)
## has_omo x period = 2023Q3   -0.1781 (1.078)
## ln_assets                   -20.75* (9.435)
## cash_ratio                  -0.7880 (1.335)
## loan_to_deposit             7.276** (2.244)
## Fixed-Effects:            -----------------
## idrssd                                  Yes
## period                                  Yes
## _________________________ _________________
## S.E.: Clustered                  by: idrssd
## Observations                         32,284
## R2                                  0.24855
## Within R2                           0.02217
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
m_did_continuous <- feols(outflow_total_dep ~ i(period, mtm_total, ref = "2022Q4") + 
                          ln_assets + cash_ratio + loan_to_deposit | idrssd + period,
                          data = df_did_panel %>% filter(has_omo == 1), 
                          cluster = ~idrssd)

iplot(m_did_continuous)

# ==============================================================================
# FIXED DiD DESIGN: CONTINUOUS TREATMENT (INTENSIVE MARGIN)
# ==============================================================================

# ==============================================================================
# STEP 1: PREPARE INTENSIVE MARGIN PANEL (OMO BANKS ONLY)
# ==============================================================================

# Create a reference for MTM losses at 2022Q4 (The Pre-Crisis Shock Level)
df_shock_ref <- df_did_panel %>%
  filter(period == "2022Q4") %>%
  select(idrssd, mtm_treatment = mtm_total)

# Merge back to panel and restrict to BTFP-eligible banks
df_did_fixed <- df_did_panel %>%
  filter(has_omo == 1) %>%
  left_join(df_shock_ref, by = "idrssd") %>%
  # Filter for banks that actually have the treatment value
  filter(!is.na(mtm_treatment))

# ==============================================================================
# STEP 2: RUN MODELS
# ==============================================================================

# 1. Static Continuous DiD
m_static_cont <- feols(
  outflow_total_dep ~ mtm_treatment:post_btfp + 
                      uninsured_lev + ln_assets + cash_ratio + 
                      book_equity_ratio + loan_to_deposit + wholesale + roa | 
                      idrssd + period,
  data = df_did_fixed, 
  cluster = ~idrssd
)

# 2. Event Study (Parallel Trends Validation)
m_event_cont <- feols(
  outflow_total_dep ~ i(period, mtm_treatment, ref = "2022Q4") + 
                      uninsured_lev + ln_assets + cash_ratio + 
                      book_equity_ratio + loan_to_deposit + wholesale + roa | 
                      idrssd + period,
  data = df_did_fixed, 
  cluster = ~idrssd
)

# ==============================================================================
# STEP 3: VISUALIZE & SAVE
# ==============================================================================

# Display Table in R
etable(m_static_cont, m_event_cont)
##                                       m_static_cont        m_event_cont
## Dependent Var.:                   outflow_total_dep   outflow_total_dep
##                                                                        
## uninsured_lev                    -3.211*** (0.4140)  -3.206*** (0.4152)
## ln_assets                         -41.36*** (4.519)   -40.90*** (4.556)
## cash_ratio                         -0.1146 (0.4658)    -0.1479 (0.4646)
## book_equity_ratio                   0.1758 (0.3852)     0.2934 (0.4030)
## loan_to_deposit                    4.111*** (1.064)    4.095*** (1.063)
## wholesale                          0.2988* (0.1176)    0.3012* (0.1174)
## roa                                 0.1355 (0.1816)     0.1392 (0.1822)
## mtm_treatment x post_btfp       -0.5517*** (0.1357)                    
## mtm_treatment x period = 2022Q1                       -0.4495* (0.2070)
## mtm_treatment x period = 2022Q2                        -0.0632 (0.2045)
## mtm_treatment x period = 2022Q3                        -0.1488 (0.2167)
## mtm_treatment x period = 2023Q1                     -0.7268*** (0.1645)
## mtm_treatment x period = 2023Q2                     -0.6422*** (0.1695)
## mtm_treatment x period = 2023Q3                     -0.7216*** (0.1647)
## Fixed-Effects:                  ------------------- -------------------
## idrssd                                          Yes                 Yes
## period                                          Yes                 Yes
## _______________________________ ___________________ ___________________
## S.E.: Clustered                          by: idrssd          by: idrssd
## Observations                                 29,211              29,211
## R2                                          0.37331             0.37359
## Within R2                                   0.14584             0.14622
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Visualize - This is what the advisor needs to see!
iplot(m_event_cont, 
      main = "Corrected Event Study: MTM Intensity (Eligible Banks Only)",
      xlab = "Quarter", ylab = "Effect of MTM Loss on Outflow")

# Save Result
COEF_MAP_FIXED <- c(
  # DiD Terms
  "mtm_treatment:post_btfp"      = "MTM Intensity ? Post-BTFP",
  "period::2022Q1:mtm_treatment" = "MTM Intensity (2022Q1)",
  "period::2022Q2:mtm_treatment" = "MTM Intensity (2022Q2)",
  "period::2022Q3:mtm_treatment" = "MTM Intensity (2022Q3)",
  "period::2023Q1:mtm_treatment" = "MTM Intensity (2023Q1)",
  "period::2023Q2:mtm_treatment" = "MTM Intensity (2023Q2)",
  "period::2023Q3:mtm_treatment" = "MTM Intensity (2023Q3)",
  
  # Controls
  "uninsured_lev"     = "Uninsured Deposits",
  "ln_assets"         = "Log Assets",
  "cash_ratio"        = "Cash Ratio",
  "book_equity_ratio" = "Book Equity Ratio",
  "loan_to_deposit"   = "Loan-to-Deposit",
  "wholesale"         = "Wholesale Funding",
  "roa"               = "ROA"
)

# If the error persists, run: names(coef(m_event_cont)) 
# and replace the keys above with the exact strings printed.

# Save Result
save_reg_table(list("Static" = m_static_cont, "Event" = m_event_cont), 
               "Table_Fixed_DiD_Intensive",
               title_text = "Different-in-Different: Intensive Margin of MTM Losses",
               notes_text = "This table reports the impact of MTM loss intensity on deposit stability among eligible banks. The treatment is the continuous MTM loss as of 2022Q4. The event study demonstrates parallel trends throughout 2022 and a significant stabilization effect starting in 2023Q1.",
               coef_map_use = COEF_MAP_FIXED, 
               gof_map_use = c("nobs", "r.squared"))


# Define the filename and dimensions
pdf("Figure_Event_Study_MTM_Intensity.pdf", width = 10, height = 7)

# Generate the plot
iplot(m_event_cont, 
      main = "Impact of MTM Intensity on Deposit Stability",
      xlab = "Quarter", 
      ylab = "Coefficient: MTM Intensity on Deposit Outflow",
      pt.join = TRUE,      # Connect the dots to show the trend clearly
      ref.line = TRUE      # Highlight the 2022Q4 reference line
)

# Close the file device
dev.off()
## png 
##   2
# ==============================================================================
# TABLE: BTFP vs DW Side-by-Side (Run Dynamics Proof)
# ==============================================================================

# BTFP models
m_btfp_base <- feols(btfp_acute ~ mtm_total + uninsured_lev + mtm_x_uninsured + 
                     ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + roa + wholesale,
                     data = df_acute, vcov = "hetero")

m_btfp_risk <- feols(btfp_acute ~ run_risk_2 + run_risk_3 + run_risk_4 + 
                     ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + roa + wholesale,
                     data = df_acute, vcov = "hetero")

# DW models (same specification)
m_dw_base <- feols(dw_acute ~ mtm_total + uninsured_lev + mtm_x_uninsured + 
                   ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + roa + wholesale,
                   data = df_acute, vcov = "hetero")

m_dw_risk <- feols(dw_acute ~ run_risk_2 + run_risk_3 + run_risk_4 + 
                   ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + roa + wholesale,
                   data = df_acute, vcov = "hetero")

# Combined table
models_comparison <- list(
  "BTFP (Base)" = m_btfp_base,
  "BTFP (Risk)" = m_btfp_risk,
  "DW (Base)" = m_dw_base,
  "DW (Risk)" = m_dw_risk
)

save_reg_table(
  models_comparison,
  "Table_BTFP_vs_DW_Acute_Comparison",
  title_text = "BTFP vs. Discount Window: Run Dynamics Comparison",
  notes_text = "This table reports Linear Probability Model (LPM) estimates comparing BTFP and Discount Window participation during the acute crisis period (March 13--May 1, 2023). Columns (1)--(2) report BTFP participation. Columns (3)--(4) report DW participation. Base specification includes continuous MTM Loss, Uninsured Leverage, and their interaction. Risk specification includes Risk 2,3,4 dummies. BTFP offered par-value collateral valuation; DW used market prices with haircuts. If identical risk patterns predict both facilities, this confirms run dynamics rather than mechanical collateral exploitation (arbitrage). The sample consists of all commercial banks excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are winsorized and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
  coef_map_use = COEF_MAP,
  gof_map_use = c("nobs", "r.squared")
)

13 ROBUSTNESS CHECKS

# ==============================================================================
# ROBUSTNESS CHECKS
# ==============================================================================

df_rob <- df_acute %>% filter(btfp_acute == 1 | non_user == 1)

# 1. Logit instead of LPM
m_rob_logit <- feglm(build_formula("btfp_acute", "mtm_total + uninsured_lev + mtm_x_uninsured"),
                     data = df_rob, family = binomial("logit"), vcov = "hetero")

# 2. Different winsorization (1%/99%)
df_rob_1 <- df_rob %>%
  mutate(
    mtm_total_1 = standardize_z(winsorize(mtm_total_raw, probs = c(0.01, 0.99))),
    uninsured_lev_1 = standardize_z(winsorize(uninsured_lev_raw, probs = c(0.01, 0.99))),
    mtm_x_unins_1 = mtm_total_1 * uninsured_lev_1
  )

m_rob_win <- feols(btfp_acute ~ mtm_total_1 + uninsured_lev_1 + mtm_x_unins_1 + 
                   ln_assets + cash_ratio + book_equity_ratio + loan_to_deposit + wholesale + roa,
                   data = df_rob_1, vcov = "hetero")

# 3. Exclude large banks
m_rob_small <- feols(build_formula("btfp_acute", "mtm_total + uninsured_lev + mtm_x_uninsured"),
                     data = df_rob %>% filter(size_cat != "Large (>$100B)"), vcov = "hetero")

# 4. Base LPM for comparison
m_rob_lpm <- feols(build_formula("btfp_acute", "mtm_total + uninsured_lev + mtm_x_uninsured"),
                   data = df_rob, vcov = "hetero")

models_rob <- list(
  "(1) LPM" = m_rob_lpm,
  "(2) Logit" = m_rob_logit,
  "(3) Win 1/99" = m_rob_win,
  "(4) Excl Large" = m_rob_small
)

modelsummary(
  models_rob,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP,
  gof_map = c("nobs", "r.squared", "logLik"),
  title = "Table: Robustness Checks",
  output = "kableExtra"
)
Table: Robustness Checks
&nbsp;(1) LPM &nbsp;(2) Logit &nbsp;(3) Win 1/99 &nbsp;(4) Excl Large
MTM Loss (z) 0.024*** 0.214*** 0.025***
(0.006) (0.062) (0.006)
Uninsured Lev (z) 0.020*** 0.209*** 0.019***
(0.007) (0.063) (0.007)
MTM ? Uninsured 0.018*** 0.046 0.018***
(0.005) (0.057) (0.005)
Log(Assets) (z) 0.067*** 0.574*** 0.067*** 0.067***
(0.007) (0.062) (0.007) (0.007)
Cash Ratio (z) −0.024*** −0.517*** −0.024*** −0.024***
(0.005) (0.105) (0.005) (0.005)
Loan/Deposit (z) −0.008 0.001 −0.008 −0.009
(0.005) (0.066) (0.005) (0.005)
Book Equity (z) −0.011** −0.329*** −0.011** −0.011**
(0.004) (0.082) (0.004) (0.004)
Wholesale (z) 0.025*** 0.183*** 0.025*** 0.025***
(0.006) (0.046) (0.006) (0.006)
ROA (z) −0.005 −0.016 −0.005 −0.005
(0.005) (0.063) (0.005) (0.005)
Num.Obs. 3737 3737 3737 3722
R2 0.090 0.131 0.090 0.090
* p < 0.1, ** p < 0.05, *** p < 0.01
save_reg_table(models_rob, "Table_Robustness",
               title_text = "Robustness Checks: Alternative Specifications",
               notes_text = "This table reports robustness checks for BTFP participation during the acute crisis period (March 13--May 1, 2023). The dependent variable equals one if the bank accessed BTFP. Column (1) reports the baseline Linear Probability Model (LPM). Column (2) reports Logit estimates. Column (3) uses alternative winsorization (1\\%/99\\% instead of 2.5\\%/97.5\\%). Column (4) excludes large banks (total assets exceeding \\$100 billion). All specifications include the base continuous variables (MTM Loss, Uninsured Leverage, interaction) plus bank controls. The sample consists of BTFP users versus pure non-users, excluding G-SIBs and failed institutions. Bank characteristics are from 2022Q4 Call Reports. All continuous variables are z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP, gof_map_use = c("nobs", "r.squared", "logLik"))

14 PLOTS

14.1 Coefficient Comparison Plot

# ==============================================================================
# PLOT 1: Coefficient Comparison Across Facilities
# ==============================================================================

# Extract coefficients from risk model
extract_risk_coefs <- function(model, facility_name) {
  coefs <- coef(model)
  ses <- sqrt(diag(vcov(model)))
  
  risk_vars <- c("run_risk_2", "run_risk_3", "run_risk_4")
  risk_labels <- c("Risk 2:\nLow MTM, High Unins", 
                   "Risk 3:\nHigh MTM, Low Unins", 
                   "Risk 4:\nHigh MTM, High Unins")
  
  tibble(
    Facility = facility_name,
    Variable = risk_labels,
    Coefficient = coefs[risk_vars],
    SE = ses[risk_vars],
    CI_low = Coefficient - 1.96 * SE,
    CI_high = Coefficient + 1.96 * SE
  )
}

# Get coefficients from BTFP and DW models
coef_btfp <- extract_risk_coefs(m_btfp_lpm$`(4) Risk2,3,4`, "BTFP")
coef_dw <- extract_risk_coefs(m_dw_lpm$`(4) Risk2,3,4`, "DW")
coef_all <- bind_rows(coef_btfp, coef_dw)

# Plot
p_coef <- ggplot(coef_all, aes(x = Variable, y = Coefficient, color = Facility)) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray50") +
  geom_pointrange(aes(ymin = CI_low, ymax = CI_high), 
                  position = position_dodge(width = 0.4), size = 0.8) +
  scale_color_manual(values = c("BTFP" = "#2166AC", "DW" = "#B2182B")) +
  labs(
    title = "Effect of Bank Risk Category on Emergency Borrowing",
    subtitle = "LPM Coefficients with 95% CI (Reference: Risk 1 - Low MTM, Low Uninsured)",
    x = NULL,
    y = "Change in Probability of Borrowing",
    color = "Facility"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "bottom",
    plot.title = element_text(face = "bold"),
    panel.grid.minor = element_blank()
  )

print(p_coef)

# SAVE FIGURE 1
save_figure(p_coef, "Figure_1_Coefficient_Comparison", width = 12, height = 8)

14.2 High-Risk Quadrant Heatmap

# ==============================================================================
# PLOT: High-Risk Quadrant Interaction Heatmap
# ==============================================================================

# Fit continuous interaction model
fml_int <- as.formula(paste("btfp_acute ~ mtm_total * uninsured_lev +", CONTROLS))
m_cont <- feols(fml_int, data = df_btfp_acute, vcov = "hetero")

# Create prediction grid (hold controls at 0 = mean)
grid <- expand.grid(
  mtm_total = seq(min(df_btfp_acute$mtm_total, na.rm = TRUE), 
                  max(df_btfp_acute$mtm_total, na.rm = TRUE), length.out = 50),
  uninsured_lev = seq(min(df_btfp_acute$uninsured_lev, na.rm = TRUE), 
                      max(df_btfp_acute$uninsured_lev, na.rm = TRUE), length.out = 50),
  ln_assets = 0, cash_ratio = 0, loan_to_deposit = 0, 
  book_equity_ratio = 0, wholesale = 0, roa = 0
)

grid$prob <- predict(m_cont, newdata = grid)
grid$prob <- pmax(pmin(grid$prob, 1), 0)  # Bound probabilities

p_heat <- ggplot(grid, aes(x = mtm_total, y = uninsured_lev, fill = prob)) +
  geom_tile() +
  scale_fill_viridis_c(option = "magma", labels = scales::percent, limits = c(0, 0.5)) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "white", alpha = 0.5) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "white", alpha = 0.5) +
  annotate("text", x = 2, y = 2, label = "Death\nZone", color = "white", fontface = "bold", size = 5) +
  labs(
    title = "Interaction of MTM Losses and Uninsured Deposits on BTFP Usage",
    subtitle = "Predicted Probability of BTFP Borrowing (Acute Period)",
    x = "MTM Losses (Z-Score)",
    y = "Uninsured Deposits (Z-Score)",
    fill = "Pr(BTFP)"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    panel.grid = element_blank(),
    plot.title = element_text(face = "bold")
  )

print(p_heat)

# SAVE FIGURE 2
save_figure(p_heat, "Figure_2_DeathZone_Heatmap", width = 12, height = 8)

14.3 Temporal Evolution Plot

# ==============================================================================
# PLOT 3: Temporal Evolution of Risk Category Effects
# ==============================================================================

# Extract Risk 4 coefficient across periods
temporal_coefs <- tibble(
  Period = c("Acute", "Post-Acute", "Arbitrage", "Wind-down"),
  Period_Order = 1:4,
  Coefficient = c(
    coef(m_acute$Risk)["run_risk_4"],
    coef(m_post$Risk)["run_risk_4"],
    coef(m_arb$Risk)["run_risk_4"],
    coef(m_wind$Risk)["run_risk_4"]
  ),
  SE = c(
    sqrt(vcov(m_acute$Risk)["run_risk_4", "run_risk_4"]),
    sqrt(vcov(m_post$Risk)["run_risk_4", "run_risk_4"]),
    sqrt(vcov(m_arb$Risk)["run_risk_4", "run_risk_4"]),
    sqrt(vcov(m_wind$Risk)["run_risk_4", "run_risk_4"])
  )
) %>%
  mutate(
    CI_low = Coefficient - 1.96 * SE,
    CI_high = Coefficient + 1.96 * SE,
    Period = factor(Period, levels = c("Acute", "Post-Acute", "Arbitrage", "Wind-down"))
  )

p_temporal <- ggplot(temporal_coefs, aes(x = Period, y = Coefficient)) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray50") +
  geom_pointrange(aes(ymin = CI_low, ymax = CI_high), 
                  color = "#2166AC", size = 1) +
  geom_line(aes(group = 1), color = "#2166AC", alpha = 0.5) +
  labs(
    title = "Evolution of High-Risk Quadrant Effect on BTFP Borrowing",
    subtitle = "Risk 4 (High MTM Loss + High Uninsured) Coefficient Over Time",
    x = "Crisis Period",
    y = "LPM Coefficient (vs. Reference: Low MTM, Low Unins)"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold"),
    panel.grid.minor = element_blank()
  )

print(p_temporal)

# SAVE FIGURE 3
save_figure(p_temporal, "Figure_3_Temporal_Evolution", width = 12, height = 6)

14.4 Participation Comparison by Size

# ==============================================================================
# PLOT 4: Participation Rates by Size and Facility
# ==============================================================================

participation <- df_acute %>%
  group_by(size_cat) %>%
  summarise(
    BTFP = mean(btfp_acute, na.rm = TRUE) * 100,
    DW = mean(dw_acute, na.rm = TRUE) * 100,
    `Any Fed` = mean(any_fed, na.rm = TRUE) * 100,
    N = n(),
    .groups = "drop"
  ) %>%
  pivot_longer(cols = c(BTFP, DW, `Any Fed`), names_to = "Facility", values_to = "Rate")

p_part <- ggplot(participation, aes(x = size_cat, y = Rate, fill = Facility)) +
  geom_bar(stat = "identity", position = position_dodge(width = 0.8), width = 0.7) +
  geom_text(aes(label = sprintf("%.1f%%", Rate)), 
            position = position_dodge(width = 0.8), vjust = -0.5, size = 3) +
  scale_fill_manual(values = c("BTFP" = "#2166AC", "DW" = "#B2182B", "Any Fed" = "#7570B3")) +
  scale_y_continuous(labels = function(x) paste0(x, "%"), expand = expansion(mult = c(0, 0.15))) +
  labs(
    title = "Emergency Facility Participation by Bank Size",
    subtitle = "Acute Period (March 13 - May 1, 2023)",
    x = "Bank Size Category",
    y = "Participation Rate",
    fill = "Facility"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "bottom",
    plot.title = element_text(face = "bold"),
    panel.grid.minor = element_blank()
  )

print(p_part)

# SAVE FIGURE 4
save_figure(p_part, "Figure_4_Participation_by_Size", width = 12, height = 8)

14.5 6.5 Risk Category Distribution

# ==============================================================================
# PLOT 5: Risk Category Distribution by User Group
# ==============================================================================

risk_dist <- df_acute %>%
  filter(user_group != "Both") %>%  # Simplify for visualization
  group_by(user_group) %>%
  summarise(
    `Risk 1\n(Low, Low)` = mean(run_risk_1, na.rm = TRUE) * 100,
    `Risk 2\n(Low, High)` = mean(run_risk_2, na.rm = TRUE) * 100,
    `Risk 3\n(High, Low)` = mean(run_risk_3, na.rm = TRUE) * 100,
    `Risk 4\n(High, High)` = mean(run_risk_4, na.rm = TRUE) * 100,
    N = n(),
    .groups = "drop"
  ) %>%
  pivot_longer(cols = -c(user_group, N), names_to = "Risk_Category", values_to = "Pct")

p_risk <- ggplot(risk_dist, aes(x = user_group, y = Pct, fill = Risk_Category)) +
  geom_bar(stat = "identity", position = "stack") +
  scale_fill_manual(values = c(
    "Risk 1\n(Low, Low)" = "#4DAF4A",
    "Risk 2\n(Low, High)" = "#FFFF33",
    "Risk 3\n(High, Low)" = "#FF7F00",
    "Risk 4\n(High, High)" = "#E41A1C"
  )) +
  scale_y_continuous(labels = function(x) paste0(x, "%")) +
  labs(
    title = "Risk Category Composition by Facility Choice",
    subtitle = "Acute Period - Distribution of Banks Across Risk Categories",
    x = "Facility Choice",
    y = "Percentage of Banks",
    fill = "Risk Category"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    plot.title = element_text(face = "bold")
  )

print(p_risk)

# SAVE FIGURE 5
save_figure(p_risk, "Figure_5_Risk_Distribution", width = 12, height = 6)

15 SECTION 7: ADDITIONAL VISUALIZATIONS

16 # Run Risk = f(Fundamentals, Liquidity Mismatch, Fundamentals ? Liquidity Mismatch)

16.1 7.1 Scatter Plot: MTM Losses vs Uninsured Deposits

# ==============================================================================
# PLOT 6: The Core Relationship
# X = MTM Losses (Fundamentals), Y = Uninsured Deposits (Liquidity Mismatch)
# Color = Facility Choice
# ==============================================================================

p_scatter <- df_acute %>%
  filter(user_group %in% c("Neither", "BTFP_Only", "DW_Only")) %>%
  mutate(
    user_group = factor(user_group, 
                        levels = c("Neither", "BTFP_Only", "DW_Only"),
                        labels = c("No Facility", "BTFP", "Discount Window"))
  ) %>%
  ggplot(aes(x = mtm_total, y = uninsured_lev, color = user_group)) +
  # Add quadrant lines at zero (which is median for z-scores)
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray50", alpha = 0.7) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray50", alpha = 0.7) +
  # Add quadrant labels
  annotate("text", x = -2, y = 2.5, label = "Risk 2\n(Low MTM, High Unins)", 
           color = "gray40", size = 3, hjust = 0) +
  annotate("text", x = 2, y = 2.5, label = "Risk 4\n(High MTM, High Unins)", 
           color = "#E41A1C", size = 3, fontface = "bold", hjust = 1) +
  annotate("text", x = -2, y = -2, label = "Risk 1\n(Low MTM, Low Unins)", 
           color = "#4DAF4A", size = 3, hjust = 0) +
  annotate("text", x = 2, y = -2, label = "Risk 3\n(High MTM, Low Unins)", 
           color = "gray40", size = 3, hjust = 1) +
  # Points
  geom_point(alpha = 0.6, size = 2) +
  scale_color_manual(values = c("No Facility" = "gray60", 
                                "BTFP" = "#2166AC", 
                                "Discount Window" = "#B2182B")) +
  labs(
    title = "The Run Risk Landscape: Where Do Facility Users Fall?",
    subtitle = "Bank Fundamentals vs Liquidity Mismatch (Z-Scores, Acute Period)",
    x = "MTM Loss (Z-Score)\n? Sound Fundamentals | Weak Fundamentals ?",
    y = "Uninsured Deposits (Z-Score)\n? Low Run Risk | High Run Risk ?",
    color = "Facility Used",
    caption = "Note: Dashed lines at z=0 (sample medians). Risk categories: (MTM Loss level, Uninsured Deposit level)."
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "bottom",
    plot.title = element_text(face = "bold"),
    panel.grid.minor = element_blank()
  )

print(p_scatter)

# SAVE FIGURE 6
save_figure(p_scatter, "Figure_6_Scatter_MTM_vs_Uninsured", width = 12, height = 8)

16.2 Marginal Effects Plot

# ==============================================================================
# PLOT 7: How Does the Effect of MTM Change with Uninsured Deposits?
# Tests the interaction: MTM ? Uninsured
# ==============================================================================

# Get coefficients from base model
base_model <- feols(btfp_acute ~ mtm_total + uninsured_lev + mtm_x_uninsured + ln_assets + 
                    cash_ratio + loan_to_deposit + book_equity_ratio + wholesale + roa,
                    data = df_btfp_acute_s, vcov = "hetero")

base_coefs <- coef(base_model)
base_vcov <- vcov(base_model)

# Calculate marginal effect of MTM at different levels of uninsured
uninsured_levels <- seq(-2, 3, by = 1)
uninsured_labels <- c("-2 SD\n(Very Low)", "-1 SD\n(Low)", "0\n(Median)", 
                      "+1 SD\n(High)", "+2 SD\n(Very High)", "+3 SD\n(Extreme)")

marginal_df <- tibble(
  Uninsured_Level = factor(uninsured_labels, levels = uninsured_labels),
  Uninsured_Z = uninsured_levels,
  # Marginal effect of MTM = beta_mtm + beta_interaction * uninsured
  Marginal_Effect = base_coefs["mtm_total"] + base_coefs["mtm_x_uninsured"] * uninsured_levels
) %>%
  mutate(
    # SE via delta method
    SE = sqrt(base_vcov["mtm_total", "mtm_total"] + 
              2 * Uninsured_Z * base_vcov["mtm_total", "mtm_x_uninsured"] +
              Uninsured_Z^2 * base_vcov["mtm_x_uninsured", "mtm_x_uninsured"]),
    CI_low = Marginal_Effect - 1.96 * SE,
    CI_high = Marginal_Effect + 1.96 * SE
  )

p_marginal <- ggplot(marginal_df, aes(x = Uninsured_Level, y = Marginal_Effect)) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray50") +
  geom_pointrange(aes(ymin = CI_low, ymax = CI_high), color = "#2166AC", size = 1) +
  geom_line(aes(group = 1), color = "#2166AC", alpha = 0.5) +
  labs(
    title = "How Liquidity Risk Amplifies Fundamental Weakness",
    subtitle = "Marginal Effect of MTM Loss on BTFP Probability at Different Uninsured Deposit Levels",
    x = "Uninsured Deposit Level (Z-Score)",
    y = "Marginal Effect of 1 SD Increase in MTM Loss\non Probability of BTFP Borrowing",
    caption = "Note: Based on LPM estimates. 95% confidence intervals shown.\nPositive effect means higher MTM losses increase BTFP usage."
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold"),
    panel.grid.minor = element_blank()
  )

print(p_marginal)

# SAVE FIGURE 7
save_figure(p_marginal, "Figure_7_Marginal_Effects_Interaction", width = 12, height = 6)

16.3 Density Comparison: MTM Losses by User Group

# ==============================================================================
# PLOT 8: Distribution of MTM Losses by Facility Choice
# ==============================================================================

p_density_mtm <- df_acute %>%
  filter(user_group %in% c("Neither", "BTFP_Only", "DW_Only")) %>%
  mutate(user_group = factor(user_group, 
                             levels = c("Neither", "BTFP_Only", "DW_Only"),
                             labels = c("No Facility", "BTFP", "Discount Window"))) %>%
  ggplot(aes(x = mtm_total_w, fill = user_group, color = user_group)) +
  geom_density(alpha = 0.4) +
  geom_vline(xintercept = median(df_acute$mtm_total_w, na.rm = TRUE), 
             linetype = "dashed", color = "gray30") +
  annotate("text", x = median(df_acute$mtm_total_w, na.rm = TRUE) + 0.3, y = 0.15, 
           label = "Median", color = "gray30", hjust = 0) +
  scale_fill_manual(values = c("No Facility" = "gray70", "BTFP" = "#2166AC", "Discount Window" = "#B2182B")) +
  scale_color_manual(values = c("No Facility" = "gray50", "BTFP" = "#2166AC", "Discount Window" = "#B2182B")) +
  labs(
    title = "Distribution of Mark-to-Market Losses by Facility Choice",
    subtitle = "Acute Period: Comparing Fundamentals Across User Groups",
    x = "MTM Loss / Total Assets (%)",
    y = "Density",
    fill = "Facility Used",
    color = "Facility Used",
    caption = "Note: Higher values indicate larger unrealized losses (worse fundamentals)."
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "bottom",
    plot.title = element_text(face = "bold")
  )

print(p_density_mtm)

# SAVE FIGURE 8
save_figure(p_density_mtm, "Figure_8_Density_MTM_Losses", width = 12, height = 6)

16.4 Density Comparison: Uninsured Deposits by User Group

# ==============================================================================
# PLOT 9: Distribution of Uninsured Deposits by Facility Choice
# ==============================================================================

p_density_unins <- df_acute %>%
  filter(user_group %in% c("Neither", "BTFP_Only", "DW_Only")) %>%
  mutate(user_group = factor(user_group, 
                             levels = c("Neither", "BTFP_Only", "DW_Only"),
                             labels = c("No Facility", "BTFP", "Discount Window"))) %>%
  ggplot(aes(x = uninsured_lev_w, fill = user_group, color = user_group)) +
  geom_density(alpha = 0.4) +
  geom_vline(xintercept = median(df_acute$uninsured_lev_w, na.rm = TRUE), 
             linetype = "dashed", color = "gray30") +
  annotate("text", x = median(df_acute$uninsured_lev_w, na.rm = TRUE) + 1, y = 0.04, 
           label = "Median", color = "gray30", hjust = 0) +
  scale_fill_manual(values = c("No Facility" = "gray70", "BTFP" = "#2166AC", "Discount Window" = "#B2182B")) +
  scale_color_manual(values = c("No Facility" = "gray50", "BTFP" = "#2166AC", "Discount Window" = "#B2182B")) +
  labs(
    title = "Distribution of Uninsured Deposits by Facility Choice",
    subtitle = "Acute Period: Comparing Liquidity Mismatch Across User Groups",
    x = "Uninsured Deposits / Total Assets (%)",
    y = "Density",
    fill = "Facility Used",
    color = "Facility Used",
    caption = "Note: Higher values indicate greater liquidity mismatch (higher run risk)."
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "bottom",
    plot.title = element_text(face = "bold")
  )

print(p_density_unins)

# SAVE FIGURE 9
save_figure(p_density_unins, "Figure_9_Density_Uninsured_Deposits", width = 12, height = 6)

16.5 Combined Risk Profile Panel

# ==============================================================================
# PLOT 10: Combined Panel - Key Story Visualization
# ==============================================================================

# Panel A: Average risk metrics by group
p_panel_a <- df_acute %>%
  filter(user_group %in% c("Neither", "BTFP_Only", "DW_Only")) %>%
  mutate(user_group = factor(user_group, 
                             levels = c("Neither", "BTFP_Only", "DW_Only"),
                             labels = c("No Facility", "BTFP", "DW"))) %>%
  group_by(user_group) %>%
  summarise(
    MTM_Loss = mean(mtm_total_w, na.rm = TRUE),
    MTM_SE = sd(mtm_total_w, na.rm = TRUE) / sqrt(n()),
    Uninsured = mean(uninsured_lev_w, na.rm = TRUE),
    Unins_SE = sd(uninsured_lev_w, na.rm = TRUE) / sqrt(n()),
    .groups = "drop"
  ) %>%
  pivot_longer(cols = c(MTM_Loss, Uninsured), names_to = "Metric", values_to = "Value") %>%
  mutate(
    SE = ifelse(Metric == "MTM_Loss", MTM_SE, Unins_SE),
    Metric = ifelse(Metric == "MTM_Loss", "MTM Loss (%)", "Uninsured Deposits (%)")
  ) %>%
  ggplot(aes(x = user_group, y = Value, fill = Metric)) +
  geom_col(position = position_dodge(0.8), width = 0.7) +
  geom_errorbar(aes(ymin = Value - 1.96*SE, ymax = Value + 1.96*SE),
                position = position_dodge(0.8), width = 0.2) +
  scale_fill_manual(values = c("MTM Loss (%)" = "#E41A1C", "Uninsured Deposits (%)" = "#377EB8")) +
  labs(
    title = "A. Average Risk Profile by Facility",
    x = NULL,
    y = "Mean Value (%)",
    fill = NULL
  ) +
  theme_minimal(base_size = 11) +
  theme(legend.position = "bottom")

# Panel B: High-risk quadrant share
p_panel_b <- df_acute %>%
  filter(user_group %in% c("Neither", "BTFP_Only", "DW_Only")) %>%
  group_by(user_group) %>%
  summarise(
    HighRisk = mean(run_risk_4, na.rm = TRUE) * 100,
    .groups = "drop"
  ) %>%
  mutate(user_group = factor(user_group, 
                             levels = c("Neither", "BTFP_Only", "DW_Only"),
                             labels = c("No Facility", "BTFP", "DW"))) %>%
  ggplot(aes(x = user_group, y = HighRisk, fill = user_group)) +
  geom_col() +
  geom_text(aes(label = sprintf("%.1f%%", HighRisk)), vjust = -0.5, size = 4) +
  scale_fill_manual(values = c("No Facility" = "gray70", "BTFP" = "#2166AC", "DW" = "#B2182B")) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.15))) +
  labs(
    title = "B. Share in Risk 4 Quadrant",
    subtitle = "(High MTM, High Uninsured)",
    x = NULL,
    y = "Percent of Group"
  ) +
  theme_minimal(base_size = 11) +
  theme(legend.position = "none")

# Combine panels
p_combined <- (p_panel_a | p_panel_b) +
  plot_annotation(
    title = "The Run Risk Profile: How BTFP Users Differ",
    subtitle = "Acute Period (March 13 - May 1, 2023)",
    theme = theme(plot.title = element_text(face = "bold", size = 14))
  )

print(p_combined)

# SAVE FIGURE 10
save_figure(p_combined, "Figure_10_Combined_Risk_Panel", width = 14, height = 6)
# ==============================================================================
# COEFFICIENT PLOTS: Acute Period - BTFP, DW, AnyFed
# ==============================================================================

## Function to extract coefficients from a model
extract_all_coefs <- function(model, model_name) {
  coefs <- coef(model)
  ses <- sqrt(diag(vcov(model)))
  
  # Get all coefficients except intercept
  vars <- names(coefs)[names(coefs) != "(Intercept)"]
  
  tibble(
    Model = model_name,
    Variable = vars,
    Coefficient = coefs[vars],
    SE = ses[vars],
    CI_low = Coefficient - 1.96 * SE,
    CI_high = Coefficient + 1.96 * SE,
    Significant = ifelse(CI_low > 0 | CI_high < 0, "Yes", "No")
  )
}

## Variable labels for display
VAR_LABELS <- c(
  "mtm_total" = "MTM Loss (Total)",
  "uninsured_lev" = "Uninsured Deposits",
  "mtm_x_uninsured" = "MTM ? Uninsured",
  "run_risk_2" = "Risk 2 (Low MTM, High Unins)",
  "run_risk_3" = "Risk 3 (High MTM, Low Unins)",
  "run_risk_4" = "Risk 4 (High MTM, High Unins)",
  "ln_assets" = "Log(Assets)",
  "cash_ratio" = "Cash Ratio",
  "loan_to_deposit" = "Loan/Deposit",
  "book_equity_ratio" = "Book Equity",
  "wholesale" = "Wholesale Funding",
  "roa" = "ROA"
)

# ------------------------------------------------------------------------------
# PLOT : BTFP Acute Period - All Coefficients (Base Model)
# ------------------------------------------------------------------------------

coef_btfp_base <- extract_all_coefs(m_btfp_lpm$`(1) Base`, "BTFP") %>%
  mutate(Variable = factor(Variable, levels = rev(names(VAR_LABELS)), labels = rev(VAR_LABELS)))

p_btfp_coef_base <- ggplot(coef_btfp_base, aes(x = Coefficient, y = Variable, color = Significant)) +

geom_vline(xintercept = 0, linetype = "dashed", color = "gray50") +
  geom_pointrange(aes(xmin = CI_low, xmax = CI_high), size = 0.8) +
  scale_color_manual(values = c("Yes" = "#2166AC", "No" = "gray60"), guide = "none") +
  labs(
    title = "BTFP Borrowing Determinants (Acute Period)",
    subtitle = "Base specification: MTM Loss + Uninsured + Interaction + Controls",
    x = "Coefficient Estimate (95% CI)",
    y = NULL,
    caption = "Note: LPM estimates. Blue = significant at 5% level."
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold"),
    panel.grid.major.y = element_blank()
  )

print(p_btfp_coef_base)

save_figure(p_btfp_coef_base, "Figure_A1_BTFP_Acute_Coefficients_Base", width = 10, height = 7)

# ------------------------------------------------------------------------------
# PLOT: BTFP Acute Period - Risk Category Model
# ------------------------------------------------------------------------------

coef_btfp_risk <- extract_all_coefs(m_btfp_lpm$`(4) Risk2,3,4`, "BTFP") %>%
  mutate(Variable = factor(Variable, levels = rev(names(VAR_LABELS)), labels = rev(VAR_LABELS)))

p_btfp_coef_risk <- ggplot(coef_btfp_risk, aes(x = Coefficient, y = Variable, color = Significant)) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray50") +
  geom_pointrange(aes(xmin = CI_low, xmax = CI_high), size = 0.8) +
  scale_color_manual(values = c("Yes" = "#2166AC", "No" = "gray60"), guide = "none") +
  labs(
    title = "BTFP Borrowing Determinants (Acute Period)",
    subtitle = "Risk category specification (Reference: Risk 1 - Low MTM, Low Uninsured)",
    x = "Coefficient Estimate (95% CI)",
    y = NULL,
    caption = "Note: LPM estimates. Blue = significant at 5% level."
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold"),
    panel.grid.major.y = element_blank()
  )

print(p_btfp_coef_risk)

save_figure(p_btfp_coef_risk, "Figure_A2_BTFP_Acute_Coefficients_Risk", width = 10, height = 7)

# ------------------------------------------------------------------------------
# PLOT: DW Acute Period - All Coefficients (Base Model)
# ------------------------------------------------------------------------------

coef_dw_base <- extract_all_coefs(m_dw_lpm$`(1) Base`, "DW") %>%
  mutate(Variable = factor(Variable, levels = rev(names(VAR_LABELS)), labels = rev(VAR_LABELS)))

p_dw_coef_base <- ggplot(coef_dw_base, aes(x = Coefficient, y = Variable, color = Significant)) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray50") +
  geom_pointrange(aes(xmin = CI_low, xmax = CI_high), size = 0.8) +
  scale_color_manual(values = c("Yes" = "#B2182B", "No" = "gray60"), guide = "none") +
  labs(
    title = "Discount Window Borrowing Determinants (Acute Period)",
    subtitle = "Base specification: MTM Loss + Uninsured + Interaction + Controls",
    x = "Coefficient Estimate (95% CI)",
    y = NULL,
    caption = "Note: LPM estimates. Red = significant at 5% level."
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold"),
    panel.grid.major.y = element_blank()
  )

print(p_dw_coef_base)

save_figure(p_dw_coef_base, "Figure_A3_DW_Acute_Coefficients_Base", width = 10, height = 7)

# ------------------------------------------------------------------------------
# PLOT: DW Acute Period - Risk Category Model
# ------------------------------------------------------------------------------

coef_dw_risk <- extract_all_coefs(m_dw_lpm$`(4) Risk2,3,4`, "DW") %>%
  mutate(Variable = factor(Variable, levels = rev(names(VAR_LABELS)), labels = rev(VAR_LABELS)))

p_dw_coef_risk <- ggplot(coef_dw_risk, aes(x = Coefficient, y = Variable, color = Significant)) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray50") +
  geom_pointrange(aes(xmin = CI_low, xmax = CI_high), size = 0.8) +
  scale_color_manual(values = c("Yes" = "#B2182B", "No" = "gray60"), guide = "none") +
  labs(
    title = "Discount Window Borrowing Determinants (Acute Period)",
    subtitle = "Risk category specification (Reference: Risk 1 - Low MTM, Low Uninsured)",
    x = "Coefficient Estimate (95% CI)",
    y = NULL,
    caption = "Note: LPM estimates. Red = significant at 5% level."
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold"),
    panel.grid.major.y = element_blank()
  )

print(p_dw_coef_risk)

save_figure(p_dw_coef_risk, "Figure_A4_DW_Acute_Coefficients_Risk", width = 10, height = 7)

# ------------------------------------------------------------------------------
# PLOT: AnyFed Acute Period - All Coefficients (Base Model)
# ------------------------------------------------------------------------------

coef_anyfed_base <- extract_all_coefs(m_all_lpm$`(1) Base`, "AnyFed") %>%
  mutate(Variable = factor(Variable, levels = rev(names(VAR_LABELS)), labels = rev(VAR_LABELS)))

p_anyfed_coef_base <- ggplot(coef_anyfed_base, aes(x = Coefficient, y = Variable, color = Significant)) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray50") +
  geom_pointrange(aes(xmin = CI_low, xmax = CI_high), size = 0.8) +
  scale_color_manual(values = c("Yes" = "#4DAF4A", "No" = "gray60"), guide = "none") +
  labs(
    title = "Any Fed Facility Borrowing Determinants (Acute Period)",
    subtitle = "Base specification: MTM Loss + Uninsured + Interaction + Controls",
    x = "Coefficient Estimate (95% CI)",
    y = NULL,
    caption = "Note: LPM estimates. Green = significant at 5% level."
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold"),
    panel.grid.major.y = element_blank()
  )

print(p_anyfed_coef_base)

save_figure(p_anyfed_coef_base, "Figure_A5_AnyFed_Acute_Coefficients_Base", width = 10, height = 7)

# ------------------------------------------------------------------------------
# PLOT: AnyFed Acute Period - Risk Category Model
# ------------------------------------------------------------------------------

coef_anyfed_risk <- extract_all_coefs(m_all_lpm$`(4) Risk2,3,4`, "AnyFed") %>%
  mutate(Variable = factor(Variable, levels = rev(names(VAR_LABELS)), labels = rev(VAR_LABELS)))

p_anyfed_coef_risk <- ggplot(coef_anyfed_risk, aes(x = Coefficient, y = Variable, color = Significant)) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray50") +
  geom_pointrange(aes(xmin = CI_low, xmax = CI_high), size = 0.8) +
  scale_color_manual(values = c("Yes" = "#4DAF4A", "No" = "gray60"), guide = "none") +
  labs(
    title = "Any Fed Facility Borrowing Determinants (Acute Period)",
    subtitle = "Risk category specification (Reference: Risk 1 - Low MTM, Low Uninsured)",
    x = "Coefficient Estimate (95% CI)",
    y = NULL,
    caption = "Note: LPM estimates. Green = significant at 5% level."
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold"),
    panel.grid.major.y = element_blank()
  )

print(p_anyfed_coef_risk)

save_figure(p_anyfed_coef_risk, "Figure_A6_AnyFed_Acute_Coefficients_Risk", width = 10, height = 7)

# ------------------------------------------------------------------------------
# PLOT: Combined Acute Period - BTFP vs DW vs AnyFed (Base Model)
# ------------------------------------------------------------------------------

coef_combined_base <- bind_rows(
  extract_all_coefs(m_btfp_lpm$`(1) Base`, "BTFP"),
  extract_all_coefs(m_dw_lpm$`(1) Base`, "DW"),
  extract_all_coefs(m_all_lpm$`(1) Base`, "Any Fed")
) %>%
  mutate(
    Variable = factor(Variable, levels = rev(names(VAR_LABELS)), labels = rev(VAR_LABELS)),
    Model = factor(Model, levels = c("BTFP", "DW", "Any Fed"))
  )

p_combined_base <- ggplot(coef_combined_base, aes(x = Coefficient, y = Variable, color = Model)) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray50") +
  geom_pointrange(aes(xmin = CI_low, xmax = CI_high), 
                  position = position_dodge(width = 0.6), size = 0.7) +
  scale_color_manual(values = c("BTFP" = "#2166AC", "DW" = "#B2182B", "Any Fed" = "#4DAF4A")) +
  labs(
    title = "Borrowing Determinants by Facility Type (Acute Period)",
    subtitle = "Base specification: MTM Loss + Uninsured + Interaction + Controls",
    x = "Coefficient Estimate (95% CI)",
    y = NULL,
    color = "Facility",
    caption = "Note: LPM estimates with heteroskedasticity-robust standard errors."
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold"),
    panel.grid.major.y = element_blank(),
    legend.position = "bottom"
  )

print(p_combined_base)

save_figure(p_combined_base, "Figure_A7_Combined_Acute_Coefficients_Base", width = 12, height = 8)

# ------------------------------------------------------------------------------
# PLOT: Combined Acute Period - BTFP vs DW vs AnyFed (Risk Model)
# ------------------------------------------------------------------------------

coef_combined_risk <- bind_rows(
  extract_all_coefs(m_btfp_lpm$`(4) Risk2,3,4`, "BTFP"),
  extract_all_coefs(m_dw_lpm$`(4) Risk2,3,4`, "DW"),
  extract_all_coefs(m_all_lpm$`(4) Risk2,3,4`, "Any Fed")
) %>%
  mutate(
    Variable = factor(Variable, levels = rev(names(VAR_LABELS)), labels = rev(VAR_LABELS)),
    Model = factor(Model, levels = c("BTFP", "DW", "Any Fed"))
  )

p_combined_risk <- ggplot(coef_combined_risk, aes(x = Coefficient, y = Variable, color = Model)) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray50") +
  geom_pointrange(aes(xmin = CI_low, xmax = CI_high), 
                  position = position_dodge(width = 0.6), size = 0.7) +
  scale_color_manual(values = c("BTFP" = "#2166AC", "DW" = "#B2182B", "Any Fed" = "#4DAF4A")) +
  labs(
    title = "Borrowing Determinants by Facility Type (Acute Period)",
    subtitle = "Risk category specification (Reference: Risk 1 - Low MTM, Low Uninsured)",
    x = "Coefficient Estimate (95% CI)",
    y = NULL,
    color = "Facility",
    caption = "Note: LPM estimates with heteroskedasticity-robust standard errors."
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold"),
    panel.grid.major.y = element_blank(),
    legend.position = "bottom"
  )

print(p_combined_risk)

save_figure(p_combined_risk, "Figure_A8_Combined_Acute_Coefficients_Risk", width = 12, height = 8)


# ==============================================================================
# TEMPORAL DYNAMICS PLOTS: Coefficient Evolution Across Crisis Phases
# ==============================================================================

# ------------------------------------------------------------------------------
# Function to extract temporal coefficients
# ------------------------------------------------------------------------------

extract_temporal_coefs <- function(models_list, facility_name, key_vars = c("mtm_total", "uninsured_lev", "mtm_x_uninsured")) {
  
  results <- list()
  
  for (period in names(models_list)) {
    model <- models_list[[period]]
    coefs <- coef(model)
    ses <- sqrt(diag(vcov(model)))
    
    for (v in key_vars) {
      if (v %in% names(coefs)) {
        results[[length(results) + 1]] <- tibble(
          Facility = facility_name,
          Period = period,
          Variable = v,
          Coefficient = coefs[v],
          SE = ses[v],
          CI_low = Coefficient - 1.96 * SE,
          CI_high = Coefficient + 1.96 * SE
        )
      }
    }
  }
  
  bind_rows(results)
}

# ------------------------------------------------------------------------------
# BTFP Temporal Models (need to create these first)
# ------------------------------------------------------------------------------

# Create temporal models for BTFP (Base specification)
btfp_temporal_base <- list(
  "Acute" = feols(btfp_acute ~ mtm_total + uninsured_lev + mtm_x_uninsured + 
                    ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + wholesale + roa,
                  data = df_btfp_acute_s, vcov = "hetero"),
  "Post-Acute" = feols(btfp_post ~ mtm_total + uninsured_lev + mtm_x_uninsured + 
                         ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + wholesale + roa,
                       data = df_btfp_post_s, vcov = "hetero"),
  "Arbitrage" = feols(btfp_arb ~ mtm_total + uninsured_lev + mtm_x_uninsured + 
                        ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + wholesale + roa,
                      data = df_btfp_arb_s, vcov = "hetero"),
  "Wind-down" = feols(btfp_wind ~ mtm_total + uninsured_lev + mtm_x_uninsured + 
                        ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + wholesale + roa,
                      data = df_btfp_wind_s, vcov = "hetero")
)

# Create temporal models for BTFP (Risk specification)
btfp_temporal_risk <- list(
  "Acute" = feols(btfp_acute ~ run_risk_2 + run_risk_3 + run_risk_4 + 
                    ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + wholesale + roa,
                  data = df_btfp_acute_s, vcov = "hetero"),
  "Post-Acute" = feols(btfp_post ~ run_risk_2 + run_risk_3 + run_risk_4 + 
                         ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + wholesale + roa,
                       data = df_btfp_post_s, vcov = "hetero"),
  "Arbitrage" = feols(btfp_arb ~ run_risk_2 + run_risk_3 + run_risk_4 + 
                        ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + wholesale + roa,
                      data = df_btfp_arb_s, vcov = "hetero"),
  "Wind-down" = feols(btfp_wind ~ run_risk_2 + run_risk_3 + run_risk_4 + 
                        ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + wholesale + roa,
                      data = df_btfp_wind_s, vcov = "hetero")
)

# ------------------------------------------------------------------------------
# DW Temporal Models
# ------------------------------------------------------------------------------

# DW temporal models (Base specification) - only Pre-BTFP and Acute available
dw_temporal_base <- list(
  "Pre-BTFP" = feols(dw_prebtfp ~ mtm_total + uninsured_lev + mtm_x_uninsured + 
                       ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + wholesale + roa,
                     data = df_dw_pre_s, vcov = "hetero"),
  "Acute" = feols(dw_acute ~ mtm_total + uninsured_lev + mtm_x_uninsured + 
                    ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + wholesale + roa,
                  data = df_dw_acute, vcov = "hetero")
)

# DW temporal models (Risk specification)
dw_temporal_risk <- list(
  "Pre-BTFP" = feols(dw_prebtfp ~ run_risk_2 + run_risk_3 + run_risk_4 + 
                       ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + wholesale + roa,
                     data = df_dw_pre_s, vcov = "hetero"),
  "Acute" = feols(dw_acute ~ run_risk_2 + run_risk_3 + run_risk_4 + 
                    ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + wholesale + roa,
                  data = df_dw_acute, vcov = "hetero")
)

# ------------------------------------------------------------------------------
# PLOT: BTFP Temporal Evolution - Base Model (Bar Chart Style)
# ------------------------------------------------------------------------------

btfp_temp_coefs_base <- extract_temporal_coefs(btfp_temporal_base, "BTFP") %>%
  mutate(
    Period = factor(Period, levels = c("Acute", "Post-Acute", "Arbitrage", "Wind-down")),
    Variable = factor(Variable, 
                      levels = c("mtm_x_uninsured", "mtm_total", "uninsured_lev"),
                      labels = c("MTM ? Uninsured", "MTM Loss (Total)", "Uninsured Deposits"))
  )

p_btfp_temporal_base <- ggplot(btfp_temp_coefs_base, aes(x = Period, y = Coefficient, fill = Period)) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray30") +
  geom_col(alpha = 0.8, width = 0.7) +
  geom_errorbar(aes(ymin = CI_low, ymax = CI_high), width = 0.2, color = "black") +
  facet_wrap(~ Variable, scales = "free_y", nrow = 1) +
  scale_fill_manual(values = c("Acute" = "#F8766D", "Post-Acute" = "#7CAE00", 
                               "Arbitrage" = "#00BFC4", "Wind-down" = "#C77CFF")) +
  labs(
    title = "BTFP: How Borrowing Determinants Changed Across Crisis Phases",
    subtitle = "Base specification coefficients with 95% confidence intervals",
    x = NULL,
    y = "Coefficient Estimate",
    caption = "Note: LPM estimates. All variables z-standardized."
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold"),
    legend.position = "none",
    axis.text.x = element_text(angle = 45, hjust = 1),
    strip.text = element_text(face = "bold")
  )

print(p_btfp_temporal_base)

save_figure(p_btfp_temporal_base, "Figure_T1_BTFP_Temporal_Base", width = 14, height = 6)

# ------------------------------------------------------------------------------
# PLOT: BTFP Temporal Evolution - Risk Categories (Bar Chart Style)
# ------------------------------------------------------------------------------

btfp_temp_coefs_risk <- extract_temporal_coefs(btfp_temporal_risk, "BTFP", 
                                                key_vars = c("run_risk_2", "run_risk_3", "run_risk_4")) %>%
  mutate(
    Period = factor(Period, levels = c("Acute", "Post-Acute", "Arbitrage", "Wind-down")),
    Variable = factor(Variable, 
                      levels = c("run_risk_2", "run_risk_3", "run_risk_4"),
                      labels = c("Risk 2\n(Low MTM, High Unins)", 
                                 "Risk 3\n(High MTM, Low Unins)", 
                                 "Risk 4\n(High MTM, High Unins)"))
  )

p_btfp_temporal_risk <- ggplot(btfp_temp_coefs_risk, aes(x = Period, y = Coefficient, fill = Period)) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray30") +
  geom_col(alpha = 0.8, width = 0.7) +
  geom_errorbar(aes(ymin = CI_low, ymax = CI_high), width = 0.2, color = "black") +
  facet_wrap(~ Variable, scales = "free_y", nrow = 1) +
  scale_fill_manual(values = c("Acute" = "#F8766D", "Post-Acute" = "#7CAE00", 
                               "Arbitrage" = "#00BFC4", "Wind-down" = "#C77CFF")) +
  labs(
    title = "BTFP: Risk Category Effects Across Crisis Phases",
    subtitle = "Reference: Risk 1 (Low MTM, Low Uninsured)",
    x = NULL,
    y = "Coefficient Estimate",
    caption = "Note: LPM estimates. Risk categories based on sample median splits."
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold"),
    legend.position = "none",
    axis.text.x = element_text(angle = 45, hjust = 1),
    strip.text = element_text(face = "bold")
  )

print(p_btfp_temporal_risk)

save_figure(p_btfp_temporal_risk, "Figure_T2_BTFP_Temporal_Risk", width = 14, height = 6)

# ------------------------------------------------------------------------------
# PLOT : DW Temporal Evolution - Base Model
# ------------------------------------------------------------------------------

dw_temp_coefs_base <- extract_temporal_coefs(dw_temporal_base, "DW") %>%
  mutate(
    Period = factor(Period, levels = c("Pre-BTFP", "Acute")),
    Variable = factor(Variable, 
                      levels = c("mtm_x_uninsured", "mtm_total", "uninsured_lev"),
                      labels = c("MTM ? Uninsured", "MTM Loss (Total)", "Uninsured Deposits"))
  )

p_dw_temporal_base <- ggplot(dw_temp_coefs_base, aes(x = Period, y = Coefficient, fill = Period)) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray30") +
  geom_col(alpha = 0.8, width = 0.6) +
  geom_errorbar(aes(ymin = CI_low, ymax = CI_high), width = 0.15, color = "black") +
  facet_wrap(~ Variable, scales = "free_y", nrow = 1) +
  scale_fill_manual(values = c("Pre-BTFP" = "#F8766D", "Acute" = "#00BFC4")) +
  labs(
    title = "Discount Window: How Borrowing Determinants Changed",
    subtitle = "Pre-BTFP (Mar 1-12) vs Acute (Mar 13 - May 1)",
    x = NULL,
    y = "Coefficient Estimate",
    caption = "Note: LPM estimates. All variables z-standardized."
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold"),
    legend.position = "none",
    strip.text = element_text(face = "bold")
  )

print(p_dw_temporal_base)

save_figure(p_dw_temporal_base, "Figure_T3_DW_Temporal_Base", width = 12, height = 6)

# ------------------------------------------------------------------------------
# PLOT : DW Temporal Evolution - Risk Categories
# ------------------------------------------------------------------------------

dw_temp_coefs_risk <- extract_temporal_coefs(dw_temporal_risk, "DW", 
                                              key_vars = c("run_risk_2", "run_risk_3", "run_risk_4")) %>%
  mutate(
    Period = factor(Period, levels = c("Pre-BTFP", "Acute")),
    Variable = factor(Variable, 
                      levels = c("run_risk_2", "run_risk_3", "run_risk_4"),
                      labels = c("Risk 2\n(Low MTM, High Unins)", 
                                 "Risk 3\n(High MTM, Low Unins)", 
                                 "Risk 4\n(High MTM, High Unins)"))
  )

p_dw_temporal_risk <- ggplot(dw_temp_coefs_risk, aes(x = Period, y = Coefficient, fill = Period)) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray30") +
  geom_col(alpha = 0.8, width = 0.6) +
  geom_errorbar(aes(ymin = CI_low, ymax = CI_high), width = 0.15, color = "black") +
  facet_wrap(~ Variable, scales = "free_y", nrow = 1) +
  scale_fill_manual(values = c("Pre-BTFP" = "#F8766D", "Acute" = "#00BFC4")) +
  labs(
    title = "Discount Window: Risk Category Effects Over Time",
    subtitle = "Reference: Risk 1 (Low MTM, Low Uninsured)",
    x = NULL,
    y = "Coefficient Estimate",
    caption = "Note: LPM estimates. Risk categories based on sample median splits."
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold"),
    legend.position = "none",
    strip.text = element_text(face = "bold")
  )

print(p_dw_temporal_risk)

save_figure(p_dw_temporal_risk, "Figure_T4_DW_Temporal_Risk", width = 12, height = 6)

# ------------------------------------------------------------------------------
# PLOT : Combined Temporal - BTFP Base (Line Plot Alternative)
# ------------------------------------------------------------------------------

p_btfp_temporal_line <- ggplot(btfp_temp_coefs_base, aes(x = Period, y = Coefficient, 
                                                          group = Variable, color = Variable)) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray50") +
  geom_line(linewidth = 1) +
  geom_pointrange(aes(ymin = CI_low, ymax = CI_high), size = 0.8) +
  scale_color_manual(values = c("MTM ? Uninsured" = "#E41A1C", 
                                "MTM Loss (Total)" = "#377EB8", 
                                "Uninsured Deposits" = "#4DAF4A")) +
  labs(
    title = "BTFP: Evolution of Key Determinants Across Crisis Phases",
    subtitle = "How the role of fundamentals and liquidity mismatch changed over time",
    x = NULL,
    y = "Coefficient Estimate (95% CI)",
    color = "Variable",
    caption = "Note: LPM estimates. All variables z-standardized."
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold"),
    legend.position = "bottom",
    axis.text.x = element_text(angle = 45, hjust = 1)
  )

print(p_btfp_temporal_line)

save_figure(p_btfp_temporal_line, "Figure_T5_BTFP_Temporal_Line", width = 12, height = 7)

# ------------------------------------------------------------------------------
# PLOT : Combined Temporal - Risk 4 Comparison (BTFP vs DW)
# ------------------------------------------------------------------------------

# Combine Risk 4 coefficients from both facilities
risk4_temporal <- bind_rows(
  btfp_temp_coefs_risk %>% filter(Variable == "Risk 4\n(High MTM, High Unins)") %>% 
    mutate(Facility = "BTFP"),
  dw_temp_coefs_risk %>% filter(Variable == "Risk 4\n(High MTM, High Unins)") %>% 
    mutate(Facility = "DW")
) %>%
  mutate(Period = as.character(Period))

p_risk4_comparison <- ggplot(risk4_temporal, aes(x = Period, y = Coefficient, fill = Facility)) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray30") +
  geom_col(position = position_dodge(width = 0.7), alpha = 0.8, width = 0.6) +
  geom_errorbar(aes(ymin = CI_low, ymax = CI_high), 
                position = position_dodge(width = 0.7), width = 0.2, color = "black") +
  scale_fill_manual(values = c("BTFP" = "#2166AC", "DW" = "#B2182B")) +
  labs(
    title = "Risk 4 (High MTM, High Uninsured) Effect: BTFP vs DW",
    subtitle = "Comparing facility choice among highest-risk banks across crisis phases",
    x = NULL,
    y = "Coefficient Estimate (95% CI)",
    fill = "Facility",
    caption = "Note: LPM estimates. Reference: Risk 1 (Low MTM, Low Uninsured)."
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold"),
    legend.position = "bottom"
  )

print(p_risk4_comparison)

save_figure(p_risk4_comparison, "Figure_T6_Risk4_BTFP_vs_DW", width = 12, height = 8)
# ==============================================================================
# PLOT 11: The Solvency Shock (Book vs. Adjusted Equity)
# ==============================================================================

# Prepare long data for histogram
plot_data_solvency <- df_acute %>%
  select(idrssd, book_equity_ratio_w, adjusted_equity) %>%
  pivot_longer(cols = c(book_equity_ratio_w, adjusted_equity), 
               names_to = "Metric", values_to = "Value") %>%
  mutate(
    Metric = factor(Metric, 
                    levels = c("book_equity_ratio_w", "adjusted_equity"),
                    labels = c("Book Equity Ratio (Official)", "MTM-Adjusted Equity Ratio (Real)"))
  )

# Calculate means
means <- plot_data_solvency %>%
  group_by(Metric) %>%
  summarise(Mean_Val = mean(Value, na.rm = TRUE))

p_insolvency <- ggplot(plot_data_solvency, aes(x = Value, fill = Metric)) +
  geom_density(alpha = 0.5) +
  geom_vline(data = means, aes(xintercept = Mean_Val, color = Metric), 
             linetype = "dashed", size = 1) +
  # Add "Insolvent" threshold line
  geom_vline(xintercept = 0, linetype = "dotted", color = "black") +
  annotate("text", x = -2, y = 0.02, label = "Technically\nInsolvent", 
           color = "red", hjust = 1, size = 3) +
  scale_fill_manual(values = c("gray50", "#E41A1C")) +
  scale_color_manual(values = c("gray50", "#E41A1C")) +
  labs(
    title = "The Hidden Solvency Shock",
    subtitle = "Distribution of Bank Capital: Book Value vs. MTM-Adjusted Value (2022Q4)",
    x = "Equity / Assets (%)",
    y = "Density",
    caption = "Note: Adjusted Equity = Book Equity - Unrealized Losses on Securities."
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "bottom")

print(p_insolvency)

save_figure(p_insolvency, "Figure_11_Insolvency_Shock", width = 12, height = 8)
# ==============================================================================
# PRE-TRENDS VISUALIZATION (OUTFLOW FRAMING)
# ==============================================================================

pretrends <- df_did_panel %>%
  group_by(period, has_omo) %>%
  summarise(
    mean_outflow = mean(outflow_total_dep, na.rm = TRUE),
    se_outflow = sd(outflow_total_dep, na.rm = TRUE) / sqrt(n()),
    .groups = "drop"
  ) %>%
  mutate(Group = ifelse(has_omo == 1, "Treated (OMO Eligible)", "Control (Ineligible)"))

p_did <- ggplot(pretrends, aes(x = period, y = mean_outflow, color = Group, group = Group)) +
  geom_hline(yintercept = 0, linetype = "solid", color = "gray70") +
  geom_vline(xintercept = "2023Q1", linetype = "dashed", color = "gray40", linewidth = 1) +
  annotate("text", x = "2023Q1", y = max(pretrends$mean_outflow, na.rm = TRUE) + 0.5, 
           label = "Crisis\nStart", hjust = -0.1, size = 3, color = "gray40") +
  geom_line(linewidth = 1) +
  geom_point(size = 3) +
  geom_errorbar(aes(ymin = mean_outflow - 1.96 * se_outflow, 
                    ymax = mean_outflow + 1.96 * se_outflow), width = 0.2) +
  scale_color_manual(values = c("Control (Ineligible)" = "#E41A1C", 
                                "Treated (OMO Eligible)" = "#377EB8")) +
  labs(
    title = "Figure: Parallel Trends in Deposit Outflows",
    subtitle = "Treated (OMO-eligible) vs Control (Ineligible) Banks",
    x = "Quarter", 
    y = "Mean Quarterly Deposit Outflow (%)\n(Positive = Runoff)",
    color = "Group",
    caption = "Note: Parallel trends pre-crisis suggest valid DiD design."
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "bottom", axis.text.x = element_text(angle = 45, hjust = 1))

print(p_did)

save_figure(p_did, "Figure_DiD_Parallel_Trends", width = 12, height = 8)
# ==============================================================================
# CRISIS TIMELINE PLOT
# ==============================================================================

# --- Daily totals ---
btfp_daily <- btfp_loans %>%
  filter(btfp_loan_date >= as.Date("2023-03-01"), btfp_loan_date <= ACUTE_END) %>%
  group_by(date = btfp_loan_date) %>%
  summarise(total_bn = sum(btfp_loan_amount, na.rm = TRUE) / 1e9, .groups = "drop") %>%
  mutate(Facility = "BTFP")

dw_daily <- dw_loans %>%
  filter(dw_loan_date >= as.Date("2023-03-01"),
         dw_loan_date <= min(ACUTE_END, DW_DATA_END)) %>%
  group_by(date = dw_loan_date) %>%
  summarise(total_bn = sum(dw_loan_amount, na.rm = TRUE) / 1e9, .groups = "drop") %>%
  mutate(Facility = "DW")

daily <- bind_rows(btfp_daily, dw_daily)

# --- Plot: lines (no stacking) ---
p_timeline <- ggplot(daily, aes(x = date, y = total_bn, color = Facility)) +
  geom_line(linewidth = 1) +
  geom_point(size = 1.5, alpha = 0.7) +
  geom_vline(xintercept = DATE_MAR10, linetype = "dashed") +
  geom_vline(xintercept = DATE_MAR13, linetype = "dashed") +
  annotate("text", x = DATE_MAR10, y = max(daily$total_bn, na.rm = TRUE) * 0.95,
           label = "SVB fails (Mar 10)", hjust = 1.05, size = 3) +
  annotate("text", x = DATE_MAR13, y = max(daily$total_bn, na.rm = TRUE) * 0.85,
           label = "BTFP announced (Mar 12/13)", hjust = -0.05, size = 3) +
  scale_x_date(date_breaks = "1 week", date_labels = "%b %d") +
  labs(
    title = "Daily Emergency Borrowing During the 2023 Banking Stress",
    subtitle = "Daily borrowing totals by facility (not stacked)",
    x = NULL, y = "Daily borrowing ($ billions)", color = NULL
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "bottom",
        axis.text.x = element_text(angle = 45, hjust = 1))

print(p_timeline)

save_figure(p_timeline, "Figure_Crisis_Timeline", width = 12, height = 8)

# two panel

p_timeline_facet <- ggplot(daily, aes(x = date, y = total_bn)) +
  geom_col(alpha = 0.8) +
  facet_wrap(~Facility, ncol = 1, scales = "fixed") +
  geom_vline(xintercept = DATE_MAR10, linetype = "dashed") +
  geom_vline(xintercept = DATE_MAR13, linetype = "dashed") +
  scale_x_date(date_breaks = "1 week", date_labels = "%b %d") +
  labs(
    title = "Daily Emergency Borrowing During the 2023 Banking Stress",
    subtitle = "Separate panels to avoid visual dominance and confusion",
    x = NULL, y = "Daily borrowing ($ billions)"
  ) +
  theme_minimal(base_size = 12) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

print(p_timeline_facet)

save_figure(p_timeline_facet, "Figure_Crisis_Timeline_Facet", width = 12, height = 7)

# cumulative
daily_cum <- daily %>%
  arrange(Facility, date) %>%
  group_by(Facility) %>%
  mutate(cum_bn = cumsum(replace_na(total_bn, 0))) %>%
  ungroup()

p_cum <- ggplot(daily_cum, aes(x = date, y = cum_bn, color = Facility)) +
  geom_line(linewidth = 1) +
  geom_vline(xintercept = DATE_MAR10, linetype = "dashed") +
  geom_vline(xintercept = DATE_MAR13, linetype = "dashed") +
  scale_x_date(date_breaks = "1 week", date_labels = "%b %d") +
  labs(
    title = "Cumulative Emergency Borrowing During the 2023 Banking Stress",
    subtitle = "Running total of borrowing by facility",
    x = NULL, y = "Cumulative borrowing ($ billions)", color = NULL
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "bottom",
        axis.text.x = element_text(angle = 45, hjust = 1))

print(p_cum)

save_figure(p_cum, "Figure_Crisis_Timeline_Cumulative", width = 12, height = 6)
# ==============================================================================
# RUN RISK LANDSCAPE
# ==============================================================================

df_plot <- df_acute %>%
  mutate(
    user_label = case_when(
      btfp_acute == 1 & dw_acute == 0 ~ "BTFP",
      btfp_acute == 0 & dw_acute == 1 ~ "DW",
      btfp_acute == 1 & dw_acute == 1 ~ "Both",
      TRUE ~ "None"
    )
  )

p_land <- ggplot(df_plot, aes(x = mtm_total, y = uninsured_lev, color = user_label)) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray50") +
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray50") +
  geom_point(alpha = 0.6, size = 2) +
  scale_color_manual(values = c("None" = "gray60", "BTFP" = "#2166AC", 
                                "DW" = "#B2182B", "Both" = "#7570B3")) +
  annotate("text", x = 2, y = 2.5, label = "Risk 4\n(High,High)", color = "red", 
           size = 3, fontface = "bold") +
  annotate("text", x = -2, y = -2, label = "Risk 1\n(Low,Low)", color = "darkgreen", size = 3) +
  labs(
    title = "Figure: Run Risk Landscape",
    x = "MTM Loss (z-score)", y = "Uninsured Leverage (z-score)",
    color = "Facility"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "bottom")

print(p_land)

save_figure(p_land, "Figure_Risk_Landscape", width = 12, height = 8)
# ==============================================================================
# BINSCATTER: PAR BENEFIT VS BORROWING
# ==============================================================================

# --- Create df_borrowers FIRST (needed for intensive margin plot) ---
df_borrowers <- df_acute %>%
  filter(btfp_acute == 1, !is.na(btfp_pct), is.finite(btfp_pct)) %>%
  mutate(
    mtm_incentive = mtm_btfp_raw   # OMO-eligible MTM loss / total assets (%)
  ) %>%
  filter(!is.na(mtm_incentive), is.finite(mtm_incentive))

# ==============================================================================
# PLOT : The Par Benefit Incentive (Intensive Margin)
# ==============================================================================

plot_data_intensive <- df_borrowers %>%
  filter(mtm_incentive < 50) %>%   # keep your outlier trim
  mutate(incentive_bin = ntile(mtm_incentive, 10)) %>%
  group_by(incentive_bin) %>%
  summarise(
    mean_incentive = mean(mtm_incentive, na.rm = TRUE),
    mean_borrowing = mean(btfp_pct, na.rm = TRUE),
    se_borrowing   = sd(btfp_pct, na.rm = TRUE) / sqrt(n()),
    .groups = "drop"
  )

p_intensive <- ggplot(plot_data_intensive, aes(x = mean_incentive, y = mean_borrowing)) +
  geom_smooth(method = "lm", color = "gray80", linetype = "dashed", se = FALSE) +
  geom_point(size = 3, color = "#2166AC") +
  geom_errorbar(aes(ymin = mean_borrowing - 1.96 * se_borrowing,
                    ymax = mean_borrowing + 1.96 * se_borrowing),
                width = 0.5, color = "#2166AC") +
  labs(
    title = "The Par Valuation Incentive: Par Benefit vs. Borrowing Volume",
    subtitle = "Do banks with deeper collateral discounts borrow more?",
    x = "Par Benefit (MTM Loss Rate on Eligible Securities, %)",
    y = "BTFP Reliance (Loan / Total Assets, %)",
    caption = "Note: Binned scatterplot of BTFP borrowers. Error bars represent 95% CI."
  ) +
  theme_classic(base_size = 12)

print(p_intensive)

save_figure(p_intensive, "Figure_12_Intensive_Margin_Binscatter", width = 10, height = 8)

# ==============================================================================
# PLOT: RUN-DRIVEN BORROWING (Binscatter)
# ==============================================================================
df_acute_outflows <- df_acute %>%
  select(idrssd, uninsured_outflow_raw, uninsured_outflow, total_outflow_raw, total_outflow) %>%
  distinct()

# --- Plot 3 expects uninsured_outflow_z. Create it from existing columns. ---
df_btfp_outflows <- df_acute %>%
  mutate(
    uninsured_outflow_z = dplyr::case_when(
      "uninsured_outflow" %in% names(.) ~ uninsured_outflow,          # already z-scored
      "uninsured_outflow_raw" %in% names(.) ~ as.numeric(scale(uninsured_outflow_raw)), # fallback
      TRUE ~ NA_real_
    )
  ) %>%
  filter(!is.na(uninsured_outflow_z)) %>%
  select(idrssd, btfp_acute, uninsured_outflow_z)



plot_data_bins <- df_btfp_outflows %>%
  mutate(outflow_bin = ntile(uninsured_outflow_z, 10)) %>%
  group_by(outflow_bin) %>%
  summarise(
    mean_outflow = mean(uninsured_outflow_z, na.rm = TRUE),
    prob_btfp    = mean(btfp_acute, na.rm = TRUE),
    se_btfp      = sd(btfp_acute, na.rm = TRUE) / sqrt(n()),
    .groups = "drop"
  )

p3 <- ggplot(plot_data_bins, aes(x = mean_outflow, y = prob_btfp)) +
  geom_smooth(method = "lm", color = "gray80", se = FALSE, linetype = "dashed") +
  geom_point(size = 3, color = "darkblue") +
  geom_errorbar(aes(ymin = prob_btfp - 1.96 * se_btfp,
                    ymax = prob_btfp + 1.96 * se_btfp),
                width = 0.1, color = "darkblue") +
  labs(
    title = "Figure: Deposit Outflows vs. BTFP Usage Probability",
    subtitle = "Banks with higher uninsured runoff were significantly more likely to use BTFP",
    x = "Uninsured Deposit Outflow (Z-Score)",
    y = "Probability of BTFP Usage"
  ) +
  theme_classic()

print(p3)

save_figure(p3, "Fig3_Outflow_Binscatter", width = 12, height = 8)
# ==============================================================================
# FHLB TEMPORAL ANALYSIS
# ==============================================================================

# FHLB analysis uses call report data (quarterly)
# Key variables: abnormal_fhlb_borrowing_10pct, abnormal_fhlb_borrowing_5pct
# change_fhlb_adv_fwd_q, change_fhlb_adv_fwd_q_zscore

# Get FHLB data across periods
df_fhlb_temporal <- call_q %>%
  filter(!idrssd %in% excluded_banks,
         period %in% c("2022Q3", "2022Q4", "2023Q1", "2023Q2", "2023Q3", "2023Q4")) %>%
  select(
    idrssd, period,
    fhlb_adv, fhlb_to_total_asset,
    abnormal_fhlb_borrowing_10pct, abnormal_fhlb_borrowing_5pct,
    change_fhlb_adv_fwd_q, change_fhlb_adv_fwd_q_zscore,
    total_asset, mtm_loss_to_total_asset, uninsured_deposit_to_total_asset,
    cash_to_total_asset, book_equity_to_total_asset
  )

# Summary by period
fhlb_summary <- df_fhlb_temporal %>%
  group_by(period) %>%
  summarise(
    n_banks = n(),
    pct_abnormal_10 = mean(abnormal_fhlb_borrowing_10pct, na.rm = TRUE) * 100,
    pct_abnormal_5 = mean(abnormal_fhlb_borrowing_5pct, na.rm = TRUE) * 100,
    mean_fhlb_ratio = mean(fhlb_to_total_asset, na.rm = TRUE),
    total_fhlb_bn = sum(fhlb_adv, na.rm = TRUE) / 1e9,
    .groups = "drop"
  )

cat("\n=== FHLB USAGE BY PERIOD ===\n")
## 
## === FHLB USAGE BY PERIOD ===
print(fhlb_summary)
## # A tibble: 6 × 6
##   period n_banks pct_abnormal_10 pct_abnormal_5 mean_fhlb_ratio total_fhlb_bn
##   <chr>    <int>           <dbl>          <dbl>           <dbl>         <dbl>
## 1 2022Q3    4724            9.46           7.45            2.16         0.345
## 2 2022Q4    4696            6.92           5.09            2.72         0.447
## 3 2023Q1    4673            7.49           6.53            2.87         0.647
## 4 2023Q2    4644            5.08           4.24            3.22         0.538
## 5 2023Q3    4604            4.60           3.76            3.23         0.463
## 6 2023Q4    4593            3.42           2.59            3.21         0.445
# Plot FHLB abnormal borrowing over time
p_fhlb_temporal <- ggplot(fhlb_summary, aes(x = period)) +
  geom_bar(aes(y = pct_abnormal_10), stat = "identity", fill = "#7570B3", alpha = 0.7) +
  geom_line(aes(y = pct_abnormal_5, group = 1), color = "#E7298A", linewidth = 1.2) +
  geom_point(aes(y = pct_abnormal_5), color = "#E7298A", size = 3) +
  geom_vline(xintercept = "2023Q1", linetype = "dashed", color = "gray40") +
  annotate("text", x = "2023Q1", y = max(fhlb_summary$pct_abnormal_10) + 2, 
           label = "Crisis\nStart", hjust = -0.1, size = 3) +
  labs(
    title = "FHLB Abnormal Borrowing Over Time",
    subtitle = "Percentage of banks with abnormal FHLB advances",
    x = "Quarter",
    y = "% of Banks",
    caption = "Bar: 10% threshold. Line: 5% threshold."
  ) +
  theme_minimal(base_size = 12) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

print(p_fhlb_temporal)

save_figure(p_fhlb_temporal, "Figure_fhlb_temporal", width = 12, height = 8)
# ==============================================================================
# SOLVENCY HETEROGENEITY (ACUTE): TABLES + BASELINE MODELS
# ==============================================================================
# 1. Ensure the generic formula builder exists
if (!exists("build_formula")) {
  build_formula <- function(lhs, rhs_str) {
    # Standard controls used across all models
    controls <- paste(
      "ln_assets", "cash_ratio", "securities_ratio", "loan_ratio", 
      "book_equity_ratio", "tier1_ratio", "roa", "fhlb_ratio", 
      "loan_to_deposit", "wholesale", 
      sep = " + "
    )
    # Combine specific RHS vars + controls (cross-sectional, no FE)
    full_rhs <- paste(rhs_str, "+", controls)
    as.formula(paste(lhs, "~", full_rhs))
  }
}

# 2. Define the specific formula wrappers (The missing functions)
#    These create the formulas for the 4 specifications in run_4spec
f_base <- function(dv) build_formula(dv, "mtm_total + uninsured_lev + mtm_x_uninsured")
f_r1   <- function(dv) build_formula(dv, "run_risk_1")
f_r12  <- function(dv) build_formula(dv, "run_risk_1 + run_risk_2") 
f_r234 <- function(dv) build_formula(dv, "run_risk_2 + run_risk_3 + run_risk_4")

# ------------------------------------------------------------------------------
# UTILITY: Define facility_group for descriptive tables
# ------------------------------------------------------------------------------
df_base <- df_acute %>%
  mutate(
    facility_group = case_when(
      user_group == "BTFP_Only" ~ "BTFP_Only",
      user_group == "DW_Only"   ~ "DW_Only",
      user_group == "Both"      ~ "Both",
      non_user == 1             ~ "Pure Non-User",
      TRUE                      ~ "Neither (non-pure)"
    ),
    facility_group = factor(
      facility_group,
      levels = c("BTFP_Only", "DW_Only", "Both", "Pure Non-User", "Neither (non-pure)")
    )
  )

# ------------------------------------------------------------------------------
# DEFINITIONS: Solvency Indicators
# ------------------------------------------------------------------------------
solvency_indicators <- tibble::tribble(
  ~solv_var,             ~solv_label,
  "mtm_insolvent",       "MTM Insolvent (Adj. Equity < 0)",
  "insolvent_idcr_s50",  "IDCR Insolvent (50% run, IDCR<0)",
  "insolvent_idcr_s100", "IDCR Insolvent (100% run, IDCR<0)"
)

# ------------------------------------------------------------------------------
# FUNCTIONS
# ------------------------------------------------------------------------------

# 1. Descriptive: Borrowers vs Non-Users (CONSISTENT with regression sample)
#    Now takes `dv` parameter to match the regression sample exactly
desc_borrower_vs_nonuser <- function(d, solv_var, dv = NULL) {
  
  # If dv is provided, filter to that specific borrower type + pure non-users
  # This ensures consistency with regression sample
  if (!is.null(dv)) {
    d <- d %>% filter(.data[[dv]] == 1 | non_user == 1)
    borrower_label <- paste0(toupper(gsub("_acute|_post|_arb|_wind", "", dv)), " Borrower")
    d <- d %>% mutate(
      borrower_group = case_when(
        .data[[dv]] == 1 ~ borrower_label,
        non_user == 1    ~ "Pure Non-User",
        TRUE             ~ NA_character_
      )
    )
  } else {
    # Fallback to any_fed for backward compatibility
    d <- d %>% mutate(
      borrower_group = case_when(
        any_fed == 1  ~ "Any Fed Borrower",
        non_user == 1 ~ "Pure Non-User",
        TRUE          ~ NA_character_
      )
    ) %>% filter(!is.na(borrower_group))
  }
  
  d %>%
    mutate(
      solvency_group = if_else(.data[[solv_var]] == 1, "Insolvent", "Solvent")
    ) %>%
    filter(!is.na(borrower_group)) %>%
    group_by(solvency_group, borrower_group) %>%
    summarise(
      N = n(),
      `MTM Loss (% assets)`      = mean(mtm_total_raw, na.rm = TRUE),
      `Uninsured Lev (% assets)` = mean(uninsured_lev_raw, na.rm = TRUE),
      `Adj. Equity (%)`          = mean(adjusted_equity_raw, na.rm = TRUE),
      `IDCR (50%)`               = mean(idcr_1_raw, na.rm = TRUE),
      `IDCR (100%)`              = mean(idcr_2_raw, na.rm = TRUE),
      `Log Assets`               = mean(ln_assets_raw, na.rm = TRUE),
      `Cash/Assets`              = mean(cash_ratio_raw, na.rm = TRUE),
      `Loan/Deposit`             = mean(loan_to_deposit_raw, na.rm = TRUE),
      `Book Equity/Assets`       = mean(book_equity_ratio_raw, na.rm = TRUE),
      `Wholesale Funding (%)`    = mean(wholesale_raw, na.rm = TRUE),
      `ROA`                      = mean(roa_raw, na.rm = TRUE),
      .groups = "drop"
    ) %>%
    mutate(across(where(is.numeric), ~ round(.x, 3))) %>%
    arrange(desc(solvency_group), borrower_group)
}

# 2. Descriptive: By Facility
desc_by_facility_within_solv <- function(d, solv_var) {
  d %>%
    mutate(
      solvency_group = if_else(.data[[solv_var]] == 1, "Insolvent", "Solvent")
    ) %>%
    filter(facility_group %in% c("BTFP_Only", "DW_Only", "Both", "Pure Non-User")) %>%
    group_by(solvency_group, facility_group) %>%
    summarise(
      N = n(),
      `MTM Loss (% assets)`      = mean(mtm_total_raw, na.rm = TRUE),
      `Uninsured Lev (% assets)` = mean(uninsured_lev_raw, na.rm = TRUE),
      `Adj. Equity (%)`          = mean(adjusted_equity_raw, na.rm = TRUE),
      `Log Assets`               = mean(ln_assets_raw, na.rm = TRUE),
      `Cash/Assets`              = mean(cash_ratio_raw, na.rm = TRUE),
      `Loan/Deposit`             = mean(loan_to_deposit_raw, na.rm = TRUE),
      `Book Equity/Assets`       = mean(book_equity_ratio_raw, na.rm = TRUE),
      `Wholesale Funding (%)`    = mean(wholesale_raw, na.rm = TRUE),
      `ROA`                      = mean(roa_raw, na.rm = TRUE),
      .groups = "drop"
    ) %>%
    mutate(across(where(is.numeric), ~ round(.x, 3))) %>%
    arrange(desc(solvency_group), facility_group)
}

# 3. Split Model Runner
run_split_models <- function(d, dv, solv_var) {
  d_samp <- d %>% filter(.data[[dv]] == 1 | non_user == 1)
  
  # Return separate dataframes
  list(
    d_solvent   = d_samp %>% filter(.data[[solv_var]] == 0),
    d_insolvent = d_samp %>% filter(.data[[solv_var]] == 1)
  )
}

# ------------------------------------------------------------------------------
# MAIN LOOP
# ------------------------------------------------------------------------------
for (k in 1:nrow(solvency_indicators)) {
  
  solv_var   <- solvency_indicators$solv_var[k]
  solv_label <- solvency_indicators$solv_label[k]
  
  
  # ============================================================================
  # (A) Table: Borrowers vs Pure Non-Users
  # ============================================================================
  data_A    <- desc_borrower_vs_nonuser(df_base, solv_var)
  caption_A <- paste0("Acute: Borrowers vs Non-Users | ", solv_label)
  
  # Display HTML table
  html_A <- data_A %>%
    kable(format = "html", caption = caption_A, align = "llr")
  cat(as.character(html_A))
  
  # Save LaTeX
  tex_A <- data_A %>%
    kable(format = "latex", booktabs = TRUE, caption = caption_A, align = "llr")
  safe_writeLines(as.character(tex_A), con = file.path(TABLE_PATH, paste0("Table_Desc_Borrowers_", solv_var, ".tex")))
  
  # ============================================================================
  # (B) Table: By Facility Group
  # ============================================================================
  data_B    <- desc_by_facility_within_solv(df_base, solv_var)
  caption_B <- paste0("Acute: Facility Groups | ", solv_label)
  
  # Display HTML table
  html_B <- data_B %>%
    kable(format = "html", caption = caption_B, align = "llr")
  cat(as.character(html_B))
  
  # Save LaTeX
  tex_B <- data_B %>%
    kable(format = "latex", booktabs = TRUE, caption = caption_B, align = "llr")
  safe_writeLines(as.character(tex_B), con = file.path(TABLE_PATH, paste0("Table_Desc_Facility_", solv_var, ".tex")))
  
  # ============================================================================
  # (C) Baseline Models (BTFP & DW)
  # ============================================================================
  for (dv in c("btfp_acute", "dw_acute")) {
    
    dv_label <- toupper(gsub("_acute", "", dv))  # "BTFP" or "DW"
    
    # 1. Get split data (consistent with regression sample)
    res_data <- run_split_models(df_base, dv, solv_var)
    
    # 2. Create facility-specific descriptive table (MATCHES regression sample)
    data_dv <- desc_borrower_vs_nonuser(df_base, solv_var, dv = dv)
    caption_dv <- paste0("Acute ", dv_label, ": Borrowers vs Non-Users | ", solv_label)
    
    html_dv <- data_dv %>%
      kable(format = "html", caption = caption_dv, align = "llr")
    cat(as.character(html_dv))
    
    tex_dv <- data_dv %>%
      kable(format = "latex", booktabs = TRUE, caption = caption_dv, align = "llr")
    safe_writeLines(as.character(tex_dv), con = file.path(TABLE_PATH, paste0("Table_Desc_", dv, "_", solv_var, ".tex")))
    
    # 3. Run models immediately (avoids complex list nesting issues)
    m_solvent   <- run_4spec(res_data$d_solvent, dv, "lpm")
    m_insolvent <- run_4spec(res_data$d_insolvent, dv, "lpm")
    
    # 4. Get row additions
    rows_sol <- add_n_rows(res_data$d_solvent, dv, n_models = 4)
    rows_ins <- add_n_rows(res_data$d_insolvent, dv, n_models = 4)
    
    # --- Panel A: Solvent Banks ---
    title_sol <- paste0(dv, " (Solvent) | ", solv_label)
    f_reg_sol <- file.path(TABLE_PATH, paste0("Reg_", dv, "_Solvent_", solv_var))
    
    # Display gt table
    ms_sol <- modelsummary(
      m_solvent,
      stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
      coef_map = COEF_MAP, gof_map = gof_lpm, add_rows = rows_sol,
      title = title_sol, output = "kableExtra"
    )
    print(ms_sol)
    
    # Save LaTeX
    modelsummary(
      m_solvent,
      stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
      coef_map = COEF_MAP, gof_map = gof_lpm, add_rows = rows_sol,
      title = title_sol, output = paste0(f_reg_sol, ".tex")
    )
    
    # --- Panel B: Insolvent Banks ---
    title_ins <- paste0(dv, " (Insolvent) | ", solv_label)
    f_reg_ins <- file.path(TABLE_PATH, paste0("Reg_", dv, "_Insolvent_", solv_var))
    
    # Display gt table
    ms_ins <- modelsummary(
      m_insolvent,
      stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
      coef_map = COEF_MAP, gof_map = gof_lpm, add_rows = rows_ins,
      title = title_ins, output = "kableExtra"
    )
    print(ms_ins)
    
    # Save LaTeX
    modelsummary(
      m_insolvent,
      stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
      coef_map = COEF_MAP, gof_map = gof_lpm, add_rows = rows_ins,
      title = title_ins, output = paste0(f_reg_ins, ".tex")
    )
  }
}
Acute: Borrowers vs Non-Users | MTM Insolvent (Adj. Equity < 0)
solvency_group borrower_group N MTM Loss (% assets) Uninsured Lev (% assets) Adj. Equity (%) IDCR (50%) IDCR (100%) Log Assets Cash/Assets Loan/Deposit Book Equity/Assets Wholesale Funding (%) ROA
Solvent Any Fed Borrower 572 5.335 27.56 4.278 0.390 0.097 13.91 5.655 78.45 9.613 1.545 1.154
Solvent Pure Non-User 2673 4.791 22.53 6.872 2.324 2.064 12.66 9.765 71.83 11.663 0.849 1.280
Insolvent Any Fed Borrower 189 7.897 26.32 -2.682 0.276 0.039 13.39 4.122 60.39 5.214 1.659 0.925
Insolvent Pure Non-User 602 7.748 23.75 -2.243 1.315 0.166 12.62 5.722 57.14 5.506 0.948 0.898
Pure Non-User 10 28.32 11.19 23.250 139.78 45.108 4.146 -5.240
Acute: Facility Groups | MTM Insolvent (Adj. Equity < 0)
solvency_group facility_group N MTM Loss (% assets) Uninsured Lev (% assets) Adj. Equity (%) Log Assets Cash/Assets Loan/Deposit Book Equity/Assets Wholesale Funding (%) ROA
Solvent BTFP_Only 256 5.435 26.47 4.047 13.67 4.943 77.84 9.482 1.553 1.129
Solvent DW_Only 242 5.254 26.80 4.649 13.91 6.563 79.44 9.903 1.442 1.180
Solvent Both 74 5.256 33.81 3.860 14.77 5.145 77.36 9.116 1.850 1.158
Solvent Pure Non-User 2673 4.791 22.53 6.872 12.66 9.765 71.83 11.663 0.849 1.280
Insolvent BTFP_Only 112 7.885 25.60 -2.691 13.24 4.127 60.17 5.193 1.437 0.913
Insolvent DW_Only 57 7.844 27.04 -2.437 13.57 4.456 60.51 5.408 2.073 0.969
Insolvent Both 20 8.113 28.30 -3.332 13.73 3.144 61.31 4.782 1.717 0.872
Insolvent Pure Non-User 602 7.748 23.75 -2.243 12.62 5.722 57.14 5.506 0.948 0.898
Pure Non-User 10 28.32 11.19 23.250 139.78 45.108 4.146 -5.240
Acute BTFP: Borrowers vs Non-Users | MTM Insolvent (Adj. Equity < 0)
solvency_group borrower_group N MTM Loss (% assets) Uninsured Lev (% assets) Adj. Equity (%) IDCR (50%) IDCR (100%) Log Assets Cash/Assets Loan/Deposit Book Equity/Assets Wholesale Funding (%) ROA
Solvent BTFP Borrower 330 5.395 28.12 4.005 0.427 0.117 13.92 4.988 77.73 9.400 1.620 1.136
Solvent Pure Non-User 2673 4.791 22.53 6.872 2.324 2.064 12.66 9.765 71.83 11.663 0.849 1.280
Insolvent BTFP Borrower 132 7.919 26.00 -2.788 0.269 0.038 13.31 3.978 60.34 5.131 1.479 0.907
Insolvent Pure Non-User 602 7.748 23.75 -2.243 1.315 0.166 12.62 5.722 57.14 5.506 0.948 0.898
Pure Non-User 10 28.32 11.19 23.250 139.78 45.108 4.146 -5.240
btfp_acute (Solvent) | MTM Insolvent (Adj. Equity < 0)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.026***
(0.007)
Uninsured Lev (z) 0.026***
(0.008)
MTM ? Uninsured 0.019***
(0.006)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.010 −0.025*
(0.011) (0.013)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.034** −0.003
(0.015) (0.014)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.002
(0.014)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.070***
(0.021)
Log(Assets) (z) 0.061*** 0.069*** 0.071*** 0.066***
(0.008) (0.008) (0.008) (0.008)
Cash Ratio (z) −0.022*** −0.027*** −0.023*** −0.024***
(0.005) (0.005) (0.005) (0.005)
Loan/Deposit (z) −0.009 −0.014** −0.012** −0.010*
(0.006) (0.006) (0.006) (0.006)
Book Equity (z) −0.007* −0.011*** −0.012*** −0.010**
(0.004) (0.004) (0.004) (0.004)
Wholesale (z) 0.026*** 0.025*** 0.025*** 0.025***
(0.007) (0.007) (0.007) (0.007)
ROA (z) −0.001 −0.001 0.001 −0.001
(0.005) (0.005) (0.005) (0.005)
Num.Obs. 3003 3003 3003 3003
R2 0.090 0.083 0.085 0.090
R2 Adj. 0.087 0.081 0.083 0.087
N (btfp_acute=1) 330.000 330.000 330.000 330.000
N (Sample) 3003.000 3003.000 3003.000 3003.000
* p < 0.1, ** p < 0.05, *** p < 0.01
btfp_acute (Insolvent) | MTM Insolvent (Adj. Equity < 0)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.029
(0.026)
Uninsured Lev (z) −0.021
(0.026)
MTM ? Uninsured 0.044*
(0.023)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.061 −0.056
(0.063) (0.064)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured 0.058 0.118
(0.093) (0.104)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.030
(0.066)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.077
(0.065)
Log(Assets) (z) 0.099*** 0.111*** 0.112*** 0.105***
(0.021) (0.020) (0.020) (0.020)
Cash Ratio (z) −0.059*** −0.063*** −0.065*** −0.067***
(0.020) (0.019) (0.020) (0.021)
Loan/Deposit (z) 0.018 0.007 0.007 0.014
(0.020) (0.019) (0.019) (0.019)
Book Equity (z) −0.127*** −0.122*** −0.119*** −0.116***
(0.040) (0.038) (0.038) (0.038)
Wholesale (z) 0.017 0.017 0.017 0.018
(0.014) (0.014) (0.014) (0.014)
ROA (z) −0.034** −0.032** −0.033** −0.036**
(0.015) (0.015) (0.015) (0.015)
Num.Obs. 734 734 734 734
R2 0.093 0.086 0.086 0.089
R2 Adj. 0.082 0.077 0.076 0.078
N (btfp_acute=1) 132.000 132.000 132.000 132.000
N (Sample) 734.000 734.000 734.000 734.000
* p < 0.1, ** p < 0.05, *** p < 0.01
Acute DW: Borrowers vs Non-Users | MTM Insolvent (Adj. Equity < 0)
solvency_group borrower_group N MTM Loss (% assets) Uninsured Lev (% assets) Adj. Equity (%) IDCR (50%) IDCR (100%) Log Assets Cash/Assets Loan/Deposit Book Equity/Assets Wholesale Funding (%) ROA
Solvent DW Borrower 316 5.255 28.44 4.464 0.417 0.104 14.11 6.231 78.95 9.719 1.538 1.175
Solvent Pure Non-User 2673 4.791 22.53 6.872 2.324 2.064 12.66 9.765 71.83 11.663 0.849 1.280
Insolvent DW Borrower 77 7.914 27.37 -2.669 0.300 0.049 13.62 4.115 60.71 5.245 1.981 0.944
Insolvent Pure Non-User 602 7.748 23.75 -2.243 1.315 0.166 12.62 5.722 57.14 5.506 0.948 0.898
Pure Non-User 10 28.32 11.19 23.250 139.78 45.108 4.146 -5.240
dw_acute (Solvent) | MTM Insolvent (Adj. Equity < 0)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.024***
(0.007)
Uninsured Lev (z) 0.016**
(0.008)
MTM ? Uninsured 0.017***
(0.006)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.016 −0.032**
(0.011) (0.013)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.036** 0.000
(0.015) (0.014)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.013
(0.014)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.064***
(0.020)
Log(Assets) (z) 0.082*** 0.085*** 0.087*** 0.083***
(0.008) (0.008) (0.008) (0.008)
Cash Ratio (z) −0.001 −0.006 −0.002 −0.002
(0.005) (0.005) (0.005) (0.005)
Loan/Deposit (z) −0.004 −0.007 −0.005 −0.004
(0.006) (0.006) (0.006) (0.006)
Book Equity (z) −0.002 −0.004 −0.005 −0.003
(0.005) (0.004) (0.004) (0.004)
Wholesale (z) 0.021*** 0.021*** 0.020*** 0.021***
(0.007) (0.007) (0.007) (0.007)
ROA (z) 0.001 0.001 0.003 0.001
(0.005) (0.005) (0.005) (0.005)
Num.Obs. 2989 2989 2989 2989
R2 0.100 0.095 0.097 0.100
R2 Adj. 0.097 0.093 0.095 0.097
N (dw_acute=1) 316.000 316.000 316.000 316.000
N (Sample) 2989.000 2989.000 2989.000 2989.000
* p < 0.1, ** p < 0.05, *** p < 0.01
dw_acute (Insolvent) | MTM Insolvent (Adj. Equity < 0)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.015
(0.023)
Uninsured Lev (z) −0.015
(0.024)
MTM ? Uninsured 0.039*
(0.020)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.002 −0.002
(0.066) (0.066)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.057** −0.052
(0.027) (0.063)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.015
(0.068)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.017
(0.066)
Log(Assets) (z) 0.093*** 0.104*** 0.103*** 0.099***
(0.020) (0.019) (0.019) (0.019)
Cash Ratio (z) −0.029 −0.032* −0.030* −0.032*
(0.018) (0.017) (0.017) (0.017)
Loan/Deposit (z) 0.012 0.004 0.003 0.007
(0.017) (0.016) (0.016) (0.017)
Book Equity (z) −0.066* −0.066** −0.069** −0.067**
(0.034) (0.031) (0.031) (0.031)
Wholesale (z) 0.017 0.017 0.016 0.017
(0.014) (0.014) (0.014) (0.014)
ROA (z) −0.024* −0.021 −0.021 −0.023*
(0.014) (0.013) (0.013) (0.013)
Num.Obs. 679 679 679 679
R2 0.097 0.087 0.088 0.090
R2 Adj. 0.085 0.078 0.077 0.078
N (dw_acute=1) 77.000 77.000 77.000 77.000
N (Sample) 679.000 679.000 679.000 679.000
* p < 0.1, ** p < 0.05, *** p < 0.01
Acute: Borrowers vs Non-Users | IDCR Insolvent (50% run, IDCR<0)
solvency_group borrower_group N MTM Loss (% assets) Uninsured Lev (% assets) Adj. Equity (%) IDCR (50%) IDCR (100%) Log Assets Cash/Assets Loan/Deposit Book Equity/Assets Wholesale Funding (%) ROA
Solvent Any Fed Borrower 724 6.040 27.564 2.463 0.389 0.106 13.76 5.183 73.85 8.503 1.590 1.086
Solvent Pure Non-User 3112 5.417 22.995 4.433 2.235 1.800 12.67 8.823 69.63 9.850 0.897 1.079
Insolvent Any Fed Borrower 37 4.625 21.127 4.242 -0.183 -0.368 14.33 7.055 76.31 8.866 1.229 1.319
Insolvent Pure Non-User 132 4.282 22.468 4.991 -0.164 -0.366 12.92 9.247 73.59 9.273 0.365 0.630
Pure Non-User 41 1.534 6.906 82.786 10.52 26.862 34.09 74.755 1.011 11.373
Acute: Facility Groups | IDCR Insolvent (50% run, IDCR<0)
solvency_group facility_group N MTM Loss (% assets) Uninsured Lev (% assets) Adj. Equity (%) Log Assets Cash/Assets Loan/Deposit Book Equity/Assets Wholesale Funding (%) ROA
Solvent BTFP_Only 355 6.219 26.529 1.945 13.52 4.662 72.58 8.164 1.527 1.047
Solvent DW_Only 278 5.845 27.162 3.204 13.82 5.970 75.56 9.049 1.591 1.131
Solvent Both 91 5.939 32.830 2.215 14.51 4.812 73.60 8.154 1.835 1.105
Solvent Pure Non-User 3112 5.417 22.995 4.433 12.67 8.823 69.63 9.850 0.897 1.079
Insolvent BTFP_Only 13 5.133 17.352 3.387 14.09 5.588 69.38 8.519 1.271 1.521
Insolvent DW_Only 21 4.460 22.665 4.544 14.24 8.697 79.46 9.004 1.177 1.261
Insolvent Both 3 3.577 26.708 5.829 15.93 1.908 84.35 9.406 1.412 0.852
Insolvent Pure Non-User 132 4.282 22.468 4.991 12.92 9.247 73.59 9.273 0.365 0.630
Pure Non-User 41 1.534 6.906 82.786 10.52 26.862 34.09 74.755 1.011 11.373
Acute BTFP: Borrowers vs Non-Users | IDCR Insolvent (50% run, IDCR<0)
solvency_group borrower_group N MTM Loss (% assets) Uninsured Lev (% assets) Adj. Equity (%) IDCR (50%) IDCR (100%) Log Assets Cash/Assets Loan/Deposit Book Equity/Assets Wholesale Funding (%) ROA
Solvent BTFP Borrower 446 6.162 27.815 2.000 0.403 0.111 13.72 4.693 72.78 8.162 1.590 1.059
Solvent Pure Non-User 3112 5.417 22.995 4.433 2.235 1.800 12.67 8.823 69.63 9.850 0.897 1.079
Insolvent BTFP Borrower 16 4.841 19.107 3.845 -0.212 -0.369 14.43 4.898 72.18 8.686 1.297 1.396
Insolvent Pure Non-User 132 4.282 22.468 4.991 -0.164 -0.366 12.92 9.247 73.59 9.273 0.365 0.630
Pure Non-User 41 1.534 6.906 82.786 10.52 26.862 34.09 74.755 1.011 11.373
btfp_acute (Solvent) | IDCR Insolvent (50% run, IDCR<0)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.024***
(0.006)
Uninsured Lev (z) 0.023***
(0.007)
MTM ? Uninsured 0.017***
(0.005)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.014 −0.027**
(0.011) (0.013)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.034** −0.000
(0.014) (0.014)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.003
(0.014)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.066***
(0.017)
Log(Assets) (z) 0.065*** 0.074*** 0.076*** 0.070***
(0.008) (0.007) (0.007) (0.007)
Cash Ratio (z) −0.023*** −0.030*** −0.025*** −0.026***
(0.005) (0.005) (0.005) (0.005)
Loan/Deposit (z) −0.005 −0.012** −0.010* −0.007
(0.006) (0.006) (0.006) (0.006)
Book Equity (z) −0.014*** −0.021*** −0.020*** −0.017***
(0.005) (0.005) (0.005) (0.005)
Wholesale (z) 0.024*** 0.023*** 0.023*** 0.023***
(0.006) (0.006) (0.006) (0.006)
ROA (z) −0.007 −0.007 −0.005 −0.008
(0.005) (0.005) (0.005) (0.005)
Num.Obs. 3558 3558 3558 3558
R2 0.088 0.082 0.083 0.087
R2 Adj. 0.086 0.080 0.081 0.085
N (btfp_acute=1) 446.000 446.000 446.000 446.000
N (Sample) 3558.000 3558.000 3558.000 3558.000
* p < 0.1, ** p < 0.05, *** p < 0.01
btfp_acute (Insolvent) | IDCR Insolvent (50% run, IDCR<0)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.040
(0.026)
Uninsured Lev (z) −0.021
(0.030)
MTM ? Uninsured −0.007
(0.019)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.006 −0.041
(0.054) (0.068)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.086 −0.045
(0.064) (0.059)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.044
(0.073)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.036
(0.102)
Log(Assets) (z) 0.095*** 0.086*** 0.094*** 0.094***
(0.031) (0.030) (0.031) (0.031)
Cash Ratio (z) −0.040* −0.055** −0.051** −0.051**
(0.023) (0.022) (0.022) (0.022)
Loan/Deposit (z) −0.039* −0.039* −0.040* −0.041*
(0.023) (0.023) (0.023) (0.023)
Book Equity (z) 0.027 0.004 0.018 0.018
(0.026) (0.025) (0.025) (0.025)
Wholesale (z) 0.094** 0.087* 0.086* 0.086*
(0.047) (0.050) (0.048) (0.048)
ROA (z) −0.001 −0.005 −0.008 −0.008
(0.025) (0.026) (0.025) (0.025)
Num.Obs. 148 148 148 148
R2 0.179 0.161 0.171 0.171
R2 Adj. 0.126 0.119 0.123 0.117
N (btfp_acute=1) 16.000 16.000 16.000 16.000
N (Sample) 148.000 148.000 148.000 148.000
* p < 0.1, ** p < 0.05, *** p < 0.01
Acute DW: Borrowers vs Non-Users | IDCR Insolvent (50% run, IDCR<0)
solvency_group borrower_group N MTM Loss (% assets) Uninsured Lev (% assets) Adj. Equity (%) IDCR (50%) IDCR (100%) Log Assets Cash/Assets Loan/Deposit Book Equity/Assets Wholesale Funding (%) ROA
Solvent DW Borrower 369 5.869 28.560 2.960 0.431 0.124 13.99 5.684 75.07 8.828 1.652 1.124
Solvent Pure Non-User 3112 5.417 22.995 4.433 2.235 1.800 12.67 8.823 69.63 9.850 0.897 1.079
Insolvent DW Borrower 24 4.350 23.171 4.705 -0.166 -0.377 14.46 7.849 80.07 9.054 1.206 1.210
Insolvent Pure Non-User 132 4.282 22.468 4.991 -0.164 -0.366 12.92 9.247 73.59 9.273 0.365 0.630
Pure Non-User 41 1.534 6.906 82.786 10.52 26.862 34.09 74.755 1.011 11.373
dw_acute (Solvent) | IDCR Insolvent (50% run, IDCR<0)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.017***
(0.006)
Uninsured Lev (z) 0.016**
(0.007)
MTM ? Uninsured 0.018***
(0.005)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.013 −0.024**
(0.011) (0.012)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.028** 0.001
(0.014) (0.014)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.004
(0.012)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.055***
(0.016)
Log(Assets) (z) 0.081*** 0.086*** 0.087*** 0.083***
(0.008) (0.007) (0.007) (0.007)
Cash Ratio (z) −0.003 −0.008 −0.003 −0.004
(0.005) (0.005) (0.005) (0.005)
Loan/Deposit (z) −0.000 −0.006 −0.004 −0.001
(0.006) (0.006) (0.006) (0.006)
Book Equity (z) 0.000 −0.005 −0.005 −0.002
(0.005) (0.005) (0.005) (0.005)
Wholesale (z) 0.020*** 0.020*** 0.020*** 0.020***
(0.006) (0.006) (0.006) (0.006)
ROA (z) −0.004 −0.003 −0.002 −0.004
(0.005) (0.005) (0.005) (0.005)
Num.Obs. 3481 3481 3481 3481
R2 0.093 0.088 0.089 0.092
R2 Adj. 0.091 0.086 0.087 0.090
N (dw_acute=1) 369.000 369.000 369.000 369.000
N (Sample) 3481.000 3481.000 3481.000 3481.000
* p < 0.1, ** p < 0.05, *** p < 0.01
dw_acute (Insolvent) | IDCR Insolvent (50% run, IDCR<0)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.036
(0.036)
Uninsured Lev (z) −0.022
(0.037)
MTM ? Uninsured −0.024
(0.027)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.043 −0.107
(0.062) (0.074)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.116 −0.011
(0.072) (0.070)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.155*
(0.082)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured −0.023
(0.087)
Log(Assets) (z) 0.128*** 0.115*** 0.127*** 0.132***
(0.035) (0.033) (0.034) (0.035)
Cash Ratio (z) 0.007 −0.001 0.005 0.006
(0.034) (0.032) (0.032) (0.031)
Loan/Deposit (z) −0.007 −0.003 −0.008 −0.016
(0.030) (0.029) (0.029) (0.030)
Book Equity (z) 0.023 0.003 0.022 0.022
(0.032) (0.029) (0.030) (0.030)
Wholesale (z) 0.067 0.057 0.065 0.067
(0.051) (0.050) (0.051) (0.051)
ROA (z) 0.021 0.022 0.018 0.017
(0.031) (0.032) (0.032) (0.031)
Num.Obs. 156 156 156 156
R2 0.172 0.157 0.172 0.186
R2 Adj. 0.121 0.118 0.127 0.136
N (dw_acute=1) 24.000 24.000 24.000 24.000
N (Sample) 156.000 156.000 156.000 156.000
* p < 0.1, ** p < 0.05, *** p < 0.01
Acute: Borrowers vs Non-Users | IDCR Insolvent (100% run, IDCR<0)
solvency_group borrower_group N MTM Loss (% assets) Uninsured Lev (% assets) Adj. Equity (%) IDCR (50%) IDCR (100%) Log Assets Cash/Assets Loan/Deposit Book Equity/Assets Wholesale Funding (%) ROA
Solvent Any Fed Borrower 532 6.033 26.422 2.797 0.449 0.177 13.81 4.977 74.66 8.830 1.962 1.082
Solvent Pure Non-User 2324 5.344 21.423 5.226 2.925 2.441 12.63 8.984 70.26 10.570 1.088 1.104
Insolvent Any Fed Borrower 229 5.829 29.179 1.973 0.159 -0.135 13.73 5.964 72.35 7.802 0.668 1.133
Insolvent Pure Non-User 920 5.437 26.889 2.509 0.148 -0.129 12.80 8.476 68.61 7.947 0.340 0.954
Pure Non-User 41 1.534 6.906 82.786 10.52 26.862 34.09 74.755 1.011 11.373
Acute: Facility Groups | IDCR Insolvent (100% run, IDCR<0)
solvency_group facility_group N MTM Loss (% assets) Uninsured Lev (% assets) Adj. Equity (%) Log Assets Cash/Assets Loan/Deposit Book Equity/Assets Wholesale Funding (%) ROA
Solvent BTFP_Only 261 6.215 25.557 2.221 13.59 4.370 73.32 8.436 1.895 1.040
Solvent DW_Only 200 5.878 25.495 3.645 13.84 5.858 76.78 9.523 1.973 1.116
Solvent Both 71 5.801 32.211 2.524 14.55 4.725 73.63 8.325 2.179 1.142
Solvent Pure Non-User 2324 5.344 21.423 5.226 12.63 8.984 70.26 10.570 1.088 1.104
Insolvent BTFP_Only 107 6.096 27.787 1.448 13.42 5.485 70.36 7.544 0.598 1.120
Insolvent DW_Only 99 5.486 29.577 2.597 13.87 6.774 73.91 8.082 0.732 1.188
Insolvent Both 23 6.059 33.942 1.732 14.57 4.702 74.91 7.791 0.719 0.959
Insolvent Pure Non-User 920 5.437 26.889 2.509 12.80 8.476 68.61 7.947 0.340 0.954
Pure Non-User 41 1.534 6.906 82.786 10.52 26.862 34.09 74.755 1.011 11.373
Acute BTFP: Borrowers vs Non-Users | IDCR Insolvent (100% run, IDCR<0)
solvency_group borrower_group N MTM Loss (% assets) Uninsured Lev (% assets) Adj. Equity (%) IDCR (50%) IDCR (100%) Log Assets Cash/Assets Loan/Deposit Book Equity/Assets Wholesale Funding (%) ROA
Solvent BTFP Borrower 332 6.126 26.980 2.286 0.467 0.180 13.79 4.446 73.39 8.412 1.956 1.062
Solvent Pure Non-User 2324 5.344 21.423 5.226 2.925 2.441 12.63 8.984 70.26 10.570 1.088 1.104
Insolvent BTFP Borrower 130 6.090 28.876 1.498 0.164 -0.124 13.62 5.347 71.17 7.588 0.619 1.092
Insolvent Pure Non-User 920 5.437 26.889 2.509 0.148 -0.129 12.80 8.476 68.61 7.947 0.340 0.954
Pure Non-User 41 1.534 6.906 82.786 10.52 26.862 34.09 74.755 1.011 11.373
btfp_acute (Solvent) | IDCR Insolvent (100% run, IDCR<0)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.027***
(0.007)
Uninsured Lev (z) 0.028***
(0.008)
MTM ? Uninsured 0.020***
(0.006)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.015 −0.029**
(0.012) (0.014)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.038** −0.002
(0.017) (0.016)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.003
(0.015)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.078***
(0.021)
Log(Assets) (z) 0.062*** 0.073*** 0.075*** 0.069***
(0.009) (0.008) (0.008) (0.008)
Cash Ratio (z) −0.025*** −0.030*** −0.025*** −0.026***
(0.006) (0.005) (0.005) (0.005)
Loan/Deposit (z) −0.003 −0.011 −0.009 −0.005
(0.007) (0.007) (0.007) (0.007)
Book Equity (z) −0.018*** −0.025*** −0.025*** −0.022***
(0.005) (0.005) (0.005) (0.005)
Wholesale (z) 0.023*** 0.022*** 0.021*** 0.022***
(0.007) (0.007) (0.007) (0.007)
ROA (z) −0.007 −0.006 −0.004 −0.007
(0.006) (0.006) (0.006) (0.006)
Num.Obs. 2656 2656 2656 2656
R2 0.102 0.093 0.095 0.101
R2 Adj. 0.099 0.091 0.092 0.098
N (btfp_acute=1) 332.000 332.000 332.000 332.000
N (Sample) 2656.000 2656.000 2656.000 2656.000
* p < 0.1, ** p < 0.05, *** p < 0.01
btfp_acute (Insolvent) | IDCR Insolvent (100% run, IDCR<0)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.029**
(0.011)
Uninsured Lev (z) 0.005
(0.013)
MTM ? Uninsured 0.010
(0.010)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.009 −0.027
(0.024) (0.026)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.040 −0.011
(0.025) (0.027)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.010
(0.030)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.042
(0.031)
Log(Assets) (z) 0.079*** 0.079*** 0.080*** 0.078***
(0.015) (0.014) (0.014) (0.014)
Cash Ratio (z) −0.022** −0.035*** −0.030*** −0.030***
(0.010) (0.009) (0.010) (0.010)
Loan/Deposit (z) −0.020 −0.025** −0.024* −0.021*
(0.012) (0.012) (0.012) (0.012)
Book Equity (z) 0.013 −0.002 0.006 0.005
(0.014) (0.013) (0.014) (0.014)
Wholesale (z) 0.037* 0.035* 0.035* 0.036*
(0.020) (0.020) (0.020) (0.020)
ROA (z) −0.006 −0.007 −0.006 −0.007
(0.010) (0.010) (0.010) (0.010)
Num.Obs. 1050 1050 1050 1050
R2 0.068 0.062 0.064 0.065
R2 Adj. 0.060 0.055 0.057 0.057
N (btfp_acute=1) 130.000 130.000 130.000 130.000
N (Sample) 1050.000 1050.000 1050.000 1050.000
* p < 0.1, ** p < 0.05, *** p < 0.01
Acute DW: Borrowers vs Non-Users | IDCR Insolvent (100% run, IDCR<0)
solvency_group borrower_group N MTM Loss (% assets) Uninsured Lev (% assets) Adj. Equity (%) IDCR (50%) IDCR (100%) Log Assets Cash/Assets Loan/Deposit Book Equity/Assets Wholesale Funding (%) ROA
Solvent DW Borrower 271 5.858 27.255 3.351 0.496 0.202 14.02 5.561 75.96 9.209 2.027 1.123
Solvent Pure Non-User 2324 5.344 21.423 5.226 2.925 2.441 12.63 8.984 70.26 10.570 1.088 1.104
Insolvent DW Borrower 122 5.594 30.400 2.434 0.168 -0.148 14.00 6.383 74.09 8.027 0.730 1.145
Insolvent Pure Non-User 920 5.437 26.889 2.509 0.148 -0.129 12.80 8.476 68.61 7.947 0.340 0.954
Pure Non-User 41 1.534 6.906 82.786 10.52 26.862 34.09 74.755 1.011 11.373
dw_acute (Solvent) | IDCR Insolvent (100% run, IDCR<0)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.020***
(0.007)
Uninsured Lev (z) 0.016**
(0.008)
MTM ? Uninsured 0.016***
(0.006)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.017 −0.028**
(0.012) (0.013)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.030* 0.002
(0.017) (0.016)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured 0.013
(0.014)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.056***
(0.019)
Log(Assets) (z) 0.077*** 0.082*** 0.084*** 0.080***
(0.009) (0.008) (0.008) (0.008)
Cash Ratio (z) −0.002 −0.007 −0.002 −0.003
(0.006) (0.005) (0.006) (0.006)
Loan/Deposit (z) 0.002 −0.002 −0.001 0.001
(0.007) (0.007) (0.007) (0.007)
Book Equity (z) −0.003 −0.007 −0.007 −0.005
(0.005) (0.005) (0.005) (0.005)
Wholesale (z) 0.019*** 0.019*** 0.018*** 0.018***
(0.007) (0.007) (0.007) (0.006)
ROA (z) −0.001 −0.001 0.001 −0.001
(0.006) (0.005) (0.005) (0.005)
Num.Obs. 2595 2595 2595 2595
R2 0.096 0.092 0.093 0.095
R2 Adj. 0.093 0.089 0.090 0.092
N (dw_acute=1) 271.000 271.000 271.000 271.000
N (Sample) 2595.000 2595.000 2595.000 2595.000
* p < 0.1, ** p < 0.05, *** p < 0.01
dw_acute (Insolvent) | IDCR Insolvent (100% run, IDCR<0)
&nbsp;(1) Base &nbsp;(2) +Risk1 &nbsp;(3) +Risk1,2 &nbsp;(4) Risk2,3,4
MTM Loss (z) 0.011
(0.012)
Uninsured Lev (z) 0.005
(0.012)
MTM ? Uninsured 0.017*
(0.010)
Risk 1: \(&amp;lt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.003 −0.020
(0.024) (0.026)
Risk 2: \(&lt;\) Med MTM &amp; \(&gt;\) Med Uninsured −0.038 −0.016
(0.024) (0.027)
Risk 3: \(&amp;gt;\) Med MTM &amp; \(&amp;lt;\) Med Uninsured −0.001
(0.029)
Risk 4: \(&amp;gt;\) Med MTM &amp; \(&amp;gt;\) Med Uninsured 0.036
(0.031)
Log(Assets) (z) 0.105*** 0.106*** 0.107*** 0.104***
(0.015) (0.014) (0.014) (0.015)
Cash Ratio (z) −0.001 −0.009 −0.004 −0.004
(0.012) (0.011) (0.011) (0.011)
Loan/Deposit (z) −0.011 −0.016 −0.015 −0.012
(0.013) (0.013) (0.013) (0.013)
Book Equity (z) 0.010 0.003 0.010 0.009
(0.014) (0.012) (0.013) (0.013)
Wholesale (z) 0.042** 0.042** 0.042** 0.043**
(0.020) (0.020) (0.020) (0.020)
ROA (z) −0.007 −0.006 −0.006 −0.007
(0.011) (0.011) (0.011) (0.011)
Num.Obs. 1042 1042 1042 1042
R2 0.098 0.094 0.096 0.097
R2 Adj. 0.090 0.088 0.089 0.089
N (dw_acute=1) 122.000 122.000 122.000 122.000
N (Sample) 1042.000 1042.000 1042.000 1042.000
* p < 0.1, ** p < 0.05, *** p < 0.01

17 MTM x UNINSURED QUARTILE ANALYSIS: ACUTE AND TEMPORAL

This section analyzes emergency facility participation using two specifications:

  1. Base Model: MTM Loss + Uninsured Leverage + Interaction (continuous, z-standardized)
  2. Risk Category Model: 16 MTM × Uninsured Leverage quartile combination dummies (mtmq{1-4}_ulq{1-4}), with mtmq1_ulq1 (lowest MTM, lowest uninsured) as reference.

17.1 Acute Period: MTM x Uninsured Quartile Analysis

# ==============================================================================
# ACUTE PERIOD: MTM x UNINSURED QUARTILE ANALYSIS
# Spec 1: Base (continuous MTM + Uninsured + Interaction)
# Spec 2: 16 MTM x Uninsured Quartile Combination Dummies
# ==============================================================================

# --- Verify 16 MTM x Uninsured combination dummies exist ---
mtm_combo_vars <- c("mtmq1_ulq1", "mtmq1_ulq2", "mtmq1_ulq3", "mtmq1_ulq4",
                    "mtmq2_ulq1", "mtmq2_ulq2", "mtmq2_ulq3", "mtmq2_ulq4",
                    "mtmq3_ulq1", "mtmq3_ulq2", "mtmq3_ulq3", "mtmq3_ulq4",
                    "mtmq4_ulq1", "mtmq4_ulq2", "mtmq4_ulq3", "mtmq4_ulq4")

cat("16 MTM x Uninsured combination dummies available:", all(mtm_combo_vars %in% names(df_acute)), "\n")
## 16 MTM x Uninsured combination dummies available: TRUE
# Show 4x4 cross-tabulation
mtm_cell_counts <- df_acute %>% 
  select(all_of(mtm_combo_vars)) %>% 
  summarise(across(everything(), ~sum(.x, na.rm = TRUE))) %>% 
  pivot_longer(everything(), names_to = "Cell", values_to = "N")

mtm_matrix_4x4 <- mtm_cell_counts %>%
  mutate(
    mtm_q = as.integer(substr(Cell, 5, 5)),
    unins_lev_q = as.integer(substr(Cell, 10, 10))
  ) %>%
  select(mtm_q, unins_lev_q, N) %>%
  pivot_wider(names_from = unins_lev_q, values_from = N, names_prefix = "ULQ") %>%
  arrange(mtm_q) %>%
  mutate(MTM_Q = paste0("MTMQ", mtm_q)) %>%
  select(MTM_Q, ULQ1, ULQ2, ULQ3, ULQ4)

cat("\n--- 4x4 Cross-Tabulation: MTM Quartile x Uninsured Leverage Quartile ---\n")
## 
## --- 4x4 Cross-Tabulation: MTM Quartile x Uninsured Leverage Quartile ---
cat("(Reference: mtmq1_ulq1 = Lowest MTM Loss, Lowest Uninsured Leverage)\n\n")
## (Reference: mtmq1_ulq1 = Lowest MTM Loss, Lowest Uninsured Leverage)
cat("                 Uninsured Leverage Quartile\n")
##                  Uninsured Leverage Quartile
cat("                 (Q1=Lowest, Q4=Highest)\n")
##                  (Q1=Lowest, Q4=Highest)
print(as.data.frame(mtm_matrix_4x4), row.names = FALSE)
##  MTM_Q ULQ1 ULQ2 ULQ3 ULQ4
##  MTMQ1  274  214  242  341
##  MTMQ2  251  267  285  267
##  MTMQ3  217  299  282  272
##  MTMQ4  328  292  264  187
cat("\nTotal banks:", sum(mtm_cell_counts$N), "\n")
## 
## Total banks: 4282
# --- Formula string for 16 combos (omit mtmq1_ulq1 as reference) ---
mtm_combo_formula_str <- paste(
  "mtmq1_ulq2", "mtmq1_ulq3", "mtmq1_ulq4",
  "mtmq2_ulq1", "mtmq2_ulq2", "mtmq2_ulq3", "mtmq2_ulq4",
  "mtmq3_ulq1", "mtmq3_ulq2", "mtmq3_ulq3", "mtmq3_ulq4",
  "mtmq4_ulq1", "mtmq4_ulq2", "mtmq4_ulq3", "mtmq4_ulq4",
  sep = " + "
)

# --- Coefficient map for MTM x Uninsured Quartile models ---
COEF_MAP_MTM_Q <- c(
  # Base model
  "mtm_total" = "MTM Loss (z)",
  "uninsured_lev" = "Uninsured Lev (z)",
  "mtm_x_uninsured" = "MTM × Uninsured",
  # 16 Combo dummies (reference = mtmq1_ulq1)
  "mtmq1_ulq2" = "MTM Q1 × UL Q2",
  "mtmq1_ulq3" = "MTM Q1 × UL Q3",
  "mtmq1_ulq4" = "MTM Q1 × UL Q4",
  "mtmq2_ulq1" = "MTM Q2 × UL Q1",
  "mtmq2_ulq2" = "MTM Q2 × UL Q2",
  "mtmq2_ulq3" = "MTM Q2 × UL Q3",
  "mtmq2_ulq4" = "MTM Q2 × UL Q4",
  "mtmq3_ulq1" = "MTM Q3 × UL Q1",
  "mtmq3_ulq2" = "MTM Q3 × UL Q2",
  "mtmq3_ulq3" = "MTM Q3 × UL Q3",
  "mtmq3_ulq4" = "MTM Q3 × UL Q4",
  "mtmq4_ulq1" = "MTM Q4 × UL Q1",
  "mtmq4_ulq2" = "MTM Q4 × UL Q2",
  "mtmq4_ulq3" = "MTM Q4 × UL Q3",
  "mtmq4_ulq4" = "MTM Q4 × UL Q4 (Riskiest)",
  # Controls
  "ln_assets" = "Log(Assets) (z)",
  "cash_ratio" = "Cash Ratio (z)",
  "loan_to_deposit" = "Loan/Deposit (z)",
  "book_equity_ratio" = "Book Equity (z)",
  "wholesale" = "Wholesale (z)",
  "roa" = "ROA (z)"
)

# --- Prepare clean samples (users vs pure non-users) ---
df_btfp_acute_mtmq <- df_acute %>% filter(btfp_acute == 1 | non_user == 1)
df_dw_acute_mtmq   <- df_acute %>% filter(dw_acute == 1 | non_user == 1)
df_any_acute_mtmq   <- df_acute %>% filter(any_fed == 1 | non_user == 1)

# ==============================================================================
# PANEL A: BTFP Acute - Base + 16 Combo
# ==============================================================================
m_btfp_acute_base  <- feols(build_formula("btfp_acute", "mtm_total + uninsured_lev + mtm_x_uninsured"),
                            data = df_btfp_acute_mtmq, vcov = "hetero")
m_btfp_acute_combo <- feols(build_formula("btfp_acute", mtm_combo_formula_str),
                            data = df_btfp_acute_mtmq, vcov = "hetero")

# ==============================================================================
# PANEL B: DW Acute - Base + 16 Combo
# ==============================================================================
m_dw_acute_base  <- feols(build_formula("dw_acute", "mtm_total + uninsured_lev + mtm_x_uninsured"),
                          data = df_dw_acute_mtmq, vcov = "hetero")
m_dw_acute_combo <- feols(build_formula("dw_acute", mtm_combo_formula_str),
                          data = df_dw_acute_mtmq, vcov = "hetero")

# ==============================================================================
# PANEL C: Any Fed Acute - Base + 16 Combo
# ==============================================================================
m_any_acute_base  <- feols(build_formula("any_fed", "mtm_total + uninsured_lev + mtm_x_uninsured"),
                           data = df_any_acute_mtmq, vcov = "hetero")
m_any_acute_combo <- feols(build_formula("any_fed", mtm_combo_formula_str),
                           data = df_any_acute_mtmq, vcov = "hetero")

# --- Combine into tables ---
models_acute_mtmq <- list(
  "BTFP (Base)" = m_btfp_acute_base,
  "BTFP (Q-Risk)" = m_btfp_acute_combo,
  "DW (Base)" = m_dw_acute_base,
  "DW (Q-Risk)" = m_dw_acute_combo,
  "Any Fed (Base)" = m_any_acute_base,
  "Any Fed (Q-Risk)" = m_any_acute_combo
)

n_rows_acute_mtmq <- data.frame(
  term = c("N (Users)", "N (Sample)"),
  `BTFP (Base)` = c(sum(df_btfp_acute_mtmq$btfp_acute), nrow(df_btfp_acute_mtmq)),
  `BTFP (Q-Risk)` = c(sum(df_btfp_acute_mtmq$btfp_acute), nrow(df_btfp_acute_mtmq)),
  `DW (Base)` = c(sum(df_dw_acute_mtmq$dw_acute), nrow(df_dw_acute_mtmq)),
  `DW (Q-Risk)` = c(sum(df_dw_acute_mtmq$dw_acute), nrow(df_dw_acute_mtmq)),
  `Any Fed (Base)` = c(sum(df_any_acute_mtmq$any_fed), nrow(df_any_acute_mtmq)),
  `Any Fed (Q-Risk)` = c(sum(df_any_acute_mtmq$any_fed), nrow(df_any_acute_mtmq)),
  check.names = FALSE
)

# SAVE
save_reg_table(models_acute_mtmq, "Table_MTM_Quartile_Acute_LPM",
               title_text = "Emergency Facility Usage: MTM x Uninsured Quartile Analysis (Acute, LPM)",
               notes_text = "This table reports LPM estimates of emergency facility usage during the acute crisis period (March 13--May 1, 2023). Base columns include continuous MTM Loss, Uninsured Leverage, and their interaction. Q-Risk columns use 16 MTM × Uninsured Leverage quartile combination dummies (mtmq{1-4} × ulq{1-4}). Reference category: MTM Q1 × UL Q1 (lowest MTM loss and lowest uninsured leverage). The sample consists of facility users vs pure non-users, excluding G-SIBs and failed institutions. All continuous variables are winsorized at 2.5/97.5 percentiles and z-standardized. Heteroskedasticity-robust standard errors in parentheses.",
               coef_map_use = COEF_MAP_MTM_Q, gof_map_use = gof_lpm, add_rows_use = n_rows_acute_mtmq)

modelsummary(
  models_acute_mtmq,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP_MTM_Q,
  gof_map = gof_lpm,
  add_rows = n_rows_acute_mtmq,
  title = "Table: Acute Period - MTM x Uninsured Quartile Analysis (LPM)",
  notes = c("LPM with heteroskedasticity-robust SEs. Reference: MTM Q1 × UL Q1 (lowest risk).",
            "Sample: facility users vs pure non-users. Variables z-standardized."),
  output = "kableExtra"
)
Table: Acute Period - MTM x Uninsured Quartile Analysis (LPM)
BTFP (Base) BTFP (Q-Risk) DW (Base) DW (Q-Risk) Any Fed (Base) Any Fed (Q-Risk)
MTM Loss (z) 0.024*** 0.016*** 0.030***
(0.006) (0.006) (0.007)
Uninsured Lev (z) 0.020*** 0.013** 0.019**
(0.007) (0.006) (0.007)
MTM × Uninsured 0.018*** 0.016*** 0.019***
(0.005) (0.005) (0.006)
MTM Q1 × UL Q2 −0.007 0.001 0.010
(0.021) (0.019) (0.025)
MTM Q1 × UL Q3 −0.071*** 0.002 −0.028
(0.018) (0.022) (0.025)
MTM Q1 × UL Q4 −0.014 −0.024 −0.014
(0.022) (0.020) (0.025)
MTM Q2 × UL Q1 −0.027 0.005 0.001
(0.018) (0.017) (0.023)
MTM Q2 × UL Q2 −0.019 −0.000 0.001
(0.022) (0.020) (0.026)
MTM Q2 × UL Q3 −0.006 −0.012 0.011
(0.025) (0.023) (0.028)
MTM Q2 × UL Q4 0.023 0.046 0.061*
(0.030) (0.030) (0.034)
MTM Q3 × UL Q1 −0.032 0.039* 0.021
(0.021) (0.023) (0.027)
MTM Q3 × UL Q2 −0.001 0.005 0.027
(0.024) (0.022) (0.028)
MTM Q3 × UL Q3 0.079*** 0.062** 0.111***
(0.029) (0.028) (0.031)
MTM Q3 × UL Q4 0.027 0.046* 0.055*
(0.029) (0.027) (0.031)
MTM Q4 × UL Q1 −0.010 −0.022 0.001
(0.020) (0.017) (0.023)
MTM Q4 × UL Q2 −0.001 0.032 0.054*
(0.026) (0.025) (0.030)
MTM Q4 × UL Q3 0.034 0.049* 0.085**
(0.030) (0.029) (0.033)
MTM Q4 × UL Q4 (Riskiest) 0.078** 0.068* 0.105***
(0.037) (0.035) (0.039)
Log(Assets) (z) 0.067*** 0.068*** 0.084*** 0.084*** 0.102*** 0.101***
(0.007) (0.007) (0.007) (0.007) (0.008) (0.008)
Cash Ratio (z) −0.024*** −0.026*** −0.002 −0.002 −0.020*** −0.021***
(0.005) (0.005) (0.005) (0.005) (0.006) (0.006)
Loan/Deposit (z) −0.008 −0.010* −0.001 −0.001 −0.003 −0.004
(0.005) (0.005) (0.005) (0.005) (0.006) (0.006)
Book Equity (z) −0.011** −0.013*** −0.000 0.001 −0.012** −0.012**
(0.004) (0.004) (0.004) (0.004) (0.005) (0.005)
Wholesale (z) 0.025*** 0.024*** 0.020*** 0.019*** 0.029*** 0.028***
(0.006) (0.006) (0.006) (0.006) (0.007) (0.007)
ROA (z) −0.005 −0.005 −0.002 −0.002 −0.001 −0.002
(0.005) (0.005) (0.005) (0.005) (0.006) (0.005)
Num.Obs. 3737 3747 3668 3678 4036 4046
R2 0.090 0.093 0.096 0.099 0.116 0.118
R2 Adj. 0.088 0.088 0.093 0.093 0.114 0.114
N (Users) 462.000 462.000 393.000 393.000 761.000 761.000
N (Sample) 3747.000 3747.000 3678.000 3678.000 4046.000 4046.000
* p < 0.1, ** p < 0.05, *** p < 0.01
LPM with heteroskedasticity-robust SEs. Reference: MTM Q1 × UL Q1 (lowest risk).
Sample: facility users vs pure non-users. Variables z-standardized.

17.2 Temporal Analysis: MTM x Uninsured Quartile Analysis

# ==============================================================================
# BTFP TEMPORAL: MTM x UNINSURED QUARTILE ANALYSIS
# Base + 16 Combo across Acute, Post-Acute, Arbitrage, Wind-down
# ==============================================================================

# --- Prepare samples ---
df_btfp_acute_mtmq <- df_acute %>% filter(btfp_acute == 1 | non_user == 1)
df_btfp_post_mtmq  <- df_post  %>% filter(btfp_post  == 1 | non_user == 1)
df_btfp_arb_mtmq   <- df_arb   %>% filter(btfp_arb   == 1 | non_user == 1)
df_btfp_wind_mtmq  <- df_wind  %>% filter(btfp_wind  == 1 | non_user == 1)

# ---- BTFP Temporal: Base Model ----
m_btfp_temp_base <- list(
  "Acute" = feols(build_formula("btfp_acute", "mtm_total + uninsured_lev + mtm_x_uninsured"), 
                  data = df_btfp_acute_mtmq, vcov = "hetero"),
  "Post" = feols(build_formula("btfp_post", "mtm_total + uninsured_lev + mtm_x_uninsured"), 
                 data = df_btfp_post_mtmq, vcov = "hetero"),
  "Arb" = feols(build_formula("btfp_arb", "mtm_total + uninsured_lev + mtm_x_uninsured"), 
                data = df_btfp_arb_mtmq, vcov = "hetero"),
  "Wind" = feols(build_formula("btfp_wind", "mtm_total + uninsured_lev + mtm_x_uninsured"), 
                 data = df_btfp_wind_mtmq, vcov = "hetero")
)

n_btfp_temp_base <- data.frame(
  term = c("N (BTFP=1)", "N (Sample)", "Baseline"),
  Acute = c(sum(df_btfp_acute_mtmq$btfp_acute), nrow(df_btfp_acute_mtmq), "2022Q4"),
  Post = c(sum(df_btfp_post_mtmq$btfp_post), nrow(df_btfp_post_mtmq), "2022Q4"),
  Arb = c(sum(df_btfp_arb_mtmq$btfp_arb), nrow(df_btfp_arb_mtmq), "2023Q3"),
  Wind = c(sum(df_btfp_wind_mtmq$btfp_wind), nrow(df_btfp_wind_mtmq), "2023Q4"),
  check.names = FALSE
)

save_reg_table(m_btfp_temp_base, "Table_MTM_Q_BTFP_Temporal_Base_LPM",
               title_text = "BTFP Participation Across Periods: Base Model (LPM)",
               notes_text = "LPM estimates with MTM Loss, Uninsured Leverage, and interaction across BTFP program periods. Acute and Post-Acute use 2022Q4 baseline; Arbitrage uses 2023Q3; Wind-down uses 2023Q4. Sample: BTFP users vs pure non-users. Heteroskedasticity-robust standard errors.",
               coef_map_use = COEF_MAP_MTM_Q, gof_map_use = gof_lpm, add_rows_use = n_btfp_temp_base)

modelsummary(
  m_btfp_temp_base,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP_MTM_Q,
  gof_map = gof_lpm,
  add_rows = n_btfp_temp_base,
  title = "Table: BTFP Temporal - Base Model (LPM)",
  notes = c("LPM with heteroskedasticity-robust SEs.",
            "Arb uses 2023Q3 baseline; Wind-down uses 2023Q4 baseline."),
  output = "kableExtra"
)
Table: BTFP Temporal - Base Model (LPM)
Acute Post Arb Wind
MTM Loss (z) 0.024*** 0.025*** 0.022*** 0.002
(0.006) (0.008) (0.007) (0.004)
Uninsured Lev (z) 0.020*** 0.018** 0.014** 0.007*
(0.007) (0.008) (0.007) (0.004)
MTM × Uninsured 0.018*** 0.016*** 0.008 −0.001
(0.005) (0.006) (0.005) (0.003)
Log(Assets) (z) 0.067*** 0.068*** 0.054*** −0.000
(0.007) (0.009) (0.007) (0.004)
Cash Ratio (z) −0.024*** −0.052*** −0.036*** −0.010***
(0.005) (0.007) (0.006) (0.004)
Loan/Deposit (z) −0.008 −0.021*** −0.003 −0.010**
(0.005) (0.007) (0.006) (0.004)
Book Equity (z) −0.011** −0.020*** −0.006 −0.006*
(0.004) (0.006) (0.006) (0.003)
Wholesale (z) 0.025*** 0.016** 0.101*** 0.053***
(0.006) (0.007) (0.007) (0.006)
ROA (z) −0.005 −0.013** −0.024*** −0.007*
(0.005) (0.006) (0.006) (0.004)
Num.Obs. 3737 3515 4038 4043
R2 0.090 0.080 0.142 0.063
R2 Adj. 0.088 0.078 0.141 0.061
N (BTFP=1) 462 775 766 229
N (Sample) 3747 3525 4055 4061
Baseline 2022Q4 2022Q4 2023Q3 2023Q4
* p < 0.1, ** p < 0.05, *** p < 0.01
LPM with heteroskedasticity-robust SEs.
Arb uses 2023Q3 baseline; Wind-down uses 2023Q4 baseline.
# ---- BTFP Temporal: 16 Combo Model ----
m_btfp_temp_combo <- list(
  "Acute" = feols(build_formula("btfp_acute", mtm_combo_formula_str), 
                  data = df_btfp_acute_mtmq, vcov = "hetero"),
  "Post" = feols(build_formula("btfp_post", mtm_combo_formula_str), 
                 data = df_btfp_post_mtmq, vcov = "hetero"),
  "Arb" = feols(build_formula("btfp_arb", mtm_combo_formula_str), 
                data = df_btfp_arb_mtmq, vcov = "hetero"),
  "Wind" = feols(build_formula("btfp_wind", mtm_combo_formula_str), 
                 data = df_btfp_wind_mtmq, vcov = "hetero")
)

n_btfp_temp_combo <- n_btfp_temp_base  # Same Ns as base

save_reg_table(m_btfp_temp_combo, "Table_MTM_Q_BTFP_Temporal_Combo_LPM",
               title_text = "BTFP Participation Across Periods: Quartile Risk Categories (LPM)",
               notes_text = "LPM estimates using 16 MTM × Uninsured Leverage quartile combination dummies across BTFP program periods. Reference: MTM Q1 × UL Q1 (lowest risk). Acute and Post-Acute use 2022Q4 baseline; Arbitrage uses 2023Q3; Wind-down uses 2023Q4. Sample: BTFP users vs pure non-users. Heteroskedasticity-robust standard errors.",
               coef_map_use = COEF_MAP_MTM_Q, gof_map_use = gof_lpm, add_rows_use = n_btfp_temp_combo)

modelsummary(
  m_btfp_temp_combo,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP_MTM_Q,
  gof_map = gof_lpm,
  add_rows = n_btfp_temp_combo,
  title = "Table: BTFP Temporal - MTM x Uninsured Quartile Dummies (LPM)",
  notes = c("LPM with heteroskedasticity-robust SEs. Reference: MTM Q1 × UL Q1.",
            "Arb uses 2023Q3 baseline; Wind-down uses 2023Q4 baseline."),
  output = "kableExtra"
)
Table: BTFP Temporal - MTM x Uninsured Quartile Dummies (LPM)
Acute Post Arb Wind
MTM Q1 × UL Q2 −0.007 0.046 −0.033 0.004
(0.021) (0.031) (0.021) (0.015)
MTM Q1 × UL Q3 −0.071*** 0.019 0.013 0.026
(0.018) (0.030) (0.025) (0.018)
MTM Q1 × UL Q4 −0.014 0.024 0.005 0.008
(0.022) (0.028) (0.025) (0.015)
MTM Q2 × UL Q1 −0.027 0.008 −0.020 −0.020
(0.018) (0.027) (0.024) (0.015)
MTM Q2 × UL Q2 −0.019 0.090*** 0.056* −0.019
(0.022) (0.032) (0.029) (0.015)
MTM Q2 × UL Q3 −0.006 0.057* 0.061** 0.005
(0.025) (0.033) (0.029) (0.017)
MTM Q2 × UL Q4 0.023 0.031 0.060** 0.045**
(0.030) (0.036) (0.031) (0.021)
MTM Q3 × UL Q1 −0.032 0.032 0.007 0.001
(0.021) (0.032) (0.029) (0.018)
MTM Q3 × UL Q2 −0.001 0.031 0.049* 0.018
(0.024) (0.032) (0.028) (0.019)
MTM Q3 × UL Q3 0.079*** 0.115*** 0.026 0.038*
(0.029) (0.035) (0.029) (0.020)
MTM Q3 × UL Q4 0.027 0.053 0.046 0.028
(0.029) (0.036) (0.028) (0.020)
MTM Q4 × UL Q1 −0.010 0.029 0.035 0.015
(0.020) (0.028) (0.026) (0.016)
MTM Q4 × UL Q2 −0.001 0.100*** 0.098*** −0.003
(0.026) (0.034) (0.031) (0.019)
MTM Q4 × UL Q3 0.034 0.096*** 0.073** 0.011
(0.030) (0.037) (0.032) (0.020)
MTM Q4 × UL Q4 (Riskiest) 0.078** 0.152*** 0.077** 0.003
(0.037) (0.045) (0.037) (0.022)
Log(Assets) (z) 0.068*** 0.069*** 0.052*** −0.002
(0.007) (0.009) (0.007) (0.004)
Cash Ratio (z) −0.026*** −0.051*** −0.036*** −0.010***
(0.005) (0.006) (0.006) (0.004)
Loan/Deposit (z) −0.010* −0.021*** −0.004 −0.010**
(0.005) (0.007) (0.006) (0.004)
Book Equity (z) −0.013*** −0.017*** −0.006 −0.007*
(0.004) (0.006) (0.005) (0.003)
Wholesale (z) 0.024*** 0.015** 0.100*** 0.053***
(0.006) (0.007) (0.007) (0.006)
ROA (z) −0.005 −0.014** −0.022*** −0.007*
(0.005) (0.006) (0.005) (0.004)
Num.Obs. 3747 3525 4055 4061
R2 0.093 0.085 0.146 0.067
R2 Adj. 0.088 0.079 0.142 0.062
N (BTFP=1) 462 775 766 229
N (Sample) 3747 3525 4055 4061
Baseline 2022Q4 2022Q4 2023Q3 2023Q4
* p < 0.1, ** p < 0.05, *** p < 0.01
LPM with heteroskedasticity-robust SEs. Reference: MTM Q1 × UL Q1.
Arb uses 2023Q3 baseline; Wind-down uses 2023Q4 baseline.
# ==============================================================================
# DW TEMPORAL: MTM x UNINSURED QUARTILE ANALYSIS
# NOTE: DW data only available through 2023-12-31 (via FOIA)
# ==============================================================================

df_dw_acute_mtmq <- df_acute %>% filter(dw_acute == 1 | non_user == 1)
df_dw_post_mtmq  <- df_post  %>% filter(dw_post  == 1 | non_user == 1)

# ---- DW Temporal: Base Model ----
m_dw_temp_base <- list(
  "Acute (Base)" = feols(build_formula("dw_acute", "mtm_total + uninsured_lev + mtm_x_uninsured"), 
                         data = df_dw_acute_mtmq, vcov = "hetero"),
  "Post (Base)" = feols(build_formula("dw_post", "mtm_total + uninsured_lev + mtm_x_uninsured"), 
                        data = df_dw_post_mtmq, vcov = "hetero")
)

# ---- DW Temporal: 16 Combo Model ----
m_dw_temp_combo <- list(
  "Acute (Q-Risk)" = feols(build_formula("dw_acute", mtm_combo_formula_str), 
                           data = df_dw_acute_mtmq, vcov = "hetero"),
  "Post (Q-Risk)" = feols(build_formula("dw_post", mtm_combo_formula_str), 
                          data = df_dw_post_mtmq, vcov = "hetero")
)

# Combine Base + Combo into one table
models_dw_temp_mtmq <- c(m_dw_temp_base, m_dw_temp_combo)

n_dw_temp_mtmq <- data.frame(
  term = c("N (DW=1)", "N (Sample)"),
  `Acute (Base)` = c(sum(df_dw_acute_mtmq$dw_acute), nrow(df_dw_acute_mtmq)),
  `Post (Base)` = c(sum(df_dw_post_mtmq$dw_post), nrow(df_dw_post_mtmq)),
  `Acute (Q-Risk)` = c(sum(df_dw_acute_mtmq$dw_acute), nrow(df_dw_acute_mtmq)),
  `Post (Q-Risk)` = c(sum(df_dw_post_mtmq$dw_post), nrow(df_dw_post_mtmq)),
  check.names = FALSE
)

save_reg_table(models_dw_temp_mtmq, "Table_MTM_Q_DW_Temporal_LPM",
               title_text = "Discount Window Usage: MTM x Uninsured Quartile Analysis (LPM)",
               notes_text = "LPM estimates of DW usage during Acute (Mar 13--May 1, 2023) and Post-Acute (May 2--Oct 31, 2023) periods. Base columns include continuous MTM Loss, Uninsured Leverage, and interaction. Q-Risk columns use 16 MTM × Uninsured Leverage quartile dummies. Reference: MTM Q1 × UL Q1. DW data available through 2023-12-31 via FOIA. Sample: DW users vs pure non-users. Heteroskedasticity-robust standard errors.",
               coef_map_use = COEF_MAP_MTM_Q, gof_map_use = gof_lpm, add_rows_use = n_dw_temp_mtmq)

modelsummary(
  models_dw_temp_mtmq,
  stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
  coef_map = COEF_MAP_MTM_Q,
  gof_map = gof_lpm,
  add_rows = n_dw_temp_mtmq,
  title = "Table: DW Temporal - MTM x Uninsured Quartile Analysis (LPM)",
  notes = c("LPM with heteroskedasticity-robust SEs.",
            "DW data only available through 2023-12-31 (via FOIA).",
            "Reference for Q-Risk: MTM Q1 × UL Q1 (lowest risk)."),
  output = "kableExtra"
)
Table: DW Temporal - MTM x Uninsured Quartile Analysis (LPM)
Acute (Base) Post (Base) Acute (Q-Risk) Post (Q-Risk)
MTM Loss (z) 0.016*** 0.008
(0.006) (0.008)
Uninsured Lev (z) 0.013** 0.018**
(0.006) (0.008)
MTM × Uninsured 0.016*** 0.016***
(0.005) (0.006)
MTM Q1 × UL Q2 0.001 −0.041
(0.019) (0.031)
MTM Q1 × UL Q3 0.002 0.021
(0.022) (0.033)
MTM Q1 × UL Q4 −0.024 −0.029
(0.020) (0.031)
MTM Q2 × UL Q1 0.005 −0.013
(0.017) (0.029)
MTM Q2 × UL Q2 −0.000 −0.017
(0.020) (0.032)
MTM Q2 × UL Q3 −0.012 0.008
(0.023) (0.036)
MTM Q2 × UL Q4 0.046 0.030
(0.030) (0.039)
MTM Q3 × UL Q1 0.039* 0.009
(0.023) (0.032)
MTM Q3 × UL Q2 0.005 −0.014
(0.022) (0.034)
MTM Q3 × UL Q3 0.062** 0.026
(0.028) (0.036)
MTM Q3 × UL Q4 0.046* 0.056
(0.027) (0.039)
MTM Q4 × UL Q1 −0.022 −0.031
(0.017) (0.030)
MTM Q4 × UL Q2 0.032 −0.018
(0.025) (0.035)
MTM Q4 × UL Q3 0.049* −0.007
(0.029) (0.037)
MTM Q4 × UL Q4 (Riskiest) 0.068* 0.038
(0.035) (0.046)
Log(Assets) (z) 0.084*** 0.115*** 0.084*** 0.116***
(0.007) (0.009) (0.007) (0.009)
Cash Ratio (z) −0.002 −0.016** −0.002 −0.017**
(0.005) (0.007) (0.005) (0.007)
Loan/Deposit (z) −0.001 0.023*** −0.001 0.022***
(0.005) (0.007) (0.005) (0.007)
Book Equity (z) −0.000 0.001 0.001 −0.001
(0.004) (0.006) (0.004) (0.006)
Wholesale (z) 0.020*** 0.004 0.019*** 0.003
(0.006) (0.007) (0.006) (0.007)
ROA (z) −0.002 −0.007 −0.002 −0.005
(0.005) (0.006) (0.005) (0.006)
Num.Obs. 3668 3513 3678 3523
R2 0.096 0.113 0.099 0.115
R2 Adj. 0.093 0.111 0.093 0.109
N (DW=1) 393.000 773.000 393.000 773.000
N (Sample) 3678.000 3523.000 3678.000 3523.000
* p < 0.1, ** p < 0.05, *** p < 0.01
LPM with heteroskedasticity-robust SEs.
DW data only available through 2023-12-31 (via FOIA).
Reference for Q-Risk: MTM Q1 × UL Q1 (lowest risk).