# 安装和加载必要的包
if (!require("fPortfolio")) install.packages("fPortfolio")
if (!require("timeSeries")) install.packages("timeSeries")
if (!require("readxl")) install.packages("readxl")
if (!require("ggplot2")) install.packages("ggplot2")
library(fPortfolio)
library(timeSeries)
library(readxl)
library(ggplot2)
# 读取数据
data_raw <- read_excel("C:/Users/11366/Desktop/data.xls", skip = 4, col_names = FALSE)
# 获取股票名称(第4行)
stock_names_row <- read_excel("C:/Users/11366/Desktop/data.xls", skip = 3, n_max = 1, col_names = FALSE)
stock_names <- as.character(stock_names_row[1, 2:21])
# 获取股票代码(第3行)
stock_codes_row <- read_excel("C:/Users/11366/Desktop/data.xls", skip = 2, n_max = 1, col_names = FALSE)
stock_codes <- as.character(stock_codes_row[1, 2:21])
# 设置列名
colnames(data_raw) <- c("Date", stock_codes)
# 转换日期
data_raw$Date <- as.Date(data_raw$Date, origin = "1899-12-30")
# 查看数据结构
head(data_raw[, 1:6])
## # A tibble: 6 × 6
## Date `430047.BJ` `430090.BJ` `430139.BJ` `430198.BJ` `430300.BJ`
## <date> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 2022-01-04 25.5 10.0 20.3 9.7 8.86
## 2 2022-01-05 24.2 9.6 19.5 9.77 8.88
## 3 2022-01-06 24.5 8.5 19.5 9.66 8.91
## 4 2022-01-07 23.2 8.29 19.4 9.53 8.67
## 5 2022-01-10 22.3 8.76 19.6 9.5 8.7
## 6 2022-01-11 20.5 8.3 20 9.44 8.61
# 学号后四位为2050
# 处理规则:零加1,重复数字加1
# 2,0,5,0 -> 2,1,5,1 -> 2,1,5,2 -> 2,1,5,3
# 最终索引: 2, 1, 5, 3 (1-based)
selected_indices <- c(2, 1, 5, 3)
selected_stocks <- stock_codes[selected_indices]
selected_names <- stock_names[selected_indices]
cat("选中的股票索引(1-based):", selected_indices, "\n")
## 选中的股票索引(1-based): 2 1 5 3
cat("选中的股票代码:", selected_stocks, "\n")
## 选中的股票代码: 430090.BJ 430047.BJ 430300.BJ 430139.BJ
cat("选中的股票名称:", selected_names, "\n")
## 选中的股票名称: 同辉信息 诺思兰德 辰光医疗 华岭股份
# 提取选中股票的价格数据
price_data <- data_raw[, c("Date", selected_stocks)]
# 计算日收益率(对数收益率)
prices <- as.matrix(price_data[, -1])
rownames(prices) <- as.character(price_data$Date)
# 计算对数收益率
returns <- diff(log(prices))
returns <- na.omit(returns)
# 转换为timeSeries对象
returns_ts <- as.timeSeries(returns)
colnames(returns_ts) <- selected_names
# 查看收益率统计
summary(returns_ts)
## Start Record: 2022-01-05
## End Record: 2023-04-14
## Observations: 309
## Format: %Y-%m-%d
## FinCenter: GMT
##
## 同辉信息 诺思兰德 辰光医疗
## Min. :-0.263523 Min. :-0.176798 Min. :-0.078953
## 1st Qu.:-0.016208 1st Qu.:-0.016473 1st Qu.:-0.004819
## Median :-0.003578 Median :-0.002001 Median : 0.000000
## Mean :-0.004259 Mean :-0.002291 Mean :-0.001261
## 3rd Qu.: 0.007859 3rd Qu.: 0.010874 3rd Qu.: 0.000000
## Max. : 0.228641 Max. : 0.182153 Max. : 0.110785
## 华岭股份
## Min. :-0.212120
## 1st Qu.:-0.008149
## Median : 0.000000
## Mean :-0.002133
## 3rd Qu.: 0.001889
## Max. : 0.172158
# 设置投资组合规范
spec <- portfolioSpec()
setRiskFreeRate(spec) <- 0.02 / 252 # 假设年化无风险利率2%
# 计算最小风险(最小方差)投资组合
minvar_portfolio <- minvariancePortfolio(returns_ts, spec, constraints = "LongOnly")
# 提取权重
minvar_weights <- getWeights(minvar_portfolio)
names(minvar_weights) <- selected_names
cat("\n===== 最小风险投资组合 =====\n")
##
## ===== 最小风险投资组合 =====
cat("股票权重:\n")
## 股票权重:
print(round(minvar_weights, 4))
## 同辉信息 诺思兰德 辰光医疗 华岭股份
## 0.0569 0.1225 0.5107 0.3098
cat("\n组合预期日收益率:", round(getTargetReturn(minvar_portfolio@portfolio)["mean"], 6), "\n")
##
## 组合预期日收益率: -0.001828
cat("组合风险(标准差):", round(getTargetRisk(minvar_portfolio@portfolio)["Cov"], 6), "\n")
## 组合风险(标准差): 0.014737
# 最小风险权重直方图
weights_df <- data.frame(
Stock = selected_names,
Weight = as.numeric(minvar_weights)
)
ggplot(weights_df, aes(x = Stock, y = Weight, fill = Stock)) +
geom_bar(stat = "identity", width = 0.7) +
geom_text(aes(label = paste0(round(Weight*100, 2), "%")),
vjust = -0.5, size = 4) +
scale_y_continuous(labels = scales::percent, limits = c(0, max(minvar_weights)*1.2)) +
labs(title = "最小风险投资组合权重分布",
subtitle = paste("学号后四位处理结果: 2,0,5,0 → 2,1,5,3"),
x = "股票",
y = "权重") +
theme_minimal() +
theme(legend.position = "none",
plot.title = element_text(size = 14, face = "bold"),
axis.text.x = element_text(angle = 45, hjust = 1))
# 计算最大回报投资组合
# 首先计算各股票的平均收益率
mean_returns <- colMeans(returns_ts)
max_return_idx <- which.max(mean_returns)
# 最大回报组合:将所有权重放在收益率最高的股票上
maxret_weights <- rep(0, length(selected_names))
maxret_weights[max_return_idx] <- 1
names(maxret_weights) <- selected_names
cat("\n===== 最大回报投资组合 =====\n")
##
## ===== 最大回报投资组合 =====
cat("股票权重:\n")
## 股票权重:
print(round(maxret_weights, 4))
## 同辉信息 诺思兰德 辰光医疗 华岭股份
## 0 0 1 0
cat("\n组合预期日收益率:", round(mean_returns[max_return_idx], 6), "\n")
##
## 组合预期日收益率: -0.001261
# 计算该组合的风险
maxret_risk <- sqrt(t(maxret_weights) %*% cov(returns_ts) %*% maxret_weights)
cat("组合风险(标准差):", round(maxret_risk, 6), "\n")
## 组合风险(标准差): 0.019418
# 最大回报权重直方图
weights_df_maxret <- data.frame(
Stock = selected_names,
Weight = as.numeric(maxret_weights)
)
ggplot(weights_df_maxret, aes(x = Stock, y = Weight, fill = Stock)) +
geom_bar(stat = "identity", width = 0.7) +
geom_text(data = weights_df_maxret,
aes(label = paste0(round(Weight*100, 2), "%")),
vjust = ifelse(weights_df_maxret$Weight > 0.5, 1.5, -0.5),
size = 4) +
scale_y_continuous(labels = scales::percent, limits = c(0, 1.1)) +
labs(title = "最大回报投资组合权重分布",
subtitle = paste("全部投资于:", selected_names[max_return_idx]),
x = "股票",
y = "权重") +
theme_minimal() +
theme(legend.position = "none",
plot.title = element_text(size = 14, face = "bold"),
axis.text.x = element_text(angle = 45, hjust = 1))
# 计算最大夏普比率(切线)投资组合
tangency_portfolio <- tangencyPortfolio(returns_ts, spec, constraints = "LongOnly")
# 提取权重
tangency_weights <- getWeights(tangency_portfolio)
names(tangency_weights) <- selected_names
cat("\n===== 最大夏普比率投资组合(切线组合)=====\n")
##
## ===== 最大夏普比率投资组合(切线组合)=====
cat("股票权重:\n")
## 股票权重:
print(round(tangency_weights, 4))
## 同辉信息 诺思兰德 辰光医疗 华岭股份
## 1 0 0 0
cat("\n组合预期日收益率:", round(getTargetReturn(tangency_portfolio@portfolio)["mean"], 6), "\n")
##
## 组合预期日收益率: -0.004259
cat("组合风险(标准差):", round(getTargetRisk(tangency_portfolio@portfolio)["Cov"], 6), "\n")
## 组合风险(标准差): 0.034952
# 计算夏普比率
rf_daily <- 0.02 / 252
sharpe_ratio <- (getTargetReturn(tangency_portfolio@portfolio)["mean"] - rf_daily) /
getTargetRisk(tangency_portfolio@portfolio)["Cov"]
cat("夏普比率:", round(sharpe_ratio, 4), "\n")
## 夏普比率: -0.1241
# 最大夏普比率权重直方图
weights_df_sharpe <- data.frame(
Stock = selected_names,
Weight = as.numeric(tangency_weights)
)
ggplot(weights_df_sharpe, aes(x = Stock, y = Weight, fill = Stock)) +
geom_bar(stat = "identity", width = 0.7) +
geom_text(aes(label = paste0(round(Weight*100, 2), "%")),
vjust = -0.5, size = 4) +
scale_y_continuous(labels = scales::percent,
limits = c(0, max(tangency_weights)*1.2)) +
labs(title = "最大夏普比率投资组合权重分布",
subtitle = paste("夏普比率:", round(sharpe_ratio, 4)),
x = "股票",
y = "权重") +
theme_minimal() +
theme(legend.position = "none",
plot.title = element_text(size = 14, face = "bold"),
axis.text.x = element_text(angle = 45, hjust = 1))
# 计算有效前沿
frontier <- portfolioFrontier(returns_ts, spec, constraints = "LongOnly")
# 提取有效前沿的点 - 修复:检查实际的列名
frontier_points <- frontierPoints(frontier, frontier = "both")
print("有效前沿点的列名:")
## [1] "有效前沿点的列名:"
print(colnames(frontier_points))
## [1] "targetRisk" "targetReturn"
# 根据实际的列名提取数据(可能是 "targetRisk" 和 "targetReturn")
if("targetRisk" %in% colnames(frontier_points)) {
frontier_risk <- frontier_points[, "targetRisk"]
frontier_return <- frontier_points[, "targetReturn"]
} else if("sigma" %in% colnames(frontier_points)) {
frontier_risk <- frontier_points[, "sigma"]
frontier_return <- frontier_points[, "mu"]
} else {
# 如果没有列名,直接使用第一列和第二列
frontier_risk <- frontier_points[, 1]
frontier_return <- frontier_points[, 2]
}
# 获取三个特殊点的坐标
minvar_risk <- getTargetRisk(minvar_portfolio@portfolio)["Cov"]
minvar_return <- getTargetReturn(minvar_portfolio@portfolio)["mean"]
maxret_risk_val <- as.numeric(maxret_risk)
maxret_return_val <- mean_returns[max_return_idx]
tangency_risk <- getTargetRisk(tangency_portfolio@portfolio)["Cov"]
tangency_return <- getTargetReturn(tangency_portfolio@portfolio)["mean"]
cat("\n===== 三个关键投资组合坐标 =====\n")
##
## ===== 三个关键投资组合坐标 =====
cat("最小风险点: 风险 =", round(minvar_risk, 6), ", 收益 =", round(minvar_return, 6), "\n")
## 最小风险点: 风险 = 0.014737 , 收益 = -0.001828
cat("最大回报点: 风险 =", round(maxret_risk_val, 6), ", 收益 =", round(maxret_return_val, 6), "\n")
## 最大回报点: 风险 = 0.019418 , 收益 = -0.001261
cat("最大夏普比率点: 风险 =", round(tangency_risk, 6), ", 收益 =", round(tangency_return, 6), "\n")
## 最大夏普比率点: 风险 = 0.034952 , 收益 = -0.004259
# 创建有效前沿图
efficient_df <- data.frame(
Risk = frontier_risk,
Return = frontier_return
)
# 创建特殊点数据框
special_points <- data.frame(
Risk = c(minvar_risk, maxret_risk_val, tangency_risk),
Return = c(minvar_return, maxret_return_val, tangency_return),
Type = c("最小风险", "最大回报", "最大夏普比率"),
Color = c("red", "blue", "green")
)
# 绘制有效前沿
ggplot() +
# 有效前沿线
geom_line(data = efficient_df, aes(x = Risk, y = Return),
color = "darkblue", linewidth = 1.2) +
# 有效前沿点
geom_point(data = efficient_df, aes(x = Risk, y = Return),
color = "lightblue", size = 2, alpha = 0.6) +
# 特殊点
geom_point(data = special_points, aes(x = Risk, y = Return, color = Type),
size = 5, shape = 18) +
# 添加标签
geom_text(data = special_points,
aes(x = Risk, y = Return, label = Type, color = Type),
vjust = -1.2, size = 4, fontface = "bold") +
# 颜色设置
scale_color_manual(values = c("最小风险" = "red",
"最大回报" = "blue",
"最大夏普比率" = "green")) +
# 标签和标题
labs(title = "投资组合有效前沿",
subtitle = paste("选中股票:", paste(selected_names, collapse = ", ")),
x = "风险 (标准差)",
y = "预期收益",
color = "投资组合类型") +
theme_minimal() +
theme(
plot.title = element_text(size = 16, face = "bold", hjust = 0.5),
plot.subtitle = element_text(size = 12, hjust = 0.5),
axis.title = element_text(size = 12),
legend.position = "bottom",
legend.title = element_text(face = "bold")
)
# 创建结果汇总表
summary_table <- data.frame(
投资组合类型 = c("最小风险", "最大回报", "最大夏普比率"),
股票1_同辉信息 = c(round(minvar_weights[1], 4),
round(maxret_weights[1], 4),
round(tangency_weights[1], 4)),
股票2_诺思兰德 = c(round(minvar_weights[2], 4),
round(maxret_weights[2], 4),
round(tangency_weights[2], 4)),
股票3_辰光医疗 = c(round(minvar_weights[3], 4),
round(maxret_weights[3], 4),
round(tangency_weights[3], 4)),
股票4_华岭股份 = c(round(minvar_weights[4], 4),
round(maxret_weights[4], 4),
round(tangency_weights[4], 4)),
预期收益 = c(round(minvar_return, 6),
round(maxret_return_val, 6),
round(tangency_return, 6)),
风险_标准差 = c(round(minvar_risk, 6),
round(maxret_risk_val, 6),
round(tangency_risk, 6))
)
print(summary_table, row.names = FALSE)
## 投资组合类型 股票1_同辉信息 股票2_诺思兰德 股票3_辰光医疗 股票4_华岭股份
## 最小风险 0.0569 0.1225 0.5107 0.3098
## 最大回报 0.0000 0.0000 1.0000 0.0000
## 最大夏普比率 1.0000 0.0000 0.0000 0.0000
## 预期收益 风险_标准差
## -0.001828 0.014737
## -0.001261 0.019418
## -0.004259 0.034952
# 三种投资组合权重对比图
comparison_df <- data.frame(
Stock = rep(selected_names, 3),
Weight = c(minvar_weights, maxret_weights, tangency_weights),
Portfolio = rep(c("最小风险", "最大回报", "最大夏普比率"), each = 4)
)
ggplot(comparison_df, aes(x = Stock, y = Weight, fill = Portfolio)) +
geom_bar(stat = "identity", position = "dodge", width = 0.7) +
scale_y_continuous(labels = scales::percent) +
scale_fill_manual(values = c("最小风险" = "red",
"最大回报" = "blue",
"最大夏普比率" = "green")) +
labs(title = "三种投资组合权重对比",
x = "股票",
y = "权重",
fill = "投资组合类型") +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "bottom"
)
说明: