1. Listed companies

library(jsonlite)

get_list <- function(api_url, suffix) {
  resp <- GET(api_url, config(ssl_verifypeer = FALSE), user_agent("Mozilla/5.0"))
  stop_for_status(resp)
  df <- fromJSON(content(resp, "text", encoding = "UTF-8"))

  # Find the column that holds 4-digit stock codes (works for both APIs)
  is_code <- map_dbl(df, ~ mean(str_detect(as.character(.x), "^[1-9][0-9]{3}$")))
  code_col <- names(which.max(is_code))

  tibble(Ticker = as.character(df[[code_col]])) %>%
    filter(str_detect(Ticker, "^[1-9][0-9]{3}$")) %>%
    distinct() %>%
    mutate(YahooTicker = paste0(Ticker, suffix))
}

listed_stocks <- bind_rows(
  get_list("https://openapi.twse.com.tw/v1/opendata/t187ap03_L", ".TW"),    # TWSE listed
  get_list("https://www.tpex.org.tw/openapi/v1/mopsfin_t187ap03_O", ".TWO") # TPEx OTC
)

nrow(listed_stocks)
## [1] 1981
head(listed_stocks)
## # A tibble: 6 × 2
##   Ticker YahooTicker
##   <chr>  <chr>      
## 1 1101   1101.TW    
## 2 1102   1102.TW    
## 3 1103   1103.TW    
## 4 1104   1104.TW    
## 5 1108   1108.TW    
## 6 1109   1109.TW

2. Download daily prices

end_date   <- Sys.Date()
start_date <- end_date - (365 * 2)

get_stock_data <- function(symbol) {
  Sys.sleep(0.3)  # avoid Yahoo rate limit
  tryCatch({
    df <- getSymbols(symbol, src = "yahoo", from = start_date, to = end_date,
                     auto.assign = FALSE)
    out <- data.frame(Date = index(df), coredata(df))
    colnames(out) <- c("Date", "Open", "High", "Low", "Close", "Volume", "Adjusted")
    out$Ticker <- sub("\\.TWO?$", "", symbol)
    out
  }, error = function(e) NULL)
}

# TRUE = first 20 stocks only (quick test); FALSE = all stocks
test_mode <- TRUE

targets <- if (test_mode) slice(listed_stocks, 1:20) else listed_stocks

cache <- if (test_mode) "tw_prices_test.rds" else "tw_prices_2y.rds"
if (file.exists(cache)) {
  all_prices <- readRDS(cache)
} else {
  all_prices <- map_dfr(targets$YahooTicker, get_stock_data)
  saveRDS(all_prices, cache)
  write_csv(all_prices, sub("\\.rds$", ".csv", cache))
}

3. Summary

all_prices %>%
  summarise(companies = n_distinct(Ticker),
            rows = n(),
            first_day = min(Date),
            last_day = max(Date))
##   companies rows  first_day   last_day
## 1        20 9720 2024-10-04 2026-10-02
head(all_prices)
##         Date  Open  High   Low Close   Volume Adjusted Ticker
## 1 2024-10-04 33.50 34.00 33.50 33.80 17262621 31.42613   1101
## 2 2024-10-07 33.80 33.85 33.30 33.50 13071136 31.14720   1101
## 3 2024-10-08 33.25 33.45 32.90 33.05 12628511 30.72880   1101
## 4 2024-10-09 32.90 32.95 32.45 32.45 11284246 30.17094   1101
## 5 2024-10-11 32.75 33.00 32.40 32.50  9354924 30.21743   1101
## 6 2024-10-14 32.55 32.70 32.45 32.55  5999260 30.26392   1101