1. Setup

# ── Data wrangling ──
library(data.table); library(dplyr); library(tidyr); library(stringr)
library(lubridate); library(purrr); library(tibble); library(readr)

# ── Display ──
library(knitr); library(kableExtra)

# ── Plotting ──
library(ggplot2); library(scales); library(patchwork)

# ── Inference ──
library(sandwich) # HC-robust variance
library(lmtest)   # coeftest with robust SE
# ══════════════════════════════════════════════════════════════════════
# HELPERS
# ══════════════════════════════════════════════════════════════════════
safe_div <- function(num, denom, default = NA_real_) {
  ifelse(is.na(denom) | denom == 0, default, num / denom)
}

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])
}

fmt  <- function(x, d = 0) formatC(x, format = "f", digits = d, big.mark = ",")
fmt2 <- function(x) fmt(x, 2)
fmt3 <- function(x) fmt(x, 3)
fmt4 <- function(x) fmt(x, 4)

z_std <- function(x) {
  mu <- mean(x, na.rm = TRUE); s <- sd(x, na.rm = TRUE)
  if (is.na(s) || s == 0) return(rep(0, length(x)))
  (x - mu) / s
}

pct <- function(x) sprintf("%.1f%%", 100 * mean(x, na.rm = TRUE))

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

robust_summary <- function(model, vcov_type = "HC1") {
  ct <- coeftest(model, vcov. = vcovHC(model, type = vcov_type))
  data.frame(
    term      = rownames(ct),
    estimate  = ct[, 1],
    std.error = ct[, 2],
    t.value   = ct[, 3],
    p.value   = ct[, 4],
    stars     = stars_pval(ct[, 4]),
    stringsAsFactors = FALSE
  )
}
# ══════════════════════════════════════════════════════════════════════
# PATHS
# ══════════════════════════════════════════════════════════════════════
BASE_PATH  <- "C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/Research 2026"
DATA_DIR   <- file.path(BASE_PATH, "01_data/Final dataset")
OUT_PATH   <- file.path(BASE_PATH, "03_documentation/results")
TABLE_PATH <- file.path(OUT_PATH, "tables")
FIG_PATH   <- file.path(OUT_PATH, "figures")
for (p in c(TABLE_PATH, FIG_PATH))
  if (!dir.exists(p)) dir.create(p, recursive = TRUE)

save_kbl_latex <- function(df, filename, col.names = NULL, caption = "",
                           align = NULL, ...) {
  tex <- kbl(df, format = "latex", booktabs = TRUE, escape = FALSE,
             col.names = col.names, caption = caption, align = align, ...) %>%
         kable_styling(latex_options = c("hold_position"))
  writeLines(tex, file.path(TABLE_PATH, paste0(filename, ".tex")))
  invisible()
}

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")
  invisible()
}
# ══════════════════════════════════════════════════════════════════════
# CALIBRATION
# ══════════════════════════════════════════════════════════════════════
y_10yr       <- 0.0392        # 10-year Treasury yield (DSSW benchmark rate)
r_market     <- y_10yr         # alias for theory notation
delta_decay  <- 0.10           # deposit decay rate
cap_factor   <- 1 / (y_10yr + delta_decay)  # DSSW capitalization factor
phi_star_baseline <- 1.0
phi_star_struct   <- 0.5

# ── Periods ──
P0_start <- as.Date("2023-03-01"); P0_end <- as.Date("2023-03-07")
P1_start <- as.Date("2023-03-08"); P1_end <- as.Date("2023-03-12")
P2_start <- as.Date("2023-03-13"); P2_end <- as.Date("2023-04-27")
P3_start <- as.Date("2023-04-28"); P3_end <- as.Date("2023-05-04")
CRISIS_START <- P1_start
CRISIS_END   <- P3_end

ARB_START <- as.Date("2023-11-15")
ARB_END   <- as.Date("2024-01-24")

BTFP_LAUNCH <- as.Date("2023-03-13")
BTFP_CLOSE  <- as.Date("2024-03-11")

# ── DW primary credit contractual maximum (days) ──
DW_CONTRACT_MAX <- 90

assign_period <- function(d) {
  dplyr::case_when(
    d >= P0_start  & d <= P0_end  ~ "P0: Pre-Crisis",
    d >= P1_start  & d <= P1_end  ~ "P1: SVB Week",
    d >= P2_start  & d <= P2_end  ~ "P2: SVB to FRC",
    d >= P3_start  & d <= P3_end  ~ "P3: FRC Week",
    d >= ARB_START & d <= ARB_END ~ "Arbitrage",
    d >  CRISIS_END & d < ARB_START ~ "Inter-Period",
    d >  ARB_END   & d <= BTFP_CLOSE ~ "Post-Arbitrage",
    TRUE ~ "Other"
  )
}

theme_gp <- theme_minimal(base_size = 13) +
  theme(plot.title      = element_text(face = "bold", size = 14),
        plot.subtitle   = element_text(color = "grey40", size = 11),
        legend.position = "bottom",
        panel.grid.minor = element_blank())

fac_colors <- c("BTFP Only" = "#1565C0", "DW Only" = "#E53935",
                "Both"      = "#7B1FA2", "Non-Borrower" = "grey60")

2. Data Loading and Variable Construction

2.1 Load four raw datasets

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

btfp_raw <- read_csv(file.path(DATA_DIR, "btfp_loan_bank_only.csv"),
                     show_col_types = FALSE) %>%
  mutate(rssd_id             = as.character(rssd_id),
         btfp_loan_date      = mdy(btfp_loan_date),
         btfp_repayment_date = mdy(btfp_repayment_date),
         btfp_maturity_date  = mdy(btfp_maturity_date))

# FIX: parse dw_maturity_date so Table 1 early-repay logic works
dw_raw <- read_csv(file.path(DATA_DIR, "dw_loan_bank_2023.csv"),
                   show_col_types = FALSE) %>%
  mutate(rssd_id              = as.character(rssd_id),
         dw_loan_date         = ymd(dw_loan_date),
         dw_repayment_date    = ymd(dw_repayment_date),
         dw_maturity_date     = ymd(dw_maturity_date),
         dw_credit_type_clean = str_squish(as.character(dw_credit_type))) %>%
  filter(str_detect(dw_credit_type_clean, "^Primary Credit"))

dssw_raw <- read_csv(file.path(DATA_DIR, "dssw_deposit_betas.csv"),
                     show_col_types = FALSE) %>%
  mutate(idrssd = as.character(idrssd))

# ── DSSW deposit costs ──
dssw_costs_file <- file.path(DATA_DIR, "dssw_deposit_costs.csv")
if (file.exists(dssw_costs_file)) {
  dssw_costs_raw <- read_csv(dssw_costs_file, show_col_types = FALSE) %>%
    mutate(idrssd = as.character(idrssd))
  HAS_DEPOSIT_COSTS <- TRUE
} else {
  HAS_DEPOSIT_COSTS <- FALSE
}

cat(sprintf("Call panel : %s bank-quarters, %s banks\n",
            fmt(nrow(call_raw)), fmt(n_distinct(call_raw$idrssd))))
## Call panel : 75,989 bank-quarters, 5,074 banks
cat(sprintf("BTFP loans : %s transactions, %s banks\n",
            fmt(nrow(btfp_raw)), fmt(n_distinct(btfp_raw$rssd_id))))
## BTFP loans : 6,734 transactions, 1,327 banks
cat(sprintf("DW loans   : %s transactions, %s banks\n",
            fmt(nrow(dw_raw)), fmt(n_distinct(dw_raw$rssd_id))))
## DW loans   : 9,673 transactions, 1,468 banks
cat(sprintf("DSSW betas : %s bank-quarters, %s banks\n",
            fmt(nrow(dssw_raw)), fmt(n_distinct(dssw_raw$idrssd))))
## DSSW betas : 13,588 bank-quarters, 4,660 banks

2.2 Build the 2022Q4 cross-section

df <- call_raw %>%
  filter(period == "2022Q4") %>%
  filter(gsib == 0, failed_bank == 0)

# FIX: pull beta_insured_w (was missing)
dssw_2022q4 <- dssw_raw %>%
  filter(estimation_date == "2022Q4") %>%
  select(idrssd,
         beta_overall,
         beta_uninsured, beta_insured,
         beta_uninsured_w, beta_insured_w)

# ── Deposit costs (DSSW) ──
if (HAS_DEPOSIT_COSTS) {
  dssw_costs_2022q4 <- dssw_costs_raw %>%
    filter(period == "2022Q4") %>%
    select(idrssd,
           deposit_cost_insured,
           deposit_cost_uninsured,
           deposit_cost_weighted)
} else {
  dssw_costs_2022q4 <- tibble(idrssd = character(),
                              deposit_cost_insured   = numeric(),
                              deposit_cost_uninsured = numeric(),
                              deposit_cost_weighted  = numeric())
}

# FIX: the previous version did the left_join TWICE, creating .x/.y suffix
# columns and breaking downstream references to beta_overall, etc.
# Single join only.
df <- df %>%
  left_join(dssw_2022q4,        by = "idrssd") %>%
  left_join(dssw_costs_2022q4,  by = "idrssd") %>%
  mutate(
    # Clip betas to [0, 1]
    beta_clipped   = pmax(0, pmin(1, beta_overall)),
    beta_u_clipped = pmax(0, pmin(1, beta_uninsured_w)),
    beta_i_clipped = pmax(0, pmin(1, beta_insured_w)),

    # Costs: substitute 0 when missing; track availability
    cost_u_raw = ifelse(HAS_DEPOSIT_COSTS & !is.na(deposit_cost_uninsured),
                        deposit_cost_uninsured, 0),
    cost_i_raw = ifelse(HAS_DEPOSIT_COSTS & !is.na(deposit_cost_insured),
                        deposit_cost_insured, 0),
    has_cost_data = as.integer(HAS_DEPOSIT_COSTS &
                               !is.na(deposit_cost_uninsured))
  )

cat(sprintf("2022Q4 cross-section: %s banks (excl. GSIBs, failed)\n",
            fmt(nrow(df))))
## 2022Q4 cross-section: 4,696 banks (excl. GSIBs, failed)
cat(sprintf("DSSW merge: %s matched (%.1f%%)\n",
            fmt(sum(!is.na(df$beta_overall))),
            100 * mean(!is.na(df$beta_overall))))
## DSSW merge: 4,602 matched (98.0%)
cat(sprintf("beta_insured_w matched: %s (%.1f%%)\n",
            fmt(sum(!is.na(df$beta_insured_w))),
            100 * mean(!is.na(df$beta_insured_w))))
## beta_insured_w matched: 4,602 (98.0%)

2.3 Construct theory variables

df <- df %>%
  mutate(
    # ── Size and ratios (pp) ──
    log_ta   = log(total_asset),
    eq_ta    = book_equity_to_total_asset,
    cash_ta  = cash_to_total_asset,
    sec_ta   = security_to_total_asset,
    omo_ta   = omo_eligible_to_total_asset,
    loan_ta  = total_loan_to_total_asset,
    dep_ta   = total_deposit_to_total_asset,
    du_ta    = uninsured_leverage,
    mu       = safe_div(uninsured_deposit, total_deposit, 0),

    # ── MTM losses / assets (pp) ──
    ell        = mtm_loss_to_total_asset,
    ell_sec    = loss_total_sec_to_ta,
    ell_omo    = loss_omo_sec_to_ta,
    ell_nonomo = loss_nonomo_sec_to_ta,
    ell_loan   = loss_total_loan_to_ta,

    # ── Deposit franchise (DSSW methodology, Option A: separate legs) ──
    # F^U/A = max[(1-β^U)·r - c^U, 0] · (1/(r+δ)) · (D^U/A)
    # F^I/A = max[(1-β^I)·r - c^I, 0] · (1/(r+δ)) · (D^I/A)
    # F/A   = F^U/A + F^I/A
    d_u_over_ta       = safe_div(uninsured_deposit, total_asset, 0),
    d_i_over_ta       = safe_div(pmax(total_deposit - uninsured_deposit, 0),
                                 total_asset, 0),
    d_over_ta         = safe_div(total_deposit, total_asset, 0),
    uninsured_share_d = safe_div(uninsured_deposit, total_deposit, 0),
    insured_share_d   = pmax(1 - uninsured_share_d, 0),

    # Per-dollar rents (as fractions, not pp)
    rent_u = pmax((1 - beta_u_clipped) * y_10yr - cost_u_raw, 0),
    rent_i = pmax((1 - beta_i_clipped) * y_10yr - cost_i_raw, 0),

    # Franchise as % of total assets (pp)
    fu_ta = ifelse(!is.na(beta_u_clipped),
                   rent_u * cap_factor * d_u_over_ta * 100, NA_real_),
    fi_ta = ifelse(!is.na(beta_i_clipped),
                   rent_i * cap_factor * d_i_over_ta * 100, NA_real_),
    # f_ta = sum of legs; NA if EITHER leg missing (conservative)
    f_ta  = rowSums(cbind(fu_ta, fi_ta), na.rm = FALSE),

    # Dollar-value franchise
    F_total     = f_ta  / 100 * total_asset,
    F_uninsured = fu_ta / 100 * total_asset,
    F_insured   = fi_ta / 100 * total_asset,

    # ── Market-value equity and post-run value ──
    E_MV             = total_equity - all_asset_loss,  # Without franchise
    E_MV_F           = E_MV + F_total,                 # With total franchise
    E_MV_F_insured   = E_MV + F_insured,
    E_MV_F_uninsured = E_MV + F_uninsured,
    # Post-run value: uninsured flee (lose share μ of franchise)
    v      = E_MV + (1 - mu) * F_total,
    emv_ta = 100 * safe_div(E_MV, total_asset),
    v_ta   = 100 * safe_div(v,    total_asset),

    # ── Coverage ratio and decomposition ──
    omo_mv = pmax(omo_eligible - loss_omo_sec, 0),
    rho    = safe_div(cash + omo_mv, uninsured_deposit),
    rho_0  = safe_div(cash + omo_eligible, uninsured_deposit),
    rho_C  = safe_div(cash, uninsured_deposit),
    rho_S  = safe_div(omo_mv, uninsured_deposit),

    # ── Liquidity gap ──
    liq_gap        = pmax(phi_star_baseline * uninsured_deposit
                          - (cash + omo_mv), 0),
    liq_gap_ta     = 100 * safe_div(liq_gap, total_asset),
    has_gap        = as.integer(liq_gap > 0),
    liq_gap_struct = pmax(phi_star_struct * uninsured_deposit
                          - (cash + omo_mv), 0),
    has_gap_struct = as.integer(liq_gap_struct > 0),

    # ── Descriptive interaction ──
    ell_x_uninsured = ell * du_ta,

    # ── other Liabilities ──
    roa = roa,
    fhlb_ta = fhlb_to_total_asset,
    loan_to_deposit =loan_to_deposit,
    wholesale_ta = fed_fund_purchase_to_total_asset + repo_to_total_asset +
                   fhlb_to_total_asset + other_borr_to_total_asset
  )

Franchise sanity check

# Option A produces insured_share_F > insured_share_D because insured β
# is lower and the insured leg is worth more per dollar of deposit.
# The old code made them equal by construction.
df %>%
  filter(!is.na(f_ta)) %>%
  summarise(
    n_banks         = n(),
    mean_f_ta       = mean(f_ta,    na.rm = TRUE),
    median_f_ta     = median(f_ta,  na.rm = TRUE),
    mean_fu_ta      = mean(fu_ta,   na.rm = TRUE),
    mean_fi_ta      = mean(fi_ta,   na.rm = TRUE),
    insured_share_F = mean(fi_ta / f_ta, na.rm = TRUE),
    insured_share_D = mean(insured_share_d, na.rm = TRUE)
  ) %>%
  kbl(digits = 3,
      caption = "Franchise sanity check (insured_share_F should exceed insured_share_D)") %>%
  kable_styling(full_width = FALSE)
Franchise sanity check (insured_share_F should exceed insured_share_D)
n_banks mean_f_ta median_f_ta mean_fu_ta mean_fi_ta insured_share_F insured_share_D
4602 13.17 13.804 2.617 10.553 0.796 0.724

2.4 Classify borrowers and merge loan-level aggregates

btfp_clean <- btfp_raw %>% filter(failed_bank == 0) %>%
  mutate(sub_period = assign_period(btfp_loan_date))
dw_clean   <- dw_raw   %>% filter(failed_bank == 0) %>%
  mutate(sub_period = assign_period(dw_loan_date))

btfp_crisis <- btfp_clean %>%
  filter(btfp_loan_date >= CRISIS_START & btfp_loan_date <= CRISIS_END)
dw_crisis   <- dw_clean   %>%
  filter(dw_loan_date   >= CRISIS_START & dw_loan_date   <= CRISIS_END)

# ── Aggregate BTFP loans and COLLATERAL by bank (crisis) ──
btfp_bank <- btfp_crisis %>%
  group_by(rssd_id) %>%
  summarise(
    btfp_amount           = sum(btfp_loan_amount, na.rm = TRUE) / 1000,    # $000
    btfp_n_loans          = n(),
    btfp_mean_term        = mean(btfp_term, na.rm = TRUE),
    btfp_mean_effmat      = mean(btfp_effective_maturity_days, na.rm = TRUE),
    btfp_mean_rate        = mean(btfp_interest_rate, na.rm = TRUE),
    btfp_total_collateral = sum(btfp_total_collateral, na.rm = TRUE) / 1000,
    .groups = "drop"
  ) %>% rename(idrssd = rssd_id)

# ── Aggregate DW loans and COLLATERAL by bank (crisis) ──
dw_bank <- dw_crisis %>%
  group_by(rssd_id) %>%
  summarise(
    dw_amount            = sum(dw_loan_amount, na.rm = TRUE) / 1000,       # $000
    dw_n_loans           = n(),
    dw_mean_term         = mean(dw_term, na.rm = TRUE),
    dw_mean_effmat       = mean(dw_effective_maturity_days, na.rm = TRUE),
    dw_mean_rate         = mean(dw_interest_rate, na.rm = TRUE),
    dw_total_collateral  = sum(dw_total_collateral, na.rm = TRUE) / 1000,
    dw_omo_collateral    = sum(dw_omo_eligible, na.rm = TRUE) / 1000,
    dw_nonomo_collateral = sum(dw_non_omo_eligible, na.rm = TRUE) / 1000,
    .groups = "drop"
  ) %>% rename(idrssd = rssd_id)

df <- df %>%
  left_join(btfp_bank, by = "idrssd") %>%
  left_join(dw_bank,   by = "idrssd") %>%
  mutate(
    btfp_amount  = replace_na(btfp_amount, 0),
    dw_amount    = replace_na(dw_amount, 0),
    btfp_n_loans = replace_na(btfp_n_loans, 0L),
    dw_n_loans   = replace_na(dw_n_loans, 0L),

    used_btfp = as.integer(btfp_amount > 0),
    used_dw   = as.integer(dw_amount   > 0),
    borrowed  = as.integer(used_btfp == 1 | used_dw == 1),

    borrower_type = dplyr::case_when(
      used_btfp == 1 & used_dw == 1 ~ "Both",
      used_btfp == 1                ~ "BTFP Only",
      used_dw   == 1                ~ "DW Only",
      TRUE                          ~ "Non-Borrower"
    ),

    total_borrow   = btfp_amount + dw_amount,
    borrow_ta      = 100 * safe_div(total_borrow, total_asset),
    borrow_du      = safe_div(total_borrow, uninsured_deposit),
    log_borrow     = ifelse(total_borrow > 0, log(total_borrow), NA_real_),
    gap_fill_ratio = safe_div(total_borrow, liq_gap),

    # ── CAPACITY-UTILIZATION RATIOS (Prediction 3, key diagnostic) ──
    btfp_loan_to_coll = safe_div(btfp_amount, btfp_total_collateral),
    dw_loan_to_coll   = safe_div(dw_amount,   dw_total_collateral)
  )

cat(sprintf("\nBorrower counts (crisis window %s to %s):\n",
            CRISIS_START, CRISIS_END))
## 
## Borrower counts (crisis window 2023-03-08 to 2023-05-04):
cat(sprintf("  Non-Borrowers: %s\n", fmt(sum(df$borrower_type == "Non-Borrower"))))
##   Non-Borrowers: 3,836
cat(sprintf("  DW Only      : %s\n", fmt(sum(df$borrower_type == "DW Only"))))
##   DW Only      : 341
cat(sprintf("  BTFP Only    : %s\n", fmt(sum(df$borrower_type == "BTFP Only"))))
##   BTFP Only    : 412
cat(sprintf("  Both         : %s\n", fmt(sum(df$borrower_type == "Both"))))
##   Both         : 107
cat(sprintf("  Total sample : %s\n", fmt(nrow(df))))
##   Total sample : 4,696

2.5 Create regression-ready variables

reg_vars <- c(
  "f_ta", "fu_ta", "fi_ta", "emv_ta", "v_ta",
  "ell", "ell_sec", "ell_omo", "ell_nonomo", "ell_loan",
  "rho", "rho_0", "rho_C", "rho_S", "liq_gap_ta",
  "eq_ta", "cash_ta", "sec_ta", "omo_ta", "loan_ta", "dep_ta",
  "du_ta", "mu", "wholesale_ta",
  "borrow_ta", "borrow_du", "gap_fill_ratio",
  "btfp_loan_to_coll", "dw_loan_to_coll"
)

reg_vars <- reg_vars[reg_vars %in% names(df)]

df <- df %>%
  mutate(across(all_of(reg_vars), ~ winsorize(.x), .names = "{.col}_w")) %>%
  mutate(across(all_of(paste0(reg_vars, "_w")), ~ z_std(.x), .names = "{.col}_z"))

cat(sprintf("Regression transforms created: %s winsorized + %s standardized vars\n",
            fmt(length(reg_vars)), fmt(length(reg_vars))))
## Regression transforms created: 29 winsorized + 29 standardized vars

2.6 Arbitrage-window sample (Prediction 5)

btfp_arb <- btfp_clean %>%
  filter(btfp_loan_date >= ARB_START & btfp_loan_date <= ARB_END)

btfp_arb_bank <- btfp_arb %>%
  group_by(rssd_id) %>%
  summarise(btfp_arb_amount = sum(btfp_loan_amount, na.rm = TRUE) / 1000,
            .groups = "drop") %>%
  rename(idrssd = rssd_id)

# FIX: pull beta_insured_w + deposit_cost_insured; apply Option A franchise
df_arb <- call_raw %>%
  filter(period == "2023Q3", gsib == 0, failed_bank == 0) %>%
  left_join(dssw_raw %>% filter(estimation_date == "2022Q4") %>%
              select(idrssd, beta_overall,
                     beta_uninsured_w, beta_insured_w),
            by = "idrssd") %>%
  left_join(if (HAS_DEPOSIT_COSTS) dssw_costs_2022q4 else
              tibble(idrssd                 = character(),
                     deposit_cost_insured   = numeric(),
                     deposit_cost_uninsured = numeric(),
                     deposit_cost_weighted  = numeric()),
            by = "idrssd") %>%
  mutate(
    idrssd         = as.character(idrssd),

    # Clip betas
    beta_clipped   = pmax(0, pmin(1, beta_overall)),
    beta_u_clipped = pmax(0, pmin(1, beta_uninsured_w)),
    beta_i_clipped = pmax(0, pmin(1, beta_insured_w)),

    # Costs
    cost_u_raw = ifelse(HAS_DEPOSIT_COSTS & !is.na(deposit_cost_uninsured),
                        deposit_cost_uninsured, 0),
    cost_i_raw = ifelse(HAS_DEPOSIT_COSTS & !is.na(deposit_cost_insured),
                        deposit_cost_insured, 0),

    mu             = safe_div(uninsured_deposit, total_asset, 0),
    ell            = all_asset_loss_pct,
    ell_sec        = loss_total_sec_to_ta,
    ell_omo        = loss_omo_sec_to_ta,
    ell_loan       = loss_total_loan_to_ta,

    # Deposit shares
    d_u_over_ta       = safe_div(uninsured_deposit, total_asset, 0),
    d_i_over_ta       = safe_div(pmax(total_deposit - uninsured_deposit, 0),
                                 total_asset, 0),
    d_over_ta         = safe_div(total_deposit, total_asset, 0),
    uninsured_share_d = safe_div(uninsured_deposit, total_deposit, 0),
    insured_share_d   = pmax(1 - uninsured_share_d, 0),

    # Option A franchise
    rent_u = pmax((1 - beta_u_clipped) * y_10yr - cost_u_raw, 0),
    rent_i = pmax((1 - beta_i_clipped) * y_10yr - cost_i_raw, 0),
    fu_ta = ifelse(!is.na(beta_u_clipped),
                   rent_u * cap_factor * d_u_over_ta * 100, NA_real_),
    fi_ta = ifelse(!is.na(beta_i_clipped),
                   rent_i * cap_factor * d_i_over_ta * 100, NA_real_),
    f_ta  = rowSums(cbind(fu_ta, fi_ta), na.rm = FALSE),

    F_total     = f_ta  / 100 * total_asset,
    F_uninsured = fu_ta / 100 * total_asset,
    F_insured   = fi_ta / 100 * total_asset,

    E_MV   = total_equity - all_asset_loss,
    # Post-uninsured-run fundamental value (uninsured lose full F^U, insured keep F^I)
    v      = E_MV + F_insured,     # not E_MV + (1-mu)*F_total
    v_ta   = 100 * safe_div(v, total_asset),

    omo_mv       = pmax(omo_eligible - loss_omo_sec, 0),
    rho          = safe_div(cash + omo_mv, uninsured_deposit),
    eq_ta        = book_equity_to_total_asset,
    cash_ta      = cash_to_total_asset,
    log_ta       = log(total_asset),
    wholesale_ta = fed_fund_purchase_to_total_asset + repo_to_total_asset +
                   fhlb_to_total_asset + other_borr_to_total_asset,
    ell_x_mu     = ell * mu
  ) %>%
  left_join(btfp_arb_bank, by = "idrssd") %>%
  mutate(
    btfp_arb_amount = replace_na(btfp_arb_amount, 0),
    borrowed_arb    = as.integer(btfp_arb_amount > 0)
  )

cat(sprintf("Arbitrage-window sample: %s banks, %s borrowers\n",
            fmt(nrow(df_arb)), fmt(sum(df_arb$borrowed_arb))))
## Arbitrage-window sample: 4,604 banks, 766 borrowers

3. Descriptive Tables

Table 3: Emergency Borrowing During Crisis (Mar 1 – May 4, 2023)

# ══════════════════════════════════════════════════════════════════════
# TABLE 1: EMERGENCY BORROWING DURING CRISIS (Mar 1 – May 4, 2023)
# Columns: P0|DW, P1|DW, P2|DW, P2|BTFP, P3|DW, P3|BTFP
# ══════════════════════════════════════════════════════════════════════

period_order <- c("P0: Pre-Crisis", "P1: SVB Week",
                  "P2: SVB to FRC", "P3: FRC Week")

# ── A. Loan-level tables with derived fields ─────────────────────────
dw_tab <- dw_clean %>%
  filter(dw_loan_date >= P0_start, dw_loan_date <= P3_end) %>%
  mutate(
    period        = assign_period(dw_loan_date),
    amount        = dw_loan_amount,
    collateral    = dw_total_collateral,
    term          = dw_term,
    held_days     = dw_effective_maturity_days,   # = repayment - loan
    rate          = dw_interest_rate,
    loan_to_coll  = safe_div(dw_loan_amount, dw_total_collateral),
    term_below_5  = as.integer(dw_term <  5),
    term_atleast5 = as.integer(dw_term >= 5),
    early_repay   = ifelse(is.na(dw_repayment_date) | is.na(dw_maturity_date),
                           NA_integer_,
                           as.integer(dw_repayment_date < dw_maturity_date)),
    facility      = "DW"
  )

btfp_tab <- btfp_clean %>%
  filter(btfp_loan_date >= P2_start, btfp_loan_date <= P3_end) %>%   # BTFP starts P2
  mutate(
    period        = assign_period(btfp_loan_date),
    amount        = btfp_loan_amount,
    collateral    = btfp_total_collateral,
    term          = btfp_term,
    held_days     = btfp_effective_maturity_days,
    rate          = btfp_interest_rate,
    loan_to_coll  = safe_div(btfp_loan_amount, btfp_total_collateral),
    term_below_5  = as.integer(btfp_term <  5),
    term_atleast5 = as.integer(btfp_term >= 5),
    early_repay   = ifelse(is.na(btfp_repayment_date) | is.na(btfp_maturity_date),
                           NA_integer_,
                           as.integer(btfp_repayment_date < btfp_maturity_date)),
    facility      = "BTFP"
  )

# ── B. Prior-borrower set per period (across BOTH facilities) ────────
all_borrow <- bind_rows(
  dw_tab   %>% select(rssd_id, period),
  btfp_tab %>% select(rssd_id, period)
)

prior_set <- function(p) {
  i <- match(p, period_order)
  if (i == 1) return(character(0))
  unique(all_borrow$rssd_id[all_borrow$period %in% period_order[seq_len(i - 1)]])
}

# ── C. Per-cell summariser ───────────────────────────────────────────
summarise_cell <- function(data, period_label, facility_label) {
  x     <- data %>% filter(period == period_label, facility == facility_label)
  prior <- prior_set(period_label)
  banks <- unique(x$rssd_id)

  tibble(
    col_id                 = paste(period_label, facility_label, sep = " | "),
    `N (Loans)`            = nrow(x),
    `N (Banks)`            = length(banks),
    `  New banks`          = sum(!banks %in% prior),
    `  Returning banks`    = sum(banks %in% prior),
    `Total borrow ($B)`    = sum(x$amount, na.rm = TRUE) / 1e9,
    `Mean borrow ($M)`     = mean(x$amount,   na.rm = TRUE) / 1e6,
    `Median borrow ($M)`   = median(x$amount, na.rm = TRUE) / 1e6,
    `Loan/Coll (aggregate)`= safe_div(sum(x$amount,     na.rm = TRUE),
                                      sum(x$collateral, na.rm = TRUE)),
    `Term — mean (days)`   = mean(x$term,   na.rm = TRUE),
    `Term — median (days)` = median(x$term, na.rm = TRUE),
    `% Term < 5 days`      = 100 * mean(x$term_below_5,  na.rm = TRUE),
    `% Term ≥ 5 days`      = 100 * mean(x$term_atleast5, na.rm = TRUE),
    `% Early repayment`    = 100 * mean(x$early_repay,   na.rm = TRUE),
    `Held days — mean`     = mean(x$held_days,   na.rm = TRUE),
    `Held days — median`   = median(x$held_days, na.rm = TRUE),
    `Rate (%)`             = mean(x$rate, na.rm = TRUE)
  )
}

# ── D. Build all six cells ───────────────────────────────────────────
cells <- bind_rows(
  summarise_cell(dw_tab,   "P0: Pre-Crisis", "DW"),
  summarise_cell(dw_tab,   "P1: SVB Week",   "DW"),
  summarise_cell(dw_tab,   "P2: SVB to FRC", "DW"),
  summarise_cell(btfp_tab, "P2: SVB to FRC", "BTFP"),
  summarise_cell(dw_tab,   "P3: FRC Week",   "DW"),
  summarise_cell(btfp_tab, "P3: FRC Week",   "BTFP")
)

# ── E. Pivot to final layout (rows = stats, cols = period|facility) ──
col_order <- c("P0: Pre-Crisis | DW",
               "P1: SVB Week | DW",
               "P2: SVB to FRC | DW", "P2: SVB to FRC | BTFP",
               "P3: FRC Week | DW",   "P3: FRC Week | BTFP")

stat_levels <- setdiff(names(cells), "col_id")

table1 <- cells %>%
  pivot_longer(-col_id, names_to = "Statistic", values_to = "value") %>%
  mutate(Statistic = factor(Statistic, levels = stat_levels),
         col_id    = factor(col_id,    levels = col_order)) %>%
  pivot_wider(names_from = col_id, values_from = value) %>%
  arrange(Statistic) %>%
  select(Statistic, all_of(col_order))   # force column order

# Quick sanity print so you can SEE the data regardless of HTML issues
cat("Table 1 shape:", nrow(table1), "rows x", ncol(table1), "cols\n")
## Table 1 shape: 16 rows x 7 cols
print(as.data.frame(table1), row.names = FALSE, digits = 3)
##              Statistic P0: Pre-Crisis | DW P1: SVB Week | DW
##              N (Loans)             171.000           124.000
##              N (Banks)              72.000            68.000
##              New banks              72.000            33.000
##        Returning banks               0.000            35.000
##      Total borrow ($B)               9.455            10.102
##       Mean borrow ($M)              55.294            81.471
##     Median borrow ($M)              10.000            10.475
##  Loan/Coll (aggregate)               0.172             0.176
##     Term — mean (days)               5.251             3.839
##   Term — median (days)               1.000             1.000
##        % Term < 5 days              86.550            87.097
##        % Term ≥ 5 days              13.450            12.903
##      % Early repayment               6.433             5.645
##       Held days — mean               4.076             3.016
##     Held days — median               1.000             1.000
##               Rate (%)               4.750             4.750
##  P2: SVB to FRC | DW P2: SVB to FRC | BTFP P3: FRC Week | DW
##             1492.000              1032.000           213.000
##              406.000               472.000           101.000
##              340.000               442.000            34.000
##               66.000                30.000            67.000
##              518.485               122.358             9.613
##              347.510               118.564            45.130
##               10.000                15.000             8.000
##                0.329                 0.406             0.146
##                4.405               313.568             3.831
##                1.000               365.000             1.000
##               88.003                10.659            90.610
##               11.997                89.341             9.390
##                4.826                64.922             2.817
##                3.005               151.043             2.784
##                1.000                14.500             1.000
##                4.920                 4.678             5.050
##  P3: FRC Week | BTFP
##              279.000
##              184.000
##               54.000
##              130.000
##               11.326
##               40.594
##               10.000
##                0.294
##              322.305
##              365.000
##                7.527
##               92.473
##               60.932
##              177.219
##              222.000
##                4.788
# ── F. Render HTML (appears in Viewer pane when run interactively) ───
tbl_k <- table1 %>%
  kbl(digits = 2, align = c("l", rep("r", 6)),
      caption = "Table 1: Emergency Borrowing During Crisis (March 1 – May 4, 2023)") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE, position = "left") %>%
  add_header_above(c(" " = 1, "Pre-Crisis" = 1, "SVB Week" = 1,
                     "SVB → FRC" = 2, "FRC Week" = 2)) %>%
  pack_rows("Participation",   1,  4) %>%
  pack_rows("Amounts",         5,  7) %>%
  pack_rows("Collateral use",  8,  8) %>%
  pack_rows("Maturity terms",  9, 13) %>%
  pack_rows("Realized life",  14, 15) %>%
  pack_rows("Price",          16, 16)

tbl_k   # explicit print
Table 1: Emergency Borrowing During Crisis (March 1 – May 4, 2023)
Pre-Crisis
SVB Week
SVB → FRC
FRC Week
Statistic P0: Pre-Crisis | DW P1: SVB Week | DW P2: SVB to FRC | DW P2: SVB to FRC | BTFP P3: FRC Week | DW P3: FRC Week | BTFP
Participation
N (Loans) 171.00 124.00 1492.00 1032.00 213.00 279.00
N (Banks) 72.00 68.00 406.00 472.00 101.00 184.00
New banks 72.00 33.00 340.00 442.00 34.00 54.00
Returning banks 0.00 35.00 66.00 30.00 67.00 130.00
Amounts
Total borrow (\(B) </td> <td style="text-align:right;"> 9.46 </td> <td style="text-align:right;"> 10.10 </td> <td style="text-align:right;"> 518.48 </td> <td style="text-align:right;"> 122.36 </td> <td style="text-align:right;"> 9.61 </td> <td style="text-align:right;"> 11.33 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Mean borrow (\)M) 55.29 81.47 347.51 118.56 45.13 40.59
Median borrow ($M) 10.00 10.47 10.00 15.00 8.00 10.00
Collateral use
Loan/Coll (aggregate) 0.17 0.18 0.33 0.41 0.15 0.29
Maturity terms
Term — mean (days) 5.25 3.84 4.41 313.57 3.83 322.30
Term — median (days) 1.00 1.00 1.00 365.00 1.00 365.00
% Term < 5 days 86.55 87.10 88.00 10.66 90.61 7.53
% Term ≥ 5 days 13.45 12.90 12.00 89.34 9.39 92.47
% Early repayment 6.43 5.65 4.83 64.92 2.82 60.93
Realized life
Held days — mean 4.08 3.02 3.01 151.04 2.78 177.22
Held days — median 1.00 1.00 1.00 14.50 1.00 222.00
Price
Rate (%) 4.75 4.75 4.92 4.68 5.05 4.79
# Optional: save LaTeX
save_kbl_latex(table1, "table3_emergency_borrowing",
               caption = "Emergency Borrowing During Crisis (March 1 – May 4, 2023)")

Figure 1: Crisis Period Borrowing

# ══════════════════════════════════════════════════════════════════════
# FIGURE 1: DAILY EMERGENCY BORROWING DURING CRISIS (Mar 1 – May 4, 2023)
# Line version — two panels: volume ($B) and unique banks/day
# Sample: excludes failed banks (already in *_clean) and GSIBs
# ══════════════════════════════════════════════════════════════════════

# ── A. GSIB list from 2022Q4 call panel ──────────────────────────────
gsib_ids <- call_raw %>%
  filter(period == "2022Q4", gsib == 1) %>%
  pull(idrssd) %>% unique() %>% as.character()

cat("Excluding", length(gsib_ids), "GSIBs from loan data.\n")
## Excluding 33 GSIBs from loan data.
# ── B. Daily aggregation by facility ─────────────────────────────────
plot_start <- P0_start   # 2023-03-01
plot_end   <- P3_end     # 2023-05-04

dw_daily <- dw_clean %>%
  filter(!rssd_id %in% gsib_ids,
         dw_loan_date >= plot_start, dw_loan_date <= plot_end) %>%
  group_by(date = dw_loan_date) %>%
  summarise(volume_b = sum(dw_loan_amount, na.rm = TRUE) / 1e9,
            n_banks  = n_distinct(rssd_id),
            .groups  = "drop") %>%
  mutate(facility = "DW")

btfp_daily <- btfp_clean %>%
  filter(!rssd_id %in% gsib_ids,
         btfp_loan_date >= plot_start, btfp_loan_date <= plot_end) %>%
  group_by(date = btfp_loan_date) %>%
  summarise(volume_b = sum(btfp_loan_amount, na.rm = TRUE) / 1e9,
            n_banks  = n_distinct(rssd_id),
            .groups  = "drop") %>%
  mutate(facility = "BTFP")

# Full grid: missing days → zero (so the line touches the x-axis on quiet days)
daily_df <- expand.grid(
  date     = seq(plot_start, plot_end, by = "1 day"),
  facility = c("DW", "BTFP"),
  stringsAsFactors = FALSE
) %>%
  left_join(bind_rows(dw_daily, btfp_daily), by = c("date", "facility")) %>%
  mutate(volume_b = replace_na(volume_b, 0),
         n_banks  = replace_na(n_banks, 0),
         facility = factor(facility, levels = c("DW", "BTFP")))

# BTFP didn't exist pre Mar 13 → drop those rows so the BTFP line starts at launch
daily_df <- daily_df %>%
  filter(!(facility == "BTFP" & date < BTFP_LAUNCH))

# ── C. Key events ────────────────────────────────────────────────────
events <- tribble(
  ~date,                 ~label,
  as.Date("2023-03-10"), "SVB failed",
  as.Date("2023-03-12"), "Signature failed",
  as.Date("2023-03-13"), "BTFP launched",
  as.Date("2023-05-01"), "First Republic failed"
)

fac_cols <- c("DW" = "#E53935", "BTFP" = "#1565C0")

# ── D. Panel A: Daily volume ─────────────────────────────────────────
y_max_A <- max(daily_df$volume_b, na.rm = TRUE)

pA <- ggplot(daily_df, aes(x = date, y = volume_b,
                           color = facility, group = facility)) +
  geom_line(linewidth = 0.9) +
  geom_point(size = 1.3, alpha = 0.85) +
  geom_vline(data = events, aes(xintercept = date),
             linetype = "dashed", color = "grey30", linewidth = 0.35) +
  geom_text(data = events,
            aes(x = date, y = y_max_A * 1.03, label = label),
            angle = 90, hjust = 0, vjust = -0.2, size = 2.9,
            color = "grey20", inherit.aes = FALSE) +
  scale_color_manual(values = fac_cols, name = NULL) +
  scale_x_date(date_breaks = "1 week", date_labels = "%b %d",
               limits = c(plot_start, plot_end),
               expand = expansion(mult = c(0.01, 0.01))) +
  scale_y_continuous(expand = expansion(mult = c(0.02, 0.45))) +
  labs(title = "A. Daily emergency-borrowing volume",
       subtitle = sprintf(
         "DW vs BTFP; excludes failed banks and GSIBs",
         length(gsib_ids)),
       x = NULL, y = "Daily volume ($B)") +
  theme_gp

# ── E. Panel B: Number of unique banks per day ───────────────────────
pB <- ggplot(daily_df, aes(x = date, y = n_banks,
                           color = facility, group = facility)) +
  geom_line(linewidth = 0.9) +
  geom_point(size = 1.3, alpha = 0.85) +
  geom_vline(data = events, aes(xintercept = date),
             linetype = "dashed", color = "grey30", linewidth = 0.35) +
  scale_color_manual(values = fac_cols, name = NULL) +
  scale_x_date(date_breaks = "1 week", date_labels = "%b %d",
               limits = c(plot_start, plot_end),
               expand = expansion(mult = c(0.01, 0.01))) +
  scale_y_continuous(expand = expansion(mult = c(0.02, 0.12))) +
  labs(title = "B. Number of unique banks borrowing per day",
       x = "Date", y = "Number of banks") +
  theme_gp

# ── F. Combine, print, save PDF ─────────────────────────────────────
fig1_crisis <- (pA / pB) +
  plot_layout(heights = c(1.25, 1), guides = "collect") &
  theme(legend.position = "bottom")

print(fig1_crisis)

save_figure(fig1_crisis, "fig_crisis_daily_borrowing",
            width = 12, height = 9)

Table 4: Mean Characteristics of Borrowers vs Non-Borrowers (2022Q4)

# ══════════════════════════════════════════════════════════════════════
# TABLE 2: Mean Characteristics of Borrowers vs Non-Borrowers (2022Q4)
# Columns mirror Table 1: 6 period|facility cells + 1 Non-Borrower column
# Sample: 2022Q4 cross-section, excl. GSIBs and failed banks (already in df)
# ══════════════════════════════════════════════════════════════════════

# ── A. Assemble borrower sets per period|facility ────────────────────
dw_with_period <- dw_clean %>%
  filter(dw_loan_date >= P0_start, dw_loan_date <= P3_end) %>%
  mutate(period = assign_period(dw_loan_date))

btfp_with_period <- btfp_clean %>%
  filter(btfp_loan_date >= P2_start, btfp_loan_date <= P3_end) %>%
  mutate(period = assign_period(btfp_loan_date))

get_banks <- function(data, period_label)
  unique(as.character(data$rssd_id[data$period == period_label]))

borrower_sets <- list(
  "P0: Pre-Crisis | DW"    = get_banks(dw_with_period,   "P0: Pre-Crisis"),
  "P1: SVB Week | DW"      = get_banks(dw_with_period,   "P1: SVB Week"),
  "P2: SVB to FRC | DW"    = get_banks(dw_with_period,   "P2: SVB to FRC"),
  "P2: SVB to FRC | BTFP"  = get_banks(btfp_with_period, "P2: SVB to FRC"),
  "P3: FRC Week | DW"      = get_banks(dw_with_period,   "P3: FRC Week"),
  "P3: FRC Week | BTFP"    = get_banks(btfp_with_period, "P3: FRC Week")
)

# Non-borrower = banks in df that never borrowed in ANY period (P0-P3)
all_period_borrowers <- unique(unlist(borrower_sets, use.names = FALSE))
non_borrower_ids <- setdiff(as.character(df$idrssd), all_period_borrowers)

# ── B. Prepare derived variables on df ───────────────────────────────

df <- df %>%
  mutate(
    total_asset_b = total_asset / 1e6,                  # thousands → $B
    mu_pct        = 100 * mu,
  )

# ── C. Variables to show, with display labels (in desired row order) ─
tbl2_vars <- c(
  "total_asset_b"  = "Total assets ($B)",
  "cash_ta"        = "Cash / TA (%)",
  "sec_ta"         = "Securities / TA (%)",
  "loan_ta"        = "Loans / TA (%)",
  "ell"            = "MTM loss — total / TA (%)",
  "ell_omo"        = "    OMO-eligible sec. loss / TA (%)",
  "ell_nonomo"     = "    Non-OMO sec. loss / TA (%)",
  "ell_loan"       = "    Loan MTM loss / TA (%)",
  "eq_ta"          = "Book equity / TA (%)",
  "dep_ta"         = "Total deposits / TA (%)",
  "mu_pct"         = "Uninsured / Deposits (%)",
  "du_ta"          = "Uninsured leverage (%)",
  "loan_to_deposit" = "Loan / Total deposits",
  "wholesale_ta"   = "Wholesale funding / TA (%)",
  "fhlb_ta"        = "FHLB advances / TA (%)",
  "roa"            = "ROA (%)"
)

# Drop any variables that aren't in df (keeps code robust if roa is missing)
tbl2_vars <- tbl2_vars[names(tbl2_vars) %in% names(df)]

# ── D. Per-group summariser ──────────────────────────────────────────
summarise_group <- function(ids, group_label) {
  sub <- df %>% filter(idrssd %in% ids)
  out <- tibble(group = group_label, `N (banks)` = nrow(sub))
  for (v in names(tbl2_vars)) {
    out[[ tbl2_vars[[v]] ]] <- mean(sub[[v]], na.rm = TRUE)
  }
  out
}

# ── E. Build all seven group rows ────────────────────────────────────
groups <- bind_rows(
  summarise_group(borrower_sets[["P0: Pre-Crisis | DW"]],   "P0: Pre-Crisis | DW"),
  summarise_group(borrower_sets[["P1: SVB Week | DW"]],     "P1: SVB Week | DW"),
  summarise_group(borrower_sets[["P2: SVB to FRC | DW"]],   "P2: SVB to FRC | DW"),
  summarise_group(borrower_sets[["P2: SVB to FRC | BTFP"]], "P2: SVB to FRC | BTFP"),
  summarise_group(borrower_sets[["P3: FRC Week | DW"]],     "P3: FRC Week | DW"),
  summarise_group(borrower_sets[["P3: FRC Week | BTFP"]],   "P3: FRC Week | BTFP"),
  summarise_group(non_borrower_ids,                          "Non-Borrower")
)

# ── F. Pivot to final layout (rows = stats, cols = groups) ───────────
col_order <- c(
  "P0: Pre-Crisis | DW", "P1: SVB Week | DW",
  "P2: SVB to FRC | DW", "P2: SVB to FRC | BTFP",
  "P3: FRC Week | DW",   "P3: FRC Week | BTFP",
  "Non-Borrower"
)
stat_order <- c("N (banks)", unname(tbl2_vars))

table2 <- groups %>%
  pivot_longer(-group, names_to = "Statistic", values_to = "value") %>%
  mutate(
    Statistic = factor(Statistic, levels = stat_order),
    group     = factor(group,     levels = col_order)
  ) %>%
  arrange(Statistic, group) %>%
  pivot_wider(names_from = group, values_from = value) %>%
  select(Statistic, all_of(col_order))

# ── G. Quick console view + kable render ─────────────────────────────
cat("Table 2 shape:", nrow(table2), "rows x", ncol(table2), "cols\n")
## Table 2 shape: 17 rows x 8 cols
cat("Bank counts per group:\n")
## Bank counts per group:
print(groups %>% select(group, `N (banks)`), n = Inf)
## # A tibble: 7 × 2
##   group                 `N (banks)`
##   <chr>                       <int>
## 1 P0: Pre-Crisis | DW            72
## 2 P1: SVB Week | DW              67
## 3 P2: SVB to FRC | DW           397
## 4 P2: SVB to FRC | BTFP         460
## 5 P3: FRC Week | DW             101
## 6 P3: FRC Week | BTFP           182
## 7 Non-Borrower                 3815
tbl2_k <- table2 %>%
  kbl(digits = 2, align = c("l", rep("r", 7)),
      caption = "Table 2: Mean Characteristics of Borrowers vs Non-Borrowers (2022Q4 baseline)") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE, position = "left", font_size = 12) %>%
  add_header_above(c(" " = 1,
                     "Pre-Crisis" = 1, "SVB Week" = 1,
                     "SVB → FRC" = 2, "FRC Week" = 2,
                     "Never Borrowed" = 1)) %>%
  pack_rows("Sample size",        1,  1) %>%
  pack_rows("Size & composition", 2,  5) %>%
  pack_rows("MTM losses",         6,  9) %>%
  pack_rows("Capital & funding", 10, 14) %>%
  pack_rows("Profitability",     15, nrow(table2))

tbl2_k
Table 2: Mean Characteristics of Borrowers vs Non-Borrowers (2022Q4 baseline)
Pre-Crisis
SVB Week
SVB → FRC
FRC Week
Never Borrowed
Statistic P0: Pre-Crisis | DW P1: SVB Week | DW P2: SVB to FRC | DW P2: SVB to FRC | BTFP P3: FRC Week | DW P3: FRC Week | BTFP Non-Borrower
Sample size
N (banks) 72.00 67.00 397.00 460.00 101.00 182.00 3815.00
Size & composition
Total assets ($B) 4.40 7.59 6.51 6.35 2.76 5.12 1.67
Cash / TA (%) 4.38 3.77 6.08 4.68 5.34 4.19 9.58
Securities / TA (%) 26.66 24.97 24.46 28.03 25.02 29.81 24.18
Loans / TA (%) 63.64 65.33 63.53 61.61 64.15 60.35 59.76
MTM losses
MTM loss — total / TA (%) 9.33 8.92 9.03 9.61 8.78 9.90 8.39
OMO-eligible sec. loss / TA (%) 1.39 1.68 1.65 1.78 1.51 1.76 1.05
Non-OMO sec. loss / TA (%) 2.92 2.48 2.45 2.95 2.58 3.15 2.50
Loan MTM loss / TA (%) 5.39 5.20 5.40 5.28 5.04 5.31 5.00
Capital & funding
Book equity / TA (%) 9.05 8.41 8.89 8.16 8.99 8.10 11.38
Total deposits / TA (%) 84.42 83.97 85.05 85.47 84.84 86.07 84.22
Uninsured / Deposits (%) 31.99 35.55 33.51 32.10 30.75 31.13 26.46
Uninsured leverage (%) 26.87 29.52 28.45 27.42 25.96 26.89 22.41
Loan / Total deposits 76.00 78.28 75.73 72.96 76.20 70.95 70.98
Profitability
Wholesale funding / TA (%) 5.65 6.72 5.04 5.48 5.33 5.09 3.32
FHLB advances / TA (%) 3.53 4.56 3.61 4.04 3.72 3.58 2.51
ROA (%) 1.03 1.04 1.16 1.07 1.15 1.01 1.42
save_kbl_latex(table2, "table4_borrower_characteristics",
               caption = "Mean Characteristics of Borrowers vs Non-Borrowers (2022Q4 baseline)")

Table 5

# ══════════════════════════════════════════════════════════════════════
# TABLE 3: Solvency Measures, Size, and Borrowing Concentration
# Columns mirror Table 1: 6 period|facility + Non-Borrower
# Post-run value v = E_MV + F_insured (correct under Option A franchise)
# ══════════════════════════════════════════════════════════════════════

# ── A. Borrower identities AND borrowing amounts by period|facility ──
dw_pf <- dw_clean %>%
  filter(dw_loan_date >= P0_start, dw_loan_date <= P3_end) %>%
  mutate(period = assign_period(dw_loan_date))

btfp_pf <- btfp_clean %>%
  filter(btfp_loan_date >= P2_start, btfp_loan_date <= P3_end) %>%
  mutate(period = assign_period(btfp_loan_date))

# Per-bank amounts within each cell (needed for concentration stats)
amt_by_cell <- bind_rows(
  dw_pf   %>% group_by(period, rssd_id) %>%
    summarise(amt = sum(dw_loan_amount,   na.rm = TRUE), .groups = "drop") %>%
    mutate(facility = "DW"),
  btfp_pf %>% group_by(period, rssd_id) %>%
    summarise(amt = sum(btfp_loan_amount, na.rm = TRUE), .groups = "drop") %>%
    mutate(facility = "BTFP")
) %>% mutate(rssd_id = as.character(rssd_id))

get_banks <- function(p, f) amt_by_cell %>%
  filter(period == p, facility == f) %>% pull(rssd_id) %>% unique()

borrower_sets <- list(
  "P0: Pre-Crisis | DW"   = get_banks("P0: Pre-Crisis", "DW"),
  "P1: SVB Week | DW"     = get_banks("P1: SVB Week",   "DW"),
  "P2: SVB to FRC | DW"   = get_banks("P2: SVB to FRC", "DW"),
  "P2: SVB to FRC | BTFP" = get_banks("P2: SVB to FRC", "BTFP"),
  "P3: FRC Week | DW"     = get_banks("P3: FRC Week",   "DW"),
  "P3: FRC Week | BTFP"   = get_banks("P3: FRC Week",   "BTFP")
)

# Never-borrowed = banks in df that didn't appear in any cell
non_borrower_ids <- setdiff(as.character(df$idrssd),
                            unique(unlist(borrower_sets, use.names = FALSE)))

# Prior-period borrower set for new/repeat split (same logic as Table 1)
period_order <- c("P0: Pre-Crisis", "P1: SVB Week",
                  "P2: SVB to FRC", "P3: FRC Week")
flat_borrowers <- amt_by_cell %>% select(rssd_id, period) %>% distinct()
prior_banks <- function(p) {
  i <- match(p, period_order)
  if (i == 1) return(character(0))
  flat_borrowers %>% filter(period %in% period_order[seq_len(i - 1)]) %>%
    pull(rssd_id) %>% unique()
}

# ── B. Derive solvency variables on df ───────────────────────────────
df <- df %>%
  mutate(
    emv_f_ta      = 100 * safe_div(E_MV_F, total_asset),
    v_postrun     = E_MV_F_insured,                   # correct under Option A
    v_postrun_ta  = 100 * safe_div(v_postrun, total_asset),
    emv_neg       = as.integer(E_MV          < 0),
    emv_f_neg     = as.integer(E_MV_F        < 0),
    v_postrun_neg = as.integer(v_postrun     < 0),
    ta_billion    = total_asset / 1e6                  # thousands → $B
  )

ta_p90 <- quantile(df$total_asset, 0.9, na.rm = TRUE)   # top-decile TA cut

# ── C. Per-group summariser ──────────────────────────────────────────
summarise_solvency <- function(ids, period_label = NA, facility_label = NA) {
  sub   <- df %>% filter(idrssd %in% ids)
  prior <- if (is.na(period_label)) character(0) else prior_banks(period_label)

  # New/repeat counts
  n_new    <- sum(!ids %in% prior)
  n_repeat <- sum( ids %in% prior)
  if (is.na(period_label)) { n_new <- NA_integer_; n_repeat <- NA_integer_ }

  # Concentration metrics (only for period|facility cells, not non-borrowers)
  if (!is.na(period_label) && !is.na(facility_label)) {
    cell <- amt_by_cell %>%
      filter(period == period_label, facility == facility_label) %>%
      left_join(df %>% select(idrssd, total_asset, v_postrun_neg, emv_neg),
                by = c("rssd_id" = "idrssd"))
    total_amt    <- sum(cell$amt, na.rm = TRUE)
    shares       <- cell$amt / total_amt
    top10_share  <- sum(sort(shares, decreasing = TRUE)[seq_len(min(10, length(shares)))])
    hhi          <- sum(shares^2)
    sh_v_neg     <- sum(cell$amt[cell$v_postrun_neg == 1], na.rm = TRUE) / total_amt
    sh_emv_neg   <- sum(cell$amt[cell$emv_neg       == 1], na.rm = TRUE) / total_amt
    sh_top_decile <- sum(cell$amt[cell$total_asset >= ta_p90], na.rm = TRUE) / total_amt
  } else {
    top10_share <- NA_real_; hhi <- NA_real_
    sh_v_neg <- NA_real_; sh_emv_neg <- NA_real_; sh_top_decile <- NA_real_
  }

  tibble(
    `N (banks)`                        = nrow(sub),
    `  of which new`                   = n_new,
    `  of which repeat`                = n_repeat,
    # Solvency — no franchise (asset-side book/market equity)
    `E_MV / TA  mean (%)`              = mean(sub$emv_ta,   na.rm = TRUE),
    `E_MV / TA  median (%)`            = median(sub$emv_ta, na.rm = TRUE),
    `% with E_MV < 0`                  = 100 * mean(sub$emv_neg, na.rm = TRUE),
    # Solvency — with full franchise (no-run fundamental)
    `E_MV_F / TA  mean (%)`            = mean(sub$emv_f_ta,   na.rm = TRUE),
    `E_MV_F / TA  median (%)`          = median(sub$emv_f_ta, na.rm = TRUE),
    `% with E_MV_F < 0`                = 100 * mean(sub$emv_f_neg, na.rm = TRUE),
    # Post uninsured-run: v = E_MV + F_insured
    `v / TA  mean (%)`                 = mean(sub$v_postrun_ta,   na.rm = TRUE),
    `v / TA  median (%)`               = median(sub$v_postrun_ta, na.rm = TRUE),
    `% with v < 0 (post-run)`          = 100 * mean(sub$v_postrun_neg, na.rm = TRUE),
    # Size
    `Total assets mean ($B)`           = mean(sub$ta_billion,   na.rm = TRUE),
    `Total assets median ($B)`         = median(sub$ta_billion, na.rm = TRUE),
    `log(TA) mean`                     = mean(sub$log_ta, na.rm = TRUE),
    # Concentration of borrowing
    `Top-10 banks' share of borrow (%)`= 100 * top10_share,
    `HHI of borrow shares`             = hhi,
    `% of borrow by v<0 (post-run) banks` = 100 * sh_v_neg,
    `% of borrow by E_MV<0 banks`      = 100 * sh_emv_neg,
    `% of borrow by top-decile TA`     = 100 * sh_top_decile
  )
}

# ── D. Build and assemble all group rows ─────────────────────────────
group_spec <- tibble(
  col_id   = c("P0: Pre-Crisis | DW", "P1: SVB Week | DW",
               "P2: SVB to FRC | DW", "P2: SVB to FRC | BTFP",
               "P3: FRC Week | DW",   "P3: FRC Week | BTFP"),
  period   = c("P0: Pre-Crisis",  "P1: SVB Week",
               "P2: SVB to FRC",  "P2: SVB to FRC",
               "P3: FRC Week",    "P3: FRC Week"),
  facility = c("DW", "DW", "DW", "BTFP", "DW", "BTFP")
)

rows_list <- lapply(seq_len(nrow(group_spec)), function(i) {
  r <- summarise_solvency(borrower_sets[[group_spec$col_id[i]]],
                          period_label   = group_spec$period[i],
                          facility_label = group_spec$facility[i])
  r$col_id <- group_spec$col_id[i]; r
})
nb_row <- summarise_solvency(non_borrower_ids)
nb_row$col_id <- "Non-Borrower"

cells <- bind_rows(rows_list) %>% bind_rows(nb_row)

# ── E. Pivot to final layout ─────────────────────────────────────────
col_order  <- c(group_spec$col_id, "Non-Borrower")
stat_order <- setdiff(names(cells), "col_id")

table3 <- cells %>%
  pivot_longer(-col_id, names_to = "Statistic", values_to = "value") %>%
  mutate(Statistic = factor(Statistic, levels = stat_order),
         col_id    = factor(col_id,    levels = col_order)) %>%
  arrange(Statistic, col_id) %>%
  pivot_wider(names_from = col_id, values_from = value) %>%
  select(Statistic, all_of(col_order))

cat("Table 3 shape:", nrow(table3), "rows x", ncol(table3), "cols\n")
## Table 3 shape: 20 rows x 8 cols
print(as.data.frame(table3), row.names = FALSE, digits = 3)
##                            Statistic P0: Pre-Crisis | DW P1: SVB Week | DW
##                            N (banks)             72.0000           67.0000
##                         of which new             72.0000           33.0000
##                      of which repeat              0.0000           35.0000
##                  E_MV / TA  mean (%)             -0.2741           -0.5191
##                E_MV / TA  median (%)             -0.8408           -1.1788
##                      % with E_MV < 0             59.7222           58.2090
##                E_MV_F / TA  mean (%)             12.1415           11.4190
##              E_MV_F / TA  median (%)             11.6592           11.4464
##                    % with E_MV_F < 0              0.0000            0.0000
##                     v / TA  mean (%)              9.2846            8.5093
##                   v / TA  median (%)              8.5761            8.4155
##              % with v < 0 (post-run)              1.3889            1.4925
##               Total assets mean ($B)              4.4025            7.5907
##             Total assets median ($B)              0.9680            1.3270
##                         log(TA) mean             14.0283           14.4322
##    Top-10 banks' share of borrow (%)             83.7907           84.2176
##                 HHI of borrow shares              0.1020            0.0963
##  % of borrow by v<0 (post-run) banks              0.0542            0.0517
##          % of borrow by E_MV<0 banks             48.8703           38.2778
##         % of borrow by top-decile TA             78.3040           88.9797
##  P2: SVB to FRC | DW P2: SVB to FRC | BTFP P3: FRC Week | DW
##              397.000               460.000          101.0000
##              340.000               442.000           34.0000
##               66.000                30.000           67.0000
##               -0.133                -1.446            0.2153
##               -0.229                -1.080            0.0369
##               51.889                58.913           48.5149
##               12.516                11.432           12.8744
##               12.485                11.576           12.8153
##                1.015                 0.659            1.0000
##                9.430                 8.425           10.1197
##                9.435                 8.635            9.5864
##                3.807                 4.396            2.0000
##                6.514                 6.350            2.7638
##                0.948                 0.673            0.8237
##               14.012                13.729           13.7549
##               94.312                64.957           82.2555
##                0.338                 0.120            0.1353
##                0.947                 3.698            0.2234
##               63.935                29.687           46.4463
##               97.828                87.133           82.0336
##  P3: FRC Week | BTFP Non-Borrower
##             182.0000     3815.000
##              54.0000           NA
##             130.0000           NA
##              -1.8072        2.790
##              -1.3880        1.525
##              59.8901       40.269
##              11.4197       15.194
##              11.6306       14.614
##               0.5495        0.510
##               8.3489       12.656
##               8.7105       11.992
##               5.4945        1.422
##               5.1194        1.668
##               0.6529        0.263
##              13.5647       12.597
##              57.2853           NA
##               0.0394           NA
##               5.9766           NA
##              65.1593           NA
##              72.0261           NA
# ── F. Render ────────────────────────────────────────────────────────
tbl3_k <- table3 %>%
  kbl(digits = 2, align = c("l", rep("r", 7)),
      caption = "Table 3: Solvency Measures, Size, and Borrowing Concentration (2022Q4 baseline)") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE, position = "left", font_size = 11) %>%
  add_header_above(c(" " = 1,
                     "Pre-Crisis" = 1, "SVB Week" = 1,
                     "SVB → FRC" = 2, "FRC Week" = 2,
                     "Never" = 1)) %>%
  pack_rows("Participation",                 1,  3) %>%
  pack_rows("Solvency: no franchise",        4,  6) %>%
  pack_rows("Solvency: with franchise",      7,  9) %>%
  pack_rows("Solvency: post uninsured run", 10, 12) %>%
  pack_rows("Size distribution",            13, 15) %>%
  pack_rows("Concentration of borrowing",   16, 20)

tbl3_k
Table 3: Solvency Measures, Size, and Borrowing Concentration (2022Q4 baseline)
Pre-Crisis
SVB Week
SVB → FRC
FRC Week
Never
Statistic P0: Pre-Crisis | DW P1: SVB Week | DW P2: SVB to FRC | DW P2: SVB to FRC | BTFP P3: FRC Week | DW P3: FRC Week | BTFP Non-Borrower
Participation
N (banks) 72.00 67.00 397.00 460.00 101.00 182.00 3815.00
of which new 72.00 33.00 340.00 442.00 34.00 54.00 NA
of which repeat 0.00 35.00 66.00 30.00 67.00 130.00 NA
Solvency: no franchise
E_MV / TA mean (%) -0.27 -0.52 -0.13 -1.45 0.22 -1.81 2.79
E_MV / TA median (%) -0.84 -1.18 -0.23 -1.08 0.04 -1.39 1.52
% with E_MV < 0 59.72 58.21 51.89 58.91 48.51 59.89 40.27
Solvency: with franchise
E_MV_F / TA mean (%) 12.14 11.42 12.52 11.43 12.87 11.42 15.19
E_MV_F / TA median (%) 11.66 11.45 12.49 11.58 12.82 11.63 14.61
% with E_MV_F < 0 0.00 0.00 1.02 0.66 1.00 0.55 0.51
Solvency: post uninsured run
v / TA mean (%) 9.28 8.51 9.43 8.43 10.12 8.35 12.66
v / TA median (%) 8.58 8.42 9.43 8.63 9.59 8.71 11.99
% with v < 0 (post-run) 1.39 1.49 3.81 4.40 2.00 5.49 1.42
Size distribution
Total assets mean (\(B) </td> <td style="text-align:right;"> 4.40 </td> <td style="text-align:right;"> 7.59 </td> <td style="text-align:right;"> 6.51 </td> <td style="text-align:right;"> 6.35 </td> <td style="text-align:right;"> 2.76 </td> <td style="text-align:right;"> 5.12 </td> <td style="text-align:right;"> 1.67 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Total assets median (\)B) 0.97 1.33 0.95 0.67 0.82 0.65 0.26
log(TA) mean 14.03 14.43 14.01 13.73 13.75 13.56 12.60
Concentration of borrowing
Top-10 banks’ share of borrow (%) 83.79 84.22 94.31 64.96 82.26 57.29 NA
HHI of borrow shares 0.10 0.10 0.34 0.12 0.14 0.04 NA
% of borrow by v<0 (post-run) banks 0.05 0.05 0.95 3.70 0.22 5.98 NA
% of borrow by E_MV<0 banks 48.87 38.28 63.94 29.69 46.45 65.16 NA
% of borrow by top-decile TA 78.30 88.98 97.83 87.13 82.03 72.03 NA
save_kbl_latex(table3, "table5_solvency_concentration",
               caption = "Solvency Measures, Size, and Borrowing Concentration")

Table 6

# ══════════════════════════════════════════════════════════════════════
# TABLE 3C: New vs Returning Borrowers — By Period | Facility
# Uses Table 1's definition (matches Table 1 counts exactly):
#   New       = did NOT borrow (DW or BTFP) in any prior period
#   Returning = borrowed (DW or BTFP) in at least one prior period
# Sample: 2022Q4 cross-section (excl. GSIBs and failed banks)
# ══════════════════════════════════════════════════════════════════════

# ── A. Period-assigned loan tables (self-contained) ──────────────────
dw_pf <- dw_clean %>%
  filter(dw_loan_date >= P0_start, dw_loan_date <= P3_end) %>%
  mutate(period  = assign_period(dw_loan_date),
         rssd_id = as.character(rssd_id))

btfp_pf <- btfp_clean %>%
  filter(btfp_loan_date >= P2_start, btfp_loan_date <= P3_end) %>%
  mutate(period  = assign_period(btfp_loan_date),
         rssd_id = as.character(rssd_id))

# ── B. Prior-borrower set per period (same logic as Table 1) ─────────
period_order <- c("P0: Pre-Crisis", "P1: SVB Week",
                  "P2: SVB to FRC", "P3: FRC Week")

all_borrow <- bind_rows(
  dw_pf   %>% select(rssd_id, period),
  btfp_pf %>% select(rssd_id, period)
)

prior_set <- function(p) {
  i <- match(p, period_order)
  if (i == 1) return(character(0))
  unique(all_borrow$rssd_id[all_borrow$period %in% period_order[seq_len(i - 1)]])
}

# ── C. Classify borrowers as New/Returning WITHIN each cell ──────────
# (matches Table 1's new_banks / returning_banks counts exactly)
classify_cell <- function(period_label, facility_label) {
  data <- if (facility_label == "DW") dw_pf else btfp_pf
  banks <- unique(data$rssd_id[data$period == period_label])
  prior <- prior_set(period_label)
  tibble(
    rssd_id  = banks,
    period   = period_label,
    facility = facility_label,
    type     = if_else(banks %in% prior, "Returning", "New")
  )
}

col_specs <- tibble(
  col_id   = c("P0: Pre-Crisis | DW", "P1: SVB Week | DW",
               "P2: SVB to FRC | DW", "P2: SVB to FRC | BTFP",
               "P3: FRC Week | DW",   "P3: FRC Week | BTFP"),
  period   = c("P0: Pre-Crisis", "P1: SVB Week",
               "P2: SVB to FRC", "P2: SVB to FRC",
               "P3: FRC Week",   "P3: FRC Week"),
  facility = c("DW", "DW", "DW", "BTFP", "DW", "BTFP")
)

cell_classification <- bind_rows(
  lapply(seq_len(nrow(col_specs)), function(i)
    classify_cell(col_specs$period[i], col_specs$facility[i]))
)

get_ids_3c <- function(p, f, t) {
  cell_classification %>%
    filter(period == p, facility == f, type == t) %>%
    pull(rssd_id)
}

# ── D. Ensure derived variables exist on df (safe if already present) ─
df <- df %>%
  mutate(
    ta_billion    = total_asset / 1e6,            # thousands → $B
    mu_pct        = 100 * mu,
    fhlb_ta       = fhlb_to_total_asset,
    v_postrun     = E_MV_F_insured,                # correct under Option A
    v_postrun_ta  = 100 * safe_div(v_postrun, total_asset),
    emv_neg       = as.integer(E_MV      < 0),
    v_postrun_neg = as.integer(v_postrun < 0),
    roa = if ("roa" %in% names(.)) roa
          else if ("net_income_to_total_asset" %in% names(.)) net_income_to_total_asset
          else NA_real_
  )

# ── E. Per-cell summariser ───────────────────────────────────────────
summarise_cell_3c <- function(ids, col_id) {
  sub <- df %>% filter(idrssd %in% ids)
  tibble(
    col_id                       = col_id,
    `N (banks)`                  = nrow(sub),
    `Total assets ($B)`          = mean(sub$ta_billion,     na.rm = TRUE),
    `Cash / TA (%)`              = mean(sub$cash_ta,        na.rm = TRUE),
    `Securities / TA (%)`        = mean(sub$sec_ta,         na.rm = TRUE),
    `Loans / TA (%)`             = mean(sub$loan_ta,        na.rm = TRUE),
    `Book equity / TA (%)`       = mean(sub$eq_ta,          na.rm = TRUE),
    `Uninsured / Deposits (%)`   = mean(sub$mu_pct,         na.rm = TRUE),
    `Uninsured leverage (%)`     = mean(sub$du_ta,          na.rm = TRUE),
    `Wholesale funding / TA (%)` = mean(sub$wholesale_ta,   na.rm = TRUE),
    `FHLB / TA (%)`              = mean(sub$fhlb_ta,        na.rm = TRUE),
    `ROA (%)`                    = mean(sub$roa,            na.rm = TRUE),
    `MTM loss / TA (%)`          = mean(sub$ell,            na.rm = TRUE),
    `E_MV / TA (%)`              = mean(sub$emv_ta,         na.rm = TRUE),
    `v / TA (post-run, %)`       = mean(sub$v_postrun_ta,   na.rm = TRUE),
    `% with E_MV < 0`            = 100 * mean(sub$emv_neg,       na.rm = TRUE),
    `% with v < 0 (post-run)`    = 100 * mean(sub$v_postrun_neg, na.rm = TRUE)
  )
}

# ── F. Build both panels ─────────────────────────────────────────────
build_panel_3c <- function(type_label) {
  rows <- lapply(seq_len(nrow(col_specs)), function(i) {
    ids <- get_ids_3c(col_specs$period[i], col_specs$facility[i], type_label)
    summarise_cell_3c(ids, col_specs$col_id[i])
  })
  bind_rows(rows)
}

new_cells      <- build_panel_3c("New")
returning_cells <- build_panel_3c("Returning")

# ── G. Pivot each panel ──────────────────────────────────────────────
stat_order <- setdiff(names(new_cells), "col_id")
col_order  <- col_specs$col_id

pivot_panel_3c <- function(cells) {
  cells %>%
    pivot_longer(-col_id, names_to = "Statistic", values_to = "value") %>%
    mutate(Statistic = factor(Statistic, levels = stat_order),
           col_id    = factor(col_id,    levels = col_order)) %>%
    arrange(Statistic, col_id) %>%
    pivot_wider(names_from = col_id, values_from = value) %>%
    select(Statistic, all_of(col_order))
}

new_panel       <- pivot_panel_3c(new_cells)
returning_panel <- pivot_panel_3c(returning_cells)

# ── H. Stack vertically ──────────────────────────────────────────────
table3c <- bind_rows(new_panel, returning_panel)

# ── I. Diagnostics — verify match with Table 1 ───────────────────────
cat("Table 3C shape:", nrow(table3c), "rows x", ncol(table3c), "cols\n\n")
## Table 3C shape: 32 rows x 7 cols
cat("Bank counts by cell and type (should match Table 1's New/Returning rows):\n")
## Bank counts by cell and type (should match Table 1's New/Returning rows):
cell_counts <- cell_classification %>%
  count(period, facility, type) %>%
  pivot_wider(names_from = type, values_from = n, values_fill = 0L) %>%
  mutate(Total = New + Returning,
         col_id = paste(period, facility, sep = " | ")) %>%
  select(col_id, New, Returning, Total)
print(as.data.frame(cell_counts), row.names = FALSE)
##                 col_id New Returning Total
##    P0: Pre-Crisis | DW  72         0    72
##      P1: SVB Week | DW  33        35    68
##  P2: SVB to FRC | BTFP 442        30   472
##    P2: SVB to FRC | DW 340        66   406
##    P3: FRC Week | BTFP  54       130   184
##      P3: FRC Week | DW  34        67   101
cat("\nRow-by-row comparison with Table 1 (if `table1` object is in memory):\n")
## 
## Row-by-row comparison with Table 1 (if `table1` object is in memory):
if (exists("table1")) {
  t1_counts <- table1 %>%
    filter(Statistic %in% c("N (Banks)", "  New banks", "  Returning banks")) %>%
    as.data.frame()
  print(t1_counts, row.names = FALSE)
  cat("→ Table 3C 'New' column totals above should equal Table 1's",
      "'New banks' row.\n")
  cat("→ Table 3C 'Returning' column totals above should equal Table 1's",
      "'Returning banks' row.\n")
} else {
  cat("(Run Table 1 first to auto-verify.)\n")
}
##          Statistic P0: Pre-Crisis | DW P1: SVB Week | DW P2: SVB to FRC | DW
##          N (Banks)                  72                68                 406
##          New banks                  72                33                 340
##    Returning banks                   0                35                  66
##  P2: SVB to FRC | BTFP P3: FRC Week | DW P3: FRC Week | BTFP
##                    472               101                 184
##                    442                34                  54
##                     30                67                 130
## → Table 3C 'New' column totals above should equal Table 1's 'New banks' row.
## → Table 3C 'Returning' column totals above should equal Table 1's 'Returning banks' row.
cat("\n")
print(as.data.frame(table3c), row.names = FALSE, digits = 3)
##                   Statistic P0: Pre-Crisis | DW P1: SVB Week | DW
##                   N (banks)              72.000            32.000
##           Total assets ($B)               4.403            12.392
##               Cash / TA (%)               4.378             3.307
##         Securities / TA (%)              26.656            21.146
##              Loans / TA (%)              63.642            68.956
##        Book equity / TA (%)               9.053             8.699
##    Uninsured / Deposits (%)              31.992            36.154
##      Uninsured leverage (%)              26.871            29.799
##  Wholesale funding / TA (%)               5.648             6.220
##               FHLB / TA (%)               3.527             4.692
##                     ROA (%)               1.028             1.179
##           MTM loss / TA (%)               9.327             9.072
##               E_MV / TA (%)              -0.274            -0.372
##        v / TA (post-run, %)               9.285             8.629
##             % with E_MV < 0              59.722            53.125
##     % with v < 0 (post-run)               1.389             0.000
##                   N (banks)               0.000            35.000
##           Total assets ($B)                 NaN             3.201
##               Cash / TA (%)                 NaN             4.197
##         Securities / TA (%)                 NaN            28.461
##              Loans / TA (%)                 NaN            62.017
##        Book equity / TA (%)                 NaN             8.137
##    Uninsured / Deposits (%)                 NaN            35.002
##      Uninsured leverage (%)                 NaN            29.274
##  Wholesale funding / TA (%)                 NaN             7.180
##               FHLB / TA (%)                 NaN             4.448
##                     ROA (%)                 NaN             0.915
##           MTM loss / TA (%)                 NaN             8.790
##               E_MV / TA (%)                 NaN            -0.653
##        v / TA (post-run, %)                 NaN             8.400
##             % with E_MV < 0                 NaN            62.857
##     % with v < 0 (post-run)                 NaN             2.857
##  P2: SVB to FRC | DW P2: SVB to FRC | BTFP P3: FRC Week | DW
##              332.000              431.0000            34.000
##                6.773                6.0548             2.975
##                6.459                4.7394             6.805
##               24.092               28.0427            21.447
##               63.433               61.5511            66.155
##                9.007                8.1333             9.680
##               33.158               31.6623            26.580
##               28.232               27.1223            22.859
##                4.727                5.3776             3.277
##                3.447                4.0134             2.252
##                1.182                1.0665             1.120
##                9.066                9.6739             8.061
##               -0.059               -1.5407             1.619
##                9.620                8.4291            12.141
##               50.602               59.1647            35.294
##                4.255                4.4601             0.000
##               65.000               29.0000            67.000
##                5.191               10.7393             2.657
##                4.132                3.8688             4.603
##               26.319               27.7836            26.834
##               64.000               62.5457            63.129
##                8.320                8.5775             8.643
##               35.330               38.5895            32.869
##               29.576               31.8581            27.531
##                6.620                6.9576             6.370
##                4.441                4.4418             4.466
##                1.032                1.0939             1.159
##                8.829                8.6178             9.140
##               -0.509               -0.0403            -0.497
##                8.473                8.3706             9.078
##               58.462               55.1724            55.224
##                1.538                3.4483             3.030
##  P3: FRC Week | BTFP
##                53.00
##                 1.40
##                 4.61
##                30.70
##                58.89
##                 8.31
##                29.21
##                25.54
##                 3.61
##                 2.38
##                 1.05
##                 9.35
##                -1.05
##                 9.62
##                52.83
##                 1.89
##               129.00
##                 6.65
##                 4.02
##                29.44
##                60.96
##                 8.01
##                31.92
##                27.44
##                 5.70
##                 4.08
##                 1.00
##                10.13
##                -2.12
##                 7.83
##                62.79
##                 6.98
# ── J. Render ────────────────────────────────────────────────────────
n_stat <- length(stat_order)

tbl3c_k <- table3c %>%
  kbl(digits = 2, align = c("l", rep("r", 6)),
      caption = paste("Table 3C: New vs Returning Borrowers by Period | Facility",
                      "(2022Q4 baseline)")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE, position = "left", font_size = 11) %>%
  add_header_above(c(" " = 1,
                     "Pre-Crisis" = 1, "SVB Week" = 1,
                     "SVB → FRC" = 2, "FRC Week" = 2)) %>%
  pack_rows("Panel A: New borrowers (no prior borrowing in earlier periods)",
            1, n_stat) %>%
  pack_rows("Panel B: Returning borrowers (borrowed in at least one prior period)",
            n_stat + 1, 2 * n_stat) %>%
  footnote(general = paste(
    "New vs Returning classification matches Table 1. A bank is Returning in",
    "period p if it borrowed from DW or BTFP in any earlier period (P0, ...,",
    "p-1); otherwise New. Cross-facility: prior BTFP borrowing makes a bank",
    "Returning in a later DW cell, and vice versa. P0 has no prior period, so",
    "all P0 borrowers are New by construction. Characteristics are 2022Q4",
    "balance-sheet means (sample excl. GSIBs and failed banks).",
    "v = E_MV + F_insured, the fundamental value after full uninsured-depositor",
    "exit."),
    general_title = "Notes:", footnote_as_chunk = TRUE)

tbl3c_k
Table 3C: New vs Returning Borrowers by Period | Facility (2022Q4 baseline)
Pre-Crisis
SVB Week
SVB → FRC
FRC Week
Statistic P0: Pre-Crisis | DW P1: SVB Week | DW P2: SVB to FRC | DW P2: SVB to FRC | BTFP P3: FRC Week | DW P3: FRC Week | BTFP
Panel A: New borrowers (no prior borrowing in earlier periods)
N (banks) 72.00 32.00 332.00 431.00 34.00 53.00
Total assets (\(B) </td> <td style="text-align:right;"> 4.40 </td> <td style="text-align:right;"> 12.39 </td> <td style="text-align:right;"> 6.77 </td> <td style="text-align:right;"> 6.05 </td> <td style="text-align:right;"> 2.97 </td> <td style="text-align:right;"> 1.40 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Cash / TA (%) </td> <td style="text-align:right;"> 4.38 </td> <td style="text-align:right;"> 3.31 </td> <td style="text-align:right;"> 6.46 </td> <td style="text-align:right;"> 4.74 </td> <td style="text-align:right;"> 6.81 </td> <td style="text-align:right;"> 4.61 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Securities / TA (%) </td> <td style="text-align:right;"> 26.66 </td> <td style="text-align:right;"> 21.15 </td> <td style="text-align:right;"> 24.09 </td> <td style="text-align:right;"> 28.04 </td> <td style="text-align:right;"> 21.45 </td> <td style="text-align:right;"> 30.70 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Loans / TA (%) </td> <td style="text-align:right;"> 63.64 </td> <td style="text-align:right;"> 68.96 </td> <td style="text-align:right;"> 63.43 </td> <td style="text-align:right;"> 61.55 </td> <td style="text-align:right;"> 66.16 </td> <td style="text-align:right;"> 58.89 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Book equity / TA (%) </td> <td style="text-align:right;"> 9.05 </td> <td style="text-align:right;"> 8.70 </td> <td style="text-align:right;"> 9.01 </td> <td style="text-align:right;"> 8.13 </td> <td style="text-align:right;"> 9.68 </td> <td style="text-align:right;"> 8.31 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Uninsured / Deposits (%) </td> <td style="text-align:right;"> 31.99 </td> <td style="text-align:right;"> 36.15 </td> <td style="text-align:right;"> 33.16 </td> <td style="text-align:right;"> 31.66 </td> <td style="text-align:right;"> 26.58 </td> <td style="text-align:right;"> 29.21 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Uninsured leverage (%) </td> <td style="text-align:right;"> 26.87 </td> <td style="text-align:right;"> 29.80 </td> <td style="text-align:right;"> 28.23 </td> <td style="text-align:right;"> 27.12 </td> <td style="text-align:right;"> 22.86 </td> <td style="text-align:right;"> 25.54 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Wholesale funding / TA (%) </td> <td style="text-align:right;"> 5.65 </td> <td style="text-align:right;"> 6.22 </td> <td style="text-align:right;"> 4.73 </td> <td style="text-align:right;"> 5.38 </td> <td style="text-align:right;"> 3.28 </td> <td style="text-align:right;"> 3.61 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> FHLB / TA (%) </td> <td style="text-align:right;"> 3.53 </td> <td style="text-align:right;"> 4.69 </td> <td style="text-align:right;"> 3.45 </td> <td style="text-align:right;"> 4.01 </td> <td style="text-align:right;"> 2.25 </td> <td style="text-align:right;"> 2.38 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> ROA (%) </td> <td style="text-align:right;"> 1.03 </td> <td style="text-align:right;"> 1.18 </td> <td style="text-align:right;"> 1.18 </td> <td style="text-align:right;"> 1.07 </td> <td style="text-align:right;"> 1.12 </td> <td style="text-align:right;"> 1.05 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> MTM loss / TA (%) </td> <td style="text-align:right;"> 9.33 </td> <td style="text-align:right;"> 9.07 </td> <td style="text-align:right;"> 9.07 </td> <td style="text-align:right;"> 9.67 </td> <td style="text-align:right;"> 8.06 </td> <td style="text-align:right;"> 9.35 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> E_MV / TA (%) </td> <td style="text-align:right;"> -0.27 </td> <td style="text-align:right;"> -0.37 </td> <td style="text-align:right;"> -0.06 </td> <td style="text-align:right;"> -1.54 </td> <td style="text-align:right;"> 1.62 </td> <td style="text-align:right;"> -1.05 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> v / TA (post-run, %) </td> <td style="text-align:right;"> 9.28 </td> <td style="text-align:right;"> 8.63 </td> <td style="text-align:right;"> 9.62 </td> <td style="text-align:right;"> 8.43 </td> <td style="text-align:right;"> 12.14 </td> <td style="text-align:right;"> 9.62 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> % with E_MV &lt; 0 </td> <td style="text-align:right;"> 59.72 </td> <td style="text-align:right;"> 53.12 </td> <td style="text-align:right;"> 50.60 </td> <td style="text-align:right;"> 59.16 </td> <td style="text-align:right;"> 35.29 </td> <td style="text-align:right;"> 52.83 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> % with v &lt; 0 (post-run) </td> <td style="text-align:right;"> 1.39 </td> <td style="text-align:right;"> 0.00 </td> <td style="text-align:right;"> 4.26 </td> <td style="text-align:right;"> 4.46 </td> <td style="text-align:right;"> 0.00 </td> <td style="text-align:right;"> 1.89 </td> </tr> <tr grouplength="16"><td colspan="7" style="border-bottom: 1px solid;"><strong>Panel B: Returning borrowers (borrowed in at least one prior period)</strong></td></tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> N (banks) </td> <td style="text-align:right;"> 0.00 </td> <td style="text-align:right;"> 35.00 </td> <td style="text-align:right;"> 65.00 </td> <td style="text-align:right;"> 29.00 </td> <td style="text-align:right;"> 67.00 </td> <td style="text-align:right;"> 129.00 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Total assets (\)B) NaN 3.20 5.19 10.74 2.66 6.65
Cash / TA (%) NaN 4.20 4.13 3.87 4.60 4.02
Securities / TA (%) NaN 28.46 26.32 27.78 26.83 29.44
Loans / TA (%) NaN 62.02 64.00 62.55 63.13 60.96
Book equity / TA (%) NaN 8.14 8.32 8.58 8.64 8.01
Uninsured / Deposits (%) NaN 35.00 35.33 38.59 32.87 31.92
Uninsured leverage (%) NaN 29.27 29.58 31.86 27.53 27.44
Wholesale funding / TA (%) NaN 7.18 6.62 6.96 6.37 5.70
FHLB / TA (%) NaN 4.45 4.44 4.44 4.47 4.08
ROA (%) NaN 0.92 1.03 1.09 1.16 1.00
MTM loss / TA (%) NaN 8.79 8.83 8.62 9.14 10.13
E_MV / TA (%) NaN -0.65 -0.51 -0.04 -0.50 -2.12
v / TA (post-run, %) NaN 8.40 8.47 8.37 9.08 7.83
% with E_MV < 0 NaN 62.86 58.46 55.17 55.22 62.79
% with v < 0 (post-run) NaN 2.86 1.54 3.45 3.03 6.98
Notes: New vs Returning classification matches Table 1. A bank is Returning in period p if it borrowed from DW or BTFP in any earlier period (P0, …, p-1); otherwise New. Cross-facility: prior BTFP borrowing makes a bank Returning in a later DW cell, and vice versa. P0 has no prior period, so all P0 borrowers are New by construction. Characteristics are 2022Q4 balance-sheet means (sample excl. GSIBs and failed banks). v = E_MV + F_insured, the fundamental value after full uninsured-depositor exit.
save_kbl_latex(table3c, "table6_new_vs_returning",
               caption = "New vs Returning Borrowers by Period | Facility")

Table 6 (another angle)

# ══════════════════════════════════════════════════════════════════════
# TABLE 3B: One-time vs Repeat Borrowers — By Period | Facility
# Columns mirror Table 1: 6 period|facility cells
# Within each cell: One-time = 1 distinct loan date in that period
#                   Repeat   = ≥2 distinct loan dates in that period
# Sample: 2022Q4 cross-section (excl. GSIBs and failed banks)
# ══════════════════════════════════════════════════════════════════════

# ── A. Period-assigned loan tables (self-contained) ──────────────────
dw_pf <- dw_clean %>%
  filter(dw_loan_date >= P0_start, dw_loan_date <= P3_end) %>%
  mutate(period  = assign_period(dw_loan_date),
         rssd_id = as.character(rssd_id))

btfp_pf <- btfp_clean %>%
  filter(btfp_loan_date >= P2_start, btfp_loan_date <= P3_end) %>%
  mutate(period  = assign_period(btfp_loan_date),
         rssd_id = as.character(rssd_id))

# ── B. Classify borrowers as One-time/Repeat WITHIN each cell ────────
dw_cell_types <- dw_pf %>%
  distinct(period, rssd_id, dw_loan_date) %>%
  count(period, rssd_id, name = "n_dates") %>%
  mutate(type     = if_else(n_dates > 1, "Repeat", "One-time"),
         facility = "DW")

btfp_cell_types <- btfp_pf %>%
  distinct(period, rssd_id, btfp_loan_date) %>%
  count(period, rssd_id, name = "n_dates") %>%
  mutate(type     = if_else(n_dates > 1, "Repeat", "One-time"),
         facility = "BTFP")

cell_types <- bind_rows(dw_cell_types, btfp_cell_types)

get_ids <- function(p, f, t) {
  cell_types %>%
    filter(period == p, facility == f, type == t) %>%
    pull(rssd_id)
}

# ── C. Ensure derived variables exist on df (safe if already present) ─
df <- df %>%
  mutate(
    ta_billion    = total_asset / 1e6,             # thousands → $B
    mu_pct        = 100 * mu,
    fhlb_ta       = fhlb_to_total_asset,
    v_postrun     = E_MV_F_insured,                 # correct under Option A
    v_postrun_ta  = 100 * safe_div(v_postrun, total_asset),
    emv_neg       = as.integer(E_MV       < 0),
    v_postrun_neg = as.integer(v_postrun  < 0),
    roa = if ("roa" %in% names(.)) roa
          else if ("net_income_to_total_asset" %in% names(.)) net_income_to_total_asset
          else NA_real_
  )

# ── D. Per-cell summariser ───────────────────────────────────────────
summarise_cell_3b <- function(ids, col_id) {
  sub <- df %>% filter(idrssd %in% ids)
  tibble(
    col_id                     = col_id,
    `N (banks)`                = nrow(sub),
    `Total assets ($B)`        = mean(sub$ta_billion,     na.rm = TRUE),
    `Cash / TA (%)`            = mean(sub$cash_ta,        na.rm = TRUE),
    `Securities / TA (%)`      = mean(sub$sec_ta,         na.rm = TRUE),
    `Loans / TA (%)`           = mean(sub$loan_ta,        na.rm = TRUE),
    `Book equity / TA (%)`     = mean(sub$eq_ta,          na.rm = TRUE),
    `Uninsured / Deposits (%)` = mean(sub$mu_pct,         na.rm = TRUE),
    `Uninsured leverage (%)`   = mean(sub$du_ta,          na.rm = TRUE),
    `Wholesale funding / TA (%)` = mean(sub$wholesale_ta, na.rm = TRUE),
    `FHLB / TA (%)`            = mean(sub$fhlb_ta,        na.rm = TRUE),
    `ROA (%)`                  = mean(sub$roa,            na.rm = TRUE),
    `MTM loss / TA (%)`        = mean(sub$ell,            na.rm = TRUE),
    `E_MV / TA (%)`            = mean(sub$emv_ta,         na.rm = TRUE),
    `v / TA (post-run, %)`     = mean(sub$v_postrun_ta,   na.rm = TRUE),
    `% with E_MV < 0`          = 100 * mean(sub$emv_neg,       na.rm = TRUE),
    `% with v < 0 (post-run)`  = 100 * mean(sub$v_postrun_neg, na.rm = TRUE)
  )
}

# ── E. Build both panels ─────────────────────────────────────────────
col_specs <- tibble(
  col_id   = c("P0: Pre-Crisis | DW", "P1: SVB Week | DW",
               "P2: SVB to FRC | DW", "P2: SVB to FRC | BTFP",
               "P3: FRC Week | DW",   "P3: FRC Week | BTFP"),
  period   = c("P0: Pre-Crisis", "P1: SVB Week",
               "P2: SVB to FRC", "P2: SVB to FRC",
               "P3: FRC Week",   "P3: FRC Week"),
  facility = c("DW", "DW", "DW", "BTFP", "DW", "BTFP")
)

build_panel <- function(type_label) {
  rows <- lapply(seq_len(nrow(col_specs)), function(i) {
    ids <- get_ids(col_specs$period[i], col_specs$facility[i], type_label)
    summarise_cell_3b(ids, col_specs$col_id[i])
  })
  bind_rows(rows)
}

onetime_cells <- build_panel("One-time")
repeat_cells  <- build_panel("Repeat")

# ── F. Pivot each panel to rows = stat, cols = period|facility ───────
stat_order <- setdiff(names(onetime_cells), "col_id")
col_order  <- col_specs$col_id

pivot_panel <- function(cells) {
  cells %>%
    pivot_longer(-col_id, names_to = "Statistic", values_to = "value") %>%
    mutate(Statistic = factor(Statistic, levels = stat_order),
           col_id    = factor(col_id,    levels = col_order)) %>%
    arrange(Statistic, col_id) %>%
    pivot_wider(names_from = col_id, values_from = value) %>%
    select(Statistic, all_of(col_order))
}

onetime_panel <- pivot_panel(onetime_cells)
repeat_panel  <- pivot_panel(repeat_cells)

# ── G. Stack vertically ──────────────────────────────────────────────
table3b <- bind_rows(onetime_panel, repeat_panel)

# Diagnostics
cat("Table 3B shape:", nrow(table3b), "rows x", ncol(table3b), "cols\n")
## Table 3B shape: 32 rows x 7 cols
cat("Panel A (One-time): rows 1 to ",  nrow(onetime_panel), "\n", sep = "")
## Panel A (One-time): rows 1 to 16
cat("Panel B (Repeat)  : rows ", nrow(onetime_panel) + 1,
    " to ", nrow(table3b), "\n", sep = "")
## Panel B (Repeat)  : rows 17 to 32
cat("\nBank counts by cell:\n")
## 
## Bank counts by cell:
cell_types %>%
  count(period, facility, type) %>%
  pivot_wider(names_from = type, values_from = n, values_fill = 0L) %>%
  print(n = Inf)
## # A tibble: 6 × 4
##   period         facility `One-time` Repeat
##   <chr>          <chr>         <int>  <int>
## 1 P0: Pre-Crisis DW               40     32
## 2 P1: SVB Week   DW               37     31
## 3 P2: SVB to FRC BTFP            235    237
## 4 P2: SVB to FRC DW              292    114
## 5 P3: FRC Week   BTFP            126     58
## 6 P3: FRC Week   DW               56     45
print(as.data.frame(table3b), row.names = FALSE, digits = 3)
##                   Statistic P0: Pre-Crisis | DW P1: SVB Week | DW
##                   N (banks)              40.000           37.0000
##           Total assets ($B)               5.274           10.9963
##               Cash / TA (%)               4.745            4.5227
##         Securities / TA (%)              23.529           22.0662
##              Loans / TA (%)              66.217           67.2013
##        Book equity / TA (%)               9.815            8.7492
##    Uninsured / Deposits (%)              28.494           36.6735
##      Uninsured leverage (%)              23.788           30.5039
##  Wholesale funding / TA (%)               5.222            5.7371
##               FHLB / TA (%)               3.495            4.0074
##                     ROA (%)               1.059            1.1041
##           MTM loss / TA (%)               9.158            8.6597
##               E_MV / TA (%)               0.657            0.0895
##        v / TA (post-run, %)              10.529            8.9813
##             % with E_MV < 0              50.000           48.6486
##     % with v < 0 (post-run)               0.000            0.0000
##                   N (banks)              32.000           30.0000
##           Total assets ($B)               3.314            3.3905
##               Cash / TA (%)               3.919            2.8465
##         Securities / TA (%)              30.564           28.5457
##              Loans / TA (%)              60.423           63.0247
##        Book equity / TA (%)               8.101            7.9811
##    Uninsured / Deposits (%)              36.365           34.1691
##      Uninsured leverage (%)              30.724           28.3172
##  Wholesale funding / TA (%)               6.181            7.9354
##               FHLB / TA (%)               3.568            5.2518
##                     ROA (%)               0.989            0.9643
##           MTM loss / TA (%)               9.538            9.2509
##               E_MV / TA (%)              -1.438           -1.2698
##        v / TA (post-run, %)               7.729            7.9273
##             % with E_MV < 0              71.875           70.0000
##     % with v < 0 (post-run)               3.125            3.3333
##  P2: SVB to FRC | DW P2: SVB to FRC | BTFP P3: FRC Week | DW
##              284.000                229.00            56.000
##                7.567                  9.25             2.560
##                6.841                  5.08             6.620
##               23.171                 26.45            21.930
##               64.077                 62.93            65.799
##                9.199                  8.11             9.411
##               32.449                 32.87            28.231
##               27.698                 28.24            23.921
##                4.412                  4.89             4.508
##                3.317                  3.71             3.253
##                1.189                  1.08             1.251
##                8.871                  9.28             8.540
##                0.329                 -1.16             0.871
##                9.988                  8.78            11.042
##               47.887                 57.64            44.643
##                3.180                  1.77             0.000
##              113.000                231.00            45.000
##                3.867                  3.48             3.017
##                4.161                  4.29             3.758
##               27.687                 29.59            28.868
##               62.141                 60.31            62.093
##                8.127                  8.21             8.471
##               36.189                 31.33            33.889
##               30.347                 26.61            28.494
##                6.606                  6.05             6.351
##                4.345                  4.36             4.302
##                1.078                  1.06             1.016
##                9.419                  9.94             9.072
##               -1.292                 -1.73            -0.601
##                8.008                  8.08             8.946
##               61.947                 60.17            53.333
##                5.405                  6.99             4.545
##  P3: FRC Week | BTFP
##               124.00
##                 6.13
##                 4.09
##                29.91
##                60.23
##                 7.97
##                30.02
##                26.00
##                 5.06
##                 3.58
##                 1.00
##                 9.94
##                -1.97
##                 8.36
##                62.10
##                 6.45
##                58.00
##                 2.95
##                 4.41
##                29.57
##                60.63
##                 8.37
##                33.50
##                28.78
##                 5.16
##                 3.58
##                 1.04
##                 9.83
##                -1.46
##                 8.33
##                55.17
##                 3.45
# ── H. Render ────────────────────────────────────────────────────────
n_stat <- length(stat_order)

tbl3b_k <- table3b %>%
  kbl(digits = 2, align = c("l", rep("r", 6)),
      caption = "Table 3B: One-time vs Repeat Borrowers by Period | Facility (2022Q4 baseline)") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE, position = "left", font_size = 11) %>%
  add_header_above(c(" " = 1,
                     "Pre-Crisis" = 1, "SVB Week" = 1,
                     "SVB → FRC" = 2, "FRC Week" = 2)) %>%
  pack_rows("Panel A: One-time borrowers (1 distinct loan date in the period)",
            1, n_stat) %>%
  pack_rows("Panel B: Repeat borrowers (≥2 distinct loan dates in the period)",
            n_stat + 1, 2 * n_stat) %>%
  footnote(general = paste(
    "One-time vs Repeat is defined within each period | facility cell by the",
    "number of distinct loan dates. Characteristics are 2022Q4 balance-sheet",
    "means (sample excl. GSIBs and failed banks). v = E_MV + F_insured,",
    "the fundamental value after full uninsured-depositor exit."),
    general_title = "Notes:", footnote_as_chunk = TRUE)

tbl3b_k
Table 3B: One-time vs Repeat Borrowers by Period | Facility (2022Q4 baseline)
Pre-Crisis
SVB Week
SVB → FRC
FRC Week
Statistic P0: Pre-Crisis | DW P1: SVB Week | DW P2: SVB to FRC | DW P2: SVB to FRC | BTFP P3: FRC Week | DW P3: FRC Week | BTFP
Panel A: One-time borrowers (1 distinct loan date in the period)
N (banks) 40.00 37.00 284.00 229.00 56.00 124.00
Total assets (\(B) </td> <td style="text-align:right;"> 5.27 </td> <td style="text-align:right;"> 11.00 </td> <td style="text-align:right;"> 7.57 </td> <td style="text-align:right;"> 9.25 </td> <td style="text-align:right;"> 2.56 </td> <td style="text-align:right;"> 6.13 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Cash / TA (%) </td> <td style="text-align:right;"> 4.74 </td> <td style="text-align:right;"> 4.52 </td> <td style="text-align:right;"> 6.84 </td> <td style="text-align:right;"> 5.08 </td> <td style="text-align:right;"> 6.62 </td> <td style="text-align:right;"> 4.09 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Securities / TA (%) </td> <td style="text-align:right;"> 23.53 </td> <td style="text-align:right;"> 22.07 </td> <td style="text-align:right;"> 23.17 </td> <td style="text-align:right;"> 26.45 </td> <td style="text-align:right;"> 21.93 </td> <td style="text-align:right;"> 29.91 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Loans / TA (%) </td> <td style="text-align:right;"> 66.22 </td> <td style="text-align:right;"> 67.20 </td> <td style="text-align:right;"> 64.08 </td> <td style="text-align:right;"> 62.93 </td> <td style="text-align:right;"> 65.80 </td> <td style="text-align:right;"> 60.23 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Book equity / TA (%) </td> <td style="text-align:right;"> 9.81 </td> <td style="text-align:right;"> 8.75 </td> <td style="text-align:right;"> 9.20 </td> <td style="text-align:right;"> 8.11 </td> <td style="text-align:right;"> 9.41 </td> <td style="text-align:right;"> 7.97 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Uninsured / Deposits (%) </td> <td style="text-align:right;"> 28.49 </td> <td style="text-align:right;"> 36.67 </td> <td style="text-align:right;"> 32.45 </td> <td style="text-align:right;"> 32.87 </td> <td style="text-align:right;"> 28.23 </td> <td style="text-align:right;"> 30.02 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Uninsured leverage (%) </td> <td style="text-align:right;"> 23.79 </td> <td style="text-align:right;"> 30.50 </td> <td style="text-align:right;"> 27.70 </td> <td style="text-align:right;"> 28.24 </td> <td style="text-align:right;"> 23.92 </td> <td style="text-align:right;"> 26.00 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Wholesale funding / TA (%) </td> <td style="text-align:right;"> 5.22 </td> <td style="text-align:right;"> 5.74 </td> <td style="text-align:right;"> 4.41 </td> <td style="text-align:right;"> 4.89 </td> <td style="text-align:right;"> 4.51 </td> <td style="text-align:right;"> 5.06 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> FHLB / TA (%) </td> <td style="text-align:right;"> 3.49 </td> <td style="text-align:right;"> 4.01 </td> <td style="text-align:right;"> 3.32 </td> <td style="text-align:right;"> 3.71 </td> <td style="text-align:right;"> 3.25 </td> <td style="text-align:right;"> 3.58 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> ROA (%) </td> <td style="text-align:right;"> 1.06 </td> <td style="text-align:right;"> 1.10 </td> <td style="text-align:right;"> 1.19 </td> <td style="text-align:right;"> 1.08 </td> <td style="text-align:right;"> 1.25 </td> <td style="text-align:right;"> 1.00 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> MTM loss / TA (%) </td> <td style="text-align:right;"> 9.16 </td> <td style="text-align:right;"> 8.66 </td> <td style="text-align:right;"> 8.87 </td> <td style="text-align:right;"> 9.28 </td> <td style="text-align:right;"> 8.54 </td> <td style="text-align:right;"> 9.94 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> E_MV / TA (%) </td> <td style="text-align:right;"> 0.66 </td> <td style="text-align:right;"> 0.09 </td> <td style="text-align:right;"> 0.33 </td> <td style="text-align:right;"> -1.16 </td> <td style="text-align:right;"> 0.87 </td> <td style="text-align:right;"> -1.97 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> v / TA (post-run, %) </td> <td style="text-align:right;"> 10.53 </td> <td style="text-align:right;"> 8.98 </td> <td style="text-align:right;"> 9.99 </td> <td style="text-align:right;"> 8.78 </td> <td style="text-align:right;"> 11.04 </td> <td style="text-align:right;"> 8.36 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> % with E_MV &lt; 0 </td> <td style="text-align:right;"> 50.00 </td> <td style="text-align:right;"> 48.65 </td> <td style="text-align:right;"> 47.89 </td> <td style="text-align:right;"> 57.64 </td> <td style="text-align:right;"> 44.64 </td> <td style="text-align:right;"> 62.10 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> % with v &lt; 0 (post-run) </td> <td style="text-align:right;"> 0.00 </td> <td style="text-align:right;"> 0.00 </td> <td style="text-align:right;"> 3.18 </td> <td style="text-align:right;"> 1.77 </td> <td style="text-align:right;"> 0.00 </td> <td style="text-align:right;"> 6.45 </td> </tr> <tr grouplength="16"><td colspan="7" style="border-bottom: 1px solid;"><strong>Panel B: Repeat borrowers (≥2 distinct loan dates in the period)</strong></td></tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> N (banks) </td> <td style="text-align:right;"> 32.00 </td> <td style="text-align:right;"> 30.00 </td> <td style="text-align:right;"> 113.00 </td> <td style="text-align:right;"> 231.00 </td> <td style="text-align:right;"> 45.00 </td> <td style="text-align:right;"> 58.00 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Total assets (\)B) 3.31 3.39 3.87 3.48 3.02 2.95
Cash / TA (%) 3.92 2.85 4.16 4.29 3.76 4.41
Securities / TA (%) 30.56 28.55 27.69 29.59 28.87 29.57
Loans / TA (%) 60.42 63.02 62.14 60.31 62.09 60.63
Book equity / TA (%) 8.10 7.98 8.13 8.21 8.47 8.37
Uninsured / Deposits (%) 36.36 34.17 36.19 31.33 33.89 33.50
Uninsured leverage (%) 30.72 28.32 30.35 26.61 28.49 28.78
Wholesale funding / TA (%) 6.18 7.94 6.61 6.05 6.35 5.16
FHLB / TA (%) 3.57 5.25 4.35 4.36 4.30 3.58
ROA (%) 0.99 0.96 1.08 1.06 1.02 1.04
MTM loss / TA (%) 9.54 9.25 9.42 9.94 9.07 9.83
E_MV / TA (%) -1.44 -1.27 -1.29 -1.73 -0.60 -1.46
v / TA (post-run, %) 7.73 7.93 8.01 8.08 8.95 8.33
% with E_MV < 0 71.88 70.00 61.95 60.17 53.33 55.17
% with v < 0 (post-run) 3.12 3.33 5.41 6.99 4.55 3.45
Notes: One-time vs Repeat is defined within each period | facility cell by the number of distinct loan dates. Characteristics are 2022Q4 balance-sheet means (sample excl. GSIBs and failed banks). v = E_MV + F_insured, the fundamental value after full uninsured-depositor exit.
save_kbl_latex(table3b, "table6b_onetime_vs_repeat",
               caption = "One-time vs Repeat Borrowers by Period | Facility")

Regression

Base Model Crisis Period:

# ══════════════════════════════════════════════════════════════════════
# TABLE 4: LPM — Determinants of Borrower Status by Period|Facility
# Six LPMs; sample = 2022Q4 cross-section (df); HC1 robust SE
# All RHS variables winsorized (2.5/97.5) and z-standardized
# ══════════════════════════════════════════════════════════════════════

# ── A. Make sure all RHS variables exist and are z-standardized ──────
df <- df %>%
  mutate(
    # Variables not already in reg_vars
    loan_to_dep = 100 * safe_div(total_loan, total_deposit),
    roa = if ("roa" %in% names(.)) roa
          else if ("net_income_to_total_asset" %in% names(.)) net_income_to_total_asset
          else NA_real_
  )

# Everything we need as `_w_z`
reg4_set <- c("ell", "du_ta",                              # main effects
              "log_ta", "cash_ta", "loan_to_dep",          # controls
              "eq_ta", "wholesale_ta", "roa")              # controls

# Only process those that don't already have a _w_z version
need_proc <- reg4_set[!paste0(reg4_set, "_w_z") %in% names(df)]
if (length(need_proc) > 0) {
  cat("Winsorizing + z-standardizing:",
      paste(need_proc, collapse = ", "), "\n")
  df <- df %>%
    mutate(across(all_of(need_proc), ~ winsorize(.x), .names = "{.col}_w")) %>%
    mutate(across(all_of(paste0(need_proc, "_w")), ~ z_std(.x),
                  .names = "{.col}_z"))
}
## Winsorizing + z-standardizing: log_ta, loan_to_dep, roa
# ── B. Build binary dependent variables per period|facility ──────────
dw_cells <- dw_clean %>%
  mutate(rssd_id = as.character(rssd_id),
         period  = assign_period(dw_loan_date))

btfp_cells <- btfp_clean %>%
  mutate(rssd_id = as.character(rssd_id),
         period  = assign_period(btfp_loan_date))

ids_in <- function(data, p) unique(data$rssd_id[data$period == p])

df <- df %>%
  mutate(
    idrssd      = as.character(idrssd),
    dep_p0_dw   = as.integer(idrssd %in% ids_in(dw_cells,   "P0: Pre-Crisis")),
    dep_p1_dw   = as.integer(idrssd %in% ids_in(dw_cells,   "P1: SVB Week")),
    dep_p2_dw   = as.integer(idrssd %in% ids_in(dw_cells,   "P2: SVB to FRC")),
    dep_p2_btfp = as.integer(idrssd %in% ids_in(btfp_cells, "P2: SVB to FRC")),
    dep_p3_dw   = as.integer(idrssd %in% ids_in(dw_cells,   "P3: FRC Week")),
    dep_p3_btfp = as.integer(idrssd %in% ids_in(btfp_cells, "P3: FRC Week"))
  )

# Sanity check: base rates
cat("\nBorrower base rates (1 = borrowed in that period|facility):\n")
## 
## Borrower base rates (1 = borrowed in that period|facility):
df %>%
  summarise(across(starts_with("dep_"),
                   list(n_borrowers = ~sum(., na.rm = TRUE),
                        mean        = ~mean(., na.rm = TRUE)))) %>%
  pivot_longer(everything(), names_to = "var", values_to = "val") %>%
  print(n = Inf)
## # A tibble: 18 × 2
##    var                           val
##    <chr>                       <dbl>
##  1 dep_ta_n_borrowers       3.97e+ 5
##  2 dep_ta_mean              8.44e+ 1
##  3 dep_ta_w_n_borrowers     4.01e+ 5
##  4 dep_ta_w_mean            8.54e+ 1
##  5 dep_ta_w_z_n_borrowers  -2.02e-12
##  6 dep_ta_w_z_mean         -4.27e-16
##  7 dep_p0_dw_n_borrowers    7.2 e+ 1
##  8 dep_p0_dw_mean           1.53e- 2
##  9 dep_p1_dw_n_borrowers    6.7 e+ 1
## 10 dep_p1_dw_mean           1.43e- 2
## 11 dep_p2_dw_n_borrowers    3.97e+ 2
## 12 dep_p2_dw_mean           8.45e- 2
## 13 dep_p2_btfp_n_borrowers  4.6 e+ 2
## 14 dep_p2_btfp_mean         9.80e- 2
## 15 dep_p3_dw_n_borrowers    1.01e+ 2
## 16 dep_p3_dw_mean           2.15e- 2
## 17 dep_p3_btfp_n_borrowers  1.82e+ 2
## 18 dep_p3_btfp_mean         3.88e- 2
# ── C. Estimate six LPMs with HC1 robust SE ──────────────────────────
controls <- "log_ta_w_z + cash_ta_w_z + loan_to_dep_w_z + eq_ta_w_z + wholesale_ta_w_z + roa_w_z"
rhs      <- paste("ell_w_z * du_ta_w_z +", controls)

models <- list(
  `P0: Pre-Crisis | DW`   = lm(as.formula(paste("dep_p0_dw ~",   rhs)), data = df),
  `P1: SVB Week | DW`     = lm(as.formula(paste("dep_p1_dw ~",   rhs)), data = df),
  `P2: SVB to FRC | DW`   = lm(as.formula(paste("dep_p2_dw ~",   rhs)), data = df),
  `P2: SVB to FRC | BTFP` = lm(as.formula(paste("dep_p2_btfp ~", rhs)), data = df),
  `P3: FRC Week | DW`     = lm(as.formula(paste("dep_p3_dw ~",   rhs)), data = df),
  `P3: FRC Week | BTFP`   = lm(as.formula(paste("dep_p3_btfp ~", rhs)), data = df)
)

# ── D. Extract coefficients with robust SE ───────────────────────────
get_robust_tidy <- function(model) {
  ct <- coeftest(model, vcov. = vcovHC(model, type = "HC1"))
  tibble(term = rownames(ct),
         estimate  = ct[, 1],
         std.error = ct[, 2],
         p.value   = ct[, 4])
}

tidy_models <- lapply(models, get_robust_tidy)

# ── E. Build the regression table (2 rows per coef: estimate, SE) ────
var_order <- c("ell_w_z",
               "du_ta_w_z",
               "ell_w_z:du_ta_w_z",
               "log_ta_w_z",
               "cash_ta_w_z",
               "loan_to_dep_w_z",
               "eq_ta_w_z",
               "wholesale_ta_w_z",
               "roa_w_z",
               "(Intercept)")

var_labels <- c(
  "ell_w_z"              = "MTM loss (total)",
  "du_ta_w_z"            = "Uninsured leverage",
  "ell_w_z:du_ta_w_z"    = "MTM × Uninsured Lev",
  "log_ta_w_z"           = "Ln(assets)",
  "cash_ta_w_z"          = "Cash / TA",
  "loan_to_dep_w_z"      = "Loan / Deposit",
  "eq_ta_w_z"            = "Book equity / TA",
  "wholesale_ta_w_z"     = "Wholesale funding",
  "roa_w_z"              = "ROA",
  "(Intercept)"          = "Constant"
)

fmt_est <- function(est, p) {
  if (is.na(est)) return("")
  sprintf("%.3f%s", est, stars_pval(p))
}
fmt_se <- function(se) if (is.na(se)) "" else sprintf("(%.3f)", se)

body <- list()
for (v in var_order) {
  est_row <- c(); se_row <- c()
  for (m_name in names(tidy_models)) {
    tm  <- tidy_models[[m_name]]
    idx <- which(tm$term == v)
    if (length(idx) == 0) {
      est_row <- c(est_row, ""); se_row <- c(se_row, "")
    } else {
      est_row <- c(est_row, fmt_est(tm$estimate[idx], tm$p.value[idx]))
      se_row  <- c(se_row,  fmt_se(tm$std.error[idx]))
    }
  }
  body[[paste0(v, "_est")]] <- c(var_labels[[v]], est_row)
  body[[paste0(v, "_se")]]  <- c("",              se_row)
}

# Footer: N, R², mean DV
n_row   <- c("N")
r2_row  <- c("R²")
mdv_row <- c("Mean of DV")
for (m_name in names(models)) {
  m <- models[[m_name]]
  n_row   <- c(n_row,   fmt(nobs(m)))
  r2_row  <- c(r2_row,  sprintf("%.3f", summary(m)$r.squared))
  mdv_row <- c(mdv_row, sprintf("%.4f", mean(m$model[[1]], na.rm = TRUE)))
}
body[["_blank"]] <- rep("", 7)
body[["_N"]]     <- n_row
body[["_R2"]]    <- r2_row
body[["_MDV"]]   <- mdv_row

reg_table <- do.call(rbind, body) %>% as.data.frame(stringsAsFactors = FALSE)
rownames(reg_table) <- NULL
colnames(reg_table) <- c("Variable", names(models))

# ── F. Render ────────────────────────────────────────────────────────
cat("\nTable 7: LPM coefficients (robust SE in parentheses)\n")
## 
## Table 7: LPM coefficients (robust SE in parentheses)
print(reg_table, right = FALSE)
##    Variable            P0: Pre-Crisis | DW P1: SVB Week | DW
## 1  MTM loss (total)    0.001               -0.002           
## 2                      (0.002)             (0.002)          
## 3  Uninsured leverage  0.000               0.002            
## 4                      (0.003)             (0.002)          
## 5  MTM × Uninsured Lev 0.002               0.001            
## 6                      (0.002)             (0.002)          
## 7  Ln(assets)          0.013***            0.014***         
## 8                      (0.003)             (0.003)          
## 9  Cash / TA           -0.004*             -0.003**         
## 10                     (0.002)             (0.001)          
## 11 Loan / Deposit      -0.005*             -0.005**         
## 12                     (0.003)             (0.002)          
## 13 Book equity / TA    0.002               -0.000           
## 14                     (0.002)             (0.001)          
## 15 Wholesale funding   0.006**             0.008***         
## 16                     (0.003)             (0.003)          
## 17 ROA                 -0.002              -0.002           
## 18                     (0.002)             (0.002)          
## 19 Constant            0.016***            0.014***         
## 20                     (0.002)             (0.002)          
## 21                                                          
## 22 N                   4,632               4,632            
## 23 R²                  0.015               0.023            
## 24 Mean of DV          0.0155              0.0145           
##    P2: SVB to FRC | DW P2: SVB to FRC | BTFP P3: FRC Week | DW
## 1  0.012**             0.015***              -0.000           
## 2  (0.005)             (0.005)               (0.003)          
## 3  0.012**             0.017***              -0.000           
## 4  (0.005)             (0.006)               (0.003)          
## 5  0.013***            0.012***              0.005**          
## 6  (0.004)             (0.004)               (0.002)          
## 7  0.066***            0.047***              0.014***         
## 8  (0.006)             (0.006)               (0.003)          
## 9  0.003               -0.021***             -0.003           
## 10 (0.004)             (0.004)               (0.002)          
## 11 -0.007              -0.024***             -0.004           
## 12 (0.005)             (0.005)               (0.003)          
## 13 0.001               -0.010**              0.001            
## 14 (0.004)             (0.004)               (0.002)          
## 15 0.015***            0.033***              0.007**          
## 16 (0.005)             (0.006)               (0.003)          
## 17 0.001               -0.002                0.000            
## 18 (0.004)             (0.004)               (0.002)          
## 19 0.086***            0.099***              0.022***         
## 20 (0.004)             (0.004)               (0.002)          
## 21                                                            
## 22 4,632               4,632                 4,632            
## 23 0.069               0.069                 0.013            
## 24 0.0857              0.0993                0.0218           
##    P3: FRC Week | BTFP
## 1  0.007**            
## 2  (0.003)            
## 3  0.007**            
## 4  (0.004)            
## 5  0.005*             
## 6  (0.003)            
## 7  0.015***           
## 8  (0.004)            
## 9  -0.013***          
## 10 (0.002)            
## 11 -0.013***          
## 12 (0.003)            
## 13 -0.002             
## 14 (0.003)            
## 15 0.012***           
## 16 (0.003)            
## 17 -0.003             
## 18 (0.002)            
## 19 0.039***           
## 20 (0.003)            
## 21                    
## 22 4,632              
## 23 0.026              
## 24 0.0393
n_coef_rows <- 2 * length(var_order)   # 20 rows of coefficients
footer_start <- n_coef_rows + 2         # blank + N rows start here

tbl4_k <- reg_table %>%
  kbl(align = c("l", rep("r", 6)),
      caption = "Table 4: LPM — Determinants of Borrower Status by Period | Facility",
      booktabs = TRUE) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE, position = "left", font_size = 11) %>%
  add_header_above(c(" " = 1, "Pre-Crisis" = 1, "SVB Week" = 1,
                     "SVB → FRC" = 2, "FRC Week" = 2)) %>%
  pack_rows("Key solvency-liquidity variables", 1, 6) %>%
  pack_rows("Controls",                        7, n_coef_rows - 2) %>%
  pack_rows("Intercept",                       n_coef_rows - 1, n_coef_rows) %>%
  pack_rows("Fit statistics", footer_start, footer_start + 2) %>%
  footnote(general = paste("Dependent variable: 1 if bank borrowed from that",
                           "facility in that period, 0 otherwise.",
                           "All RHS variables winsorized at 2.5/97.5 and",
                           "z-standardized. HC1 robust SE in parentheses.",
                           "* p<0.10, ** p<0.05, *** p<0.01."),
           general_title = "Notes:", footnote_as_chunk = TRUE)

tbl4_k
Table 4: LPM — Determinants of Borrower Status by Period | Facility
Pre-Crisis
SVB Week
SVB → FRC
FRC Week
Variable P0: Pre-Crisis | DW P1: SVB Week | DW P2: SVB to FRC | DW P2: SVB to FRC | BTFP P3: FRC Week | DW P3: FRC Week | BTFP
Key solvency-liquidity variables
MTM loss (total) 0.001 -0.002 0.012** 0.015*** -0.000 0.007**
(0.002) (0.002) (0.005) (0.005) (0.003) (0.003)
Uninsured leverage 0.000 0.002 0.012** 0.017*** -0.000 0.007**
(0.003) (0.002) (0.005) (0.006) (0.003) (0.004)
MTM × Uninsured Lev 0.002 0.001 0.013*** 0.012*** 0.005** 0.005*
(0.002) (0.002) (0.004) (0.004) (0.002) (0.003)
Controls
Ln(assets) 0.013*** 0.014*** 0.066*** 0.047*** 0.014*** 0.015***
(0.003) (0.003) (0.006) (0.006) (0.003) (0.004)
Cash / TA -0.004* -0.003** 0.003 -0.021*** -0.003 -0.013***
(0.002) (0.001) (0.004) (0.004) (0.002) (0.002)
Loan / Deposit -0.005* -0.005** -0.007 -0.024*** -0.004 -0.013***
(0.003) (0.002) (0.005) (0.005) (0.003) (0.003)
Book equity / TA 0.002 -0.000 0.001 -0.010** 0.001 -0.002
(0.002) (0.001) (0.004) (0.004) (0.002) (0.003)
Wholesale funding 0.006** 0.008*** 0.015*** 0.033*** 0.007** 0.012***
(0.003) (0.003) (0.005) (0.006) (0.003) (0.003)
ROA -0.002 -0.002 0.001 -0.002 0.000 -0.003
(0.002) (0.002) (0.004) (0.004) (0.002) (0.002)
Intercept
Constant 0.016*** 0.014*** 0.086*** 0.099*** 0.022*** 0.039***
(0.002) (0.002) (0.004) (0.004) (0.002) (0.003)
Fit statistics
N 4,632 4,632 4,632 4,632 4,632 4,632
0.015 0.023 0.069 0.069 0.013 0.026
Mean of DV 0.0155 0.0145 0.0857 0.0993 0.0218 0.0393
Notes: Dependent variable: 1 if bank borrowed from that facility in that period, 0 otherwise. All RHS variables winsorized at 2.5/97.5 and z-standardized. HC1 robust SE in parentheses. * p<0.10, ** p<0.05, *** p<0.01.
save_kbl_latex(reg_table, "table7_lpm_by_period",
               caption = "LPM — Determinants of Borrower Status by Period | Facility")

# Optional: save model objects so you can inspect them later
saveRDS(models, file.path(TABLE_PATH, "table7_models.rds"))

Insured Deposit Leverage

# ══════════════════════════════════════════════════════════════════════
# TABLE 8: LPM with INSURED leverage (mirrors Table 4)
# ══════════════════════════════════════════════════════════════════════

# Build insured leverage (pp), winsorize, z-standardize
df <- df %>%
  mutate(di_ta     = 100 * safe_div(insured_deposit, total_asset, 0),
         di_ta_w   = winsorize(di_ta),
         di_ta_w_z = z_std(di_ta_w))

# Regressions: same RHS as Table 4, swap du_ta_w_z → di_ta_w_z
rhs_4b <- "ell_w_z * di_ta_w_z + log_ta_w_z + cash_ta_w_z + loan_to_dep_w_z + eq_ta_w_z + wholesale_ta_w_z + roa_w_z"

models_4b <- list(
  `P0: Pre-Crisis | DW`   = lm(as.formula(paste("dep_p0_dw ~",   rhs_4b)), data = df),
  `P1: SVB Week | DW`     = lm(as.formula(paste("dep_p1_dw ~",   rhs_4b)), data = df),
  `P2: SVB to FRC | DW`   = lm(as.formula(paste("dep_p2_dw ~",   rhs_4b)), data = df),
  `P2: SVB to FRC | BTFP` = lm(as.formula(paste("dep_p2_btfp ~", rhs_4b)), data = df),
  `P3: FRC Week | DW`     = lm(as.formula(paste("dep_p3_dw ~",   rhs_4b)), data = df),
  `P3: FRC Week | BTFP`   = lm(as.formula(paste("dep_p3_btfp ~", rhs_4b)), data = df)
)

tidy_4b <- lapply(models_4b, get_robust_tidy)

# Build table (same format as Table 4)
var_order_4b <- c("ell_w_z", "di_ta_w_z", "ell_w_z:di_ta_w_z",
                  "log_ta_w_z", "cash_ta_w_z", "loan_to_dep_w_z",
                  "eq_ta_w_z", "wholesale_ta_w_z", "roa_w_z",
                  "(Intercept)")

var_labels_4b <- c(
  "ell_w_z"            = "MTM loss (total)",
  "di_ta_w_z"          = "Insured leverage",
  "ell_w_z:di_ta_w_z"  = "MTM × Insured Lev",
  "log_ta_w_z"         = "Ln(assets)",
  "cash_ta_w_z"        = "Cash / TA",
  "loan_to_dep_w_z"    = "Loan / Deposit",
  "eq_ta_w_z"          = "Book equity / TA",
  "wholesale_ta_w_z"   = "Wholesale funding",
  "roa_w_z"            = "ROA",
  "(Intercept)"        = "Constant"
)

body_4b <- list()
for (v in var_order_4b) {
  est_row <- c(); se_row <- c()
  for (m_name in names(tidy_4b)) {
    tm  <- tidy_4b[[m_name]]
    idx <- which(tm$term == v)
    if (length(idx) == 0) {
      est_row <- c(est_row, ""); se_row <- c(se_row, "")
    } else {
      est_row <- c(est_row, fmt_est(tm$estimate[idx], tm$p.value[idx]))
      se_row  <- c(se_row,  fmt_se(tm$std.error[idx]))
    }
  }
  body_4b[[paste0(v, "_est")]] <- c(var_labels_4b[[v]], est_row)
  body_4b[[paste0(v, "_se")]]  <- c("",                 se_row)
}

n_row   <- c("N")
r2_row  <- c("R²")
mdv_row <- c("Mean of DV")
for (m in models_4b) {
  n_row   <- c(n_row,   fmt(nobs(m)))
  r2_row  <- c(r2_row,  sprintf("%.3f", summary(m)$r.squared))
  mdv_row <- c(mdv_row, sprintf("%.4f", mean(m$model[[1]], na.rm = TRUE)))
}
body_4b[["_blank"]] <- rep("", 7)
body_4b[["_N"]]     <- n_row
body_4b[["_R2"]]    <- r2_row
body_4b[["_MDV"]]   <- mdv_row

reg_table_4b <- do.call(rbind, body_4b) %>% as.data.frame(stringsAsFactors = FALSE)
rownames(reg_table_4b) <- NULL
colnames(reg_table_4b) <- c("Variable", names(models_4b))

# Render
cat("\nTable 4B: LPM with insured leverage (robust SE in parentheses)\n")
## 
## Table 4B: LPM with insured leverage (robust SE in parentheses)
print(reg_table_4b, right = FALSE)
##    Variable          P0: Pre-Crisis | DW P1: SVB Week | DW P2: SVB to FRC | DW
## 1  MTM loss (total)  0.000               -0.002            0.010**            
## 2                    (0.002)             (0.002)           (0.005)            
## 3  Insured leverage  0.001               -0.001            -0.012**           
## 4                    (0.003)             (0.002)           (0.006)            
## 5  MTM × Insured Lev -0.001              0.001             -0.010**           
## 6                    (0.002)             (0.002)           (0.004)            
## 7  Ln(assets)        0.014***            0.015***          0.066***           
## 8                    (0.003)             (0.003)           (0.006)            
## 9  Cash / TA         -0.004*             -0.004**          0.002              
## 10                   (0.002)             (0.001)           (0.004)            
## 11 Loan / Deposit    -0.005*             -0.005**          -0.008             
## 12                   (0.003)             (0.002)           (0.005)            
## 13 Book equity / TA  0.002               -0.001            -0.005             
## 14                   (0.002)             (0.001)           (0.004)            
## 15 Wholesale funding 0.006**             0.008***          0.010*             
## 16                   (0.003)             (0.003)           (0.005)            
## 17 ROA               -0.002              -0.002            0.002              
## 18                   (0.002)             (0.002)           (0.004)            
## 19 Constant          0.015***            0.014***          0.087***           
## 20                   (0.002)             (0.002)           (0.004)            
## 21                                                                            
## 22 N                 4,632               4,632             4,632              
## 23 R²                0.015               0.023             0.068              
## 24 Mean of DV        0.0155              0.0145            0.0857             
##    P2: SVB to FRC | BTFP P3: FRC Week | DW P3: FRC Week | BTFP
## 1  0.013***              -0.001            0.006*             
## 2  (0.005)               (0.002)           (0.003)            
## 3  -0.018***             0.002             -0.007*            
## 4  (0.006)               (0.003)           (0.004)            
## 5  -0.006                -0.004*           -0.003             
## 6  (0.004)               (0.002)           (0.002)            
## 7  0.047***              0.015***          0.016***           
## 8  (0.006)               (0.003)           (0.004)            
## 9  -0.022***             -0.003            -0.014***          
## 10 (0.004)               (0.002)           (0.002)            
## 11 -0.024***             -0.004*           -0.014***          
## 12 (0.005)               (0.003)           (0.003)            
## 13 -0.019***             0.001             -0.006*            
## 14 (0.005)               (0.002)           (0.003)            
## 15 0.027***              0.007**           0.009**            
## 16 (0.006)               (0.003)           (0.004)            
## 17 -0.001                0.001             -0.003             
## 18 (0.004)               (0.002)           (0.002)            
## 19 0.098***              0.022***          0.039***           
## 20 (0.004)               (0.002)           (0.003)            
## 21                                                            
## 22 4,632                 4,632             4,632              
## 23 0.068                 0.013             0.025              
## 24 0.0993                0.0218            0.0393
n_coef_rows_4b  <- 2 * length(var_order_4b)
footer_start_4b <- n_coef_rows_4b + 2

tbl4b_k <- reg_table_4b %>%
  kbl(align = c("l", rep("r", 6)),
      caption = "Table 8: LPM — Determinants of Borrower Status (Insured leverage)",
      booktabs = TRUE) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE, position = "left", font_size = 11) %>%
  add_header_above(c(" " = 1, "Pre-Crisis" = 1, "SVB Week" = 1,
                     "SVB → FRC" = 2, "FRC Week" = 2)) %>%
  pack_rows("Key solvency-liquidity variables", 1, 6) %>%
  pack_rows("Controls",                         7, n_coef_rows_4b - 2) %>%
  pack_rows("Intercept",        n_coef_rows_4b - 1, n_coef_rows_4b) %>%
  pack_rows("Fit statistics",    footer_start_4b,    footer_start_4b + 2) %>%
  footnote(general = paste(
    "Dependent variable: 1 if bank borrowed from that facility in that",
    "period, 0 otherwise. Insured leverage = 100 × insured_deposit /",
    "total_asset. All RHS variables winsorized at 2.5/97.5 and",
    "z-standardized. HC1 robust SE in parentheses.",
    "* p<0.10, ** p<0.05, *** p<0.01."),
    general_title = "Notes:", footnote_as_chunk = TRUE)

tbl4b_k
Table 8: LPM — Determinants of Borrower Status (Insured leverage)
Pre-Crisis
SVB Week
SVB → FRC
FRC Week
Variable P0: Pre-Crisis | DW P1: SVB Week | DW P2: SVB to FRC | DW P2: SVB to FRC | BTFP P3: FRC Week | DW P3: FRC Week | BTFP
Key solvency-liquidity variables
MTM loss (total) 0.000 -0.002 0.010** 0.013*** -0.001 0.006*
(0.002) (0.002) (0.005) (0.005) (0.002) (0.003)
Insured leverage 0.001 -0.001 -0.012** -0.018*** 0.002 -0.007*
(0.003) (0.002) (0.006) (0.006) (0.003) (0.004)
MTM × Insured Lev -0.001 0.001 -0.010** -0.006 -0.004* -0.003
(0.002) (0.002) (0.004) (0.004) (0.002) (0.002)
Controls
Ln(assets) 0.014*** 0.015*** 0.066*** 0.047*** 0.015*** 0.016***
(0.003) (0.003) (0.006) (0.006) (0.003) (0.004)
Cash / TA -0.004* -0.004** 0.002 -0.022*** -0.003 -0.014***
(0.002) (0.001) (0.004) (0.004) (0.002) (0.002)
Loan / Deposit -0.005* -0.005** -0.008 -0.024*** -0.004* -0.014***
(0.003) (0.002) (0.005) (0.005) (0.003) (0.003)
Book equity / TA 0.002 -0.001 -0.005 -0.019*** 0.001 -0.006*
(0.002) (0.001) (0.004) (0.005) (0.002) (0.003)
Wholesale funding 0.006** 0.008*** 0.010* 0.027*** 0.007** 0.009**
(0.003) (0.003) (0.005) (0.006) (0.003) (0.004)
ROA -0.002 -0.002 0.002 -0.001 0.001 -0.003
(0.002) (0.002) (0.004) (0.004) (0.002) (0.002)
Intercept
Constant 0.015*** 0.014*** 0.087*** 0.098*** 0.022*** 0.039***
(0.002) (0.002) (0.004) (0.004) (0.002) (0.003)
Fit statistics
N 4,632 4,632 4,632 4,632 4,632 4,632
0.015 0.023 0.068 0.068 0.013 0.025
Mean of DV 0.0155 0.0145 0.0857 0.0993 0.0218 0.0393
Notes: Dependent variable: 1 if bank borrowed from that facility in that period, 0 otherwise. Insured leverage = 100 × insured_deposit / total_asset. All RHS variables winsorized at 2.5/97.5 and z-standardized. HC1 robust SE in parentheses. * p<0.10, ** p<0.05, *** p<0.01.
save_kbl_latex(reg_table_4b, "table8_lpm_insured_leverage",
               caption = "LPM — Determinants of Borrower Status (Insured Leverage)")

saveRDS(models_4b, file.path(TABLE_PATH, "table4b_models.rds"))

# Quick side-by-side: the interaction across both specs
cat("\nMTM × deposit-type interaction — Table 4 vs Table 4B:\n")
## 
## MTM × deposit-type interaction — Table 4 vs Table 4B:
tibble(
  cell = names(models),
  mtm_x_uninsured = sapply(tidy_models, function(t) {
    i <- which(t$term == "ell_w_z:du_ta_w_z")
    if (length(i) == 0) NA_real_ else t$estimate[i]
  }),
  mtm_x_insured = sapply(tidy_4b, function(t) {
    i <- which(t$term == "ell_w_z:di_ta_w_z")
    if (length(i) == 0) NA_real_ else t$estimate[i]
  })
) %>% print(digits = 3)
## # A tibble: 6 × 3
##   cell                  mtm_x_uninsured mtm_x_insured
##   <chr>                           <dbl>         <dbl>
## 1 P0: Pre-Crisis | DW          0.00154      -0.00128 
## 2 P1: SVB Week | DW            0.000793      0.000504
## 3 P2: SVB to FRC | DW          0.0125       -0.0101  
## 4 P2: SVB to FRC | BTFP        0.0119       -0.00570 
## 5 P3: FRC Week | DW            0.00457      -0.00409 
## 6 P3: FRC Week | BTFP          0.00474      -0.00338

Size

# ══════════════════════════════════════════════════════════════════════
# TABLE 9: LPM by Bank Size — Crisis Window (Mar 8 – May 4, 2023)
# 4 models: {DW, BTFP} × {Small (<$1B), Large (≥$1B)}
# Sample: df (2022Q4 baseline, excl. GSIBs and failed banks)
# ══════════════════════════════════════════════════════════════════════

# ── A. Size indicator and subsamples ─────────────────────────────────

SIZE_CUTOFF <- 1e6   # $1B 

df <- df %>%
  mutate(size_group = if_else(total_asset >= SIZE_CUTOFF, "Large", "Small"))

cat(sprintf("Size distribution: Small = %s banks, Large = %s banks\n",
            fmt(sum(df$size_group == "Small")),
            fmt(sum(df$size_group == "Large"))))
## Size distribution: Small = 3,743 banks, Large = 953 banks
# ── B. Crisis-window borrower indicators (used_dw, used_btfp exist in df) ─
# These were built in classify_borrowers and cover the full Mar 8 – May 4
# crisis window, which is exactly what we want here.
df_small <- df %>% filter(size_group == "Small")
df_large <- df %>% filter(size_group == "Large")

cat(sprintf("Small — DW borrowers: %s  |  BTFP borrowers: %s\n",
            fmt(sum(df_small$used_dw)),
            fmt(sum(df_small$used_btfp))))
## Small — DW borrowers: 234  |  BTFP borrowers: 325
cat(sprintf("Large — DW borrowers: %s  |  BTFP borrowers: %s\n",
            fmt(sum(df_large$used_dw)),
            fmt(sum(df_large$used_btfp))))
## Large — DW borrowers: 214  |  BTFP borrowers: 194
# ── C. Four LPMs ─────────────────────────────────────────────────────
rhs6 <- "ell_w_z * du_ta_w_z + log_ta_w_z + cash_ta_w_z + loan_to_dep_w_z + eq_ta_w_z + wholesale_ta_w_z + roa_w_z"

models_6 <- list(
  `Small — DW`   = lm(as.formula(paste("used_dw   ~", rhs6)), data = df_small),
  `Large — DW`   = lm(as.formula(paste("used_dw   ~", rhs6)), data = df_large),
  `Small — BTFP` = lm(as.formula(paste("used_btfp ~", rhs6)), data = df_small),
  `Large — BTFP` = lm(as.formula(paste("used_btfp ~", rhs6)), data = df_large)
)

tidy_6 <- lapply(models_6, get_robust_tidy)

# ── D. Format table (same pattern as Table 7) ────────────────────────
var_order_6 <- c("ell_w_z", "du_ta_w_z", "ell_w_z:du_ta_w_z",
                 "log_ta_w_z", "cash_ta_w_z", "loan_to_dep_w_z",
                 "eq_ta_w_z", "wholesale_ta_w_z", "roa_w_z",
                 "(Intercept)")

var_labels_6 <- c(
  "ell_w_z"            = "MTM loss (total)",
  "du_ta_w_z"          = "Uninsured leverage",
  "ell_w_z:du_ta_w_z"  = "MTM × Uninsured Lev",
  "log_ta_w_z"         = "Ln(assets)",
  "cash_ta_w_z"        = "Cash / TA",
  "loan_to_dep_w_z"    = "Loan / Deposit",
  "eq_ta_w_z"          = "Book equity / TA",
  "wholesale_ta_w_z"   = "Wholesale funding",
  "roa_w_z"            = "ROA",
  "(Intercept)"        = "Constant"
)

body_6 <- list()
for (v in var_order_6) {
  est_row <- c(); se_row <- c()
  for (m_name in names(tidy_6)) {
    tm  <- tidy_6[[m_name]]
    idx <- which(tm$term == v)
    if (length(idx) == 0) {
      est_row <- c(est_row, ""); se_row <- c(se_row, "")
    } else {
      est_row <- c(est_row, fmt_est(tm$estimate[idx], tm$p.value[idx]))
      se_row  <- c(se_row,  fmt_se(tm$std.error[idx]))
    }
  }
  body_6[[paste0(v, "_est")]] <- c(var_labels_6[[v]], est_row)
  body_6[[paste0(v, "_se")]]  <- c("",                se_row)
}

n_row   <- c("N")
r2_row  <- c("R²")
mdv_row <- c("Mean of DV")
for (m in models_6) {
  n_row   <- c(n_row,   fmt(nobs(m)))
  r2_row  <- c(r2_row,  sprintf("%.3f", summary(m)$r.squared))
  mdv_row <- c(mdv_row, sprintf("%.4f", mean(m$model[[1]], na.rm = TRUE)))
}
body_6[["_blank"]] <- rep("", 5)
body_6[["_N"]]     <- n_row
body_6[["_R2"]]    <- r2_row
body_6[["_MDV"]]   <- mdv_row

reg_table_6 <- do.call(rbind, body_6) %>% as.data.frame(stringsAsFactors = FALSE)
rownames(reg_table_6) <- NULL
colnames(reg_table_6) <- c("Variable", names(models_6))

# ── E. Render ────────────────────────────────────────────────────────
cat("\nTable 9: LPM by size — Crisis window (robust SE in parentheses)\n")
## 
## Table 9: LPM by size — Crisis window (robust SE in parentheses)
print(reg_table_6, right = FALSE)
##    Variable            Small — DW Large — DW Small — BTFP Large — BTFP
## 1  MTM loss (total)    0.007      0.015      0.012**      0.021       
## 2                      (0.005)    (0.016)    (0.006)      (0.017)     
## 3  Uninsured leverage  0.002      0.035**    0.017***     0.024*      
## 4                      (0.006)    (0.015)    (0.006)      (0.014)     
## 5  MTM × Uninsured Lev 0.006      0.029**    0.010*       0.006       
## 6                      (0.004)    (0.013)    (0.005)      (0.012)     
## 7  Ln(assets)          0.058***   0.088***   0.050***     0.047**     
## 8                      (0.008)    (0.024)    (0.008)      (0.023)     
## 9  Cash / TA           0.006      -0.046**   -0.024***    -0.060***   
## 10                     (0.004)    (0.023)    (0.004)      (0.020)     
## 11 Loan / Deposit      -0.005     -0.017     -0.023***    -0.054***   
## 12                     (0.005)    (0.020)    (0.005)      (0.021)     
## 13 Book equity / TA    -0.007**   0.020      -0.008**     -0.040*     
## 14                     (0.003)    (0.023)    (0.004)      (0.021)     
## 15 Wholesale funding   0.018***   -0.006     0.030***     0.047***    
## 16                     (0.006)    (0.014)    (0.006)      (0.015)     
## 17 ROA                 0.002      0.019      0.000        -0.000      
## 18                     (0.004)    (0.017)    (0.004)      (0.015)     
## 19 Constant            0.086***   0.068*     0.111***     0.102***    
## 20                     (0.006)    (0.038)    (0.006)      (0.036)     
## 21                                                                    
## 22 N                   3,685      947        3,685        947         
## 23 R²                  0.033      0.040      0.053        0.061       
## 24 Mean of DV          0.0635     0.2260     0.0882       0.2049
n_coef_6 <- 2 * length(var_order_6)
footer_6 <- n_coef_6 + 2

tbl6_k <- reg_table_6 %>%
  kbl(align = c("l", rep("r", 4)),
      caption = "Table 9: LPM by Bank Size — Crisis Window (Mar 8 – May 4, 2023)",
      booktabs = TRUE) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE, position = "left", font_size = 11) %>%
  add_header_above(c(" " = 1, "Discount Window" = 2, "BTFP" = 2)) %>%
  pack_rows("Key solvency-liquidity variables", 1, 6) %>%
  pack_rows("Controls",                         7, n_coef_6 - 2) %>%
  pack_rows("Intercept",         n_coef_6 - 1, n_coef_6) %>%
  pack_rows("Fit statistics",    footer_6,     footer_6 + 2) %>%
  footnote(general = paste(
    "Dependent variable: 1 if bank borrowed from the facility during the",
    "crisis window (Mar 8 – May 4, 2023), 0 otherwise. Sample split by",
    "total assets at $1B (Small < $1B; Large ≥ $1B). 2022Q4 baseline;",
    "excludes GSIBs and failed banks. All RHS variables winsorized at",
    "2.5/97.5 and z-standardized. HC1 robust SE in parentheses.",
    "* p<0.10, ** p<0.05, *** p<0.01."),
    general_title = "Notes:", footnote_as_chunk = TRUE)

tbl6_k
Table 9: LPM by Bank Size — Crisis Window (Mar 8 – May 4, 2023)
Discount Window
BTFP
Variable Small — DW Large — DW Small — BTFP Large — BTFP
Key solvency-liquidity variables
MTM loss (total) 0.007 0.015 0.012** 0.021
(0.005) (0.016) (0.006) (0.017)
Uninsured leverage 0.002 0.035** 0.017*** 0.024*
(0.006) (0.015) (0.006) (0.014)
MTM × Uninsured Lev 0.006 0.029** 0.010* 0.006
(0.004) (0.013) (0.005) (0.012)
Controls
Ln(assets) 0.058*** 0.088*** 0.050*** 0.047**
(0.008) (0.024) (0.008) (0.023)
Cash / TA 0.006 -0.046** -0.024*** -0.060***
(0.004) (0.023) (0.004) (0.020)
Loan / Deposit -0.005 -0.017 -0.023*** -0.054***
(0.005) (0.020) (0.005) (0.021)
Book equity / TA -0.007** 0.020 -0.008** -0.040*
(0.003) (0.023) (0.004) (0.021)
Wholesale funding 0.018*** -0.006 0.030*** 0.047***
(0.006) (0.014) (0.006) (0.015)
ROA 0.002 0.019 0.000 -0.000
(0.004) (0.017) (0.004) (0.015)
Intercept
Constant 0.086*** 0.068* 0.111*** 0.102***
(0.006) (0.038) (0.006) (0.036)
Fit statistics
N 3,685 947 3,685 947
0.033 0.040 0.053 0.061
Mean of DV 0.0635 0.2260 0.0882 0.2049
Notes: Dependent variable: 1 if bank borrowed from the facility during the crisis window (Mar 8 – May 4, 2023), 0 otherwise. Sample split by total assets at $1B (Small < $1B; Large ≥ $1B). 2022Q4 baseline; excludes GSIBs and failed banks. All RHS variables winsorized at 2.5/97.5 and z-standardized. HC1 robust SE in parentheses. * p<0.10, ** p<0.05, *** p<0.01.
save_kbl_latex(reg_table_6, "table9_lpm_by_size",
               caption = "LPM by Bank Size — Crisis Window")

saveRDS(models_6, file.path(TABLE_PATH, "table6_models.rds"))

# ── F. Diagnostic: formal test of Small = Large coefficient equality ─
# For each facility, fit a pooled model with Size × all covariates and
# test whether the size-interaction coefficients are jointly zero.
# Interpretation: rejecting H0 means small and large banks respond
# differently to the same covariates (justifies the subsample split).

df <- df %>% mutate(is_large = as.integer(size_group == "Large"))

test_equality <- function(dv) {
  rhs_interact <- paste0("is_large * (ell_w_z * du_ta_w_z + log_ta_w_z + ",
                         "cash_ta_w_z + loan_to_dep_w_z + eq_ta_w_z + ",
                         "wholesale_ta_w_z + roa_w_z)")
  m_full <- lm(as.formula(paste(dv, "~", rhs_interact)),        data = df)
  m_null <- lm(as.formula(paste(dv, "~ is_large +", rhs6)),     data = df)  # same slopes across size
  # Wald F-test with HC1
  waldtest(m_null, m_full, vcov = vcovHC(m_full, type = "HC1"))
}

cat("\n── Small vs Large coefficient equality (Wald F, HC1) ──\n")
## 
## ── Small vs Large coefficient equality (Wald F, HC1) ──
cat("H0: all non-intercept slopes are equal across Small and Large\n\n")
## H0: all non-intercept slopes are equal across Small and Large
cat("DW facility:\n");   print(test_equality("used_dw"))
## DW facility:
## Wald test
## 
## Model 1: used_dw ~ is_large + ell_w_z * du_ta_w_z + log_ta_w_z + cash_ta_w_z + 
##     loan_to_dep_w_z + eq_ta_w_z + wholesale_ta_w_z + roa_w_z
## Model 2: used_dw ~ is_large * (ell_w_z * du_ta_w_z + log_ta_w_z + cash_ta_w_z + 
##     loan_to_dep_w_z + eq_ta_w_z + wholesale_ta_w_z + roa_w_z)
##   Res.Df Df      F  Pr(>F)  
## 1   4621                    
## 2   4612  9 2.2515 0.01652 *
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
cat("\nBTFP facility:\n"); print(test_equality("used_btfp"))
## 
## BTFP facility:
## Wald test
## 
## Model 1: used_btfp ~ is_large + ell_w_z * du_ta_w_z + log_ta_w_z + cash_ta_w_z + 
##     loan_to_dep_w_z + eq_ta_w_z + wholesale_ta_w_z + roa_w_z
## Model 2: used_btfp ~ is_large * (ell_w_z * du_ta_w_z + log_ta_w_z + cash_ta_w_z + 
##     loan_to_dep_w_z + eq_ta_w_z + wholesale_ta_w_z + roa_w_z)
##   Res.Df Df      F  Pr(>F)  
## 1   4621                    
## 2   4612  9 1.6364 0.09905 .
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Temporal

# ══════════════════════════════════════════════════════════════════════
# TABLE 10: LPM — Full Crisis and Arbitrage Window
# 6 models: {DW Only, BTFP Only, Both} × {Crisis, Arbitrage}
#   Crisis   : df      (2022Q4 baseline) | loans Mar 8 – May 4, 2023
#   Arbitrage: df_arb  (2023Q3 baseline) | loans Nov 15, 2023 – Jan 24, 2024
# ══════════════════════════════════════════════════════════════════════

# ── A. Crisis DVs (borrower_type already built in df) ────────────────
df <- df %>%
  mutate(
    dep_dw_only_crisis   = as.integer(borrower_type == "DW Only"),
    dep_btfp_only_crisis = as.integer(borrower_type == "BTFP Only"),
    dep_both_crisis      = as.integer(borrower_type == "Both")
  )

# ── B. Arbitrage DVs and controls in df_arb ──────────────────────────
dw_arb_banks <- dw_clean %>%
  filter(dw_loan_date >= ARB_START, dw_loan_date <= ARB_END) %>%
  pull(rssd_id) %>% unique() %>% as.character()

df_arb <- df_arb %>%
  mutate(
    idrssd            = as.character(idrssd),
    used_dw_arb       = as.integer(idrssd %in% dw_arb_banks),
    used_btfp_arb     = as.integer(borrowed_arb == 1),
    dep_dw_only_arb   = as.integer(used_dw_arb == 1 & used_btfp_arb == 0),
    dep_btfp_only_arb = as.integer(used_dw_arb == 0 & used_btfp_arb == 1),
    dep_both_arb      = as.integer(used_dw_arb == 1 & used_btfp_arb == 1),
    # Controls that aren't already in df_arb
    du_ta       = uninsured_leverage,
    loan_to_dep = 100 * safe_div(total_loan, total_deposit),
    roa = if ("roa" %in% names(.)) roa
          else if ("net_income_to_total_asset" %in% names(.)) net_income_to_total_asset
          else NA_real_
  )

cat("\nCrisis (n =", nrow(df), "):\n")
## 
## Crisis (n = 4696 ):
cat(sprintf("  DW Only : %s   BTFP Only : %s   Both : %s\n",
            fmt(sum(df$dep_dw_only_crisis)),
            fmt(sum(df$dep_btfp_only_crisis)),
            fmt(sum(df$dep_both_crisis))))
##   DW Only : 341   BTFP Only : 412   Both : 107
cat(sprintf("Arbitrage (n = %s):\n", fmt(nrow(df_arb))))
## Arbitrage (n = 4,604):
cat(sprintf("  DW Only : %s   BTFP Only : %s   Both : %s\n",
            fmt(sum(df_arb$dep_dw_only_arb)),
            fmt(sum(df_arb$dep_btfp_only_arb)),
            fmt(sum(df_arb$dep_both_arb))))
##   DW Only : 257   BTFP Only : 655   Both : 111
# ── C. Winsorize + z-standardize df_arb RHS vars ─────────────────────
arb_vars <- c("ell", "du_ta", "log_ta", "cash_ta", "loan_to_dep",
              "eq_ta", "wholesale_ta", "roa")
df_arb <- df_arb %>%
  mutate(across(all_of(arb_vars), ~ winsorize(.x), .names = "{.col}_w")) %>%
  mutate(across(all_of(paste0(arb_vars, "_w")), ~ z_std(.x), .names = "{.col}_z"))

# ── D. Six LPMs ──────────────────────────────────────────────────────
rhs5 <- "ell_w_z * du_ta_w_z + log_ta_w_z + cash_ta_w_z + loan_to_dep_w_z + eq_ta_w_z + wholesale_ta_w_z + roa_w_z"

models_5 <- list(
  `Crisis — DW Only`      = lm(as.formula(paste("dep_dw_only_crisis   ~", rhs5)), data = df),
  `Crisis — BTFP Only`    = lm(as.formula(paste("dep_btfp_only_crisis ~", rhs5)), data = df),
  `Crisis — Both`         = lm(as.formula(paste("dep_both_crisis      ~", rhs5)), data = df),
  `Arbitrage — DW Only`   = lm(as.formula(paste("dep_dw_only_arb      ~", rhs5)), data = df_arb),
  `Arbitrage — BTFP Only` = lm(as.formula(paste("dep_btfp_only_arb    ~", rhs5)), data = df_arb),
  `Arbitrage — Both`      = lm(as.formula(paste("dep_both_arb         ~", rhs5)), data = df_arb)
)

tidy_5 <- lapply(models_5, get_robust_tidy)

# ── E. Format table (same pattern as Table 4) ────────────────────────
var_order_5 <- c("ell_w_z", "du_ta_w_z", "ell_w_z:du_ta_w_z",
                 "log_ta_w_z", "cash_ta_w_z", "loan_to_dep_w_z",
                 "eq_ta_w_z", "wholesale_ta_w_z", "roa_w_z",
                 "(Intercept)")

var_labels_5 <- c(
  "ell_w_z"            = "MTM loss (total)",
  "du_ta_w_z"          = "Uninsured leverage",
  "ell_w_z:du_ta_w_z"  = "MTM × Uninsured Lev",
  "log_ta_w_z"         = "Ln(assets)",
  "cash_ta_w_z"        = "Cash / TA",
  "loan_to_dep_w_z"    = "Loan / Deposit",
  "eq_ta_w_z"          = "Book equity / TA",
  "wholesale_ta_w_z"   = "Wholesale funding",
  "roa_w_z"            = "ROA",
  "(Intercept)"        = "Constant"
)

body_5 <- list()
for (v in var_order_5) {
  est_row <- c(); se_row <- c()
  for (m_name in names(tidy_5)) {
    tm  <- tidy_5[[m_name]]
    idx <- which(tm$term == v)
    if (length(idx) == 0) {
      est_row <- c(est_row, ""); se_row <- c(se_row, "")
    } else {
      est_row <- c(est_row, fmt_est(tm$estimate[idx], tm$p.value[idx]))
      se_row  <- c(se_row,  fmt_se(tm$std.error[idx]))
    }
  }
  body_5[[paste0(v, "_est")]] <- c(var_labels_5[[v]], est_row)
  body_5[[paste0(v, "_se")]]  <- c("",                se_row)
}

n_row   <- c("N")
r2_row  <- c("R²")
mdv_row <- c("Mean of DV")
for (m in models_5) {
  n_row   <- c(n_row,   fmt(nobs(m)))
  r2_row  <- c(r2_row,  sprintf("%.3f", summary(m)$r.squared))
  mdv_row <- c(mdv_row, sprintf("%.4f", mean(m$model[[1]], na.rm = TRUE)))
}
body_5[["_blank"]] <- rep("", 7)
body_5[["_N"]]     <- n_row
body_5[["_R2"]]    <- r2_row
body_5[["_MDV"]]   <- mdv_row

reg_table_5 <- do.call(rbind, body_5) %>% as.data.frame(stringsAsFactors = FALSE)
rownames(reg_table_5) <- NULL
colnames(reg_table_5) <- c("Variable", names(models_5))

# ── F. Render ────────────────────────────────────────────────────────
cat("\nTable 10: LPM — Crisis and Arbitrage (robust SE in parentheses)\n")
## 
## Table 10: LPM — Crisis and Arbitrage (robust SE in parentheses)
print(reg_table_5, right = FALSE)
##    Variable            Crisis — DW Only Crisis — BTFP Only Crisis — Both
## 1  MTM loss (total)    0.007            0.012**            0.004        
## 2                      (0.005)          (0.005)            (0.003)      
## 3  Uninsured leverage  0.001            0.011**            0.008**      
## 4                      (0.005)          (0.005)            (0.003)      
## 5  MTM × Uninsured Lev 0.007*           0.005              0.007**      
## 6                      (0.004)          (0.004)            (0.003)      
## 7  Ln(assets)          0.051***         0.025***           0.024***     
## 8                      (0.006)          (0.005)            (0.004)      
## 9  Cash / TA           0.004            -0.024***          -0.002       
## 10                     (0.004)          (0.004)            (0.002)      
## 11 Loan / Deposit      0.001            -0.020***          -0.007***    
## 12                     (0.005)          (0.005)            (0.003)      
## 13 Book equity / TA    -0.001           -0.012***          0.001        
## 14                     (0.004)          (0.004)            (0.002)      
## 15 Wholesale funding   0.005            0.026***           0.008***     
## 16                     (0.005)          (0.005)            (0.003)      
## 17 ROA                 0.005            0.002              -0.004*      
## 18                     (0.004)          (0.004)            (0.002)      
## 19 Constant            0.074***         0.088***           0.024***     
## 20                     (0.004)          (0.004)            (0.002)      
## 21                                                                      
## 22 N                   4,632            4,632              4,632        
## 23 R²                  0.041            0.042              0.039        
## 24 Mean of DV          0.0736           0.0889             0.0231       
##    Arbitrage — DW Only Arbitrage — BTFP Only Arbitrage — Both
## 1  -0.008*             0.013**               0.003           
## 2  (0.004)             (0.006)               (0.003)         
## 3  -0.009**            0.014**               0.001           
## 4  (0.004)             (0.006)               (0.003)         
## 5  -0.004              0.006                 0.003           
## 6  (0.003)             (0.004)               (0.002)         
## 7  0.048***            0.039***              0.017***        
## 8  (0.005)             (0.006)               (0.003)         
## 9  0.003               -0.037***             -0.005***       
## 10 (0.004)             (0.005)               (0.002)         
## 11 0.004               -0.034***             -0.005**        
## 12 (0.004)             (0.006)               (0.002)         
## 13 -0.005              -0.006                -0.002          
## 14 (0.003)             (0.005)               (0.002)         
## 15 -0.004              0.054***              0.005**         
## 16 (0.004)             (0.006)               (0.003)         
## 17 -0.002              -0.016***             -0.004*         
## 18 (0.004)             (0.005)               (0.002)         
## 19 0.055***            0.142***              0.024***        
## 20 (0.003)             (0.005)               (0.002)         
## 21                                                           
## 22 4,531               4,531                 4,531           
## 23 0.037               0.072                 0.020           
## 24 0.0567              0.1446                0.0245
n_coef_5  <- 2 * length(var_order_5)
footer_5  <- n_coef_5 + 2

tbl5_k <- reg_table_5 %>%
  kbl(align = c("l", rep("r", 6)),
      caption = "Table 5: LPM — Borrower Category Across Crisis and Arbitrage Windows",
      booktabs = TRUE) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE, position = "left", font_size = 11) %>%
  add_header_above(c(" " = 1,
                     "Crisis (Mar 8 – May 4, 2023; 2022Q4 baseline)" = 3,
                     "Arbitrage (Nov 15, 2023 – Jan 24, 2024; 2023Q3 baseline)" = 3)) %>%
  pack_rows("Key solvency-liquidity variables", 1, 6) %>%
  pack_rows("Controls",                         7, n_coef_5 - 2) %>%
  pack_rows("Intercept",         n_coef_5 - 1, n_coef_5) %>%
  pack_rows("Fit statistics",    footer_5,     footer_5 + 2) %>%
  footnote(general = paste(
    "Dependent variables are mutually exclusive borrower categories: DW Only,",
    "BTFP Only, Both. Crisis regressions use the 2022Q4 cross-section;",
    "arbitrage regressions use 2023Q3. DW loan data covers only 2023, so DW",
    "usage in January 2024 is not observed — the Arbitrage DW Only and Both",
    "columns effectively capture Nov 15 – Dec 31, 2023 DW activity.",
    "All RHS variables winsorized at 2.5/97.5 and z-standardized. HC1 robust",
    "SE in parentheses. * p<0.10, ** p<0.05, *** p<0.01."),
    general_title = "Notes:", footnote_as_chunk = TRUE)

tbl5_k
Table 5: LPM — Borrower Category Across Crisis and Arbitrage Windows
Crisis (Mar 8 – May 4, 2023; 2022Q4 baseline)
Arbitrage (Nov 15, 2023 – Jan 24, 2024; 2023Q3 baseline)
Variable Crisis — DW Only Crisis — BTFP Only Crisis — Both Arbitrage — DW Only Arbitrage — BTFP Only Arbitrage — Both
Key solvency-liquidity variables
MTM loss (total) 0.007 0.012** 0.004 -0.008* 0.013** 0.003
(0.005) (0.005) (0.003) (0.004) (0.006) (0.003)
Uninsured leverage 0.001 0.011** 0.008** -0.009** 0.014** 0.001
(0.005) (0.005) (0.003) (0.004) (0.006) (0.003)
MTM × Uninsured Lev 0.007* 0.005 0.007** -0.004 0.006 0.003
(0.004) (0.004) (0.003) (0.003) (0.004) (0.002)
Controls
Ln(assets) 0.051*** 0.025*** 0.024*** 0.048*** 0.039*** 0.017***
(0.006) (0.005) (0.004) (0.005) (0.006) (0.003)
Cash / TA 0.004 -0.024*** -0.002 0.003 -0.037*** -0.005***
(0.004) (0.004) (0.002) (0.004) (0.005) (0.002)
Loan / Deposit 0.001 -0.020*** -0.007*** 0.004 -0.034*** -0.005**
(0.005) (0.005) (0.003) (0.004) (0.006) (0.002)
Book equity / TA -0.001 -0.012*** 0.001 -0.005 -0.006 -0.002
(0.004) (0.004) (0.002) (0.003) (0.005) (0.002)
Wholesale funding 0.005 0.026*** 0.008*** -0.004 0.054*** 0.005**
(0.005) (0.005) (0.003) (0.004) (0.006) (0.003)
ROA 0.005 0.002 -0.004* -0.002 -0.016*** -0.004*
(0.004) (0.004) (0.002) (0.004) (0.005) (0.002)
Intercept
Constant 0.074*** 0.088*** 0.024*** 0.055*** 0.142*** 0.024***
(0.004) (0.004) (0.002) (0.003) (0.005) (0.002)
Fit statistics
N 4,632 4,632 4,632 4,531 4,531 4,531
0.041 0.042 0.039 0.037 0.072 0.020
Mean of DV 0.0736 0.0889 0.0231 0.0567 0.1446 0.0245
Notes: Dependent variables are mutually exclusive borrower categories: DW Only, BTFP Only, Both. Crisis regressions use the 2022Q4 cross-section; arbitrage regressions use 2023Q3. DW loan data covers only 2023, so DW usage in January 2024 is not observed — the Arbitrage DW Only and Both columns effectively capture Nov 15 – Dec 31, 2023 DW activity. All RHS variables winsorized at 2.5/97.5 and z-standardized. HC1 robust SE in parentheses. * p<0.10, ** p<0.05, *** p<0.01.
save_kbl_latex(reg_table_5, "table10_lpm_crisis_arbitrage",
               caption = "LPM — Crisis and Arbitrage Window Regressions")

saveRDS(models_5, file.path(TABLE_PATH, "table10_models.rds"))