## ----setup, include = FALSE---------------------------------------------------
knitr::opts_chunk$set(
  collapse = TRUE,
  comment  = "#>"
)

## ----simulate-----------------------------------------------------------------
library(ROOT)
set.seed(123)

n_assets <- 100

# Asset features
volatility <- runif(n_assets, 0.05, 0.40)  # annualised volatility
beta       <- runif(n_assets, 0.5,  1.8)   # market beta
sector     <- sample(c("Tech", "Finance", "Energy", "Health"),
                     n_assets, replace = TRUE)

# Simulate returns: r_i = beta_i * r_market + epsilon_i
market      <- rnorm(1000, 0.0005, 0.01)
returns_mat <- sapply(seq_len(n_assets), function(i)
  beta[i] * market + rnorm(1000, 0, volatility[i] / sqrt(252))
)

# Per-asset return variance (the objective proxy ROOT will minimize)
vsq <- apply(returns_mat, 2, var)

dat_portfolio <- data.frame(
  vsq    = vsq,
  vol    = volatility,
  beta   = beta,
  sector = as.integer(factor(sector))
)

head(dat_portfolio)

## ----risk-plot, fig.width = 6, fig.height = 4, fig.alt = "Scatter plot of asset beta vs volatility"----
plot(
  dat_portfolio$beta, dat_portfolio$vol,
  xlab = "Market beta", ylab = "Annualised volatility",
  pch  = 16, col = "#4E79A7AA",
  main = "Asset universe: volatility vs beta"
)

## ----fit, message = FALSE, warning = FALSE------------------------------------
portfolio_fit <- ROOT(
  data        = dat_portfolio,
  num_trees   = 20,
  top_k_trees = TRUE,
  k           = 10,
  seed        = 42
)

## ----print--------------------------------------------------------------------
print(portfolio_fit)

## ----summary------------------------------------------------------------------
summary(portfolio_fit)

## ----plot, fig.width = 7, fig.height = 5, fig.alt = "Characterized tree for portfolio selection"----
plot(portfolio_fit)

## ----weights------------------------------------------------------------------
dat_portfolio$w_opt <- portfolio_fit$D_rash$w_opt

# Summary statistics by inclusion decision
included <- dat_portfolio[dat_portfolio$w_opt == 1, ]
excluded <- dat_portfolio[dat_portfolio$w_opt == 0, ]

cat("Included assets (w = 1):", nrow(included), "\n")
cat("  Mean beta:       ", round(mean(included$beta), 3), "\n")
cat("  Mean volatility: ", round(mean(included$vol),  3), "\n\n")

cat("Excluded assets (w = 0):", nrow(excluded), "\n")
cat("  Mean beta:       ", round(mean(excluded$beta), 3), "\n")
cat("  Mean volatility: ", round(mean(excluded$vol),  3), "\n")

## ----weights-plot, fig.width = 6, fig.height = 4, fig.alt = "Asset universe with inclusion decisions highlighted"----
plot(
  dat_portfolio$beta, dat_portfolio$vol,
  xlab = "Market beta", ylab = "Annualised volatility",
  pch  = ifelse(dat_portfolio$w_opt == 1, 16, 4),
  col  = ifelse(dat_portfolio$w_opt == 1, "#4E79A7", "#E15759"),
  main = "Portfolio inclusion decisions"
)
legend(
  "topleft",
  legend = c("w = 1 (included)", "w = 0 (excluded)"),
  pch    = c(16, 4),
  col    = c("#4E79A7", "#E15759"),
  bty    = "n"
)

## ----custom-obj, message = FALSE, warning = FALSE-----------------------------
iqr_objective <- function(D) {
  w <- D$w
  if (sum(w) == 0) return(Inf)
  # Weighted IQR: compute quantiles using the included assets only
  included_vsq <- D$vsq[w == 1]
  diff(quantile(included_vsq, probs = c(0.25, 0.75)))
}

portfolio_fit_iqr <- ROOT(
  data                = dat_portfolio,
  global_objective_fn = iqr_objective,
  num_trees           = 20,
  top_k_trees         = TRUE,
  k                   = 10,
  seed                = 112
)

