1. Objective

Collect the daily prices of all listed companies in the Taiwan stock market for the last two years, check the data quality, summarize it, and publish this report to RPubs.

The data come from one file, Taiwan_Stocks_2024_2026_FINAL.csv, which already combines the TWSE and GreTai (TPEx) markets from 2024-10-07 to 2026-10-05.

2. Settings

csv_file <- file.path("data", "Taiwan_Stocks_2024_2026_FINAL.csv")
out_dir  <- "output"    # folder for the cleaned data set

if (!file.exists(csv_file)) {
  stop("File not found: '", csv_file, "'. Put the CSV in a folder named 'data' ",
       "next to this .Rmd file.")
}
dir.create(out_dir, showWarnings = FALSE)

3. Import the CSV

Important details:

  • COID is read as text so stock codes keep their exact form.
  • Date is stored as text (2024/10/07), so it is read as text first and converted to a real date afterwards.
  • The first line (header) is skipped and replaced by simpler column names, because the original names contain brackets such as Open(NTD). The file has a UTF-8 BOM, which read_csv() handles automatically.
col_names <- c("coid", "name", "date", "open", "high", "low",
               "close", "volume_1000s")

raw <- read_csv(
  csv_file,
  skip = 1,
  col_names = col_names,
  col_types = cols(
    coid = col_character(), name = col_character(), date = col_character(),
    open = col_double(), high = col_double(), low = col_double(),
    close = col_double(), volume_1000s = col_double()
  )
)

nrow(raw)
## [1] 957924

4. Clean the data

Steps:

  1. Convert date from text to Date.
  2. Remove any duplicate coid + date rows (a safety check; the file should already be free of duplicates).
  3. Keep only the last two years counted back from the latest date in the data.
  4. Add a security-type flag (6-digit 91xxxx codes are Taiwan Depositary Receipts, TDRs) and an approximate daily turnover in NTD.
n_raw <- nrow(raw)
n_dup <- n_raw - nrow(distinct(raw, coid, date))

stocks <- raw %>%
  mutate(date = as.Date(date, format = "%Y/%m/%d"))

n_bad_date <- sum(is.na(stocks$date))

stocks <- stocks %>%
  filter(!is.na(date)) %>%
  distinct(coid, date, .keep_all = TRUE) %>%
  filter(date > max(date) %m-% years(2)) %>%
  mutate(
    security_type = if_else(str_detect(coid, "^91\\d{4}$"), "TDR", "Stock/other"),
    turnover_ntd  = close * volume_1000s * 1000   # approximate NTD traded
  ) %>%
  arrange(coid, date) %>%
  select(coid, name, date, open, high, low, close, volume_1000s,
         turnover_ntd, security_type)

5. Data quality checks

tibble(
  Check = c("Rows in CSV",
            "Duplicate rows removed",
            "Rows with unreadable dates",
            "Rows in final data",
            "Number of companies / securities",
            "Number of trading days",
            "First date",
            "Last date",
            "Rows with High < Low",
            "Rows with missing prices",
            "Rows with zero volume"),
  Value = c(comma(n_raw),
            comma(n_dup),
            comma(n_bad_date),
            comma(nrow(stocks)),
            comma(n_distinct(stocks$coid)),
            comma(n_distinct(stocks$date)),
            as.character(min(stocks$date)),
            as.character(max(stocks$date)),
            comma(sum(stocks$high < stocks$low, na.rm = TRUE)),
            comma(sum(!complete.cases(stocks[, c("open", "high", "low", "close")]))),
            comma(sum(stocks$volume_1000s == 0, na.rm = TRUE)))
) %>%
  kable(caption = "Summary of the final data set")
Summary of the final data set
Check Value
Rows in CSV 957,924
Duplicate rows removed 0
Rows with unreadable dates 0
Rows in final data 957,924
Number of companies / securities 1,987
Number of trading days 485
First date 2024-10-07
Last date 2026-10-05
Rows with High < Low 0
Rows with missing prices 0
Rows with zero volume 10,442

Preview of the data (TSMC, 2330, most recent 10 days):

stocks %>%
  filter(coid == "2330") %>%
  arrange(desc(date)) %>%
  head(10) %>%
  kable(digits = 2)
coid name date open high low close volume_1000s turnover_ntd security_type
2330 TSMC 2026-10-05 2550 2580 2545 2575 26404 67990300000 Stock/other
2330 TSMC 2026-10-02 2505 2515 2495 2500 15792 39480000000 Stock/other
2330 TSMC 2026-10-01 2490 2510 2485 2510 20666 51871660000 Stock/other
2330 TSMC 2026-09-30 2495 2510 2480 2480 34282 85019360000 Stock/other
2330 TSMC 2026-09-29 2475 2495 2475 2475 26893 66560175000 Stock/other
2330 TSMC 2026-09-24 2480 2490 2470 2475 14557 36028575000 Stock/other
2330 TSMC 2026-09-23 2475 2505 2475 2500 22817 57042500000 Stock/other
2330 TSMC 2026-09-22 2505 2510 2460 2460 22009 54142140000 Stock/other
2330 TSMC 2026-09-21 2445 2485 2445 2480 16086 39893280000 Stock/other
2330 TSMC 2026-09-18 2460 2460 2435 2460 40892 100594320000 Stock/other

6. Quick look at the data

6.1 TSMC (2330) closing price

stocks %>%
  filter(coid == "2330") %>%
  ggplot(aes(date, close)) +
  geom_line(linewidth = 0.6) +
  scale_y_continuous(labels = comma) +
  labs(title = "TSMC (2330) daily closing price",
       x = NULL, y = "Close (NTD)") +
  theme_minimal()

6.2 Top 10 securities by total traded value

top10 <- stocks %>%
  group_by(coid, name) %>%
  summarise(total_turnover_bn = sum(turnover_ntd, na.rm = TRUE) / 1e9,
            .groups = "drop") %>%
  slice_max(total_turnover_bn, n = 10) %>%
  mutate(label = paste(coid, name))

ggplot(top10, aes(reorder(label, total_turnover_bn), total_turnover_bn)) +
  geom_col() +
  coord_flip() +
  scale_y_continuous(labels = comma) +
  labs(title = "Top 10 by total traded value (last two years)",
       x = NULL, y = "Total turnover (NTD billion, approximate)") +
  theme_minimal()

6.3 Number of securities traded per month

stocks %>%
  mutate(month = floor_date(date, "month")) %>%
  group_by(month) %>%
  summarise(securities = n_distinct(coid), .groups = "drop") %>%
  ggplot(aes(month, securities)) +
  geom_col() +
  labs(title = "Distinct securities with a price record, by month",
       x = NULL, y = "Securities") +
  theme_minimal()

7. Save the cleaned data

saveRDS(stocks, file.path(out_dir, "taiwan_stocks_daily_2yr.rds"))
write_csv(stocks, file.path(out_dir, "taiwan_stocks_daily_2yr.csv.gz"))

Saved to the output/ folder:

  • taiwan_stocks_daily_2yr.rds (open in R with readRDS())
  • taiwan_stocks_daily_2yr.csv.gz (compressed CSV; open in R with read_csv())

8. Notes and limitations

  • Prices look dividend-adjusted. Many prices have extra decimals (e.g. 31.4261), so they will not match the raw exchange quotes exactly. Use them for return and trend analysis.
  • ETFs are not in this file. For example 0050 does not appear, so the data cover listed companies plus a small number of TDRs (flagged in security_type).
  • Volume is in thousands of shares. Some rows have zero volume (no trading that day).
  • Coverage depends on the file supplied. Companies that were delisted before the data were built may not appear.
  • TWSE and GreTai are combined in this file and cannot be separated.
sessionInfo()
## R version 4.6.1 (2026-06-24)
## Platform: aarch64-apple-darwin23
## Running under: macOS Sequoia 15.7.4
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRblas.0.dylib 
## LAPACK: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## time zone: Asia/Taipei
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] knitr_1.52      scales_1.4.0    ggplot2_4.0.3   lubridate_1.9.5
## [5] stringr_1.6.0   dplyr_1.2.1     readr_2.2.0    
## 
## loaded via a namespace (and not attached):
##  [1] bit_4.6.0          gtable_0.3.6       jsonlite_2.0.0     crayon_1.5.3      
##  [5] compiler_4.6.1     tidyselect_1.2.1   parallel_4.6.1     jquerylib_0.1.4   
##  [9] yaml_2.3.12        fastmap_1.2.0      R6_2.6.1           labeling_0.4.3    
## [13] generics_0.1.4     tibble_3.3.1       RColorBrewer_1.1-3 bslib_0.12.0      
## [17] pillar_1.11.1      tzdb_0.5.0         rlang_1.3.0        cachem_1.1.0      
## [21] stringi_1.8.9      xfun_0.60          S7_0.2.2           sass_0.4.10       
## [25] bit64_4.8.6        otel_0.2.0         timechange_0.4.0   cli_3.6.6         
## [29] withr_3.0.3        magrittr_2.0.5     digest_0.6.39      grid_4.6.1        
## [33] vroom_1.7.1        rstudioapi_0.19.0  hms_1.1.4          lifecycle_1.0.5   
## [37] vctrs_0.7.3        evaluate_1.0.5     glue_1.8.1         farver_2.1.2      
## [41] rmarkdown_2.32     tools_4.6.1        pkgconfig_2.0.3    htmltools_0.5.9