Libraries

knitr::opts_chunk$set(
    fig.align = "center",
    message = FALSE,
    warning = FALSE,
    out.width = "100%"
)
library(tidyverse)
library(tidyquant)
library(forecast)
library(ggrepel)
library(scales)

# Save a ggplot as a high-resolution PNG and display it inline
hd_plot <- function(p, name, width = 9, height = 5, dpi = 300) {
  dir.create("figures", showWarnings = FALSE)
  path <- file.path("figures", paste0(name, ".png"))
  ggsave(path, plot = p, width = width, height = height, dpi = dpi,
         device = ragg::agg_png, bg = "white")
  knitr::include_graphics(path)
}

# Colour palette and theme
pal <- c(navy = "#1f4e79", orange = "#e07b39", green = "#2e8b57",
         red = "#c0392b", grey = "grey55")

theme_ihsg <- function(base_size = 12) {
  theme_minimal(base_size = base_size) +
    theme(
      plot.title           = element_text(face = "bold", size = rel(1.25)),
      plot.subtitle        = element_text(colour = "grey40", margin = margin(b = 8)),
      plot.caption         = element_text(colour = "grey50", size = rel(0.75)),
      plot.title.position  = "plot",
      panel.grid.minor     = element_blank(),
      legend.position      = "top",
      legend.justification = "left",
      legend.title         = element_blank(),
      strip.text           = element_text(face = "bold", hjust = 0)
    )
}

source_label <- "Source: Yahoo Finance (^JKSE)"

Settings

forecast_start    <- as.Date("2026-11-02")
forecast_end      <- as.Date("2026-11-10")
train_years       <- 3       # length of the estimation window (years)
run_slow_backtest <- FALSE   # TRUE adds TBATS and NNAR to the backtest (much slower)
lv                <- c(80, 95)

# IDX holidays that could fall inside a forecast horizon (2026 calendar); extend as needed
idx_holidays <- as.Date(c("2026-12-24", "2026-12-25", "2026-12-31"))

# Trading days in the forecast horizon
future_dates <- seq(forecast_start, forecast_end, by = "day")
future_dates <- future_dates[lubridate::wday(future_dates, week_start = 1) <= 5 &
                               !future_dates %in% idx_holidays]
h <- length(future_dates)

future_dates
[1] "2026-11-02" "2026-11-03" "2026-11-04" "2026-11-05" "2026-11-06" "2026-11-09" "2026-11-10"

Data

Daily data are downloaded from Yahoo Finance. Weekend dates and flat bars (open = high = low = close), which Yahoo occasionally records on IDX holidays, are listed first and then removed.

ihsg_raw <- tq_get("^JKSE", from = "2015-01-01")

ihsg_flagged <- ihsg_raw %>%
  dplyr::filter(!is.na(close), date < forecast_start) %>%
  distinct(date, .keep_all = TRUE) %>%
  arrange(date) %>%
  mutate(
    weekend  = lubridate::wday(date, week_start = 1) > 5,
    flat_bar = coalesce(open == high & high == low & low == close, FALSE)
  )

# Rows to be removed
ihsg_flagged %>% filter(weekend | flat_bar)

ihsg <- ihsg_flagged %>%
  filter(!weekend, !flat_bar) %>%
  select(date, close)

tail(ihsg)

Trading Calendar

The series contains IDX trading days only, so gaps longer than a weekend correspond to national holidays and collective leave days.

# Longest gaps between consecutive trading dates
ihsg %>%
  mutate(previous_date = lag(date),
         gap_days      = as.numeric(date - previous_date)) %>%
  filter(gap_days > 3) %>%
  arrange(desc(gap_days)) %>%
  select(previous_date, date, gap_days) %>%
  head(10)
# Trading days per complete calendar year
trading_days <- ihsg %>%
  count(year = lubridate::year(date), name = "trading_days") %>%
  filter(year < lubridate::year(max(ihsg$date)))

trading_days

# Annual seasonal period for TBATS: mean of the last five complete years
annual_period <- round(mean(tail(trading_days$trading_days, 5)))
annual_period
[1] 241

Closing Price

Full history

key_points <- bind_rows(
  ihsg %>% slice_max(close, n = 1, with_ties = FALSE) %>% mutate(tag = "All-time high"),
  ihsg %>% slice_min(close, n = 1, with_ties = FALSE) %>% mutate(tag = "Lowest close"),
  ihsg %>% slice_tail(n = 1)                          %>% mutate(tag = "Latest")
) %>%
  mutate(label = paste0(tag, "\n", format(date, "%d %b %Y"), "\n",
                        comma(close, accuracy = 1)))

p <- ggplot(ihsg, aes(date, close)) +
  geom_line(aes(colour = "Daily close"), linewidth = 0.35) +
  geom_ma(aes(colour = "50-day MA"),  ma_fun = SMA, n = 50,
          linetype = "solid", linewidth = 0.8) +
  geom_ma(aes(colour = "200-day MA"), ma_fun = SMA, n = 200,
          linetype = "solid", linewidth = 0.8) +
  geom_point(data = key_points, size = 2.2) +
  geom_label_repel(data = key_points, aes(label = label),
                   size = 3, label.size = 0.15, fill = alpha("white", 0.9),
                   box.padding = 0.6, min.segment.length = 0, seed = 42) +
  scale_colour_manual(values = c("Daily close" = pal[["grey"]],
                                 "50-day MA"   = pal[["orange"]],
                                 "200-day MA"  = pal[["navy"]]),
                      breaks = c("Daily close", "50-day MA", "200-day MA")) +
  scale_x_date(date_breaks = "1 year", date_labels = "%Y",
               expand = expansion(mult = c(0.01, 0.03))) +
  scale_y_continuous(labels = comma, expand = expansion(mult = 0.08)) +
  labs(title = "Jakarta Composite Index (JKSE)",
       subtitle = paste("Daily closing price,",
                        format(min(ihsg$date), "%b %Y"), "to",
                        format(max(ihsg$date), "%b %Y")),
       x = NULL, y = "Index level", caption = source_label) +
  theme_ihsg()

hd_plot(p, "01_close_full")

Last 12 months

ihsg_12m <- ihsg %>% filter(date >= max(date) %m-% months(12))

p <- ggplot(ihsg_12m, aes(date, close)) +
  geom_ribbon(aes(ymin = min(close), ymax = close),
              fill = pal[["navy"]], alpha = 0.12) +
  geom_line(colour = pal[["navy"]], linewidth = 0.6) +
  geom_ma(ma_fun = SMA, n = 20, colour = pal[["orange"]],
          linetype = "solid", linewidth = 0.7) +
  scale_x_date(date_breaks = "1 month", date_labels = "%b\n%Y") +
  scale_y_continuous(labels = comma, expand = expansion(mult = c(0, 0.05))) +
  labs(title = "JKSE Closing Price, Last 12 Months",
       subtitle = "Orange line: 20-day simple moving average",
       x = NULL, y = "Index level", caption = source_label) +
  theme_ihsg()

hd_plot(p, "02_close_12m")

Forecasting

Estimation window

train <- ihsg %>% filter(date >= forecast_start %m-% years(train_years))

nrow(train)
[1] 711
range(train$date)
[1] "2023-11-02" "2026-10-30"

Weekday effects in returns

Calendar effects are the only information about the forecast period that is known in advance. If mean returns differ by weekday, weekday dummies can make point forecasts vary from day to day; if they do not, any such variation is sampling noise. Note that Monday returns span the weekend.

ret_dow <- train %>%
  mutate(r   = 100 * log(close / lag(close)),
         dow = factor(lubridate::wday(date, week_start = 1), levels = 1:5,
                      labels = c("Mon", "Tue", "Wed", "Thu", "Fri"))) %>%
  filter(!is.na(r))

# Daily log returns (%) by weekday
ret_dow %>%
  group_by(dow) %>%
  summarise(n = n(), mean_return = mean(r), median_return = median(r), sd = sd(r)) %>%
  mutate(across(where(is.double), ~ round(.x, 3)))

# Rank-based test for differences between weekdays (robust to fat tails)
kruskal.test(r ~ dow, data = ret_dow)

    Kruskal-Wallis rank sum test

data:  r by dow
Kruskal-Wallis chi-squared = 3.0788, df = 4, p-value = 0.5447

Candidate models

Daily index levels behave close to a random walk, so the candidate set combines benchmarks for the level with a volatility model for the uncertainty:

  • Random walk and random walk with drift: the benchmarks every model must beat.
  • ARIMA and ETS on the log scale (lambda = 0), which keeps prediction intervals positive.
  • Theta: a strong benchmark in the M3 and M4 forecasting competitions.
  • TBATS with weekly (5) and annual trading-day cycles, the annual period estimated from the data.
  • NNAR: a neural network autoregression.
  • GJR-GARCH(1,1) with Student-t errors on daily log returns r_t = \log(P_t / P_{t-1}):

r_t = \mu + \varepsilon_t, \qquad \varepsilon_t = \sigma_t z_t, \qquad z_t \sim t_\nu

\sigma_t^2 = \omega + \left(\alpha + \gamma\, \mathbb{1}\{\varepsilon_{t-1} < 0\}\right)\varepsilon_{t-1}^2 + \beta\,\sigma_{t-1}^2

where \gamma captures the leverage effect (negative shocks raise volatility more than positive shocks of the same size). Price forecasts and intervals are obtained from 5,000 simulated return paths.

The ARIMA + weekday model regresses daily log returns (in %) on weekday dummies, with Monday as the baseline and ARMA errors:

r_t = \beta_0 + \sum_{d = \text{Tue}}^{\text{Fri}} \beta_d D_{d,t} + u_t, \qquad u_t \sim \text{ARMA}(p, q)

Its price forecasts and intervals come from 2,000 bootstrapped simulated paths.

library(rugarch)   

garch_spec <- ugarchspec(
  variance.model     = list(model = "gjrGARCH", garchOrder = c(1, 1)),
  mean.model         = list(armaOrder = c(0, 0), include.mean = TRUE),
  distribution.model = "std"
)

# GJR-GARCH price forecasts from simulated log-return paths
fc_garch <- function(y, h, nsim = 5000) {
  fit <- ugarchfit(garch_spec, data = diff(log(y)), solver = "hybrid")
  if (convergence(fit) != 0) stop("GARCH estimation did not converge")
  sim   <- ugarchsim(fit, n.sim = h, m.sim = nsim, startMethod = "sample")
  ret   <- matrix(fitted(sim), nrow = h)
  paths <- matrix(tail(y, 1) * exp(apply(ret, 2, cumsum)), nrow = h)
  q     <- function(p) apply(paths, 1, quantile, probs = p)
  list(method = "GJR-GARCH(1,1), Student-t",
       mean   = rowMeans(paths),
       lower  = cbind(q(0.100), q(0.025)),
       upper  = cbind(q(0.900), q(0.975)),
       model  = fit)
}

# Weekday dummies (Monday is the baseline)
dow_matrix <- function(dates) {
  dow <- factor(lubridate::wday(dates, week_start = 1), levels = 1:5,
                labels = c("Mon", "Tue", "Wed", "Thu", "Fri"))
  model.matrix(~ dow)[, -1, drop = FALSE]
}

# Weekday regression with ARMA errors on log returns (%); needs the dates of the
# estimation window and of the trading days to be forecast
fc_weekday <- function(y, dates, new_dates, nsim = 2000) {
  h     <- length(new_dates)
  r     <- 100 * diff(log(y))
  fit   <- auto.arima(r, xreg = dow_matrix(dates[-1]), d = 0)
  sims  <- replicate(nsim, as.numeric(simulate(fit, nsim = h, xreg = dow_matrix(new_dates),
                                               future = TRUE, bootstrap = TRUE)))
  paths <- matrix(tail(y, 1) * exp(apply(matrix(sims, nrow = h) / 100, 2, cumsum)), nrow = h)
  q     <- function(p) apply(paths, 1, quantile, probs = p)
  list(method = sprintf("Weekday + ARMA(%d,%d) errors", fit$arma[1], fit$arma[2]),
       mean   = rowMeans(paths),
       lower  = cbind(q(0.100), q(0.025)),
       upper  = cbind(q(0.900), q(0.975)),
       model  = fit)
}

# Each function takes a numeric price vector y and forecasts h trading days ahead
fc_funs <- list(
  "Random walk"            = function(y, h) naive(ts(y), h = h, level = lv),
  "Random walk with drift" = function(y, h) rwf(ts(y), drift = TRUE, h = h, level = lv),
  "ARIMA"                  = function(y, h) forecast(auto.arima(ts(y), lambda = 0, biasadj = TRUE),
                                                     h = h, level = lv),
  "ETS"                    = function(y, h) forecast(ets(ts(y), lambda = 0, biasadj = TRUE),
                                                     h = h, level = lv),
  "Theta"                  = function(y, h) thetaf(ts(y), h = h, level = lv),
  "TBATS"                  = function(y, h) forecast(tbats(msts(y, seasonal.periods = c(5, annual_period)),
                                                           use.box.cox = TRUE, use.parallel = FALSE),
                                                     h = h, level = lv),
  "NNAR"                   = function(y, h) forecast(nnetar(ts(y), lambda = 0),
                                                     h = h, PI = TRUE, level = lv),
  "GJR-GARCH"              = function(y, h) fc_garch(y, h)
)

# Convert a forecast object (or a simulation-based list) into a tidy tibble
tidy_fc <- function(fc, model, h) {
  if (is.null(fc)) {
    return(tibble(model = model, method = NA_character_, horizon = seq_len(h),
                  mean = NA_real_, lo80 = NA_real_, hi80 = NA_real_,
                  lo95 = NA_real_, hi95 = NA_real_))
  }
  tibble(model   = model,
         method  = if (is.null(fc$method)) model else fc$method,
         horizon = seq_len(h),
         mean    = as.numeric(fc$mean),
         lo80    = as.numeric(fc$lower[, 1]), hi80 = as.numeric(fc$upper[, 1]),
         lo95    = as.numeric(fc$lower[, 2]), hi95 = as.numeric(fc$upper[, 2]))
}

# Fit the selected price-only models on price vector y; failed fits return NA rows
run_models <- function(y, h, models = names(fc_funs)) {
  fcs <- map(fc_funs[models], ~ tryCatch(.x(y, h), error = function(e) NULL))
  imap_dfr(fcs, ~ tidy_fc(.x, .y, h))
}

Forecasts for 2 to 10 November 2026

set.seed(123)
fc_objects <- map(fc_funs, ~ tryCatch(.x(train$close, h), error = function(e) NULL))

# The weekday model also needs dates; list() keeps a failed fit as NULL
fc_objects["ARIMA + weekday"] <- list(
  tryCatch(fc_weekday(train$close, train$date, future_dates), error = function(e) NULL)
)

fc_final <- imap_dfr(fc_objects, ~ tidy_fc(.x, .y, h)) %>%
  mutate(date = future_dates[horizon])

# Specification selected for each model
fc_final %>% distinct(model, method)
garch_fit <- fc_objects[["GJR-GARCH"]]$model
wd_fit    <- fc_objects[["ARIMA + weekday"]]$model

# GJR-GARCH estimates, persistence, and half-life of volatility shocks (trading days)
if (!is.null(garch_fit)) {
  print(round(garch_fit@fit$matcoef, 4))
  print(c(persistence = persistence(garch_fit), half_life_days = halflife(garch_fit)))
}
        Estimate  Std. Error  t value Pr(>|t|)
mu        0.0005      0.0003   1.6035   0.1088
omega     0.0000      0.0000  38.2196   0.0000
alpha1    0.0000      0.0140   0.0000   1.0000
beta1     0.7942      0.0267  29.7512   0.0000
gamma1    0.2553      0.0585   4.3632   0.0000
shape     5.3398      0.8927   5.9814   0.0000
   persistence half_life_days 
     0.9218104      8.5136758 
# Weekday regression with ARMA errors (returns in %)
if (!is.null(wd_fit)) print(wd_fit)
Series: r 
Regression with ARIMA(3,0,0) errors 

Coefficients:
         ar1      ar2     ar3  dowTue  dowWed   dowThu   dowFri
      0.0669  -0.0799  0.0829  0.0722  0.0650  -0.0363  -0.2137
s.e.  0.0374   0.0377  0.0376  0.1127  0.1132   0.1132   0.1146

sigma^2 = 1.847:  log likelihood = -1221.75
AIC=2459.51   AICc=2459.71   BIC=2496.03
# Point forecasts by model
fc_final %>%
  select(date, model, mean) %>%
  pivot_wider(names_from = model, values_from = mean) %>%
  mutate(across(-date, ~ round(.x, 1)))
# Point forecasts with 80% and 95% prediction intervals
fc_final %>%
  select(model, date, mean, lo80, hi80, lo95, hi95) %>%
  mutate(across(mean:hi95, ~ round(.x, 1)))
plot_fc <- function(fc, hist, ncol = 2) {
  fc       <- fc %>% mutate(model = factor(model, levels = unique(model)))
  last_obs <- slice_tail(hist, n = 1)

  # Connect the last observation to each forecast line
  bridge <- distinct(fc, model) %>%
    mutate(date = last_obs$date, mean = last_obs$close,
           lo80 = mean, hi80 = mean, lo95 = mean, hi95 = mean)

  fc_line  <- bind_rows(fc, bridge) %>% arrange(model, date)
  hist_rep <- cross_join(distinct(fc, model), select(hist, date, close))

  ggplot() +
    geom_ribbon(data = fc_line, aes(date, ymin = lo95, ymax = hi95, fill = "95% interval"),
                alpha = 0.35) +
    geom_ribbon(data = fc_line, aes(date, ymin = lo80, ymax = hi80, fill = "80% interval"),
                alpha = 0.55) +
    geom_line(data = hist_rep, aes(date, close, colour = "Actual"), linewidth = 0.5) +
    geom_line(data = fc_line, aes(date, mean, colour = "Point forecast"), linewidth = 0.7) +
    geom_point(data = fc, aes(date, mean, colour = "Point forecast"), size = 1.2) +
    facet_wrap(~ model, ncol = ncol) +
    scale_fill_manual(values = c("95% interval" = "#c6d9ec", "80% interval" = "#8fb3d9"),
                      breaks = c("80% interval", "95% interval")) +
    scale_colour_manual(values = c("Actual" = "grey25", "Point forecast" = pal[["orange"]])) +
    scale_x_date(date_breaks = "2 weeks", date_labels = "%d %b") +
    scale_y_continuous(labels = comma) +
    labs(x = NULL, y = "Index level") +
    theme_ihsg()
}

# Use the selected specification as the panel title where it is informative
fc_plot_data <- fc_final %>%
  mutate(model = if_else(model %in% c("ARIMA", "ETS", "TBATS", "NNAR",
                                      "GJR-GARCH", "ARIMA + weekday"),
                         coalesce(method, model), model))

p <- plot_fc(fc_plot_data, slice_tail(train, n = 40), ncol = 3) +
  labs(title = "JKSE Closing Price Forecasts, 2 to 10 November 2026",
       subtitle = "Last 40 trading days with point forecasts and 80% / 95% prediction intervals",
       caption = source_label)

hd_plot(p, "03_forecasts_by_model", width = 10, height = 9)

Simulated price paths

The figure shows 30 of 2,000 paths simulated from the fitted GJR-GARCH model. Individual paths move up and down, but because the direction of each move is unpredictable, their average is nearly flat. The bands reflect the current level of volatility.

if (is.null(garch_fit)) stop("The GJR-GARCH fit is not available")

set.seed(123)
n_paths    <- 2000
last_close <- tail(train$close, 1)

sim   <- ugarchsim(garch_fit, n.sim = h, m.sim = n_paths, startMethod = "sample")
ret   <- matrix(fitted(sim), nrow = h)
paths <- rbind(rep(last_close, n_paths),
               last_close * exp(apply(ret, 2, cumsum)))
colnames(paths) <- paste0("path_", seq_len(n_paths))

path_tbl <- as_tibble(paths) %>%
  mutate(date = c(max(train$date), future_dates)) %>%
  pivot_longer(-date, names_to = "path", values_to = "close")

band_tbl <- path_tbl %>%
  group_by(date) %>%
  summarise(mean = mean(close),
            lo80 = quantile(close, 0.100), hi80 = quantile(close, 0.900),
            lo95 = quantile(close, 0.025), hi95 = quantile(close, 0.975),
            .groups = "drop")

shown <- sample(colnames(paths), 30)

p <- ggplot() +
  geom_ribbon(data = band_tbl, aes(date, ymin = lo95, ymax = hi95),
              fill = "#c6d9ec", alpha = 0.7) +
  geom_ribbon(data = band_tbl, aes(date, ymin = lo80, ymax = hi80),
              fill = "#8fb3d9", alpha = 0.7) +
  geom_line(data = filter(path_tbl, path %in% shown),
            aes(date, close, group = path),
            colour = pal[["navy"]], alpha = 0.4, linewidth = 0.35) +
  geom_line(data = slice_tail(train, n = 40), aes(date, close),
            colour = "grey25", linewidth = 0.6) +
  geom_line(data = band_tbl, aes(date, mean), colour = pal[["orange"]], linewidth = 1) +
  scale_x_date(date_breaks = "2 weeks", date_labels = "%d %b") +
  scale_y_continuous(labels = comma) +
  labs(title = "Simulated JKSE Price Paths, 2 to 10 November 2026",
       subtitle = "30 of 2,000 GJR-GARCH paths (blue), their mean (orange), and 80% / 95% bands",
       x = NULL, y = "Index level", caption = source_label) +
  theme_ihsg()

hd_plot(p, "04_simulated_paths")

Rolling-origin evaluation

A single holdout of a few days is too noisy to rank models. Each model is re-estimated on a rolling window of the same length as the final estimation window, at forecast origins spaced 5 trading days apart over the last 250 trading days. The ARIMA + weekday model uses the dates of each window and the actual trading dates that follow each origin, which are known in advance from the IDX calendar. Errors are averaged over all origins and horizons. Coverage close to 0.80 and 0.95 indicates well-calibrated prediction intervals. TBATS and NNAR are only included when run_slow_backtest = TRUE.

bt_models <- c("Random walk", "Random walk with drift", "ARIMA", "ETS", "Theta",
               "GJR-GARCH", "ARIMA + weekday")
if (run_slow_backtest) bt_models <- c(bt_models, "TBATS", "NNAR")

y_all   <- ihsg$close
n_win   <- nrow(train)                               # same window length as the final models
origins <- seq(length(y_all) - 250, length(y_all) - h, by = 5)

set.seed(123)
bt <- map_dfr(origins, function(o) {
  idx <- (o - n_win + 1):o

  # Models that use prices only
  fc_price <- run_models(y_all[idx], h, models = intersect(bt_models, names(fc_funs)))

  # Weekday model: also needs the window dates and the next h trading dates
  wd_obj <- tryCatch(fc_weekday(y_all[idx], ihsg$date[idx], ihsg$date[o + seq_len(h)],
                                nsim = 1000),
                     error = function(e) NULL)

  bind_rows(fc_price, tidy_fc(wd_obj, "ARIMA + weekday", h)) %>%
    mutate(origin      = o,
           origin_date = ihsg$date[o],
           actual      = y_all[o + horizon])
})

saveRDS(bt, "backtest_results.rds")   
bt %>%
  group_by(model) %>%
  summarise(
    RMSE           = sqrt(mean((actual - mean)^2, na.rm = TRUE)),
    MAE            = mean(abs(actual - mean), na.rm = TRUE),
    MAPE           = 100 * mean(abs((actual - mean) / actual), na.rm = TRUE),
    `Coverage 80%` = mean(actual >= lo80 & actual <= hi80, na.rm = TRUE),
    `Coverage 95%` = mean(actual >= lo95 & actual <= hi95, na.rm = TRUE),
    `Failed fits`  = sum(is.na(mean)) / h,
    .groups = "drop"
  ) %>%
  mutate(`RMSE vs RW` = RMSE / RMSE[model == "Random walk"]) %>%
  relocate(`RMSE vs RW`, .after = RMSE) %>%
  arrange(RMSE) %>%
  mutate(across(where(is.numeric), ~ round(.x, 3)))
rmse_h <- bt %>%
  group_by(model, horizon) %>%
  summarise(RMSE = sqrt(mean((actual - mean)^2, na.rm = TRUE)), .groups = "drop") %>%
  group_by(horizon) %>%
  mutate(rel_rmse = RMSE / RMSE[model == "Random walk"]) %>%
  ungroup() %>%
  mutate(model = factor(model, levels = bt_models))

# Nine distinct colours, enough for all models when run_slow_backtest = TRUE
model_cols <- c("#1f4e79", "#e07b39", "#2e8b57", "#c0392b", "#7b3294",
                "#8c6d31", "#17becf", "#e377c2", "grey40")

p <- ggplot(rmse_h, aes(horizon, rel_rmse, colour = model)) +
  geom_hline(yintercept = 1, linetype = "dashed", colour = "grey40") +
  geom_line(linewidth = 0.8) +
  geom_point(size = 1.8) +
  scale_colour_manual(values = model_cols) +
  scale_x_continuous(breaks = seq_len(h)) +
  guides(colour = guide_legend(nrow = 2)) +
  labs(title = "Forecast Accuracy Relative to the Random Walk",
       subtitle = "RMSE ratio by horizon across all origins; values below 1 beat the random walk",
       x = "Forecast horizon (trading days)", y = "Relative RMSE",
       caption = source_label) +
  theme_ihsg()

hd_plot(p, "05_relative_rmse", height = 4.5)

Diebold–Mariano tests compare one-step-ahead squared errors of each model with the random walk. A negative statistic means the model has smaller errors; a p-value below 0.05 indicates that the difference is statistically significant.

e1 <- bt %>%
  filter(horizon == 1) %>%
  transmute(origin, model, error = actual - mean) %>%
  pivot_wider(names_from = model, values_from = error)

map_dfr(setdiff(bt_models, "Random walk"), function(m) {
  ok   <- complete.cases(e1[[m]], e1[["Random walk"]])
  test <- dm.test(e1[[m]][ok], e1[["Random walk"]][ok], h = 1, power = 2)
  tibble(model = m, DM_statistic = unname(test$statistic), p_value = test$p.value)
}) %>%
  mutate(across(where(is.numeric), ~ round(.x, 3)))
LS0tCnRpdGxlOiAiSktTRSBJbmRleCBBbmFseXNpcyIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBtYXRoX21ldGhvZDoga2F0ZXgKICAgIHRoZW1lOiB5ZXRpCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDoKICAgICAgdG9jX2NvbGxhcHNlZDogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQogICAgZGZfcHJpbnQ6IHBhZ2VkCi0tLQoKIyMgTGlicmFyaWVzCgpgYGB7ciBzZXR1cH0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAoJZmlnLmFsaWduID0gImNlbnRlciIsCgltZXNzYWdlID0gRkFMU0UsCgl3YXJuaW5nID0gRkFMU0UsCglvdXQud2lkdGggPSAiMTAwJSIKKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh0aWR5cXVhbnQpCmxpYnJhcnkoZm9yZWNhc3QpCmxpYnJhcnkoZ2dyZXBlbCkKbGlicmFyeShzY2FsZXMpCgojIFNhdmUgYSBnZ3Bsb3QgYXMgYSBoaWdoLXJlc29sdXRpb24gUE5HIGFuZCBkaXNwbGF5IGl0IGlubGluZQpoZF9wbG90IDwtIGZ1bmN0aW9uKHAsIG5hbWUsIHdpZHRoID0gOSwgaGVpZ2h0ID0gNSwgZHBpID0gMzAwKSB7CiAgZGlyLmNyZWF0ZSgiZmlndXJlcyIsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQogIHBhdGggPC0gZmlsZS5wYXRoKCJmaWd1cmVzIiwgcGFzdGUwKG5hbWUsICIucG5nIikpCiAgZ2dzYXZlKHBhdGgsIHBsb3QgPSBwLCB3aWR0aCA9IHdpZHRoLCBoZWlnaHQgPSBoZWlnaHQsIGRwaSA9IGRwaSwKICAgICAgICAgZGV2aWNlID0gcmFnZzo6YWdnX3BuZywgYmcgPSAid2hpdGUiKQogIGtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKHBhdGgpCn0KCiMgQ29sb3VyIHBhbGV0dGUgYW5kIHRoZW1lCnBhbCA8LSBjKG5hdnkgPSAiIzFmNGU3OSIsIG9yYW5nZSA9ICIjZTA3YjM5IiwgZ3JlZW4gPSAiIzJlOGI1NyIsCiAgICAgICAgIHJlZCA9ICIjYzAzOTJiIiwgZ3JleSA9ICJncmV5NTUiKQoKdGhlbWVfaWhzZyA8LSBmdW5jdGlvbihiYXNlX3NpemUgPSAxMikgewogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gYmFzZV9zaXplKSArCiAgICB0aGVtZSgKICAgICAgcGxvdC50aXRsZSAgICAgICAgICAgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgc2l6ZSA9IHJlbCgxLjI1KSksCiAgICAgIHBsb3Quc3VidGl0bGUgICAgICAgID0gZWxlbWVudF90ZXh0KGNvbG91ciA9ICJncmV5NDAiLCBtYXJnaW4gPSBtYXJnaW4oYiA9IDgpKSwKICAgICAgcGxvdC5jYXB0aW9uICAgICAgICAgPSBlbGVtZW50X3RleHQoY29sb3VyID0gImdyZXk1MCIsIHNpemUgPSByZWwoMC43NSkpLAogICAgICBwbG90LnRpdGxlLnBvc2l0aW9uICA9ICJwbG90IiwKICAgICAgcGFuZWwuZ3JpZC5taW5vciAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgIGxlZ2VuZC5wb3NpdGlvbiAgICAgID0gInRvcCIsCiAgICAgIGxlZ2VuZC5qdXN0aWZpY2F0aW9uID0gImxlZnQiLAogICAgICBsZWdlbmQudGl0bGUgICAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgc3RyaXAudGV4dCAgICAgICAgICAgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwKQogICAgKQp9Cgpzb3VyY2VfbGFiZWwgPC0gIlNvdXJjZTogWWFob28gRmluYW5jZSAoXkpLU0UpIgpgYGAKCiMjIFNldHRpbmdzCgpgYGB7cn0KZm9yZWNhc3Rfc3RhcnQgICAgPC0gYXMuRGF0ZSgiMjAyNi0xMS0wMiIpCmZvcmVjYXN0X2VuZCAgICAgIDwtIGFzLkRhdGUoIjIwMjYtMTEtMTAiKQp0cmFpbl95ZWFycyAgICAgICA8LSAzICAgICAgICMgbGVuZ3RoIG9mIHRoZSBlc3RpbWF0aW9uIHdpbmRvdyAoeWVhcnMpCnJ1bl9zbG93X2JhY2t0ZXN0IDwtIEZBTFNFICAgIyBUUlVFIGFkZHMgVEJBVFMgYW5kIE5OQVIgdG8gdGhlIGJhY2t0ZXN0IChtdWNoIHNsb3dlcikKbHYgICAgICAgICAgICAgICAgPC0gYyg4MCwgOTUpCgojIElEWCBob2xpZGF5cyB0aGF0IGNvdWxkIGZhbGwgaW5zaWRlIGEgZm9yZWNhc3QgaG9yaXpvbiAoMjAyNiBjYWxlbmRhcik7IGV4dGVuZCBhcyBuZWVkZWQKaWR4X2hvbGlkYXlzIDwtIGFzLkRhdGUoYygiMjAyNi0xMi0yNCIsICIyMDI2LTEyLTI1IiwgIjIwMjYtMTItMzEiKSkKCiMgVHJhZGluZyBkYXlzIGluIHRoZSBmb3JlY2FzdCBob3Jpem9uCmZ1dHVyZV9kYXRlcyA8LSBzZXEoZm9yZWNhc3Rfc3RhcnQsIGZvcmVjYXN0X2VuZCwgYnkgPSAiZGF5IikKZnV0dXJlX2RhdGVzIDwtIGZ1dHVyZV9kYXRlc1tsdWJyaWRhdGU6OndkYXkoZnV0dXJlX2RhdGVzLCB3ZWVrX3N0YXJ0ID0gMSkgPD0gNSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAhZnV0dXJlX2RhdGVzICVpbiUgaWR4X2hvbGlkYXlzXQpoIDwtIGxlbmd0aChmdXR1cmVfZGF0ZXMpCgpmdXR1cmVfZGF0ZXMKYGBgCgojIyBEYXRhCgpEYWlseSBkYXRhIGFyZSBkb3dubG9hZGVkIGZyb20gWWFob28gRmluYW5jZS4gV2Vla2VuZCBkYXRlcyBhbmQgZmxhdCBiYXJzCihvcGVuID0gaGlnaCA9IGxvdyA9IGNsb3NlKSwgd2hpY2ggWWFob28gb2NjYXNpb25hbGx5IHJlY29yZHMgb24gSURYIGhvbGlkYXlzLAphcmUgbGlzdGVkIGZpcnN0IGFuZCB0aGVuIHJlbW92ZWQuCgpgYGB7cn0KaWhzZ19yYXcgPC0gdHFfZ2V0KCJeSktTRSIsIGZyb20gPSAiMjAxNS0wMS0wMSIpCgppaHNnX2ZsYWdnZWQgPC0gaWhzZ19yYXcgJT4lCiAgZHBseXI6OmZpbHRlcighaXMubmEoY2xvc2UpLCBkYXRlIDwgZm9yZWNhc3Rfc3RhcnQpICU+JQogIGRpc3RpbmN0KGRhdGUsIC5rZWVwX2FsbCA9IFRSVUUpICU+JQogIGFycmFuZ2UoZGF0ZSkgJT4lCiAgbXV0YXRlKAogICAgd2Vla2VuZCAgPSBsdWJyaWRhdGU6OndkYXkoZGF0ZSwgd2Vla19zdGFydCA9IDEpID4gNSwKICAgIGZsYXRfYmFyID0gY29hbGVzY2Uob3BlbiA9PSBoaWdoICYgaGlnaCA9PSBsb3cgJiBsb3cgPT0gY2xvc2UsIEZBTFNFKQogICkKCiMgUm93cyB0byBiZSByZW1vdmVkCmloc2dfZmxhZ2dlZCAlPiUgZmlsdGVyKHdlZWtlbmQgfCBmbGF0X2JhcikKCmloc2cgPC0gaWhzZ19mbGFnZ2VkICU+JQogIGZpbHRlcighd2Vla2VuZCwgIWZsYXRfYmFyKSAlPiUKICBzZWxlY3QoZGF0ZSwgY2xvc2UpCgp0YWlsKGloc2cpCmBgYAoKIyMgVHJhZGluZyBDYWxlbmRhcgoKVGhlIHNlcmllcyBjb250YWlucyBJRFggdHJhZGluZyBkYXlzIG9ubHksIHNvIGdhcHMgbG9uZ2VyIHRoYW4gYSB3ZWVrZW5kCmNvcnJlc3BvbmQgdG8gbmF0aW9uYWwgaG9saWRheXMgYW5kIGNvbGxlY3RpdmUgbGVhdmUgZGF5cy4KCmBgYHtyfQojIExvbmdlc3QgZ2FwcyBiZXR3ZWVuIGNvbnNlY3V0aXZlIHRyYWRpbmcgZGF0ZXMKaWhzZyAlPiUKICBtdXRhdGUocHJldmlvdXNfZGF0ZSA9IGxhZyhkYXRlKSwKICAgICAgICAgZ2FwX2RheXMgICAgICA9IGFzLm51bWVyaWMoZGF0ZSAtIHByZXZpb3VzX2RhdGUpKSAlPiUKICBmaWx0ZXIoZ2FwX2RheXMgPiAzKSAlPiUKICBhcnJhbmdlKGRlc2MoZ2FwX2RheXMpKSAlPiUKICBzZWxlY3QocHJldmlvdXNfZGF0ZSwgZGF0ZSwgZ2FwX2RheXMpICU+JQogIGhlYWQoMTApCmBgYAoKYGBge3J9CiMgVHJhZGluZyBkYXlzIHBlciBjb21wbGV0ZSBjYWxlbmRhciB5ZWFyCnRyYWRpbmdfZGF5cyA8LSBpaHNnICU+JQogIGNvdW50KHllYXIgPSBsdWJyaWRhdGU6OnllYXIoZGF0ZSksIG5hbWUgPSAidHJhZGluZ19kYXlzIikgJT4lCiAgZmlsdGVyKHllYXIgPCBsdWJyaWRhdGU6OnllYXIobWF4KGloc2ckZGF0ZSkpKQoKdHJhZGluZ19kYXlzCgojIEFubnVhbCBzZWFzb25hbCBwZXJpb2QgZm9yIFRCQVRTOiBtZWFuIG9mIHRoZSBsYXN0IGZpdmUgY29tcGxldGUgeWVhcnMKYW5udWFsX3BlcmlvZCA8LSByb3VuZChtZWFuKHRhaWwodHJhZGluZ19kYXlzJHRyYWRpbmdfZGF5cywgNSkpKQphbm51YWxfcGVyaW9kCmBgYAoKIyMgQ2xvc2luZyBQcmljZQoKIyMjIEZ1bGwgaGlzdG9yeQoKYGBge3J9CmtleV9wb2ludHMgPC0gYmluZF9yb3dzKAogIGloc2cgJT4lIHNsaWNlX21heChjbG9zZSwgbiA9IDEsIHdpdGhfdGllcyA9IEZBTFNFKSAlPiUgbXV0YXRlKHRhZyA9ICJBbGwtdGltZSBoaWdoIiksCiAgaWhzZyAlPiUgc2xpY2VfbWluKGNsb3NlLCBuID0gMSwgd2l0aF90aWVzID0gRkFMU0UpICU+JSBtdXRhdGUodGFnID0gIkxvd2VzdCBjbG9zZSIpLAogIGloc2cgJT4lIHNsaWNlX3RhaWwobiA9IDEpICAgICAgICAgICAgICAgICAgICAgICAgICAlPiUgbXV0YXRlKHRhZyA9ICJMYXRlc3QiKQopICU+JQogIG11dGF0ZShsYWJlbCA9IHBhc3RlMCh0YWcsICJcbiIsIGZvcm1hdChkYXRlLCAiJWQgJWIgJVkiKSwgIlxuIiwKICAgICAgICAgICAgICAgICAgICAgICAgY29tbWEoY2xvc2UsIGFjY3VyYWN5ID0gMSkpKQoKcCA8LSBnZ3Bsb3QoaWhzZywgYWVzKGRhdGUsIGNsb3NlKSkgKwogIGdlb21fbGluZShhZXMoY29sb3VyID0gIkRhaWx5IGNsb3NlIiksIGxpbmV3aWR0aCA9IDAuMzUpICsKICBnZW9tX21hKGFlcyhjb2xvdXIgPSAiNTAtZGF5IE1BIiksICBtYV9mdW4gPSBTTUEsIG4gPSA1MCwKICAgICAgICAgIGxpbmV0eXBlID0gInNvbGlkIiwgbGluZXdpZHRoID0gMC44KSArCiAgZ2VvbV9tYShhZXMoY29sb3VyID0gIjIwMC1kYXkgTUEiKSwgbWFfZnVuID0gU01BLCBuID0gMjAwLAogICAgICAgICAgbGluZXR5cGUgPSAic29saWQiLCBsaW5ld2lkdGggPSAwLjgpICsKICBnZW9tX3BvaW50KGRhdGEgPSBrZXlfcG9pbnRzLCBzaXplID0gMi4yKSArCiAgZ2VvbV9sYWJlbF9yZXBlbChkYXRhID0ga2V5X3BvaW50cywgYWVzKGxhYmVsID0gbGFiZWwpLAogICAgICAgICAgICAgICAgICAgc2l6ZSA9IDMsIGxhYmVsLnNpemUgPSAwLjE1LCBmaWxsID0gYWxwaGEoIndoaXRlIiwgMC45KSwKICAgICAgICAgICAgICAgICAgIGJveC5wYWRkaW5nID0gMC42LCBtaW4uc2VnbWVudC5sZW5ndGggPSAwLCBzZWVkID0gNDIpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoIkRhaWx5IGNsb3NlIiA9IHBhbFtbImdyZXkiXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI1MC1kYXkgTUEiICAgPSBwYWxbWyJvcmFuZ2UiXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIyMDAtZGF5IE1BIiAgPSBwYWxbWyJuYXZ5Il1dKSwKICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoIkRhaWx5IGNsb3NlIiwgIjUwLWRheSBNQSIsICIyMDAtZGF5IE1BIikpICsKICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3MgPSAiMSB5ZWFyIiwgZGF0ZV9sYWJlbHMgPSAiJVkiLAogICAgICAgICAgICAgICBleHBhbmQgPSBleHBhbnNpb24obXVsdCA9IGMoMC4wMSwgMC4wMykpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hLCBleHBhbmQgPSBleHBhbnNpb24obXVsdCA9IDAuMDgpKSArCiAgbGFicyh0aXRsZSA9ICJKYWthcnRhIENvbXBvc2l0ZSBJbmRleCAoSktTRSkiLAogICAgICAgc3VidGl0bGUgPSBwYXN0ZSgiRGFpbHkgY2xvc2luZyBwcmljZSwiLAogICAgICAgICAgICAgICAgICAgICAgICBmb3JtYXQobWluKGloc2ckZGF0ZSksICIlYiAlWSIpLCAidG8iLAogICAgICAgICAgICAgICAgICAgICAgICBmb3JtYXQobWF4KGloc2ckZGF0ZSksICIlYiAlWSIpKSwKICAgICAgIHggPSBOVUxMLCB5ID0gIkluZGV4IGxldmVsIiwgY2FwdGlvbiA9IHNvdXJjZV9sYWJlbCkgKwogIHRoZW1lX2loc2coKQoKaGRfcGxvdChwLCAiMDFfY2xvc2VfZnVsbCIpCmBgYAoKIyMjIExhc3QgMTIgbW9udGhzCgpgYGB7cn0KaWhzZ18xMm0gPC0gaWhzZyAlPiUgZmlsdGVyKGRhdGUgPj0gbWF4KGRhdGUpICVtLSUgbW9udGhzKDEyKSkKCnAgPC0gZ2dwbG90KGloc2dfMTJtLCBhZXMoZGF0ZSwgY2xvc2UpKSArCiAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSBtaW4oY2xvc2UpLCB5bWF4ID0gY2xvc2UpLAogICAgICAgICAgICAgIGZpbGwgPSBwYWxbWyJuYXZ5Il1dLCBhbHBoYSA9IDAuMTIpICsKICBnZW9tX2xpbmUoY29sb3VyID0gcGFsW1sibmF2eSJdXSwgbGluZXdpZHRoID0gMC42KSArCiAgZ2VvbV9tYShtYV9mdW4gPSBTTUEsIG4gPSAyMCwgY29sb3VyID0gcGFsW1sib3JhbmdlIl1dLAogICAgICAgICAgbGluZXR5cGUgPSAic29saWQiLCBsaW5ld2lkdGggPSAwLjcpICsKICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3MgPSAiMSBtb250aCIsIGRhdGVfbGFiZWxzID0gIiViXG4lWSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEsIGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLCAwLjA1KSkpICsKICBsYWJzKHRpdGxlID0gIkpLU0UgQ2xvc2luZyBQcmljZSwgTGFzdCAxMiBNb250aHMiLAogICAgICAgc3VidGl0bGUgPSAiT3JhbmdlIGxpbmU6IDIwLWRheSBzaW1wbGUgbW92aW5nIGF2ZXJhZ2UiLAogICAgICAgeCA9IE5VTEwsIHkgPSAiSW5kZXggbGV2ZWwiLCBjYXB0aW9uID0gc291cmNlX2xhYmVsKSArCiAgdGhlbWVfaWhzZygpCgpoZF9wbG90KHAsICIwMl9jbG9zZV8xMm0iKQpgYGAKCiMjIEZvcmVjYXN0aW5nCgojIyMgRXN0aW1hdGlvbiB3aW5kb3cKCmBgYHtyfQp0cmFpbiA8LSBpaHNnICU+JSBmaWx0ZXIoZGF0ZSA+PSBmb3JlY2FzdF9zdGFydCAlbS0lIHllYXJzKHRyYWluX3llYXJzKSkKCm5yb3codHJhaW4pCnJhbmdlKHRyYWluJGRhdGUpCmBgYAoKIyMjIFdlZWtkYXkgZWZmZWN0cyBpbiByZXR1cm5zCgpDYWxlbmRhciBlZmZlY3RzIGFyZSB0aGUgb25seSBpbmZvcm1hdGlvbiBhYm91dCB0aGUgZm9yZWNhc3QgcGVyaW9kIHRoYXQgaXMKa25vd24gaW4gYWR2YW5jZS4gSWYgbWVhbiByZXR1cm5zIGRpZmZlciBieSB3ZWVrZGF5LCB3ZWVrZGF5IGR1bW1pZXMgY2FuIG1ha2UKcG9pbnQgZm9yZWNhc3RzIHZhcnkgZnJvbSBkYXkgdG8gZGF5OyBpZiB0aGV5IGRvIG5vdCwgYW55IHN1Y2ggdmFyaWF0aW9uIGlzCnNhbXBsaW5nIG5vaXNlLiBOb3RlIHRoYXQgTW9uZGF5IHJldHVybnMgc3BhbiB0aGUgd2Vla2VuZC4KCmBgYHtyfQpyZXRfZG93IDwtIHRyYWluICU+JQogIG11dGF0ZShyICAgPSAxMDAgKiBsb2coY2xvc2UgLyBsYWcoY2xvc2UpKSwKICAgICAgICAgZG93ID0gZmFjdG9yKGx1YnJpZGF0ZTo6d2RheShkYXRlLCB3ZWVrX3N0YXJ0ID0gMSksIGxldmVscyA9IDE6NSwKICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIk1vbiIsICJUdWUiLCAiV2VkIiwgIlRodSIsICJGcmkiKSkpICU+JQogIGZpbHRlcighaXMubmEocikpCgojIERhaWx5IGxvZyByZXR1cm5zICglKSBieSB3ZWVrZGF5CnJldF9kb3cgJT4lCiAgZ3JvdXBfYnkoZG93KSAlPiUKICBzdW1tYXJpc2UobiA9IG4oKSwgbWVhbl9yZXR1cm4gPSBtZWFuKHIpLCBtZWRpYW5fcmV0dXJuID0gbWVkaWFuKHIpLCBzZCA9IHNkKHIpKSAlPiUKICBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLmRvdWJsZSksIH4gcm91bmQoLngsIDMpKSkKCiMgUmFuay1iYXNlZCB0ZXN0IGZvciBkaWZmZXJlbmNlcyBiZXR3ZWVuIHdlZWtkYXlzIChyb2J1c3QgdG8gZmF0IHRhaWxzKQprcnVza2FsLnRlc3QociB+IGRvdywgZGF0YSA9IHJldF9kb3cpCmBgYAoKIyMjIENhbmRpZGF0ZSBtb2RlbHMKCkRhaWx5IGluZGV4IGxldmVscyBiZWhhdmUgY2xvc2UgdG8gYSByYW5kb20gd2Fsaywgc28gdGhlIGNhbmRpZGF0ZSBzZXQgY29tYmluZXMKYmVuY2htYXJrcyBmb3IgdGhlIGxldmVsIHdpdGggYSB2b2xhdGlsaXR5IG1vZGVsIGZvciB0aGUgdW5jZXJ0YWludHk6CgotICoqUmFuZG9tIHdhbGsqKiBhbmQgKipyYW5kb20gd2FsayB3aXRoIGRyaWZ0Kio6IHRoZSBiZW5jaG1hcmtzIGV2ZXJ5IG1vZGVsIG11c3QgYmVhdC4KLSAqKkFSSU1BKiogYW5kICoqRVRTKiogb24gdGhlIGxvZyBzY2FsZSAoYGxhbWJkYSA9IDBgKSwgd2hpY2gga2VlcHMgcHJlZGljdGlvbiBpbnRlcnZhbHMgcG9zaXRpdmUuCi0gKipUaGV0YSoqOiBhIHN0cm9uZyBiZW5jaG1hcmsgaW4gdGhlIE0zIGFuZCBNNCBmb3JlY2FzdGluZyBjb21wZXRpdGlvbnMuCi0gKipUQkFUUyoqIHdpdGggd2Vla2x5ICg1KSBhbmQgYW5udWFsIHRyYWRpbmctZGF5IGN5Y2xlcywgdGhlIGFubnVhbCBwZXJpb2QgZXN0aW1hdGVkIGZyb20gdGhlIGRhdGEuCi0gKipOTkFSKio6IGEgbmV1cmFsIG5ldHdvcmsgYXV0b3JlZ3Jlc3Npb24uCi0gKipHSlItR0FSQ0goMSwxKSoqIHdpdGggU3R1ZGVudC10IGVycm9ycyBvbiBkYWlseSBsb2cgcmV0dXJucyAkcl90ID0gXGxvZyhQX3QgLyBQX3t0LTF9KSQ6CgokJApyX3QgPSBcbXUgKyBcdmFyZXBzaWxvbl90LCBccXF1YWQgXHZhcmVwc2lsb25fdCA9IFxzaWdtYV90IHpfdCwgXHFxdWFkIHpfdCBcc2ltIHRfXG51CiQkCgokJApcc2lnbWFfdF4yID0gXG9tZWdhICsgXGxlZnQoXGFscGhhICsgXGdhbW1hXCwgXG1hdGhiYnsxfVx7XHZhcmVwc2lsb25fe3QtMX0gPCAwXH1ccmlnaHQpXHZhcmVwc2lsb25fe3QtMX1eMiArIFxiZXRhXCxcc2lnbWFfe3QtMX1eMgokJAoKd2hlcmUgJFxnYW1tYSQgY2FwdHVyZXMgdGhlIGxldmVyYWdlIGVmZmVjdCAobmVnYXRpdmUgc2hvY2tzIHJhaXNlIHZvbGF0aWxpdHkgbW9yZQp0aGFuIHBvc2l0aXZlIHNob2NrcyBvZiB0aGUgc2FtZSBzaXplKS4gUHJpY2UgZm9yZWNhc3RzIGFuZCBpbnRlcnZhbHMgYXJlIG9idGFpbmVkCmZyb20gNSwwMDAgc2ltdWxhdGVkIHJldHVybiBwYXRocy4KClRoZSAqKkFSSU1BICsgd2Vla2RheSoqIG1vZGVsIHJlZ3Jlc3NlcyBkYWlseSBsb2cgcmV0dXJucyAoaW4gJSkgb24gd2Vla2RheQpkdW1taWVzLCB3aXRoIE1vbmRheSBhcyB0aGUgYmFzZWxpbmUgYW5kIEFSTUEgZXJyb3JzOgoKJCQKcl90ID0gXGJldGFfMCArIFxzdW1fe2QgPSBcdGV4dHtUdWV9fV57XHRleHR7RnJpfX0gXGJldGFfZCBEX3tkLHR9ICsgdV90LCBccXF1YWQgdV90IFxzaW0gXHRleHR7QVJNQX0ocCwgcSkKJCQKCkl0cyBwcmljZSBmb3JlY2FzdHMgYW5kIGludGVydmFscyBjb21lIGZyb20gMiwwMDAgYm9vdHN0cmFwcGVkIHNpbXVsYXRlZCBwYXRocy4KCmBgYHtyfQpsaWJyYXJ5KHJ1Z2FyY2gpICAgCgpnYXJjaF9zcGVjIDwtIHVnYXJjaHNwZWMoCiAgdmFyaWFuY2UubW9kZWwgICAgID0gbGlzdChtb2RlbCA9ICJnanJHQVJDSCIsIGdhcmNoT3JkZXIgPSBjKDEsIDEpKSwKICBtZWFuLm1vZGVsICAgICAgICAgPSBsaXN0KGFybWFPcmRlciA9IGMoMCwgMCksIGluY2x1ZGUubWVhbiA9IFRSVUUpLAogIGRpc3RyaWJ1dGlvbi5tb2RlbCA9ICJzdGQiCikKCiMgR0pSLUdBUkNIIHByaWNlIGZvcmVjYXN0cyBmcm9tIHNpbXVsYXRlZCBsb2ctcmV0dXJuIHBhdGhzCmZjX2dhcmNoIDwtIGZ1bmN0aW9uKHksIGgsIG5zaW0gPSA1MDAwKSB7CiAgZml0IDwtIHVnYXJjaGZpdChnYXJjaF9zcGVjLCBkYXRhID0gZGlmZihsb2coeSkpLCBzb2x2ZXIgPSAiaHlicmlkIikKICBpZiAoY29udmVyZ2VuY2UoZml0KSAhPSAwKSBzdG9wKCJHQVJDSCBlc3RpbWF0aW9uIGRpZCBub3QgY29udmVyZ2UiKQogIHNpbSAgIDwtIHVnYXJjaHNpbShmaXQsIG4uc2ltID0gaCwgbS5zaW0gPSBuc2ltLCBzdGFydE1ldGhvZCA9ICJzYW1wbGUiKQogIHJldCAgIDwtIG1hdHJpeChmaXR0ZWQoc2ltKSwgbnJvdyA9IGgpCiAgcGF0aHMgPC0gbWF0cml4KHRhaWwoeSwgMSkgKiBleHAoYXBwbHkocmV0LCAyLCBjdW1zdW0pKSwgbnJvdyA9IGgpCiAgcSAgICAgPC0gZnVuY3Rpb24ocCkgYXBwbHkocGF0aHMsIDEsIHF1YW50aWxlLCBwcm9icyA9IHApCiAgbGlzdChtZXRob2QgPSAiR0pSLUdBUkNIKDEsMSksIFN0dWRlbnQtdCIsCiAgICAgICBtZWFuICAgPSByb3dNZWFucyhwYXRocyksCiAgICAgICBsb3dlciAgPSBjYmluZChxKDAuMTAwKSwgcSgwLjAyNSkpLAogICAgICAgdXBwZXIgID0gY2JpbmQocSgwLjkwMCksIHEoMC45NzUpKSwKICAgICAgIG1vZGVsICA9IGZpdCkKfQoKIyBXZWVrZGF5IGR1bW1pZXMgKE1vbmRheSBpcyB0aGUgYmFzZWxpbmUpCmRvd19tYXRyaXggPC0gZnVuY3Rpb24oZGF0ZXMpIHsKICBkb3cgPC0gZmFjdG9yKGx1YnJpZGF0ZTo6d2RheShkYXRlcywgd2Vla19zdGFydCA9IDEpLCBsZXZlbHMgPSAxOjUsCiAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJNb24iLCAiVHVlIiwgIldlZCIsICJUaHUiLCAiRnJpIikpCiAgbW9kZWwubWF0cml4KH4gZG93KVssIC0xLCBkcm9wID0gRkFMU0VdCn0KCiMgV2Vla2RheSByZWdyZXNzaW9uIHdpdGggQVJNQSBlcnJvcnMgb24gbG9nIHJldHVybnMgKCUpOyBuZWVkcyB0aGUgZGF0ZXMgb2YgdGhlCiMgZXN0aW1hdGlvbiB3aW5kb3cgYW5kIG9mIHRoZSB0cmFkaW5nIGRheXMgdG8gYmUgZm9yZWNhc3QKZmNfd2Vla2RheSA8LSBmdW5jdGlvbih5LCBkYXRlcywgbmV3X2RhdGVzLCBuc2ltID0gMjAwMCkgewogIGggICAgIDwtIGxlbmd0aChuZXdfZGF0ZXMpCiAgciAgICAgPC0gMTAwICogZGlmZihsb2coeSkpCiAgZml0ICAgPC0gYXV0by5hcmltYShyLCB4cmVnID0gZG93X21hdHJpeChkYXRlc1stMV0pLCBkID0gMCkKICBzaW1zICA8LSByZXBsaWNhdGUobnNpbSwgYXMubnVtZXJpYyhzaW11bGF0ZShmaXQsIG5zaW0gPSBoLCB4cmVnID0gZG93X21hdHJpeChuZXdfZGF0ZXMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1dHVyZSA9IFRSVUUsIGJvb3RzdHJhcCA9IFRSVUUpKSkKICBwYXRocyA8LSBtYXRyaXgodGFpbCh5LCAxKSAqIGV4cChhcHBseShtYXRyaXgoc2ltcywgbnJvdyA9IGgpIC8gMTAwLCAyLCBjdW1zdW0pKSwgbnJvdyA9IGgpCiAgcSAgICAgPC0gZnVuY3Rpb24ocCkgYXBwbHkocGF0aHMsIDEsIHF1YW50aWxlLCBwcm9icyA9IHApCiAgbGlzdChtZXRob2QgPSBzcHJpbnRmKCJXZWVrZGF5ICsgQVJNQSglZCwlZCkgZXJyb3JzIiwgZml0JGFybWFbMV0sIGZpdCRhcm1hWzJdKSwKICAgICAgIG1lYW4gICA9IHJvd01lYW5zKHBhdGhzKSwKICAgICAgIGxvd2VyICA9IGNiaW5kKHEoMC4xMDApLCBxKDAuMDI1KSksCiAgICAgICB1cHBlciAgPSBjYmluZChxKDAuOTAwKSwgcSgwLjk3NSkpLAogICAgICAgbW9kZWwgID0gZml0KQp9CgojIEVhY2ggZnVuY3Rpb24gdGFrZXMgYSBudW1lcmljIHByaWNlIHZlY3RvciB5IGFuZCBmb3JlY2FzdHMgaCB0cmFkaW5nIGRheXMgYWhlYWQKZmNfZnVucyA8LSBsaXN0KAogICJSYW5kb20gd2FsayIgICAgICAgICAgICA9IGZ1bmN0aW9uKHksIGgpIG5haXZlKHRzKHkpLCBoID0gaCwgbGV2ZWwgPSBsdiksCiAgIlJhbmRvbSB3YWxrIHdpdGggZHJpZnQiID0gZnVuY3Rpb24oeSwgaCkgcndmKHRzKHkpLCBkcmlmdCA9IFRSVUUsIGggPSBoLCBsZXZlbCA9IGx2KSwKICAiQVJJTUEiICAgICAgICAgICAgICAgICAgPSBmdW5jdGlvbih5LCBoKSBmb3JlY2FzdChhdXRvLmFyaW1hKHRzKHkpLCBsYW1iZGEgPSAwLCBiaWFzYWRqID0gVFJVRSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaCA9IGgsIGxldmVsID0gbHYpLAogICJFVFMiICAgICAgICAgICAgICAgICAgICA9IGZ1bmN0aW9uKHksIGgpIGZvcmVjYXN0KGV0cyh0cyh5KSwgbGFtYmRhID0gMCwgYmlhc2FkaiA9IFRSVUUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGggPSBoLCBsZXZlbCA9IGx2KSwKICAiVGhldGEiICAgICAgICAgICAgICAgICAgPSBmdW5jdGlvbih5LCBoKSB0aGV0YWYodHMoeSksIGggPSBoLCBsZXZlbCA9IGx2KSwKICAiVEJBVFMiICAgICAgICAgICAgICAgICAgPSBmdW5jdGlvbih5LCBoKSBmb3JlY2FzdCh0YmF0cyhtc3RzKHksIHNlYXNvbmFsLnBlcmlvZHMgPSBjKDUsIGFubnVhbF9wZXJpb2QpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1c2UuYm94LmNveCA9IFRSVUUsIHVzZS5wYXJhbGxlbCA9IEZBTFNFKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoID0gaCwgbGV2ZWwgPSBsdiksCiAgIk5OQVIiICAgICAgICAgICAgICAgICAgID0gZnVuY3Rpb24oeSwgaCkgZm9yZWNhc3Qobm5ldGFyKHRzKHkpLCBsYW1iZGEgPSAwKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoID0gaCwgUEkgPSBUUlVFLCBsZXZlbCA9IGx2KSwKICAiR0pSLUdBUkNIIiAgICAgICAgICAgICAgPSBmdW5jdGlvbih5LCBoKSBmY19nYXJjaCh5LCBoKQopCgojIENvbnZlcnQgYSBmb3JlY2FzdCBvYmplY3QgKG9yIGEgc2ltdWxhdGlvbi1iYXNlZCBsaXN0KSBpbnRvIGEgdGlkeSB0aWJibGUKdGlkeV9mYyA8LSBmdW5jdGlvbihmYywgbW9kZWwsIGgpIHsKICBpZiAoaXMubnVsbChmYykpIHsKICAgIHJldHVybih0aWJibGUobW9kZWwgPSBtb2RlbCwgbWV0aG9kID0gTkFfY2hhcmFjdGVyXywgaG9yaXpvbiA9IHNlcV9sZW4oaCksCiAgICAgICAgICAgICAgICAgIG1lYW4gPSBOQV9yZWFsXywgbG84MCA9IE5BX3JlYWxfLCBoaTgwID0gTkFfcmVhbF8sCiAgICAgICAgICAgICAgICAgIGxvOTUgPSBOQV9yZWFsXywgaGk5NSA9IE5BX3JlYWxfKSkKICB9CiAgdGliYmxlKG1vZGVsICAgPSBtb2RlbCwKICAgICAgICAgbWV0aG9kICA9IGlmIChpcy5udWxsKGZjJG1ldGhvZCkpIG1vZGVsIGVsc2UgZmMkbWV0aG9kLAogICAgICAgICBob3Jpem9uID0gc2VxX2xlbihoKSwKICAgICAgICAgbWVhbiAgICA9IGFzLm51bWVyaWMoZmMkbWVhbiksCiAgICAgICAgIGxvODAgICAgPSBhcy5udW1lcmljKGZjJGxvd2VyWywgMV0pLCBoaTgwID0gYXMubnVtZXJpYyhmYyR1cHBlclssIDFdKSwKICAgICAgICAgbG85NSAgICA9IGFzLm51bWVyaWMoZmMkbG93ZXJbLCAyXSksIGhpOTUgPSBhcy5udW1lcmljKGZjJHVwcGVyWywgMl0pKQp9CgojIEZpdCB0aGUgc2VsZWN0ZWQgcHJpY2Utb25seSBtb2RlbHMgb24gcHJpY2UgdmVjdG9yIHk7IGZhaWxlZCBmaXRzIHJldHVybiBOQSByb3dzCnJ1bl9tb2RlbHMgPC0gZnVuY3Rpb24oeSwgaCwgbW9kZWxzID0gbmFtZXMoZmNfZnVucykpIHsKICBmY3MgPC0gbWFwKGZjX2Z1bnNbbW9kZWxzXSwgfiB0cnlDYXRjaCgueCh5LCBoKSwgZXJyb3IgPSBmdW5jdGlvbihlKSBOVUxMKSkKICBpbWFwX2RmcihmY3MsIH4gdGlkeV9mYygueCwgLnksIGgpKQp9CmBgYAoKIyMjIEZvcmVjYXN0cyBmb3IgMiB0byAxMCBOb3ZlbWJlciAyMDI2CgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQpmY19vYmplY3RzIDwtIG1hcChmY19mdW5zLCB+IHRyeUNhdGNoKC54KHRyYWluJGNsb3NlLCBoKSwgZXJyb3IgPSBmdW5jdGlvbihlKSBOVUxMKSkKCiMgVGhlIHdlZWtkYXkgbW9kZWwgYWxzbyBuZWVkcyBkYXRlczsgbGlzdCgpIGtlZXBzIGEgZmFpbGVkIGZpdCBhcyBOVUxMCmZjX29iamVjdHNbIkFSSU1BICsgd2Vla2RheSJdIDwtIGxpc3QoCiAgdHJ5Q2F0Y2goZmNfd2Vla2RheSh0cmFpbiRjbG9zZSwgdHJhaW4kZGF0ZSwgZnV0dXJlX2RhdGVzKSwgZXJyb3IgPSBmdW5jdGlvbihlKSBOVUxMKQopCgpmY19maW5hbCA8LSBpbWFwX2RmcihmY19vYmplY3RzLCB+IHRpZHlfZmMoLngsIC55LCBoKSkgJT4lCiAgbXV0YXRlKGRhdGUgPSBmdXR1cmVfZGF0ZXNbaG9yaXpvbl0pCgojIFNwZWNpZmljYXRpb24gc2VsZWN0ZWQgZm9yIGVhY2ggbW9kZWwKZmNfZmluYWwgJT4lIGRpc3RpbmN0KG1vZGVsLCBtZXRob2QpCmBgYAoKYGBge3J9CmdhcmNoX2ZpdCA8LSBmY19vYmplY3RzW1siR0pSLUdBUkNIIl1dJG1vZGVsCndkX2ZpdCAgICA8LSBmY19vYmplY3RzW1siQVJJTUEgKyB3ZWVrZGF5Il1dJG1vZGVsCgojIEdKUi1HQVJDSCBlc3RpbWF0ZXMsIHBlcnNpc3RlbmNlLCBhbmQgaGFsZi1saWZlIG9mIHZvbGF0aWxpdHkgc2hvY2tzICh0cmFkaW5nIGRheXMpCmlmICghaXMubnVsbChnYXJjaF9maXQpKSB7CiAgcHJpbnQocm91bmQoZ2FyY2hfZml0QGZpdCRtYXRjb2VmLCA0KSkKICBwcmludChjKHBlcnNpc3RlbmNlID0gcGVyc2lzdGVuY2UoZ2FyY2hfZml0KSwgaGFsZl9saWZlX2RheXMgPSBoYWxmbGlmZShnYXJjaF9maXQpKSkKfQoKIyBXZWVrZGF5IHJlZ3Jlc3Npb24gd2l0aCBBUk1BIGVycm9ycyAocmV0dXJucyBpbiAlKQppZiAoIWlzLm51bGwod2RfZml0KSkgcHJpbnQod2RfZml0KQpgYGAKCmBgYHtyfQojIFBvaW50IGZvcmVjYXN0cyBieSBtb2RlbApmY19maW5hbCAlPiUKICBzZWxlY3QoZGF0ZSwgbW9kZWwsIG1lYW4pICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBtb2RlbCwgdmFsdWVzX2Zyb20gPSBtZWFuKSAlPiUKICBtdXRhdGUoYWNyb3NzKC1kYXRlLCB+IHJvdW5kKC54LCAxKSkpCmBgYAoKYGBge3J9CiMgUG9pbnQgZm9yZWNhc3RzIHdpdGggODAlIGFuZCA5NSUgcHJlZGljdGlvbiBpbnRlcnZhbHMKZmNfZmluYWwgJT4lCiAgc2VsZWN0KG1vZGVsLCBkYXRlLCBtZWFuLCBsbzgwLCBoaTgwLCBsbzk1LCBoaTk1KSAlPiUKICBtdXRhdGUoYWNyb3NzKG1lYW46aGk5NSwgfiByb3VuZCgueCwgMSkpKQpgYGAKCmBgYHtyfQpwbG90X2ZjIDwtIGZ1bmN0aW9uKGZjLCBoaXN0LCBuY29sID0gMikgewogIGZjICAgICAgIDwtIGZjICU+JSBtdXRhdGUobW9kZWwgPSBmYWN0b3IobW9kZWwsIGxldmVscyA9IHVuaXF1ZShtb2RlbCkpKQogIGxhc3Rfb2JzIDwtIHNsaWNlX3RhaWwoaGlzdCwgbiA9IDEpCgogICMgQ29ubmVjdCB0aGUgbGFzdCBvYnNlcnZhdGlvbiB0byBlYWNoIGZvcmVjYXN0IGxpbmUKICBicmlkZ2UgPC0gZGlzdGluY3QoZmMsIG1vZGVsKSAlPiUKICAgIG11dGF0ZShkYXRlID0gbGFzdF9vYnMkZGF0ZSwgbWVhbiA9IGxhc3Rfb2JzJGNsb3NlLAogICAgICAgICAgIGxvODAgPSBtZWFuLCBoaTgwID0gbWVhbiwgbG85NSA9IG1lYW4sIGhpOTUgPSBtZWFuKQoKICBmY19saW5lICA8LSBiaW5kX3Jvd3MoZmMsIGJyaWRnZSkgJT4lIGFycmFuZ2UobW9kZWwsIGRhdGUpCiAgaGlzdF9yZXAgPC0gY3Jvc3Nfam9pbihkaXN0aW5jdChmYywgbW9kZWwpLCBzZWxlY3QoaGlzdCwgZGF0ZSwgY2xvc2UpKQoKICBnZ3Bsb3QoKSArCiAgICBnZW9tX3JpYmJvbihkYXRhID0gZmNfbGluZSwgYWVzKGRhdGUsIHltaW4gPSBsbzk1LCB5bWF4ID0gaGk5NSwgZmlsbCA9ICI5NSUgaW50ZXJ2YWwiKSwKICAgICAgICAgICAgICAgIGFscGhhID0gMC4zNSkgKwogICAgZ2VvbV9yaWJib24oZGF0YSA9IGZjX2xpbmUsIGFlcyhkYXRlLCB5bWluID0gbG84MCwgeW1heCA9IGhpODAsIGZpbGwgPSAiODAlIGludGVydmFsIiksCiAgICAgICAgICAgICAgICBhbHBoYSA9IDAuNTUpICsKICAgIGdlb21fbGluZShkYXRhID0gaGlzdF9yZXAsIGFlcyhkYXRlLCBjbG9zZSwgY29sb3VyID0gIkFjdHVhbCIpLCBsaW5ld2lkdGggPSAwLjUpICsKICAgIGdlb21fbGluZShkYXRhID0gZmNfbGluZSwgYWVzKGRhdGUsIG1lYW4sIGNvbG91ciA9ICJQb2ludCBmb3JlY2FzdCIpLCBsaW5ld2lkdGggPSAwLjcpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGZjLCBhZXMoZGF0ZSwgbWVhbiwgY29sb3VyID0gIlBvaW50IGZvcmVjYXN0IiksIHNpemUgPSAxLjIpICsKICAgIGZhY2V0X3dyYXAofiBtb2RlbCwgbmNvbCA9IG5jb2wpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIjk1JSBpbnRlcnZhbCIgPSAiI2M2ZDllYyIsICI4MCUgaW50ZXJ2YWwiID0gIiM4ZmIzZDkiKSwKICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoIjgwJSBpbnRlcnZhbCIsICI5NSUgaW50ZXJ2YWwiKSkgKwogICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJBY3R1YWwiID0gImdyZXkyNSIsICJQb2ludCBmb3JlY2FzdCIgPSBwYWxbWyJvcmFuZ2UiXV0pKSArCiAgICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3MgPSAiMiB3ZWVrcyIsIGRhdGVfbGFiZWxzID0gIiVkICViIikgKwogICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgICBsYWJzKHggPSBOVUxMLCB5ID0gIkluZGV4IGxldmVsIikgKwogICAgdGhlbWVfaWhzZygpCn0KCiMgVXNlIHRoZSBzZWxlY3RlZCBzcGVjaWZpY2F0aW9uIGFzIHRoZSBwYW5lbCB0aXRsZSB3aGVyZSBpdCBpcyBpbmZvcm1hdGl2ZQpmY19wbG90X2RhdGEgPC0gZmNfZmluYWwgJT4lCiAgbXV0YXRlKG1vZGVsID0gaWZfZWxzZShtb2RlbCAlaW4lIGMoIkFSSU1BIiwgIkVUUyIsICJUQkFUUyIsICJOTkFSIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiR0pSLUdBUkNIIiwgIkFSSU1BICsgd2Vla2RheSIpLAogICAgICAgICAgICAgICAgICAgICAgICAgY29hbGVzY2UobWV0aG9kLCBtb2RlbCksIG1vZGVsKSkKCnAgPC0gcGxvdF9mYyhmY19wbG90X2RhdGEsIHNsaWNlX3RhaWwodHJhaW4sIG4gPSA0MCksIG5jb2wgPSAzKSArCiAgbGFicyh0aXRsZSA9ICJKS1NFIENsb3NpbmcgUHJpY2UgRm9yZWNhc3RzLCAyIHRvIDEwIE5vdmVtYmVyIDIwMjYiLAogICAgICAgc3VidGl0bGUgPSAiTGFzdCA0MCB0cmFkaW5nIGRheXMgd2l0aCBwb2ludCBmb3JlY2FzdHMgYW5kIDgwJSAvIDk1JSBwcmVkaWN0aW9uIGludGVydmFscyIsCiAgICAgICBjYXB0aW9uID0gc291cmNlX2xhYmVsKQoKaGRfcGxvdChwLCAiMDNfZm9yZWNhc3RzX2J5X21vZGVsIiwgd2lkdGggPSAxMCwgaGVpZ2h0ID0gOSkKYGBgCgoKIyMjIFNpbXVsYXRlZCBwcmljZSBwYXRocwoKVGhlIGZpZ3VyZSBzaG93cyAzMCBvZiAyLDAwMCBwYXRocyBzaW11bGF0ZWQgZnJvbSB0aGUgZml0dGVkIEdKUi1HQVJDSCBtb2RlbC4KSW5kaXZpZHVhbCBwYXRocyBtb3ZlIHVwIGFuZCBkb3duLCBidXQgYmVjYXVzZSB0aGUgZGlyZWN0aW9uIG9mIGVhY2ggbW92ZSBpcwp1bnByZWRpY3RhYmxlLCB0aGVpciBhdmVyYWdlIGlzIG5lYXJseSBmbGF0LiBUaGUgYmFuZHMgcmVmbGVjdCB0aGUgY3VycmVudApsZXZlbCBvZiB2b2xhdGlsaXR5LgoKYGBge3J9CmlmIChpcy5udWxsKGdhcmNoX2ZpdCkpIHN0b3AoIlRoZSBHSlItR0FSQ0ggZml0IGlzIG5vdCBhdmFpbGFibGUiKQoKc2V0LnNlZWQoMTIzKQpuX3BhdGhzICAgIDwtIDIwMDAKbGFzdF9jbG9zZSA8LSB0YWlsKHRyYWluJGNsb3NlLCAxKQoKc2ltICAgPC0gdWdhcmNoc2ltKGdhcmNoX2ZpdCwgbi5zaW0gPSBoLCBtLnNpbSA9IG5fcGF0aHMsIHN0YXJ0TWV0aG9kID0gInNhbXBsZSIpCnJldCAgIDwtIG1hdHJpeChmaXR0ZWQoc2ltKSwgbnJvdyA9IGgpCnBhdGhzIDwtIHJiaW5kKHJlcChsYXN0X2Nsb3NlLCBuX3BhdGhzKSwKICAgICAgICAgICAgICAgbGFzdF9jbG9zZSAqIGV4cChhcHBseShyZXQsIDIsIGN1bXN1bSkpKQpjb2xuYW1lcyhwYXRocykgPC0gcGFzdGUwKCJwYXRoXyIsIHNlcV9sZW4obl9wYXRocykpCgpwYXRoX3RibCA8LSBhc190aWJibGUocGF0aHMpICU+JQogIG11dGF0ZShkYXRlID0gYyhtYXgodHJhaW4kZGF0ZSksIGZ1dHVyZV9kYXRlcykpICU+JQogIHBpdm90X2xvbmdlcigtZGF0ZSwgbmFtZXNfdG8gPSAicGF0aCIsIHZhbHVlc190byA9ICJjbG9zZSIpCgpiYW5kX3RibCA8LSBwYXRoX3RibCAlPiUKICBncm91cF9ieShkYXRlKSAlPiUKICBzdW1tYXJpc2UobWVhbiA9IG1lYW4oY2xvc2UpLAogICAgICAgICAgICBsbzgwID0gcXVhbnRpbGUoY2xvc2UsIDAuMTAwKSwgaGk4MCA9IHF1YW50aWxlKGNsb3NlLCAwLjkwMCksCiAgICAgICAgICAgIGxvOTUgPSBxdWFudGlsZShjbG9zZSwgMC4wMjUpLCBoaTk1ID0gcXVhbnRpbGUoY2xvc2UsIDAuOTc1KSwKICAgICAgICAgICAgLmdyb3VwcyA9ICJkcm9wIikKCnNob3duIDwtIHNhbXBsZShjb2xuYW1lcyhwYXRocyksIDMwKQoKcCA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9yaWJib24oZGF0YSA9IGJhbmRfdGJsLCBhZXMoZGF0ZSwgeW1pbiA9IGxvOTUsIHltYXggPSBoaTk1KSwKICAgICAgICAgICAgICBmaWxsID0gIiNjNmQ5ZWMiLCBhbHBoYSA9IDAuNykgKwogIGdlb21fcmliYm9uKGRhdGEgPSBiYW5kX3RibCwgYWVzKGRhdGUsIHltaW4gPSBsbzgwLCB5bWF4ID0gaGk4MCksCiAgICAgICAgICAgICAgZmlsbCA9ICIjOGZiM2Q5IiwgYWxwaGEgPSAwLjcpICsKICBnZW9tX2xpbmUoZGF0YSA9IGZpbHRlcihwYXRoX3RibCwgcGF0aCAlaW4lIHNob3duKSwKICAgICAgICAgICAgYWVzKGRhdGUsIGNsb3NlLCBncm91cCA9IHBhdGgpLAogICAgICAgICAgICBjb2xvdXIgPSBwYWxbWyJuYXZ5Il1dLCBhbHBoYSA9IDAuNCwgbGluZXdpZHRoID0gMC4zNSkgKwogIGdlb21fbGluZShkYXRhID0gc2xpY2VfdGFpbCh0cmFpbiwgbiA9IDQwKSwgYWVzKGRhdGUsIGNsb3NlKSwKICAgICAgICAgICAgY29sb3VyID0gImdyZXkyNSIsIGxpbmV3aWR0aCA9IDAuNikgKwogIGdlb21fbGluZShkYXRhID0gYmFuZF90YmwsIGFlcyhkYXRlLCBtZWFuKSwgY29sb3VyID0gcGFsW1sib3JhbmdlIl1dLCBsaW5ld2lkdGggPSAxKSArCiAgc2NhbGVfeF9kYXRlKGRhdGVfYnJlYWtzID0gIjIgd2Vla3MiLCBkYXRlX2xhYmVscyA9ICIlZCAlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBsYWJzKHRpdGxlID0gIlNpbXVsYXRlZCBKS1NFIFByaWNlIFBhdGhzLCAyIHRvIDEwIE5vdmVtYmVyIDIwMjYiLAogICAgICAgc3VidGl0bGUgPSAiMzAgb2YgMiwwMDAgR0pSLUdBUkNIIHBhdGhzIChibHVlKSwgdGhlaXIgbWVhbiAob3JhbmdlKSwgYW5kIDgwJSAvIDk1JSBiYW5kcyIsCiAgICAgICB4ID0gTlVMTCwgeSA9ICJJbmRleCBsZXZlbCIsIGNhcHRpb24gPSBzb3VyY2VfbGFiZWwpICsKICB0aGVtZV9paHNnKCkKCmhkX3Bsb3QocCwgIjA0X3NpbXVsYXRlZF9wYXRocyIpCmBgYAoKIyMjIFJvbGxpbmctb3JpZ2luIGV2YWx1YXRpb24KCkEgc2luZ2xlIGhvbGRvdXQgb2YgYSBmZXcgZGF5cyBpcyB0b28gbm9pc3kgdG8gcmFuayBtb2RlbHMuIEVhY2ggbW9kZWwgaXMKcmUtZXN0aW1hdGVkIG9uIGEgcm9sbGluZyB3aW5kb3cgb2YgdGhlIHNhbWUgbGVuZ3RoIGFzIHRoZSBmaW5hbCBlc3RpbWF0aW9uCndpbmRvdywgYXQgZm9yZWNhc3Qgb3JpZ2lucyBzcGFjZWQgNSB0cmFkaW5nIGRheXMgYXBhcnQgb3ZlciB0aGUgbGFzdCAyNTAKdHJhZGluZyBkYXlzLiBUaGUgQVJJTUEgKyB3ZWVrZGF5IG1vZGVsIHVzZXMgdGhlIGRhdGVzIG9mIGVhY2ggd2luZG93IGFuZCB0aGUKYWN0dWFsIHRyYWRpbmcgZGF0ZXMgdGhhdCBmb2xsb3cgZWFjaCBvcmlnaW4sIHdoaWNoIGFyZSBrbm93biBpbiBhZHZhbmNlIGZyb20KdGhlIElEWCBjYWxlbmRhci4gRXJyb3JzIGFyZSBhdmVyYWdlZCBvdmVyIGFsbCBvcmlnaW5zIGFuZCBob3Jpem9ucy4gQ292ZXJhZ2UKY2xvc2UgdG8gMC44MCBhbmQgMC45NSBpbmRpY2F0ZXMgd2VsbC1jYWxpYnJhdGVkIHByZWRpY3Rpb24gaW50ZXJ2YWxzLiBUQkFUUyBhbmQKTk5BUiBhcmUgb25seSBpbmNsdWRlZCB3aGVuIGBydW5fc2xvd19iYWNrdGVzdCA9IFRSVUVgLiAKCmBgYHtyfQpidF9tb2RlbHMgPC0gYygiUmFuZG9tIHdhbGsiLCAiUmFuZG9tIHdhbGsgd2l0aCBkcmlmdCIsICJBUklNQSIsICJFVFMiLCAiVGhldGEiLAogICAgICAgICAgICAgICAiR0pSLUdBUkNIIiwgIkFSSU1BICsgd2Vla2RheSIpCmlmIChydW5fc2xvd19iYWNrdGVzdCkgYnRfbW9kZWxzIDwtIGMoYnRfbW9kZWxzLCAiVEJBVFMiLCAiTk5BUiIpCgp5X2FsbCAgIDwtIGloc2ckY2xvc2UKbl93aW4gICA8LSBucm93KHRyYWluKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHNhbWUgd2luZG93IGxlbmd0aCBhcyB0aGUgZmluYWwgbW9kZWxzCm9yaWdpbnMgPC0gc2VxKGxlbmd0aCh5X2FsbCkgLSAyNTAsIGxlbmd0aCh5X2FsbCkgLSBoLCBieSA9IDUpCgpzZXQuc2VlZCgxMjMpCmJ0IDwtIG1hcF9kZnIob3JpZ2lucywgZnVuY3Rpb24obykgewogIGlkeCA8LSAobyAtIG5fd2luICsgMSk6bwoKICAjIE1vZGVscyB0aGF0IHVzZSBwcmljZXMgb25seQogIGZjX3ByaWNlIDwtIHJ1bl9tb2RlbHMoeV9hbGxbaWR4XSwgaCwgbW9kZWxzID0gaW50ZXJzZWN0KGJ0X21vZGVscywgbmFtZXMoZmNfZnVucykpKQoKICAjIFdlZWtkYXkgbW9kZWw6IGFsc28gbmVlZHMgdGhlIHdpbmRvdyBkYXRlcyBhbmQgdGhlIG5leHQgaCB0cmFkaW5nIGRhdGVzCiAgd2Rfb2JqIDwtIHRyeUNhdGNoKGZjX3dlZWtkYXkoeV9hbGxbaWR4XSwgaWhzZyRkYXRlW2lkeF0sIGloc2ckZGF0ZVtvICsgc2VxX2xlbihoKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnNpbSA9IDEwMDApLAogICAgICAgICAgICAgICAgICAgICBlcnJvciA9IGZ1bmN0aW9uKGUpIE5VTEwpCgogIGJpbmRfcm93cyhmY19wcmljZSwgdGlkeV9mYyh3ZF9vYmosICJBUklNQSArIHdlZWtkYXkiLCBoKSkgJT4lCiAgICBtdXRhdGUob3JpZ2luICAgICAgPSBvLAogICAgICAgICAgIG9yaWdpbl9kYXRlID0gaWhzZyRkYXRlW29dLAogICAgICAgICAgIGFjdHVhbCAgICAgID0geV9hbGxbbyArIGhvcml6b25dKQp9KQoKc2F2ZVJEUyhidCwgImJhY2t0ZXN0X3Jlc3VsdHMucmRzIikgICAKYGBgCgpgYGB7cn0KYnQgJT4lCiAgZ3JvdXBfYnkobW9kZWwpICU+JQogIHN1bW1hcmlzZSgKICAgIFJNU0UgICAgICAgICAgID0gc3FydChtZWFuKChhY3R1YWwgLSBtZWFuKV4yLCBuYS5ybSA9IFRSVUUpKSwKICAgIE1BRSAgICAgICAgICAgID0gbWVhbihhYnMoYWN0dWFsIC0gbWVhbiksIG5hLnJtID0gVFJVRSksCiAgICBNQVBFICAgICAgICAgICA9IDEwMCAqIG1lYW4oYWJzKChhY3R1YWwgLSBtZWFuKSAvIGFjdHVhbCksIG5hLnJtID0gVFJVRSksCiAgICBgQ292ZXJhZ2UgODAlYCA9IG1lYW4oYWN0dWFsID49IGxvODAgJiBhY3R1YWwgPD0gaGk4MCwgbmEucm0gPSBUUlVFKSwKICAgIGBDb3ZlcmFnZSA5NSVgID0gbWVhbihhY3R1YWwgPj0gbG85NSAmIGFjdHVhbCA8PSBoaTk1LCBuYS5ybSA9IFRSVUUpLAogICAgYEZhaWxlZCBmaXRzYCAgPSBzdW0oaXMubmEobWVhbikpIC8gaCwKICAgIC5ncm91cHMgPSAiZHJvcCIKICApICU+JQogIG11dGF0ZShgUk1TRSB2cyBSV2AgPSBSTVNFIC8gUk1TRVttb2RlbCA9PSAiUmFuZG9tIHdhbGsiXSkgJT4lCiAgcmVsb2NhdGUoYFJNU0UgdnMgUldgLCAuYWZ0ZXIgPSBSTVNFKSAlPiUKICBhcnJhbmdlKFJNU0UpICU+JQogIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMubnVtZXJpYyksIH4gcm91bmQoLngsIDMpKSkKYGBgCgpgYGB7cn0Kcm1zZV9oIDwtIGJ0ICU+JQogIGdyb3VwX2J5KG1vZGVsLCBob3Jpem9uKSAlPiUKICBzdW1tYXJpc2UoUk1TRSA9IHNxcnQobWVhbigoYWN0dWFsIC0gbWVhbileMiwgbmEucm0gPSBUUlVFKSksIC5ncm91cHMgPSAiZHJvcCIpICU+JQogIGdyb3VwX2J5KGhvcml6b24pICU+JQogIG11dGF0ZShyZWxfcm1zZSA9IFJNU0UgLyBSTVNFW21vZGVsID09ICJSYW5kb20gd2FsayJdKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKG1vZGVsID0gZmFjdG9yKG1vZGVsLCBsZXZlbHMgPSBidF9tb2RlbHMpKQoKIyBOaW5lIGRpc3RpbmN0IGNvbG91cnMsIGVub3VnaCBmb3IgYWxsIG1vZGVscyB3aGVuIHJ1bl9zbG93X2JhY2t0ZXN0ID0gVFJVRQptb2RlbF9jb2xzIDwtIGMoIiMxZjRlNzkiLCAiI2UwN2IzOSIsICIjMmU4YjU3IiwgIiNjMDM5MmIiLCAiIzdiMzI5NCIsCiAgICAgICAgICAgICAgICAiIzhjNmQzMSIsICIjMTdiZWNmIiwgIiNlMzc3YzIiLCAiZ3JleTQwIikKCnAgPC0gZ2dwbG90KHJtc2VfaCwgYWVzKGhvcml6b24sIHJlbF9ybXNlLCBjb2xvdXIgPSBtb2RlbCkpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvdXIgPSAiZ3JleTQwIikgKwogIGdlb21fbGluZShsaW5ld2lkdGggPSAwLjgpICsKICBnZW9tX3BvaW50KHNpemUgPSAxLjgpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IG1vZGVsX2NvbHMpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxX2xlbihoKSkgKwogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQobnJvdyA9IDIpKSArCiAgbGFicyh0aXRsZSA9ICJGb3JlY2FzdCBBY2N1cmFjeSBSZWxhdGl2ZSB0byB0aGUgUmFuZG9tIFdhbGsiLAogICAgICAgc3VidGl0bGUgPSAiUk1TRSByYXRpbyBieSBob3Jpem9uIGFjcm9zcyBhbGwgb3JpZ2luczsgdmFsdWVzIGJlbG93IDEgYmVhdCB0aGUgcmFuZG9tIHdhbGsiLAogICAgICAgeCA9ICJGb3JlY2FzdCBob3Jpem9uICh0cmFkaW5nIGRheXMpIiwgeSA9ICJSZWxhdGl2ZSBSTVNFIiwKICAgICAgIGNhcHRpb24gPSBzb3VyY2VfbGFiZWwpICsKICB0aGVtZV9paHNnKCkKCmhkX3Bsb3QocCwgIjA1X3JlbGF0aXZlX3Jtc2UiLCBoZWlnaHQgPSA0LjUpCmBgYAoKRGllYm9sZOKAk01hcmlhbm8gdGVzdHMgY29tcGFyZSBvbmUtc3RlcC1haGVhZCBzcXVhcmVkIGVycm9ycyBvZiBlYWNoIG1vZGVsIHdpdGgKdGhlIHJhbmRvbSB3YWxrLiBBIG5lZ2F0aXZlIHN0YXRpc3RpYyBtZWFucyB0aGUgbW9kZWwgaGFzIHNtYWxsZXIgZXJyb3JzOyBhCnAtdmFsdWUgYmVsb3cgMC4wNSBpbmRpY2F0ZXMgdGhhdCB0aGUgZGlmZmVyZW5jZSBpcyBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50LgoKYGBge3J9CmUxIDwtIGJ0ICU+JQogIGZpbHRlcihob3Jpem9uID09IDEpICU+JQogIHRyYW5zbXV0ZShvcmlnaW4sIG1vZGVsLCBlcnJvciA9IGFjdHVhbCAtIG1lYW4pICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBtb2RlbCwgdmFsdWVzX2Zyb20gPSBlcnJvcikKCm1hcF9kZnIoc2V0ZGlmZihidF9tb2RlbHMsICJSYW5kb20gd2FsayIpLCBmdW5jdGlvbihtKSB7CiAgb2sgICA8LSBjb21wbGV0ZS5jYXNlcyhlMVtbbV1dLCBlMVtbIlJhbmRvbSB3YWxrIl1dKQogIHRlc3QgPC0gZG0udGVzdChlMVtbbV1dW29rXSwgZTFbWyJSYW5kb20gd2FsayJdXVtva10sIGggPSAxLCBwb3dlciA9IDIpCiAgdGliYmxlKG1vZGVsID0gbSwgRE1fc3RhdGlzdGljID0gdW5uYW1lKHRlc3Qkc3RhdGlzdGljKSwgcF92YWx1ZSA9IHRlc3QkcC52YWx1ZSkKfSkgJT4lCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgfiByb3VuZCgueCwgMykpKQpgYGA=