2. List of TWSE listed companies

library(jsonlite)

# Direct TWSE OpenAPI endpoint
twse_data <- fromJSON("https://openapi.twse.com.tw/v1/exchangeReport/BWIBBU_ALL")

# Extract standard 4-digit stock tickers
listed <- twse_data %>%
  rename(code = Code, name = Name) %>%
  filter(str_detect(code, "^[0-9]{4}$")) %>%
  select(code, name) %>%
  distinct(code, .keep_all = TRUE) %>%
  mutate(symbol = paste0(code, ".TW"))

cat(sprintf("Total listed companies found: %d\n", nrow(listed)))
## Total listed companies found: 1083
head(listed, 10)
##    code name  symbol
## 1  1101 台泥 1101.TW
## 2  1102 亞泥 1102.TW
## 3  1103 嘉泥 1103.TW
## 4  1104 環泥 1104.TW
## 5  1108 幸福 1108.TW
## 6  1109 信大 1109.TW
## 7  1110 東泥 1110.TW
## 8  1201 味全 1201.TW
## 9  1203 味王 1203.TW
## 10 1210 大成 1210.TW
# Set MAX_SYMBOLS to Inf for a full run, or a smaller number (e.g., 50) for testing
MAX_SYMBOLS <- Inf

start <- Sys.Date() %m-% years(2)
end   <- Sys.Date()

get_prices <- function(sym, retries = 3) {
  for (attempt in seq_len(retries)) {
    d <- tryCatch({
      getSymbols(sym, src = "yahoo", from = start, to = end,
                 auto.assign = FALSE, periodicity = "daily")
    }, error = function(e) NULL)
    
    if (!is.null(d) && nrow(d) > 0) {
      return(data.frame(
        symbol = sym,
        date   = as.Date(index(d)),
        close  = as.numeric(Ad(d)), # Uses Adjusted Close to account for dividends & splits
        stringsAsFactors = FALSE
      ))
    }
    
    Sys.sleep(1 * attempt) # Wait longer on consecutive retry attempts
  }
  return(NULL)
}

syms <- listed$symbol
if (is.finite(MAX_SYMBOLS)) syms <- syms[seq_len(min(MAX_SYMBOLS, length(syms)))]

results <- vector("list", length(syms))
for (i in seq_along(syms)) {
  results[[i]] <- get_prices(syms[i])
  if (i %% 50 == 0) message(sprintf("Fetched %d / %d", i, length(syms)))
  Sys.sleep(0.3) # Rate-limit protection delay
}

prices <- bind_rows(results) %>% filter(!is.na(close))

n_ok <- length(unique(prices$symbol))
cat(sprintf("Successfully downloaded data for %d out of %d requested companies.\n",
            n_ok, length(syms)))
## Successfully downloaded data for 1083 out of 1083 requested companies.
if (nrow(prices) > 0) {
  cat(sprintf("Date range: %s to %s\n", min(prices$date), max(prices$date)))
  cat(sprintf("Total rows: %s\n", format(nrow(prices), big.mark = ",")))
}
## Date range: 2024-10-07 to 2026-10-05
## Total rows: 520,806
datatable(head(prices, 5000),
          options = list(pageLength = 10),
          filter = "top")
top_symbols <- c("2330.TW", "2317.TW", "2454.TW", "2308.TW", "2412.TW")
available_top <- intersect(top_symbols, unique(prices$symbol))

n_charts <- length(available_top)

if (n_charts > 0) {
  n_cols <- 2
  n_rows <- ceiling(n_charts / n_cols)
  
  par(mfrow = c(n_rows, n_cols), mar = c(3, 4, 3, 1))
  
  for (s in available_top) {
    d <- prices[prices$symbol == s, ]
    plot(
      d$date, d$close, 
      type = "l", 
      col = "steelblue", 
      lwd = 2,
      main = s, 
      xlab = "", 
      ylab = "Adj Close (TWD)"
    )
  }
  
  par(mfrow = c(1, 1))
}

## 7. Export to CSV
write.csv(prices, "twse_daily_prices_2y.csv", row.names = FALSE)
cat("Saved to twse_daily_prices_2y.csv\n")
## Saved to twse_daily_prices_2y.csv

How to publish to RPubs

  1. Open this .Rmd file in RStudio.
  2. Click the Knit button (or press Ctrl/Cmd + Shift + K) to render it to HTML.
  3. When the HTML preview opens, click the Publish button (top-right of the viewer) and choose RPubs.
  4. Sign in (or create) an RPubs account and publish — you’ll get a public URL back.