程式說明
if (!requireNamespace("quantmod", quietly = TRUE)) install.packages("quantmod")
if (!requireNamespace("xts", quietly = TRUE)) install.packages("xts")
if (!requireNamespace("dygraphs", quietly = TRUE)) install.packages("dygraphs")
if (!requireNamespace("lubridate", quietly = TRUE)) install.packages("lubridate")
library(quantmod) # 用於下載股價資料
library(xts) # 用於處理時間序列資料
library(dygraphs) # 用於繪製互動式圖表
library(lubridate)# 用於日期時間處理函數
# 設定股票代號和時間範圍
tsmc_ticker <- "2330.TW"
taiex_ticker <- "^TWII" # 加權指數代號包含特殊符號 ^
# 設定資料起始和結束日期 (過去五年)
end_date <- Sys.Date()
start_date <- as.Date(end_date - years(5)) # 這裡需要 lubridate 套件
cat(paste0("正在下載 ", format(start_date, "%Y-%m-%d"), " 到 ", format(end_date, "%Y-%m-%d"), " 的資料...\n"))
tryCatch({
# *** 修正點 1: auto.assign = FALSE,並手動賦值給有效變數名 ***
# 下載台積電資料,並將其結果直接賦值給 tsmc_data
tsmc_data <- getSymbols(tsmc_ticker, src = "yahoo", from = start_date, to = end_date, auto.assign = FALSE)
# 下載加權指數資料,並將其結果直接賦值給 taiex_data
taiex_data <- getSymbols(taiex_ticker, src = "yahoo", from = start_date, to = end_date, auto.assign = FALSE)
}, error = function(e) {
message(paste0("資料下載錯誤: ", e$message))
message("請檢查您的網路連線或股票代號是否正確。程式終止。")
q(save = "no") # 終止 R 會話
})
# 檢查下載的資料是否為空
if (NROW(tsmc_data) == 0 || NROW(taiex_data) == 0) {
message("下載的資料為空。程式終止。")
q(save = "no")
}
# 使用調整後的收盤價 (Adj Close)
# *** 修正點 2: 因為已經直接賦值,所以不需要 get() 了 ***
tsmc_prices <- Ad(tsmc_data)
taiex_prices <- Ad(taiex_data)
# 合併股價資料,並移除任何包含 NA 的列
combined_prices <- merge(tsmc_prices, taiex_prices, all = FALSE)
colnames(combined_prices) <- c("TSMC", "TAIEX")
cat("\n合併後的股價資料範例 (前幾筆):\n")
print(head(combined_prices))
# 使用 log return: log(今日收盤價 / 昨日收盤價)
tsmc_returns <- diff(log(combined_prices$TSMC))
taiex_returns <- diff(log(combined_prices$TAIEX))
# 合併報酬率資料,並移除任何包含 NA 的列 (第一筆會是 NA)
returns_df <- merge(tsmc_returns, taiex_returns, all = FALSE)
colnames(returns_df) <- c("TSMC_Returns", "TAIEX_Returns")
returns_df <- na.omit(returns_df) # 移除第一筆的 NA
cat("\n日報酬率資料範例 (前幾筆):\n")
print(head(returns_df))
# 您可以調整這個視窗大小。常見的選擇有 20 (月)、60 (季)、252 (年)
rolling_window <- 252 # 大約一年的交易日
- 利用 array + for 迴圈計算移動 Beta
# Beta = 協方差(資產報酬率, 市場報酬率) / 方差(市場報酬率)
# 初始化一個空的數組來儲存移動 Beta 值
# 預先分配空間可以提高效率
rolling_betas_array <- array(NA, dim = NROW(returns_df) - rolling_window + 1)
dates_for_beta <- array(NA, dim = NROW(returns_df) - rolling_window + 1)
# 檢查是否有足夠的資料來計算 Beta
if (NROW(returns_df) < rolling_window) {
message(paste0("資料不足以計算滾動 Beta (視窗大小為 ", rolling_window, ")。"))
message(paste0("目前可用的資料點數: ", NROW(returns_df)))
message("請減少滾動視窗大小或獲取更多歷史資料。程式終止。")
q(save = "no")
}
# 迴圈計算 Beta
for (i in 1:(NROW(returns_df) - rolling_window + 1)) {
# 取得當前視窗內的報酬率資料
window_returns <- returns_df[i:(i + rolling_window - 1), ]
# 將 xts 物件轉換為數值向量,以便進行計算
tsmc_window_returns <- as.numeric(window_returns$TSMC_Returns)
taiex_window_returns <- as.numeric(window_returns$TAIEX_Returns)
# 計算協方差和方差
# R 的 cov() 函式已經考慮了自由度
covariance_tsmc_taiex <- cov(tsmc_window_returns, taiex_window_returns)
variance_taiex <- var(taiex_window_returns) # 預設 ddof=1
# 避免除以零
if (variance_taiex == 0) {
beta <- NA
} else {
beta <- covariance_tsmc_taiex / variance_taiex
}
rolling_betas_array[i] <- beta
# 與此 Beta 值相關聯的日期是該視窗的最後一天
dates_for_beta[i] <- index(window_returns)[rolling_window]
}
# 將結果轉換為 xts 物件,方便 dygraphs 處理
rolling_beta_xts <- xts(rolling_betas_array, order.by = as.Date(dates_for_beta))
colnames(rolling_beta_xts) <- "Rolling Beta"
cat(paste0("\n已計算 ", NROW(rolling_beta_xts), " 個移動 Beta 值 (視窗大小 = ", rolling_window, " 天)。\n"))
cat("\n移動 Beta 值範例 (後幾筆):\n")
print(tail(rolling_beta_xts))
cat("\n正在產生 dygraphs 圖表,請在 Viewer 或瀏覽器中查看。\n")
dygraph(rolling_beta_xts,
main = paste0("台積電 (2330.TW) 相對於加權指數 (^TWII) 的移動 Beta 值 (視窗: ", rolling_window, " 天)")) %>%
dySeries("Rolling Beta", label = "移動 Beta") %>%
dyAxis("y", label = "Beta 值", # <-- Y 軸的標籤設定在這裡
valueFormatter = htmlwidgets::JS("function(y) { return y.toFixed(2); }"),
axisLabelFormatter = htmlwidgets::JS("function(y) { return y.toFixed(2); }")
) %>%
dyOptions(fillGraph = FALSE, drawPoints = FALSE, colors = c("#007BFF"), strokeWidth = 1.5
# 移除 'axes' 參數,因為它應該在 dyAxis() 中設定
# axes = list(
# y = list(valueFormatter = htmlwidgets::JS("function(y) { return y.toFixed(2); }"),
# axisLabelFormatter = htmlwidgets::JS("function(y) { return y.toFixed(2); }"))
# )
) %>%
dyRangeSelector() %>% # 允許使用者選擇日期範圍
dyCrosshair(direction = "vertical") %>% # 滑鼠懸停時顯示垂直線
dyHighlight(highlightSeriesOpts = list(strokeWidth = 3, strokeBorderWidth = 1, highlightCircleSize = 5)) %>%
dyLegend(show = "always", width = 200) # 永遠顯示圖例
程式執行結果
if (!requireNamespace("quantmod", quietly = TRUE)) install.packages("quantmod")
if (!requireNamespace("xts", quietly = TRUE)) install.packages("xts")
if (!requireNamespace("dygraphs", quietly = TRUE)) install.packages("dygraphs")
if (!requireNamespace("lubridate", quietly = TRUE)) install.packages("lubridate")
library(quantmod) # 用於下載股價資料
library(xts) # 用於處理時間序列資料
library(dygraphs) # 用於繪製互動式圖表
library(lubridate)# 用於日期時間處理函數
# 1. 取得歷史股價資料
# 設定股票代號和時間範圍
tsmc_ticker <- "2330.TW"
taiex_ticker <- "^TWII" # 加權指數代號包含特殊符號 ^
# 設定資料起始和結束日期 (過去五年)
end_date <- Sys.Date()
start_date <- as.Date(end_date - years(5)) # 這裡需要 lubridate 套件
cat(paste0("正在下載 ", format(start_date, "%Y-%m-%d"), " 到 ", format(end_date, "%Y-%m-%d"), " 的資料...\n"))
## 正在下載 2020-06-05 到 2025-06-05 的資料...
tryCatch({
# *** 修正點 1: auto.assign = FALSE,並手動賦值給有效變數名 ***
# 下載台積電資料,並將其結果直接賦值給 tsmc_data
tsmc_data <- getSymbols(tsmc_ticker, src = "yahoo", from = start_date, to = end_date, auto.assign = FALSE)
# 下載加權指數資料,並將其結果直接賦值給 taiex_data
taiex_data <- getSymbols(taiex_ticker, src = "yahoo", from = start_date, to = end_date, auto.assign = FALSE)
}, error = function(e) {
message(paste0("資料下載錯誤: ", e$message))
message("請檢查您的網路連線或股票代號是否正確。程式終止。")
q(save = "no") # 終止 R 會話
})
# 檢查下載的資料是否為空
if (NROW(tsmc_data) == 0 || NROW(taiex_data) == 0) {
message("下載的資料為空。程式終止。")
q(save = "no")
}
# 使用調整後的收盤價 (Adj Close)
# *** 修正點 2: 因為已經直接賦值,所以不需要 get() 了 ***
tsmc_prices <- Ad(tsmc_data)
taiex_prices <- Ad(taiex_data)
# 合併股價資料,並移除任何包含 NA 的列
combined_prices <- merge(tsmc_prices, taiex_prices, all = FALSE)
colnames(combined_prices) <- c("TSMC", "TAIEX")
cat("\n合併後的股價資料範例 (前幾筆):\n")
##
## 合併後的股價資料範例 (前幾筆):
print(head(combined_prices))
## TSMC TAIEX
## 2020-06-05 281.9039 11479.40
## 2020-06-08 287.7863 11610.32
## 2020-06-09 288.6913 11637.11
## 2020-06-10 291.8588 11720.16
## 2020-06-11 290.0488 11535.77
## 2020-06-12 285.9763 11429.94
# 2. 計算日對數報酬率
# 使用 log return: log(今日收盤價 / 昨日收盤價)
tsmc_returns <- diff(log(combined_prices$TSMC))
taiex_returns <- diff(log(combined_prices$TAIEX))
# 合併報酬率資料,並移除任何包含 NA 的列 (第一筆會是 NA)
returns_df <- merge(tsmc_returns, taiex_returns, all = FALSE)
colnames(returns_df) <- c("TSMC_Returns", "TAIEX_Returns")
returns_df <- na.omit(returns_df) # 移除第一筆的 NA
cat("\n日報酬率資料範例 (前幾筆):\n")
##
## 日報酬率資料範例 (前幾筆):
## TSMC_Returns TAIEX_Returns
## 2020-06-08 0.020652015 0.011340226
## 2020-06-09 0.003139862 0.002304775
## 2020-06-10 0.010912001 0.007111289
## 2020-06-11 -0.006220931 -0.015857847
## 2020-06-12 -0.014140067 -0.009216336
## 2020-06-15 -0.020783982 -0.010879732
# 3. 定義移動視窗
# 您可以調整這個視窗大小。常見的選擇有 20 (月)、60 (季)、252 (年)
rolling_window <- 252 # 大約一年的交易日
# 4. 利用 array + for 迴圈計算移動 Beta
# Beta = 協方差(資產報酬率, 市場報酬率) / 方差(市場報酬率)
# 初始化一個空的數組來儲存移動 Beta 值
# 預先分配空間可以提高效率
rolling_betas_array <- array(NA, dim = NROW(returns_df) - rolling_window + 1)
dates_for_beta <- array(NA, dim = NROW(returns_df) - rolling_window + 1)
# 檢查是否有足夠的資料來計算 Beta
if (NROW(returns_df) < rolling_window) {
message(paste0("資料不足以計算滾動 Beta (視窗大小為 ", rolling_window, ")。"))
message(paste0("目前可用的資料點數: ", NROW(returns_df)))
message("請減少滾動視窗大小或獲取更多歷史資料。程式終止。")
q(save = "no")
}
# 迴圈計算 Beta
for (i in 1:(NROW(returns_df) - rolling_window + 1)) {
# 取得當前視窗內的報酬率資料
window_returns <- returns_df[i:(i + rolling_window - 1), ]
# 將 xts 物件轉換為數值向量,以便進行計算
tsmc_window_returns <- as.numeric(window_returns$TSMC_Returns)
taiex_window_returns <- as.numeric(window_returns$TAIEX_Returns)
# 計算協方差和方差
# R 的 cov() 函式已經考慮了自由度
covariance_tsmc_taiex <- cov(tsmc_window_returns, taiex_window_returns)
variance_taiex <- var(taiex_window_returns) # 預設 ddof=1
# 避免除以零
if (variance_taiex == 0) {
beta <- NA
} else {
beta <- covariance_tsmc_taiex / variance_taiex
}
rolling_betas_array[i] <- beta
# 與此 Beta 值相關聯的日期是該視窗的最後一天
dates_for_beta[i] <- index(window_returns)[rolling_window]
}
# 將結果轉換為 xts 物件,方便 dygraphs 處理
rolling_beta_xts <- xts(rolling_betas_array, order.by = as.Date(dates_for_beta))
colnames(rolling_beta_xts) <- "Rolling Beta"
cat(paste0("\n已計算 ", NROW(rolling_beta_xts), " 個移動 Beta 值 (視窗大小 = ", rolling_window, " 天)。\n"))
##
## 已計算 961 個移動 Beta 值 (視窗大小 = 252 天)。
cat("\n移動 Beta 值範例 (後幾筆):\n")
##
## 移動 Beta 值範例 (後幾筆):
print(tail(rolling_beta_xts))
## Rolling Beta
## 2025-05-27 1.287902
## 2025-05-28 1.286842
## 2025-05-29 1.286939
## 2025-06-02 1.286635
## 2025-06-03 1.287094
## 2025-06-04 1.290347
# 5. 繪製走勢圖 (使用 dygraphs)
cat("\n正在產生 dygraphs 圖表,請在 Viewer 或瀏覽器中查看。\n")
##
## 正在產生 dygraphs 圖表,請在 Viewer 或瀏覽器中查看。
dygraph(rolling_beta_xts,
main = paste0("台積電 (2330.TW) 相對於加權指數 (^TWII) 的移動 Beta 值 (視窗: ", rolling_window, " 天)")) %>%
dySeries("Rolling Beta", label = "移動 Beta") %>%
dyAxis("y", label = "Beta 值", # <-- Y 軸的標籤設定在這裡
valueFormatter = htmlwidgets::JS("function(y) { return y.toFixed(2); }"),
axisLabelFormatter = htmlwidgets::JS("function(y) { return y.toFixed(2); }")
) %>%
dyOptions(fillGraph = FALSE, drawPoints = FALSE, colors = c("#007BFF"), strokeWidth = 1.5
# 移除 'axes' 參數,因為它應該在 dyAxis() 中設定
# axes = list(
# y = list(valueFormatter = htmlwidgets::JS("function(y) { return y.toFixed(2); }"),
# axisLabelFormatter = htmlwidgets::JS("function(y) { return y.toFixed(2); }"))
# )
) %>%
dyRangeSelector() %>% # 允許使用者選擇日期範圍
dyCrosshair(direction = "vertical") %>% # 滑鼠懸停時顯示垂直線
dyHighlight(highlightSeriesOpts = list(strokeWidth = 3, strokeBorderWidth = 1, highlightCircleSize = 5)) %>%
dyLegend(show = "always", width = 200) # 永遠顯示圖例