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
## 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))
}

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