基于R语言的序列二次规划求解等风险贡献(ERC)组合最优权重
用R语言实现等风险贡献(ERC)投资组合权重求解
嘿,我来一步步教你用R实现等风险贡献(ERC)投资组合的权重求解——Maillard、Roncalli和Teiletche提出的这个方法,核心就是让每个资产对组合的总风险贡献完全相等,比均值方差模型更侧重风险分散,实用性很强!
先明确ERC的核心逻辑
等风险贡献组合的核心约束是:每个资产的**风险贡献(Risk Contribution, RC)**相等。风险贡献的计算公式是:
$RC_i = x_i \cdot (Σx)i / \sqrt{xTΣx}$,其中$Σ$是资产的协方差矩阵,$x$是权重向量,$\sqrt{xTΣx}$是组合的总风险。
我们的优化目标是最小化各资产风险贡献的差异,等价于求解如下问题:
$$\min_x \sum{i=1}^N (RC_i - \frac{TotalRisk}{N})^2$$
约束条件:
- $\sum_{i=1}^N x_i = 1$(权重和为1)
- $x_i \geq 0$(仅做多,若允许做空可去掉此约束)
R代码实现步骤
我们用nloptr包的**序列二次规划(SQP)**算法来求解,它非常适合处理这种带非线性约束的优化问题。
1. 安装并加载必要的包
# 首次运行先安装包 install.packages(c("nloptr", "PerformanceAnalytics")) # 加载包 library(nloptr) library(PerformanceAnalytics)
2. 准备数据(示例或自有数据)
这里我们生成模拟的资产收益率数据,你也可以替换成自己的真实收益率矩阵:
# 设置随机种子保证结果可复现 set.seed(123) # 生成5个资产、100个交易日的收益率数据 returns <- matrix(rnorm(5*100, mean=0.001, sd=0.02), ncol=5) colnames(returns) <- paste0("Asset_", 1:5) # 计算资产协方差矩阵(ERC的核心输入) Sigma <- cov(returns)
3. 定义优化目标函数
目标是最小化各资产风险贡献的平方差:
erc_objective <- function(x, Sigma) { n_assets <- length(x) # 计算组合总风险 total_risk <- sqrt(t(x) %*% Sigma %*% x) # 计算每个资产的风险贡献 risk_contributions <- x * (Sigma %*% x) / total_risk # 目标风险贡献:总风险平均分配给每个资产 target_rc <- total_risk / n_assets # 返回风险贡献与目标值的平方差之和 sum((risk_contributions - target_rc)^2) }
4. 定义目标函数的梯度(加速收敛)
为了让SQP算法更快收敛,我们手动定义目标函数的梯度(推导过程省略,直接用现成的公式):
erc_gradient <- function(x, Sigma) { n_assets <- length(x) total_risk <- sqrt(t(x) %*% Sigma %*% x) sigma_x <- Sigma %*% x risk_contributions <- x * sigma_x / total_risk target_rc <- total_risk / n_assets grad <- rep(0, n_assets) for (i in 1:n_assets) { # 梯度计算的核心项 term1 <- 2 * (risk_contributions[i] - target_rc) * (sigma_x[i]/total_risk + x[i]*(Sigma[i,]%*%x)/total_risk^2) term2 <- 2 * (risk_contributions[i] - target_rc) * (-x[i]*sigma_x[i]/(2*total_risk^3)) * (2*t(x)%*%Sigma) grad[i] <- term1 + term2[i] } return(grad) }
5. 设置约束条件
我们需要两个约束:权重和为1(等式约束),以及权重非负(仅做多的不等式约束):
# 等式约束:权重和为1 eq_constraint <- function(x) { return(sum(x) - 1) } # 等式约束的梯度 eq_gradient <- function(x) { return(rep(1, length(x))) } # 不等式约束:权重非负(仅做多) ineq_constraint <- function(x) { return(x) } # 不等式约束的梯度 ineq_gradient <- function(x) { return(diag(length(x))) }
6. 运行SQP优化求解
# 初始权重:用等权重作为起点,收敛更快 init_weights <- rep(1/ncol(Sigma), ncol(Sigma)) # 设置优化选项 opts <- list( "algorithm" = "NLOPT_LD_SLSQP", # 序列二次规划算法 "xtol_rel" = 1e-8, # 收敛精度 "maxeval" = 1000, # 最大迭代次数 "print_level" = 1 # 打印迭代过程(0为不打印) ) # 执行优化 result <- nloptr( x0 = init_weights, eval_f = erc_objective, eval_grad_f = erc_gradient, eval_g_eq = eq_constraint, eval_jac_g_eq = eq_gradient, eval_g_ineq = ineq_constraint, eval_jac_g_ineq = ineq_gradient, opts = opts, Sigma = Sigma ) # 查看优化结果 cat("优化状态码:", result$status, "\n") cat("最优ERC权重:\n") erc_weights <- result$solution names(erc_weights) <- colnames(Sigma) print(round(erc_weights, 4)) # 验证风险贡献是否相等 total_risk <- sqrt(t(erc_weights) %*% Sigma %*% erc_weights) final_rc <- erc_weights * (Sigma %*% erc_weights) / total_risk cat("\n各资产风险贡献:\n") print(round(final_rc, 6))
关键说明
- 算法选择:
NLOPT_LD_SLSQP是nloptr中专门的序列二次规划实现,完美适配ERC的非线性约束优化需求。 - 做空设置:如果允许做空,只需要去掉
eval_g_ineq和eval_jac_g_ineq这两个参数即可。 - 结果验证:运行完后你会看到,各资产的风险贡献非常接近(误差在1e-6级别),完全符合ERC的要求。
内容的提问来源于stack exchange,提问作者ChicagoCubs




