Depositor Runs = f(Fundamentals, Liquidity Fragility)

Public / common shock (helps coordination in panic) = Mark-to-Market (MTM) losses on securities

Larger MTM losses → worse perceived solvency / collateral value → higher run probability especially in an acute panic window

Liquidity fragility / mismatch (amplifies run incentives) = Uninsured deposit leverage

Higher uninsured deposits (relative to liquid resources/equity) → greater rollover risk and weaker ability to meet withdrawals → higher run probability

Key interaction (panic mechanism)

MTM × Uninsured leverage: Runs rise with (i) public bad news about solvency (MTM) and (ii) rollover fragility (uninsured leverage), and the effect of MTM is strongest when uninsured leverage is high.

Identification Strategy:

Extensive Margin (Crisis Period):

  1. Base Model \[\text{Borrower}_i = \alpha + \beta_1 \cdot \text{MTM\_total}_i + \beta_2 \cdot \text{UninsuredLev}_i + \beta_3 (\text{MTM\_total}_i \times \text{UninsuredLev}_i) + \gamma \mathbf{X}_i + \varepsilon_i\]

Controls: ln_assets, cash_ratio, loan_to_deposit, book_equity_ratio, wholesale, roa We estimate this across crisis periods (1 model*4 borrower) for Anyfed,BTFP, DW borrowers, fhlb abnormal.

Threshold Gradient At low levels of mark-to-market (MTM) losses, banks with higher uninsured leverage are expected to borrow more to preempt run risk. We test this threshold effect by splitting the sample into Uninsured terciles (Low, Medium, High) and estimating the extensive margin separately for each group \(k \in \{L, M, H\}\):

\[ \text{Borrower}_i = \alpha^{(k)} + \beta^{(k)} \cdot \text{mtm_loss}_i + \gamma \mathbf{X}_i + \varepsilon_i \]

Prediction: \(\beta^H > \beta^M > \beta^L\)

A steeper gradient in the high-fragility (High Uninsured) group indicates a lower threshold for emergency borrowing, implying these banks seek liquidity at weak fundamental levels to front-run potential deposit flight. We estimate this across the Acute and Post-Acute periods for BTFP, DW, Both, and Any Facility borrowers.

Intensive Margin

Among borrowers only:

\[\text{Borrow_Amt} = \alpha + \beta_1 \cdot \text{MTM\_total}_i + \beta_2 \cdot \text{UninsuredLev}_i + \beta_3 (\text{MTM\_total}_i \times \text{UninsuredLev}_i) + \gamma \mathbf{X}_i + \varepsilon_i\]

Purpose: Conditional on borrowing, did fragile banks borrow more at higher adjusted equity? Do this by Acute, post-acute (1 model*2 period) across BTFP, DW, both, all borrower

Robustness: Temporal Crisis Vs Arbitrage Repeat Extensive Margin for BTFP (1 model*2 period).

*** Falsefiction Test FHLB borrower(abnormal 10pct)** Repeat Extensive Margin for FHLB Prediction: results will be insignificant or week.

** Other clean test** Add the below for robustness: 1. Another powerful test would be to use insured deposits. run three regression. First, include insured deporit in the basline specification that has uninsured and the interaction with mtm. second replace the uninsured in the baseline regression with insured and see what happens to the main effects. Third run one with just MTM and insured with no interaction.

  1. We have some repeat borrowers.  How many borrowers during the crisis period (or during the acute phase) and then borrowed again during some post-crisis period?  It would interesting to see if these same borrowers display different characteristics during the crisis vs. non-crisis periods - this would be a clean test (same bank, two different time periods!).

1 SETUP

1.1 Packages

rm(list = ls())

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

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

# Tables
library(modelsummary)
library(knitr)
library(kableExtra)

# Statistics
library(DescTools)

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

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

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

1.2 Helper Functions

# ==============================================================================
# CORE HELPERS
# ==============================================================================

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

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

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

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

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

# ==============================================================================
# SUMMARY STATISTICS
# ==============================================================================

summary_stats_function <- function(df, column_list, group_by = NULL) {
  summary_stat_df <- df %>% select(all_of(column_list)) %>% data.table()
  calc_stats <- function(data) {
    data %>%
      summarise(across(everything(), list(
        N    = ~ sum(!is.na(.)),
        Mean = ~ round(mean(., na.rm = TRUE), 3),
        SD   = ~ round(sd(., na.rm = TRUE), 3),
        P10  = ~ round(quantile(., 0.10, na.rm = TRUE), 3),
        P25  = ~ round(quantile(., 0.25, na.rm = TRUE), 3),
        P50  = ~ round(quantile(., 0.50, na.rm = TRUE), 3),
        P75  = ~ round(quantile(., 0.75, na.rm = TRUE), 3),
        P90  = ~ round(quantile(., 0.90, na.rm = TRUE), 3)
      ), .names = "{col}__{fn}")) %>%
      pivot_longer(cols = everything(),
                   names_to = c("Variable", ".value"),
                   names_sep = "__")
  }
  if (!is.null(group_by)) {
    summary_stat_df %>%
      group_by(across(all_of(group_by))) %>%
      calc_stats()
  } else {
    calc_stats(summary_stat_df)
  }
}

# ==============================================================================
# FILE I/O
# ==============================================================================

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

save_table <- function(tbl, filename, caption_text = "") {
  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_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")
}

1.3 Paths

# ==============================================================================
# PATHS — EDIT THIS BLOCK ONLY
# ==============================================================================

BASE_PATH   <- "C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025"
DATA_PROC   <- file.path(BASE_PATH, "01_data/processed")
OUTPUT_PATH <- file.path(BASE_PATH, "03_documentation/GExam_all_borrower")
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)
}

1.4 Key Dates and Analysis Windows

# ==============================================================================
# KEY DATES AND PERIODS
# ==============================================================================

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

# --- Analysis windows (non-overlapping) ---
PRE_CRISIS_START <- as.Date("2023-01-01")
PRE_CRISIS_END   <- as.Date("2023-03-07")

# 1. CRISIS PERIOD (SVB Announcement through First Republic)
CRISIS_START     <- as.Date("2023-03-08") 
CRISIS_END       <- as.Date("2023-05-04") 

# 2. ARBITRAGE PERIOD (Sustained sub-IORB to Pricing Floor)
ARB_START        <- as.Date("2023-11-15")
ARB_END          <- as.Date("2024-01-24")

# 3. WIND-DOWN PERIOD
WIND_START       <- as.Date("2024-01-25")
WIND_END         <- as.Date("2024-03-11")

# --- Dataset bounds ---
DW_DATA_END      <- as.Date("2023-12-31")

cat("=== DATE CONSTANTS SET ===\n")
## === DATE CONSTANTS SET ===
cat("Crisis window:     ", format(CRISIS_START), "to", format(CRISIS_END), "\n")
## Crisis window:      2023-03-08 to 2023-05-04
cat("Arbitrage window:  ", format(ARB_START), "to", format(ARB_END), "\n")
## Arbitrage window:   2023-11-15 to 2024-01-24
cat("Wind-down window:  ", format(WIND_START), "to", format(WIND_END), "\n")
## Wind-down window:   2024-01-25 to 2024-03-11

2 DATA LOADING

2.1 Load Raw Data

# ==============================================================================
# LOAD ALL RAW DATA FILES
# ==============================================================================

# --- Call Reports (quarterly bank financials) ---
call_q <- read_csv(
  file.path(DATA_PROC, "final_call_gsib.csv"),
  show_col_types = FALSE
) %>%
  mutate(idrssd = as.character(idrssd))

# --- BTFP Loan-Level Data ---
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)
  )

# --- Discount Window Loan-Level Data ---
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), "loans\n")
## BTFP Loans:        6734 loans
cat("DW Loans:         ", nrow(dw_loans_raw), "loans\n")
## DW Loans:          10008 loans

2.2 Exclude Failed Banks and G-SIBs

# ==============================================================================
# EXCLUSIONS: Failed banks and G-SIBs
# Identified at BASELINE_MAIN (2022Q4) to keep the exclusion list stable
# ==============================================================================

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
# Apply exclusions to loan data
btfp_loans <- btfp_loans_raw %>% filter(!rssd_id %in% excluded_banks)
dw_loans   <- dw_loans_raw   %>% filter(!rssd_id %in% excluded_banks)

cat("BTFP loans after exclusions:", nrow(btfp_loans), "\n")
## BTFP loans after exclusions: 6695
cat("DW loans after exclusions:  ", nrow(dw_loans), "\n")
## DW loans after exclusions:   9935

3 BORROWER INDICATOR FACTORY

# ==============================================================================
# CREATE PERIOD-SPECIFIC BORROWER INDICATORS FROM LOAN-LEVEL DATA
#
# Returns a data frame with:
#   idrssd          — bank identifier
#   {prefix}        — binary indicator (1 = borrowed in window)
#   {prefix}_amt    — total amount borrowed in window
#   {prefix}_first  — date of first loan in window
# ==============================================================================

# ==============================================================================
# CREATE PERIOD-SPECIFIC BORROWER INDICATORS FROM LOAN-LEVEL DATA
# ==============================================================================

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 BORROWER INDICATORS ---
btfp_crisis <- create_borrower_indicator(
  btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount",
  CRISIS_START, CRISIS_END, "btfp_crisis"
)
btfp_arb <- create_borrower_indicator(
  btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount",
  ARB_START, ARB_END, "btfp_arb"
)
btfp_wind <- create_borrower_indicator(
  btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount",
  WIND_START, WIND_END, "btfp_wind"
)

# --- DISCOUNT WINDOW BORROWER INDICATORS ---
dw_crisis <- create_borrower_indicator(
  dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount",
  CRISIS_START, min(CRISIS_END, DW_DATA_END), "dw_crisis"
)

cat("=== BORROWER COUNTS ===\n")
## === BORROWER COUNTS ===
cat("BTFP:  Crisis=", nrow(btfp_crisis),
    " Arb=",  nrow(btfp_arb),
    " Wind=", nrow(btfp_wind), "\n")
## BTFP:  Crisis= 526  Arb= 780  Wind= 237
cat("DW:    Crisis=", nrow(dw_crisis), "\n")
## DW:    Crisis= 459

4 VARIABLE CONSTRUCTION

4.1 Core Variable Builder

# ==============================================================================
# Variable naming convention:
#   *_raw  — original scale (for descriptive tables)
#   *_w    — winsorized at [2.5%, 97.5%]
#   *      — z-standardized (mean 0, SD 1) — used in all regressions
# ==============================================================================
construct_analysis_vars <- function(baseline_data) {
  baseline_data %>%
    mutate(

      # ======================================================================
      # 1. RAW VARIABLES (original scale, for descriptive tables)
      # ======================================================================

      mtm_total_raw         = mtm_loss_to_total_asset,
      mtm_btfp_raw          = mtm_loss_omo_eligible_to_total_asset,
      mtm_other_raw         = mtm_loss_non_omo_eligible_to_total_asset,
      uninsured_lev_raw     = uninsured_deposit_to_total_asset,
      insured_lev_raw       = r_insured_deposit,
      uninsured_share_raw   = uninsured_to_deposit,

      # Controls
      ln_assets_raw         = log(total_asset),
      cash_ratio_raw        = cash_to_total_asset,
      securities_ratio_raw  = security_to_total_asset,
      loan_ratio_raw        = total_loan_to_total_asset,
      book_equity_ratio_raw = book_equity_to_total_asset,
      tier1_ratio_raw       = tier1cap_to_total_asset,
      roa_raw               = roa,
      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 (forward-looking)
      uninsured_outflow_raw = change_uninsured_fwd_q,
      insured_outflow_raw   = change_insured_deposit_fwd_q,
      total_outflow_raw     = change_total_deposit_fwd_q,

      # ======================================================================
      # 2. SOLVENCY MEASURES
      # ======================================================================

      # Adjusted Equity: Book Equity minus MTM losses (% of assets)
      # This is the theta in Goldstein-Pauzner — the bank's true economic position
      adjusted_equity_raw = book_equity_to_total_asset - mtm_loss_to_total_asset,

      # Market-value assets (for IDCR measures)
      mv_assets = total_asset * (1 - mtm_loss_to_total_asset / 100),

      # Jiang et al. (2023) insolvency measures
      idcr_1 = safe_div(
        mv_assets - 0.5 * uninsured_deposit - insured_deposit,
        insured_deposit, NA_real_
      ),
      idcr_2 = safe_div(
        mv_assets - uninsured_deposit - insured_deposit,
        insured_deposit, NA_real_
      ),

      # Binary solvency indicators
      mtm_insolvent       = as.integer(adjusted_equity_raw < 0),
      mtm_solvent         = as.integer(adjusted_equity_raw >= 0),
      insolvent_idcr_s50  = as.integer(idcr_1 < 0),
      insolvent_idcr_s100 = as.integer(idcr_2 < 0),

      # ======================================================================
      # 3. TRIMMED VARIABLES [2.5%, 97.5%] (Replaces outliers with NA)
      # ======================================================================

      # Primary explanatory
      mtm_total_w         = winsorize(mtm_total_raw),
      mtm_btfp_w          = winsorize(mtm_btfp_raw),
      mtm_other_w         = winsorize(mtm_other_raw),
      uninsured_lev_w     = winsorize(uninsured_lev_raw),
      insured_lev_w       = winsorize(r_insured_deposit),
      adjusted_equity_w   = winsorize(adjusted_equity_raw),

      # Controls
      ln_assets_w           = winsorize(ln_assets_raw),
      cash_ratio_w          = winsorize(cash_ratio_raw),
      book_equity_ratio_w   = winsorize(book_equity_ratio_raw),
      roa_w                 = winsorize(roa_raw),
      loan_to_deposit_w     = winsorize(loan_to_deposit_raw),
      wholesale_w           = winsorize(wholesale_raw),

      # Deposit outflows
      uninsured_outflow_w   = winsorize(uninsured_outflow_raw),
      insured_outflow_w     = winsorize(insured_outflow_raw),
      total_outflow_w       = winsorize(total_outflow_raw),

      # ======================================================================
      # 4. Z-STANDARDIZED VARIABLES (mean = 0, SD = 1)
      # All regression variables use this scale — coefficients are
      # interpreted as the effect of a 1-SD increase.
      # ======================================================================

      # Primary explanatory
      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),
      insured_lev         = standardize_z(insured_lev_w),
      adjusted_equity     = standardize_z(adjusted_equity_w),

      # Controls
      ln_assets           = standardize_z(ln_assets_w),
      cash_ratio          = standardize_z(cash_ratio_w),
      book_equity_ratio   = standardize_z(book_equity_ratio_w),
      roa                 = standardize_z(roa_w),
      loan_to_deposit     = standardize_z(loan_to_deposit_w),
      wholesale           = standardize_z(wholesale_w),

      # Deposit outflows (standardized)
      uninsured_outflow   = standardize_z(uninsured_outflow_w),
      insured_outflow     = standardize_z(insured_outflow_w),
      total_outflow       = standardize_z(total_outflow_w),

      # ======================================================================
      # 5. INTERACTION TERMS
      # Model 1 (Base):        MTM_total × UninsuredLev
      # All built from z-standardized components.
      # ======================================================================

      # Base model interaction
      mtm_x_uninsured          = mtm_total  * uninsured_lev,

      # insured model
      mtm_x_insured    = mtm_total * insured_lev,
      # ======================================================================
      # 6. PAR BENEFIT AND COLLATERAL CAPACITY
      # ======================================================================

      par_benefit_raw = safe_div(
        mtm_btfp_raw,
        mtm_btfp_raw + 100 * safe_div(omo_eligible, total_asset * 1000, 0),
        NA_real_
      ),
      collateral_capacity_raw = safe_div(
        omo_eligible, total_asset * 1000, 0
      ) * 100,

      # ======================================================================
      # 7. CATEGORICAL / GROUPING VARIABLES
      # ======================================================================

      size_cat = factor(
        create_size_category_3(total_asset),
        levels = size_levels_3
      ),

      state        = if ("state"        %in% names(.)) state        else NA_character_,
      fed_district = if ("fed_district" %in% names(.)) fed_district else NA_character_

    ) %>%
    # Par benefit needs prior columns to exist — computed in second mutate pass
    mutate(
      par_benefit_w   = winsorize(par_benefit_raw),
      par_benefit     = standardize_z(par_benefit_w),
      par_x_uninsured = par_benefit * uninsured_lev
    )
}

5 BUILD BASELINE DATASETS

# ==============================================================================
# BUILD BASELINE DATASETS
#
# Each baseline merges the appropriate deposit beta BEFORE calling
# construct_analysis_vars(), so beta_pooled is available inside that function.
#
# Baseline → Deposit Beta used:
#   2022Q4  → beta_pooled  (full-period stable estimate, pre-crisis)
#   2023Q3  → beta_2023    (year-specific, known at arbitrage start)
#   2023Q4  → beta_2023    (year-specific, same year as wind-down)
# ==============================================================================

# Helper: prepare yearly beta for a specific year to reuse the same column name
# --- 2022Q4 Baseline (main crisis analysis) ---
df_2022q4 <- call_q %>%
  filter(
    period  == BASELINE_MAIN,
    !idrssd %in% excluded_banks,
    !is.na(omo_eligible) & omo_eligible > 0
  ) %>%
  construct_analysis_vars()

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

# --- 2023Q4 Baseline (wind-down period) ---
df_2023q4 <- call_q %>%
  filter(
    period  == BASELINE_WIND,
    !idrssd %in% excluded_banks,
    !is.na(omo_eligible) & omo_eligible > 0
  ) %>%
  construct_analysis_vars()

cat("=== BASELINE DATASETS ===\n")
## === BASELINE DATASETS ===
cat("2022Q4:", nrow(df_2022q4), "banks |",
    "Insolvent (AE<0):", sum(df_2022q4$mtm_insolvent, na.rm = TRUE), "\n")
## 2022Q4: 4292 banks | Insolvent (AE<0): 825
cat("2023Q3:", nrow(df_2023q3), "banks\n")
## 2023Q3: 4214 banks
cat("2023Q4:", nrow(df_2023q4), "banks\n")
## 2023Q4: 4197 banks

6 BUILD PERIOD DATASETS

6.1 Helper: Join All Borrower Indicators

# ==============================================================================
# JOIN_ALL_BORROWERS() HELPER
# ==============================================================================

join_all_borrowers <- function(df_base, btfp_df, dw_df, btfp_var, dw_var) {
  df_base %>%
    left_join(btfp_df %>% select(idrssd, starts_with(btfp_var)), by = "idrssd") %>%
    left_join(dw_df   %>% select(idrssd, starts_with(dw_var)),   by = "idrssd") %>%
    mutate(
      "{btfp_var}" := replace_na(!!sym(btfp_var), 0L),
      "{dw_var}"   := replace_na(!!sym(dw_var),   0L),
      fhlb_user = as.integer(abnormal_fhlb_borrowing_10pct == 1),
      
      # any_fed represents your "All" borrower category
      any_fed  = as.integer(!!sym(btfp_var) == 1 | !!sym(dw_var) == 1),
      both_fed = as.integer(!!sym(btfp_var) == 1 & !!sym(dw_var) == 1),
      
      user_group = factor(
        case_when(
          both_fed == 1 ~ "Both",
          !!sym(btfp_var) == 1 ~ "BTFP_Only",
          !!sym(dw_var)   == 1 ~ "DW_Only",
          TRUE                 ~ "Neither"
        ),
        levels = c("Neither", "BTFP_Only", "DW_Only", "Both")
      ),
      non_user = as.integer(any_fed == 0 & fhlb_user == 0)
    )
}

# ==============================================================================
# BUILD PERIOD DATASETS
# ==============================================================================

# --- CRISIS PERIOD (Mar 8 - May 4) ---
df_crisis <- join_all_borrowers(
  df_2022q4, btfp_crisis, dw_crisis, "btfp_crisis", "dw_crisis"
) %>%
  mutate(
    # Intensive-margin variables (amount relative to bank size)
    btfp_pct = ifelse(
      btfp_crisis == 1 & btfp_crisis_amt > 0,
      100 * btfp_crisis_amt / (total_asset * 1000), NA_real_
    ),
    dw_pct = ifelse(
      dw_crisis == 1 & dw_crisis_amt > 0,
      100 * dw_crisis_amt / (total_asset * 1000), NA_real_
    ),
    log_btfp_amt = ifelse(
      btfp_crisis == 1 & btfp_crisis_amt > 0, log(btfp_crisis_amt), NA_real_
    ),
    log_dw_amt = ifelse(
      dw_crisis == 1 & dw_crisis_amt > 0, log(dw_crisis_amt), NA_real_
    )
  )

# --- ARBITRAGE PERIOD (Nov 15 - Jan 24) ---
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, # No DW data needed here per specifications
    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) ---
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)
  )

cat("=== PERIOD DATASET COUNTS ===\n")
## === PERIOD DATASET COUNTS ===
cat("Crisis:    ", nrow(df_crisis),
    " | BTFP=1:", sum(df_crisis$btfp_crisis),
    " | DW=1:",   sum(df_crisis$dw_crisis),
    " | AnyFed:", sum(df_crisis$any_fed), "\n")
## Crisis:     4292  | BTFP=1: 501  | DW=1: 433  | AnyFed: 828

6.2 Build All Period Datasets

# ==============================================================================
# ACUTE PERIOD (2022Q4 baseline)
# ==============================================================================

df_crisis <- join_all_borrowers(
  df_2022q4, btfp_crisis, dw_crisis, "btfp_crisis", "dw_crisis"
) %>%
  mutate(
    # Intensive-margin variables (amount relative to bank size)
    btfp_pct = ifelse(
      btfp_crisis == 1 & btfp_crisis_amt > 0,
      100 * btfp_crisis_amt / (total_asset * 1000), NA_real_
    ),
    dw_pct = ifelse(
      dw_crisis == 1 & dw_crisis_amt > 0,
      100 * dw_crisis_amt / (total_asset * 1000), NA_real_
    ),
    log_btfp_amt = ifelse(
      btfp_crisis == 1 & btfp_crisis_amt > 0, log(btfp_crisis_amt), NA_real_
    ),
    log_dw_amt = ifelse(
      dw_crisis == 1 & dw_crisis_amt > 0, log(dw_crisis_amt), NA_real_
    )
  )

# ==============================================================================
# PRE-BTFP / NARROW WINDOWS (2022Q4 baseline)
# For falsification: before par-value subsidy existed
# ==============================================================================

# Mar 1 – Mar 12 (any DW before BTFP announcement)
btfp_prebtfp <- create_borrower_indicator(
  btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount",
  as.Date("2023-03-01"), as.Date("2023-03-12"), "btfp_prebtfp"
)
## Warning: There was 1 warning in `summarise()`.
## ℹ In argument: `btfp_prebtfp_first = min(btfp_loan_date)`.
## Caused by warning in `min.default()`:
## ! no non-missing arguments to min; returning Inf
dw_prebtfp <- create_borrower_indicator(
  dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount",
  as.Date("2023-03-01"), as.Date("2023-03-12"), "dw_prebtfp"
)
df_prebtfp <- join_all_borrowers(
  df_2022q4, btfp_prebtfp, dw_prebtfp, "btfp_prebtfp", "dw_prebtfp"
)

# SVB closure day only (Mar 10)
btfp_mar10 <- create_borrower_indicator(
  btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount",
  as.Date("2023-03-10"), as.Date("2023-03-10"), "btfp_mar10"
)
## Warning: There was 1 warning in `summarise()`.
## ℹ In argument: `btfp_mar10_first = min(btfp_loan_date)`.
## Caused by warning in `min.default()`:
## ! no non-missing arguments to min; returning Inf
dw_mar10 <- create_borrower_indicator(
  dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount",
  as.Date("2023-03-10"), as.Date("2023-03-10"), "dw_mar10"
)
df_mar10 <- join_all_borrowers(
  df_2022q4, btfp_mar10, dw_mar10, "btfp_mar10", "dw_mar10"
)

# Mar 10 – Mar 13 (SVB closure through BTFP launch)
btfp_mar10_13 <- create_borrower_indicator(
  btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount",
  as.Date("2023-03-10"), as.Date("2023-03-13"), "btfp_mar10_13"
)
dw_mar10_13 <- create_borrower_indicator(
  dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount",
  as.Date("2023-03-10"), as.Date("2023-03-13"), "dw_mar10_13"
)
df_mar10_13 <- join_all_borrowers(
  df_2022q4, btfp_mar10_13, dw_mar10_13, "btfp_mar10_13", "dw_mar10_13"
)

# ==============================================================================
# ARBITRAGE PERIOD (2023Q3 baseline)
# No DW data in this window per design
# ==============================================================================

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 (2023Q4 baseline)
# No DW data in this window per design
# ==============================================================================

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

cat("=== PERIOD DATASET COUNTS ===\n")
## === PERIOD DATASET COUNTS ===
cat("Crisis:    ", nrow(df_crisis),
    " | BTFP=1:", sum(df_crisis$btfp_crisis),
    " | DW=1:",   sum(df_crisis$dw_crisis),
    " | AnyFed:", sum(df_crisis$any_fed),
    " | Non-user:", sum(df_crisis$non_user), "\n")
## Crisis:     4292  | BTFP=1: 501  | DW=1: 433  | AnyFed: 828  | Non-user: 3226
cat("Arb:       ", nrow(df_arb),
    " | BTFP=1:", sum(df_arb$btfp_arb), "\n")
## Arb:        4214  | BTFP=1: 749
cat("Wind-down: ", nrow(df_wind),
    " | BTFP=1:", sum(df_wind$btfp_wind), "\n")
## Wind-down:  4197  | BTFP=1: 229

7 REGRESSION SAMPLES

# ==============================================================================
# PURE COMPARISON SAMPLES
# Each sample isolates a specific borrower type against pure non-borrowers
# ==============================================================================

# --- Extensive Margin: Crisis Period ---
df_btfp_s   <- df_crisis %>% filter(btfp_crisis == 1 | non_user == 1)
df_dw_s     <- df_crisis %>% filter(dw_crisis   == 1 | non_user == 1)
df_anyfed_s <- df_crisis %>% filter(any_fed     == 1 | non_user == 1) # Your "All" Group
df_fhlb_s   <- df_crisis %>% filter(fhlb_user   == 1 | non_user == 1) # Falsification

# --- Solvency Splits: Crisis Period ---
df_btfp_sol <- df_btfp_s %>% filter(mtm_solvent   == 1)
df_btfp_ins <- df_btfp_s %>% filter(mtm_insolvent == 1)
df_dw_sol   <- df_dw_s   %>% filter(mtm_solvent   == 1)
df_dw_ins   <- df_dw_s   %>% filter(mtm_insolvent == 1)

# --- Intensive Margin: Crisis Period (Borrowers Only) ---
df_btfp_int     <- df_crisis %>% filter(btfp_crisis == 1, !is.na(btfp_pct))
df_btfp_int_sol <- df_btfp_int %>% filter(mtm_solvent   == 1)
df_btfp_int_ins <- df_btfp_int %>% filter(mtm_insolvent == 1)

# --- Temporal Robustness: Arbitrage Period ---
df_btfp_arb_s <- df_arb %>% filter(btfp_arb  == 1 | non_user == 1)
df_fhlb_arb_s <- df_arb %>% filter(fhlb_user == 1 | non_user == 1)

# --- Temporal Robustness: Wind-down Period ---
df_btfp_wind_s <- df_wind %>% filter(btfp_wind == 1 | non_user == 1)

cat("=== REGRESSION SAMPLES (Crisis) ===\n")
## === REGRESSION SAMPLES (Crisis) ===
cat("BTFP vs Non-user:   ", nrow(df_btfp_s),   " (BTFP=1:", sum(df_btfp_s$btfp_crisis), ")\n")
## BTFP vs Non-user:    3727  (BTFP=1: 501 )
cat("DW vs Non-user:     ", nrow(df_dw_s),     " (DW=1:",   sum(df_dw_s$dw_crisis), ")\n")
## DW vs Non-user:      3659  (DW=1: 433 )
cat("AnyFed vs Non-user: ", nrow(df_anyfed_s), " (AnyFed=1:", sum(df_anyfed_s$any_fed), ")\n")
## AnyFed vs Non-user:  4054  (AnyFed=1: 828 )
cat("FHLB vs Non-user:   ", nrow(df_fhlb_s),   " (FHLB=1:", sum(df_fhlb_s$fhlb_user), ")\n")
## FHLB vs Non-user:    3528  (FHLB=1: 302 )

8 MODEL INFRASTRUCTURE

# MODEL INFRASTRUCTURE

# ==============================================================================
# CONTROLS STRING
# book_equity_ratio included here (not collinear in MTM framework)
# ==============================================================================

CONTROLS <- "ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + wholesale + roa"

# ==============================================================================
# FORMULA STRINGS: IDENTIFICATION STRATEGY
# Mapped directly to the theoretical framework and robustness tests.
# ==============================================================================

# --- 1. Base Model (Extensive & Intensive Margin) ---
# Borrower_i = a + b1*MTM + b2*Uninsured + b3*(MTM x Uninsured) + Controls
EXPL_BASE <- "mtm_total + uninsured_lev + mtm_x_uninsured"

# --- 2. Threshold Gradient Test ---
# Run within Uninsured terciles (Low, Medium, High)
# Borrower_i = a + b*MTM + Controls
EXPL_GRADIENT <- "mtm_total"

# --- 3. Robustness: Insured Deposits (2 variations) ---
# Test 1: MTM and insured (no interaction
EXPL_INSURED_1 <- "mtm_total + insured_lev"

# Test 2: Replace uninsured with insured in baseline (main effects + interaction)
EXPL_INSURED_2 <- "mtm_total + insured_lev + mtm_x_insured"



# ==============================================================================
# FIXEST VARIABLE DICTIONARY (display labels for etable)
# ==============================================================================

setFixest_dict(c(
  # Primary explanatory
  mtm_total             = "MTM Loss (z)",
  uninsured_lev         = "Uninsured Leverage (z)",
  insured_lev           = "Insured Leverage (z)",

  # Interactions
  mtm_x_uninsured       = "MTM $\\times$ Uninsured",
  mtm_x_insured         = "MTM $\\times$ Insured",

  # Controls
  ln_assets             = "Log(Assets)",
  cash_ratio            = "Cash Ratio",
  loan_to_deposit       = "Loan-to-Deposit",
  book_equity_ratio     = "Book Equity Ratio",
  wholesale             = "Wholesale Funding",
  roa                   = "ROA"
))

# ==============================================================================
# COEFFICIENT DISPLAY ORDER (for etable order = argument)
# ==============================================================================

COEF_ORDER <- c(
  "Constant",
  "MTM Loss",
  "Uninsured Leverage",
  "Insured Leverage",
  "MTM.*Uninsured",
  "MTM.*Insured"
)

# ==============================================================================
# GENERIC MODEL RUNNER
# ==============================================================================

run_model <- function(data, dv, explanatory, 
                      family_type = "lpm", controls = CONTROLS) {
  ff <- as.formula(paste(dv, "~", explanatory, "+", controls))
  
  if (family_type == "lpm") {
    feols(ff, data = data, vcov = "hetero")
  } else if (family_type == "logit") {
    feglm(ff, data = data, family = binomial("logit"), vcov = "hetero")
  } else {
    stop("family_type must be either 'lpm' or 'logit'")
  }
}

# ==============================================================================
# ETABLE SAVER
# ==============================================================================

save_etable <- function(models, filename, title_text, notes_text, 
                        fitstat_use = ~ n + r2, 
                        extra_lines = NULL) {
  etable(
    models,
    title      = title_text,
    notes      = notes_text,
    fitstat    = fitstat_use,
    order      = COEF_ORDER,
    extralines = extra_lines,
    se.below   = TRUE,
    tex        = TRUE,
    file       = file.path(TABLE_PATH, paste0(filename, ".tex")),
    replace    = TRUE,
    style.tex  = style.tex("aer")
  )
  message("Saved: ", filename, ".tex")
}

# ==============================================================================
# VISUALIZATION THEME
# ==============================================================================

theme_paper <- theme_minimal(base_size = 12) +
  theme(
    plot.title       = element_text(face = "bold", size = 13, hjust = 0),
    plot.subtitle    = element_text(size = 10, color = "grey40", hjust = 0),
    legend.position  = "bottom",
    panel.grid.minor = element_blank(),
    strip.text       = element_text(face = "bold", size = 11)
  )

pal_user <- c(
  "DW"                = "#D62828",
  "BTFP"              = "#003049",
  "FHLB"              = "#F77F00",
  "Pure Non-Borrower" = "grey70",
  "Both"              = "#7209B7",
  "AnyFed"            = "#2A9D8F"
)

cat("=== MODEL INFRASTRUCTURE READY ===\n")
## === MODEL INFRASTRUCTURE READY ===
cat("Formulas loaded for Base, Gradient, and Insured Robustness.\n")
## Formulas loaded for Base, Gradient, and Insured Robustness.
cat("Controls:", CONTROLS, "\n")
## Controls: ln_assets + cash_ratio + loan_to_deposit + book_equity_ratio + wholesale + roa

9 Regression Analysis

9.1 Extensive Margin

# ==============================================================================
# EXTENSIVE MARGIN: CRISIS PERIOD
# Base Model: Borrower = MTM + Uninsured + (MTM x Uninsured) + Controls
# ==============================================================================

# 1. Estimate Models (Linear Probability Models)
mod_ext_anyfed <- run_model(df_anyfed_s, "any_fed",     EXPL_BASE, family_type = "lpm")
mod_ext_btfp   <- run_model(df_btfp_s,   "btfp_crisis", EXPL_BASE, family_type = "lpm")
mod_ext_dw     <- run_model(df_dw_s,     "dw_crisis",   EXPL_BASE, family_type = "lpm")
mod_ext_fhlb   <- run_model(df_fhlb_s,   "fhlb_user",   EXPL_BASE, family_type = "lpm")

# Combine into a list for fixest::etable
models_extensive <- list(
  "Any Fed" = mod_ext_anyfed,
  "BTFP"    = mod_ext_btfp,
  "DW"      = mod_ext_dw,
  "FHLB"    = mod_ext_fhlb
)

# 2. Calculate Dependent Variable Means and Count (N) for the Table Footer
mean_anyfed <- round(mean(df_anyfed_s$any_fed,     na.rm = TRUE), 3)
mean_btfp   <- round(mean(df_btfp_s$btfp_crisis,   na.rm = TRUE), 3)
mean_dw     <- round(mean(df_dw_s$dw_crisis,       na.rm = TRUE), 3)
mean_fhlb   <- round(mean(df_fhlb_s$fhlb_user,     na.rm = TRUE), 3)

n_anyfed <- sum(df_anyfed_s$any_fed,     na.rm = TRUE)
n_btfp   <- sum(df_btfp_s$btfp_crisis,   na.rm = TRUE)
n_dw     <- sum(df_dw_s$dw_crisis,       na.rm = TRUE)
n_fhlb   <- sum(df_fhlb_s$fhlb_user,     na.rm = TRUE)

# 3. Save the Table
save_etable(
  models      = models_extensive,
  filename    = "tab_extensive_margin_crisis",
  title_text  = "Extensive Margin: Bank Selection into Emergency Lending Facilities (Crisis Period)",
  notes_text  = "Linear probability models. The dependent variable is an indicator equal to 1 if the bank borrowed from the respective facility during the Crisis Period (March 8 -- May 4, 2023), and 0 if it used no emergency liquidity facilities. FHLB borrowing represents abnormal borrowing (top 10 percent). Standard errors are heteroskedasticity-robust. All continuous independent variables are z-standardized.",
  extra_lines = list(
    c("Borrowers (N)", n_anyfed, n_btfp, n_dw, n_fhlb),
    c("Mean of DV", mean_anyfed, mean_btfp, mean_dw, mean_fhlb)
  )
)

# Print a console summary to check results immediately
cat("=== EXTENSIVE MARGIN RESULTS (CRISIS) ===\n")
## === EXTENSIVE MARGIN RESULTS (CRISIS) ===
etable(models_extensive, fitstat = ~ n + r2, se.below = TRUE)
##                           Any Fed        BTFP         DW       FHLB
## Dependent Var.:           any_fed btfp_crisis  dw_crisis  fhlb_user
##                                                                    
## Constant                0.2081***   0.1435***  0.1272***  0.0908***
##                        (0.0060)    (0.0056)   (0.0054)   (0.0050)  
## MTM Loss (z)            0.0300***   0.0249***  0.0158**   0.0039   
##                        (0.0070)    (0.0062)   (0.0061)   (0.0056)  
## Uninsured Leverage (z)  0.0198**    0.0214**   0.0126     0.0064   
##                        (0.0076)    (0.0070)   (0.0067)   (0.0051)  
## MTM $\times$ Uninsured  0.0199***   0.0193***  0.0170*** -0.0039   
##                        (0.0057)    (0.0052)   (0.0051)   (0.0043)  
## Log(Assets)             0.1064***   0.0713***  0.0920***  0.0266***
##                        (0.0082)    (0.0077)   (0.0077)   (0.0065)  
## Cash Ratio             -0.0246***  -0.0285*** -0.0043    -0.0214***
##                        (0.0061)    (0.0048)   (0.0053)   (0.0039)  
## Loan-to-Deposit        -0.0035     -0.0107    -0.0007     0.0244***
##                        (0.0067)    (0.0057)   (0.0057)   (0.0050)  
## Book Equity Ratio      -0.0135*    -0.0118*   -0.0006     0.0101*  
##                        (0.0055)    (0.0047)   (0.0044)   (0.0041)  
## Wholesale Funding       0.0297***   0.0263***  0.0204**   0.0019   
##                        (0.0069)    (0.0065)   (0.0063)   (0.0051)  
## ROA                    -0.0019     -0.0059    -0.0029    -0.0091*  
##                        (0.0057)    (0.0049)   (0.0048)   (0.0043)  
## ______________________ __________ ___________ __________ __________
## S.E. type              Hete.-rob. Heter.-rob. Hete.-rob. Hete.-rob.
## Observations                4,044       3,717      3,649      3,518
## R2                        0.12097     0.09615    0.10314    0.04090
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

9.2 Threshold Gradient

# ==============================================================================
# THRESHOLD GRADIENT: CRISIS PERIOD
# Model: Borrower = MTM + Controls (Estimated across Uninsured Terciles)
# Prediction: Beta on MTM is steepest for the High Uninsured group.
# ==============================================================================

# 1. Define Uninsured Leverage Terciles using the population (df_crisis)
# This ensures the definition of "High" fragility is consistent across all models.
df_crisis <- df_crisis %>%
  mutate(
    unins_tercile = ntile(uninsured_lev, 3)
  )

# Refresh the pure comparison samples to include the new tercile variable
df_anyfed_s <- df_crisis %>% filter(any_fed     == 1 | non_user == 1)
df_btfp_s   <- df_crisis %>% filter(btfp_crisis == 1 | non_user == 1)
df_dw_s     <- df_crisis %>% filter(dw_crisis   == 1 | non_user == 1)

# 2. Estimate Models by Tercile (Any Fed)
mod_any_L <- run_model(df_anyfed_s %>% filter(unins_tercile == 1), "any_fed", EXPL_GRADIENT, "lpm")
mod_any_M <- run_model(df_anyfed_s %>% filter(unins_tercile == 2), "any_fed", EXPL_GRADIENT, "lpm")
mod_any_H <- run_model(df_anyfed_s %>% filter(unins_tercile == 3), "any_fed", EXPL_GRADIENT, "lpm")

# 3. Estimate Models by Tercile (BTFP)
mod_btfp_L <- run_model(df_btfp_s %>% filter(unins_tercile == 1), "btfp_crisis", EXPL_GRADIENT, "lpm")
mod_btfp_M <- run_model(df_btfp_s %>% filter(unins_tercile == 2), "btfp_crisis", EXPL_GRADIENT, "lpm")
mod_btfp_H <- run_model(df_btfp_s %>% filter(unins_tercile == 3), "btfp_crisis", EXPL_GRADIENT, "lpm")

# 4. Estimate Models by Tercile (DW)
mod_dw_L <- run_model(df_dw_s %>% filter(unins_tercile == 1), "dw_crisis", EXPL_GRADIENT, "lpm")
mod_dw_M <- run_model(df_dw_s %>% filter(unins_tercile == 2), "dw_crisis", EXPL_GRADIENT, "lpm")
mod_dw_H <- run_model(df_dw_s %>% filter(unins_tercile == 3), "dw_crisis", EXPL_GRADIENT, "lpm")

# Combine into a list for fixest::etable
models_gradient <- list(
  "Any Fed (Low)"  = mod_any_L,
  "Any Fed (Med)"  = mod_any_M,
  "Any Fed (High)" = mod_any_H,
  "BTFP (Low)"     = mod_btfp_L,
  "BTFP (Med)"     = mod_btfp_M,
  "BTFP (High)"    = mod_btfp_H,
  "DW (Low)"       = mod_dw_L,
  "DW (Med)"       = mod_dw_M,
  "DW (High)"      = mod_dw_H
)

# 5. Calculate Dependent Variable Means and Counts (N) for the Table Footer
calc_stats <- function(df, dv, terc) {
  sub_df <- df %>% filter(unins_tercile == terc)
  c(
    n_borrowers = sum(sub_df[[dv]], na.rm = TRUE),
    mean_dv     = round(mean(sub_df[[dv]], na.rm = TRUE), 3)
  )
}

stats_any_L <- calc_stats(df_anyfed_s, "any_fed", 1)
stats_any_M <- calc_stats(df_anyfed_s, "any_fed", 2)
stats_any_H <- calc_stats(df_anyfed_s, "any_fed", 3)

stats_btfp_L <- calc_stats(df_btfp_s, "btfp_crisis", 1)
stats_btfp_M <- calc_stats(df_btfp_s, "btfp_crisis", 2)
stats_btfp_H <- calc_stats(df_btfp_s, "btfp_crisis", 3)

stats_dw_L <- calc_stats(df_dw_s, "dw_crisis", 1)
stats_dw_M <- calc_stats(df_dw_s, "dw_crisis", 2)
stats_dw_H <- calc_stats(df_dw_s, "dw_crisis", 3)

# 6. Save the Table
save_etable(
  models      = models_gradient,
  filename    = "tab_threshold_gradient",
  title_text  = "Threshold Gradient: Borrowing Sensitivity to MTM Losses by Fragility Level",
  notes_text  = "Linear probability models estimated separately across terciles of Uninsured Leverage (Low, Medium, High). The dependent variable is an indicator equal to 1 if the bank borrowed from the respective facility during the Crisis Period, and 0 if it used no emergency liquidity facilities. Standard errors are heteroskedasticity-robust. All continuous independent variables are z-standardized.",
  extra_lines = list(
    c("Uninsured Lev.", "Low", "Medium", "High", "Low", "Medium", "High", "Low", "Medium", "High"),
    c("Borrowers (N)", 
      stats_any_L["n_borrowers"], stats_any_M["n_borrowers"], stats_any_H["n_borrowers"],
      stats_btfp_L["n_borrowers"], stats_btfp_M["n_borrowers"], stats_btfp_H["n_borrowers"],
      stats_dw_L["n_borrowers"], stats_dw_M["n_borrowers"], stats_dw_H["n_borrowers"]),
    c("Mean of DV", 
      stats_any_L["mean_dv"], stats_any_M["mean_dv"], stats_any_H["mean_dv"],
      stats_btfp_L["mean_dv"], stats_btfp_M["mean_dv"], stats_btfp_H["mean_dv"],
      stats_dw_L["mean_dv"], stats_dw_M["mean_dv"], stats_dw_H["mean_dv"])
  )
)

# Print a console summary to check results immediately
cat("=== THRESHOLD GRADIENT RESULTS ===\n")
## === THRESHOLD GRADIENT RESULTS ===
cat("Prediction: Beta on MTM should increase from Low -> Medium -> High\n")
## Prediction: Beta on MTM should increase from Low -> Medium -> High
etable(models_gradient, fitstat = ~ n + r2, se.below = TRUE)
##                   Any Fed .. Any Fed ...1 Any Fed ...2  BTFP (Low)  BTFP (Med)
## Dependent Var.:      any_fed      any_fed      any_fed btfp_crisis btfp_crisis
##                                                                               
## Constant           0.1826***    0.2028***    0.2163***   0.1203***   0.1344***
##                   (0.0132)     (0.0105)     (0.0126)    (0.0119)    (0.0094)  
## MTM Loss (z)       0.0138       0.0169       0.0487***   0.0099      0.0072   
##                   (0.0088)     (0.0128)     (0.0145)    (0.0072)    (0.0105)  
## Log(Assets)        0.1015***    0.0849***    0.1266***   0.0718***   0.0586***
##                   (0.0135)     (0.0157)     (0.0129)    (0.0120)    (0.0145)  
## Cash Ratio        -0.0107      -0.0225      -0.0388**   -0.0117     -0.0355***
##                   (0.0084)     (0.0120)     (0.0119)    (0.0062)    (0.0096)  
## Loan-to-Deposit   -0.0046       0.0047      -0.0120     -0.0032     -0.0212   
##                   (0.0087)     (0.0152)     (0.0139)    (0.0072)    (0.0131)  
## Book Equity Ratio -0.0144*     -0.0368*     -0.0071     -0.0097     -0.0268*  
##                   (0.0064)     (0.0144)     (0.0140)    (0.0053)    (0.0128)  
## Wholesale Funding  0.0071       0.0589***    0.0252      0.0028      0.0567***
##                   (0.0089)     (0.0132)     (0.0138)    (0.0067)    (0.0136)  
## ROA               -0.0101       0.0145       0.0015     -0.0121      0.0078   
##                   (0.0080)     (0.0115)     (0.0112)    (0.0066)    (0.0099)  
## _________________ __________   __________   __________ ___________ ___________
## S.E. type         Hete.-rob.   Hete.-rob.   Hete.-rob. Heter.-rob. Heter.-rob.
## Observations           1,358        1,343        1,343       1,286       1,230
## R2                   0.09509      0.08573      0.13268     0.07578     0.08066
## 
##                   BTFP (High)   DW (Low)   DW (Med)  DW (High)
## Dependent Var.:   btfp_crisis  dw_crisis  dw_crisis  dw_crisis
##                                                               
## Constant            0.1578***  0.1070***  0.1213***  0.1207***
##                    (0.0117)   (0.0117)   (0.0093)   (0.0105)  
## MTM Loss (z)        0.0384**   0.0014     0.0114     0.0325*  
##                    (0.0134)   (0.0070)   (0.0112)   (0.0129)  
## Log(Assets)         0.0853***  0.0684***  0.0701***  0.1278***
##                    (0.0132)   (0.0122)   (0.0141)   (0.0126)  
## Cash Ratio         -0.0411*** -0.0027     0.0105    -0.0154   
##                    (0.0099)   (0.0072)   (0.0106)   (0.0105)  
## Loan-to-Deposit    -0.0140    -0.0070     0.0246    -0.0185   
##                    (0.0127)   (0.0069)   (0.0132)   (0.0124)  
## Book Equity Ratio  -0.0179    -0.0072    -0.0194     0.0103   
##                    (0.0122)   (0.0049)   (0.0109)   (0.0126)  
## Wholesale Funding   0.0240     0.0061     0.0342**   0.0236   
##                    (0.0138)   (0.0076)   (0.0131)   (0.0132)  
## ROA                -0.0026    -0.0041     0.0048    -0.0004   
##                    (0.0097)   (0.0067)   (0.0098)   (0.0099)  
## _________________ ___________ __________ __________ __________
## S.E. type         Heter.-rob. Hete.-rob. Hete.-rob. Hete.-rob.
## Observations            1,201      1,269      1,200      1,180
## R2                    0.10041    0.06042    0.06120    0.13847
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

9.3 Intensive Margin

# ==============================================================================
# INTENSIVE MARGIN: CRISIS PERIOD
# Base Model: Borrow_Pct = MTM + Uninsured + (MTM x Uninsured) + Controls
# Estimated ONLY among banks that borrowed (Amount > 0)
# ==============================================================================

# 0. Ensure DW intensive margin datasets exist (filtering from df_crisis)
df_dw_int     <- df_crisis %>% filter(dw_crisis == 1, !is.na(dw_pct))
df_dw_int_sol <- df_dw_int %>% filter(mtm_solvent   == 1)
df_dw_int_ins <- df_dw_int %>% filter(mtm_insolvent == 1)

# 1. Estimate Models for BTFP
mod_int_btfp     <- run_model(df_btfp_int,     "btfp_pct", EXPL_BASE, family_type = "lpm")
mod_int_btfp_sol <- run_model(df_btfp_int_sol, "btfp_pct", EXPL_BASE, family_type = "lpm")
mod_int_btfp_ins <- run_model(df_btfp_int_ins, "btfp_pct", EXPL_BASE, family_type = "lpm")

# 2. Estimate Models for DW
mod_int_dw       <- run_model(df_dw_int,     "dw_pct", EXPL_BASE, family_type = "lpm")
mod_int_dw_sol   <- run_model(df_dw_int_sol, "dw_pct", EXPL_BASE, family_type = "lpm")
mod_int_dw_ins   <- run_model(df_dw_int_ins, "dw_pct", EXPL_BASE, family_type = "lpm")

# Combine into a list
models_intensive <- list(
  "BTFP (All)" = mod_int_btfp,
  "BTFP (Sol)" = mod_int_btfp_sol,
  "BTFP (Ins)" = mod_int_btfp_ins,
  "DW (All)"   = mod_int_dw,
  "DW (Sol)"   = mod_int_dw_sol,
  "DW (Ins)"   = mod_int_dw_ins
)

# 3. Calculate Dependent Variable Means
mean_btfp_all <- round(mean(df_btfp_int$btfp_pct,     na.rm = TRUE), 2)
mean_btfp_sol <- round(mean(df_btfp_int_sol$btfp_pct, na.rm = TRUE), 2)
mean_btfp_ins <- round(mean(df_btfp_int_ins$btfp_pct, na.rm = TRUE), 2)
mean_dw_all   <- round(mean(df_dw_int$dw_pct,         na.rm = TRUE), 2)
mean_dw_sol   <- round(mean(df_dw_int_sol$dw_pct,     na.rm = TRUE), 2)
mean_dw_ins   <- round(mean(df_dw_int_ins$dw_pct,     na.rm = TRUE), 2)

# 4. Save the Table
save_etable(
  models      = models_intensive,
  filename    = "tab_intensive_margin_crisis",
  title_text  = "Intensive Margin: Borrowing Amounts as a Percentage of Assets",
  notes_text  = "OLS regressions conditional on borrowing. The dependent variable is the total amount borrowed from the BTFP or DW during the Crisis Period scaled by total assets (in percent). The sample is restricted to banks that borrowed from the respective facility. Standard errors are heteroskedasticity-robust. All continuous independent variables are z-standardized.",
  extra_lines = list(
    c("Mean Borrowing (%)", 
      mean_btfp_all, mean_btfp_sol, mean_btfp_ins,
      mean_dw_all, mean_dw_sol, mean_dw_ins)
  )
)

# Print summary
cat("=== INTENSIVE MARGIN RESULTS (CRISIS) ===\n")
## === INTENSIVE MARGIN RESULTS (CRISIS) ===
etable(models_intensive, fitstat = ~ n + r2, se.below = TRUE)
##                        BTFP (A.. BTFP (S..  BTFP (..  DW (All)  DW (Sol)
## Dependent Var.:         btfp_pct  btfp_pct  btfp_pct    dw_pct    dw_pct
##                                                                         
## Constant                7.791***  7.879***  -0.8703    3.943**    3.687*
##                        (1.113)   (1.091)    (2.460)   (1.512)    (1.690)
## MTM Loss (z)           -1.199    -1.744      1.384     0.6626     2.017 
##                        (1.026)   (1.491)    (0.9999)  (1.993)    (2.859)
## Uninsured Leverage (z) -1.622    -1.919      1.174     2.280      3.694 
##                        (1.477)   (1.495)    (1.766)   (2.036)    (2.670)
## MTM $\times$ Uninsured  1.392     1.475     -0.4346    1.861      3.067 
##                        (0.9568)  (1.110)    (1.528)   (1.444)    (2.103)
## Log(Assets)             1.742     1.907      0.6483    3.102      2.971 
##                        (1.287)   (1.408)    (0.8416)  (1.844)    (2.090)
## Cash Ratio              2.072     2.394     -0.2366   -2.279     -1.745 
##                        (1.626)   (1.836)    (1.487)   (1.457)    (1.552)
## Loan-to-Deposit        -2.639    -3.074     -1.285    -0.6124     0.2873
##                        (1.565)   (2.037)    (1.053)   (2.508)    (2.692)
## Book Equity Ratio       0.8910    1.209     -3.887    -0.2767    -0.0739
##                        (0.8683)  (1.212)    (1.971)   (2.008)    (1.767)
## Wholesale Funding       0.6748    0.9329*    0.0674    2.524*     3.174*
##                        (0.3504)  (0.4665)   (0.4649)  (1.265)    (1.544)
## ROA                    -0.1763    0.3057    -2.936*   -0.0455    -0.0678
##                        (0.8310)  (0.9396)   (1.407)   (1.059)    (1.086)
## ______________________ _________ _________  ________  ________  ________
## S.E. type              Het.-rob. Het.-rob. Het.-rob. Het.-rob. Het.-rob.
## Observations                 501       357       144       433       348
## R2                       0.11747   0.15575   0.14633   0.03726   0.04980
## 
##                         DW (Ins)
## Dependent Var.:           dw_pct
##                                 
## Constant                  6.843 
##                         (12.13) 
## MTM Loss (z)             -5.750 
##                          (4.689)
## Uninsured Leverage (z)   -8.548 
##                          (5.959)
## MTM $\times$ Uninsured    7.179 
##                          (4.387)
## Log(Assets)               2.736 
##                          (2.273)
## Cash Ratio               -2.260 
##                          (4.605)
## Loan-to-Deposit           0.4213
##                          (5.610)
## Book Equity Ratio        -6.181 
##                          (8.297)
## Wholesale Funding         0.2798
##                          (2.252)
## ROA                      -0.9944
##                          (2.872)
## ______________________  ________
## S.E. type              Het.-rob.
## Observations                  85
## R2                       0.04547
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

9.4 Robustness: Insured Deposits

# ==============================================================================
# ROBUSTNESS: INSURED DEPOSITS
# Tests whether the panic mechanism is specifically driven by UNINSURED leverage
# Sample: BTFP vs Pure Non-Borrowers (Crisis Period)
# ==============================================================================

# Model 1: Include insured deposits in the baseline specification
mod_ins_1 <- run_model(df_btfp_s, "btfp_crisis", EXPL_INSURED_1, family_type = "lpm")

# Model 2: Replace uninsured with insured in baseline (main effects + interaction)
mod_ins_2 <- run_model(df_btfp_s, "btfp_crisis", EXPL_INSURED_2, family_type = "lpm")


models_insured <- list(
  "Insured"       = mod_ins_1,
  "Interaction" = mod_ins_2

)

# Save the Table
save_etable(
  models      = models_insured,
  filename    = "tab_robustness_insured",
  title_text  = "Robustness: The Role of Insured vs. Uninsured Deposits",
  notes_text  = "Linear probability models predicting BTFP borrowing during the Crisis Period. Model 1 Insured Leverage to the baseline model. Model 2 with the interaction term. Standard errors are heteroskedasticity-robust."
)

cat("=== ROBUSTNESS: INSURED DEPOSITS ===\n")
## === ROBUSTNESS: INSURED DEPOSITS ===
etable(models_insured, fitstat = ~ n + r2, se.below = TRUE)
##                          Insured Interaction
## Dependent Var.:      btfp_crisis btfp_crisis
##                                             
## Constant               0.1410***   0.1439***
##                       (0.0055)    (0.0057)  
## MTM Loss (z)           0.0214***   0.0233***
##                       (0.0057)    (0.0059)  
## Insured Leverage (z)  -0.0291***  -0.0334***
##                       (0.0066)    (0.0072)  
## Log(Assets)            0.0659***   0.0653***
##                       (0.0076)    (0.0076)  
## Cash Ratio            -0.0304***  -0.0296***
##                       (0.0048)    (0.0048)  
## Loan-to-Deposit       -0.0134*    -0.0142*  
##                       (0.0058)    (0.0059)  
## Book Equity Ratio     -0.0252***  -0.0234***
##                       (0.0051)    (0.0052)  
## Wholesale Funding      0.0234***   0.0229***
##                       (0.0065)    (0.0065)  
## ROA                   -0.0053     -0.0049   
##                       (0.0049)    (0.0049)  
## MTM $\times$ Insured              -0.0131** 
##                                   (0.0048)  
## ____________________ ___________ ___________
## S.E. type            Heter.-rob. Heter.-rob.
## Observations               3,717       3,717
## R2                       0.09614     0.09770
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

9.5 Temporal Robustness: Crisis vs. Arbitrage

# ==============================================================================
# TEMPORAL ROBUSTNESS: CRISIS VS. ARBITRAGE
# Model: Borrower = MTM + Uninsured + (MTM x Uninsured) + Controls
# Prediction: The GP run mechanism (MTM x Uninsured) should be strong in the 
# Crisis period but weak/insignificant in the Arbitrage period.
# ==============================================================================

# 1. Estimate Models (Linear Probability Models)
# Crisis Period (BTFP vs pure non-borrowers)
mod_temp_crisis <- run_model(df_btfp_s, "btfp_crisis", EXPL_BASE, family_type = "lpm")

# Arbitrage Period (BTFP vs pure non-borrowers)
mod_temp_arb    <- run_model(df_btfp_arb_s, "btfp_arb", EXPL_BASE, family_type = "lpm")

# Combine into a list for fixest::etable
models_temporal <- list(
  "Crisis (Mar-May)"    = mod_temp_crisis,
  "Arbitrage (Nov-Jan)" = mod_temp_arb
)

# 2. Calculate Dependent Variable Means and Counts (N) for the Table Footer
mean_crisis <- round(mean(df_btfp_s$btfp_crisis, na.rm = TRUE), 3)
mean_arb    <- round(mean(df_btfp_arb_s$btfp_arb,    na.rm = TRUE), 3)

n_crisis <- sum(df_btfp_s$btfp_crisis, na.rm = TRUE)
n_arb    <- sum(df_btfp_arb_s$btfp_arb,    na.rm = TRUE)

# 3. Save the Table
save_etable(
  models      = models_temporal,
  filename    = "tab_temporal_robustness",
  title_text  = "Temporal Robustness: Crisis Panic vs. Arbitrage Borrowing",
  notes_text  = "Linear probability models comparing BTFP borrowing during the Crisis Period (March 8 -- May 4, 2023) versus the Arbitrage Period (Nov 15, 2023 -- Jan 24, 2024). The dependent variable is an indicator equal to 1 if the bank borrowed from the BTFP during the respective period, and 0 if it was a non-borrower. Standard errors are heteroskedasticity-robust. All continuous independent variables are z-standardized.",
  extra_lines = list(
    c("Borrowers (N)", n_crisis, n_arb),
    c("Mean of DV", mean_crisis, mean_arb)
  )
)

# Print a console summary to check results immediately
cat("=== TEMPORAL ROBUSTNESS (CRISIS VS ARBITRAGE) ===\n")
## === TEMPORAL ROBUSTNESS (CRISIS VS ARBITRAGE) ===
etable(models_temporal, fitstat = ~ n + r2, se.below = TRUE)
##                        Crisis (M.. Arbitrag..
## Dependent Var.:        btfp_crisis   btfp_arb
##                                              
## Constant                 0.1435***  0.1865***
##                         (0.0056)   (0.0057)  
## MTM Loss (z)             0.0249***  0.0229***
##                         (0.0062)   (0.0068)  
## Uninsured Leverage (z)   0.0214**   0.0128   
##                         (0.0070)   (0.0066)  
## MTM $\times$ Uninsured   0.0193***  0.0082   
##                         (0.0052)   (0.0051)  
## Log(Assets)              0.0713***  0.0496***
##                         (0.0077)   (0.0069)  
## Cash Ratio              -0.0285*** -0.0362***
##                         (0.0048)   (0.0057)  
## Loan-to-Deposit         -0.0107    -0.0033   
##                         (0.0057)   (0.0062)  
## Book Equity Ratio       -0.0118*   -0.0053   
##                         (0.0047)   (0.0056)  
## Wholesale Funding        0.0263***  0.1030***
##                         (0.0065)   (0.0075)  
## ROA                     -0.0059    -0.0236***
##                         (0.0049)   (0.0057)  
## ______________________ ___________ __________
## S.E. type              Heter.-rob. Hete.-rob.
## Observations                 3,717      4,038
## R2                         0.09615    0.14406
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

9.6 Repeat Borrowers (Crisis vs. Arbitrage)

9.7 Repeat Borrowers Characteristics

# ==============================================================================
# REPEAT BORROWERS VS. SINGLE-PERIOD BORROWERS
# Question: Are repeat borrowers fundamentally different from banks that 
# only borrowed during the Crisis or only borrowed during the Arbitrage window?
# ==============================================================================

# 1. Create DW Arbitrage indicator (Nov 15 to Dec 31, 2023)
dw_arb_temp <- create_borrower_indicator(
  dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount",
  ARB_START, DW_DATA_END, "dw_arb"
)

# 2. Merge indicators onto the 2022Q4 baseline and classify bank types
df_repeat_analysis <- df_2022q4 %>%
  left_join(btfp_crisis %>% select(idrssd, btfp_crisis), by = "idrssd") %>%
  left_join(btfp_arb    %>% select(idrssd, btfp_arb),    by = "idrssd") %>%
  left_join(dw_crisis   %>% select(idrssd, dw_crisis),   by = "idrssd") %>%
  left_join(dw_arb_temp %>% select(idrssd, dw_arb),      by = "idrssd") %>%
  mutate(
    across(c(btfp_crisis, btfp_arb, dw_crisis, dw_arb), ~replace_na(., 0L)),
    
    # BTFP Classifications
    btfp_status = case_when(
      btfp_crisis == 1 & btfp_arb == 1 ~ "Repeat",
      btfp_crisis == 1 & btfp_arb == 0 ~ "Crisis_Only",
      btfp_crisis == 0 & btfp_arb == 1 ~ "Arb_Only",
      TRUE ~ "Neither"
    ),
    btfp_is_repeat = ifelse(btfp_status == "Repeat", 1, 0),
    
    # DW Classifications
    dw_status = case_when(
      dw_crisis == 1 & dw_arb == 1 ~ "Repeat",
      dw_crisis == 1 & dw_arb == 0 ~ "Crisis_Only",
      dw_crisis == 0 & dw_arb == 1 ~ "Arb_Only",
      TRUE ~ "Neither"
    ),
    dw_is_repeat = ifelse(dw_status == "Repeat", 1, 0)
  )

# Print Group Counts
cat("=== BANK CLASSIFICATIONS ===\n")
## === BANK CLASSIFICATIONS ===
cat("BTFP -> Repeat:", sum(df_repeat_analysis$btfp_status == "Repeat"), 
    " | Crisis-Only:", sum(df_repeat_analysis$btfp_status == "Crisis_Only"), 
    " | Arb-Only:", sum(df_repeat_analysis$btfp_status == "Arb_Only"), "\n")
## BTFP -> Repeat: 245  | Crisis-Only: 256  | Arb-Only: 504
cat("DW   -> Repeat:", sum(df_repeat_analysis$dw_status == "Repeat"), 
    " | Crisis-Only:", sum(df_repeat_analysis$dw_status == "Crisis_Only"), 
    " | Arb-Only:", sum(df_repeat_analysis$dw_status == "Arb_Only"), "\n\n")
## DW   -> Repeat: 120  | Crisis-Only: 313  | Arb-Only: 248
# 3. Build Comparison Samples
# A. BTFP: Repeat vs. Crisis-Only (Sample: All Crisis Borrowers)
df_btfp_rep_vs_cris <- df_repeat_analysis %>% filter(btfp_status %in% c("Repeat", "Crisis_Only"))

# B. BTFP: Repeat vs. Arb-Only (Sample: All Arbitrage Borrowers)
df_btfp_rep_vs_arb  <- df_repeat_analysis %>% filter(btfp_status %in% c("Repeat", "Arb_Only"))

# C. DW: Repeat vs. Crisis-Only (Sample: All DW Crisis Borrowers)
df_dw_rep_vs_cris   <- df_repeat_analysis %>% filter(dw_status %in% c("Repeat", "Crisis_Only"))

# D. DW: Repeat vs. Arb-Only (Sample: All DW Arbitrage Borrowers)
df_dw_rep_vs_arb    <- df_repeat_analysis %>% filter(dw_status %in% c("Repeat", "Arb_Only"))

# 4. Estimate Models
mod_btfp_rc <- run_model(df_btfp_rep_vs_cris, "btfp_is_repeat", EXPL_BASE, family_type = "lpm")
mod_btfp_ra <- run_model(df_btfp_rep_vs_arb,  "btfp_is_repeat", EXPL_BASE, family_type = "lpm")
mod_dw_rc   <- run_model(df_dw_rep_vs_cris,   "dw_is_repeat",   EXPL_BASE, family_type = "lpm")
mod_dw_ra   <- run_model(df_dw_rep_vs_arb,    "dw_is_repeat",   EXPL_BASE, family_type = "lpm")

# Combine into a list
models_repeat_chars <- list(
  "BTFP: vs Crisis-Only" = mod_btfp_rc,
  "BTFP: vs Arb-Only"    = mod_btfp_ra,
  "DW: vs Crisis-Only"   = mod_dw_rc,
  "DW: vs Arb-Only"      = mod_dw_ra
)

# 5. Save the Table
save_etable(
  models      = models_repeat_chars,
  filename    = "tab_repeat_borrower_characteristics",
  title_text  = "Extensive Margin: What Drives a Bank to Become a Repeat Borrower?",
  notes_text  = "Linear probability models comparing the characteristics of repeat borrowers against single-period borrowers. The dependent variable is 1 if the bank borrowed in BOTH the Crisis and Arbitrage periods. In Columns 1 and 3, the sample is restricted to Crisis borrowers (baseline 0 = Crisis-Only). In Columns 2 and 4, the sample is restricted to Arbitrage borrowers (baseline 0 = Arbitrage-Only). Standard errors are heteroskedasticity-robust.",
  extra_lines = list(
    c("Repeaters (N)", 
      sum(df_btfp_rep_vs_cris$btfp_is_repeat), sum(df_btfp_rep_vs_arb$btfp_is_repeat),
      sum(df_dw_rep_vs_cris$dw_is_repeat),     sum(df_dw_rep_vs_arb$dw_is_repeat)),
    c("Comparison Group (N)", 
      sum(df_btfp_rep_vs_cris$btfp_is_repeat == 0), sum(df_btfp_rep_vs_arb$btfp_is_repeat == 0),
      sum(df_dw_rep_vs_cris$dw_is_repeat == 0),     sum(df_dw_rep_vs_arb$dw_is_repeat == 0))
  )
)

# Print summary
cat("=== REPEAT BORROWER CHARACTERISTICS ===\n")
## === REPEAT BORROWER CHARACTERISTICS ===
etable(models_repeat_chars, fitstat = ~ n + r2, se.below = TRUE)
##                        BTFP: vs Cri.. BTFP: vs Arb.. DW: vs Cri.. DW: vs Arb..
## Dependent Var.:        btfp_is_repeat btfp_is_repeat dw_is_repeat dw_is_repeat
##                                                                               
## Constant                    0.4233***      0.2425***    0.2521***    0.2820***
##                            (0.0285)       (0.0190)     (0.0280)     (0.0266)  
## MTM Loss (z)                0.0275         0.0049      -0.0425      -0.0079   
##                            (0.0291)       (0.0208)     (0.0266)     (0.0253)  
## Uninsured Leverage (z)      0.0537*        0.0695**     0.0093       0.0503   
##                            (0.0236)       (0.0219)     (0.0243)     (0.0259)  
## MTM $\times$ Uninsured     -0.0565*       -0.0406      -0.0300       0.0077   
##                            (0.0233)       (0.0208)     (0.0235)     (0.0211)  
## Log(Assets)                 0.0074         0.0419      -0.0114       0.0048   
##                            (0.0263)       (0.0229)     (0.0257)     (0.0279)  
## Cash Ratio                 -0.1465***     -0.1012***   -0.0404      -0.0299   
##                            (0.0373)       (0.0297)     (0.0284)     (0.0373)  
## Loan-to-Deposit            -0.0561        -0.0189       0.0179       0.0135   
##                            (0.0334)       (0.0248)     (0.0283)     (0.0360)  
## Book Equity Ratio           0.0509        -0.0249      -0.0608      -0.0430   
##                            (0.0333)       (0.0230)     (0.0352)     (0.0366)  
## Wholesale Funding          -0.0244         0.0183       0.0457*      0.1014***
##                            (0.0177)       (0.0165)     (0.0201)     (0.0232)  
## ROA                        -0.0043        -0.0010      -0.0085       0.0060   
##                            (0.0278)       (0.0195)     (0.0243)     (0.0280)  
## ______________________ ______________ ______________ ____________ ____________
## S.E. type              Heterosk.-rob. Heterosk.-rob. Hetero.-rob. Hetero.-rob.
## Observations                      501            749          433          368
## R2                            0.04791        0.05193      0.03418      0.07329
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

10 EMERGENCY Borrowing FACILITY ACTIVITY BY PERIOD

# ==============================================================================
# TABLE: EMERGENCY LENDING FACILITY ACTIVITY BY PERIOD
# Panel A: All Fed | Panel B: Discount Window | Panel C: BTFP | Panel D: FHLB
#
# Run AFTER analysis_dataprep.Rmd so these objects exist:
#   btfp_loans, dw_loans, call_q, excluded_banks
#   All date constants, TABLE_PATH, safe_writeLines()
# ==============================================================================

# ==============================================================================
# 1. PERIOD DEFINITIONS
# ==============================================================================
# Loan-level windows (used for DW, BTFP, and All Fed)
periods <- list(
  "Pre-BTFP"    = list(start = as.Date("2023-03-01"), end = as.Date("2023-03-12")),
  "Acute"       = list(start = as.Date("2023-03-08"), end = as.Date("2023-03-15")),
  "Post-Acute"  = list(start = as.Date("2023-03-16"), end = as.Date("2023-05-04")),
  "Arbitrage"   = list(start = as.Date("2023-11-15"), end = as.Date("2024-01-24")),
  "Entire BTFP" = list(start = as.Date("2023-03-13"), end = as.Date("2024-03-11"))
)

col_names <- names(periods)

# FHLB uses Call Report quarters — map each period to quarter(s) to include.
fhlb_period_quarters <- list(
  "Pre-BTFP"    = c("2022Q4"),
  "Acute"       = c("2022Q4"),
  "Post-Acute"  = c("2023Q1"),
  "Arbitrage"   = c("2023Q3"),
  "Entire BTFP" = c("2023Q1", "2023Q2", "2023Q3", "2023Q4")
)

# ==============================================================================
# 2. FORMATTERS
# ==============================================================================

fmt_num <- function(x, digits = 2) {
  ifelse(
    is.na(x) | is.nan(x) | is.infinite(x), "---",
    formatC(x, format = "f", digits = digits, big.mark = ",")
  )
}

fmt_int <- function(x) {
  ifelse(
    is.na(x) | is.nan(x), "---",
    formatC(as.integer(round(x)), format = "d", big.mark = ",")
  )
}

# ==============================================================================
# 3. PANEL A: ALL FED BORROWING (BTFP + DW)
# ==============================================================================

build_all_fed_panel <- function() {
  
  rows <- list()
  
  for (col in col_names) {
    s <- periods[[col]]$start
    e <- periods[[col]]$end
    
    # Cap DW end date based on availability
    e_capped_dw <- min(e, DW_DATA_END)
    
    # 1. Pull DW Loans
    if (s <= DW_DATA_END) {
      df_dw <- dw_loans %>% 
        filter(dw_loan_date >= s, dw_loan_date <= e_capped_dw) %>%
        select(rssd_id, amount = dw_loan_amount)
    } else {
      df_dw <- tibble(rssd_id = character(), amount = numeric())
    }
    
    # 2. Pull BTFP Loans
    if (col == "Pre-BTFP") {
      df_btfp <- tibble(rssd_id = character(), amount = numeric())
    } else {
      df_btfp <- btfp_loans %>% 
        filter(btfp_loan_date >= s, btfp_loan_date <= e) %>%
        select(rssd_id, amount = btfp_loan_amount)
    }
    
    # Combine
    df_all <- bind_rows(df_dw, df_btfp)
    
    if (nrow(df_all) == 0) {
      rows[[col]] <- list(banks = 0, loans = 0, total_b = 0, mean_m = NA, median_m = NA)
      next
    }
    
    rows[[col]] <- list(
      banks    = n_distinct(df_all$rssd_id),
      loans    = nrow(df_all),
      total_b  = sum(df_all$amount, na.rm = TRUE) / 1e9,
      mean_m   = mean(df_all$amount, na.rm = TRUE) / 1e6,
      median_m = median(df_all$amount, na.rm = TRUE) / 1e6
    )
  }
  
  metrics <- c("Banks (N)", "Loans (N)", "Total ($B)", "Mean ($M)", "Median ($M)")
  
  map_dfr(col_names, function(col) {
    r <- rows[[col]]
    tibble(
      Metric = metrics,
      Period = col,
      Value  = c(
        fmt_int(r$banks),   fmt_int(r$loans),
        fmt_num(r$total_b), fmt_num(r$mean_m), fmt_num(r$median_m)
      )
    )
  }) %>%
    pivot_wider(names_from = Period, values_from = Value) %>%
    select(Metric, all_of(col_names))
}

# ==============================================================================
# 4. PANEL B: DISCOUNT WINDOW
# ==============================================================================

build_dw_panel <- function() {

  rows <- list()

  for (col in col_names) {

    s        <- periods[[col]]$start
    e        <- periods[[col]]$end
    e_capped <- min(e, DW_DATA_END)

    if (s > DW_DATA_END) {
      rows[[col]] <- list(banks = NA, loans = NA, total_b = NA, mean_m = NA, median_m = NA, avg_rate = NA, avg_term = NA, avg_effmat = NA, loan_coll = NA)
      next
    }

    df <- dw_loans %>% filter(dw_loan_date >= s, dw_loan_date <= e_capped)

    if (nrow(df) == 0) {
      rows[[col]] <- list(banks = 0, loans = 0, total_b = 0, mean_m = NA, median_m = NA, avg_rate = NA, avg_term = NA, avg_effmat = NA, loan_coll = NA)
      next
    }

    rows[[col]] <- list(
      banks      = n_distinct(df$rssd_id),
      loans      = nrow(df),
      total_b    = sum(df$dw_loan_amount,              na.rm = TRUE) / 1e9,
      mean_m     = mean(df$dw_loan_amount,             na.rm = TRUE) / 1e6,
      median_m   = median(df$dw_loan_amount,           na.rm = TRUE) / 1e6,
      avg_rate   = mean(df$dw_interest_rate,           na.rm = TRUE),
      avg_term   = mean(df$dw_term,                    na.rm = TRUE),
      avg_effmat = mean(df$dw_effective_maturity_days, na.rm = TRUE),
      loan_coll  = mean(safe_div(df$dw_loan_amount, df$dw_total_collateral) * 100, na.rm = TRUE)
    )
  }

  metrics <- c("Banks (N)", "Loans (N)", "Total ($B)", "Mean ($M)", "Median ($M)", "Avg. Rate (%)", "Avg. Term (Days)", "Avg. Eff. Maturity (Days)", "Loan/Collateral (%)")

  map_dfr(col_names, function(col) {
    r <- rows[[col]]
    tibble(Metric = metrics, Period = col, Value = c(fmt_int(r$banks), fmt_int(r$loans), fmt_num(r$total_b), fmt_num(r$mean_m), fmt_num(r$median_m), fmt_num(r$avg_rate), fmt_num(r$avg_term), fmt_num(r$avg_effmat), fmt_num(r$loan_coll)))
  }) %>%
    pivot_wider(names_from = Period, values_from = Value) %>%
    select(Metric, all_of(col_names))
}

# ==============================================================================
# 5. PANEL C: BTFP
# ==============================================================================

build_btfp_panel <- function() {

  rows <- list()

  for (col in col_names) {

    if (col == "Pre-BTFP") {
      rows[[col]] <- list(banks = NA, loans = NA, total_b = NA, mean_m = NA, median_m = NA, avg_rate = NA, avg_term = NA, avg_effmat = NA, loan_coll = NA)
      next
    }

    s  <- periods[[col]]$start
    e  <- periods[[col]]$end
    df <- btfp_loans %>% filter(btfp_loan_date >= s, btfp_loan_date <= e)

    if (nrow(df) == 0) {
      rows[[col]] <- list(banks = 0, loans = 0, total_b = 0, mean_m = NA, median_m = NA, avg_rate = NA, avg_term = NA, avg_effmat = NA, loan_coll = NA)
      next
    }

    rows[[col]] <- list(
      banks      = n_distinct(df$rssd_id),
      loans      = nrow(df),
      total_b    = sum(df$btfp_loan_amount,              na.rm = TRUE) / 1e9,
      mean_m     = mean(df$btfp_loan_amount,             na.rm = TRUE) / 1e6,
      median_m   = median(df$btfp_loan_amount,           na.rm = TRUE) / 1e6,
      avg_rate   = mean(df$btfp_interest_rate,           na.rm = TRUE),
      avg_term   = mean(df$btfp_term,                    na.rm = TRUE),
      avg_effmat = mean(df$btfp_effective_maturity_days, na.rm = TRUE),
      loan_coll  = mean(safe_div(df$btfp_loan_amount, df$btfp_total_collateral) * 100, na.rm = TRUE)
    )
  }

  metrics <- c("Banks (N)", "Loans (N)", "Total ($B)", "Mean ($M)", "Median ($M)", "Avg. Rate (%)", "Avg. Term (Days)", "Avg. Eff. Maturity (Days)", "Loan/Collateral (%)")

  map_dfr(col_names, function(col) {
    r <- rows[[col]]
    tibble(Metric = metrics, Period = col, Value = c(fmt_int(r$banks), fmt_int(r$loans), fmt_num(r$total_b), fmt_num(r$mean_m), fmt_num(r$median_m), fmt_num(r$avg_rate), fmt_num(r$avg_term), fmt_num(r$avg_effmat), fmt_num(r$loan_coll)))
  }) %>%
    pivot_wider(names_from = Period, values_from = Value) %>%
    mutate(`Pre-BTFP` = "N/A") %>%
    select(Metric, all_of(col_names))
}

# ==============================================================================
# 6. PANEL D: FHLB
# ==============================================================================

build_fhlb_panel <- function() {

  rows <- list()

  for (col in col_names) {

    qtrs <- fhlb_period_quarters[[col]]

    df_q <- call_q %>% filter(period %in% qtrs, !idrssd %in% excluded_banks, !is.na(fhlb_adv))

    if (nrow(df_q) == 0) {
      rows[[col]] <- list(banks_any = NA, banks_abn = NA, total_b = NA, mean_m = NA, median_m = NA, wa_term = NA)
      next
    }

    df_bank <- df_q %>%
      group_by(idrssd) %>%
      summarise(
        fhlb_adv           = sum(fhlb_adv, na.rm = TRUE),
        fhlb_less_than_1yr = sum(fhlb_less_than_1yr, na.rm = TRUE),
        fhlb_1to3yr        = sum(fhlb_1to3yr, na.rm = TRUE),
        fhlb_3to5yr        = sum(fhlb_3to5yr, na.rm = TRUE),
        fhlb_more_than_5yr = sum(fhlb_more_than_5yr, na.rm = TRUE),
        abnormal_fhlb      = max(abnormal_fhlb_borrowing_10pct, na.rm = TRUE),
        .groups = "drop"
      )

    df_bor    <- df_bank %>% filter(fhlb_adv > 0)
    total_adv <- sum(df_bor$fhlb_adv, na.rm = TRUE)

    wa_term <- if (total_adv > 0) {
      sum(replace_na(df_bor$fhlb_less_than_1yr, 0) * 0.5 + replace_na(df_bor$fhlb_1to3yr, 0) * 2.0 + replace_na(df_bor$fhlb_3to5yr, 0) * 4.0 + replace_na(df_bor$fhlb_more_than_5yr, 0) * 7.0, na.rm = TRUE) / total_adv
    } else { NA_real_ }

    rows[[col]] <- list(banks_any = nrow(df_bor), banks_abn = sum(df_bank$abnormal_fhlb == 1, na.rm = TRUE), total_b = total_adv / 1e6, mean_m = mean(df_bor$fhlb_adv, na.rm = TRUE) / 1e3, median_m = median(df_bor$fhlb_adv, na.rm = TRUE) / 1e3, wa_term = wa_term)
  }

  metrics <- c("Banks with Advances (N)", "Abnormal Borrowers (N)", "Total ($B)", "Mean ($M)", "Median ($M)", "Wtd. Avg. Term (Years)")

  map_dfr(col_names, function(col) {
    r <- rows[[col]]
    tibble(Metric = metrics, Period = col, Value = c(fmt_int(r$banks_any), fmt_int(r$banks_abn), fmt_num(r$total_b), fmt_num(r$mean_m), fmt_num(r$median_m), fmt_num(r$wa_term)))
  }) %>%
    pivot_wider(names_from = Period, values_from = Value) %>%
    select(Metric, all_of(col_names))
}

# ==============================================================================
# 7. BUILD ALL FOUR PANELS
# ==============================================================================

panel_all <- build_all_fed_panel()
panel_dw  <- build_dw_panel()
panel_b   <- build_btfp_panel()
panel_c   <- build_fhlb_panel()

# ==============================================================================
# 8. HTML DISPLAY
# ==============================================================================

html_col_names <- c(
  "Metric",
  "Pre-BTFP<br><small>(Mar 1–12)</small>",
  "Acute<br><small>(Mar 8–15)</small>",
  "Post-Acute<br><small>(Mar 16–May 4)</small>",
  "Arbitrage<br><small>(Nov 15–Jan 24)</small>",
  "Entire BTFP<br><small>(Mar 13 '23 – Mar 11 '24)</small>"
)

display_panel <- function(df, panel_title) {
  kable(
    df, format = "html", escape = FALSE,
    caption   = panel_title,
    col.names = html_col_names,
    align     = c("l", rep("r", 5))
  ) %>%
    kable_styling(
      bootstrap_options = c("striped", "condensed", "hover"),
      full_width = FALSE, font_size = 12
    ) %>%
    row_spec(0, bold = TRUE)
}

display_panel(panel_all, "Panel A: All Fed Borrowing (BTFP & DW)")
Panel A: All Fed Borrowing (BTFP & DW)
Metric Pre-BTFP
(Mar 1–12)
Acute
(Mar 8–15)
Post-Acute
(Mar 16–May 4)
Arbitrage
(Nov 15–Jan 24)
Entire BTFP
(Mar 13 ’23 – Mar 11 ’24)
Banks (N) 106 188 802 1,052 2,146
Loans (N) 296 369 2,776 4,135 14,764
Total (\(B) </td> <td style="text-align:right;"> 19.56 </td> <td style="text-align:right;"> 144.57 </td> <td style="text-align:right;"> 525.12 </td> <td style="text-align:right;"> 234.34 </td> <td style="text-align:right;"> 1,079.38 </td> </tr> <tr> <td style="text-align:left;"> Mean (\)M) 66.08 391.79 189.16 56.67 73.11
Median ($M) 10.00 10.50 10.00 10.80 9.00
display_panel(panel_dw,  "Panel B: Discount Window Borrowing")
Panel B: Discount Window Borrowing
Metric Pre-BTFP
(Mar 1–12)
Acute
(Mar 8–15)
Post-Acute
(Mar 16–May 4)
Arbitrage
(Nov 15–Jan 24)
Entire BTFP
(Mar 13 ’23 – Mar 11 ’24)
Banks (N) 106 165 372 389 1,389
Loans (N) 296 336 1,506 1,037 8,069
Total (\(B) </td> <td style="text-align:right;"> 19.56 </td> <td style="text-align:right;"> 138.32 </td> <td style="text-align:right;"> 399.88 </td> <td style="text-align:right;"> 14.90 </td> <td style="text-align:right;"> 669.02 </td> </tr> <tr> <td style="text-align:left;"> Mean (\)M) 66.08 411.67 265.52 14.37 82.91
Median ($M) 10.00 10.00 9.00 3.50 5.50
Avg. Rate (%) 4.75 4.75 4.96 5.50 5.31
Avg. Term (Days) 4.72 5.72 4.11 5.47 5.23
Avg. Eff. Maturity (Days) 3.69 3.38 3.00 3.87 3.84
Loan/Collateral (%) 23.41 23.34 27.43 17.38 22.70
display_panel(panel_b,   "Panel C: BTFP Borrowing")
Panel C: BTFP Borrowing
Metric Pre-BTFP
(Mar 1–12)
Acute
(Mar 8–15)
Post-Acute
(Mar 16–May 4)
Arbitrage
(Nov 15–Jan 24)
Entire BTFP
(Mar 13 ’23 – Mar 11 ’24)
Banks (N) N/A 27 515 780 1,316
Loans (N) N/A 33 1,270 3,098 6,695
Total (\(B) </td> <td style="text-align:right;"> N/A </td> <td style="text-align:right;"> 6.25 </td> <td style="text-align:right;"> 125.24 </td> <td style="text-align:right;"> 219.45 </td> <td style="text-align:right;"> 410.36 </td> </tr> <tr> <td style="text-align:left;"> Mean (\)M) N/A 189.35 98.61 70.84 61.29
Median ($M) N/A 20.80 12.60 15.00 10.00
Avg. Rate (%) N/A 4.55 4.71 4.95 5.02
Avg. Term (Days) N/A 289.15 317.22 338.00 311.14
Avg. Eff. Maturity (Days) N/A 144.70 157.89 88.10 104.47
Loan/Collateral (%) N/A 65.16 51.06 50.45 45.13
display_panel(panel_c,   "Panel D: FHLB Borrowing")
Panel D: FHLB Borrowing
Metric Pre-BTFP
(Mar 1–12)
Acute
(Mar 8–15)
Post-Acute
(Mar 16–May 4)
Arbitrage
(Nov 15–Jan 24)
Entire BTFP
(Mar 13 ’23 – Mar 11 ’24)
Banks with Advances (N) 2,481 2,481 2,530 2,639 3,043
Abnormal Borrowers (N) 325 325 350 212 707
Total (\(B) </td> <td style="text-align:right;"> 446.95 </td> <td style="text-align:right;"> 446.95 </td> <td style="text-align:right;"> 646.65 </td> <td style="text-align:right;"> 462.93 </td> <td style="text-align:right;"> 2,092.48 </td> </tr> <tr> <td style="text-align:left;"> Mean (\)M) 180.15 180.15 255.59 175.42 687.64
Median ($M) 15.50 15.50 16.41 18.00 55.00
Wtd. Avg. Term (Years) 1.10 1.10 1.05 1.37 1.26
# ==============================================================================
# 9. LATEX SAVE
# ==============================================================================

build_latex_table <- function(p_all, p_dw, p_btfp, p_fhlb, filename) {

  panel_to_rows <- function(df) {
    apply(df, 1, function(r) {
      paste0("\\quad ", r["Metric"], " & ", r["Pre-BTFP"], " & ", r["Acute"], " & ", r["Post-Acute"], " & ", r["Arbitrage"], " & ", r["Entire BTFP"], " \\\\\n")
    })
  }

  rows_all  <- panel_to_rows(p_all)
  rows_dw   <- panel_to_rows(p_dw)
  rows_btfp <- panel_to_rows(p_btfp)
  rows_fhlb <- panel_to_rows(p_fhlb)

  latex_out <- paste0(
    "% =================================================================\n",
    "% Table: Emergency Lending Facility Activity by Crisis Period\n",
    "% Preamble: \\usepackage{booktabs,threeparttable,multirow}\n",
    "% =================================================================\n",
    "\\begin{table}[htbp]\n",
    "\\centering\n",
    "\\caption{Emergency Lending Facility Activity by Crisis Period}\n",
    "\\label{tab:borrowing_activity}\n",
    "\\small\n",
    "\\begin{threeparttable}\n",
    "\\begin{tabular}{l rrrrr}\n",
    "\\toprule\n",

    " & Pre-BTFP & \\multicolumn{2}{c}{Crisis Period} & Arbitrage & Entire BTFP \\\\\n",
    "\\cmidrule(lr){3-4}\n",
    " & (Mar 1--12) & Acute & Post-Acute & (Nov 15--Jan 24) & (Mar 13, `23--Mar 11, `24) \\\\\n",
    " &  & (Mar 8--15) & (Mar 16--May 4) &  &  \\\\\n",
    "\\midrule\n",

    "\\multicolumn{6}{l}{\\textbf{Panel A: All Fed Borrowing (BTFP + DW)}} \\\\\n",
    paste(rows_all, collapse = ""),
    "\\midrule\n",

    "\\multicolumn{6}{l}{\\textbf{Panel B: Discount Window}} \\\\\n",
    paste(rows_dw, collapse = ""),
    "\\midrule\n",

    "\\multicolumn{6}{l}{\\textbf{Panel C: Bank Term Funding Program (BTFP)}} \\\\\n",
    paste(rows_btfp, collapse = ""),
    "\\midrule\n",

    "\\multicolumn{6}{l}{\\textbf{Panel D: Federal Home Loan Bank (FHLB)}} \\\\\n",
    paste(rows_fhlb, collapse = ""),
    "\\bottomrule\n",
    "\\end{tabular}\n",

    "\\begin{tablenotes}[flushleft]\n",
    "\\footnotesize\n",
    "\\item \\textit{Notes:} Sample excludes failed banks and G-SIBs. All dollar amounts in billions (\\$B) or millions (\\$M). ",
    "\\textbf{Panel A --- All Fed Borrowing:} Aggregates DW and BTFP originations. ",
    "\\textbf{Panel B --- Discount Window:} Loan-level data available through December 31, 2023; Arbitrage and Entire BTFP columns show \\texttt{---} (data unavailable). ",
    "\\textbf{Panel C --- BTFP:} BTFP launched March 13, 2023; Pre-BTFP column shows N/A. ",
    "\\textbf{Panel D --- FHLB:} Computed from quarterly Call Report snapshots. Pre-BTFP and Acute use 2022Q4; Post-Acute uses 2023Q1; Arbitrage uses 2023Q3. ",
    "Entire BTFP aggregates four quarters (2023Q1--2023Q4). Abnormal Borrowers = banks whose quarter-over-quarter change in FHLB advances exceeds a bank-specific Z-score of 1.28.",
    "\\end{tablenotes}\n",
    "\\end{threeparttable}\n",
    "\\end{table}\n"
  )

  safe_writeLines(latex_out, file.path(TABLE_PATH, paste0(filename, ".tex")))
  message("Saved: ", filename, ".tex")
  invisible(latex_out)
}

build_latex_table(panel_all, panel_dw, panel_b, panel_c, "Table_Borrowing_Activity_ByPeriod")

# ==============================================================================
# 10. VERIFICATION
# ==============================================================================

cat("\n=== BORROWING ACTIVITY TABLE COMPLETE ===\n")
## 
## === BORROWING ACTIVITY TABLE COMPLETE ===
cat("Panel A (All Fed):", nrow(panel_all), "metrics x", ncol(panel_all) - 1, "periods\n")
## Panel A (All Fed): 5 metrics x 5 periods
cat("Panel B (DW):     ", nrow(panel_dw), "metrics x", ncol(panel_dw) - 1, "periods\n")
## Panel B (DW):      9 metrics x 5 periods
cat("Panel C (BTFP):   ", nrow(panel_b), "metrics x", ncol(panel_b) - 1, "periods\n")
## Panel C (BTFP):    9 metrics x 5 periods
cat("Panel D (FHLB):   ", nrow(panel_c), "metrics x", ncol(panel_c) - 1, "periods\n")
## Panel D (FHLB):    6 metrics x 5 periods

11 Descriptive Evidence

11.1 MEAN CHARACTERISTICS — Crisis Period, Crisis vs. Arbitrage

# ==============================================================================
# 1. VARIABLE LIST AND LABELS
# ==============================================================================


mean_vars <- c(
  "total_asset_b",          # Total Assets ($B) — computed inline below
  "cash_ratio_raw",
  "securities_ratio_raw",
  "loan_ratio_raw",
  "mtm_total_raw",
  "mtm_btfp_raw",
  "mtm_other_raw",
  "book_equity_ratio_raw",
  "insured_lev_raw",        # r_insured_deposit
  "uninsured_lev_raw",
  "uninsured_share_raw",
  "wholesale_raw",
  "fhlb_ratio_raw",
  "roa_raw",
  "adjusted_equity_raw"
)

mean_labels_tex <- c(
  "Total Assets (\\$B)",
  "Cash Ratio",
  "Securities  Ratio",
  "Loans  Ratio",
  "Total MTM Loss  Ratio",
  "OMO MTM Loss  Ratio",
  "Non-OMO MTM Loss  Ratio",
  "Book Equity  Ratio",
  "Insured Dep. Leverage",
  "Uninsured Dep.Leverage",
  "Uninsured / Total Dep.",
  "Wholesale Funding Ratio",
  "FHLB Advances  Ratio",
  "ROA ",
  "Adjusted Equity "
)

# ==============================================================================
# 2. HELPERS
# ==============================================================================

fmt_mean <- function(x, digits = 3) {
  if (is.na(x) | is.nan(x) | is.infinite(x)) return("---")
  sprintf(paste0("%.", digits, "f"), x)
}

fmt_int_cell <- function(x) {
  if (is.na(x)) return("---")
  formatC(as.integer(x), format = "d", big.mark = ",")
}

# "mean [t-stat]***" cell — tests x_g vs x_ref (Welch)
mean_ttest_cell <- function(x_g, x_ref) {
  x_g   <- x_g[!is.na(x_g)]
  x_ref <- x_ref[!is.na(x_ref)]
  m_g   <- mean(x_g, na.rm = TRUE)

  if (length(x_g) < 2 | length(x_ref) < 2) return(fmt_mean(m_g))

  tt <- tryCatch(t.test(x_g, x_ref), error = function(e) NULL)
  if (is.null(tt)) return(fmt_mean(m_g))

  stars <- case_when(
    tt$p.value < 0.01 ~ "$^{***}$",
    tt$p.value < 0.05 ~ "$^{**}$",
    tt$p.value < 0.10 ~ "$^{*}$",
    TRUE              ~ ""
  )
  paste0(fmt_mean(m_g), " [", sprintf("%.2f", tt$statistic), "]", stars)
}

# Helper to format base display variables
add_display_vars <- function(df) {
  df %>%
    mutate(
      total_asset_b   = total_asset / 1e6,
      insured_lev_raw = if ("insured_lev_raw" %in% names(.)) insured_lev_raw else r_insured_deposit
    )
}

# ==============================================================================
# 3. REBUILD SUB-PERIODS & PREPARE DATASETS
# ==============================================================================

# Rebuild Acute (Mar 8 - Mar 15) and Post-Acute (Mar 16 - May 4) on the fly
btfp_acute <- create_borrower_indicator(btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount", as.Date("2023-03-08"), as.Date("2023-03-15"), "btfp_acute")
dw_acute   <- create_borrower_indicator(dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount", as.Date("2023-03-08"), as.Date("2023-03-15"), "dw_acute")
df_acute   <- join_all_borrowers(df_2022q4, btfp_acute, dw_acute, "btfp_acute", "dw_acute")

btfp_post  <- create_borrower_indicator(btfp_loans, "btfp_loan_date", "rssd_id", "btfp_loan_amount", as.Date("2023-03-16"), as.Date("2023-05-04"), "btfp_post")
dw_post    <- create_borrower_indicator(dw_loans, "dw_loan_date", "rssd_id", "dw_loan_amount", as.Date("2023-03-16"), as.Date("2023-05-04"), "dw_post")
df_post    <- join_all_borrowers(df_2022q4, btfp_post, dw_post, "btfp_post", "dw_post")

# Add display variables
df_crisis_d <- add_display_vars(df_crisis)
df_acute_d  <- add_display_vars(df_acute)
df_post_d   <- add_display_vars(df_post)

# Extract the 4 specific population groups for Table 1
df_all_crisis <- df_crisis_d %>% filter(any_fed == 1)
df_all_acute  <- df_acute_d  %>% filter(any_fed == 1)
df_all_post   <- df_post_d   %>% filter(any_fed == 1)
df_pure_non   <- df_crisis_d %>% filter(non_user == 1) # The Reference Group

# ==============================================================================
# 4. TABLE 1: CRISIS PERIOD (All Borrowers vs Pure Non-Borrowers)
#    4 columns: Crisis All | Acute All | Post-Acute All | Pure Non-Borrower
# ==============================================================================

build_table1 <- function() {

  # N row (Total Borrowers/Non-Borrowers)
  n_row <- tibble(
    Variable    = "\\textit{N (Banks)}",
    `Crisis`    = fmt_int_cell(nrow(df_all_crisis)),
    `Acute`     = fmt_int_cell(nrow(df_all_acute)),
    `Post`      = fmt_int_cell(nrow(df_all_post)),
    `NonB`      = fmt_int_cell(nrow(df_pure_non))
  )

  # Breakdown Sub-rows for Borrower Types
  n_btfp <- tibble(
    Variable    = "\\quad \\textit{BTFP Only}",
    `Crisis`    = fmt_int_cell(sum(df_all_crisis$user_group == "BTFP_Only", na.rm=T)),
    `Acute`     = fmt_int_cell(sum(df_all_acute$user_group == "BTFP_Only", na.rm=T)),
    `Post`      = fmt_int_cell(sum(df_all_post$user_group == "BTFP_Only", na.rm=T)),
    `NonB`      = "---"
  )
  n_dw <- tibble(
    Variable    = "\\quad \\textit{DW Only}",
    `Crisis`    = fmt_int_cell(sum(df_all_crisis$user_group == "DW_Only", na.rm=T)),
    `Acute`     = fmt_int_cell(sum(df_all_acute$user_group == "DW_Only", na.rm=T)),
    `Post`      = fmt_int_cell(sum(df_all_post$user_group == "DW_Only", na.rm=T)),
    `NonB`      = "---"
  )
  n_both <- tibble(
    Variable    = "\\quad \\textit{Both BTFP \\& DW}",
    `Crisis`    = fmt_int_cell(sum(df_all_crisis$user_group == "Both", na.rm=T)),
    `Acute`     = fmt_int_cell(sum(df_all_acute$user_group == "Both", na.rm=T)),
    `Post`      = fmt_int_cell(sum(df_all_post$user_group == "Both", na.rm=T)),
    `NonB`      = "---"
  )

  # N Insolvent Row
  insol_row <- tibble(
    Variable    = "\\textit{N adj.\\ equity $<$ 0}",
    `Crisis`    = fmt_int_cell(sum(df_all_crisis$mtm_insolvent == 1, na.rm=T)),
    `Acute`     = fmt_int_cell(sum(df_all_acute$mtm_insolvent == 1, na.rm=T)),
    `Post`      = fmt_int_cell(sum(df_all_post$mtm_insolvent == 1, na.rm=T)),
    `NonB`      = fmt_int_cell(sum(df_pure_non$mtm_insolvent == 1, na.rm=T))
  )

  # Variable Rows (Means + t-tests against Pure Non-Borrower)
  var_rows <- map2_dfr(mean_vars, mean_labels_tex, function(v, lbl) {
    r_ref <- df_pure_non[[v]]
    tibble(
      Variable = lbl,
      `Crisis` = mean_ttest_cell(df_all_crisis[[v]], r_ref),
      `Acute`  = mean_ttest_cell(df_all_acute[[v]],  r_ref),
      `Post`   = mean_ttest_cell(df_all_post[[v]],   r_ref),
      `NonB`   = fmt_mean(mean(r_ref, na.rm = TRUE))
    )
  })

  bind_rows(n_row, n_btfp, n_dw, n_both, insol_row, var_rows)
}

tab1_output <- build_table1()

# ==============================================================================
# 5. PRINT FULL TABLE TO CONSOLE
# ==============================================================================
cat("=== TABLE 1: MEAN CHARACTERISTICS (CRISIS PERIOD) ===\n")
## === TABLE 1: MEAN CHARACTERISTICS (CRISIS PERIOD) ===
print(tab1_output, n = Inf)
## # A tibble: 20 × 5
##    Variable                            Crisis                 Acute  Post  NonB 
##    <chr>                               <chr>                  <chr>  <chr> <chr>
##  1 "\\textit{N (Banks)}"               828                    177    760   3,226
##  2 "\\quad \\textit{BTFP Only}"        395                    23     408   ---  
##  3 "\\quad \\textit{DW Only}"          327                    151    270   ---  
##  4 "\\quad \\textit{Both BTFP \\& DW}" 106                    3      82    ---  
##  5 "\\textit{N adj.\\ equity $<$ 0}"   205                    38     193   587  
##  6 "Total Assets (\\$B)"               5.843 [3.44]$^{***}$   10.35… 4.83… 1.848
##  7 "Cash Ratio"                        5.256 [-14.93]$^{***}$ 4.893… 5.08… 9.138
##  8 "Securities  Ratio"                 26.472 [1.03]          25.44… 26.8… 25.8…
##  9 "Loans  Ratio"                      62.470 [6.25]$^{***}$  63.50… 62.3… 58.6…
## 10 "Total MTM Loss  Ratio"             5.951 [7.85]$^{***}$   5.701… 6.00… 5.326
## 11 "OMO MTM Loss  Ratio"               0.824 [4.69]$^{***}$   0.847… 0.83… 0.657
## 12 "Non-OMO MTM Loss  Ratio"           4.975 [6.76]$^{***}$   4.844… 5.01… 4.473
## 13 "Book Equity  Ratio"                8.540 [-10.32]$^{***}$ 8.655… 8.49… 10.6…
## 14 "Insured Dep. Leverage"             58.687 [-7.44]$^{***}$ 54.60… 58.7… 62.5…
## 15 "Uninsured Dep.Leverage"            27.149 [9.42]$^{***}$  30.61… 26.9… 22.7…
## 16 "Uninsured / Total Dep."            31.809 [9.55]$^{***}$  36.42… 31.6… 26.5…
## 17 "Wholesale Funding Ratio"           1.530 [4.93]$^{***}$   1.727… 1.61… 0.876
## 18 "FHLB Advances  Ratio"              3.624 [7.20]$^{***}$   4.015… 3.67… 2.344
## 19 "ROA "                              1.092 [-1.80]$^{*}$    1.119… 1.08… 1.193
## 20 "Adjusted Equity "                  2.590 [-11.23]$^{***}$ 2.954… 2.48… 5.241
# ==============================================================================
# 6. SAVE TO LATEX
# ==============================================================================
# We use escape = FALSE so the t-test stars and italics render correctly
latex_file <- file.path(TABLE_PATH, "Table_Mean_Characteristics_Crisis.tex")

latex_content <- kable(
  tab1_output, 
  format = "latex", 
  escape = FALSE,
  booktabs = TRUE,
  col.names = c("Variable", "Crisis All", "Acute All", "Post-Acute All", "Pure Non-Borrower"),
  caption = "Mean Characteristics of Borrowers vs. Non-Borrowers (Crisis Period)",
  label = "tab:mean_chars_crisis",
  align = c("l", "c", "c", "c", "c")
) %>%
  row_spec(1:4, background = "gray!10") %>% # Optional: light shade for N counts
  footnote(
    general = "Characteristics measured at the 2022Q4 baseline. Continuous variables are winsorized at the 2.5 and 97.5 percentiles. Welch's t-tests compare the mean of each respective borrower group against the shared Pure Non-Borrower reference group. Brackets contain t-statistics. Statistical significance: * p < 0.10, ** p < 0.05, *** p < 0.01.",
    threeparttable = TRUE,
    escape = FALSE
  )

safe_writeLines(as.character(latex_content), latex_file)