Nothing
## ----setup, include=FALSE-----------------------------------------------------
NOT_CRAN <- identical(tolower(Sys.getenv("NOT_CRAN")), "true")
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
eval = NOT_CRAN,
fig.width = 8,
fig.height = 6
)
## ----libs, message=FALSE, warning=FALSE---------------------------------------
# library(smoothbp)
# library(ggplot2)
# library(dplyr)
# library(posterior)
# library(tidyr)
## ----real-data----------------------------------------------------------------
# dat_market <- data.frame(
# month = rep(1:15, 3),
# ticker = rep(c("NVDA", "MSFT", "AAPL"), each = 15),
# price = c(
# 13.50, 16.50, 14.60, 19.52, 23.19, 27.75, 27.72, 37.80, 42.27, 46.69, 49.32, 43.47, 40.75, 46.74, 49.49,
# 232, 255, 240, 241.47, 243.65, 281.63, 300.15, 321.49, 333.39, 328.87, 321.56, 309.77, 331.71, 372.49, 369.67,
# 153, 148, 130, 142.01, 145.30, 162.55, 167.26, 174.96, 191.46, 193.91, 185.69, 169.23, 168.79, 188.00, 190.55
# )
# )
# dat_market$ticker <- factor(dat_market$ticker, levels = c("AAPL", "MSFT", "NVDA"))
## ----fit-market---------------------------------------------------------------
# fit_market <- smoothbp(
# formula = price ~ month, b0 = ~ ticker,
# deltas = list(~ ticker, ~ ticker, ~ ticker, ~ ticker, ~ ticker),
# omega = list(~ ticker, ~ ticker, ~ ticker, ~ ticker, ~ ticker),
# rho = list(~ 1, ~ 1, ~ 1, ~ 1, ~ 1),
# data = dat_market,
# priors = smoothbp_priors(
# b0 = prior_normal(200, 500), b1 = prior_normal(0, 50), deltas = prior_normal(0, 80),
# # Constrain omegas: intercepts centered on windows, narrow priors for ticker offsets
# omega = lapply(list(3, 6, 9, 12, 14), function(m) {
# list("(Intercept)" = prior_normal(m, 1, lb = 1, ub = 15),
# "tickerMSFT" = prior_normal(0, 1),
# "tickerNVDA" = prior_normal(0, 1))
# }),
# rho = prior_normal(20, 5, lb = 5)
# ),
# chains = 4, iter = 4000, warmup = 2000
# )
## ----event-omegas-------------------------------------------------------------
# n_bp <- 5
# draws <- as_draws_matrix(fit_market$draws)
# tickers <- levels(dat_market$ticker)
#
# # Helper to safely extract a named draw column (handles special chars via grep)
# val <- function(d, pat) {
# v <- d[grep(paste0("^", pat, "$"), names(d))]
# if (length(v) == 0) return(0)
# unname(v[1])
# }
#
# # Extract omega_k for each ticker from a single draw row
# get_omegas <- function(d, tk) {
# sapply(1:n_bp, function(k) {
# base <- val(d, paste0("omega", k, "_\\(Intercept\\)"))
# off <- if (tk == tickers[1]) 0 else val(d, paste0("omega", k, "_ticker", tk))
# base + off
# })
# }
#
# # Extract all ticker-specific parameters for a single draw row
# get_params <- function(d, tk) {
# b1 <- val(d, "b1_\\(Intercept\\)")
# if (tk != tickers[1]) b1 <- b1 + val(d, paste0("b1_ticker", tk))
#
# deltas <- omegas <- rhos <- numeric(n_bp)
# for (k in 1:n_bp) {
# deltas[k] <- val(d, paste0("delta", k, "_\\(Intercept\\)"))
# omegas[k] <- val(d, paste0("omega", k, "_\\(Intercept\\)"))
# rhos[k] <- val(d, paste0("rho", k, "_\\(Intercept\\)"))
#
# if (tk != tickers[1]) {
# deltas[k] <- deltas[k] + val(d, paste0("delta", k, "_ticker", tk))
# omegas[k] <- omegas[k] + val(d, paste0("omega", k, "_ticker", tk))
# rhos[k] <- rhos[k] + val(d, paste0("rho", k, "_ticker", tk))
# }
# }
# list(b1 = b1, deltas = deltas, omegas = omegas, rhos = rhos)
# }
#
# # Posterior summary of omega_k per ticker
# omega_summary <- do.call(rbind, lapply(tickers, function(tk) {
# om_mat <- t(apply(draws, 1, get_omegas, tk = tk))
# do.call(rbind, lapply(1:n_bp, function(k) {
# data.frame(
# ticker = tk, bp = k,
# mean = mean(om_mat[, k]),
# Q2.5 = quantile(om_mat[, k], 0.025),
# Q97.5 = quantile(om_mat[, k], 0.975)
# )
# }))
# })) |> mutate(ticker = factor(ticker, levels = levels(dat_market$ticker)))
# print(omega_summary)
## ----plot-events--------------------------------------------------------------
# pred_orig <- fitted(fit_market)
# dat_market$y_fit <- pred_orig$fitted_mean
# dat_market$lo <- pred_orig$fitted_Q2.5
# dat_market$hi <- pred_orig$fitted_Q97.5
#
# # Compute y-position on the mean curve at each omega
# omega_plot <- omega_summary |>
# rowwise() |>
# mutate(y_val = approx(
# dat_market$month[dat_market$ticker == ticker],
# dat_market$y_fit[dat_market$ticker == ticker],
# xout = mean)$y) |>
# ungroup()
#
# month_labels <- c("Oct22","Nov","Dec","Jan23","Feb","Mar","Apr","May",
# "Jun","Jul","Aug","Sep","Oct","Nov","Dec")
#
# ggplot(dat_market, aes(x = month, y = price, color = ticker)) +
# geom_point(alpha = 0.5) +
# geom_ribbon(aes(ymin = lo, ymax = hi, fill = ticker), alpha = 0.1, color = NA) +
# geom_line(aes(y = y_fit), size = 1) +
# geom_point(
# data = omega_plot, aes(x = mean, y = y_val),
# color = "red", shape = 4, size = 4, stroke = 1.5, inherit.aes = FALSE
# ) +
# geom_errorbarh(
# data = omega_plot, aes(xmin = Q2.5, xmax = Q97.5, y = y_val),
# color = "red", height = 0, alpha = 0.4, inherit.aes = FALSE
# ) +
# scale_x_continuous(breaks = 1:15, labels = month_labels) +
# facet_wrap(~ticker, scales = "free_y") +
# theme_minimal() + theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
# labs(
# title = "Structural Events: Point of Maximum Curvature (omega_k)",
# subtitle = "Red X = posterior mean omega_k; bars = 95% CI."
# )
## ----leadership-analysis------------------------------------------------------
# # Extract omegas for all draws for the targeted indices
# om_nvda <- t(apply(draws, 1, get_omegas, tk = "NVDA"))
# om_msft <- t(apply(draws, 1, get_omegas, tk = "MSFT"))
# om_aapl <- t(apply(draws, 1, get_omegas, tk = "AAPL"))
#
# # 1. Early Pivot (BP 1)
# p1_nvda_msft <- mean(om_nvda[, 1] < om_msft[, 1])
# p1_nvda_aapl <- mean(om_nvda[, 1] < om_aapl[, 1])
#
# # 2. Autumn Shift (BP 4)
# p4_nvda_msft <- mean(om_nvda[, 4] < om_msft[, 4])
# p4_msft_aapl <- mean(om_msft[, 4] < om_aapl[, 4])
#
# # 3. Cross-Event: NVDA Surge (BP 2) vs AAPL Recovery (BP 1)
# p_cross <- mean(om_nvda[, 2] < om_aapl[, 1])
#
# cat("--- Event 1: Early Pivot (BP 1) ---\n")
# message(sprintf("Prob NVDA led MSFT: %.2f", p1_nvda_msft))
# message(sprintf("Prob NVDA led AAPL: %.2f", p1_nvda_aapl))
#
# cat("\n--- Event 2: Autumn Shift (BP 4) ---\n")
# message(sprintf("Prob NVDA led MSFT: %.2f", p4_nvda_msft))
# message(sprintf("Prob MSFT led AAPL: %.2f", p4_msft_aapl))
#
# cat("\n--- Cross-Event Dynamics ---\n")
# message(sprintf("Prob NVDA AI Surge (BP 2) led AAPL Recovery (BP 1): %.2f", p_cross))
## ----sim-hier-----------------------------------------------------------------
# set.seed(42)
# n_tickers <- 10
# n_months <- 25
# dat_sim <- expand.grid(
# month = 1:n_months,
# ticker = paste0("T", formatC(1:n_tickers, width = 2, flag = "0"))
# )
#
# # True shared market events: rally at month 7, correction at month 18
# om_true <- c(7, 18)
#
# # Ticker-specific timing offsets (mean 0, SD 0.8 months)
# timing_offsets <- matrix(rnorm(n_tickers * 2, 0, 0.8), n_tickers, 2)
#
# # Rally magnitude varies by ticker (all positive = same direction)
# rally_mag <- runif(n_tickers, 1.5, 3.5)
# # Correction magnitude (all negative = same direction)
# correction_mag <- runif(n_tickers, 1.0, 2.5)
#
# # Simulate using smooth logistic transitions (matches the model)
# # Event 2 REVERSES the rally: delta_2 cancels delta_1 and adds a negative slope
# # so prices genuinely fall after month 18, creating a clear inverted-U shape.
# rho_true <- 3
# dat_sim$price <- unlist(lapply(1:n_tickers, function(i) {
# t <- 1:n_months
# om1_k <- om_true[1] + timing_offsets[i, 1]
# om2_k <- om_true[2] + timing_offsets[i, 2]
# y <- 10 +
# rally_mag[i] * (t - om1_k) * plogis(rho_true * (t - om1_k)) -
# (rally_mag[i] + correction_mag[i]) * (t - om2_k) * plogis(rho_true * (t - om2_k)) +
# rnorm(n_months, 0, 0.5)
# y
# }))
#
# ggplot(dat_sim, aes(x = month, y = price, color = ticker)) +
# geom_line(linewidth = 0.7) +
# geom_vline(xintercept = om_true, linetype = "dashed", alpha = 0.4) +
# theme_minimal() +
# theme(legend.position = "none") +
# labs(
# title = "Simulated Market Data: 10 Tickers, 2 Shared Events",
# subtitle = "Dashed lines = true event times (month 7 and 18). Individual timing varies slightly."
# )
## ----fit-sim-hier-------------------------------------------------------------
# my_spike <- prior_spike_slab(
# pi = 2/8
# )
#
# t_min <- min(dat_sim$month)
# t_max <- max(dat_sim$month)
#
# # Use the space_omega_priors helper to initialize 8 candidate breakpoints.
# # The function automatically names the priors `(Intercept)` so they apply correctly
# # to the global mean, while random effects are handled automatically by the model.
# omega_priors_hier <- space_omega_priors(
# K = 8,
# tau_min = t_min,
# tau_max = t_max
# )
#
#
# fit_hier <- smoothbp_ss(
# formula = price ~ month,
# b0 = ~ (1 | ticker),
# # (1 | ticker): intercept = market mean, ticker columns = random deviations
# deltas = replicate(8, ~ ticker, simplify = FALSE),
# omega = replicate(8, ~ (1 | ticker), simplify = FALSE),
# rho = replicate(8, ~ 1, simplify = FALSE),
# data = dat_sim,
# spike = my_spike,
# priors = smoothbp_priors(
# sigma_re_om = prior_invgamma(2, 1),
# omega = omega_priors_hier),
# chains = 2L, iter = 4000, warmup = 2000, cores = 4L
# )
## ----pip-sim-hier-------------------------------------------------------------
# draws_hier <- as_draws_df(fit_hier$draws)
#
# # PIP = P(market-level delta is non-zero) = mean of the (Intercept) gamma.
# # Using 'any ticker active' would inflate PIPs due to 10-ticker multiplicity.
# pip_summary <- data.frame(
# bp = 1:8,
# pip = sapply(1:8, function(k) {
# col <- paste0("gamma_delta", k, "_(Intercept)")
# if (!col %in% names(draws_hier)) return(0)
# mean(draws_hier[[col]])
# })
# )
#
# ggplot(pip_summary, aes(x = factor(bp), y = pip)) +
# geom_col(aes(fill = pip > 0.5), show.legend = FALSE) +
# geom_hline(yintercept = 0.5, linetype = "dashed") +
# scale_fill_manual(values = c("FALSE" = "grey70", "TRUE" = "#2166ac")) +
# scale_y_continuous(limits = c(0, 1), labels = scales::percent) +
# theme_minimal() +
# labs(
# x = "Candidate breakpoint",
# y = "PIP",
# title = "Posterior Inclusion Probabilities (market-level intercept gamma)",
# subtitle = "Blue = selected (PIP > 0.5). Prior: Beta(1,9) ≈ 10% expected inclusion."
# )
## ----plot-sim-hier------------------------------------------------------------
# pred_hier <- fitted(fit_hier)
# dat_sim$y_fit <- pred_hier$fitted_mean
# dat_sim$lo <- pred_hier$fitted_Q2.5
# dat_sim$hi <- pred_hier$fitted_Q97.5
#
# active_bps <- pip_summary$bp[pip_summary$pip > 0.5]
# tickers_sim <- levels(factor(dat_sim$ticker))
# ref_ticker <- tickers_sim[1] # reference level in design matrix
#
# if (length(active_bps) > 0) {
# # With (1 | ticker), the design matrix has:
# # column "(Intercept)" = market mean (fixed)
# # columns "tickerT01", "tickerT02", ... = per-ticker RE deviations
# # Ticker-specific omega = market mean + that ticker's RE column
# tickers_sim <- levels(factor(dat_sim$ticker))
#
# omega_summary_sim <- do.call(rbind, lapply(tickers_sim, function(tk) {
# om_vals <- as.matrix(sapply(active_bps, function(k) {
# mu <- draws_hier[[paste0("omega", k, "_(Intercept)")]]
# u_k <- draws_hier[[paste0("omega", k, "_ticker", tk)]]
# if (is.null(u_k)) mu else mu + u_k
# }))
# do.call(rbind, lapply(seq_along(active_bps), function(j) {
# data.frame(
# ticker = tk, bp = active_bps[j],
# mean = mean(om_vals[, j]),
# Q2.5 = quantile(om_vals[, j], 0.025),
# Q97.5 = quantile(om_vals[, j], 0.975)
# )
# }))
# }))
#
# omega_plot_sim <- omega_summary_sim |>
# rowwise() |>
# mutate(y_val = approx(
# dat_sim$month[dat_sim$ticker == ticker],
# dat_sim$y_fit[dat_sim$ticker == ticker],
# xout = mean)$y) |>
# ungroup()
# } else {
# omega_plot_sim <- data.frame()
# }
#
# ggplot(dat_sim, aes(x = month, color = ticker, fill = ticker)) +
# geom_point(aes(y = price), alpha = 0.25, size = 0.8) +
# geom_ribbon(aes(ymin = lo, ymax = hi), alpha = 0.1, color = NA) +
# geom_line(aes(y = y_fit), linewidth = 0.8) +
# {if (nrow(omega_plot_sim) > 0)
# geom_point(
# data = omega_plot_sim, aes(x = mean, y = y_val),
# color = "black", shape = 18, size = 3.5, inherit.aes = FALSE
# )
# } +
# {if (nrow(omega_plot_sim) > 0)
# geom_errorbarh(
# data = omega_plot_sim,
# aes(xmin = Q2.5, xmax = Q97.5, y = y_val),
# height = 0, color = "black", alpha = 0.4, inherit.aes = FALSE
# )
# } +
# facet_wrap(~ ticker, ncol = 5) +
# theme_minimal() +
# theme(legend.position = "none") +
# labs(
# x = "Month",
# y = "Price",
# title = "Hierarchical Event Discovery: Fitted Curves",
# subtitle = "Black diamonds = discovered market events (PIP > 0.5); bars = 95% CI."
# )
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.