CVaR与均值-方差、GMVP、切点组合的Matlab对比实现

CVaR与均值-方差、GMVP、切点组合的Matlab对比实现 做资产配置研究的人多多少少都遇到过这样一个尴尬时刻均值-方差模型跑出来的权重非常极端稍微调整一下预期收益的输入仓位就从满仓A资产变成做空A资产让人根本不敢照着下单。后来我把CVaR条件风险价值、均值-方差、全局最小方差组合GMVP、以及切点组合市场组合的实证代理放在同一套数据下逐一对比才发现问题不是模型本身而是我们往往只盯着一个优化目标忽略了不同风险度量方式背后的决策逻辑差异。这篇博文就围绕这套对比展开从原理到Matlab代码逐步说清楚每个组合到底在优化什么、结果在风险收益平面上的位置有何不同、样本外和极端行情下谁更稳、实操中又应该注意哪些坑。适合正在做组合优化研究、或者刚接触CVaR但不知道如何落地的朋友参考。1. 为什么传统均值-方差框架在极端行情下不够用1.1 标准差不是投资者真正担心的那类风险均值-方差模型是Markowitz在1952年提出的经典框架用收益率的方差或标准差来度量风险逻辑非常漂亮给定目标收益找到方差最小的权重组合。但落地久了你会发现标准差这个指标有一个天然缺陷——它对上行波动和下行波动一视同仁。现实中的投资者并不会因为资产某个月涨了10%而焦虑真正让人睡不着的是那种单月下跌15%、连续回撤超过20%的情形。标准差把两种波动混在一起算相当于把好事和坏事放在同一杆秤上称这在收益分布近似对称时问题不大一旦遇到厚尾分布金融资产收益率的典型特征标准差就会低估尾部风险。2008年全球金融危机期间很多看似分散的资产组合在几天之内同步暴跌相关性趋近于1。你用均值-方差模型优化出来的低风险组合在那种极端行情下完全失灵原因就是方差度量本身没有对尾部损失赋予足够的惩罚权重。1.2 CVaR与VaR的关系风险管理视角下的补位CVaRConditional Value at Risk解决的就是这个问题。理解CVaR之前先要理解VaRValue at Risk。VaR回答的问题是在α的置信水平下组合的最大可能损失阈值是多少比如一个组合的95% VaR是-5%意思是95%的概率下损失不会超过5%。但VaR有一个著名的缺陷它只看阈值本身不管阈值之外发生了什么。如果最坏情况下损失是-6%还是-60%VaR给出的数值完全一样——这显然不符合风险管理的直觉。CVaR弥补了这个缺陷它衡量的是损失超过VaR阈值之后的平均损失。继续上面的例子如果你的组合在5%的尾部情景下平均损失是-18%那么这个-18%就是CVaR。它回答的是一旦坏事情发生平均会坏到什么程度。对组合优化来说CVaR作为目标函数还有一个非常重要的数学性质——它是凸函数而且可以在情景模拟下转化为线性规划问题求解。这个性质比VaR好用得多因为VaR虽然也是风险度量但它不是一致性风险度量直接拿VaR做优化很容易陷入非凸优化问题求解困难且结果不稳定。这也是CVaR在学术研究和实务中被广泛采用的核心原因。1.3 GMVP和市场组合在比较中的特殊位置把GMVP和市场组合拉进来对比不是为了凑数而是因为它们代表了两类不同的基准逻辑。GMVP的权重只依赖协方差矩阵Σ完全不依赖预期收益率μ。这在实际操作中极其珍贵因为预期收益率是最难估计的参数——均值估计的标准误差大得离谱而协方差矩阵的相对估计要稳定得多。用GMVP做基准相当于回答如果我完全不对未来收益做预测只看风险结构我应该怎么配。市场组合在理论上是个非常强的概念在CAPM的假设下市场组合是所有投资者按市值加权持有的组合也是切点组合Tangency Portfolio。但在实证中真正的市场组合无法观测到常见的做法是用宽基指数收益率或者切点组合来代理。切点组合的权重计算需要知道无风险利率rf目标是最大化夏普比率。它是收益-风险权衡的代表性答案。把CVaR组合、均值-方差组合、GMVP和切点组合放在同一张风险收益图上对比你能直观地看到一个关键事实由于四者优化的目标函数不同它们给出的权重可能截然不同但并不意味着哪个是错的而是它们各自回答的决策问题不一样。接下来的章节我会先把数学逻辑讲透再上Matlab代码。2. 四个模型背后的数学逻辑与优化问题表述2.1 均值-方差框架的解析解与有效前沿均值-方差优化的标准形式是在组合预期收益等于某个目标值μ_p的约束下最小化组合方差。写成数学式子是min_w (1/2) wΣw s.t. w1 1 wμ μ_p其中w是n维权重向量Σ是协方差矩阵μ是预期收益向量1是元素全为1的列向量。这个问题的解可以通过拉格朗日乘子法得到解析解。构造拉格朗日函数L (1/2) wΣw - λ1(w1 - 1) - λ2(wμ - μ_p)对w求导并令其为零得到Σw - λ1·1 - λ2·μ 0 → w λ1·Σ⁻¹·1 λ2·Σ⁻¹·μ再代入两个约束条件就能解出λ1和λ2。定义三个标量A 1Σ⁻¹1 B 1Σ⁻¹μ C μΣ⁻¹μ代入约束后可以解得λ1 (C - B·μ_p) / (A·C - B²) λ2 (A·μ_p - B) / (A·C - B²)也就是说任意一个目标收益μ_p对应的最小方差组合权重都可以直接通过矩阵运算算出来。遍历μ_p从最小值到最大值画在标准差-收益平面上就是经典的有效前沿。从解析解的表达式里你能发现一个非常重要的细节w是μ_p的线性函数。这意味着当μ_p变化一点点权重w会沿着有效前沿连续移动但同样也意味着——如果μ的估计有误差整个前沿的位置和形态都会被扭曲权重可能会剧烈变化。这正是均值-方差模型在实操中被诟病最多的点。2.2 GMVP的解析解与几何含义GMVP是有效前沿上最左边那个点也就是全局方差最小的组合。它的优化问题更简单不需要目标收益约束min_w (1/2) wΣw s.t. w1 1同样用拉格朗日法可以得到w_gmvp (Σ⁻¹·1) / (1Σ⁻¹·1)从公式可以清晰看到GMVP的权重完全不涉及μ。这意味着即使你对未来收益的判断完全错误GMVP的方差仍然是最小的。几何上GMVP位于有效前沿的顶点是所有可行组合中波动率最低的。但GMVP也有自己的问题它放弃了收益目标所以组合期望收益完全由市场环境决定——如果所有资产的预期收益都很低GMVP会在低收益水平上实现最小波动而不是帮你追求更高的收益。此外GMVP对协方差矩阵的依赖仍然存在如果Σ估计不准它的最小方差保证也会打折扣只是敏感度远低于均值-方差模型对μ的敏感度。2.3 切点组合作为市场组合的实证代理在引入无风险资产的前提下最优风险组合变成了有效前沿与从无风险利率出发的射线的切点。这个切点组合在理论上等价于市场组合——所有投资者持有相同的风险资产组合再根据个人风险偏好搭配无风险资产。切点组合的权重公式是w_tan Σ⁻¹·(μ - rf·1) / (1Σ⁻¹·(μ - rf·1))其中rf是无风险利率。这个组合的显著特征是最大化夏普比率(wμ - rf) / √(wΣw)。在Matlab代码实现中切点组合的计算成本很低先用样本数据估计μ和Σ再代入上述公式即可。但要特别注意切点组合对μ的敏感度和均值-方差组合差不多都对预期收益估计误差非常敏感。一个常见做法是给μ施加一些收缩shrinkage或者用贝叶斯估计来处理这在实际项目中非常有效。用rf0计算的切点组合习惯上也可以被理解为最大夏普比率组合在比较中可以作为市场组合代理的一个可选形式。如果手头有宽基指数比如沪深300、标普500的收益率序列也可以直接把指数当作市场组合参与比较这取决于你手上有什么数据。2.4 CVaR优化与线性规划转化CVaR的数学定义比较抽象但优化的时候有一个非常重要的结论。Rockafellar和Uryasev在2000年证明了CVaR可以通过一个辅助优化问题来计算在样本情景下CVaR的最小化可以等价于一个线性规划。给定T个历史或模拟情景第k个情景下组合损失为L_k(w) -wr_k其中r_k是第k个情景的收益向量。那么置信水平α下的CVaR可以这样转化CVaR_α(w) min_β { β (1/((1-α)·T)) · Σ_{k1}^{T} max(0, -wr_k - β) }这里的β就是VaR的估计值max(0, -wr_k - β)表示第k个情景下损失超过VaR的部分。为了把这个目标函数改造成线性规划需要引入T个非负松弛变量z_k令z_k ≥ -wr_k - β同时z_k ≥ 0。于是CVaR最小化的完整线性规划形式为min_{w,β,z} β (1/((1-α)·T)) · Σ z_k s.t. z_k ≥ -wr_k - β, k1,...,T z_k ≥ 0, k1,...,T w1 1 wμ μ_p可选如果不加收益约束就是最小CVaR组合 w ∈ 可行域这个转化是整个CVaR优化的核心步骤。为什么这个转化意义重大因为线性规划问题的求解器已经非常成熟即使决策变量数量很多n 1 T个linprog在Matlab中也能秒解。不夸张地说CVaR优化是尾部风险入门的钥匙——一旦掌握这个线性规划构造方法你就可以给组合加上各种现实约束权重上下界、行业暴露限制等都在线性规划框架内解决。需要提醒的是CVaR的目标函数中隐含了α的选择α0.95表示关注的是最差5%情景下的平均损失α0.99则更保守。α越大CvaR优化对尾部越敏感但也需要越多的情景数据来支撑估计的稳定性。3. Matlab完整实现从数据到四类组合3.1 组织代码结构的思路写Matlab代码之前先讲一下整体结构设计。这段代码的目的是做模型对比不是做一个生产级的投资系统所以重点是模块清晰、公式透明、容易二次修改。我的建议是拆成三部分主脚本负责生成或加载数据、设置参数、调用各优化函数、汇总结果并绘图。优化函数均值-方差、GMVP、切点组合、CVaR分别封装成独立函数。绩效与绘图计算收益、波动率、历史CVaR并画出有效前沿和组合位置。这样组织的好处是你后续想换掉数据源、改置信水平、增加约束都只需要动局部代码不会把整个脚本搅乱。此外我不建议一上来就使用cvx等第三方工具箱因为用Matlab自带的quadprog和linprog你才能清楚地看到每个约束矩阵是怎么构造出来的——这对理解模型本身非常有帮助。模拟数据用mvnrnd生成随机种子固定为42保证结果可复现。如果你有真实数据只需要把returns换成自己的T×n矩阵即可后面的流程完全不需要改动。3.2 均值-方差与GMVP的解析解实现先看均值-方差优化函数直接实现前面的解析解公式function [w, mu_p, sigma_p] mean_variance_opt(mu, Sigma, target_ret) % 均值-方差优化给定目标收益返回最小方差权重 % 输入: % mu - n x 1 预期收益向量 % Sigma - n x n 协方差矩阵 % target_ret - 目标收益 % 输出: % w - n x 1 权重向量 % mu_p - 组合期望收益 % sigma_p - 组合波动率 n length(mu); one ones(n, 1); invSigma inv(Sigma); A one * invSigma * one; B one * invSigma * mu; C mu * invSigma * mu; Delta A * C - B^2; lambda1 (C - B * target_ret) / Delta; lambda2 (A * target_ret - B) / Delta; w lambda1 * invSigma * one lambda2 * invSigma * mu; mu_p w * mu; sigma_p sqrt(w * Sigma * w); end这个解析解形式适用于允许卖空的情况权重可以为负且没有权重上下界约束。如果你不希望组合中出现负权重就得改用quadprog做带约束优化。后面第5章会专门讨论这个问题。GMVP的实现更简洁function [w, mu_p, sigma_p] gmvp_opt(Sigma) % 全局最小方差组合 n size(Sigma, 1); one ones(n, 1); invSigma inv(Sigma); w invSigma * one / (one * invSigma * one); mu_p w * mean_portfolio_return_placeholder; % 需要在主脚本中传入mu sigma_p sqrt(w * Sigma * w); end严格来说GMVP不需要μ但如果要计算组合期望收益还是需要外部传入μ。所以我更推荐把函数定义改成接收可选参数的形式function [w, mu_p, sigma_p] gmvp_opt(Sigma, mu) n size(Sigma, 1); one ones(n, 1); invSigma inv(Sigma); w invSigma * one / (one * invSigma * one); if nargin 2 ~isempty(mu) mu_p w * mu; else mu_p NaN; end sigma_p sqrt(w * Sigma * w); end这样灵活性更好统计绩效时不会因为缺了μ而中断。3.3 CVaR线性规划的实现细节CVaR优化这块是最容易写错的我直接给出完整函数function [w, cvar_val, beta_opt] cvar_opt(returns, alpha, target_ret) % CVaR优化最小化给定目标收益约束下的CVaR % 输入: % returns - T x n 历史收益矩阵每行一个时期每列一个资产 % alpha - 置信水平如0.95 % target_ret - 目标收益可设为[]表示不设收益约束 % 输出: % w - n x 1 权重向量 % cvar_val - 最小化后的CVaR值损失负数表示亏损 % beta_opt - VaR阈值估计 [T, n] size(returns); mu mean(returns); % 决策变量: [w(n); beta(1); z(T)] % 目标 f*x f [zeros(n, 1); 1; ones(T, 1) / ((1 - alpha) * T)]; % 不等式约束 A*x b % 1) z_k -returns(k,:)*w - beta % 即 -returns(k,:)*w - beta - z_k 0 % 2) z_k 0 % 即 -z_k 0 A [-returns, -ones(T, 1), -eye(T); zeros(T, n), zeros(T, 1), -eye(T)]; b zeros(2 * T, 1); % 等式约束 % 权重之和为1 Aeq [ones(1, n), 0, zeros(1, T)]; beq 1; % 如果设置了目标收益加上 w*mu target_ret if ~isempty(target_ret) Aeq [Aeq; mu, 0, zeros(1, T)]; beq [beq; target_ret]; end % 变量边界: w和beta无界z0 lb [-inf(n, 1); -inf; zeros(T, 1)]; ub []; options optimoptions(linprog, Display, off); [x, fval] linprog(f, A, b, Aeq, beq, lb, ub, options); w x(1:n); beta_opt x(n 1); cvar_val fval; end这段代码有几个地方要特别留意第一约束矩阵A的构造。前T行对应z_k ≥ -wr_k - β写成线性不等式是-r_kw - β - z_k ≤ 0。由于returns是T×n矩阵-returns直接就是所有r_k的负值组成的矩阵每一行乘上决策向量中的w部分就得到-r_kw。-ones(T,1)对应-β项-eye(T)对应-z_k项。后T行是-z_k ≤ 0因为z_k非负。第二决策变量的顺序必须和f、A、Aeq、lb完全一致。我统一用[w; beta; z]在代码里反复核对维度w占n个位置beta占1个位置z占T个位置总长度是n1T。一旦顺序错位linprog会报维度错误或者执行错误。第三linprog默认求解的是最小化fx的问题所以CVaR目标函数直接放进f即可不需要加负号。第四如果不设目标收益约束得到的就是全局最小CVaR组合。这类似于GMVP在均值-方差世界里的角色——完全不考虑收益水平只追求尾部风险最小化。3.4 切点组合与绩效回测函数切点组合的实现和均值-方差、GMVP类似直接按公式来function [w, mu_p, sigma_p, sharpe] tangency_opt(mu, Sigma, rf) % 切点组合最大Sharpe比率组合 n length(mu); one ones(n, 1); invSigma inv(Sigma); w invSigma * (mu - rf * one) / (one * invSigma * (mu - rf * one)); mu_p w * mu; sigma_p sqrt(w * Sigma * w); sharpe (mu_p - rf) / sigma_p; end这里要注意切点组合要求超额收益向量μ - rf·1中至少有一部分为正否则求出来的权重会非常奇怪甚至无意义。遇到这种情况通常需要重新审视输入参数或者降低rf。绩效计算还需要一个历史CVaR函数用于评估任意给定权重组合在样本数据上的实际尾部风险function cvar_val cvar_historical(returns, w, alpha) % 计算给定权重组合的历史CVaR losses -returns * w; var_level quantile(losses, alpha); cvar_val mean(losses(losses var_level)); end这个函数虽然简单但很有用。因为cvar_opt返回的fval是优化目标值它是带有β和松弛变量结构的估计值而cvar_historical是直接基于历史损失的经验CVaR两者应该非常接近。如果差异显著说明linprog收敛有问题或者约束构造有误这是一个很好的交叉验证手段。主脚本的骨架部分可以这样搭%% 参数设置 rng(42); % 固定随机种子保证可复现 n 10; % 资产数量 T 120; % 样本期数 alpha 0.95; % CVaR置信水平 rf 0.002; % 月度无风险利率 target_ret 0.010; % 目标收益 %% 生成模拟数据 mu_true 0.008 0.004 * rand(n, 1); sigma_true 0.04 0.06 * rand(n, 1); rho 0.3; Corr rho * ones(n, n) (1 - rho) * eye(n); Sigma_true diag(sigma_true) * Corr * diag(sigma_true); returns mvnrnd(mu_true, Sigma_true, T); %% 样本估计 mu_hat mean(returns); Sigma_hat cov(returns); %% 求解四个组合 [w_mv, mv_mu, mv_sigma] mean_variance_opt(mu_hat, Sigma_hat, target_ret); [w_gmvp, gmvp_mu, gmvp_sigma] gmvp_opt(Sigma_hat, mu_hat); [w_tan, tan_mu, tan_sigma, tan_sharpe] tangency_opt(mu_hat, Sigma_hat, rf); [w_cvar, cvar_val] cvar_opt(returns, alpha, target_ret); w_ew ones(n, 1) / n; % 等权组合做基准参考建议把等权组合也加上。等权组合在实务中永远是最朴素的基准很多花哨的策略长期跑不赢等权这不是一句玩笑话。运行完主脚本后你会得到一个基础结果四组权重向量、每个组合的期望收益和标准差。但只有这些还不够接下来就要做对比分析也就是第4章的内容。4. 不同市场环境下组合表现的真实对比4.1 样本内结果有效前沿图上的位置用模拟数据跑完代码后最直观的对比方式是把四个组合放在有效前沿图上。这里补一段画图代码%% 画有效前沿 mu_range linspace(0.006, 0.016, 60); front_sigma zeros(size(mu_range)); for i 1:length(mu_range) [~, ~, front_sigma(i)] mean_variance_opt(mu_hat, Sigma_hat, mu_range(i)); end figure(Position, [100, 100, 800, 600]); plot(front_sigma * 100, mu_range * 100, b-, LineWidth, 1.5); hold on; plot(sqrt(diag(Sigma_hat)) * 100, mu_hat * 100, k., MarkerSize, 12); plot(mv_sigma * 100, mv_mu * 100, r*, MarkerSize, 14, DisplayName, 均值-方差); plot(gmvp_sigma * 100, gmvp_mu * 100, go, MarkerSize, 10, DisplayName, GMVP); plot(tan_sigma * 100, tan_mu * 100, ms, MarkerSize, 12, DisplayName, 切点组合); plot(sqrt(w_cvar * Sigma_hat * w_cvar) * 100, w_cvar * mu_hat * 100, c^, MarkerSize, 12, DisplayName, CVaR); plot(sqrt(w_ew * Sigma_hat * w_ew) * 100, w_ew * mu_hat * 100, kd, MarkerSize, 10, DisplayName, 等权); xlabel(年化波动率 (%)); ylabel(年化收益 (%)); legend(Location, best); grid on;注意我这里用了月度数据但为了直观把月度波动率和收益率乘以√12和12换算成年化值如果你直接用日频数据年化方式类似x100只是为了显示方便不影响相对位置。这张图能清楚地展示几个事实第一均值-方差组合和CVaR组合都设定了相同的目标收益0.010所以它们在图上的纵坐标几乎相同但横坐标可能不同。如果两个组合的标准差非常接近说明在正态分布场景下两种风险度量的最优解基本重合如果标准差差异较大说明数据中存在明显的尾部特征CVaR优化选择了在方差意义上略差、但在尾部风险意义上更优的权重。第二GMVP一定位于有效前沿的最左端这是它的定义决定的。它的收益可能不高但波动率是所有组合中最低的。如果你的投资目标就是不求大涨、但求不跌GMVP是一个不错的选择。第三切点组合一定位于有效前沿上并且是从无风险利率点rf, 0出发的射线与前沿的切点理论上它的夏普比率最高。注意我这里的纵轴是0.010左右rf0.002射线斜率对应夏普比率。如果切点组合跑到了均值-方差组合的右边说明目标收益设高了两条约束下的解自然不同。第四等权组合通常不会落在有效前沿上它既不是方差最优也不是夏普最优只是一个朴素的分散化方案。但千万不要小看它——它是检验模型是否白忙一场的试金石。4.2 样本外测试不要被样本内数字骗了样本内的对比只能说明在拟合数据上谁更好不能说明真实投资中谁更优。更稳健的做法是做样本外测试用前80个月的数据估计参数并确定权重然后用后40个月的数据评估这些权重的实际表现。代码可以这样扩展%% 样本外测试 train_idx 1:80; test_idx 81:120; returns_train returns(train_idx, :); returns_test returns(test_idx, :); mu_tr mean(returns_train); Sigma_tr cov(returns_train); [w_mv_tr, ~, ~] mean_variance_opt(mu_tr, Sigma_tr, target_ret); [w_gmvp_tr, ~, ~] gmvp_opt(Sigma_tr, mu_tr); [w_tan_tr, ~, ~, ~] tangency_opt(mu_tr, Sigma_tr, rf); [w_cvar_tr, ~, ~] cvar_opt(returns_train, alpha, target_ret); % 在测试集上评估 models {w_mv_tr, w_gmvp_tr, w_tan_tr, w_cvar_tr, w_ew}; model_names {均值-方差, GMVP, 切点组合, CVaR, 等权}; test_perf zeros(length(models), 4); for i 1:length(models) w models{i}; test_perf(i, 1) mean(returns_test * w); % 样本外月均收益 test_perf(i, 2) std(returns_test * w); % 样本外波动率 test_perf(i, 3) cvar_historical(returns_test, w, alpha); % 样本外CVaR test_perf(i, 4) mean(returns_test * w) / std(returns_test * w); % 简化夏普 end % 打印结果 fprintf(%-10s %10s %10s %10s %10s\n, 模型, 月均收益, 波动率, CVaR, 夏普); for i 1:length(models) fprintf(%-10s %10.4f %10.4f %10.4f %10.4f\n, ... model_names{i}, test_perf(i,1), test_perf(i,2), test_perf(i,3), test_perf(i,4)); end样本外测试的结论往往和样本内不同。最常见的情况是样本内表现最好的均值-方差组合样本外可能大幅变差因为它对μ的拟合过度敏感而GMVP和CVaR组合的样本外结果通常更稳定。为什么因为GMVP只用协方差矩阵而CVaR虽然也用收益数据但它优化的是尾部损失的平均水平对极端收益赋予更高权重这反而让它在样本外对极端情景有一定缓冲能力。这不是理论上的绝对保证而是大量实证中观察到的规律。4.3 极端情景压力测试除了样本外测试压力测试更能看出CVaR组合的价值。做法很简单在测试集中人为加入一个暴跌月份比如某一列资产单月-20%然后看各组合的损失差异。%% 极端情景测试 shock_returns returns_test; shock_returns(1, 1) -0.20; % 第一个资产在第一期暴跌20% for i 1:length(models) w models{i}; loss_normal -returns_test(1, :) * w; loss_shock -shock_returns(1, :) * w; fprintf(%s 正常月份损失: %.4f, 冲击月份损失: %.4f\n, ... model_names{i}, loss_normal, loss_shock); end由于模拟数据用的是同一个协方差结构资产间存在正相关性一只资产暴跌通常意味着整个组合都受影响。在这个压力测试中CVaR组合通常展现出更小的极端损失因为它的优化过程已经对最差情景的平均损失做出了惩罚权重会天然地避开那些对尾部风险贡献大的资产。均值-方差组合呢因为它只惩罚方差对单只资产的极端暴跌不敏感尤其是当该资产整体波动率不算高时所以在压力测试中的表现往往不如CVaR组合。这就是两种风险度量在极端行情下的核心分歧。5. 实操中的五个坑与优化方向5.1 预期收益估计误差导致权重不稳这是均值-方差框架最大、最经典的坑。样本均值μ_hat的标准误差大约等于σ/√T月度数据120期的情况下即使σ只有5%标准误差也有0.46%/月而μ本身的量级可能只有0.8%~1.2%/月。这意味着你估计的预期收益率噪声比信号还要大。解决思路大致有三条收缩估计把μ_hat向一个先验值收缩比如全体资产均值或者等权组合的均值最常用的是James-Stein收缩。因子模型用CAPM或Fama-French因子模型给资产收益率建模降低个券特异性噪声。改用Black-Litterman模型把市场均衡收益作为先验再叠加主观观点得到更稳定的预期收益估计。如果你暂时不想引入复杂方法至少可以做一件事使用均值-方差优化时不要用单一目标收益而是扫描一个收益区间画出权重随目标收益变化的曲线。如果权重剧烈摆动说明结果不可靠需要先处理μ的估计问题。5.2 协方差矩阵奇异与收缩估计当资产数量n接近样本期数T时样本协方差矩阵Σ_hat可能接近奇异或直接奇异。最典型的场景是你有50只股票的日频收益数据只有60个交易日此时Σ_hat的秩最多为59无法求逆均值-方差和GMVP的解析解公式直接失效。解决办法包括使用Ledoit-Wolf收缩估计把样本协方差矩阵向单位矩阵收缩保证可逆性和正定性。用因子协方差结构代替全样本协方差。增加观测频率或者减少资产数量。在Matlab里如果你想快速实现Ledoit-Wolf收缩有一个开源函数covMarket可以下载也可以自己写一个简化版Σ_shrink δ·Σ_hat (1-δ)·Σ_target其中δ根据样本量确定。虽然简化版不完美但往往已经能解决奇异性问题。5.3 CVaR情景数量与置信水平的选择CVaR优化对情景数量T非常敏感。如果T只有30期那么α0.95时真正进入CVaR计算的只有1.5个情景估计噪声极大α0.99时更是只有0.3个情景几乎没法用。经验法则T至少要有几百期α0.95才比较稳α0.99至少需要上千期数据如果历史数据不够可以先用GARCH模型或蒙特卡洛模拟生成增广情景再对情景做CVaR优化。另外α的选择直接影响组合行为。α0.90对尾部没那么敏感权重分布更接近均值-方差α0.99则高度突出灾难防备逻辑组合可能会极端保守或集中持有少量低尾部风险资产。建议至少对比α0.90、0.95、0.99三组结果理解尾部的敏感度。5.4 权重约束允许卖空与禁止卖空的实际差异本文给出的解析解都允许负权重即允许卖空。但在实践中大多数投资者不能做空或者做空成本很高。加入w ≥ 0的约束后优化问题变成带约束优化均值-方差部分需要改用quadprog。以均值-方差为例%% 带权重下界的均值-方差优化 n length(mu_hat); H Sigma_hat; f_mv zeros(n, 1); Aeq [ones(1, n); mu_hat]; beq [1; target_ret]; lb zeros(n, 1); ub []; w_mv_lb quadprog(H, f_mv, [], [], Aeq, beq, lb, ub, [], optimoptions(quadprog, Display, off));CVaR的线性规划版本也类似只需把lb改成lb [zeros(n, 1); -inf; zeros(T, 1)];禁止卖空后组合的可行域变小有效前沿会缩短——低收益端的组合可能会被边界约束截断高收益端的组合也不再能达到那么高的收益。直观来说做空限制让组合少了翅膀但换来的是更符合投资逻辑的仓位。在你的对比分析中建议同时跑允许卖空与禁止卖空两个版本因为这两个版本的结论可能完全不同。很多时候CVaR和均值-方差的差异在禁止卖空后会明显缩小——因为权重边界约束本身就限制了极端头寸的出现尾部风险差异自然被压低了。5.5 再平衡频率与交易成本最后一个容易忽略的坑是再平衡频率。优化模型给出的是一组静态权重但市场在动资产在涨跌组合权重会不断漂移。如果不做再平衡组合的表现会偏离模型预期如果过于频繁再平衡交易成本会侵蚀收益。建议至少做两件事在绩效对比中加入换手率指标换手率 Σ |w_new - w_old|。均值-方差组合的换手率通常最高GMVP和CVaR组合则相对平稳。做月度或季度再平衡对比把优化权重按月度重新计算并交易扣除交易成本比如双边万分之五后比较净值曲线。组合优化不是一锤子买卖而是一个持续决策的过程。模型的真正价值在于给出一个决策框架而不是给出一个最终答案。就我个人的实测经验来说用CVaR做目标函数最大的收获不是收益更高而是组合权重更稳定、极端回撤更可控。均值-方差在样本内可以画出很漂亮的有效前沿但一旦跨出样本权重的不稳定性往往是致命的。建议拿到任何一组优化结果后先做一个Bootstrap扰动测试——对历史收益做有放回抽样重复估计和优化看权重分布的离散程度。如果权重在扰动下剧烈摆动再精妙的模型也难以落地。把这一点纳入你自己的研究流程可以少走很多弯路。