#————————————————————————–
options(repos = c(CRAN = "https://cloud.r-project.org/"))
# Sau đó mới chạy install.packages
install.packages("PerformanceAnalytics", type = "source") # (Nếu bạn vẫn muốn cài trong Rmd)
## Installing package into 'C:/Users/DELL/AppData/Local/R/win-library/4.4'
## (as 'lib' is unspecified)
## Warning in install.packages("PerformanceAnalytics", type = "source"):
## installation of package 'PerformanceAnalytics' had non-zero exit status
library(quantmod) # Để tải dữ liệu tài chính và vẽ biểu đồ
## Warning: package 'quantmod' was built under R version 4.4.3
## Loading required package: xts
## Warning: package 'xts' was built under R version 4.4.3
## Loading required package: zoo
##
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
## Loading required package: TTR
## Warning: package 'TTR' was built under R version 4.4.3
## Registered S3 method overwritten by 'quantmod':
## method from
## as.zoo.data.frame zoo
library(rugarch) # Thư viện chính cho mô hình GARCH
## Warning: package 'rugarch' was built under R version 4.4.3
## Loading required package: parallel
##
## Attaching package: 'rugarch'
## The following object is masked from 'package:stats':
##
## sigma
library(forecast) # Hỗ trợ tìm mô hình ARMA tối ưu (auto.arima)
## Warning: package 'forecast' was built under R version 4.4.3
library(tseries) # Cho các kiểm định như ADF test, Jarque-Bera test
## Warning: package 'tseries' was built under R version 4.4.3
library(PerformanceAnalytics) # Để tính toán tỷ suất sinh lợi
## Warning: package 'PerformanceAnalytics' was built under R version 4.4.3
##
## Attaching package: 'PerformanceAnalytics'
## The following object is masked from 'package:graphics':
##
## legend
# Xác định mã cổ phiếu và khoảng thời gian
fpt_ticker <- "FPT.VN" # Kiểm tra lại mã trên Yahoo Finance
start_date <- "2022-01-01"
end_date <- Sys.Date() # Lấy đến ngày hiện tại
# Tải dữ liệu giá cổ phiếu
cat("Đang tải dữ liệu cho mã:", fpt_ticker, "...\n")
## Đang tải dữ liệu cho mã: FPT.VN ...
getSymbols(fpt_ticker, src = "yahoo", from = start_date, to = end_date, auto.assign = TRUE)
## Warning: FPT.VN contains missing values. Some functions will not work if
## objects contain missing values in the middle of the series. Consider using
## na.omit(), na.approx(), na.fill(), etc to remove or replace them.
## [1] "FPT.VN"
# Kiểm tra xem dữ liệu đã được tải thành công chưa
if (!exists(gsub("\\^", "", fpt_ticker))) { # Yahoo finance có thể trả về tên không có .VN
if (exists(gsub("\\.VN", "", fpt_ticker))) {
fpt_data_raw <- get(gsub("\\.VN", "", fpt_ticker))
} else {
stop("Không thể tải dữ liệu. Vui lòng kiểm tra mã cổ phiếu hoặc kết nối mạng.")
}
} else {
fpt_data_raw <- get(fpt_ticker)
}
# Lấy giá đóng cửa điều chỉnh
fpt_prices <- Ad(fpt_data_raw)
colnames(fpt_prices) <- "Adjusted"
# Loại bỏ các giá trị NA (nếu có)
fpt_prices <- na.omit(fpt_prices)
if (nrow(fpt_prices) < 50) {
stop("Dữ liệu quá ít để phân tích. Vui lòng chọn khoảng thời gian dài hơn.")
}
cat("Dữ liệu giá FPT (Adjusted Close):\n")
## Dữ liệu giá FPT (Adjusted Close):
print(head(fpt_prices))
## Adjusted
## 2022-01-04 57276.88
## 2022-01-05 57338.14
## 2022-01-06 57276.88
## 2022-01-07 57215.63
## 2022-01-10 56358.00
## 2022-01-11 54826.54
print(tail(fpt_prices))
## Adjusted
## 2025-05-30 116500
## 2025-06-02 116100
## 2025-06-03 117400
## 2025-06-04 117100
## 2025-06-05 116800
## 2025-06-06 115000
# Vẽ biểu đồ giá
chartSeries(fpt_prices, theme = chartTheme("white"), name = paste("Giá cổ phiếu", fpt_ticker))
# 3. TÍNH TOÁN TỶ SUẤT SINH LỢI (LOG RETURNS)
# --------------------------------------------------------------------------
# Sử dụng log returns vì chúng có tính chất thống kê tốt hơn cho mô hình hóa tài chính
fpt_returns <- Return.calculate(fpt_prices, method = "log")
fpt_returns <- fpt_returns[-1, ] # Loại bỏ giá trị NA đầu tiên
colnames(fpt_returns) <- "LogReturns"
cat("\nTỷ suất sinh lợi (Log Returns) của FPT:\n")
##
## Tỷ suất sinh lợi (Log Returns) của FPT:
print(head(fpt_returns))
## LogReturns
## 2022-01-05 0.001068932
## 2022-01-06 -0.001068932
## 2022-01-07 -0.001070007
## 2022-01-10 -0.015102824
## 2022-01-11 -0.027549903
## 2022-01-12 0.001116536
print(tail(fpt_returns))
## LogReturns
## 2025-05-30 -0.005136998
## 2025-06-02 -0.003439384
## 2025-06-03 0.011135019
## 2025-06-04 -0.002558637
## 2025-06-05 -0.002565200
## 2025-06-06 -0.015530942
# Vẽ biểu đồ tỷ suất sinh lợi
plot(fpt_returns, main = paste("Tỷ suất sinh lợi (Log Returns) của", fpt_ticker),
col = "blue", ylab = "Log Returns", major.ticks = "months", minor.ticks = FALSE)
abline(h = 0, col = "red", lty = 2)
# 4. KIỂM TRA TÍNH DỪNG CỦA CHUỖI TỶ SUẤT SINH LỢI
# --------------------------------------------------------------------------
# Chuỗi tỷ suất sinh lợi thường là chuỗi dừng
cat("\nKiểm định Augmented Dickey-Fuller (ADF) cho tính dừng của Log Returns:\n")
##
## Kiểm định Augmented Dickey-Fuller (ADF) cho tính dừng của Log Returns:
adf_test_result <- adf.test(na.omit(fpt_returns))
## Warning in adf.test(na.omit(fpt_returns)): p-value smaller than printed p-value
print(adf_test_result)
##
## Augmented Dickey-Fuller Test
##
## data: na.omit(fpt_returns)
## Dickey-Fuller = -8.348, Lag order = 9, p-value = 0.01
## alternative hypothesis: stationary
if (adf_test_result$p.value < 0.05) {
cat("Kết luận: Chuỗi Log Returns có tính dừng (p-value < 0.05).\n")
} else {
cat("Kết luận: Chuỗi Log Returns không có tính dừng (p-value >= 0.05). Cần xem xét sai phân.\n")
# Nếu không dừng, bạn cần lấy sai phân: fpt_returns_diff <- diff(fpt_returns); fpt_returns_diff <- fpt_returns_diff[-1,]
# Tuy nhiên, log returns thường đã dừng.
}
## Kết luận: Chuỗi Log Returns có tính dừng (p-value < 0.05).
# 5. KIỂM TRA HIỆU ỨNG ARCH (ARCH EFFECT)
# --------------------------------------------------------------------------
# Hiệu ứng ARCH là sự cụm lại của biến động (volatility clustering).
# GARCH được sử dụng khi có hiệu ứng ARCH.
# Kiểm tra bằng cách xem ACF của bình phương tỷ suất sinh lợi
# Hoặc sử dụng Ljung-Box test trên bình phương residuals của mô hình ARMA cho mean.
# Vẽ ACF của bình phương Log Returns
acf(coredata(fpt_returns^2), na.action = na.omit, main = paste("ACF của Bình phương Log Returns của", fpt_ticker))
pacf(coredata(fpt_returns^2), na.action = na.omit, main = paste("PACF của Bình phương Log Returns của", fpt_ticker))
cat("\nNếu ACF/PACF của bình phương Log Returns có các spikes đáng kể, điều đó gợi ý sự hiện diện của hiệu ứng ARCH.\n")
##
## Nếu ACF/PACF của bình phương Log Returns có các spikes đáng kể, điều đó gợi ý sự hiện diện của hiệu ứng ARCH.
# Kiểm định Ljung-Box trên bình phương residuals của một mô hình ARMA đơn giản cho mean
# (Hoặc có thể dùng ArchTest từ thư viện FinTS nếu muốn)
cat("\nKiểm định Ljung-Box cho hiệu ứng ARCH (trên bình phương residuals của ARMA model cho mean):\n")
##
## Kiểm định Ljung-Box cho hiệu ứng ARCH (trên bình phương residuals của ARMA model cho mean):
# Fit một mô hình ARMA đơn giản cho mean (ví dụ ARMA(0,0) nếu mean gần 0)
mean_model_simple <- arima(fpt_returns, order = c(0,0,0)) # Hoặc để auto.arima chọn
arch_test_result <- Box.test(residuals(mean_model_simple)^2, lag = 20, type = "Ljung-Box")
print(arch_test_result)
##
## Box-Ljung test
##
## data: residuals(mean_model_simple)^2
## X-squared = 222.23, df = 20, p-value < 2.2e-16
if (arch_test_result$p.value < 0.05) {
cat("Kết luận: Có bằng chứng về hiệu ứng ARCH (p-value < 0.05). Mô hình GARCH là phù hợp.\n")
} else {
cat("Kết luận: Không có bằng chứng rõ ràng về hiệu ứng ARCH (p-value >= 0.05). Cân nhắc lại sự cần thiết của GARCH.\n")
}
## Kết luận: Có bằng chứng về hiệu ứng ARCH (p-value < 0.05). Mô hình GARCH là phù hợp.
# 6. LỰA CHỌN MÔ HÌNH ARMA CHO PHƯƠNG TRÌNH MEAN
# --------------------------------------------------------------------------
# Sử dụng auto.arima để gợi ý bậc p, q cho mô hình ARMA(p,q) của mean
# Chúng ta muốn mô hình hóa phần còn lại (residuals) sau khi đã loại bỏ cấu trúc tự tương quan trong mean.
cat("\nLựa chọn mô hình ARMA cho phương trình Mean sử dụng auto.arima:\n")
##
## Lựa chọn mô hình ARMA cho phương trình Mean sử dụng auto.arima:
arma_order_suggestion <- auto.arima(fpt_returns, stationary = TRUE, seasonal = FALSE,
ic = "aic", trace = FALSE, allowdrift = FALSE,
allowmean = TRUE) # Cho phép hằng số mean
print(summary(arma_order_suggestion))
## Series: fpt_returns
## ARIMA(0,0,2) with non-zero mean
##
## Coefficients:
## ma1 ma2 mean
## 0.0556 -0.0882 8e-04
## s.e. 0.0344 0.0344 6e-04
##
## sigma^2 = 0.0003028: log likelihood = 2231.08
## AIC=-4454.17 AICc=-4454.12 BIC=-4435.2
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE ACF1
## Training set 3.799582e-07 0.01736928 0.01186288 NaN Inf 0.6821665 0.003766303
p_order <- arma_order_suggestion$arma[1] # Bậc AR
q_order <- arma_order_suggestion$arma[2] # Bậc MA
# d_order (bậc sai phân) nên là 0 vì log returns thường đã dừng
cat(paste("Gợi ý bậc ARMA cho phương trình Mean: ARMA(", p_order, ",", q_order, ")\n", sep=""))
## Gợi ý bậc ARMA cho phương trình Mean: ARMA(0,2)
# 7. ĐẶC ĐỊNH VÀ ƯỚC LƯỢNG MÔ HÌNH GARCH
# --------------------------------------------------------------------------
# Phổ biến nhất là GARCH(1,1). Bạn có thể thử các bậc khác (ví dụ GARCH(1,2), GARCH(2,1)).
# Các loại mô hình GARCH khác: eGARCH (cho hiệu ứng đòn bẩy), gjrGARCH,...
# Phân phối của innovations: "norm" (normal), "std" (Student's t), "sstd" (skewed Student's t)
# Student's t hoặc skewed Student's t thường phù hợp hơn cho dữ liệu tài chính do "fat tails".
# Ví dụ: sGARCH(1,1) với ARMA(p,q) cho mean và phân phối skewed Student's t
garch_spec <- ugarchspec(
variance.model = list(model = "sGARCH", garchOrder = c(1, 1)),
mean.model = list(armaOrder = c(p_order, q_order), include.mean = TRUE), # include.mean = TRUE là quan trọng
distribution.model = "sstd" # Skewed Student's t distribution
)
cat("\nĐang ước lượng mô hình GARCH...\n")
##
## Đang ước lượng mô hình GARCH...
# Cần xử lý các giá trị NA trước khi fit, fpt_returns đã được xử lý NA ở bước 3
garch_fit <- ugarchfit(spec = garch_spec, data = na.omit(fpt_returns), solver = 'hybrid')
cat("\nKết quả ước lượng mô hình GARCH:\n")
##
## Kết quả ước lượng mô hình GARCH:
print(garch_fit) # In ra các hệ số và ý nghĩa thống kê
##
## *---------------------------------*
## * GARCH Model Fit *
## *---------------------------------*
##
## Conditional Variance Dynamics
## -----------------------------------
## GARCH Model : sGARCH(1,1)
## Mean Model : ARFIMA(0,0,2)
## Distribution : sstd
##
## Optimal Parameters
## ------------------------------------
## Estimate Std. Error t value Pr(>|t|)
## mu 0.000636 0.000488 1.30490 0.191928
## ma1 0.009029 0.032328 0.27929 0.780021
## ma2 -0.083132 0.033039 -2.51620 0.011863
## omega 0.000009 0.000013 0.66396 0.506715
## alpha1 0.130619 0.072840 1.79324 0.072935
## beta1 0.868376 0.041329 21.01109 0.000000
## skew 1.001760 0.043789 22.87720 0.000000
## shape 3.338335 0.841998 3.96478 0.000073
##
## Robust Standard Errors:
## Estimate Std. Error t value Pr(>|t|)
## mu 0.000636 0.001113 0.57180 0.567457
## ma1 0.009029 0.030098 0.29999 0.764188
## ma2 -0.083132 0.037426 -2.22128 0.026332
## omega 0.000009 0.000064 0.14048 0.888282
## alpha1 0.130619 0.330676 0.39501 0.692839
## beta1 0.868376 0.167095 5.19689 0.000000
## skew 1.001760 0.066715 15.01544 0.000000
## shape 3.338335 3.650715 0.91443 0.360489
##
## LogLikelihood : 2362.036
##
## Information Criteria
## ------------------------------------
##
## Akaike -5.5585
## Bayes -5.5137
## Shibata -5.5587
## Hannan-Quinn -5.5414
##
## Weighted Ljung-Box Test on Standardized Residuals
## ------------------------------------
## statistic p-value
## Lag[1] 3.587 0.05822
## Lag[2*(p+q)+(p+q)-1][5] 4.092 0.05255
## Lag[4*(p+q)+(p+q)-1][9] 5.413 0.36452
## d.o.f=2
## H0 : No serial correlation
##
## Weighted Ljung-Box Test on Standardized Squared Residuals
## ------------------------------------
## statistic p-value
## Lag[1] 0.6491 0.4204
## Lag[2*(p+q)+(p+q)-1][5] 1.5640 0.7241
## Lag[4*(p+q)+(p+q)-1][9] 2.9939 0.7600
## d.o.f=2
##
## Weighted ARCH LM Tests
## ------------------------------------
## Statistic Shape Scale P-Value
## ARCH Lag[3] 0.4567 0.500 2.000 0.4992
## ARCH Lag[5] 1.4452 1.440 1.667 0.6068
## ARCH Lag[7] 2.4147 2.315 1.543 0.6300
##
## Nyblom stability test
## ------------------------------------
## Joint Statistic: 2.4516
## Individual Statistics:
## mu 0.12678
## ma1 0.11106
## ma2 0.12838
## omega 0.22913
## alpha1 0.07023
## beta1 0.08982
## skew 0.05452
## shape 0.15047
##
## Asymptotic Critical Values (10% 5% 1%)
## Joint Statistic: 1.89 2.11 2.59
## Individual Statistic: 0.35 0.47 0.75
##
## Sign Bias Test
## ------------------------------------
## t-value prob sig
## Sign Bias 0.1132 0.9099
## Negative Sign Bias 0.5584 0.5768
## Positive Sign Bias 0.8599 0.3901
## Joint Effect 1.0567 0.7875
##
##
## Adjusted Pearson Goodness-of-Fit Test:
## ------------------------------------
## group statistic p-value(g-1)
## 1 20 15.17 0.7116
## 2 30 22.22 0.8109
## 3 40 31.91 0.7823
## 4 50 35.82 0.9199
##
##
## Elapsed time : 0.209693
# 8. KIỂM TRA MÔ HÌNH (MODEL DIAGNOSTICS)
# --------------------------------------------------------------------------
# Kiểm tra các residuals chuẩn hóa (standardized residuals)
# - Chúng nên là nhiễu trắng (không có tự tương quan).
# - Bình phương residuals chuẩn hóa cũng không nên có tự tương quan (không còn ARCH effect).
# - Phân phối của residuals chuẩn hóa nên gần với phân phối đã giả định (ví dụ, sstd).
cat("\nKiểm tra mô hình GARCH:\n")
##
## Kiểm tra mô hình GARCH:
# Lấy residuals chuẩn hóa
std_resid <- residuals(garch_fit, standardize = TRUE)
std_resid <- as.numeric(std_resid) # Chuyển sang vector số
# Biểu đồ residuals chuẩn hóa
plot(std_resid, type = 'l', main = 'Residuals Chuẩn hóa', ylab = '')
abline(h = 0, col = "red")
# ACF và PACF của residuals chuẩn hóa
acf(std_resid, na.action = na.omit, main = 'ACF của Residuals Chuẩn hóa')
pacf(std_resid, na.action = na.omit, main = 'PACF của Residuals Chuẩn hóa')
# ACF và PACF của bình phương residuals chuẩn hóa
acf(std_resid^2, na.action = na.omit, main = 'ACF của Bình phương Residuals Chuẩn hóa')
pacf(std_resid^2, na.action = na.omit, main = 'PACF của Bình phương Residuals Chuẩn hóa')
# Kiểm định Ljung-Box trên residuals chuẩn hóa và bình phương residuals chuẩn hóa
cat("\nKiểm định Ljung-Box trên Residuals chuẩn hóa:\n")
##
## Kiểm định Ljung-Box trên Residuals chuẩn hóa:
print(Box.test(std_resid, lag = 20, type = "Ljung-Box"))
##
## Box-Ljung test
##
## data: std_resid
## X-squared = 17.891, df = 20, p-value = 0.5946
cat("\nKiểm định Ljung-Box trên Bình phương Residuals chuẩn hóa (kiểm tra ARCH còn sót lại):\n")
##
## Kiểm định Ljung-Box trên Bình phương Residuals chuẩn hóa (kiểm tra ARCH còn sót lại):
print(Box.test(std_resid^2, lag = 20, type = "Ljung-Box"))
##
## Box-Ljung test
##
## data: std_resid^2
## X-squared = 12.952, df = 20, p-value = 0.8795
# p-value lớn (>0.05) cho thấy không có tự tương quan => mô hình tốt.
# Kiểm tra phân phối của residuals chuẩn hóa (ví dụ: Jarque-Bera test)
cat("\nKiểm định Jarque-Bera cho tính chuẩn của Residuals chuẩn hóa (nếu giả định là normal):\n")
##
## Kiểm định Jarque-Bera cho tính chuẩn của Residuals chuẩn hóa (nếu giả định là normal):
# Lưu ý: nếu distribution.model là 'std' hoặc 'sstd', residuals sẽ không chuẩn.
# Chúng ta kiểm tra xem nó có phù hợp với phân phối đã chọn không.
# QQ-plot là một cách tốt để kiểm tra trực quan
qqnorm(std_resid, main = "QQ-Plot của Residuals Chuẩn hóa")
qqline(std_resid, col = "red")
# Nếu dùng sstd, có thể so sánh với qdist("sstd", ...)
# Lệnh plot tích hợp của rugarch rất hữu ích
# plot(garch_fit, which = "all") # Hiển thị tất cả các biểu đồ chẩn đoán
# Hoặc chọn các biểu đồ cụ thể:
# plot(garch_fit, which = 1) # Conditional SD
# plot(garch_fit, which = 3) # Series with 1% VaR limits
# plot(garch_fit, which = 8) # Standardized Residuals ACF
# plot(garch_fit, which = 9) # Standardized Squared Residuals ACF
# plot(garch_fit, which = 10) # Standardized Residuals QQ-plot
# 9. DỰ BÁO (FORECASTING)
# --------------------------------------------------------------------------
n_forecast <- 30 # Số phiên giao dịch muốn dự báo (ví dụ: 30 ngày ~ 1.5 tháng)
cat(paste("\nĐang thực hiện dự báo cho", n_forecast, "phiên tới...\n"))
##
## Đang thực hiện dự báo cho 30 phiên tới...
# Thực hiện dự báo
garch_forecast <- ugarchforecast(garch_fit, n.ahead = n_forecast)
# Kết quả dự báo bao gồm:
# - seriesFor: dự báo cho chuỗi (tỷ suất sinh lợi)
# - sigmaFor: dự báo cho độ lệch chuẩn có điều kiện (volatility)
cat("\nKết quả dự báo:\n")
##
## Kết quả dự báo:
print(garch_forecast)
##
## *------------------------------------*
## * GARCH Model Forecast *
## *------------------------------------*
## Model: sGARCH
## Horizon: 30
## Roll Steps: 0
## Out of Sample: 0
##
## 0-roll forecast [T0=2025-06-06]:
## Series Sigma
## T+1 0.0006817 0.01559
## T+2 0.0020040 0.01586
## T+3 0.0006362 0.01613
## T+4 0.0006362 0.01640
## T+5 0.0006362 0.01666
## T+6 0.0006362 0.01692
## T+7 0.0006362 0.01717
## T+8 0.0006362 0.01742
## T+9 0.0006362 0.01767
## T+10 0.0006362 0.01791
## T+11 0.0006362 0.01815
## T+12 0.0006362 0.01838
## T+13 0.0006362 0.01862
## T+14 0.0006362 0.01885
## T+15 0.0006362 0.01907
## T+16 0.0006362 0.01929
## T+17 0.0006362 0.01952
## T+18 0.0006362 0.01973
## T+19 0.0006362 0.01995
## T+20 0.0006362 0.02016
## T+21 0.0006362 0.02037
## T+22 0.0006362 0.02058
## T+23 0.0006362 0.02078
## T+24 0.0006362 0.02099
## T+25 0.0006362 0.02119
## T+26 0.0006362 0.02139
## T+27 0.0006362 0.02158
## T+28 0.0006362 0.02178
## T+29 0.0006362 0.02197
## T+30 0.0006362 0.02216
# Lấy giá trị dự báo cho tỷ suất sinh lợi và volatility
forecasted_returns <- fitted(garch_forecast) # Đây là vector các giá trị dự báo cho mean (log returns)
forecasted_volatility <- sigma(garch_forecast) # Đây là vector các giá trị dự báo cho sigma (volatility)
# 10. CHUYỂN ĐỔI TỶ SUẤT SINH LỢI DỰ BÁO SANG GIÁ DỰ BÁO
# --------------------------------------------------------------------------
# Lấy giá đóng cửa cuối cùng trong dữ liệu lịch sử
last_price <- as.numeric(tail(fpt_prices, 1))
# Tính toán giá dự báo
# P_t = P_{t-1} * exp(log_return_t)
forecasted_prices <- numeric(n_forecast)
current_price_level <- last_price
for (i in 1:n_forecast) {
current_price_level <- current_price_level * exp(forecasted_returns[i])
forecasted_prices[i] <- current_price_level
}
# Tạo chuỗi ngày cho các giá trị dự báo
last_date <- index(tail(fpt_prices, 1))
# Tạo một chuỗi ngày làm việc (bỏ qua cuối tuần, nhưng không bỏ qua ngày lễ cụ thể)
# Đây là cách đơn giản, có thể cần điều chỉnh nếu muốn chính xác hơn về ngày giao dịch
forecast_dates <- seq(from = last_date + 1, by = "days", length.out = n_forecast * 2) # Tạo dư ngày
forecast_dates <- forecast_dates[!weekdays(forecast_dates) %in% c("Saturday", "Sunday")] # Bỏ cuối tuần
forecast_dates <- head(forecast_dates, n_forecast) # Lấy đủ số ngày dự báo
# Tạo đối tượng xts cho giá dự báo
forecasted_prices_xts <- xts(forecasted_prices, order.by = forecast_dates)
colnames(forecasted_prices_xts) <- "ForecastedPrice"
cat("\nGiá cổ phiếu FPT dự báo:\n")
##
## Giá cổ phiếu FPT dự báo:
print(forecasted_prices_xts)
## ForecastedPrice
## 2025-06-09 115078.4
## 2025-06-10 115309.3
## 2025-06-11 115382.7
## 2025-06-12 115456.1
## 2025-06-13 115529.6
## 2025-06-16 115603.1
## 2025-06-17 115676.7
## 2025-06-18 115750.3
## 2025-06-19 115823.9
## 2025-06-20 115897.7
## 2025-06-23 115971.4
## 2025-06-24 116045.2
## 2025-06-25 116119.1
## 2025-06-26 116193.0
## 2025-06-27 116266.9
## 2025-06-30 116340.9
## 2025-07-01 116415.0
## 2025-07-02 116489.0
## 2025-07-03 116563.2
## 2025-07-04 116637.4
## 2025-07-07 116711.6
## 2025-07-08 116785.9
## 2025-07-09 116860.2
## 2025-07-10 116934.6
## 2025-07-11 117009.0
## 2025-07-14 117083.5
## 2025-07-15 117158.0
## 2025-07-16 117232.5
## 2025-07-17 117307.1
## 2025-07-18 117381.8
# 11. TRỰC QUAN HÓA KẾT QUẢ DỰ BÁO
# --------------------------------------------------------------------------
# Kết hợp dữ liệu lịch sử và dự báo để vẽ đồ thị
# Giới hạn dữ liệu lịch sử để biểu đồ dễ nhìn hơn (ví dụ: 100 ngày cuối)
historical_to_plot <- tail(fpt_prices, 100)
combined_prices <- rbind(historical_to_plot, forecasted_prices_xts)
# Vẽ biểu đồ giá lịch sử và giá dự báo
plot(historical_to_plot,
main = paste("Dự báo giá cổ phiếu", fpt_ticker, "sử dụng GARCH"),
ylab = "Giá (VND)",
xlab = "Thời gian",
col = "black",
xlim = c(index(historical_to_plot)[1], tail(index(forecasted_prices_xts),1) + 5), # Mở rộng xlim
ylim = range(c(coredata(historical_to_plot), coredata(forecasted_prices_xts)), na.rm = TRUE) * c(0.95, 1.05) # Điều chỉnh ylim
)
lines(forecasted_prices_xts, col = "blue", lwd = 2)
abline(v = last_date, col = "grey", lty = 2) # Đường phân cách lịch sử và dự báo
legend("topleft",
legend = c("Giá lịch sử", "Giá dự báo GARCH"),
col = c("black", "blue"),
lty = 1, lwd = c(1, 2),
cex = 0.8, bg = "white")
# Vẽ biểu đồ volatility dự báo
plot(forecasted_volatility, type = "l", main = "Dự báo Biến động (Conditional Volatility - Sigma)",
xlab = "Số ngày dự báo", ylab = "Sigma", col = "darkgreen", lwd = 2)
abline(h = mean(forecasted_volatility), col="red", lty=2)
legend("topright", legend="Mean Sigma Forecast", col="red", lty=2, cex=0.8)
cat("\n--- HOÀN THÀNH PHÂN TÍCH VÀ DỰ BÁO ---\n")
##
## --- HOÀN THÀNH PHÂN TÍCH VÀ DỰ BÁO ---
cat("LƯU Ý: Kết quả dự báo tài chính chỉ mang tính tham khảo và có độ không chắc chắn cao.\n")
## LƯU Ý: Kết quả dự báo tài chính chỉ mang tính tham khảo và có độ không chắc chắn cao.
cat("Mô hình GARCH tập trung vào dự báo biến động. Dự báo giá là một ứng dụng phụ.\n")
## Mô hình GARCH tập trung vào dự báo biến động. Dự báo giá là một ứng dụng phụ.
# --------------------------------------------------------------------------
# CÁC CẢI TIẾN TIỀM NĂNG:
# 1. Thử các mô hình GARCH khác: eGARCH, gjrGARCH (nếu nghi ngờ hiệu ứng đòn bẩy).
# 2. Thử các bậc khác nhau cho ARMA và GARCH (ví dụ: (1,2), (2,1), (2,2)).
# 3. Sử dụng `ugarchroll` để thực hiện backtesting và đánh giá hiệu suất dự báo của mô hình.
# 4. Kết hợp các yếu tố ngoại sinh (exogenous variables) nếu có (mô hình GARCH-X).
# 5. Xây dựng khoảng tin cậy cho dự báo giá (phức tạp hơn do tính path-dependent).
# Có thể dùng `ugarchboot` để tạo khoảng tin cậy bằng bootstrap.
# --------------------------------------------------------------------------