1 SETUP

rm(list = ls())
library(data.table); library(dplyr); library(tidyr); library(stringr)
library(lubridate); library(purrr); library(tibble)
library(fixest); library(modelsummary)
library(knitr); library(kableExtra)
library(ggplot2); library(scales); library(patchwork)
library(readr); library(readxl)

winsorize <- function(x, probs = c(0.025, 0.975)) {
  if (all(is.na(x))) return(x)
  q <- quantile(x, probs = probs, na.rm = TRUE, names = FALSE)
  pmax(pmin(x, q[2]), q[1])
}
standardize_z <- function(x) {
  if (all(is.na(x))) return(x)
  s <- sd(x, na.rm = TRUE)
  if (is.na(s) || s == 0) return(rep(0, length(x)))
  (x - mean(x, na.rm = TRUE)) / s
}
safe_div <- function(num, denom, default = NA_real_) {
  ifelse(is.na(denom) | denom == 0, default, num / denom)
}
format_pval <- function(p) {
  case_when(is.na(p) ~ "", p < 0.01 ~ "***", p < 0.05 ~ "**", p < 0.10 ~ "*", TRUE ~ "")
}
save_figure <- function(plot_obj, filename, width = 12, height = 8) {
  ggsave(file.path(FIG_PATH, paste0(filename, ".pdf")),
    plot = plot_obj, width = width, height = height, device = "pdf")
  message("Saved: ", filename, ".pdf")
}
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", "scale_down"))
  writeLines(tex, file.path(TABLE_PATH, paste0(filename, ".tex")))
  cat(sprintf("Saved: %s.tex\n", filename))
}
save_reg_latex <- function(model_list, filename, ...) {
  msummary(model_list, output = file.path(TABLE_PATH, paste0(filename, ".tex")),
    stars = c("*"=.10, "**"=.05, "***"=.01), gof_omit = "AIC|BIC|Log|RMSE", ...)
  cat(sprintf("Saved: %s.tex\n", filename))
}

BASE_PATH   <- "C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025"
DATA_PROC   <- file.path(BASE_PATH, "01_data/processed")
OUTPUT_PATH <- file.path(BASE_PATH, "03_documentation/Borrowing_Nuance_Phi")
TABLE_PATH  <- file.path(OUTPUT_PATH, "tables"); FIG_PATH <- file.path(OUTPUT_PATH, "figures")
for (p in c(TABLE_PATH, FIG_PATH)) if (!dir.exists(p)) dir.create(p, recursive = TRUE)

BASELINE_MAIN <- "2022Q4"
DW_DATA_END   <- as.Date("2023-12-31")
y_10yr <- 0.0392; delta_decay <- 0.10; cap_factor <- 1 / (y_10yr + delta_decay)

# ── Crisis phases ──
PHASES <- tribble(
  ~phase, ~start, ~end, ~label,
  "P0", as.Date("2023-03-01"), as.Date("2023-03-07"), "Pre-Crisis (Mar 1-7)",
  "P1", as.Date("2023-03-08"), as.Date("2023-03-12"), "SVB Fail, Pre-BTFP (Mar 8-12)",
  "P2", as.Date("2023-03-13"), as.Date("2023-04-27"), "BTFP Active (Mar 13 - Apr 27)",
  "P3", as.Date("2023-04-28"), as.Date("2023-05-04"), "First Republic Week (Apr 28 - May 4)"
)

ACUTE_START <- as.Date("2023-03-13")
ACUTE_END   <- as.Date("2023-04-30")
BTFP_END    <- as.Date("2024-03-11")

dist_names <- c("1"="Boston", "2"="New York", "3"="Philadelphia", "4"="Cleveland",
  "5"="Richmond", "6"="Atlanta", "7"="Chicago", "8"="St. Louis",
  "9"="Minneapolis", "10"="Kansas City", "11"="Dallas", "12"="San Francisco")

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())
phase_colors <- c("P0" = "#78909C", "P1" = "#FF8F00", "P2" = "#1565C0", "P3" = "#C62828")
fac_colors   <- c("BTFP" = "#1565C0", "DW" = "#E53935")

2 LOAD DATA

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

# ── Loan-level data ──
btfp_loans_raw <- read_csv(file.path(DATA_PROC, "btfp_loan_bank_only.csv"), show_col_types = FALSE) %>%
  mutate(rssd_id = as.character(rssd_id),
         btfp_loan_date = mdy(btfp_loan_date),
         btfp_repayment_date = mdy(btfp_repayment_date),
         btfp_maturity_date = mdy(btfp_maturity_date))

dw_loans_raw <- read_csv(file.path(DATA_PROC, "dw_loan_bank_2023.csv"), show_col_types = FALSE) %>%
  mutate(rssd_id = as.character(rssd_id),
         dw_loan_date = ymd(dw_loan_date),
         dw_repayment_date = ymd(dw_repayment_date),
         dw_credit_type_clean = str_squish(as.character(dw_credit_type))) %>%
  filter(str_detect(dw_credit_type_clean, "^Primary Credit"))

# ── Deposit betas (DSSW) ──
dssw_betas <- read_csv(file.path(DATA_PROC, "dssw_deposit_betas.csv"), show_col_types = FALSE) %>%
  mutate(idrssd = as.character(idrssd))
dssw_beta_2022q4 <- dssw_betas %>% filter(estimation_date == "2022Q4") %>%
  select(idrssd, beta_overall, beta_insured, beta_uninsured,
         beta_insured_w, beta_uninsured_w, gamma_hat, alpha_hat)

# ── Public flag & deposit costs (optional) ──
public_flag <- read_csv(file.path(DATA_PROC, "public_bank_flag.csv"), show_col_types = FALSE) %>%
  mutate(idrssd = as.character(idrssd)) %>% select(idrssd, period, is_public)

deposit_costs_file <- file.path(DATA_PROC, "dssw_deposit_costs.csv")
if (file.exists(deposit_costs_file)) {
  deposit_costs <- read_csv(deposit_costs_file, show_col_types = FALSE) %>%
    mutate(idrssd = as.character(idrssd))
  deposit_costs_2022q4 <- deposit_costs %>% filter(period == "2022Q4") %>%
    select(idrssd, deposit_cost_weighted, deposit_cost_insured, deposit_cost_uninsured)
  HAS_DEPOSIT_COSTS <- TRUE
} else { deposit_costs_2022q4 <- NULL; HAS_DEPOSIT_COSTS <- FALSE }

# ── Excluded banks (failed + GSIBs) ──
excluded_banks <- call_q %>%
  filter(period == BASELINE_MAIN, failed_bank == 1 | gsib == 1) %>% pull(idrssd)
btfp_loans <- btfp_loans_raw %>% filter(!rssd_id %in% excluded_banks)
dw_loans   <- dw_loans_raw   %>% filter(!rssd_id %in% excluded_banks)

# ── Phase assignment ──
assign_phase <- function(d) {
  case_when(d >= PHASES$start[1] & d <= PHASES$end[1] ~ "P0",
            d >= PHASES$start[2] & d <= PHASES$end[2] ~ "P1",
            d >= PHASES$start[3] & d <= PHASES$end[3] ~ "P2",
            d >= PHASES$start[4] & d <= PHASES$end[4] ~ "P3",
            TRUE ~ NA_character_)
}

cat(sprintf("DW loans:  %d transactions, %d unique banks\n", nrow(dw_loans), n_distinct(dw_loans$rssd_id)))
## DW loans:  9600 transactions, 1447 unique banks
cat(sprintf("BTFP loans: %d transactions, %d unique banks\n", nrow(btfp_loans), n_distinct(btfp_loans$rssd_id)))
## BTFP loans: 6695 transactions, 1316 unique banks
cat(sprintf("Call report: %d obs, %d banks, periods: %s\n",
  nrow(call_q), n_distinct(call_q$idrssd), paste(unique(call_q$period), collapse=", ")))
## Call report: 75989 obs, 5074 banks, periods: 2021Q1, 2021Q2, 2021Q3, 2021Q4, 2022Q1, 2022Q2, 2022Q3, 2022Q4, 2023Q1, 2023Q2, 2023Q3, 2023Q4, 2024Q1, 2024Q2, 2024Q3, 2024Q4

3 PART A: BORROWING BEHAVIOR NUANCES

Institutional background: A bank must have pre-pledged securities at the Discount Window on or before March 12, 2023 to be BTFP-eligible. The pre-pledged OMO-eligible collateral at DW consists of dw_treasury_agency + dw_mbs_agency (= dw_omo_eligible), which maps to BTFP-eligible collateral (btfp_treasury_sec, btfp_agency_cmo, btfp_agency_mbs, btfp_agency_debt).


3.1 A1. DW Loan-Level Nuances

3.1.1 A1.1 Basic Dimensions

cat("DW LOAN DATA DIMENSIONS\n")
## DW LOAN DATA DIMENSIONS
cat(sprintf("Total loan transactions:         %s\n", format(nrow(dw_loans), big.mark = ",")))
## Total loan transactions:         9,600
cat(sprintf("Unique borrower banks (rssd_id): %s\n", format(n_distinct(dw_loans$rssd_id), big.mark = ",")))
## Unique borrower banks (rssd_id): 1,447
cat(sprintf("Date range:      %s  to  %s\n", min(dw_loans$dw_loan_date), max(dw_loans$dw_loan_date)))
## Date range:      2023-01-03  to  2023-12-29
cat(sprintf("Maturity range:  %s  to  %s\n", min(dw_loans$dw_maturity_date, na.rm=T),
            max(dw_loans$dw_maturity_date, na.rm=T)))
## Maturity range:  2023-01-04  to  2024-03-27
cat(sprintf("Total $ lent:       $%s\n", format(sum(dw_loans$dw_loan_amount), big.mark = ",")))
## Total $ lent:       $752,850,804,950
cat(sprintf("Total $ collateral: $%s\n", format(sum(dw_loans$dw_total_collateral), big.mark = ",")))
## Total $ collateral: $4.239325e+12

3.1.2 A1.2 Credit Type Breakdown

ct <- dw_loans %>%
  group_by(`Credit Type` = dw_credit_type) %>%
  summarise(
    `N Loans` = n(),
    `N Banks` = n_distinct(rssd_id),
    `Total ($M)` = round(sum(dw_loan_amount) / 1e6, 0),
    `Mean ($M)` = round(mean(dw_loan_amount) / 1e6, 1),
    `Median ($M)` = round(median(dw_loan_amount) / 1e6, 1),
    `Mean Rate (%)` = round(mean(dw_interest_rate, na.rm = TRUE), 3),
    `Mean Term (d)` = round(mean(dw_term, na.rm = TRUE), 1),
    .groups = "drop"
  ) %>%
  mutate(`% of Total $` = round(100 * `Total ($M)` / sum(`Total ($M)`), 1))

kbl(ct, format = "html", caption = "DW Loans by Credit Type") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE) %>%
  footnote(general = "Primary credit is standard; secondary is for banks not qualifying for primary;
           seasonal for small agricultural/tourism banks. Trailing * indicates multiple same-day loans aggregated.")
DW Loans by Credit Type
Credit Type N Loans N Banks Total (\(M) </th> <th style="text-align:right;"> Mean (\)M) Median ($M) Mean Rate (%) Mean Term (d) % of Total $
Primary Credit 9433 1445 636783 67.5 7 5.176 4.6 84.6
Primary Credit* 167 58 116068 695.0 27 5.052 5.9 15.4
Note:
Primary credit is standard; secondary is for banks not qualifying for primary;
seasonal for small agricultural/tourism banks. Trailing * indicates multiple same-day loans aggregated.
save_kbl_latex(ct, "Table_DW_CreditType", caption = "DW Loans by Credit Type")
## Saved: Table_DW_CreditType.tex

3.1.3 A1.3 Term Structure

dw_loans <- dw_loans %>%
  mutate(
    term_bucket = cut(dw_term,
      breaks = c(0, 1, 7, 14, 28, 60, 90, 180, 365, Inf),
      labels = c("Overnight", "2-7d", "8-14d", "15-28d", "29-60d", "61-90d", "91-180d", "181-365d", ">365d"),
      right = TRUE)
  )

tb <- dw_loans %>%
  group_by(`Term Bucket` = term_bucket) %>%
  summarise(
    `N Loans` = n(),
    `N Banks` = n_distinct(rssd_id),
    `Total ($M)` = round(sum(dw_loan_amount) / 1e6, 0),
    `Mean Amount ($M)` = round(mean(dw_loan_amount) / 1e6, 1),
    .groups = "drop"
  ) %>%
  mutate(`% of Loans` = round(100 * `N Loans` / sum(`N Loans`), 1),
         `% of Amount` = round(100 * `Total ($M)` / sum(`Total ($M)`), 1))

kbl(tb, format = "html", caption = "DW Term Structure") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE) %>%
  footnote(general = "Overnight = 1-day. Longer terms during crisis signal genuine distress vs routine liquidity.")
DW Term Structure
Term Bucket N Loans N Banks Total (\(M) </th> <th style="text-align:right;"> Mean Amount (\)M) % of Loans % of Amount
Overnight 6677 1306 562108 84.2 69.6 74.7
2-7d 2126 285 162570 76.5 22.1 21.6
8-14d 192 64 3892 20.3 2.0 0.5
15-28d 266 67 15193 57.1 2.8 2.0
29-60d 158 44 3877 24.5 1.6 0.5
61-90d 170 47 4086 24.0 1.8 0.5
91-180d 11 4 1125 102.3 0.1 0.1
Note:
Overnight = 1-day. Longer terms during crisis signal genuine distress vs routine liquidity.
save_kbl_latex(tb, "Table_DW_TermStructure", caption = "DW Term Structure")
## Saved: Table_DW_TermStructure.tex
# Distribution
cat("\nDW Term Summary Statistics:\n")
## 
## DW Term Summary Statistics:
print(summary(dw_loans$dw_term))
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.000   1.000   1.000   4.626   3.000  92.000
cat(sprintf("Std Dev: %.1f\n", sd(dw_loans$dw_term, na.rm = TRUE)))
## Std Dev: 13.0

3.1.4 A1.4 Early Repayment (Pre-payment)

dw_loans <- dw_loans %>%
  mutate(
    held_days = as.integer(dw_repayment_date - dw_loan_date),
    early_repay_days = dw_term - held_days,
    prepaid = as.integer(early_repay_days > 0)
  )

n_prepaid <- sum(dw_loans$prepaid, na.rm = TRUE)
cat(sprintf("Loans prepaid (repaid before maturity): %d / %d  (%.1f%%)\n",
    n_prepaid, nrow(dw_loans), 100 * n_prepaid / nrow(dw_loans)))
## Loans prepaid (repaid before maturity): 456 / 9600  (4.8%)
cat(sprintf("Banks that prepaid at least once: %d\n",
    n_distinct(dw_loans$rssd_id[dw_loans$prepaid == 1])))
## Banks that prepaid at least once: 92
pp <- dw_loans %>% filter(prepaid == 1)
if (nrow(pp) > 0) {
  cat(sprintf("\nAmong prepaid DW loans:\n"))
  cat(sprintf("  Mean days early:   %.1f\n", mean(pp$early_repay_days, na.rm = TRUE)))
  cat(sprintf("  Median days early: %.1f\n", median(pp$early_repay_days, na.rm = TRUE)))
  cat(sprintf("  Max days early:    %d\n", max(pp$early_repay_days, na.rm = TRUE)))
  cat(sprintf("  Total $ prepaid:   $%s  (%.1f%% of total DW)\n",
      format(sum(pp$dw_loan_amount), big.mark = ","),
      100 * sum(pp$dw_loan_amount) / sum(dw_loans$dw_loan_amount)))
}
## 
## Among prepaid DW loans:
##   Mean days early:   25.6
##   Median days early: 14.0
##   Max days early:    89
##   Total $ prepaid:   $21,894,915,711  (2.9% of total DW)
# Prepayment by term bucket
pp_by_term <- dw_loans %>%
  group_by(term_bucket) %>%
  summarise(
    N = n(),
    `Prepaid %` = round(100 * mean(prepaid, na.rm = TRUE), 1),
    `Mean Days Early` = round(mean(early_repay_days[prepaid == 1], na.rm = TRUE), 1),
    .groups = "drop"
  )
kbl(pp_by_term, format = "html", caption = "DW Pre-payment by Term Bucket") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE)
DW Pre-payment by Term Bucket
term_bucket N Prepaid % Mean Days Early
Overnight 6677 0.0 NaN
2-7d 2126 3.2 2.4
8-14d 192 20.3 5.2
15-28d 266 45.1 11.7
29-60d 158 62.7 19.3
61-90d 170 73.5 62.5
91-180d 11 36.4 44.8

3.1.5 A1.5 Repeat Borrowers (Capacity Re-use)

dw_bank <- dw_loans %>%
  group_by(rssd_id) %>%
  summarise(
    n_loans = n(),
    total_borrowed = sum(dw_loan_amount),
    first_loan = min(dw_loan_date),
    last_loan = max(dw_loan_date),
    n_distinct_dates = n_distinct(dw_loan_date),
    mean_loan = mean(dw_loan_amount),
    .groups = "drop"
  ) %>%
  mutate(span_days = as.integer(last_loan - first_loan))

# Frequency table
freq <- dw_bank %>% count(n_loans, name = "n_banks") %>% arrange(n_loans)
kbl(freq %>% head(15), format = "html", col.names = c("# Loans", "# Banks"),
    caption = "DW: Number of Loans per Bank") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE)
DW: Number of Loans per Bank
# Loans # Banks
1 982
2 163
3 54
4 59
5 20
6 20
7 9
8 6
9 6
10 3
11 4
12 8
13 3
14 5
15 2
cat(sprintf("\nDistribution of # loans per bank:\n"))
## 
## Distribution of # loans per bank:
print(summary(dw_bank$n_loans))
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.000   1.000   1.000   6.634   2.000 250.000
multi <- dw_bank %>% filter(n_loans > 1)
cat(sprintf("\nMulti-loan banks: %d / %d (%.1f%%)\n",
    nrow(multi), nrow(dw_bank), 100 * nrow(multi) / nrow(dw_bank)))
## 
## Multi-loan banks: 465 / 1447 (32.1%)
cat(sprintf("Mean span (first to last loan): %.1f days\n", mean(multi$span_days)))
## Mean span (first to last loan): 195.4 days
cat(sprintf("Mean total borrowed per repeat bank: $%s\n", format(mean(multi$total_borrowed), big.mark = ",")))
## Mean total borrowed per repeat bank: $1,615,115,965
cat(sprintf("Median total borrowed per repeat bank: $%s\n", format(median(multi$total_borrowed), big.mark = ",")))
## Median total borrowed per repeat bank: $9,600,000

3.1.6 A1.6 Federal Reserve District

dw_dist <- dw_loans %>%
  group_by(dw_fed_district) %>%
  summarise(
    `N Loans` = n(),
    `N Banks` = n_distinct(rssd_id),
    `Total ($M)` = round(sum(dw_loan_amount) / 1e6, 0),
    `Mean Rate (%)` = round(mean(dw_interest_rate, na.rm = TRUE), 3),
    `Mean Term (d)` = round(mean(dw_term, na.rm = TRUE), 1),
    .groups = "drop"
  ) %>%
  mutate(District = dist_names[as.character(dw_fed_district)],
         `% of Amount` = round(100 * `Total ($M)` / sum(`Total ($M)`), 1)) %>%
  select(dw_fed_district, District, everything())

kbl(dw_dist, format = "html", caption = "DW Lending by Federal Reserve District") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
DW Lending by Federal Reserve District
dw_fed_district District N Loans N Banks Total ($M) Mean Rate (%) Mean Term (d) % of Amount
Atlanta (6) NA 513 154 5594 5.186 18.1 0.7
Boston (1) NA 571 107 12777 5.157 8.6 1.7
Chicago (7) NA 1848 220 49321 5.187 2.4 6.6
Cleveland (4) NA 214 67 472 5.246 2.5 0.1
Dallas (11) NA 958 125 25742 5.139 4.9 3.4
Kansas City (10) NA 853 170 50783 5.125 4.1 6.7
Minneapolis (9) NA 999 96 15028 5.163 2.9 2.0
New York (2) NA 334 67 17935 5.112 2.6 2.4
Philadelphia (3) NA 698 59 23968 5.168 1.6 3.2
Richmond (5) NA 582 101 10927 5.131 9.7 1.5
San Francisco (12) NA 1434 151 529631 5.193 2.4 70.3
St. Louis (8) NA 596 130 10674 5.289 4.8 1.4
save_kbl_latex(dw_dist, "Table_DW_ByDistrict", caption = "DW Lending by Federal Reserve District")
## Saved: Table_DW_ByDistrict.tex

3.1.7 A1.7 Interest Rate

cat("DW Interest Rate Distribution:\n")
## DW Interest Rate Distribution:
print(summary(dw_loans$dw_interest_rate))
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   4.500   5.000   5.250   5.174   5.500   5.500
cat(sprintf("Std Dev: %.4f\n", sd(dw_loans$dw_interest_rate, na.rm = TRUE)))
## Std Dev: 0.3434
# Rate over time (monthly)
dw_rate_m <- dw_loans %>%
  mutate(loan_month = floor_date(dw_loan_date, "month")) %>%
  group_by(loan_month) %>%
  summarise(
    mean_rate = round(mean(dw_interest_rate, na.rm = TRUE), 3),
    median_rate = round(median(dw_interest_rate, na.rm = TRUE), 3),
    n_loans = n(),
    total_m = round(sum(dw_loan_amount) / 1e6, 0),
    .groups = "drop"
  )

kbl(dw_rate_m, format = "html", caption = "DW Rate by Month",
    col.names = c("Month", "Mean Rate (%)", "Median Rate (%)", "N Loans", "Total ($M)")) %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE)
DW Rate by Month
Month Mean Rate (%) Median Rate (%) N Loans Total ($M)
2023-01-01 4.500 4.50 877 32157
2023-02-01 4.733 4.75 693 32630
2023-03-01 4.821 4.75 1074 480277
2023-04-01 5.000 5.00 748 60333
2023-05-01 5.212 5.25 858 41205
2023-06-01 5.250 5.25 867 22335
2023-07-01 5.280 5.25 721 18968
2023-08-01 5.500 5.50 774 15036
2023-09-01 5.500 5.50 825 15865
2023-10-01 5.500 5.50 841 14928
2023-11-01 5.500 5.50 652 9615
2023-12-01 5.500 5.50 670 9502
# Rate vs term
p_rate_term <- ggplot(dw_loans, aes(x = dw_term, y = dw_interest_rate)) +
  geom_point(alpha = 0.2, size = 1, color = "#E53935") +
  geom_smooth(method = "loess", color = "black", se = TRUE) +
  labs(title = "DW: Interest Rate vs Contractual Term",
       x = "Term (days)", y = "Interest Rate (%)") + theme_gp
print(p_rate_term)

save_figure(p_rate_term, "Fig_DW_Rate_vs_Term", width = 10, height = 6)

3.1.8 A1.8 Other Outstanding (Concurrent Borrowing)

has_other <- dw_loans$dw_other_outstanding > 0

cat(sprintf("Loans with other outstanding > 0: %d / %d (%.1f%%)\n",
    sum(has_other), nrow(dw_loans), 100 * mean(has_other)))
## Loans with other outstanding > 0: 1164 / 9600 (12.1%)
cat(sprintf("Banks with other outstanding > 0: %d\n",
    n_distinct(dw_loans$rssd_id[has_other])))
## Banks with other outstanding > 0: 97
if (sum(has_other) > 0) {
  oth <- dw_loans %>% filter(dw_other_outstanding > 0)
  cat(sprintf("\nAmong those with other outstanding:\n"))
  cat(sprintf("  Mean other outstanding:   $%s\n", format(round(mean(oth$dw_other_outstanding)), big.mark = ",")))
  cat(sprintf("  Mean total outstanding:   $%s\n", format(round(mean(oth$dw_total_outstanding)), big.mark = ",")))
  cat(sprintf("  Ratio new_loan/total_out: %.3f\n",
      mean(oth$dw_loan_amount / oth$dw_total_outstanding, na.rm = TRUE)))
}
## 
## Among those with other outstanding:
##   Mean other outstanding:   $82,143,634
##   Mean total outstanding:   $124,243,177
##   Ratio new_loan/total_out: 0.306
# Histogram: other outstanding relative to current loan
p_oth <- ggplot(dw_loans %>% filter(dw_other_outstanding > 0),
       aes(x = dw_other_outstanding / 1e6)) +
  geom_histogram(bins = 50, fill = "#E53935", alpha = 0.7) +
  labs(title = "DW: Distribution of Other Outstanding Loans (when > 0)",
       subtitle = "Each point is a loan-transaction; shows concurrent DW exposure",
       x = "Other Outstanding ($M)", y = "Count") + theme_gp
print(p_oth)

3.1.9 A1.9 Collateral Composition (Pre-Pledged)

dw_coll_cols <- c("dw_comm_loans", "dw_res_mortgages", "dw_cre_loans", "dw_consumer_loans",
  "dw_treasury_agency", "dw_municipal_sec", "dw_corporate_instruments",
  "dw_mbs_agency", "dw_mbs_other", "dw_abs", "dw_intl_securities",
  "dw_tdf_deposits", "dw_other_collateral")

coll_agg <- map_dfr(dw_coll_cols, function(col) {
  tibble(
    `Collateral Type` = str_replace(col, "dw_", ""),
    Total = sum(dw_loans[[col]], na.rm = TRUE),
    Mean = mean(dw_loans[[col]], na.rm = TRUE),
    N_Nonzero = sum(dw_loans[[col]] > 0, na.rm = TRUE)
  )
}) %>%
  mutate(`% of Total Coll` = round(100 * Total / sum(dw_loans$dw_total_collateral, na.rm = TRUE), 1),
         `% Loans Pledging` = round(100 * N_Nonzero / nrow(dw_loans), 1)) %>%
  arrange(desc(`% of Total Coll`))

kbl(coll_agg, format = "html", caption = "DW Collateral Composition",
    digits = c(0, 0, 0, 0, 1, 1)) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE) %>%
  footnote(general = "OMO-eligible = treasury_agency + mbs_agency. These are what qualify banks for BTFP.")
DW Collateral Composition
Collateral Type Total Mean N_Nonzero % of Total Coll % Loans Pledging
comm_loans 1.614937e+12 168222575 3329 38.1 34.7
cre_loans 8.683030e+11 90448225 2736 20.5 28.5
consumer_loans 6.172027e+11 64291947 1400 14.6 14.6
mbs_agency 4.454554e+11 46401608 3010 10.5 31.4
municipal_sec 2.867713e+11 29872012 2961 6.8 30.8
res_mortgages 1.503206e+11 15658395 965 3.5 10.1
abs 1.033801e+11 10768761 1092 2.4 11.4
treasury_agency 9.023667e+10 9399653 3070 2.1 32.0
corporate_instruments 3.532671e+10 3679865 1519 0.8 15.8
mbs_other 2.436777e+10 2538309 513 0.6 5.3
intl_securities 3.023548e+09 314953 5 0.1 0.1
tdf_deposits 0.000000e+00 0 0 0.0 0.0
other_collateral 0.000000e+00 0 0 0.0 0.0
Note:
OMO-eligible = treasury_agency + mbs_agency. These are what qualify banks for BTFP.
save_kbl_latex(coll_agg, "Table_DW_CollateralComposition", caption = "DW Collateral Composition")
## Saved: Table_DW_CollateralComposition.tex
# OMO-eligible summary
omo_total <- sum(dw_loans$dw_treasury_agency, na.rm=T) + sum(dw_loans$dw_mbs_agency, na.rm=T)
total_coll <- sum(dw_loans$dw_total_collateral, na.rm = TRUE)
cat(sprintf("\nOMO-eligible collateral (treasury_agency + mbs_agency):\n"))
## 
## OMO-eligible collateral (treasury_agency + mbs_agency):
cat(sprintf("  Total: $%s  (%.1f%% of total collateral)\n",
    format(omo_total, big.mark = ","), 100 * omo_total / total_coll))
##   Total: $535,692,100,737  (12.6% of total collateral)
cat(sprintf("  This is what qualified banks for BTFP (pre-pledged on or before March 12)\n"))
##   This is what qualified banks for BTFP (pre-pledged on or before March 12)
# Non-OMO-eligible breakdown
non_omo_cols <- c("dw_comm_loans", "dw_res_mortgages", "dw_cre_loans", "dw_consumer_loans",
  "dw_municipal_sec", "dw_corporate_instruments", "dw_mbs_other", "dw_abs",
  "dw_intl_securities", "dw_tdf_deposits", "dw_other_collateral")
non_omo_total <- sum(sapply(non_omo_cols, function(c) sum(dw_loans[[c]], na.rm=T)))
cat(sprintf("  Non-OMO-eligible: $%s  (%.1f%%)\n",
    format(non_omo_total, big.mark = ","), 100 * non_omo_total / total_coll))
##   Non-OMO-eligible: $3.703632e+12  (87.4%)

3.1.10 A1.10 Loan-to-Collateral Ratios

dw_loans <- dw_loans %>%
  mutate(
    ltc_total = safe_div(dw_loan_amount, dw_total_collateral, NA),
    ltc_omo   = safe_div(dw_loan_amount, dw_omo_eligible, NA)
  )

cat("Loan / Total Collateral:\n")
## Loan / Total Collateral:
print(summary(dw_loans$ltc_total))
##      Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
## 2.000e-08 3.341e-02 1.439e-01 2.280e-01 3.585e-01 1.000e+00
cat("\nLoan / OMO-Eligible Collateral (when OMO > 0):\n")
## 
## Loan / OMO-Eligible Collateral (when OMO > 0):
print(summary(dw_loans$ltc_omo[dw_loans$dw_omo_eligible > 0]))
##      Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
## 0.000e+00 8.980e-02 3.030e-01 1.974e+02 6.810e-01 1.585e+04
# Plot
p_ltc <- ggplot(dw_loans %>% filter(!is.na(ltc_total)),
       aes(x = ltc_total)) +
  geom_histogram(bins = 80, fill = "#E53935", alpha = 0.7) +
  geom_vline(xintercept = 1, linetype = "dashed", color = "black") +
  coord_cartesian(xlim = c(0, 2)) +
  labs(title = "DW Loan-to-Collateral Ratio", x = "Loan / Total Collateral", y = "Count") + theme_gp
print(p_ltc)


3.2 A2. BTFP Loan-Level Nuances

3.2.1 A2.1 Basic Dimensions

cat("BTFP LOAN DATA DIMENSIONS\n")
## BTFP LOAN DATA DIMENSIONS
cat(sprintf("Total loan transactions:         %s\n", format(nrow(btfp_loans), big.mark = ",")))
## Total loan transactions:         6,695
cat(sprintf("Unique borrower banks (rssd_id): %s\n", format(n_distinct(btfp_loans$rssd_id), big.mark = ",")))
## Unique borrower banks (rssd_id): 1,316
cat(sprintf("Date range:      %s  to  %s\n", min(btfp_loans$btfp_loan_date), max(btfp_loans$btfp_loan_date)))
## Date range:      2023-03-13  to  2024-03-11
cat(sprintf("Maturity range:  %s  to  %s\n", min(btfp_loans$btfp_maturity_date, na.rm=T),
            max(btfp_loans$btfp_maturity_date, na.rm=T)))
## Maturity range:  2023-03-14  to  2025-03-11
cat(sprintf("Total $ lent:       $%s\n", format(sum(btfp_loans$btfp_loan_amount), big.mark = ",")))
## Total $ lent:       $4.10358e+11
cat(sprintf("Total $ collateral: $%s\n", format(sum(btfp_loans$btfp_total_collateral), big.mark = ",")))
## Total $ collateral: $1.28049e+12

3.2.2 A2.2 Collateral Composition

btfp_coll_cols <- c("btfp_treasury_sec", "btfp_agency_cmo", "btfp_agency_mbs", "btfp_agency_debt")

btfp_coll_agg <- map_dfr(btfp_coll_cols, function(col) {
  tibble(
    `Collateral Type` = str_replace(col, "btfp_", ""),
    Total = sum(btfp_loans[[col]], na.rm = TRUE),
    Mean = mean(btfp_loans[[col]], na.rm = TRUE),
    N_Nonzero = sum(btfp_loans[[col]] > 0, na.rm = TRUE)
  )
}) %>%
  mutate(`% of Total` = round(100 * Total / sum(btfp_loans$btfp_total_collateral, na.rm = TRUE), 1),
         `% Loans With` = round(100 * N_Nonzero / nrow(btfp_loans), 1)) %>%
  arrange(desc(`% of Total`))

kbl(btfp_coll_agg, format = "html",
    caption = "BTFP Collateral Composition (Treasury + Agency only, valued at PAR)") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE) %>%
  footnote(general = "BTFP accepted ONLY Treasury, Agency CMO, Agency MBS, and Agency Debt.
           These are the SAME securities pre-pledged at DW as dw_treasury_agency + dw_mbs_agency.
           BTFP lends at PAR (face) value, not market value -- the subsidy equals the MTM loss.")
BTFP Collateral Composition (Treasury + Agency only, valued at PAR)
Collateral Type Total Mean N_Nonzero % of Total % Loans With
agency_mbs 641725476088 95851453 4399 50.1 65.7
agency_cmo 302775455877 45224116 3087 23.6 46.1
treasury_sec 202464164442 30241100 2821 15.8 42.1
agency_debt 133524816817 19943961 3381 10.4 50.5
Note:
BTFP accepted ONLY Treasury, Agency CMO, Agency MBS, and Agency Debt.
These are the SAME securities pre-pledged at DW as dw_treasury_agency + dw_mbs_agency.
BTFP lends at PAR (face) value, not market value – the subsidy equals the MTM loss.
save_kbl_latex(btfp_coll_agg, "Table_BTFP_CollateralComposition",
  caption = "BTFP Collateral Composition")
## Saved: Table_BTFP_CollateralComposition.tex

3.2.3 A2.3 Term and Early Repayment

btfp_loans <- btfp_loans %>%
  mutate(
    held_days = as.integer(btfp_repayment_date - btfp_loan_date),
    early_repay_days = btfp_term - held_days,
    prepaid = as.integer(early_repay_days > 0)
  )

cat("BTFP Contractual Term (days):\n")
## BTFP Contractual Term (days):
print(summary(btfp_loans$btfp_term))
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##     1.0   364.0   365.0   311.1   366.0   369.0
n_pp <- sum(btfp_loans$prepaid, na.rm = TRUE)
cat(sprintf("\nPrepaid (repaid before maturity): %d / %d (%.1f%%)\n",
    n_pp, nrow(btfp_loans), 100 * n_pp / nrow(btfp_loans)))
## 
## Prepaid (repaid before maturity): 5321 / 6695 (79.5%)
cat(sprintf("Banks that prepaid: %d\n", n_distinct(btfp_loans$rssd_id[btfp_loans$prepaid == 1])))
## Banks that prepaid: 1023
if (n_pp > 0) {
  pp <- btfp_loans %>% filter(prepaid == 1)
  cat(sprintf("  Mean days early:   %.1f\n", mean(pp$early_repay_days, na.rm = TRUE)))
  cat(sprintf("  Median days early: %.1f\n", median(pp$early_repay_days, na.rm = TRUE)))
  cat(sprintf("  Total $ prepaid:   $%s (%.1f%% of total BTFP $)\n",
      format(sum(pp$btfp_loan_amount), big.mark = ","),
      100 * sum(pp$btfp_loan_amount) / sum(btfp_loans$btfp_loan_amount)))
}
##   Mean days early:   260.0
##   Median days early: 350.0
##   Total $ prepaid:   $353,702,839,293 (86.2% of total BTFP $)
# Term distribution
btfp_term_tbl <- btfp_loans %>%
  mutate(term_bucket = cut(btfp_term,
    breaks = c(0, 30, 90, 180, 350, 366, Inf),
    labels = c("<30d", "30-90d", "91-180d", "181-350d", "~365d", ">365d"))) %>%
  group_by(`Term Bucket` = term_bucket) %>%
  summarise(N = n(), `Total ($M)` = round(sum(btfp_loan_amount)/1e6, 0),
            `Prepaid %` = round(100*mean(prepaid, na.rm=T), 1),
            `Mean Held (d)` = round(mean(held_days, na.rm=T), 1), .groups = "drop") %>%
  mutate(`% of Loans` = round(100 * N / sum(N), 1))

kbl(btfp_term_tbl, format = "html", caption = "BTFP Term Distribution") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE) %>%
  footnote(general = "Most BTFP loans are ~365 days (precautionary). Early repay = liquidity need resolved.")
BTFP Term Distribution
Term Bucket N Total ($M) Prepaid % Mean Held (d) % of Loans
<30d 868 23273 12.4 3.6 13.0
30-90d 82 2959 52.4 31.8 1.2
91-180d 53 1155 60.4 63.2 0.8
181-350d 100 7297 73.0 130.4 1.5
~365d 4931 350927 91.2 121.3 73.7
>365d 661 24746 85.9 119.8 9.9
Note:
Most BTFP loans are ~365 days (precautionary). Early repay = liquidity need resolved.

3.2.4 A2.4 Repeat Borrowers

btfp_bank <- btfp_loans %>%
  group_by(rssd_id) %>%
  summarise(
    n_loans = n(),
    total_borrowed = sum(btfp_loan_amount),
    first_loan = min(btfp_loan_date),
    last_loan = max(btfp_loan_date),
    n_distinct_dates = n_distinct(btfp_loan_date),
    .groups = "drop"
  ) %>%
  mutate(span_days = as.integer(last_loan - first_loan))

freq_b <- btfp_bank %>% count(n_loans, name = "n_banks") %>% arrange(n_loans)
kbl(freq_b %>% head(15), format = "html", col.names = c("# Loans", "# Banks"),
    caption = "BTFP: Number of Loans per Bank") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE)
BTFP: Number of Loans per Bank
# Loans # Banks
1 401
2 229
3 156
4 98
5 74
6 67
7 44
8 40
9 27
10 21
11 21
12 21
13 16
14 11
15 14
cat(sprintf("\nDistribution of # loans per bank:\n"))
## 
## Distribution of # loans per bank:
print(summary(btfp_bank$n_loans))
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.000   1.000   3.000   5.087   6.000  97.000
multi_b <- btfp_bank %>% filter(n_loans > 1)
cat(sprintf("\nMulti-loan banks: %d / %d (%.1f%%)\n",
    nrow(multi_b), nrow(btfp_bank), 100 * nrow(multi_b) / nrow(btfp_bank)))
## 
## Multi-loan banks: 915 / 1316 (69.5%)
if (nrow(multi_b) > 0) {
  cat(sprintf("Mean span (first to last): %.1f days\n", mean(multi_b$span_days)))
  cat(sprintf("Mean total borrowed per repeat bank: $%s\n",
      format(mean(multi_b$total_borrowed), big.mark = ",")))
}
## Mean span (first to last): 183.6 days
## Mean total borrowed per repeat bank: $437,817,148

3.2.5 A2.5 Federal Reserve District and Interest Rate

btfp_dist <- btfp_loans %>%
  group_by(btfp_fed_district) %>%
  summarise(
    `N Loans` = n(),
    `N Banks` = n_distinct(rssd_id),
    `Total ($M)` = round(sum(btfp_loan_amount) / 1e6, 0),
    `Mean Rate (%)` = round(mean(btfp_interest_rate, na.rm = TRUE), 3),
    `Mean Term (d)` = round(mean(btfp_term, na.rm = TRUE), 0),
    .groups = "drop"
  ) %>%
  mutate(District = dist_names[as.character(btfp_fed_district)],
         `% of Amount` = round(100 * `Total ($M)` / sum(`Total ($M)`), 1)) %>%
  select(btfp_fed_district, District, everything())

kbl(btfp_dist, format = "html", caption = "BTFP by Federal Reserve District") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
BTFP by Federal Reserve District
btfp_fed_district District N Loans N Banks Total ($M) Mean Rate (%) Mean Term (d) % of Amount
Atlanta NA 563 134 16237 5.053 311 4.0
Boston NA 508 75 17437 4.958 331 4.2
Chicago NA 902 179 21056 5.005 344 5.1
Cleveland NA 269 62 11924 4.949 329 2.9
Dallas NA 947 166 78828 5.082 276 19.2
Kansas City NA 868 194 36748 4.979 335 9.0
Minneapolis NA 754 147 14290 5.017 312 3.5
New York NA 231 62 9336 5.019 318 2.3
Philadelphia NA 232 34 13697 5.022 214 3.3
Richmond NA 289 62 11740 5.004 297 2.9
San Francisco NA 536 94 140223 5.009 299 34.2
St. Louis NA 596 107 38842 5.066 311 9.5
save_kbl_latex(btfp_dist, "Table_BTFP_ByDistrict", caption = "BTFP by Federal Reserve District")
## Saved: Table_BTFP_ByDistrict.tex
cat("\nBTFP Interest Rate Distribution:\n")
## 
## BTFP Interest Rate Distribution:
print(summary(btfp_loans$btfp_interest_rate))
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   4.370   4.810   4.900   5.019   5.310   5.590
# Rate over time
btfp_rate_m <- btfp_loans %>%
  mutate(loan_month = floor_date(btfp_loan_date, "month")) %>%
  group_by(loan_month) %>%
  summarise(
    mean_rate = round(mean(btfp_interest_rate, na.rm = TRUE), 3),
    n_loans = n(),
    total_m = round(sum(btfp_loan_amount) / 1e6, 0),
    .groups = "drop"
  )

kbl(btfp_rate_m, format = "html", caption = "BTFP Rate and Volume by Month",
    col.names = c("Month", "Mean Rate (%)", "N Loans", "Total ($M)")) %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE)
BTFP Rate and Volume by Month
Month Mean Rate (%) N Loans Total ($M)
2023-03-01 4.564 520 74373
2023-04-01 4.801 527 46484
2023-05-01 4.842 759 23612
2023-06-01 5.275 350 11025
2023-07-01 5.432 250 5255
2023-08-01 5.463 235 4396
2023-09-01 5.538 191 1859
2023-10-01 5.488 200 5430
2023-11-01 5.289 667 28269
2023-12-01 4.936 1519 108818
2024-01-01 4.850 1132 88102
2024-02-01 5.400 147 2440
2024-03-01 5.400 198 10295

3.2.6 A2.6 Loan-to-Collateral (Par Value Subsidy)

btfp_loans <- btfp_loans %>%
  mutate(ltc = safe_div(btfp_loan_amount, btfp_total_collateral, NA))

cat("BTFP Loan/Collateral Ratio:\n")
## BTFP Loan/Collateral Ratio:
cat("(BTFP lends at PAR value, so ratio near 1 means borrowing close to face value of pledged securities)\n\n")
## (BTFP lends at PAR value, so ratio near 1 means borrowing close to face value of pledged securities)
print(summary(btfp_loans$ltc))
##      Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
## 6.000e-08 1.492e-01 3.717e-01 4.513e-01 8.000e-01 1.000e+00
# By collateral type
for (col in btfp_coll_cols) {
  mask <- btfp_loans[[col]] > 0
  if (sum(mask) > 0) {
    ratio <- btfp_loans$btfp_loan_amount[mask] / btfp_loans[[col]][mask]
    cat(sprintf("Loan / %s: mean=%.3f, median=%.3f (N=%d)\n",
        str_replace(col, "btfp_", ""), mean(ratio, na.rm=T), median(ratio, na.rm=T), sum(mask)))
  }
}
## Loan / treasury_sec: mean=2.197, median=0.950 (N=2821)
## Loan / agency_cmo: mean=22.456, median=1.196 (N=3087)
## Loan / agency_mbs: mean=38.542, median=0.683 (N=4399)
## Loan / agency_debt: mean=3.918, median=0.998 (N=3381)

3.3 A3. Cross-Facility: DW vs BTFP Overlap

3.3.1 A3.1 Overlap and Substitution

dw_banks   <- unique(dw_loans$rssd_id)
btfp_banks <- unique(btfp_loans$rssd_id)
both_banks <- intersect(dw_banks, btfp_banks)
dw_only_banks   <- setdiff(dw_banks, btfp_banks)
btfp_only_banks <- setdiff(btfp_banks, dw_banks)

venn_tbl <- tibble(
  Category = c("DW Only", "BTFP Only", "Both DW + BTFP", "Total Unique"),
  `N Banks` = c(length(dw_only_banks), length(btfp_only_banks), length(both_banks),
                length(union(dw_banks, btfp_banks)))
)

kbl(venn_tbl, format = "html", caption = "DW vs BTFP Facility Usage") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE)
DW vs BTFP Facility Usage
Category N Banks
DW Only 864
BTFP Only 733
Both DW + BTFP 583
Total Unique 2180
# For 'both' banks: who borrowed first?
if (length(both_banks) > 0) {
  dw_first <- dw_loans %>% filter(rssd_id %in% both_banks) %>%
    group_by(rssd_id) %>% summarise(dw_first = min(dw_loan_date), .groups = "drop")
  btfp_first <- btfp_loans %>% filter(rssd_id %in% both_banks) %>%
    group_by(rssd_id) %>% summarise(btfp_first = min(btfp_loan_date), .groups = "drop")

  timing <- inner_join(dw_first, btfp_first, by = "rssd_id") %>%
    mutate(order = case_when(
      dw_first < btfp_first ~ "DW first",
      dw_first == btfp_first ~ "Same day",
      dw_first > btfp_first ~ "BTFP first"))

  cat("\nAmong 'both' banks -- which facility first?\n")
  print(table(timing$order))
  cat(sprintf("\nMedian gap (BTFP date - DW date): %.0f days\n",
      median(as.integer(timing$btfp_first - timing$dw_first))))
}
## 
## Among 'both' banks -- which facility first?
## 
## BTFP first   DW first   Same day 
##        172        351         60 
## 
## Median gap (BTFP date - DW date): 16 days

3.3.2 A3.2 Acute Crisis Period Comparison

dw_acute   <- dw_loans %>% filter(dw_loan_date >= ACUTE_START, dw_loan_date <= ACUTE_END)
btfp_acute <- btfp_loans %>% filter(btfp_loan_date >= ACUTE_START, btfp_loan_date <= ACUTE_END)

comp <- tibble(
  Metric = c("N Loan Transactions", "N Unique Banks", "Total ($M)",
             "Mean $ per Loan ($M)", "Mean Rate (%)", "Mean Term (days)"),
  DW = c(nrow(dw_acute), n_distinct(dw_acute$rssd_id),
         round(sum(dw_acute$dw_loan_amount)/1e6, 0),
         round(mean(dw_acute$dw_loan_amount)/1e6, 1),
         round(mean(dw_acute$dw_interest_rate, na.rm=T), 3),
         round(mean(dw_acute$dw_term, na.rm=T), 1)),
  BTFP = c(nrow(btfp_acute), n_distinct(btfp_acute$rssd_id),
           round(sum(btfp_acute$btfp_loan_amount)/1e6, 0),
           round(mean(btfp_acute$btfp_loan_amount)/1e6, 1),
           round(mean(btfp_acute$btfp_interest_rate, na.rm=T), 3),
           round(mean(btfp_acute$btfp_term, na.rm=T), 1))
)

kbl(comp, format = "html",
    caption = sprintf("Acute Crisis Period: %s to %s", ACUTE_START, ACUTE_END)) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
Acute Crisis Period: 2023-03-13 to 2023-04-30
Metric DW BTFP
N Loan Transactions 1527.000 1047.000
N Unique Banks 404.000 475.000
Total ($M) 521052.000 120857.000
Mean $ per Loan ($M) 341.200 115.400
Mean Rate (%) 4.923 4.683
Mean Term (days) 4.500 313.000
save_kbl_latex(comp, "Table_AcuteDW_vs_BTFP",
  caption = sprintf("DW vs BTFP: %s to %s", ACUTE_START, ACUTE_END))
## Saved: Table_AcuteDW_vs_BTFP.tex

3.3.3 A3.3 Pre-Pledged Collateral and BTFP Eligibility

cat("KEY INSTITUTIONAL FACT:\n")
## KEY INSTITUTIONAL FACT:
cat("Banks must have pre-pledged securities at the DW on or before March 12, 2023\n")
## Banks must have pre-pledged securities at the DW on or before March 12, 2023
cat("to be BTFP-eligible. The pre-pledged OMO-eligible collateral at DW consists of:\n")
## to be BTFP-eligible. The pre-pledged OMO-eligible collateral at DW consists of:
cat("  dw_treasury_agency  -> maps to btfp_treasury_sec + btfp_agency_debt\n")
##   dw_treasury_agency  -> maps to btfp_treasury_sec + btfp_agency_debt
cat("  dw_mbs_agency       -> maps to btfp_agency_mbs + btfp_agency_cmo\n")
##   dw_mbs_agency       -> maps to btfp_agency_mbs + btfp_agency_cmo
cat("  Together: dw_omo_eligible = dw_treasury_agency + dw_mbs_agency\n\n")
##   Together: dw_omo_eligible = dw_treasury_agency + dw_mbs_agency
# DW loans on or before March 12 with OMO-eligible collateral
dw_pre <- dw_loans %>% filter(dw_loan_date <= as.Date("2023-03-12"))
dw_pre_omo_banks <- dw_pre %>% filter(dw_omo_eligible > 0) %>% pull(rssd_id) %>% unique()

cat(sprintf("DW borrowers before March 13 with OMO-eligible > 0: %d\n", length(dw_pre_omo_banks)))
## DW borrowers before March 13 with OMO-eligible > 0: 112
cat(sprintf("BTFP borrowers (all time):                         %d\n", length(btfp_banks)))
## BTFP borrowers (all time):                         1316
cat(sprintf("BTFP borrowers also in pre-Mar-13 DW OMO set:      %d\n",
    length(intersect(btfp_banks, dw_pre_omo_banks))))
## BTFP borrowers also in pre-Mar-13 DW OMO set:      57
cat(sprintf("BTFP borrowers NOT in pre-Mar-13 DW borrower set:  %d\n",
    length(setdiff(btfp_banks, dw_pre_omo_banks))))
## BTFP borrowers NOT in pre-Mar-13 DW borrower set:  1259
cat("\nNote: Banks can pre-pledge collateral at DW without borrowing.\n")
## 
## Note: Banks can pre-pledge collateral at DW without borrowing.
cat("The DW data only records actual borrowers, so some BTFP banks may have\n")
## The DW data only records actual borrowers, so some BTFP banks may have
cat("pre-pledged without appearing as DW borrowers.\n")
## pre-pledged without appearing as DW borrowers.

3.4 A4. Time-Series Dynamics

3.4.1 A4.1 Daily Lending Volume

daily_dw <- dw_loans %>%
  group_by(date = dw_loan_date) %>%
  summarise(n = n(), total_m = sum(dw_loan_amount)/1e6,
            n_banks = n_distinct(rssd_id), .groups = "drop") %>%
  mutate(facility = "DW")

daily_btfp <- btfp_loans %>%
  group_by(date = btfp_loan_date) %>%
  summarise(n = n(), total_m = sum(btfp_loan_amount)/1e6,
            n_banks = n_distinct(rssd_id), .groups = "drop") %>%
  mutate(facility = "BTFP")

daily <- bind_rows(daily_dw, daily_btfp)

# Top 5 days
cat("Top 5 DW days by total amount:\n")
## Top 5 DW days by total amount:
print(daily_dw %>% arrange(desc(total_m)) %>% head(5))
## # A tibble: 5 × 5
##   date           n total_m n_banks facility
##   <date>     <int>   <dbl>   <int> <chr>   
## 1 2023-03-13    81  43827.      79 DW      
## 2 2023-03-15    59  43066.      58 DW      
## 3 2023-03-16    58  41588.      57 DW      
## 4 2023-03-14    70  41322.      69 DW      
## 5 2023-03-17    50  41199.      50 DW
cat("\nTop 5 BTFP days by total amount:\n")
## 
## Top 5 BTFP days by total amount:
print(daily_btfp %>% arrange(desc(total_m)) %>% head(5))
## # A tibble: 5 × 5
##   date           n total_m n_banks facility
##   <date>     <int>   <dbl>   <int> <chr>   
## 1 2024-01-16   348  37705.     319 BTFP    
## 2 2024-01-12   254  27309.     237 BTFP    
## 3 2023-12-14   292  20843.     265 BTFP    
## 4 2023-03-20    84  19235.      77 BTFP    
## 5 2023-03-24   133  16339.     119 BTFP
# Plots
p1 <- ggplot(daily, aes(x = date, y = total_m, fill = facility)) +
  geom_col(position = "stack", alpha = 0.8) +
  geom_vline(xintercept = as.Date("2023-03-10"), linetype = "dashed", color = "#E53935") +
  geom_vline(xintercept = as.Date("2023-03-13"), linetype = "dashed", color = "#1565C0") +
  geom_vline(xintercept = as.Date("2023-05-01"), linetype = "dashed", color = "#C62828") +
  annotate("text", x = as.Date("2023-03-10"), y = Inf, vjust = 2, label = "SVB", size = 3) +
  annotate("text", x = as.Date("2023-03-13"), y = Inf, vjust = 4, label = "BTFP", size = 3) +
  annotate("text", x = as.Date("2023-05-01"), y = Inf, vjust = 2, label = "FRC", size = 3) +
  scale_fill_manual(values = fac_colors) +
  scale_y_continuous(labels = label_comma()) +
  labs(title = "Daily Total Lending ($M)", x = NULL, y = "$M") + theme_gp

p2 <- ggplot(daily, aes(x = date, y = n_banks, color = facility)) +
  geom_line(linewidth = 0.8) + geom_point(size = 1) +
  geom_vline(xintercept = as.Date("2023-03-10"), linetype = "dashed", color = "#E53935") +
  geom_vline(xintercept = as.Date("2023-03-13"), linetype = "dashed", color = "#1565C0") +
  scale_color_manual(values = fac_colors) +
  labs(title = "Daily Unique Banks Borrowing", x = NULL, y = "N Banks") + theme_gp

print(p1 / p2)

save_figure(p1 / p2, "Fig_DailyVolume", width = 14, height = 10)

3.4.2 A4.2 Outstanding Balance Over Time

# Build daily outstanding
date_grid <- seq(as.Date("2023-01-01"), as.Date("2024-04-01"), by = "day")

compute_outstanding_ts <- function(loans_df, date_col, repay_col, amt_col) {
  out <- numeric(length(date_grid))
  names(out) <- as.character(date_grid)
  for (i in seq_len(nrow(loans_df))) {
    s <- loans_df[[date_col]][i]; e <- loans_df[[repay_col]][i]; a <- loans_df[[amt_col]][i]
    if (is.na(s) | is.na(e) | is.na(a)) next
    mask <- date_grid >= s & date_grid <= e
    out[mask] <- out[mask] + a
  }
  tibble(date = date_grid, outstanding = out)
}

dw_out  <- compute_outstanding_ts(dw_loans, "dw_loan_date", "dw_repayment_date", "dw_loan_amount") %>%
  mutate(facility = "DW")
btfp_out <- compute_outstanding_ts(btfp_loans, "btfp_loan_date", "btfp_repayment_date", "btfp_loan_amount") %>%
  mutate(facility = "BTFP")

outstanding <- bind_rows(dw_out, btfp_out)

p_out <- ggplot(outstanding, aes(x = date, y = outstanding / 1e9, color = facility)) +
  geom_line(linewidth = 1.2) +
  geom_vline(xintercept = as.Date("2023-03-13"), linetype = "dashed", color = "red", alpha = 0.7) +
  annotate("text", x = as.Date("2023-03-13"), y = Inf, vjust = 2, label = "BTFP Launch", size = 3) +
  scale_color_manual(values = fac_colors) +
  scale_y_continuous(labels = label_dollar(suffix = "B")) +
  labs(title = "Daily Outstanding Balance: DW vs BTFP",
       x = NULL, y = "Outstanding ($B)") + theme_gp

print(p_out)

save_figure(p_out, "Fig_OutstandingBalance", width = 14, height = 6)

cat(sprintf("Peak DW outstanding:   $%.2fB on %s\n",
    max(dw_out$outstanding)/1e9, dw_out$date[which.max(dw_out$outstanding)]))
## Peak DW outstanding:   $90.64B on 2023-03-16
cat(sprintf("Peak BTFP outstanding: $%.2fB on %s\n",
    max(btfp_out$outstanding)/1e9, btfp_out$date[which.max(btfp_out$outstanding)]))
## Peak BTFP outstanding: $133.35B on 2024-01-16

4 PART B: STRUCTURAL RUN PRESSURE \(\phi\)

Model (from standalone theory concept):

Pre-crisis liquidity coverage: \[\phi = \frac{C + S^{\text{OMO}}_{MV}}{D^U}\]

where \(C\) = cash, \(S^{\text{OMO}}_{MV}\) = market value of OMO-eligible securities, \(D^U\) = uninsured deposits. All measured at 2022Q4 (pre-crisis).

Borrowing intensity (separate from \(\phi\)): \[g / D^U\] where \(g\) = total borrowed (DW + BTFP). This is NOT added to \(\phi\) because borrowing \(g\) is obtained by pledging securities already in \(S^{\text{OMO}}\); including both would double-count.

Run value: \(v = E^{MV} - F^U\). Run equilibrium exists when \(v < 0\).

Franchise value (Approach A): \(F = (1-\beta^U) \cdot r \cdot D / (r + \delta)\), scaled by \(D/TA\). \(F^U = (D^U/D) \cdot F\).


4.1 B0. Build Merged Panel

# ── Bank-level borrowing aggregates (post-crisis) ──
dw_bank_agg <- dw_loans %>%
  filter(dw_loan_date >= ACUTE_START) %>%
  group_by(rssd_id) %>%
  summarise(
    dw_total_borrowed = sum(dw_loan_amount),
    dw_n_loans = n(),
    dw_max_collateral = max(dw_total_collateral, na.rm = TRUE),
    dw_max_omo_eligible = max(dw_omo_eligible, na.rm = TRUE),
    dw_mean_rate = mean(dw_interest_rate, na.rm = TRUE),
    dw_mean_term = mean(dw_term, na.rm = TRUE),
    dw_n_prepaid = sum(prepaid, na.rm = TRUE),
    dw_first_loan = min(dw_loan_date),
    dw_last_loan = max(dw_loan_date),
    dw_fed_district = first(dw_fed_district),
    .groups = "drop"
  ) %>% rename(idrssd = rssd_id)

btfp_bank_agg <- btfp_loans %>%
  group_by(rssd_id) %>%
  summarise(
    btfp_total_borrowed = sum(btfp_loan_amount),
    btfp_n_loans = n(),
    btfp_max_collateral = max(btfp_total_collateral, na.rm = TRUE),
    btfp_mean_rate = mean(btfp_interest_rate, na.rm = TRUE),
    btfp_mean_term = mean(btfp_term, na.rm = TRUE),
    btfp_n_prepaid = sum(prepaid, na.rm = TRUE),
    btfp_first_loan = min(btfp_loan_date),
    btfp_last_loan = max(btfp_loan_date),
    btfp_fed_district = first(btfp_fed_district),
    .groups = "drop"
  ) %>% rename(idrssd = rssd_id)

# ── Start from 2022Q4 call report baseline ──
df <- call_q %>%
  filter(period == BASELINE_MAIN, !idrssd %in% excluded_banks) %>%
  left_join(dssw_beta_2022q4, by = "idrssd") %>%
  { if (HAS_DEPOSIT_COSTS) left_join(., deposit_costs_2022q4, by = "idrssd") else . } %>%
  left_join(public_flag %>% filter(period == "2022Q4") %>% select(idrssd, is_public), by = "idrssd") %>%
  mutate(is_public = replace_na(is_public, 0L))

# ── Merge borrowing ──
df <- df %>%
  left_join(dw_bank_agg, by = "idrssd") %>%
  left_join(btfp_bank_agg, by = "idrssd") %>%
  mutate(
    dw_total_borrowed   = replace_na(dw_total_borrowed, 0),
    btfp_total_borrowed = replace_na(btfp_total_borrowed, 0),
    dw_n_loans   = replace_na(dw_n_loans, 0L),
    btfp_n_loans = replace_na(btfp_n_loans, 0L),
    g = dw_total_borrowed + btfp_total_borrowed,
    used_dw   = as.integer(dw_total_borrowed > 0),
    used_btfp = as.integer(btfp_total_borrowed > 0),
    borrower_type = case_when(
      used_dw == 1 & used_btfp == 1 ~ "Both",
      used_dw == 1 ~ "DW Only",
      used_btfp == 1 ~ "BTFP Only",
      TRUE ~ "Non-Borrower"),
    borrowed = as.integer(g > 0)
  )

cat(sprintf("Panel: %d banks\n", nrow(df)))
## Panel: 4696 banks
cat(sprintf("Usage flag distribution:\n"))
## Usage flag distribution:
print(table(df$borrower_type))
## 
##         Both    BTFP Only      DW Only Non-Borrower 
##          543          762          804         2587

4.2 B1. Franchise Value (Approach A Only)

# ==============================================================================
# FRANCHISE VALUE — APPROACH A
# ==============================================================================
# F = (1 - beta^U) * r * (D/TA) * cap_factor * 100   [in pp of TA]
# F^U = (D^U / D) * F                                 [uninsured share of F]
#
# This uses total D/TA for F, then extracts the uninsured portion via D^U/D.
# No double-counting of D^U.
# ==============================================================================

# Clip beta to [0, 1]
df <- df %>%
  mutate(
    beta_u_clipped = pmin(pmax(ifelse(!is.na(beta_uninsured), beta_uninsured, NA_real_), 0), 1),
    one_minus_beta = 1 - beta_u_clipped,
    cost_u_raw = ifelse(HAS_DEPOSIT_COSTS & !is.na(deposit_cost_uninsured),
                        deposit_cost_uninsured, 0)
  )

cat(sprintf("Banks with beta available: %d / %d (%.1f%%)\n",
    sum(!is.na(df$beta_u_clipped)), nrow(df),
    100 * sum(!is.na(df$beta_u_clipped)) / nrow(df)))
## Banks with beta available: 4602 / 4696 (98.0%)
cat("\nbeta_uninsured distribution:\n")
## 
## beta_uninsured distribution:
print(summary(df$beta_u_clipped))
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##  0.0000  0.2671  0.3123  0.3337  0.3774  1.0000      94
df <- df %>%
  mutate(
    # Key ratios
    mu_decimal = uninsured_deposit / total_asset,         # D^U / TA
    d_over_ta  = (insured_deposit + uninsured_deposit) / total_asset,  # D / TA
    insured_share   = safe_div(insured_deposit, insured_deposit + uninsured_deposit, NA_real_),
    uninsured_share_d = safe_div(uninsured_deposit, insured_deposit + uninsured_deposit, NA_real_),

    gross_rent = (1 - beta_u_clipped) * y_10yr,
    net_rent   = gross_rent - cost_u_raw,

    # Franchise: F uses D/TA, then F^U = (D^U/D) * F
    f_pp = ifelse(!is.na(beta_u_clipped),
      pmax(net_rent * cap_factor * d_over_ta, 0) * 100, NA_real_),
    f_u_pp = ifelse(!is.na(f_pp) & !is.na(uninsured_share_d),
      uninsured_share_d * f_pp, NA_real_)
  )

cat(sprintf("\nFranchise value (Approach A):\n"))
## 
## Franchise value (Approach A):
cat(sprintf("  F (pp):   mean=%.3f, median=%.3f\n", mean(df$f_pp, na.rm=T), median(df$f_pp, na.rm=T)))
##   F (pp):   mean=9.479, median=9.945
cat(sprintf("  F^U (pp): mean=%.3f, median=%.3f\n", mean(df$f_u_pp, na.rm=T), median(df$f_u_pp, na.rm=T)))
##   F^U (pp): mean=2.611, median=2.330

4.3 B2. Run Value (\(v\)) and MTM Losses

# E^MV = book_equity (pp) - mtm_loss (pp) + F (pp)
# v    = E^MV - F^U
df <- df %>%
  mutate(
    mtm_total_raw  = mtm_loss_to_total_asset,
    mtm_sec_raw    = mtm_loss_to_total_asset - mtm_loss_total_loan_to_total_asset,
    mtm_loan_raw   = mtm_loss_total_loan_to_total_asset,
    book_eq_raw    = book_equity_to_total_asset,
    cash_ratio_raw = cash_to_total_asset,

    # Market-value equity (pp of assets)
    emv_pp = ifelse(!is.na(f_pp), book_eq_raw - mtm_total_raw + f_pp, NA_real_),
    # Run value
    v_pp = ifelse(!is.na(emv_pp) & !is.na(f_u_pp), emv_pp - f_u_pp, NA_real_),
    # Run possible: v < 0
    run_possible = as.integer(!is.na(v_pp) & v_pp < 0)
  )

cat("Run value v (pp of assets) distribution:\n")
## Run value v (pp of assets) distribution:
print(summary(df$v_pp))
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##  -5.055   7.898  10.565  11.404  13.708  95.469      94
cat(sprintf("\nBanks with v < 0 (run equilibrium possible): %d / %d (%.1f%%)\n",
    sum(df$run_possible, na.rm=T), sum(!is.na(df$v_pp)),
    100 * mean(df$run_possible, na.rm=T)))
## 
## Banks with v < 0 (run equilibrium possible): 34 / 4602 (0.7%)
# By borrower type
cat("\nRun-possible by borrower type:\n")
## 
## Run-possible by borrower type:
df %>%
  filter(!is.na(v_pp)) %>%
  group_by(borrower_type) %>%
  summarise(N = n(), run_pct = round(100 * mean(run_possible), 1),
            mean_v = round(mean(v_pp, na.rm=T), 3),
            median_v = round(median(v_pp, na.rm=T), 3), .groups = "drop") %>%
  kbl(format = "html", caption = "Run Value by Borrower Type") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE) %>%
  print()
## <table class="table table-striped table-condensed" style="width: auto !important; margin-left: auto; margin-right: auto;">
## <caption>Run Value by Borrower Type</caption>
##  <thead>
##   <tr>
##    <th style="text-align:left;"> borrower_type </th>
##    <th style="text-align:right;"> N </th>
##    <th style="text-align:right;"> run_pct </th>
##    <th style="text-align:right;"> mean_v </th>
##    <th style="text-align:right;"> median_v </th>
##   </tr>
##  </thead>
## <tbody>
##   <tr>
##    <td style="text-align:left;"> BTFP Only </td>
##    <td style="text-align:right;"> 754 </td>
##    <td style="text-align:right;"> 0.9 </td>
##    <td style="text-align:right;"> 9.470 </td>
##    <td style="text-align:right;"> 9.367 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Both </td>
##    <td style="text-align:right;"> 538 </td>
##    <td style="text-align:right;"> 1.1 </td>
##    <td style="text-align:right;"> 8.638 </td>
##    <td style="text-align:right;"> 8.602 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> DW Only </td>
##    <td style="text-align:right;"> 802 </td>
##    <td style="text-align:right;"> 0.7 </td>
##    <td style="text-align:right;"> 10.793 </td>
##    <td style="text-align:right;"> 10.311 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Non-Borrower </td>
##    <td style="text-align:right;"> 2508 </td>
##    <td style="text-align:right;"> 0.6 </td>
##    <td style="text-align:right;"> 12.775 </td>
##    <td style="text-align:right;"> 11.690 </td>
##   </tr>
## </tbody>
## </table>

4.4 B3. OMO-Eligible Securities and MTM Losses

df <- df %>%
  mutate(
    omo_book   = omo_eligible,
    lambda_omo = mtm_loss_omo_eligible,
    omo_mv     = pmax(omo_book - lambda_omo, 0)
  )

cat("Using exact OMO variables from the data:\n")
## Using exact OMO variables from the data:
cat("- omo_book   = omo_eligible\n")
## - omo_book   = omo_eligible
cat("- lambda_omo = mtm_loss_omo_eligible\n")
## - lambda_omo = mtm_loss_omo_eligible
cat("- omo_mv     = omo_eligible - mtm_loss_omo_eligible\n\n")
## - omo_mv     = omo_eligible - mtm_loss_omo_eligible
cat(sprintf("OMO-eligible book (mean, $000): %s\n",
    format(round(mean(df$omo_book, na.rm=TRUE)), big.mark=",")))
## OMO-eligible book (mean, $000): 315,023
cat(sprintf("OMO MTM loss (mean, $000): %s\n",
    format(round(mean(df$lambda_omo, na.rm=TRUE)), big.mark=",")))
## OMO MTM loss (mean, $000): 32,318
cat(sprintf("OMO market value (mean, $000): %s\n",
    format(round(mean(df$omo_mv, na.rm=TRUE)), big.mark=",")))
## OMO market value (mean, $000): 283,881

4.5 B4. Compute \(\phi\) (Pre-Crisis Liquidity Coverage)

cat("PRE-CRISIS LIQUIDITY COVERAGE: phi\n\n")
## PRE-CRISIS LIQUIDITY COVERAGE: phi
cat("Formula:\n")
## Formula:
cat("  phi = (C + S^OMO_MV) / D^U\n\n")
##   phi = (C + S^OMO_MV) / D^U
cat("This measures how much of the uninsured deposit base the bank\n")
## This measures how much of the uninsured deposit base the bank
cat("can cover using its own pre-crisis liquid resources (cash +\n")
## can cover using its own pre-crisis liquid resources (cash +
cat("market value of pledgeable securities), WITHOUT borrowing.\n\n")
## market value of pledgeable securities), WITHOUT borrowing.
cat("Borrowing intensity g/D^U is tracked separately.\n")
## Borrowing intensity g/D^U is tracked separately.
cat("We do NOT add g to the numerator because g is obtained by\n")
## We do NOT add g to the numerator because g is obtained by
cat("pledging the same securities already in S^OMO_MV.\n")
## pledging the same securities already in S^OMO_MV.
cat("Including both would double-count.\n\n")
## Including both would double-count.
df <- df %>%
  mutate(
    C   = replace_na(cash, 0),
    D_U = replace_na(uninsured_deposit, 0),
    D   = replace_na(insured_deposit, 0) + D_U,

    # g in $000s to match call report units
    g_000 = g / 1000,

    # phi = pre-crisis coverage (NO g in numerator)
    phi_num = C + omo_mv,
    phi     = safe_div(phi_num, D_U, NA_real_),

    # Borrowing intensity (separate measure)
    g_over_du = safe_div(g_000, D_U, NA_real_),

    # Components of phi
    phi_C   = safe_div(C, D_U, NA_real_),
    phi_omo = safe_div(omo_mv, D_U, NA_real_)
  )

borrowers <- df %>% filter(g > 0)

cat(sprintf("phi distribution (ALL %d banks):\n", nrow(df)))
## phi distribution (ALL 4696 banks):
print(summary(df$phi))
##      Min.   1st Qu.    Median      Mean   3rd Qu.      Max.      NA's 
## 3.740e-03 3.326e-01 6.305e-01 2.339e+00 1.193e+00 2.698e+03        74
cat(sprintf("\nphi distribution (BORROWERS, n=%d):\n", nrow(borrowers)))
## 
## phi distribution (BORROWERS, n=2109):
print(summary(borrowers$phi))
##      Min.   1st Qu.    Median      Mean   3rd Qu.      Max.      NA's 
## 3.212e-02 2.803e-01 5.158e-01 2.658e+00 9.007e-01 2.698e+03         5
cat(sprintf("\nphi distribution (NON-BORROWERS, n=%d):\n", sum(df$g == 0)))
## 
## phi distribution (NON-BORROWERS, n=2587):
print(summary(df$phi[df$g == 0]))
##      Min.   1st Qu.    Median      Mean   3rd Qu.      Max.      NA's 
##   0.00374   0.38763   0.75960   2.07232   1.56919 386.18630        69
cat(sprintf("\nBorrowing intensity g/D^U (borrowers only):\n"))
## 
## Borrowing intensity g/D^U (borrowers only):
print(summary(borrowers$g_over_du))
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max.     NA's 
##  0.00000  0.00015  0.10022  0.72793  0.50411 91.82465        5
cat(sprintf("\nBanks with phi < 1 (cannot cover full run pre-crisis): %d / %d (%.1f%%)\n",
    sum(df$phi < 1, na.rm=T), sum(!is.na(df$phi)),
    100 * mean(df$phi < 1, na.rm=T)))
## 
## Banks with phi < 1 (cannot cover full run pre-crisis): 3167 / 4622 (68.5%)
cat(sprintf("Among borrowers: %d / %d (%.1f%%)\n",
    sum(borrowers$phi < 1, na.rm=T), sum(!is.na(borrowers$phi)),
    100 * mean(borrowers$phi < 1, na.rm=T)))
## Among borrowers: 1656 / 2104 (78.7%)

4.5.1 B4b. \(\phi\) by Usage Flag

phi_flag <- df %>%
  group_by(borrower_type) %>%
  summarise(
    N = n(),
    `Mean phi` = round(mean(phi, na.rm=T), 4),
    `Median phi` = round(median(phi, na.rm=T), 4),
    `Std phi` = round(sd(phi, na.rm=T), 4),
    `Mean g/D^U` = round(mean(g_over_du, na.rm=T), 4),
    `Mean C/D^U` = round(mean(phi_C, na.rm=T), 4),
    `Mean OMO/D^U` = round(mean(phi_omo, na.rm=T), 4),
    `Mean v (pp)` = round(mean(v_pp, na.rm=T), 3),
    `Run % (v<0)` = round(100*mean(run_possible, na.rm=T), 1),
    .groups = "drop"
  )

kbl(phi_flag, format = "html",
    caption = "Pre-Crisis Liquidity Coverage (phi) and Borrowing Intensity by Group") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE) %>%
  footnote(general = "phi = (C + S^OMO_MV) / D^U measures pre-crisis coverage.
           g/D^U measures borrowing intensity (separate, not added to phi).
           Non-borrowers have g/D^U = 0 by definition.")
Pre-Crisis Liquidity Coverage (phi) and Borrowing Intensity by Group
borrower_type N Mean phi Median phi Std phi Mean g/D^U Mean C/D^U Mean OMO/D^U Mean v (pp) Run % (v<0)
BTFP Only 762 0.8783 0.5960 1.1969 0.8098 0.3196 0.5587 9.470 0.9
Both 543 0.6296 0.4381 0.7123 1.0090 0.2107 0.4189 8.638 1.1
DW Only 804 5.7214 0.4971 100.7499 0.4597 4.9812 0.7403 10.793 0.7
Non-Borrower 2587 2.0723 0.7596 11.1505 0.0000 1.3619 0.7151 12.775 0.6
Note:
phi = (C + S^OMO_MV) / D^U measures pre-crisis coverage.
g/D^U measures borrowing intensity (separate, not added to phi).
Non-borrowers have g/D^U = 0 by definition.
save_kbl_latex(phi_flag, "Table_Phi_ByFacility",
  caption = "Pre-Crisis Liquidity Coverage and Borrowing Intensity")
## Saved: Table_Phi_ByFacility.tex

4.6 B5. \(\phi\) Decomposition

decomp <- tibble(
  Component = c("C / D^U (cash)", "OMO_MV / D^U (securities)",
                "phi (pre-crisis coverage)", "g / D^U (borrowing intensity)"),
  Mean   = c(mean(borrowers$phi_C, na.rm=T), mean(borrowers$phi_omo, na.rm=T),
             mean(borrowers$phi, na.rm=T), mean(borrowers$g_over_du, na.rm=T)),
  Median = c(median(borrowers$phi_C, na.rm=T), median(borrowers$phi_omo, na.rm=T),
             median(borrowers$phi, na.rm=T), median(borrowers$g_over_du, na.rm=T)),
  Std    = c(sd(borrowers$phi_C, na.rm=T), sd(borrowers$phi_omo, na.rm=T),
             sd(borrowers$phi, na.rm=T), sd(borrowers$g_over_du, na.rm=T))
) %>% mutate(across(where(is.numeric), ~round(., 4)))

kbl(decomp, format = "html", caption = "phi Decomposition and Borrowing Intensity (borrowers)") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE) %>%
  footnote(general = "phi = C/D^U + OMO_MV/D^U. g/D^U is NOT part of phi (reported separately).")
phi Decomposition and Borrowing Intensity (borrowers)
Component Mean Median Std
C / D^U (cash) 2.0662 0.1723 59.2158
OMO_MV / D^U (securities) 0.5918 0.2658 3.5209
phi (pre-crisis coverage) 2.6579 0.5158 62.1915
g / D^U (borrowing intensity) 0.7279 0.1002 3.4958
Note:
phi = C/D^U + OMO_MV/D^U. g/D^U is NOT part of phi (reported separately).
save_kbl_latex(decomp, "Table_Phi_Decomposition",
  caption = "phi Decomposition and Borrowing Intensity")
## Saved: Table_Phi_Decomposition.tex

4.7 B6. \(\phi\) by Bank Size

asset_breaks <- quantile(borrowers$total_asset, probs = c(0, .25, .50, .75, 1), na.rm = TRUE)
borrowers <- borrowers %>%
  mutate(size_bucket = cut(total_asset, breaks = asset_breaks,
    labels = c("Q1 (Small)", "Q2", "Q3", "Q4 (Large)"), include.lowest = TRUE))

phi_size <- borrowers %>%
  group_by(size_bucket) %>%
  summarise(
    N = n(),
    `Mean phi` = round(mean(phi, na.rm=T), 4),
    `Median phi` = round(median(phi, na.rm=T), 4),
    `Mean g/D^U` = round(mean(g_over_du, na.rm=T), 4),
    `Mean v (pp)` = round(mean(v_pp, na.rm=T), 3),
    `Run % (v<0)` = round(100*mean(run_possible, na.rm=T), 1),
    .groups = "drop"
  )

kbl(phi_size, format = "html", caption = "phi by Bank Size Quartile (borrowers)") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE)
phi by Bank Size Quartile (borrowers)
size_bucket N Mean phi Median phi Mean g/D^U Mean v (pp) Run % (v<0)
Q1 (Small) 528 1.1799 0.8415 0.8489 10.904 0.9
Q2 527 5.8729 0.5731 0.7057 9.459 1.1
Q3 527 0.5746 0.4358 0.6708 9.389 0.9
Q4 (Large) 527 2.9952 0.3726 0.6866 9.316 0.6
save_kbl_latex(phi_size, "Table_Phi_BySize", caption = "phi by Bank Size Quartile")
## Saved: Table_Phi_BySize.tex

4.8 B7. Correlations with Bank Characteristics

corr_vars <- c("phi", "g_over_du", "v_pp", "beta_u_clipped", "mtm_total_raw", "mtm_sec_raw",
  "emv_pp", "f_u_pp", "book_eq_raw", "cash_ratio_raw",
  "mu_decimal", "uninsured_share_d")
corr_avail <- intersect(corr_vars, names(borrowers))

corr_mat <- cor(borrowers[corr_avail], use = "pairwise.complete.obs")
phi_corr <- sort(corr_mat["phi", ], decreasing = TRUE)

corr_tbl <- tibble(Variable = names(phi_corr), `Corr with phi` = round(phi_corr, 4))
kbl(corr_tbl, format = "html", caption = "Correlation with phi (borrowers)") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE)
Correlation with phi (borrowers)
Variable Corr with phi
phi 1.0000
cash_ratio_raw 0.2106
book_eq_raw 0.1472
v_pp 0.1153
beta_u_clipped 0.0994
emv_pp 0.0914
g_over_du 0.0261
mtm_sec_raw -0.0239
f_u_pp -0.0572
mtm_total_raw -0.0683
uninsured_share_d -0.0723
mu_decimal -0.0724

4.9 B8. Cross-Partial: MTM Loss \(\times\) Uninsured Franchise

cat("Testing equation (11): d^2 Pr(borrow) / d(lambda) d(F^U) > 0\n\n")
## Testing equation (11): d^2 Pr(borrow) / d(lambda) d(F^U) > 0
df_test <- df %>% filter(!is.na(mtm_total_raw), !is.na(f_u_pp))

df_test <- df_test %>%
  mutate(
    lambda_q = ntile(mtm_total_raw, 3),
    lambda_q = factor(lambda_q, labels = c("Low MTM", "Med MTM", "High MTM")),
    FU_q = ntile(f_u_pp, 3),
    FU_q = factor(FU_q, labels = c("Low F^U", "Med F^U", "High F^U"))
  )

cross <- df_test %>%
  group_by(lambda_q, FU_q) %>%
  summarise(
    Pr_borrow = round(mean(borrowed), 4),
    N = n(),
    .groups = "drop"
  ) %>%
  pivot_wider(names_from = FU_q, values_from = c(Pr_borrow, N))

kbl(cross, format = "html",
    caption = "Pr(Borrow) by MTM Loss Tercile x F^U Tercile") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE) %>%
  footnote(general = "Positive cross-partial: effect of MTM loss on borrowing is larger when F^U is larger.")
Pr(Borrow) by MTM Loss Tercile x F^U Tercile
lambda_q Pr_borrow_Low F^U Pr_borrow_Med F^U Pr_borrow_High F^U N_Low F^U N_Med F^U N_High F^U
Low MTM 0.2939 0.3542 0.4132 524 480 530
Med MTM 0.4125 0.4851 0.5947 463 538 533
High MTM 0.3967 0.5349 0.6136 547 516 471
Note:
Positive cross-partial: effect of MTM loss on borrowing is larger when F^U is larger.
save_kbl_latex(cross, "Table_CrossPartial",
  caption = "Pr(Borrow) by MTM Loss Tercile x Uninsured Franchise Tercile")
## Saved: Table_CrossPartial.tex
# Difference-in-differences
cat("\nInteraction check (High - Low MTM effect across F^U terciles):\n")
## 
## Interaction check (High - Low MTM effect across F^U terciles):
for (fu in c("Low F^U", "Med F^U", "High F^U")) {
  high <- df_test %>% filter(lambda_q == "High MTM", FU_q == fu) %>% pull(borrowed) %>% mean()
  low  <- df_test %>% filter(lambda_q == "Low MTM",  FU_q == fu) %>% pull(borrowed) %>% mean()
  cat(sprintf("  %s: Pr(borrow|High MTM) - Pr(borrow|Low MTM) = %.4f\n", fu, high - low))
}
##   Low F^U: Pr(borrow|High MTM) - Pr(borrow|Low MTM) = 0.1028
##   Med F^U: Pr(borrow|High MTM) - Pr(borrow|Low MTM) = 0.1807
##   High F^U: Pr(borrow|High MTM) - Pr(borrow|Low MTM) = 0.2004

4.10 B9. Run Threshold \(\lambda^\dagger\)

# lambda^dagger = E + F - F^U  (in pp of assets)
df <- df %>%
  mutate(
    lambda_dagger_pp = book_eq_raw + f_pp - f_u_pp,
    excess_loss_pp = mtm_total_raw - lambda_dagger_pp,
    above_threshold = as.integer(!is.na(excess_loss_pp) & excess_loss_pp > 0)
  )

cat("lambda^dagger (run threshold, pp of assets):\n")
## lambda^dagger (run threshold, pp of assets):
print(summary(df$lambda_dagger_pp))
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   3.715  13.875  15.999  16.857  18.372  95.638      94
cat(sprintf("\nBanks above run threshold (lambda > lambda^dagger): %d / %d (%.1f%%)\n",
    sum(df$above_threshold, na.rm=T), sum(!is.na(df$lambda_dagger_pp)),
    100 * mean(df$above_threshold, na.rm=T)))
## 
## Banks above run threshold (lambda > lambda^dagger): 34 / 4602 (0.7%)
# By borrower type
thresh_by_type <- df %>%
  filter(!is.na(lambda_dagger_pp)) %>%
  group_by(borrower_type) %>%
  summarise(
    N = n(),
    `Mean lambda^dag (pp)` = round(mean(lambda_dagger_pp, na.rm=T), 3),
    `Mean excess loss (pp)` = round(mean(excess_loss_pp, na.rm=T), 3),
    `% Above Threshold` = round(100*mean(above_threshold), 1),
    .groups = "drop"
  )

kbl(thresh_by_type, format = "html",
    caption = "Run Threshold by Borrower Type") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE)
Run Threshold by Borrower Type
borrower_type N Mean lambda^dag (pp) Mean excess loss (pp) % Above Threshold
BTFP Only 754 15.536 -9.470 0.9
Both 538 14.647 -8.638 1.1
DW Only 802 16.125 -10.793 0.7
Non-Borrower 2508 17.963 -12.775 0.6
save_kbl_latex(thresh_by_type, "Table_RunThreshold", caption = "Run Threshold by Borrower Type")
## Saved: Table_RunThreshold.tex

4.11 B10. Sensitivity of \(v\) to Beta

cat("Sensitivity: how v changes with different beta assumptions.\n")
## Sensitivity: how v changes with different beta assumptions.
cat("Uses Approach A: F = (1-beta)*r*cap_factor*(D/TA)*100.\n\n")
## Uses Approach A: F = (1-beta)*r*cap_factor*(D/TA)*100.
beta_grid <- c(0, 0.10, 0.20, 0.30, 0.38, 0.50, 0.60, 0.80, 1.0)

sens <- map_dfr(beta_grid, function(b) {
  # Approach A: F uses d_over_ta
  F_b  <- (1 - b) * pmax(y_10yr * cap_factor * df$d_over_ta, 0) * 100
  FU_b <- df$uninsured_share_d * F_b
  emv_b <- df$book_eq_raw - df$mtm_total_raw + F_b
  v_b   <- emv_b - FU_b

  borr_mask <- df$g > 0

  tibble(
    beta = b,
    mean_FU   = round(mean(FU_b, na.rm=T), 3),
    mean_v    = round(mean(v_b, na.rm=T), 3),
    pct_v_neg = round(100 * mean(v_b < 0, na.rm=T), 1),
    mean_v_borrowers    = round(mean(v_b[borr_mask], na.rm=T), 3),
    pct_v_neg_borrowers = round(100 * mean(v_b[borr_mask] < 0, na.rm=T), 1)
  )
})

kbl(sens, format = "html",
    col.names = c("beta", "Mean F^U (pp)", "Mean v (pp)", "% v<0 (all)",
                  "Mean v (borr)", "% v<0 (borr)"),
    caption = "Sensitivity of Run Value to Different Beta Assumptions (Approach A)") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE) %>%
  footnote(general = "Higher beta = smaller franchise = lower v. When beta=1, franchise=0.")
Sensitivity of Run Value to Different Beta Assumptions (Approach A)
beta Mean F^U (pp) Mean v (pp) % v<0 (all) Mean v (borr) % v<0 (borr)
0.00 6.620 21.957 0.1 20.127 0.0
0.10 5.958 20.208 0.1 18.432 0.0
0.20 5.296 18.460 0.1 16.737 0.0
0.30 4.634 16.712 0.2 15.042 0.1
0.38 4.104 15.313 0.2 13.686 0.1
0.50 3.310 13.215 0.3 11.652 0.3
0.60 2.648 11.466 0.6 9.958 0.8
0.80 1.324 7.969 4.1 6.568 5.6
1.00 0.000 4.472 18.7 3.178 22.3
Note:
Higher beta = smaller franchise = lower v. When beta=1, franchise=0.
save_kbl_latex(sens, "Table_Sensitivity_Beta", caption = "Sensitivity of Run Value to Beta")
## Saved: Table_Sensitivity_Beta.tex

4.12 B11. Summary Panel: Structural Variables

summary_vars <- list(
  c("Total Assets ($000s)", "total_asset"),
  c("Cash ($000s)", "C"),
  c("Total Deposits ($000s)", "D"),
  c("Uninsured Deposits ($000s)", "D_U"),
  c("D^U / D", "uninsured_share_d"),
  c("Book Equity / TA (pp)", "book_eq_raw"),
  c("MTM Loss / TA (pp)", "mtm_total_raw"),
  c("Sec MTM Loss / TA (pp)", "mtm_sec_raw"),
  c("Deposit Beta (uninsured)", "beta_u_clipped"),
  c("Franchise F (pp)", "f_pp"),
  c("Uninsured Franchise F^U (pp)", "f_u_pp"),
  c("E^MV (pp)", "emv_pp"),
  c("Run Value v (pp)", "v_pp"),
  c("OMO-Eligible Book ($000s)", "omo_book"),
  c("OMO-Eligible MV ($000s)", "omo_mv"),
  c("phi (pre-crisis coverage)", "phi"),
  c("g / D^U (borrowing intensity)", "g_over_du")
)

panel <- map_dfr(summary_vars, function(v) {
  label <- v[1]; col <- v[2]
  if (!col %in% names(borrowers)) return(NULL)
  x <- borrowers[[col]]
  tibble(
    Variable = label,
    Mean = round(mean(x, na.rm=T), 4),
    Std  = round(sd(x, na.rm=T), 4),
    P10  = round(quantile(x, 0.10, na.rm=T), 4),
    P25  = round(quantile(x, 0.25, na.rm=T), 4),
    Median = round(median(x, na.rm=T), 4),
    P75  = round(quantile(x, 0.75, na.rm=T), 4),
    P90  = round(quantile(x, 0.90, na.rm=T), 4)
  )
})

kbl(panel, format = "html",
    caption = sprintf("Summary Statistics: Structural Variables (Borrowers, N=%d)", nrow(borrowers))) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
Summary Statistics: Structural Variables (Borrowers, N=2109)
Variable Mean Std P10 P25 Median P75 P90
Total Assets ($000s) 4403674.4211 2.766275e+07 135389.6000 265886.0000 572592.0000 1542625.0000 4653026.0000
Cash ($000s) 263579.5775 2.117765e+06 5679.8000 10563.0000 24468.0000 69173.0000 198481.2000
Total Deposits ($000s) 3618268.8748 2.235487e+07 116125.2000 232023.0000 501212.0000 1296859.0000 4069657.8000
Uninsured Deposits ($000s) 1456748.5595 9.869476e+06 21879.8000 51790.0000 134157.0000 401611.0000 1508672.0000
D^U / D 0.2997 1.377000e-01 0.1434 0.2046 0.2802 0.3740 0.4830
Book Equity / TA (pp) 8.9590 3.673300e+00 5.1708 6.9505 8.5700 10.3452 12.8186
MTM Loss / TA (pp) 5.7806 2.103900e+00 3.1612 4.2990 5.6436 7.1828 8.6564
Sec MTM Loss / TA (pp) 2.2650 1.718900e+00 0.3420 0.9489 1.9112 3.2206 4.6795
Deposit Beta (uninsured) 0.3417 1.133000e-01 0.2339 0.2695 0.3183 0.3859 0.4769
Franchise F (pp) 9.3507 2.796900e+00 5.6994 7.9620 9.8320 11.2797 12.3233
Uninsured Franchise F^U (pp) 2.8120 1.632000e+00 1.0445 1.7646 2.5479 3.5993 4.8252
E^MV (pp) 12.5751 4.605300e+00 7.2047 9.6866 12.3389 15.2307 17.9298
Run Value v (pp) 9.7631 4.464900e+00 4.6749 7.0911 9.4654 12.0105 14.9308
OMO-Eligible Book ($000s) 539391.7734 4.587328e+06 2704.2000 10993.0000 36423.0000 118942.0000 459704.4000
OMO-Eligible MV ($000s) 482237.0308 4.011823e+06 2362.6075 10279.6501 33628.5621 109562.1688 419784.5273
phi (pre-crisis coverage) 2.6579 6.219150e+01 0.1676 0.2803 0.5158 0.9007 1.5689
g / D^U (borrowing intensity) 0.7279 3.495800e+00 0.0000 0.0001 0.1002 0.5041 1.3626
save_kbl_latex(panel, "Table_SummaryPanel_Structural",
  caption = sprintf("Summary Statistics: Structural Variables (Borrowers, N=%d)", nrow(borrowers)))
## Saved: Table_SummaryPanel_Structural.tex

4.13 B12. Visualizations

# (a) Histogram of phi (pre-crisis coverage)
p_phi_hist <- ggplot(df %>% filter(!is.na(phi)),
       aes(x = pmin(phi, 5))) +
  geom_histogram(bins = 60, fill = "#1565C0", alpha = 0.7, color = "white") +
  geom_vline(xintercept = 1, linetype = "dashed", color = "red", linewidth = 0.8) +
  annotate("text", x = 1.05, y = Inf, vjust = 2, hjust = 0, label = "phi=1 (full coverage)", color = "red") +
  labs(title = "Distribution of phi (All Banks)",
       subtitle = "Pre-crisis liquidity coverage = (C + OMO_MV) / D^U",
       x = "phi", y = "Count") +
  theme_gp

# (b) phi by facility
p_phi_fac <- ggplot(df %>% filter(!is.na(phi), borrower_type != "Non-Borrower"),
       aes(x = pmin(phi, 5), fill = borrower_type)) +
  geom_histogram(bins = 40, alpha = 0.6, position = "identity") +
  geom_vline(xintercept = 1, linetype = "dashed", color = "red") +
  scale_fill_manual(values = c("DW Only"="#E53935", "BTFP Only"="#1565C0", "Both"="#7B1FA2")) +
  labs(title = "phi by Facility Used (Borrowers)", x = "phi", y = "Count") + theme_gp

# (c) phi vs v
p_phi_v <- ggplot(df %>% filter(!is.na(phi), !is.na(v_pp)),
       aes(x = v_pp, y = pmin(phi, 5), color = factor(borrowed))) +
  geom_point(alpha = 0.3, size = 1.5) +
  geom_hline(yintercept = 1, linetype = "dashed", color = "red", alpha = 0.5) +
  geom_vline(xintercept = 0, linetype = "dotted", color = "grey50") +
  scale_color_manual(values = c("0"="grey70", "1"="#1565C0"), labels = c("Non-Borrower", "Borrower")) +
  labs(title = "phi vs Run Value v",
       subtitle = "phi < 1 = insufficient pre-crisis coverage; v < 0 = run possible",
       x = "Run value v (pp of assets)", y = "phi", color = "") + theme_gp

# (d) g/D^U vs phi (borrowers only)
p_g_phi <- ggplot(borrowers %>% filter(!is.na(phi), !is.na(g_over_du)),
       aes(x = pmin(phi, 5), y = pmin(g_over_du, 5))) +
  geom_point(alpha = 0.3, size = 1.5, color = "#E53935") +
  geom_smooth(method = "loess", color = "black", se = TRUE) +
  geom_vline(xintercept = 1, linetype = "dashed", color = "red", alpha = 0.5) +
  labs(title = "Borrowing Intensity vs Pre-Crisis Coverage",
       subtitle = "Lower phi -> higher g/D^U (banks with less coverage borrow more)",
       x = "phi (pre-crisis coverage)", y = "g / D^U (borrowing intensity)") + theme_gp

print((p_phi_hist | p_phi_fac) / (p_phi_v | p_g_phi))

save_figure((p_phi_hist | p_phi_fac) / (p_phi_v | p_g_phi),
  "Fig_Phi_Panel", width = 16, height = 12)

4.14 B13. Save Dataset

out_path <- file.path(DATA_PROC, "bank_phi_structural.csv")

save_cols <- c("idrssd", "total_asset", "C", "D", "D_U",
  "book_eq_raw", "mtm_total_raw", "mtm_sec_raw", "mtm_loan_raw",
  "cash_ratio_raw", "mu_decimal", "uninsured_share_d",
  "beta_u_clipped", "one_minus_beta", "f_pp", "f_u_pp", "emv_pp", "v_pp",
  "run_possible", "lambda_dagger_pp", "above_threshold",
  "omo_book", "lambda_omo", "omo_mv",
  "dw_total_borrowed", "btfp_total_borrowed", "g", "g_000",
  "phi_num", "phi", "phi_C", "phi_omo", "g_over_du",
  "used_dw", "used_btfp", "borrower_type", "borrowed")

save_avail <- intersect(save_cols, names(df))
write_csv(df[save_avail], out_path)
cat(sprintf("Saved %d banks (%d columns) to:\n  %s\n", nrow(df), length(save_avail), out_path))
## Saved 4696 banks (37 columns) to:
##   C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/01_data/processed/bank_phi_structural.csv

4.15 B14. Key Findings

cat("KEY FINDINGS\n\n")
## KEY FINDINGS
cat(sprintf("1. BORROWING BEHAVIOR:\n"))
## 1. BORROWING BEHAVIOR:
cat(sprintf("   DW:   %d transactions, %d banks\n", nrow(dw_loans), n_distinct(dw_loans$rssd_id)))
##    DW:   9600 transactions, 1447 banks
cat(sprintf("   BTFP: %d transactions, %d banks\n", nrow(btfp_loans), n_distinct(btfp_loans$rssd_id)))
##    BTFP: 6695 transactions, 1316 banks
cat(sprintf("   Both: %d banks\n\n", length(both_banks)))
##    Both: 583 banks
cat(sprintf("2. PHI (pre-crisis liquidity coverage = (C + OMO_MV) / D^U):\n"))
## 2. PHI (pre-crisis liquidity coverage = (C + OMO_MV) / D^U):
cat(sprintf("   Mean phi (all):       %.4f\n", mean(df$phi, na.rm=T)))
##    Mean phi (all):       2.3389
cat(sprintf("   Median phi (all):     %.4f\n", median(df$phi, na.rm=T)))
##    Median phi (all):     0.6305
cat(sprintf("   Mean phi (borrowers): %.4f\n", mean(borrowers$phi, na.rm=T)))
##    Mean phi (borrowers): 2.6579
cat(sprintf("   Median phi (borr):    %.4f\n", median(borrowers$phi, na.rm=T)))
##    Median phi (borr):    0.5158
cat(sprintf("   phi < 1 (all):        %d / %d (%.1f%%)\n",
    sum(df$phi < 1, na.rm=T), sum(!is.na(df$phi)),
    100*mean(df$phi < 1, na.rm=T)))
##    phi < 1 (all):        3167 / 4622 (68.5%)
cat(sprintf("   phi < 1 (borrowers):  %d / %d (%.1f%%)\n\n",
    sum(borrowers$phi < 1, na.rm=T), sum(!is.na(borrowers$phi)),
    100*mean(borrowers$phi < 1, na.rm=T)))
##    phi < 1 (borrowers):  1656 / 2104 (78.7%)
cat(sprintf("3. BORROWING INTENSITY (g/D^U, borrowers only):\n"))
## 3. BORROWING INTENSITY (g/D^U, borrowers only):
cat(sprintf("   Mean g/D^U:   %.4f\n", mean(borrowers$g_over_du, na.rm=T)))
##    Mean g/D^U:   0.7279
cat(sprintf("   Median g/D^U: %.4f\n\n", median(borrowers$g_over_du, na.rm=T)))
##    Median g/D^U: 0.1002
cat(sprintf("4. RUN VALUE (v = E^MV - F^U, Approach A):\n"))
## 4. RUN VALUE (v = E^MV - F^U, Approach A):
cat(sprintf("   Banks with v < 0:  %d / %d (%.1f%%)\n",
    sum(df$run_possible, na.rm=T), sum(!is.na(df$v_pp)),
    100*mean(df$run_possible, na.rm=T)))
##    Banks with v < 0:  34 / 4602 (0.7%)
cat(sprintf("   Among borrowers:   %d / %d (%.1f%%)\n\n",
    sum(borrowers$run_possible, na.rm=T), nrow(borrowers),
    100*mean(borrowers$run_possible, na.rm=T)))
##    Among borrowers:   19 / 2109 (0.9%)
cat("5. INTERPRETATION:\n")
## 5. INTERPRETATION:
cat("   phi measures pre-crisis liquidity coverage. Banks with low phi\n")
##    phi measures pre-crisis liquidity coverage. Banks with low phi
cat("   had insufficient buffers and were more likely to need emergency borrowing.\n")
##    had insufficient buffers and were more likely to need emergency borrowing.
cat("   g/D^U measures how much they actually borrowed relative to run exposure.\n")
##    g/D^U measures how much they actually borrowed relative to run exposure.
cat("   Double-counting is avoided: phi and g/D^U are separate measures.\n")
##    Double-counting is avoided: phi and g/D^U are separate measures.
# ============================================================
# REMINDER: phi = (C + OMO_MV) / D^U  (no g in numerator)
#           g/D^U is reported separately
# ============================================================

CRISIS_START <- as.Date("2023-03-08")
CRISIS_END   <- as.Date("2023-05-04")
ARBITRAGE_START <- as.Date("2023-11-15")
ARBITRAGE_END   <- as.Date("2024-01-24")

# Helper function for phi summary
phi_summary <- function(data, label = "") {
  data %>%
    summarise(
      N = n(),
      mean_phi    = round(mean(phi, na.rm=T), 4),
      median_phi  = round(median(phi, na.rm=T), 4),
      mean_C_DU   = round(mean(phi_C, na.rm=T), 4),
      median_C_DU = round(median(phi_C, na.rm=T), 4),
      mean_OMO_DU = round(mean(phi_omo, na.rm=T), 4),
      mean_g_DU   = round(mean(g_over_du, na.rm=T), 4),
      median_g_DU = round(median(g_over_du, na.rm=T), 4),
      mean_v      = round(mean(v_pp, na.rm=T), 3),
      pct_v_neg   = round(100 * mean(run_possible, na.rm=T), 1),
      pct_phi_lt1 = round(100 * mean(phi < 1, na.rm=T), 1),
      .groups = "drop"
    ) %>%
    mutate(label = label)
}

# ==================================================================
# PANELS C & D: One-Time vs Repeat Borrowers (crisis window only)
# ==================================================================

# DW loans during crisis window
dw_crisis_repeat <- dw_loans %>%
  filter(dw_loan_date >= CRISIS_START, dw_loan_date <= CRISIS_END) %>%
  group_by(rssd_id) %>%
  summarise(dw_crisis_n = n(), .groups = "drop") %>%
  mutate(dw_repeat = as.integer(dw_crisis_n > 1)) %>%
  rename(idrssd = rssd_id)

# BTFP loans during crisis window
btfp_crisis_repeat <- btfp_loans %>%
  filter(btfp_loan_date >= CRISIS_START, btfp_loan_date <= CRISIS_END) %>%
  group_by(rssd_id) %>%
  summarise(btfp_crisis_n = n(), .groups = "drop") %>%
  mutate(btfp_repeat = as.integer(btfp_crisis_n > 1)) %>%
  rename(idrssd = rssd_id)

df <- df %>%
  left_join(dw_crisis_repeat, by = "idrssd") %>%
  left_join(btfp_crisis_repeat, by = "idrssd")

cat("\n=== Panel C: DW One-Time vs Repeat (Crisis Window) ===\n")
## 
## === Panel C: DW One-Time vs Repeat (Crisis Window) ===
panel_c <- df %>%
  filter(used_dw == 1, !is.na(dw_repeat)) %>%
  group_by(dw_repeat) %>%
  phi_summary() %>%
  mutate(label = ifelse(dw_repeat == 0, "DW One-Time", "DW Repeat"))
print(panel_c)
## # A tibble: 2 × 13
##   dw_repeat     N mean_phi median_phi mean_C_DU median_C_DU mean_OMO_DU
##       <int> <int>    <dbl>      <dbl>     <dbl>       <dbl>       <dbl>
## 1         0   314    1.15       0.501     0.654      0.168        0.497
## 2         1   126    0.546      0.342     0.183      0.0987       0.363
## # ℹ 6 more variables: mean_g_DU <dbl>, median_g_DU <dbl>, mean_v <dbl>,
## #   pct_v_neg <dbl>, pct_phi_lt1 <dbl>, label <chr>
cat("\n=== Panel D: BTFP One-Time vs Repeat (Crisis Window) ===\n")
## 
## === Panel D: BTFP One-Time vs Repeat (Crisis Window) ===
panel_d <- df %>%
  filter(used_btfp == 1, !is.na(btfp_repeat)) %>%
  group_by(btfp_repeat) %>%
  phi_summary() %>%
  mutate(label = ifelse(btfp_repeat == 0, "BTFP One-Time", "BTFP Repeat"))
print(panel_d)
## # A tibble: 2 × 13
##   btfp_repeat     N mean_phi median_phi mean_C_DU median_C_DU mean_OMO_DU
##         <int> <int>    <dbl>      <dbl>     <dbl>       <dbl>       <dbl>
## 1           0   232    0.659      0.490     0.225       0.144       0.434
## 2           1   287    0.702      0.503     0.199       0.124       0.503
## # ℹ 6 more variables: mean_g_DU <dbl>, median_g_DU <dbl>, mean_v <dbl>,
## #   pct_v_neg <dbl>, pct_phi_lt1 <dbl>, label <chr>
# ==================================================================
# PANEL E: By Borrowing Period
# ==================================================================

dw_crisis_banks <- dw_loans %>%
  filter(dw_loan_date >= CRISIS_START, dw_loan_date <= CRISIS_END) %>%
  pull(rssd_id) %>% unique()
btfp_crisis_banks <- btfp_loans %>%
  filter(btfp_loan_date >= CRISIS_START, btfp_loan_date <= CRISIS_END) %>%
  pull(rssd_id) %>% unique()
crisis_banks <- union(dw_crisis_banks, btfp_crisis_banks)

dw_arb_banks <- dw_loans %>%
  filter(dw_loan_date >= ARBITRAGE_START, dw_loan_date <= ARBITRAGE_END) %>%
  pull(rssd_id) %>% unique()
btfp_arb_banks <- btfp_loans %>%
  filter(btfp_loan_date >= ARBITRAGE_START, btfp_loan_date <= ARBITRAGE_END) %>%
  pull(rssd_id) %>% unique()
arb_banks <- union(dw_arb_banks, btfp_arb_banks)

df <- df %>%
  mutate(
    in_crisis = as.integer(idrssd %in% crisis_banks),
    in_arb    = as.integer(idrssd %in% arb_banks),
    period_type = case_when(
      in_crisis == 1 & in_arb == 1 ~ "Both Periods",
      in_crisis == 1 & in_arb == 0 ~ "Crisis Only",
      in_crisis == 0 & in_arb == 1 ~ "Arbitrage Only",
      TRUE ~ "Non-Borrower"
    )
  )

cat("\n=== Panel E: By Period ===\n")
## 
## === Panel E: By Period ===
panel_e <- df %>%
  filter(period_type != "Non-Borrower") %>%
  group_by(period_type) %>%
  phi_summary()
print(panel_e)
## # A tibble: 3 × 13
##   period_type        N mean_phi median_phi mean_C_DU median_C_DU mean_OMO_DU
##   <chr>          <int>    <dbl>      <dbl>     <dbl>       <dbl>       <dbl>
## 1 Arbitrage Only   616    0.820      0.546     0.326       0.172       0.494
## 2 Both Periods     414    0.614      0.470     0.179       0.119       0.434
## 3 Crisis Only      446    1.07       0.496     0.556       0.170       0.516
## # ℹ 6 more variables: mean_g_DU <dbl>, median_g_DU <dbl>, mean_v <dbl>,
## #   pct_v_neg <dbl>, pct_phi_lt1 <dbl>, label <chr>
# ==================================================================
#  By FACILITY and CRISIS SUB-PERIOD
# ==================================================================

# Define crisis sub-periods
P1_start <- as.Date("2023-03-08"); P1_end <- as.Date("2023-03-12")  # SVB Week
P2_start <- as.Date("2023-03-13"); P2_end <- as.Date("2023-04-27")  # BTFP Active
P3_start <- as.Date("2023-04-28"); P3_end <- as.Date("2023-05-04")  # First Republic

# Tag each bank by WHEN and WHERE they first borrowed
dw_by_phase <- dw_loans %>%
  mutate(phase = case_when(
    dw_loan_date >= P1_start & dw_loan_date <= P1_end ~ "P1_SVB",
    dw_loan_date >= P2_start & dw_loan_date <= P2_end ~ "P2_BTFP_Active",
    dw_loan_date >= P3_start & dw_loan_date <= P3_end ~ "P3_FRC",
    dw_loan_date >= ARBITRAGE_START & dw_loan_date <= ARBITRAGE_END ~ "Arbitrage",
    TRUE ~ "Other"
  )) %>%
  filter(phase != "Other") %>%
  group_by(rssd_id, phase) %>%
  summarise(
    dw_g_phase = sum(dw_loan_amount),
    dw_n_phase = n(),
    .groups = "drop"
  ) %>%
  rename(idrssd = rssd_id)

btfp_by_phase <- btfp_loans %>%
  mutate(phase = case_when(
    btfp_loan_date >= P2_start & btfp_loan_date <= P2_end ~ "P2_BTFP_Active",
    btfp_loan_date >= P3_start & btfp_loan_date <= P3_end ~ "P3_FRC",
    btfp_loan_date >= ARBITRAGE_START & btfp_loan_date <= ARBITRAGE_END ~ "Arbitrage",
    TRUE ~ "Other"
  )) %>%
  filter(phase != "Other") %>%
  group_by(rssd_id, phase) %>%
  summarise(
    btfp_g_phase = sum(btfp_loan_amount),
    btfp_n_phase = n(),
    .groups = "drop"
  ) %>%
  rename(idrssd = rssd_id)

# For each facility x phase, merge to the 2022Q4 baseline and compute phi
cat("\n\n==============================================================\n")
## 
## 
## ==============================================================
cat("phi by Facility x Crisis Sub-Period\n")
## phi by Facility x Crisis Sub-Period
cat("All balance-sheet variables from 2022Q4\n")
## All balance-sheet variables from 2022Q4
cat("==============================================================\n\n")
## ==============================================================
# DW borrowers by phase
cat("=== DW Borrowers by Phase ===\n")
## === DW Borrowers by Phase ===
for (ph in c("P1_SVB", "P2_BTFP_Active", "P3_FRC", "Arbitrage")) {
  banks_in_phase <- dw_by_phase %>% filter(phase == ph) %>% pull(idrssd) %>% unique()

  if (length(banks_in_phase) == 0) {
    cat(sprintf("  %s: no DW borrowers\n", ph))
    next
  }

  sub <- df %>%
    filter(idrssd %in% banks_in_phase) %>%
    left_join(dw_by_phase %>% filter(phase == ph) %>% select(idrssd, dw_g_phase), by = "idrssd") %>%
    mutate(g_phase_du = safe_div(dw_g_phase / 1000, D_U, NA_real_))

  cat(sprintf("\n  %s (N=%d):\n", ph, nrow(sub)))
  cat(sprintf("    Mean phi (C+OMO)/D^U:  %.4f\n", mean(sub$phi, na.rm=T)))
  cat(sprintf("    Median phi:            %.4f\n", median(sub$phi, na.rm=T)))
  cat(sprintf("    Mean C/D^U:            %.4f\n", mean(sub$phi_C, na.rm=T)))
  cat(sprintf("    Median C/D^U:          %.4f\n", median(sub$phi_C, na.rm=T)))
  cat(sprintf("    Mean OMO/D^U:          %.4f\n", mean(sub$phi_omo, na.rm=T)))
  cat(sprintf("    Mean g_phase/D^U:      %.4f\n", mean(sub$g_phase_du, na.rm=T)))
  cat(sprintf("    Median g_phase/D^U:    %.4f\n", median(sub$g_phase_du, na.rm=T)))
  cat(sprintf("    Mean v (pp):           %.3f\n", mean(sub$v_pp, na.rm=T)))
  cat(sprintf("    %% v < 0:               %.1f%%\n", 100 * mean(sub$run_possible, na.rm=T)))
  cat(sprintf("    %% phi < 1:             %.1f%%\n", 100 * mean(sub$phi < 1, na.rm=T)))
}
## 
##   P1_SVB (N=67):
##     Mean phi (C+OMO)/D^U:  0.5132
##     Median phi:            0.3938
##     Mean C/D^U:            0.1428
##     Median C/D^U:          0.0813
##     Mean OMO/D^U:          0.3704
##     Mean g_phase/D^U:      0.1194
##     Median g_phase/D^U:    0.0397
##     Mean v (pp):           8.222
##     % v < 0:               0.0%
##     % phi < 1:             83.6%
## 
##   P2_BTFP_Active (N=397):
##     Mean phi (C+OMO)/D^U:  1.0060
##     Median phi:            0.4235
##     Mean C/D^U:            0.5417
##     Median C/D^U:          0.1348
##     Mean OMO/D^U:          0.4643
##     Mean g_phase/D^U:      0.3439
##     Median g_phase/D^U:    0.0010
##     Mean v (pp):           9.278
##     % v < 0:               1.5%
##     % phi < 1:             81.4%
## 
##   P3_FRC (N=101):
##     Mean phi (C+OMO)/D^U:  0.6598
##     Median phi:            0.4465
##     Mean C/D^U:            0.2469
##     Median C/D^U:          0.1226
##     Mean OMO/D^U:          0.4129
##     Mean g_phase/D^U:      0.1312
##     Median g_phase/D^U:    0.0267
##     Mean v (pp):           9.585
##     % v < 0:               1.0%
##     % phi < 1:             76.2%
## 
##   Arbitrage (N=371):
##     Mean phi (C+OMO)/D^U:  0.7223
##     Median phi:            0.4392
##     Mean C/D^U:            0.2948
##     Median C/D^U:          0.1518
##     Mean OMO/D^U:          0.4274
##     Mean g_phase/D^U:      0.1505
##     Median g_phase/D^U:    0.0002
##     Mean v (pp):           9.846
##     % v < 0:               0.8%
##     % phi < 1:             80.3%
# BTFP borrowers by phase
cat("\n\n=== BTFP Borrowers by Phase ===\n")
## 
## 
## === BTFP Borrowers by Phase ===
for (ph in c("P2_BTFP_Active", "P3_FRC", "Arbitrage")) {
  banks_in_phase <- btfp_by_phase %>% filter(phase == ph) %>% pull(idrssd) %>% unique()

  if (length(banks_in_phase) == 0) {
    cat(sprintf("  %s: no BTFP borrowers\n", ph))
    next
  }

  sub <- df %>%
    filter(idrssd %in% banks_in_phase) %>%
    left_join(btfp_by_phase %>% filter(phase == ph) %>% select(idrssd, btfp_g_phase), by = "idrssd") %>%
    mutate(g_phase_du = safe_div(btfp_g_phase / 1000, D_U, NA_real_))

  cat(sprintf("\n  %s (N=%d):\n", ph, nrow(sub)))
  cat(sprintf("    Mean phi (C+OMO)/D^U:  %.4f\n", mean(sub$phi, na.rm=T)))
  cat(sprintf("    Median phi:            %.4f\n", median(sub$phi, na.rm=T)))
  cat(sprintf("    Mean C/D^U:            %.4f\n", mean(sub$phi_C, na.rm=T)))
  cat(sprintf("    Median C/D^U:          %.4f\n", median(sub$phi_C, na.rm=T)))
  cat(sprintf("    Mean OMO/D^U:          %.4f\n", mean(sub$phi_omo, na.rm=T)))
  cat(sprintf("    Mean g_phase/D^U:      %.4f\n", mean(sub$g_phase_du, na.rm=T)))
  cat(sprintf("    Median g_phase/D^U:    %.4f\n", median(sub$g_phase_du, na.rm=T)))
  cat(sprintf("    Mean v (pp):           %.3f\n", mean(sub$v_pp, na.rm=T)))
  cat(sprintf("    %% v < 0:               %.1f%%\n", 100 * mean(sub$run_possible, na.rm=T)))
  cat(sprintf("    %% phi < 1:             %.1f%%\n", 100 * mean(sub$phi < 1, na.rm=T)))
}
## 
##   P2_BTFP_Active (N=460):
##     Mean phi (C+OMO)/D^U:  0.6813
##     Median phi:            0.4939
##     Mean C/D^U:            0.2095
##     Median C/D^U:          0.1284
##     Mean OMO/D^U:          0.4718
##     Mean g_phase/D^U:      0.3247
##     Median g_phase/D^U:    0.1613
##     Mean v (pp):           8.394
##     % v < 0:               1.7%
##     % phi < 1:             84.3%
## 
##   P3_FRC (N=182):
##     Mean phi (C+OMO)/D^U:  0.6362
##     Median phi:            0.5201
##     Mean C/D^U:            0.1754
##     Median C/D^U:          0.1288
##     Mean OMO/D^U:          0.4608
##     Mean g_phase/D^U:      0.1588
##     Median g_phase/D^U:    0.0953
##     Mean v (pp):           8.509
##     % v < 0:               1.1%
##     % phi < 1:             84.6%
## 
##   Arbitrage (N=772):
##     Mean phi (C+OMO)/D^U:  0.7294
##     Median phi:            0.5368
##     Mean C/D^U:            0.2476
##     Median C/D^U:          0.1442
##     Mean OMO/D^U:          0.4818
##     Mean g_phase/D^U:      0.7370
##     Median g_phase/D^U:    0.2379
##     Mean v (pp):           8.995
##     % v < 0:               0.6%
##     % phi < 1:             80.6%
# ==================================================================
# KEY COMPARISON TABLE: DW vs BTFP side by side
# ==================================================================
cat("\n\n==============================================================\n")
## 
## 
## ==============================================================
cat("SUMMARY TABLE: DW vs BTFP Borrowers (Crisis Window, 2022Q4 baseline)\n")
## SUMMARY TABLE: DW vs BTFP Borrowers (Crisis Window, 2022Q4 baseline)
cat("==============================================================\n\n")
## ==============================================================
crisis_dw_banks <- dw_loans %>%
  filter(dw_loan_date >= CRISIS_START, dw_loan_date <= CRISIS_END) %>%
  pull(rssd_id) %>% unique()
crisis_btfp_banks <- btfp_loans %>%
  filter(btfp_loan_date >= CRISIS_START, btfp_loan_date <= CRISIS_END) %>%
  pull(rssd_id) %>% unique()

# DW-only during crisis (exclude banks that also used BTFP)
crisis_dw_only <- setdiff(crisis_dw_banks, crisis_btfp_banks)
crisis_btfp_only <- setdiff(crisis_btfp_banks, crisis_dw_banks)
crisis_both <- intersect(crisis_dw_banks, crisis_btfp_banks)

comparison <- bind_rows(
  df %>% filter(idrssd %in% crisis_dw_only) %>% phi_summary("DW Only (crisis)"),
  df %>% filter(idrssd %in% crisis_btfp_only) %>% phi_summary("BTFP Only (crisis)"),
  df %>% filter(idrssd %in% crisis_both) %>% phi_summary("Both (crisis)"),
  df %>% filter(borrowed == 0) %>% phi_summary("Non-Borrower")
)

print(comparison %>% select(label, N, median_phi, median_C_DU, mean_OMO_DU,
                            median_g_DU, mean_v, pct_v_neg, pct_phi_lt1))
## # A tibble: 4 × 9
##   label        N median_phi median_C_DU mean_OMO_DU median_g_DU mean_v pct_v_neg
##   <chr>    <int>      <dbl>       <dbl>       <dbl>       <dbl>  <dbl>     <dbl>
## 1 DW Only…   341      0.439       0.160       0.483      0.0181   9.9        0.9
## 2 BTFP On…   412      0.512       0.141       0.498      0.416    8.64       1.5
## 3 Both (c…   107      0.446       0.101       0.372      0.494    7.86       2.8
## 4 Non-Bor…  2587      0.760       0.337       0.715      0       12.8        0.6
## # ℹ 1 more variable: pct_phi_lt1 <dbl>
cat("BTFP borrowers: phi < 1 (cannot cover full run) is the BINDING constraint.\n")
## BTFP borrowers: phi < 1 (cannot cover full run) is the BINDING constraint.
cat("DW borrowers:   phi >= 1 possible (cash-rich), so v < 0 (insolvency) is binding.\n\n")
## DW borrowers:   phi >= 1 possible (cash-rich), so v < 0 (insolvency) is binding.
# T-test: C/D^U between DW-only and BTFP-only crisis borrowers
dw_only_data <- df %>% filter(idrssd %in% crisis_dw_only)
btfp_only_data <- df %>% filter(idrssd %in% crisis_btfp_only)

tt <- t.test(dw_only_data$phi_C, btfp_only_data$phi_C)
cat(sprintf("C/D^U: DW-only mean=%.4f, BTFP-only mean=%.4f, diff=%.4f, t=%.2f, p=%.4f\n",
    mean(dw_only_data$phi_C, na.rm=T),
    mean(btfp_only_data$phi_C, na.rm=T),
    mean(dw_only_data$phi_C, na.rm=T) - mean(btfp_only_data$phi_C, na.rm=T),
    tt$statistic, tt$p.value))
## C/D^U: DW-only mean=0.6237, BTFP-only mean=0.2238, diff=0.3999, t=1.26, p=0.2080
tt2 <- t.test(dw_only_data$phi, btfp_only_data$phi)
cat(sprintf("phi:   DW-only mean=%.4f, BTFP-only mean=%.4f, diff=%.4f, t=%.2f, p=%.4f\n",
    mean(dw_only_data$phi, na.rm=T),
    mean(btfp_only_data$phi, na.rm=T),
    mean(dw_only_data$phi, na.rm=T) - mean(btfp_only_data$phi, na.rm=T),
    tt2$statistic, tt2$p.value))
## phi:   DW-only mean=1.1071, BTFP-only mean=0.7221, diff=0.3850, t=0.92, p=0.3561
# Fraction with phi >= 1 by group
cat(sprintf("\n%% phi >= 1 (can cover full run pre-crisis):\n"))
## 
## % phi >= 1 (can cover full run pre-crisis):
cat(sprintf("  DW-only:   %.1f%%\n", 100 * mean(dw_only_data$phi >= 1, na.rm=T)))
##   DW-only:   22.0%
cat(sprintf("  BTFP-only: %.1f%%\n", 100 * mean(btfp_only_data$phi >= 1, na.rm=T)))
##   BTFP-only: 17.3%
cat(sprintf("  Both:      %.1f%%\n", 100 * mean(df$phi[df$idrssd %in% crisis_both] >= 1, na.rm=T)))
##   Both:      11.2%
# Among phi >= 1 banks (DW borrowers with sufficient cash):
# What fraction have v < 0?
cat(sprintf("\nAmong DW-only crisis borrowers with phi >= 1 (cash-rich):\n"))
## 
## Among DW-only crisis borrowers with phi >= 1 (cash-rich):
dw_cash_rich <- dw_only_data %>% filter(phi >= 1)
cat(sprintf("  N = %d\n", nrow(dw_cash_rich)))
##   N = 75
cat(sprintf("  Mean v (pp): %.3f\n", mean(dw_cash_rich$v_pp, na.rm=T)))
##   Mean v (pp): 11.526
cat(sprintf("  %% v < 0:     %.1f%%\n", 100 * mean(dw_cash_rich$run_possible, na.rm=T)))
##   % v < 0:     0.0%
cat(sprintf("  Mean C/D^U:  %.4f\n", mean(dw_cash_rich$phi_C, na.rm=T)))
##   Mean C/D^U:  2.1767
cat(sprintf("  These banks had CASH to cover a run but borrowed anyway.\n"))
##   These banks had CASH to cover a run but borrowed anyway.
cat(sprintf("  If v < 0, insolvency risk is the binding constraint.\n"))
##   If v < 0, insolvency risk is the binding constraint.
cat(sprintf("  If v > 0, they may have borrowed for precautionary reasons\n"))
##   If v > 0, they may have borrowed for precautionary reasons
cat(sprintf("  or because outflows exceeded what they were willing to\n"))
##   or because outflows exceeded what they were willing to
cat(sprintf("  absorb from cash alone (preserving liquidity buffer).\n"))
##   absorb from cash alone (preserving liquidity buffer).
# ==================================================================
# NOTE ON QUARTERLY DATA
# ==================================================================
cat("\n\n==============================================================\n")
## 
## 
## ==============================================================
cat("Quarterly baseline data\n")
## Quarterly baseline data
cat("==============================================================\n")
## ==============================================================
cat("Current analysis uses 2022Q4 for ALL periods.\n")
## Current analysis uses 2022Q4 for ALL periods.
cat("This is clean for P1 (SVB week, Mar 8-12) since no crisis\n")
## This is clean for P1 (SVB week, Mar 8-12) since no crisis
cat("borrowing could have occurred before then.\n\n")
## borrowing could have occurred before then.
cat("For P2 (Mar 13 - Apr 27) and later\n")
## For P2 (Mar 13 - Apr 27) and later
cat("using 2023Q1 or latest prior quarter. To do this:\n")
## using 2023Q1 or latest prior quarter. To do this:
cat("  1. Load call_q for period == '2023Q1'\n")
##   1. Load call_q for period == '2023Q1'
cat("  2. Subtract any borrowing that occurred in Q1 from cash\n")
##   2. Subtract any borrowing that occurred in Q1 from cash
cat("     (to get the 'pre-borrowing' cash position)\n")
##      (to get the 'pre-borrowing' cash position)
cat("  3. Recompute phi = (C_adj + OMO_MV) / D_U\n")
##   3. Recompute phi = (C_adj + OMO_MV) / D_U
cat("This ensures phi reflects the bank's ACTUAL pre-crisis\n")
## This ensures phi reflects the bank's ACTUAL pre-crisis
cat("liquidity position at the time it borrowed, not a stale\n")
## liquidity position at the time it borrowed, not a stale
cat("baseline from 3+ months earlier.\n")
## baseline from 3+ months earlier.
cat("For THIS submission, 2022Q4 is defensible for all crisis\n")
## For THIS submission, 2022Q4 is defensible for all crisis
cat("sub-periods since positions were predetermined.\n")
## sub-periods since positions were predetermined.

5 ============================================================

6 B15. Cash deficit vs borrowing by phase, with updated baseline

7 ============================================================

8 What this does:

9 1) Compares DW-only vs BTFP-only vs Both within each phase.

10 2) Uses 2022Q4 for SVB week.

11 3) Uses 2023Q1 for P2/P3.

12 4) Uses latest prior quarter for Arbitrage (will be 2023Q3).

13 5) Nets out prior outstanding DW/BTFP borrowing from cash

14 when the baseline quarter is post-crisis, so quarter-end

15 cash is not mechanically inflated by earlier borrowing.

16 6) Reports whether the binding margin is:

17 - liquidity shortfall: phi_adj < 1

18 - solvency/run margin: phi_adj >= 1 AND v < 0

19

20 IMPORTANT:

21 - Loan amounts are in dollars.

22 - Call report balances are in $000.

23 - So all borrowing amounts are divided by 1000 when merged

24 into the bank-level panel.

cat("==============================================================\n")
## ==============================================================
cat(" CASH DEFICIT VS BORROWING BY PHASE\n")
##  CASH DEFICIT VS BORROWING BY PHASE
cat("==============================================================\n\n")
## ==============================================================
# -----------------------------
# 0. Phase definitions
# -----------------------------
phase_defs <- tibble(
  phase = c("P1_SVB", "P2_BTFP_Active", "P3_FRC", "Arbitrage"),
  start = as.Date(c("2023-03-08", "2023-03-13", "2023-04-28", "2023-11-15")),
  end   = as.Date(c("2023-03-12", "2023-04-27", "2023-05-04", "2024-01-24"))
)

# -----------------------------
# 1. Quarter helpers
# -----------------------------
period_to_qend <- function(period_vec) {
  yr <- as.integer(substr(period_vec, 1, 4))
  q  <- as.integer(sub(".*Q", "", period_vec))
  q_end_map <- c("03-31", "06-30", "09-30", "12-31")
  as.Date(sprintf("%04d-%s", yr, q_end_map[q]))
}

period_lookup <- tibble(period = unique(call_q$period)) %>%
  mutate(q_end = period_to_qend(period)) %>%
  distinct() %>%
  arrange(q_end)

latest_prior_period <- function(date_x) {
  out <- period_lookup %>%
    filter(q_end < date_x) %>%
    arrange(q_end) %>%
    slice_tail(n = 1) %>%
    pull(period)
  if (length(out) == 0) NA_character_ else out
}

# baseline choice:
# - P1: 2022Q4
# - P2/P3: 2023Q1
# - Arbitrage: latest prior quarter (=> 2023Q3)
get_baseline_period <- function(phase_name, phase_start) {
  case_when(
    phase_name == "P1_SVB" ~ "2022Q4",
    phase_name == "P2_BTFP_Active" ~ "2022Q4",
    phase_name == "P3_FRC" ~ "2023Q1",
    phase_name == "Arbitrage" ~ "2023Q3",
    TRUE ~ latest_prior_period(phase_start)
  )
}

# If you want a stricter "latest quarter before phase start" rule for ALL phases,
# replace get_baseline_period() with:
# get_baseline_period <- function(phase_name, phase_start) latest_prior_period(phase_start)

# -----------------------------
# 2. Outstanding borrowing as of a quarter-end date
# -----------------------------
dw_outstanding_asof <- function(as_of_date) {
  dw_loans %>%
    filter(
      dw_loan_date <= as_of_date,
      is.na(dw_repayment_date) | dw_repayment_date > as_of_date
    ) %>%
    group_by(rssd_id) %>%
    summarise(dw_prior_out = sum(dw_loan_amount, na.rm = TRUE), .groups = "drop") %>%
    transmute(idrssd = rssd_id, dw_prior_out)
}

btfp_outstanding_asof <- function(as_of_date) {
  btfp_loans %>%
    filter(
      btfp_loan_date <= as_of_date,
      is.na(btfp_repayment_date) | btfp_repayment_date > as_of_date
    ) %>%
    group_by(rssd_id) %>%
    summarise(btfp_prior_out = sum(btfp_loan_amount, na.rm = TRUE), .groups = "drop") %>%
    transmute(idrssd = rssd_id, btfp_prior_out)
}

# -----------------------------
# 3. Rebuild structural panel for any baseline period
#    Uses the same formulas as your current script
# -----------------------------
build_structural_panel <- function(period_use) {

  x <- call_q %>%
    filter(period == period_use, !idrssd %in% excluded_banks) %>%
    left_join(dssw_beta_2022q4, by = "idrssd") %>%
    { if (HAS_DEPOSIT_COSTS) left_join(., deposit_costs_2022q4, by = "idrssd") else . }

  # ---- Franchise value inputs (same as current script) ----
  x <- x %>%
    mutate(
      beta_u_clipped = pmin(pmax(ifelse(!is.na(beta_uninsured), beta_uninsured, NA_real_), 0), 1),
      cost_u_raw = ifelse(HAS_DEPOSIT_COSTS & !is.na(deposit_cost_uninsured),
                          deposit_cost_uninsured, 0),
      mu_decimal = uninsured_deposit / total_asset,
      d_over_ta  = (insured_deposit + uninsured_deposit) / total_asset,
      insured_share = safe_div(insured_deposit, insured_deposit + uninsured_deposit, NA_real_),
      uninsured_share_d = safe_div(uninsured_deposit, insured_deposit + uninsured_deposit, NA_real_),
      gross_rent = (1 - beta_u_clipped) * y_10yr,
      net_rent   = gross_rent - cost_u_raw,
      f_pp = ifelse(!is.na(beta_u_clipped),
                    pmax(net_rent * cap_factor * d_over_ta, 0) * 100, NA_real_),
      f_u_pp = ifelse(!is.na(f_pp) & !is.na(uninsured_share_d),
                      uninsured_share_d * f_pp, NA_real_)
    )

  # ---- Run value v ----
  x <- x %>%
    mutate(
      mtm_total_raw = mtm_loss_to_total_asset,
      book_eq_raw   = book_equity_to_total_asset,
      emv_pp = ifelse(!is.na(f_pp), book_eq_raw - mtm_total_raw + f_pp, NA_real_),
      v_pp   = ifelse(!is.na(emv_pp) & !is.na(f_u_pp), emv_pp - f_u_pp, NA_real_),
      run_possible = as.integer(!is.na(v_pp) & v_pp < 0)
    )

  # ---- OMO-eligible book value ----
  x$omo_book <- x$omo_eligible
  
  # ---- MTM loss on OMO bucket ----
  x$lambda_omo <- x$mtm_loss_omo_eligible
  
  # ---- Market value of OMO-eligible assets ----
  x <- x %>%
    mutate(
      omo_mv = pmax(omo_book - lambda_omo, 0)
    )

  # ---- phi ingredients ----
  x <- x %>%
    mutate(
      omo_mv = pmax(omo_book - lambda_omo, 0),
      C   = replace_na(cash, 0),
      D_U = replace_na(uninsured_deposit, 0),
      D   = replace_na(insured_deposit, 0) + D_U,
      phi_C_raw = safe_div(C, D_U, NA_real_),
      phi_omo   = safe_div(omo_mv, D_U, NA_real_),
      phi_raw   = safe_div(C + omo_mv, D_U, NA_real_),
      baseline_period = period_use
    )

  x
}

# -----------------------------
# 4. Build one phase sample
# -----------------------------
make_phase_sample <- function(phase_name, phase_start, phase_end) {

  base_period <- get_baseline_period(phase_name, phase_start)
  base_q_end  <- period_lookup$q_end[match(base_period, period_lookup$period)]

  base_panel <- build_structural_panel(base_period)

  # prior outstanding at the baseline quarter-end
  prior_dw   <- dw_outstanding_asof(base_q_end)
  prior_btfp <- btfp_outstanding_asof(base_q_end)

  # phase borrowing
  dw_phase <- dw_loans %>%
    filter(dw_loan_date >= phase_start, dw_loan_date <= phase_end) %>%
    group_by(rssd_id) %>%
    summarise(dw_g_phase = sum(dw_loan_amount, na.rm = TRUE),
              dw_n_phase = n(),
              .groups = "drop") %>%
    transmute(idrssd = rssd_id, dw_g_phase, dw_n_phase, phase_used_dw = 1L)

  btfp_phase <- btfp_loans %>%
    filter(btfp_loan_date >= phase_start, btfp_loan_date <= phase_end) %>%
    group_by(rssd_id) %>%
    summarise(btfp_g_phase = sum(btfp_loan_amount, na.rm = TRUE),
              btfp_n_phase = n(),
              .groups = "drop") %>%
    transmute(idrssd = rssd_id, btfp_g_phase, btfp_n_phase, phase_used_btfp = 1L)

  out <- base_panel %>%
    left_join(prior_dw, by = "idrssd") %>%
    left_join(prior_btfp, by = "idrssd") %>%
    left_join(dw_phase, by = "idrssd") %>%
    left_join(btfp_phase, by = "idrssd") %>%
    mutate(
      dw_prior_out   = replace_na(dw_prior_out, 0),
      btfp_prior_out = replace_na(btfp_prior_out, 0),
      dw_g_phase     = replace_na(dw_g_phase, 0),
      btfp_g_phase   = replace_na(btfp_g_phase, 0),
      dw_n_phase     = replace_na(dw_n_phase, 0L),
      btfp_n_phase   = replace_na(btfp_n_phase, 0L),
      phase_used_dw   = replace_na(phase_used_dw, 0L),
      phase_used_btfp = replace_na(phase_used_btfp, 0L),

      # Prior outstanding borrowing at baseline q-end, converted to $000
      prior_outstanding_000 = (dw_prior_out + btfp_prior_out) / 1000,
      prior_outstanding_du  = safe_div(prior_outstanding_000, D_U, NA_real_),

      # Adjust cash by removing previously borrowed funds already embedded in quarter-end cash
      cash_adj = pmax(C - prior_outstanding_000, 0),

      # Adjusted cash-only and total coverage
      phi_C_adj = safe_div(cash_adj, D_U, NA_real_),
      phi_adj   = safe_div(cash_adj + omo_mv, D_U, NA_real_),

      # Cash deficit relative to a full uninsured run
      cash_gap_du = pmax(1 - phi_C_adj, 0),

      # New borrowing in THIS phase only, converted to $000
      dw_g_phase_000   = dw_g_phase / 1000,
      btfp_g_phase_000 = btfp_g_phase / 1000,
      g_phase_000      = dw_g_phase_000 + btfp_g_phase_000,
      g_phase_du       = safe_div(g_phase_000, D_U, NA_real_),

      # How close is borrowing to the cash deficit?
      g_to_cash_gap    = ifelse(cash_gap_du > 0, g_phase_du / cash_gap_du, NA_real_),
      cash_gap_after_g = pmax(cash_gap_du - g_phase_du, 0),

      phase_group = case_when(
        phase_used_dw == 1L & phase_used_btfp == 0L ~ "DW only",
        phase_used_dw == 0L & phase_used_btfp == 1L ~ "BTFP only",
        phase_used_dw == 1L & phase_used_btfp == 1L ~ "Both",
        TRUE ~ "Non-borrower"
      ),

      liquidity_binding = as.integer(!is.na(phi_adj) & phi_adj < 1),
      solvency_binding  = as.integer(!is.na(phi_adj) & phi_adj >= 1 & run_possible == 1),

      phase = phase_name,
      phase_start = phase_start,
      phase_end   = phase_end,
      baseline_q_end = base_q_end
    ) %>%
    filter(phase_group != "Non-borrower")

  out
}

# -----------------------------
# 5. Build stacked bank-level sample
# -----------------------------
phase_sample_all <- bind_rows(lapply(seq_len(nrow(phase_defs)), function(i) {
  make_phase_sample(
    phase_name  = phase_defs$phase[i],
    phase_start = phase_defs$start[i],
    phase_end   = phase_defs$end[i]
  )
}))

# Phase ordering
phase_levels <- phase_defs$phase
group_levels <- c("DW only", "BTFP only", "Both")

phase_sample_all <- phase_sample_all %>%
  mutate(
    phase = factor(phase, levels = phase_levels),
    phase_group = factor(phase_group, levels = group_levels)
  )

cat("Baseline mapping used in this chunk:\n")
## Baseline mapping used in this chunk:
phase_sample_all %>%
  distinct(phase, baseline_period, baseline_q_end) %>%
  arrange(phase) %>%
  print()
## # A tibble: 4 × 3
##   phase          baseline_period baseline_q_end
##   <fct>          <chr>           <date>        
## 1 P1_SVB         2022Q4          2022-12-31    
## 2 P2_BTFP_Active 2022Q4          2022-12-31    
## 3 P3_FRC         2023Q1          2023-03-31    
## 4 Arbitrage      2023Q3          2023-09-30
# Save bank-level output
bank_out <- file.path(DATA_PROC, "phase_cash_gap_banklevel.csv")
write_csv(phase_sample_all, bank_out)
cat(sprintf("\nSaved bank-level file to:\n  %s\n\n", bank_out))
## 
## Saved bank-level file to:
##   C:/Users/mferdo2/OneDrive - Louisiana State University/Finance_PhD/DW_Stigma_paper/Liquidity_project_2025/01_data/processed/phase_cash_gap_banklevel.csv
# -----------------------------
# 6. Main summary table
# -----------------------------
phase_summary <- phase_sample_all %>%
  group_by(phase, baseline_period, phase_group) %>%
  summarise(
    N = n(),
    mean_C_DU_raw     = mean(phi_C_raw, na.rm = TRUE),
    mean_C_DU_adj     = mean(phi_C_adj, na.rm = TRUE),
    mean_cash_gap_DU  = mean(cash_gap_du, na.rm = TRUE),
    median_cash_gap_DU = median(cash_gap_du, na.rm = TRUE),
    mean_OMO_DU       = mean(phi_omo, na.rm = TRUE),
    mean_phi_adj      = mean(phi_adj, na.rm = TRUE),
    median_phi_adj    = median(phi_adj, na.rm = TRUE),
    mean_g_phase_DU   = mean(g_phase_du, na.rm = TRUE),
    median_g_phase_DU = median(g_phase_du, na.rm = TRUE),
    mean_g_to_gap     = mean(g_to_cash_gap, na.rm = TRUE),
    median_g_to_gap   = median(g_to_cash_gap, na.rm = TRUE),
    mean_prior_out_DU = mean(prior_outstanding_du, na.rm = TRUE),
    pct_phi_lt1       = 100 * mean(phi_adj < 1, na.rm = TRUE),
    pct_phi_ge1       = 100 * mean(phi_adj >= 1, na.rm = TRUE),
    pct_v_neg         = 100 * mean(run_possible == 1, na.rm = TRUE),
    pct_liq_binding   = 100 * mean(liquidity_binding == 1, na.rm = TRUE),
    pct_solv_binding  = 100 * mean(solvency_binding == 1, na.rm = TRUE),
    pct_g_covers_gap  = 100 * mean(g_phase_du >= cash_gap_du, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  mutate(
    across(-c(phase, baseline_period, phase_group, N), ~ round(.x, 3))
  ) %>%
  arrange(phase, phase_group)

kbl(
  phase_summary,
  format = "html",
  caption = "Cash Deficit vs Borrowing by Phase (Adjusted for Prior Outstanding Borrowing)"
) %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE) %>%
  print()
## <table class="table table-striped table-condensed" style="width: auto !important; margin-left: auto; margin-right: auto;">
## <caption>Cash Deficit vs Borrowing by Phase (Adjusted for Prior Outstanding Borrowing)</caption>
##  <thead>
##   <tr>
##    <th style="text-align:left;"> phase </th>
##    <th style="text-align:left;"> baseline_period </th>
##    <th style="text-align:left;"> phase_group </th>
##    <th style="text-align:right;"> N </th>
##    <th style="text-align:right;"> mean_C_DU_raw </th>
##    <th style="text-align:right;"> mean_C_DU_adj </th>
##    <th style="text-align:right;"> mean_cash_gap_DU </th>
##    <th style="text-align:right;"> median_cash_gap_DU </th>
##    <th style="text-align:right;"> mean_OMO_DU </th>
##    <th style="text-align:right;"> mean_phi_adj </th>
##    <th style="text-align:right;"> median_phi_adj </th>
##    <th style="text-align:right;"> mean_g_phase_DU </th>
##    <th style="text-align:right;"> median_g_phase_DU </th>
##    <th style="text-align:right;"> mean_g_to_gap </th>
##    <th style="text-align:right;"> median_g_to_gap </th>
##    <th style="text-align:right;"> mean_prior_out_DU </th>
##    <th style="text-align:right;"> pct_phi_lt1 </th>
##    <th style="text-align:right;"> pct_phi_ge1 </th>
##    <th style="text-align:right;"> pct_v_neg </th>
##    <th style="text-align:right;"> pct_liq_binding </th>
##    <th style="text-align:right;"> pct_solv_binding </th>
##    <th style="text-align:right;"> pct_g_covers_gap </th>
##   </tr>
##  </thead>
## <tbody>
##   <tr>
##    <td style="text-align:left;"> P1_SVB </td>
##    <td style="text-align:left;"> 2022Q4 </td>
##    <td style="text-align:left;"> DW only </td>
##    <td style="text-align:right;"> 67 </td>
##    <td style="text-align:right;"> 0.143 </td>
##    <td style="text-align:right;"> 0.143 </td>
##    <td style="text-align:right;"> 0.857 </td>
##    <td style="text-align:right;"> 0.919 </td>
##    <td style="text-align:right;"> 0.370 </td>
##    <td style="text-align:right;"> 0.513 </td>
##    <td style="text-align:right;"> 0.394 </td>
##    <td style="text-align:right;"> 0.119 </td>
##    <td style="text-align:right;"> 0.040 </td>
##    <td style="text-align:right;"> 0.148 </td>
##    <td style="text-align:right;"> 0.044 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 83.582 </td>
##    <td style="text-align:right;"> 16.418 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 83.582 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 1.493 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> P2_BTFP_Active </td>
##    <td style="text-align:left;"> 2022Q4 </td>
##    <td style="text-align:left;"> DW only </td>
##    <td style="text-align:right;"> 306 </td>
##    <td style="text-align:right;"> 0.658 </td>
##    <td style="text-align:right;"> 0.658 </td>
##    <td style="text-align:right;"> 0.734 </td>
##    <td style="text-align:right;"> 0.844 </td>
##    <td style="text-align:right;"> 0.497 </td>
##    <td style="text-align:right;"> 1.155 </td>
##    <td style="text-align:right;"> 0.426 </td>
##    <td style="text-align:right;"> 0.308 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 0.315 </td>
##    <td style="text-align:right;"> 0.001 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 78.758 </td>
##    <td style="text-align:right;"> 21.242 </td>
##    <td style="text-align:right;"> 0.980 </td>
##    <td style="text-align:right;"> 78.758 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 11.765 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> P2_BTFP_Active </td>
##    <td style="text-align:left;"> 2022Q4 </td>
##    <td style="text-align:left;"> BTFP only </td>
##    <td style="text-align:right;"> 369 </td>
##    <td style="text-align:right;"> 0.224 </td>
##    <td style="text-align:right;"> 0.224 </td>
##    <td style="text-align:right;"> 0.797 </td>
##    <td style="text-align:right;"> 0.860 </td>
##    <td style="text-align:right;"> 0.501 </td>
##    <td style="text-align:right;"> 0.725 </td>
##    <td style="text-align:right;"> 0.514 </td>
##    <td style="text-align:right;"> 0.348 </td>
##    <td style="text-align:right;"> 0.170 </td>
##    <td style="text-align:right;"> 0.395 </td>
##    <td style="text-align:right;"> 0.204 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 82.880 </td>
##    <td style="text-align:right;"> 17.120 </td>
##    <td style="text-align:right;"> 1.355 </td>
##    <td style="text-align:right;"> 82.656 </td>
##    <td style="text-align:right;"> 0.271 </td>
##    <td style="text-align:right;"> 8.967 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> P2_BTFP_Active </td>
##    <td style="text-align:left;"> 2022Q4 </td>
##    <td style="text-align:left;"> Both </td>
##    <td style="text-align:right;"> 91 </td>
##    <td style="text-align:right;"> 0.151 </td>
##    <td style="text-align:right;"> 0.151 </td>
##    <td style="text-align:right;"> 0.849 </td>
##    <td style="text-align:right;"> 0.899 </td>
##    <td style="text-align:right;"> 0.353 </td>
##    <td style="text-align:right;"> 0.504 </td>
##    <td style="text-align:right;"> 0.412 </td>
##    <td style="text-align:right;"> 0.697 </td>
##    <td style="text-align:right;"> 0.226 </td>
##    <td style="text-align:right;"> 0.811 </td>
##    <td style="text-align:right;"> 0.247 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 90.110 </td>
##    <td style="text-align:right;"> 9.890 </td>
##    <td style="text-align:right;"> 3.297 </td>
##    <td style="text-align:right;"> 90.110 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 15.385 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> P3_FRC </td>
##    <td style="text-align:left;"> 2023Q1 </td>
##    <td style="text-align:left;"> DW only </td>
##    <td style="text-align:right;"> 87 </td>
##    <td style="text-align:right;"> 0.267 </td>
##    <td style="text-align:right;"> 0.225 </td>
##    <td style="text-align:right;"> 0.792 </td>
##    <td style="text-align:right;"> 0.889 </td>
##    <td style="text-align:right;"> 0.454 </td>
##    <td style="text-align:right;"> 0.678 </td>
##    <td style="text-align:right;"> 0.393 </td>
##    <td style="text-align:right;"> 0.157 </td>
##    <td style="text-align:right;"> 0.027 </td>
##    <td style="text-align:right;"> 0.182 </td>
##    <td style="text-align:right;"> 0.037 </td>
##    <td style="text-align:right;"> 0.059 </td>
##    <td style="text-align:right;"> 82.759 </td>
##    <td style="text-align:right;"> 17.241 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 82.759 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 9.195 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> P3_FRC </td>
##    <td style="text-align:left;"> 2023Q1 </td>
##    <td style="text-align:left;"> BTFP only </td>
##    <td style="text-align:right;"> 168 </td>
##    <td style="text-align:right;"> 0.180 </td>
##    <td style="text-align:right;"> 0.145 </td>
##    <td style="text-align:right;"> 0.855 </td>
##    <td style="text-align:right;"> 0.879 </td>
##    <td style="text-align:right;"> 0.491 </td>
##    <td style="text-align:right;"> 0.636 </td>
##    <td style="text-align:right;"> 0.505 </td>
##    <td style="text-align:right;"> 0.171 </td>
##    <td style="text-align:right;"> 0.104 </td>
##    <td style="text-align:right;"> 0.228 </td>
##    <td style="text-align:right;"> 0.116 </td>
##    <td style="text-align:right;"> 0.055 </td>
##    <td style="text-align:right;"> 83.929 </td>
##    <td style="text-align:right;"> 16.071 </td>
##    <td style="text-align:right;"> 0.595 </td>
##    <td style="text-align:right;"> 83.929 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 4.167 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> P3_FRC </td>
##    <td style="text-align:left;"> 2023Q1 </td>
##    <td style="text-align:left;"> Both </td>
##    <td style="text-align:right;"> 14 </td>
##    <td style="text-align:right;"> 0.242 </td>
##    <td style="text-align:right;"> 0.202 </td>
##    <td style="text-align:right;"> 0.800 </td>
##    <td style="text-align:right;"> 0.933 </td>
##    <td style="text-align:right;"> 0.632 </td>
##    <td style="text-align:right;"> 0.834 </td>
##    <td style="text-align:right;"> 0.694 </td>
##    <td style="text-align:right;"> 0.273 </td>
##    <td style="text-align:right;"> 0.132 </td>
##    <td style="text-align:right;"> 0.355 </td>
##    <td style="text-align:right;"> 0.148 </td>
##    <td style="text-align:right;"> 0.082 </td>
##    <td style="text-align:right;"> 78.571 </td>
##    <td style="text-align:right;"> 21.429 </td>
##    <td style="text-align:right;"> 14.286 </td>
##    <td style="text-align:right;"> 78.571 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 21.429 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Arbitrage </td>
##    <td style="text-align:left;"> 2023Q3 </td>
##    <td style="text-align:left;"> DW only </td>
##    <td style="text-align:right;"> 257 </td>
##    <td style="text-align:right;"> 0.444 </td>
##    <td style="text-align:right;"> 0.406 </td>
##    <td style="text-align:right;"> 0.722 </td>
##    <td style="text-align:right;"> 0.809 </td>
##    <td style="text-align:right;"> 0.552 </td>
##    <td style="text-align:right;"> 0.958 </td>
##    <td style="text-align:right;"> 0.532 </td>
##    <td style="text-align:right;"> 0.218 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 0.268 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 0.050 </td>
##    <td style="text-align:right;"> 80.156 </td>
##    <td style="text-align:right;"> 19.844 </td>
##    <td style="text-align:right;"> 0.778 </td>
##    <td style="text-align:right;"> 80.156 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 12.451 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Arbitrage </td>
##    <td style="text-align:left;"> 2023Q3 </td>
##    <td style="text-align:left;"> BTFP only </td>
##    <td style="text-align:right;"> 655 </td>
##    <td style="text-align:right;"> 0.269 </td>
##    <td style="text-align:right;"> 0.168 </td>
##    <td style="text-align:right;"> 0.846 </td>
##    <td style="text-align:right;"> 0.906 </td>
##    <td style="text-align:right;"> 0.529 </td>
##    <td style="text-align:right;"> 0.697 </td>
##    <td style="text-align:right;"> 0.492 </td>
##    <td style="text-align:right;"> 0.778 </td>
##    <td style="text-align:right;"> 0.281 </td>
##    <td style="text-align:right;"> 0.879 </td>
##    <td style="text-align:right;"> 0.310 </td>
##    <td style="text-align:right;"> 0.162 </td>
##    <td style="text-align:right;"> 80.916 </td>
##    <td style="text-align:right;"> 19.084 </td>
##    <td style="text-align:right;"> 1.221 </td>
##    <td style="text-align:right;"> 80.916 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 25.038 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Arbitrage </td>
##    <td style="text-align:left;"> 2023Q3 </td>
##    <td style="text-align:left;"> Both </td>
##    <td style="text-align:right;"> 111 </td>
##    <td style="text-align:right;"> 0.255 </td>
##    <td style="text-align:right;"> 0.172 </td>
##    <td style="text-align:right;"> 0.842 </td>
##    <td style="text-align:right;"> 0.914 </td>
##    <td style="text-align:right;"> 0.444 </td>
##    <td style="text-align:right;"> 0.615 </td>
##    <td style="text-align:right;"> 0.398 </td>
##    <td style="text-align:right;"> 0.667 </td>
##    <td style="text-align:right;"> 0.236 </td>
##    <td style="text-align:right;"> 0.699 </td>
##    <td style="text-align:right;"> 0.229 </td>
##    <td style="text-align:right;"> 0.127 </td>
##    <td style="text-align:right;"> 84.685 </td>
##    <td style="text-align:right;"> 15.315 </td>
##    <td style="text-align:right;"> 0.901 </td>
##    <td style="text-align:right;"> 84.685 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 26.126 </td>
##   </tr>
## </tbody>
## </table>
save_kbl_latex(
  phase_summary,
  "Table_CashGap_ByPhase",
  caption = "Cash Deficit vs Borrowing by Phase (Adjusted for Prior Outstanding Borrowing)"
)
## Saved: Table_CashGap_ByPhase.tex
# -----------------------------
# 7. Focused DW-only vs BTFP-only comparison
# -----------------------------
focus <- phase_summary %>%
  filter(phase_group %in% c("DW only", "BTFP only"))

kbl(
  focus,
  format = "html",
  caption = "DW-only vs BTFP-only: Cash Deficit vs Borrowing by Phase"
) %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE) %>%
  print()
## <table class="table table-striped table-condensed" style="width: auto !important; margin-left: auto; margin-right: auto;">
## <caption>DW-only vs BTFP-only: Cash Deficit vs Borrowing by Phase</caption>
##  <thead>
##   <tr>
##    <th style="text-align:left;"> phase </th>
##    <th style="text-align:left;"> baseline_period </th>
##    <th style="text-align:left;"> phase_group </th>
##    <th style="text-align:right;"> N </th>
##    <th style="text-align:right;"> mean_C_DU_raw </th>
##    <th style="text-align:right;"> mean_C_DU_adj </th>
##    <th style="text-align:right;"> mean_cash_gap_DU </th>
##    <th style="text-align:right;"> median_cash_gap_DU </th>
##    <th style="text-align:right;"> mean_OMO_DU </th>
##    <th style="text-align:right;"> mean_phi_adj </th>
##    <th style="text-align:right;"> median_phi_adj </th>
##    <th style="text-align:right;"> mean_g_phase_DU </th>
##    <th style="text-align:right;"> median_g_phase_DU </th>
##    <th style="text-align:right;"> mean_g_to_gap </th>
##    <th style="text-align:right;"> median_g_to_gap </th>
##    <th style="text-align:right;"> mean_prior_out_DU </th>
##    <th style="text-align:right;"> pct_phi_lt1 </th>
##    <th style="text-align:right;"> pct_phi_ge1 </th>
##    <th style="text-align:right;"> pct_v_neg </th>
##    <th style="text-align:right;"> pct_liq_binding </th>
##    <th style="text-align:right;"> pct_solv_binding </th>
##    <th style="text-align:right;"> pct_g_covers_gap </th>
##   </tr>
##  </thead>
## <tbody>
##   <tr>
##    <td style="text-align:left;"> P1_SVB </td>
##    <td style="text-align:left;"> 2022Q4 </td>
##    <td style="text-align:left;"> DW only </td>
##    <td style="text-align:right;"> 67 </td>
##    <td style="text-align:right;"> 0.143 </td>
##    <td style="text-align:right;"> 0.143 </td>
##    <td style="text-align:right;"> 0.857 </td>
##    <td style="text-align:right;"> 0.919 </td>
##    <td style="text-align:right;"> 0.370 </td>
##    <td style="text-align:right;"> 0.513 </td>
##    <td style="text-align:right;"> 0.394 </td>
##    <td style="text-align:right;"> 0.119 </td>
##    <td style="text-align:right;"> 0.040 </td>
##    <td style="text-align:right;"> 0.148 </td>
##    <td style="text-align:right;"> 0.044 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 83.582 </td>
##    <td style="text-align:right;"> 16.418 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 83.582 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 1.493 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> P2_BTFP_Active </td>
##    <td style="text-align:left;"> 2022Q4 </td>
##    <td style="text-align:left;"> DW only </td>
##    <td style="text-align:right;"> 306 </td>
##    <td style="text-align:right;"> 0.658 </td>
##    <td style="text-align:right;"> 0.658 </td>
##    <td style="text-align:right;"> 0.734 </td>
##    <td style="text-align:right;"> 0.844 </td>
##    <td style="text-align:right;"> 0.497 </td>
##    <td style="text-align:right;"> 1.155 </td>
##    <td style="text-align:right;"> 0.426 </td>
##    <td style="text-align:right;"> 0.308 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 0.315 </td>
##    <td style="text-align:right;"> 0.001 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 78.758 </td>
##    <td style="text-align:right;"> 21.242 </td>
##    <td style="text-align:right;"> 0.980 </td>
##    <td style="text-align:right;"> 78.758 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 11.765 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> P2_BTFP_Active </td>
##    <td style="text-align:left;"> 2022Q4 </td>
##    <td style="text-align:left;"> BTFP only </td>
##    <td style="text-align:right;"> 369 </td>
##    <td style="text-align:right;"> 0.224 </td>
##    <td style="text-align:right;"> 0.224 </td>
##    <td style="text-align:right;"> 0.797 </td>
##    <td style="text-align:right;"> 0.860 </td>
##    <td style="text-align:right;"> 0.501 </td>
##    <td style="text-align:right;"> 0.725 </td>
##    <td style="text-align:right;"> 0.514 </td>
##    <td style="text-align:right;"> 0.348 </td>
##    <td style="text-align:right;"> 0.170 </td>
##    <td style="text-align:right;"> 0.395 </td>
##    <td style="text-align:right;"> 0.204 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 82.880 </td>
##    <td style="text-align:right;"> 17.120 </td>
##    <td style="text-align:right;"> 1.355 </td>
##    <td style="text-align:right;"> 82.656 </td>
##    <td style="text-align:right;"> 0.271 </td>
##    <td style="text-align:right;"> 8.967 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> P3_FRC </td>
##    <td style="text-align:left;"> 2023Q1 </td>
##    <td style="text-align:left;"> DW only </td>
##    <td style="text-align:right;"> 87 </td>
##    <td style="text-align:right;"> 0.267 </td>
##    <td style="text-align:right;"> 0.225 </td>
##    <td style="text-align:right;"> 0.792 </td>
##    <td style="text-align:right;"> 0.889 </td>
##    <td style="text-align:right;"> 0.454 </td>
##    <td style="text-align:right;"> 0.678 </td>
##    <td style="text-align:right;"> 0.393 </td>
##    <td style="text-align:right;"> 0.157 </td>
##    <td style="text-align:right;"> 0.027 </td>
##    <td style="text-align:right;"> 0.182 </td>
##    <td style="text-align:right;"> 0.037 </td>
##    <td style="text-align:right;"> 0.059 </td>
##    <td style="text-align:right;"> 82.759 </td>
##    <td style="text-align:right;"> 17.241 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 82.759 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 9.195 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> P3_FRC </td>
##    <td style="text-align:left;"> 2023Q1 </td>
##    <td style="text-align:left;"> BTFP only </td>
##    <td style="text-align:right;"> 168 </td>
##    <td style="text-align:right;"> 0.180 </td>
##    <td style="text-align:right;"> 0.145 </td>
##    <td style="text-align:right;"> 0.855 </td>
##    <td style="text-align:right;"> 0.879 </td>
##    <td style="text-align:right;"> 0.491 </td>
##    <td style="text-align:right;"> 0.636 </td>
##    <td style="text-align:right;"> 0.505 </td>
##    <td style="text-align:right;"> 0.171 </td>
##    <td style="text-align:right;"> 0.104 </td>
##    <td style="text-align:right;"> 0.228 </td>
##    <td style="text-align:right;"> 0.116 </td>
##    <td style="text-align:right;"> 0.055 </td>
##    <td style="text-align:right;"> 83.929 </td>
##    <td style="text-align:right;"> 16.071 </td>
##    <td style="text-align:right;"> 0.595 </td>
##    <td style="text-align:right;"> 83.929 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 4.167 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Arbitrage </td>
##    <td style="text-align:left;"> 2023Q3 </td>
##    <td style="text-align:left;"> DW only </td>
##    <td style="text-align:right;"> 257 </td>
##    <td style="text-align:right;"> 0.444 </td>
##    <td style="text-align:right;"> 0.406 </td>
##    <td style="text-align:right;"> 0.722 </td>
##    <td style="text-align:right;"> 0.809 </td>
##    <td style="text-align:right;"> 0.552 </td>
##    <td style="text-align:right;"> 0.958 </td>
##    <td style="text-align:right;"> 0.532 </td>
##    <td style="text-align:right;"> 0.218 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 0.268 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 0.050 </td>
##    <td style="text-align:right;"> 80.156 </td>
##    <td style="text-align:right;"> 19.844 </td>
##    <td style="text-align:right;"> 0.778 </td>
##    <td style="text-align:right;"> 80.156 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 12.451 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Arbitrage </td>
##    <td style="text-align:left;"> 2023Q3 </td>
##    <td style="text-align:left;"> BTFP only </td>
##    <td style="text-align:right;"> 655 </td>
##    <td style="text-align:right;"> 0.269 </td>
##    <td style="text-align:right;"> 0.168 </td>
##    <td style="text-align:right;"> 0.846 </td>
##    <td style="text-align:right;"> 0.906 </td>
##    <td style="text-align:right;"> 0.529 </td>
##    <td style="text-align:right;"> 0.697 </td>
##    <td style="text-align:right;"> 0.492 </td>
##    <td style="text-align:right;"> 0.778 </td>
##    <td style="text-align:right;"> 0.281 </td>
##    <td style="text-align:right;"> 0.879 </td>
##    <td style="text-align:right;"> 0.310 </td>
##    <td style="text-align:right;"> 0.162 </td>
##    <td style="text-align:right;"> 80.916 </td>
##    <td style="text-align:right;"> 19.084 </td>
##    <td style="text-align:right;"> 1.221 </td>
##    <td style="text-align:right;"> 80.916 </td>
##    <td style="text-align:right;"> 0.000 </td>
##    <td style="text-align:right;"> 25.038 </td>
##   </tr>
## </tbody>
## </table>
save_kbl_latex(
  focus,
  "Table_CashGap_DWvsBTFP",
  caption = "DW-only vs BTFP-only: Cash Deficit vs Borrowing by Phase"
)
## Saved: Table_CashGap_DWvsBTFP.tex
# -----------------------------
# 8. Conditional solvency test among banks with phi_adj >= 1
#    "second condition binding" idea
# -----------------------------
conditional_binding <- phase_sample_all %>%
  filter(phase_group %in% c("DW only", "BTFP only", "Both"), phi_adj >= 1) %>%
  group_by(phase, baseline_period, phase_group) %>%
  summarise(
    N_phi_ge1 = n(),
    mean_phi_adj = mean(phi_adj, na.rm = TRUE),
    mean_C_DU_adj = mean(phi_C_adj, na.rm = TRUE),
    mean_g_phase_DU = mean(g_phase_du, na.rm = TRUE),
    mean_v_pp = mean(v_pp, na.rm = TRUE),
    pct_v_neg_given_phi_ge1 = 100 * mean(run_possible == 1, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  mutate(
    across(-c(phase, baseline_period, phase_group, N_phi_ge1), ~ round(.x, 3))
  ) %>%
  arrange(phase, phase_group)

kbl(
  conditional_binding,
  format = "html",
  caption = "Among Banks with phi_adj >= 1: Is v < 0 the Binding Margin?"
) %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE) %>%
  print()
## <table class="table table-striped table-condensed" style="width: auto !important; margin-left: auto; margin-right: auto;">
## <caption>Among Banks with phi_adj &gt;= 1: Is v &lt; 0 the Binding Margin?</caption>
##  <thead>
##   <tr>
##    <th style="text-align:left;"> phase </th>
##    <th style="text-align:left;"> baseline_period </th>
##    <th style="text-align:left;"> phase_group </th>
##    <th style="text-align:right;"> N_phi_ge1 </th>
##    <th style="text-align:right;"> mean_phi_adj </th>
##    <th style="text-align:right;"> mean_C_DU_adj </th>
##    <th style="text-align:right;"> mean_g_phase_DU </th>
##    <th style="text-align:right;"> mean_v_pp </th>
##    <th style="text-align:right;"> pct_v_neg_given_phi_ge1 </th>
##   </tr>
##  </thead>
## <tbody>
##   <tr>
##    <td style="text-align:left;"> P1_SVB </td>
##    <td style="text-align:left;"> 2022Q4 </td>
##    <td style="text-align:left;"> DW only </td>
##    <td style="text-align:right;"> 11 </td>
##    <td style="text-align:right;"> 1.356 </td>
##    <td style="text-align:right;"> 0.302 </td>
##    <td style="text-align:right;"> 0.178 </td>
##    <td style="text-align:right;"> 10.303 </td>
##    <td style="text-align:right;"> 0.000 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> P2_BTFP_Active </td>
##    <td style="text-align:left;"> 2022Q4 </td>
##    <td style="text-align:left;"> DW only </td>
##    <td style="text-align:right;"> 65 </td>
##    <td style="text-align:right;"> 3.981 </td>
##    <td style="text-align:right;"> 2.415 </td>
##    <td style="text-align:right;"> 0.754 </td>
##    <td style="text-align:right;"> 11.235 </td>
##    <td style="text-align:right;"> 0.000 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> P2_BTFP_Active </td>
##    <td style="text-align:left;"> 2022Q4 </td>
##    <td style="text-align:left;"> BTFP only </td>
##    <td style="text-align:right;"> 63 </td>
##    <td style="text-align:right;"> 2.010 </td>
##    <td style="text-align:right;"> 0.543 </td>
##    <td style="text-align:right;"> 0.954 </td>
##    <td style="text-align:right;"> 9.243 </td>
##    <td style="text-align:right;"> 1.587 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> P2_BTFP_Active </td>
##    <td style="text-align:left;"> 2022Q4 </td>
##    <td style="text-align:left;"> Both </td>
##    <td style="text-align:right;"> 9 </td>
##    <td style="text-align:right;"> 1.389 </td>
##    <td style="text-align:right;"> 0.264 </td>
##    <td style="text-align:right;"> 1.457 </td>
##    <td style="text-align:right;"> 9.890 </td>
##    <td style="text-align:right;"> 0.000 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> P3_FRC </td>
##    <td style="text-align:left;"> 2023Q1 </td>
##    <td style="text-align:left;"> DW only </td>
##    <td style="text-align:right;"> 15 </td>
##    <td style="text-align:right;"> 2.113 </td>
##    <td style="text-align:right;"> 0.630 </td>
##    <td style="text-align:right;"> 0.078 </td>
##    <td style="text-align:right;"> 13.103 </td>
##    <td style="text-align:right;"> 0.000 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> P3_FRC </td>
##    <td style="text-align:left;"> 2023Q1 </td>
##    <td style="text-align:left;"> BTFP only </td>
##    <td style="text-align:right;"> 27 </td>
##    <td style="text-align:right;"> 1.641 </td>
##    <td style="text-align:right;"> 0.278 </td>
##    <td style="text-align:right;"> 0.343 </td>
##    <td style="text-align:right;"> 9.934 </td>
##    <td style="text-align:right;"> 0.000 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> P3_FRC </td>
##    <td style="text-align:left;"> 2023Q1 </td>
##    <td style="text-align:left;"> Both </td>
##    <td style="text-align:right;"> 3 </td>
##    <td style="text-align:right;"> 1.986 </td>
##    <td style="text-align:right;"> 0.635 </td>
##    <td style="text-align:right;"> 0.324 </td>
##    <td style="text-align:right;"> 13.083 </td>
##    <td style="text-align:right;"> 0.000 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Arbitrage </td>
##    <td style="text-align:left;"> 2023Q3 </td>
##    <td style="text-align:left;"> DW only </td>
##    <td style="text-align:right;"> 51 </td>
##    <td style="text-align:right;"> 3.002 </td>
##    <td style="text-align:right;"> 1.254 </td>
##    <td style="text-align:right;"> 0.611 </td>
##    <td style="text-align:right;"> 12.233 </td>
##    <td style="text-align:right;"> 0.000 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Arbitrage </td>
##    <td style="text-align:left;"> 2023Q3 </td>
##    <td style="text-align:left;"> BTFP only </td>
##    <td style="text-align:right;"> 125 </td>
##    <td style="text-align:right;"> 1.811 </td>
##    <td style="text-align:right;"> 0.378 </td>
##    <td style="text-align:right;"> 1.616 </td>
##    <td style="text-align:right;"> 10.384 </td>
##    <td style="text-align:right;"> 0.000 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Arbitrage </td>
##    <td style="text-align:left;"> 2023Q3 </td>
##    <td style="text-align:left;"> Both </td>
##    <td style="text-align:right;"> 17 </td>
##    <td style="text-align:right;"> 1.886 </td>
##    <td style="text-align:right;"> 0.487 </td>
##    <td style="text-align:right;"> 1.063 </td>
##    <td style="text-align:right;"> 9.517 </td>
##    <td style="text-align:right;"> 0.000 </td>
##   </tr>
## </tbody>
## </table>
save_kbl_latex(
  conditional_binding,
  "Table_ConditionalBinding",
  caption = "Among Banks with phi_adj >= 1: Is v < 0 the Binding Margin?"
)
## Saved: Table_ConditionalBinding.tex
# -----------------------------
# 9. Quick console readout
# -----------------------------


phase_sample_all %>%
  filter(phase_group %in% c("DW only", "BTFP only")) %>%
  group_by(phase, phase_group) %>%
  summarise(
    N = n(),
    mean_C_DU_adj = mean(phi_C_adj, na.rm = TRUE),
    mean_cash_gap = mean(cash_gap_du, na.rm = TRUE),
    mean_g_phase_DU = mean(g_phase_du, na.rm = TRUE),
    mean_g_to_gap = mean(g_to_cash_gap, na.rm = TRUE),
    pct_phi_lt1 = 100 * mean(phi_adj < 1, na.rm = TRUE),
    pct_phi_ge1_and_vneg = 100 * mean(phi_adj >= 1 & run_possible == 1, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  mutate(across(-c(phase, phase_group, N), ~ round(.x, 3))) %>%
  print()
## # A tibble: 7 × 9
##   phase          phase_group     N mean_C_DU_adj mean_cash_gap mean_g_phase_DU
##   <fct>          <fct>       <int>         <dbl>         <dbl>           <dbl>
## 1 P1_SVB         DW only        67         0.143         0.857           0.119
## 2 P2_BTFP_Active DW only       306         0.658         0.734           0.308
## 3 P2_BTFP_Active BTFP only     369         0.224         0.797           0.348
## 4 P3_FRC         DW only        87         0.225         0.792           0.157
## 5 P3_FRC         BTFP only     168         0.145         0.855           0.171
## 6 Arbitrage      DW only       257         0.406         0.722           0.218
## 7 Arbitrage      BTFP only     655         0.168         0.846           0.778
## # ℹ 3 more variables: mean_g_to_gap <dbl>, pct_phi_lt1 <dbl>,
## #   pct_phi_ge1_and_vneg <dbl>
cat("\nInterpretation guide:\n")
## 
## Interpretation guide:
cat("- mean_C_DU_adj: cash / uninsured deposits after netting out prior outstanding borrowing.\n")
## - mean_C_DU_adj: cash / uninsured deposits after netting out prior outstanding borrowing.
cat("- mean_cash_gap: max(1 - cash_adj/D^U, 0). Higher means larger cash shortfall.\n")
## - mean_cash_gap: max(1 - cash_adj/D^U, 0). Higher means larger cash shortfall.
cat("- mean_g_phase_DU: borrowing in that phase relative to uninsured deposits.\n")
## - mean_g_phase_DU: borrowing in that phase relative to uninsured deposits.
cat("- mean_g_to_gap: how closely borrowing matches the cash deficit.\n")
## - mean_g_to_gap: how closely borrowing matches the cash deficit.
cat("- pct_phi_lt1: share still unable to cover a full uninsured run even after adding OMO collateral.\n")
## - pct_phi_lt1: share still unable to cover a full uninsured run even after adding OMO collateral.
cat("- pct_phi_ge1_and_vneg: share with enough liquidity coverage but still vulnerable on the v < 0 margin.\n")
## - pct_phi_ge1_and_vneg: share with enough liquidity coverage but still vulnerable on the v < 0 margin.

25 Update Table 3 and Figure 2

# ── Period definitions ──
periods <- tribble(
  ~period,     ~start,                    ~end,                      ~label,
  "P0",        as.Date("2023-03-01"),     as.Date("2023-03-07"),     "Pre-Crisis",
  "P1",        as.Date("2023-03-08"),     as.Date("2023-03-12"),     "SVB Week",
  "P2",        as.Date("2023-03-13"),     as.Date("2023-04-27"),     "BTFP Active",
  "P3",        as.Date("2023-04-28"),     as.Date("2023-05-04"),     "FRC Week",
  "Crisis",    as.Date("2023-03-08"),     as.Date("2023-05-04"),     "Crisis",
  "Arbitrage", as.Date("2023-11-15"),     as.Date("2024-01-24"),     "Arbitrage",
  "All_BTFP",  as.Date("2023-03-13"),     as.Date("2024-03-11"),     "Entire BTFP"
)

# ── Helper: compute stats for a given set of loans ──
compute_panel_stats <- function(loans_df, date_col, amt_col, rate_col = NULL,
                                term_col = NULL, held_col = NULL, ltc_col = NULL,
                                start_date, end_date) {
  sub <- loans_df %>%
    filter(.data[[date_col]] >= start_date, .data[[date_col]] <= end_date)

  if (nrow(sub) == 0) {
    return(tibble(
      n_banks = 0L, n_loans = 0L,
      total_b = 0, mean_m = NA_real_, median_m = NA_real_,
      avg_rate = NA_real_, avg_term = NA_real_, avg_held = NA_real_,
      avg_ltc = NA_real_
    ))
  }

  out <- tibble(
    n_banks  = n_distinct(sub$rssd_id),
    n_loans  = nrow(sub),
    total_b  = sum(sub[[amt_col]], na.rm = T) / 1e9,
    mean_m   = mean(sub[[amt_col]], na.rm = T) / 1e6,
    median_m = median(sub[[amt_col]], na.rm = T) / 1e6
  )

  if (!is.null(rate_col) && rate_col %in% names(sub))
    out$avg_rate <- mean(sub[[rate_col]], na.rm = T)
  else out$avg_rate <- NA_real_

  if (!is.null(term_col) && term_col %in% names(sub))
    out$avg_term <- mean(sub[[term_col]], na.rm = T)
  else out$avg_term <- NA_real_

  if (!is.null(held_col) && held_col %in% names(sub))
    out$avg_held <- mean(sub[[held_col]], na.rm = T)
  else out$avg_held <- NA_real_

  if (!is.null(ltc_col) && ltc_col %in% names(sub))
    out$avg_ltc <- mean(sub[[ltc_col]], na.rm = T) * 100
  else out$avg_ltc <- NA_real_

  out
}

# ── Ensure held_days and ltc exist ──
if (!"held_days" %in% names(dw_loans)) {
  dw_loans <- dw_loans %>%
    mutate(held_days = as.integer(dw_repayment_date - dw_loan_date))
}
if (!"ltc_total" %in% names(dw_loans)) {
  dw_loans <- dw_loans %>%
    mutate(ltc_total = safe_div(dw_loan_amount, dw_total_collateral, NA))
}
if (!"held_days" %in% names(btfp_loans)) {
  btfp_loans <- btfp_loans %>%
    mutate(held_days = as.integer(btfp_repayment_date - btfp_loan_date))
}
if (!"ltc" %in% names(btfp_loans)) {
  btfp_loans <- btfp_loans %>%
    mutate(ltc = safe_div(btfp_loan_amount, btfp_total_collateral, NA))
}

# ── Compute DW stats for each period ──
dw_end <- min(as.Date("2024-03-11"), DW_DATA_END)

dw_stats <- map_dfr(seq_len(nrow(periods)), function(i) {
  p <- periods[i, ]
  end_use <- min(p$end, dw_end)
  compute_panel_stats(dw_loans, "dw_loan_date", "dw_loan_amount",
    "dw_interest_rate", "dw_term", "held_days", "ltc_total",
    p$start, end_use) %>%
    mutate(period = p$period, label = p$label)
})

# ── Compute BTFP stats for each period ──
btfp_stats <- map_dfr(seq_len(nrow(periods)), function(i) {
  p <- periods[i, ]
  if (p$end < as.Date("2023-03-13")) {
    return(tibble(
      n_banks = NA_integer_, n_loans = NA_integer_,
      total_b = NA_real_, mean_m = NA_real_, median_m = NA_real_,
      avg_rate = NA_real_, avg_term = NA_real_, avg_held = NA_real_,
      avg_ltc = NA_real_, period = p$period, label = p$label))
  }
  start_use <- max(p$start, as.Date("2023-03-13"))
  compute_panel_stats(btfp_loans, "btfp_loan_date", "btfp_loan_amount",
    "btfp_interest_rate", "btfp_term", "held_days", "ltc",
    start_use, p$end) %>%
    mutate(period = p$period, label = p$label)
})

# ── Compute combined Fed stats ──
all_fed <- map_dfr(seq_len(nrow(periods)), function(i) {
  p <- periods[i, ]
  dw_sub <- dw_loans %>%
    filter(dw_loan_date >= p$start, dw_loan_date <= min(p$end, dw_end))
  btfp_sub <- btfp_loans %>%
    filter(btfp_loan_date >= max(p$start, as.Date("2023-03-13")),
           btfp_loan_date <= p$end)

  all_banks <- union(dw_sub$rssd_id, btfp_sub$rssd_id)
  all_amounts <- c(dw_sub$dw_loan_amount, btfp_sub$btfp_loan_amount)
  n_loans_total <- nrow(dw_sub) + nrow(btfp_sub)

  tibble(
    n_banks  = length(unique(all_banks)),
    n_loans  = n_loans_total,
    total_b  = sum(all_amounts, na.rm = T) / 1e9,
    mean_m   = ifelse(n_loans_total > 0, mean(all_amounts, na.rm = T) / 1e6, NA_real_),
    median_m = ifelse(n_loans_total > 0, median(all_amounts, na.rm = T) / 1e6, NA_real_),
    period = p$period, label = p$label
  )
})

# ── Compute FHLB stats for each period ──
fhlb_qtrs <- list(
  "P0" = "2022Q4", "P1" = "2022Q4", "P2" = "2023Q1", "P3" = "2023Q1",
  "Crisis" = "2023Q1", "Arbitrage" = "2023Q3", "All_BTFP" = c("2023Q1", "2023Q2", "2023Q3", "2023Q4")
)

fhlb_stats <- map_dfr(names(fhlb_qtrs), function(pid) {
  qtrs <- fhlb_qtrs[[pid]]
  df_q <- call_q %>% filter(period %in% qtrs, !idrssd %in% excluded_banks, !is.na(fhlb_adv))
  
  if (nrow(df_q) == 0) {
    return(tibble(period = pid, banks_any = NA_integer_, banks_abn = NA_integer_,
                  total_b = NA_real_, mean_m = NA_real_, median_m = NA_real_, wa_term = NA_real_))
  }
  
  df_bank <- df_q %>%
    group_by(idrssd) %>%
    summarise(
      fhlb_adv           = sum(fhlb_adv, na.rm = TRUE),
      fhlb_less_than_1yr = sum(fhlb_less_than_1yr, na.rm = TRUE),
      fhlb_1to3yr        = sum(fhlb_1to3yr, na.rm = TRUE),
      fhlb_3to5yr        = sum(fhlb_3to5yr, na.rm = TRUE),
      fhlb_more_than_5yr = sum(fhlb_more_than_5yr, na.rm = TRUE),
      abnormal_fhlb      = max(abnormal_fhlb_borrowing_10pct, na.rm = TRUE),
      .groups = "drop"
    )
  
  df_bor <- df_bank %>% filter(fhlb_adv > 0)
  total_adv <- sum(df_bor$fhlb_adv, na.rm = TRUE)
  
  wa_term <- if (total_adv > 0) {
    sum(
      replace_na(df_bor$fhlb_less_than_1yr, 0) * 0.5 + replace_na(df_bor$fhlb_1to3yr, 0) * 2.0 +
      replace_na(df_bor$fhlb_3to5yr, 0) * 4.0 + replace_na(df_bor$fhlb_more_than_5yr, 0) * 7.0, na.rm = TRUE
    ) / total_adv
  } else { NA_real_ }
  
  tibble(
    period = pid, banks_any = nrow(df_bor), banks_abn = sum(df_bank$abnormal_fhlb == 1, na.rm = TRUE),
    total_b = total_adv / 1e6, mean_m = mean(df_bor$fhlb_adv, na.rm = TRUE) / 1e3,
    median_m = median(df_bor$fhlb_adv, na.rm = TRUE) / 1e3, wa_term = wa_term
  )
})

# ── Format for display ──
fmt_num <- function(x, d = 0) ifelse(is.na(x), "---", format(round(x, d), big.mark = ",", nsmall = d))
fmt_pct <- function(x, d = 2) ifelse(is.na(x), "---", sprintf("%.*f", d, x))

col_order <- c("P0", "P1", "P2", "P3", "Crisis", "Arbitrage", "All_BTFP")

# ── Build display table (FIXED DYNAMIC COLUMNS) ──
build_display <- function(stats_df, panel_name, include_rate = FALSE,
                          include_term = FALSE, include_held = FALSE, include_ltc = FALSE) {
  
  metrics_base <- c("Banks (N)", "Loans (N)", "Total ($B)", "Mean ($M)", "Median ($M)")
  metrics_extra <- character()
  if (include_rate) metrics_extra <- c(metrics_extra, "Avg. Rate (%)")
  if (include_term) metrics_extra <- c(metrics_extra, "Avg. Term (days)")
  if (include_held) metrics_extra <- c(metrics_extra, "Avg. Held (days)")
  if (include_ltc) metrics_extra <- c(metrics_extra, "Loan/Collateral (%)")

  out <- tibble(Metric = c(metrics_base, metrics_extra))

  for (pid in col_order) {
    s <- stats_df %>% filter(period == pid)
    if (nrow(s) == 0) s <- tibble(n_banks=NA, n_loans=NA, total_b=NA, mean_m=NA, median_m=NA, avg_rate=NA, avg_term=NA, avg_held=NA, avg_ltc=NA)
    
    vals <- c(fmt_num(s$n_banks[1]), fmt_num(s$n_loans[1]), fmt_pct(s$total_b[1], 2), fmt_pct(s$mean_m[1], 2), fmt_pct(s$median_m[1], 2))
    if (include_rate) vals <- c(vals, fmt_pct(s$avg_rate[1], 2))
    if (include_term) vals <- c(vals, fmt_pct(s$avg_term[1], 1))
    if (include_held) vals <- c(vals, fmt_pct(if(is.null(s$avg_held)) NA else s$avg_held[1], 1))
    if (include_ltc) vals <- c(vals, fmt_pct(s$avg_ltc[1], 2))

    if (panel_name == "BTFP" && pid %in% c("P0", "P1")) vals <- rep("N/A", length(vals))
    out[[pid]] <- vals
  }
  out
}

build_fhlb_display <- function(fhlb_df) {
  metrics <- c("Banks with Advances (N)", "Abnormal Borrowers (N)", "Total ($B)", "Mean ($M)", "Median ($M)", "Wtd. Avg. Term (Years)")
  out <- tibble(Metric = metrics)
  for (pid in col_order) {
    s <- fhlb_df %>% filter(period == pid)
    if (nrow(s) == 0) s <- tibble(banks_any=NA, banks_abn=NA, total_b=NA, mean_m=NA, median_m=NA, wa_term=NA)
    out[[pid]] <- c(fmt_num(s$banks_any[1]), fmt_num(s$banks_abn[1]), fmt_pct(s$total_b[1], 2), fmt_pct(s$mean_m[1], 2), fmt_pct(s$median_m[1], 2), fmt_pct(s$wa_term[1], 2))
  }
  out
}

panel_a <- build_display(all_fed, "All Fed", include_held = FALSE)
panel_b <- build_display(dw_stats, "DW", include_rate = TRUE, include_term = TRUE, include_held = TRUE, include_ltc = TRUE)
panel_c <- build_display(btfp_stats, "BTFP", include_rate = TRUE, include_term = TRUE, include_held = TRUE, include_ltc = TRUE)
panel_d <- build_fhlb_display(fhlb_stats)

col_labels <- c("", "Pre-Crisis\n(Mar 1-7)", "SVB Week\n(Mar 8-12)", "BTFP Active\n(Mar 13-Apr 27)", 
                "FRC Week\n(Apr 28-May 4)", "Crisis\n(Mar 8-May 4)", "Arbitrage\n(Nov 15-Jan 24)", "Entire BTFP\n(Mar 13-Mar 11)")

# Print HTML Outputs
cat("\n", strrep("=", 80), "\nPANEL A: ALL FED BORROWING (DW + BTFP)\n", strrep("=", 80), "\n")
## 
##  ================================================================================ 
## PANEL A: ALL FED BORROWING (DW + BTFP)
##  ================================================================================
kbl(panel_a, format = "html", col.names = col_labels, caption = "Panel A: All Fed Borrowing (BTFP + DW)") %>% kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE) %>% print()
## <table class="table table-striped table-condensed" style="width: auto !important; margin-left: auto; margin-right: auto;">
## <caption>Panel A: All Fed Borrowing (BTFP + DW)</caption>
##  <thead>
##   <tr>
##    <th style="text-align:left;">  </th>
##    <th style="text-align:left;"> Pre-Crisis
## (Mar 1-7) </th>
##    <th style="text-align:left;"> SVB Week
## (Mar 8-12) </th>
##    <th style="text-align:left;"> BTFP Active
## (Mar 13-Apr 27) </th>
##    <th style="text-align:left;"> FRC Week
## (Apr 28-May 4) </th>
##    <th style="text-align:left;"> Crisis
## (Mar 8-May 4) </th>
##    <th style="text-align:left;"> Arbitrage
## (Nov 15-Jan 24) </th>
##    <th style="text-align:left;"> Entire BTFP
## (Mar 13-Mar 11) </th>
##   </tr>
##  </thead>
## <tbody>
##   <tr>
##    <td style="text-align:left;"> Banks (N) </td>
##    <td style="text-align:left;"> 72 </td>
##    <td style="text-align:left;"> 68 </td>
##    <td style="text-align:left;"> 774 </td>
##    <td style="text-align:left;"> 270 </td>
##    <td style="text-align:left;"> 868 </td>
##    <td style="text-align:left;"> 1,039 </td>
##    <td style="text-align:left;"> 2,123 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Loans (N) </td>
##    <td style="text-align:left;"> 171 </td>
##    <td style="text-align:left;"> 124 </td>
##    <td style="text-align:left;"> 2,513 </td>
##    <td style="text-align:left;"> 491 </td>
##    <td style="text-align:left;"> 3,128 </td>
##    <td style="text-align:left;"> 4,083 </td>
##    <td style="text-align:left;"> 14,430 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Total ($B) </td>
##    <td style="text-align:left;"> 9.46 </td>
##    <td style="text-align:left;"> 10.10 </td>
##    <td style="text-align:left;"> 638.63 </td>
##    <td style="text-align:left;"> 20.94 </td>
##    <td style="text-align:left;"> 669.67 </td>
##    <td style="text-align:left;"> 234.28 </td>
##    <td style="text-align:left;"> 1078.86 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Mean ($M) </td>
##    <td style="text-align:left;"> 55.29 </td>
##    <td style="text-align:left;"> 81.47 </td>
##    <td style="text-align:left;"> 254.13 </td>
##    <td style="text-align:left;"> 42.64 </td>
##    <td style="text-align:left;"> 214.09 </td>
##    <td style="text-align:left;"> 57.38 </td>
##    <td style="text-align:left;"> 74.77 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Median ($M) </td>
##    <td style="text-align:left;"> 10.00 </td>
##    <td style="text-align:left;"> 10.47 </td>
##    <td style="text-align:left;"> 11.00 </td>
##    <td style="text-align:left;"> 10.00 </td>
##    <td style="text-align:left;"> 10.00 </td>
##    <td style="text-align:left;"> 11.00 </td>
##    <td style="text-align:left;"> 10.00 </td>
##   </tr>
## </tbody>
## </table>
cat("\n", strrep("=", 80), "\nPANEL B: DISCOUNT WINDOW\n", strrep("=", 80), "\n")
## 
##  ================================================================================ 
## PANEL B: DISCOUNT WINDOW
##  ================================================================================
kbl(panel_b, format = "html", col.names = col_labels, caption = "Panel B: Discount Window") %>% kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE) %>% print()
## <table class="table table-striped table-condensed" style="width: auto !important; margin-left: auto; margin-right: auto;">
## <caption>Panel B: Discount Window</caption>
##  <thead>
##   <tr>
##    <th style="text-align:left;">  </th>
##    <th style="text-align:left;"> Pre-Crisis
## (Mar 1-7) </th>
##    <th style="text-align:left;"> SVB Week
## (Mar 8-12) </th>
##    <th style="text-align:left;"> BTFP Active
## (Mar 13-Apr 27) </th>
##    <th style="text-align:left;"> FRC Week
## (Apr 28-May 4) </th>
##    <th style="text-align:left;"> Crisis
## (Mar 8-May 4) </th>
##    <th style="text-align:left;"> Arbitrage
## (Nov 15-Jan 24) </th>
##    <th style="text-align:left;"> Entire BTFP
## (Mar 13-Mar 11) </th>
##   </tr>
##  </thead>
## <tbody>
##   <tr>
##    <td style="text-align:left;"> Banks (N) </td>
##    <td style="text-align:left;"> 72 </td>
##    <td style="text-align:left;"> 68 </td>
##    <td style="text-align:left;"> 402 </td>
##    <td style="text-align:left;"> 101 </td>
##    <td style="text-align:left;"> 453 </td>
##    <td style="text-align:left;"> 374 </td>
##    <td style="text-align:left;"> 1,358 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Loans (N) </td>
##    <td style="text-align:left;"> 171 </td>
##    <td style="text-align:left;"> 124 </td>
##    <td style="text-align:left;"> 1,488 </td>
##    <td style="text-align:left;"> 213 </td>
##    <td style="text-align:left;"> 1,825 </td>
##    <td style="text-align:left;"> 985 </td>
##    <td style="text-align:left;"> 7,735 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Total ($B) </td>
##    <td style="text-align:left;"> 9.46 </td>
##    <td style="text-align:left;"> 10.10 </td>
##    <td style="text-align:left;"> 518.46 </td>
##    <td style="text-align:left;"> 9.61 </td>
##    <td style="text-align:left;"> 538.18 </td>
##    <td style="text-align:left;"> 14.84 </td>
##    <td style="text-align:left;"> 668.51 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Mean ($M) </td>
##    <td style="text-align:left;"> 55.29 </td>
##    <td style="text-align:left;"> 81.47 </td>
##    <td style="text-align:left;"> 348.43 </td>
##    <td style="text-align:left;"> 45.13 </td>
##    <td style="text-align:left;"> 294.89 </td>
##    <td style="text-align:left;"> 15.06 </td>
##    <td style="text-align:left;"> 86.43 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Median ($M) </td>
##    <td style="text-align:left;"> 10.00 </td>
##    <td style="text-align:left;"> 10.47 </td>
##    <td style="text-align:left;"> 10.00 </td>
##    <td style="text-align:left;"> 8.00 </td>
##    <td style="text-align:left;"> 9.70 </td>
##    <td style="text-align:left;"> 3.90 </td>
##    <td style="text-align:left;"> 6.31 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Avg. Rate (%) </td>
##    <td style="text-align:left;"> 4.75 </td>
##    <td style="text-align:left;"> 4.75 </td>
##    <td style="text-align:left;"> 4.92 </td>
##    <td style="text-align:left;"> 5.05 </td>
##    <td style="text-align:left;"> 4.92 </td>
##    <td style="text-align:left;"> 5.50 </td>
##    <td style="text-align:left;"> 5.31 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Avg. Term (days) </td>
##    <td style="text-align:left;"> 5.3 </td>
##    <td style="text-align:left;"> 3.8 </td>
##    <td style="text-align:left;"> 4.4 </td>
##    <td style="text-align:left;"> 3.8 </td>
##    <td style="text-align:left;"> 4.3 </td>
##    <td style="text-align:left;"> 4.9 </td>
##    <td style="text-align:left;"> 4.6 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Avg. Held (days) </td>
##    <td style="text-align:left;"> 4.1 </td>
##    <td style="text-align:left;"> 3.0 </td>
##    <td style="text-align:left;"> 3.0 </td>
##    <td style="text-align:left;"> 2.8 </td>
##    <td style="text-align:left;"> 3.0 </td>
##    <td style="text-align:left;"> 3.5 </td>
##    <td style="text-align:left;"> 3.4 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Loan/Collateral (%) </td>
##    <td style="text-align:left;"> 24.17 </td>
##    <td style="text-align:left;"> 22.22 </td>
##    <td style="text-align:left;"> 27.53 </td>
##    <td style="text-align:left;"> 23.96 </td>
##    <td style="text-align:left;"> 26.76 </td>
##    <td style="text-align:left;"> 17.31 </td>
##    <td style="text-align:left;"> 22.55 </td>
##   </tr>
## </tbody>
## </table>
cat("\n", strrep("=", 80), "\nPANEL C: BANK TERM FUNDING PROGRAM\n", strrep("=", 80), "\n")
## 
##  ================================================================================ 
## PANEL C: BANK TERM FUNDING PROGRAM
##  ================================================================================
kbl(panel_c, format = "html", col.names = col_labels, caption = "Panel C: Bank Term Funding Program") %>% kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE) %>% print()
## <table class="table table-striped table-condensed" style="width: auto !important; margin-left: auto; margin-right: auto;">
## <caption>Panel C: Bank Term Funding Program</caption>
##  <thead>
##   <tr>
##    <th style="text-align:left;">  </th>
##    <th style="text-align:left;"> Pre-Crisis
## (Mar 1-7) </th>
##    <th style="text-align:left;"> SVB Week
## (Mar 8-12) </th>
##    <th style="text-align:left;"> BTFP Active
## (Mar 13-Apr 27) </th>
##    <th style="text-align:left;"> FRC Week
## (Apr 28-May 4) </th>
##    <th style="text-align:left;"> Crisis
## (Mar 8-May 4) </th>
##    <th style="text-align:left;"> Arbitrage
## (Nov 15-Jan 24) </th>
##    <th style="text-align:left;"> Entire BTFP
## (Mar 13-Mar 11) </th>
##   </tr>
##  </thead>
## <tbody>
##   <tr>
##    <td style="text-align:left;"> Banks (N) </td>
##    <td style="text-align:left;"> N/A </td>
##    <td style="text-align:left;"> N/A </td>
##    <td style="text-align:left;"> 467 </td>
##    <td style="text-align:left;"> 183 </td>
##    <td style="text-align:left;"> 526 </td>
##    <td style="text-align:left;"> 780 </td>
##    <td style="text-align:left;"> 1,316 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Loans (N) </td>
##    <td style="text-align:left;"> N/A </td>
##    <td style="text-align:left;"> N/A </td>
##    <td style="text-align:left;"> 1,025 </td>
##    <td style="text-align:left;"> 278 </td>
##    <td style="text-align:left;"> 1,303 </td>
##    <td style="text-align:left;"> 3,098 </td>
##    <td style="text-align:left;"> 6,695 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Total ($B) </td>
##    <td style="text-align:left;"> N/A </td>
##    <td style="text-align:left;"> N/A </td>
##    <td style="text-align:left;"> 120.16 </td>
##    <td style="text-align:left;"> 11.32 </td>
##    <td style="text-align:left;"> 131.49 </td>
##    <td style="text-align:left;"> 219.45 </td>
##    <td style="text-align:left;"> 410.36 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Mean ($M) </td>
##    <td style="text-align:left;"> N/A </td>
##    <td style="text-align:left;"> N/A </td>
##    <td style="text-align:left;"> 117.23 </td>
##    <td style="text-align:left;"> 40.74 </td>
##    <td style="text-align:left;"> 100.91 </td>
##    <td style="text-align:left;"> 70.84 </td>
##    <td style="text-align:left;"> 61.29 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Median ($M) </td>
##    <td style="text-align:left;"> N/A </td>
##    <td style="text-align:left;"> N/A </td>
##    <td style="text-align:left;"> 15.00 </td>
##    <td style="text-align:left;"> 10.00 </td>
##    <td style="text-align:left;"> 13.00 </td>
##    <td style="text-align:left;"> 15.00 </td>
##    <td style="text-align:left;"> 10.00 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Avg. Rate (%) </td>
##    <td style="text-align:left;"> N/A </td>
##    <td style="text-align:left;"> N/A </td>
##    <td style="text-align:left;"> 4.68 </td>
##    <td style="text-align:left;"> 4.79 </td>
##    <td style="text-align:left;"> 4.70 </td>
##    <td style="text-align:left;"> 4.95 </td>
##    <td style="text-align:left;"> 5.02 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Avg. Term (days) </td>
##    <td style="text-align:left;"> N/A </td>
##    <td style="text-align:left;"> N/A </td>
##    <td style="text-align:left;"> 315.0 </td>
##    <td style="text-align:left;"> 322.2 </td>
##    <td style="text-align:left;"> 316.5 </td>
##    <td style="text-align:left;"> 338.0 </td>
##    <td style="text-align:left;"> 311.1 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Avg. Held (days) </td>
##    <td style="text-align:left;"> N/A </td>
##    <td style="text-align:left;"> N/A </td>
##    <td style="text-align:left;"> 152.1 </td>
##    <td style="text-align:left;"> 177.9 </td>
##    <td style="text-align:left;"> 157.6 </td>
##    <td style="text-align:left;"> 88.1 </td>
##    <td style="text-align:left;"> 104.5 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Loan/Collateral (%) </td>
##    <td style="text-align:left;"> N/A </td>
##    <td style="text-align:left;"> N/A </td>
##    <td style="text-align:left;"> 53.07 </td>
##    <td style="text-align:left;"> 45.32 </td>
##    <td style="text-align:left;"> 51.42 </td>
##    <td style="text-align:left;"> 50.45 </td>
##    <td style="text-align:left;"> 45.13 </td>
##   </tr>
## </tbody>
## </table>
cat("\n", strrep("=", 80), "\nPANEL D: FEDERAL HOME LOAN BANK\n", strrep("=", 80), "\n")
## 
##  ================================================================================ 
## PANEL D: FEDERAL HOME LOAN BANK
##  ================================================================================
kbl(panel_d, format = "html", col.names = col_labels, caption = "Panel D: Federal Home Loan Bank (FHLB)") %>% kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE) %>% print()
## <table class="table table-striped table-condensed" style="width: auto !important; margin-left: auto; margin-right: auto;">
## <caption>Panel D: Federal Home Loan Bank (FHLB)</caption>
##  <thead>
##   <tr>
##    <th style="text-align:left;">  </th>
##    <th style="text-align:left;"> Pre-Crisis
## (Mar 1-7) </th>
##    <th style="text-align:left;"> SVB Week
## (Mar 8-12) </th>
##    <th style="text-align:left;"> BTFP Active
## (Mar 13-Apr 27) </th>
##    <th style="text-align:left;"> FRC Week
## (Apr 28-May 4) </th>
##    <th style="text-align:left;"> Crisis
## (Mar 8-May 4) </th>
##    <th style="text-align:left;"> Arbitrage
## (Nov 15-Jan 24) </th>
##    <th style="text-align:left;"> Entire BTFP
## (Mar 13-Mar 11) </th>
##   </tr>
##  </thead>
## <tbody>
##   <tr>
##    <td style="text-align:left;"> Banks with Advances (N) </td>
##    <td style="text-align:left;"> 2,481 </td>
##    <td style="text-align:left;"> 2,481 </td>
##    <td style="text-align:left;"> 2,530 </td>
##    <td style="text-align:left;"> 2,530 </td>
##    <td style="text-align:left;"> 2,530 </td>
##    <td style="text-align:left;"> 2,639 </td>
##    <td style="text-align:left;"> 3,043 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Abnormal Borrowers (N) </td>
##    <td style="text-align:left;"> 325 </td>
##    <td style="text-align:left;"> 325 </td>
##    <td style="text-align:left;"> 350 </td>
##    <td style="text-align:left;"> 350 </td>
##    <td style="text-align:left;"> 350 </td>
##    <td style="text-align:left;"> 212 </td>
##    <td style="text-align:left;"> 707 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Total ($B) </td>
##    <td style="text-align:left;"> 446.95 </td>
##    <td style="text-align:left;"> 446.95 </td>
##    <td style="text-align:left;"> 646.65 </td>
##    <td style="text-align:left;"> 646.65 </td>
##    <td style="text-align:left;"> 646.65 </td>
##    <td style="text-align:left;"> 462.93 </td>
##    <td style="text-align:left;"> 2092.48 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Mean ($M) </td>
##    <td style="text-align:left;"> 180.15 </td>
##    <td style="text-align:left;"> 180.15 </td>
##    <td style="text-align:left;"> 255.59 </td>
##    <td style="text-align:left;"> 255.59 </td>
##    <td style="text-align:left;"> 255.59 </td>
##    <td style="text-align:left;"> 175.42 </td>
##    <td style="text-align:left;"> 687.64 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Median ($M) </td>
##    <td style="text-align:left;"> 15.50 </td>
##    <td style="text-align:left;"> 15.50 </td>
##    <td style="text-align:left;"> 16.41 </td>
##    <td style="text-align:left;"> 16.41 </td>
##    <td style="text-align:left;"> 16.41 </td>
##    <td style="text-align:left;"> 18.00 </td>
##    <td style="text-align:left;"> 55.00 </td>
##   </tr>
##   <tr>
##    <td style="text-align:left;"> Wtd. Avg. Term (Years) </td>
##    <td style="text-align:left;"> 1.10 </td>
##    <td style="text-align:left;"> 1.10 </td>
##    <td style="text-align:left;"> 1.05 </td>
##    <td style="text-align:left;"> 1.05 </td>
##    <td style="text-align:left;"> 1.05 </td>
##    <td style="text-align:left;"> 1.37 </td>
##    <td style="text-align:left;"> 1.26 </td>
##   </tr>
## </tbody>
## </table>