test_that("vasicekfit recovers known parameters from large sample", { set.seed(123) n <- 5000 p_true <- 0.03 rho_true <- 0.05 kappa_true <- c(unemp = 0.13, hpi = -0.07) u1 <- rnorm(n) u2 <- rnorm(n) z <- rnorm(n) x <- pnorm( (qnorm(p_true) + kappa_true[1] * u1 + kappa_true[2] * u2 + sqrt(rho_true) * z) / sqrt(1 - rho_true) ) d <- data.frame(default_rate = x, unemp = u1, hpi = u2) fit <- vasicekfit(default_rate ~ unemp + hpi, data = d) expect_s3_class(fit, "vasicekfit") expect_equal(fit$p, p_true, tolerance = 0.005) expect_equal(fit$rho, rho_true, tolerance = 0.005) expect_equal(unname(fit$kappa["unemp"]), unname(kappa_true["unemp"]), tolerance = 0.01) expect_equal(unname(fit$kappa["hpi"]), unname(kappa_true["hpi"]), tolerance = 0.01) }) test_that("intercept-only model recovers standard Vasicek", { set.seed(456) n <- 50000 p_true <- 0.05 rho_true <- 0.10 z <- rnorm(n) x <- pnorm((qnorm(p_true) + sqrt(rho_true) * z) / sqrt(1 - rho_true)) d <- data.frame(default_rate = x) fit <- vasicekfit(default_rate ~ 1, data = d) expect_equal(fit$p, p_true, tolerance = 0.02) expect_equal(fit$rho, rho_true, tolerance = 0.02) expect_length(fit$kappa, 0) }) test_that("bias_correct adjusts sigma2 upward", { set.seed(789) n <- 50 u <- rnorm(n) z <- rnorm(n) x <- pnorm((qnorm(0.03) + 0.1 * u + sqrt(0.05) * z) / sqrt(0.95)) d <- data.frame(y = x, u = u) fit_raw <- vasicekfit(y ~ u, data = d, bias_correct = FALSE) fit_bc <- vasicekfit(y ~ u, data = d, bias_correct = TRUE) expect_gt(fit_bc$sigma2, fit_raw$sigma2) }) test_that("response must be in (0, 1)", { d <- data.frame(y = c(0, 0.5, 1), u = 1:3) expect_error(vasicekfit(y ~ u, data = d), "\\(0, 1\\)") }) test_that("coef returns named vector with p, rho, kappa", { set.seed(1) n <- 100 u <- rnorm(n) x <- pnorm((qnorm(0.03) + 0.1 * u + sqrt(0.02) * rnorm(n)) / sqrt(0.98)) d <- data.frame(y = x, u = u) fit <- vasicekfit(y ~ u, data = d) cc <- coef(fit) expect_named(cc, c("p", "rho", "u")) expect_length(cc, 3) }) test_that("fitted and residuals are in probit space", { set.seed(2) n <- 100 u <- rnorm(n) x <- pnorm((qnorm(0.03) + 0.1 * u + sqrt(0.02) * rnorm(n)) / sqrt(0.98)) d <- data.frame(y = x, u = u) fit <- vasicekfit(y ~ u, data = d) expect_equal(unname(fitted(fit) + residuals(fit)), qnorm(x)) }) test_that("intercept-only fit matches vasicek::vsk_imm", { skip_if_not_installed("vasicek") set.seed(42) x <- vasicek::vsk_rvs(500, Rho = 0.1, P = 0.03) d <- data.frame(delq_rate = x) ref <- vasicek::vsk_imm(d$delq_rate) fit <- vasicekfit(delq_rate ~ 1, data = d) expect_equal(fit$p, ref$P, tolerance = 1e-12) expect_equal(fit$rho, ref$Rho, tolerance = 1e-12) }) test_that("portfolio_size correction works", { set.seed(3) n <- 100 u <- rnorm(n) x <- pnorm((qnorm(0.03) + 0.1 * u + sqrt(0.02) * rnorm(n)) / sqrt(0.98)) d <- data.frame(y = x, u = u) fit1 <- vasicekfit(y ~ u, data = d) fit2 <- vasicekfit(y ~ u, data = d, portfolio_size = 1000) # With finite portfolio correction, rho should be slightly different expect_false(identical(fit1$rho, fit2$rho)) })