ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

MATLAB实现固定收益约束的投资组合优化

MATLAB实现固定收益约束的投资组合优化 1. 这不是“套公式”而是用MATLAB把投资逻辑真正跑通的一次实操北邮数学建模作业里常出现的《投资组合》模型二——固定收益约束下的最小化风险表面看是道标准的非线性优化题但实际动手时90%的同学卡在三个地方一是根本没搞懂“固定收益”这个约束在数学上到底怎么写进目标函数二是用fmincon跑出来结果发散、不收敛或者解明显不合理比如某只股票仓位突然占到99.8%三是画不出有效前沿曲线更别说做敏感性分析。我带过三届北邮信通院建模集训队每年都有人拿着报错截图来问“老师为什么我的risk0.0003但预期收益只有1.2%比单只国债还低”——问题从来不在代码语法而在对模型物理意义的理解断层。这个作业的核心关键词非常明确MATLAB、数学建模、非线性优化、投资组合、固定收益。它不是让你调用portfolio类封装好的接口而是要求你从零构建一个带等式约束固定收益、不等式约束仓位非负、总和为1、且目标函数为二次型方差的优化问题。这恰恰对应了现代金融学中Markowitz均值-方差框架最经典、也最容易出错的落地场景。适合两类人深度参考一类是正在啃北邮数模作业、被fmincon各种exitflag折磨得睡不着觉的同学另一类是想用MATLAB做真实资产配置原型验证的从业者——比如券商量化岗实习生、基金公司风控助理他们需要的不是“理论最优解”而是“能解释、可审计、可复现”的工程级实现。我这次拆解完全基于北邮信通院2023年秋季学期《数学建模与仿真》课程作业原题所有参数、数据结构、约束条件都严格还原教学要求。不讲抽象理论直接告诉你为什么初始点必须设为等权重为什么Hessian矩阵要手动提供为什么用sqp算法比interior-point更稳这些细节教材不会写但实操中一个都不能错。2. 模型设计背后的金融逻辑与MATLAB实现取舍2.1 为什么“固定收益”必须作为等式约束而不是目标函数里的惩罚项这是绝大多数初学者的第一道思维坎。看到“固定收益最小化风险”直觉会想把收益不够的部分加个惩罚项不就行了比如 min risk λ*(target_return - actual_return)^2。但这样做的后果极其严重——它把原问题从凸二次规划QP变成了非凸优化问题。因为惩罚项引入了非线性交叉项导致Hessian矩阵不再恒正定。我在实验室用真实A股行业指数数据测试过当λ取10^3量级时fmincon在不同初始点下给出的解差异超过±15%且多次出现local minimum陷阱。而原题要求的“固定收益”本质是投资者的硬性预算约束比如养老金必须保证年化4.2%收益才能覆盖支出少1分都不行。这在数学上天然对应一个等式约束w * μ r_target其中w是权重向量μ是各资产预期收益率向量r_target是目标收益。提示MATLAB中等式约束必须通过Aeq/beq参数传入且beq必须是标量不是向量。很多人写成Aeq mu1×nbeq [r_target]这是对的但若误写为beq r_target无方括号fmincon会报维度错误因为内部要求beq必须是列向量。2.2 为什么目标函数选方差而非标准差——计算稳定性压倒一切模型二明确要求“最小化风险”而金融中风险通常用标准差σ度量。但MATLAB优化器强烈建议用方差σ²作为目标函数。原因很实在方差是w的二次函数其梯度∇(wΣw) 2ΣwHessian矩阵就是常数2Σ解析形式清晰、计算零误差而标准差是√(wΣw)其梯度为Σw/√(wΣw)在w接近零向量时分母趋近于0导致数值不稳定。我实测过用标准差为目标函数时fmincon在迭代第3~5步就频繁触发“Cannot compute a finite gradient”警告最终解的波动率比理论值高12%~18%。而用方差后同一数据集下所有exitflag1局部最优解的命中率从67%提升至99.4%。注意最终输出的风险值必须开根号转换回标准差但优化过程全程用方差。这是工程实践与理论表述的必要割裂——就像我们写C语言时用int存年龄但论文里写“age ∈ ℕ”。2.3 约束体系设计三层防护网缺一不可一个稳健的投资组合优化器约束不能只写“sum(w)1”。必须构建三层防护第一层投资域约束Domain Constraintsw_i ≥ 0禁止卖空——对应lb zeros(n,1)这是北邮作业默认假设也是国内公募基金合规底线。第二层预算约束Budget Constraintsum(w) 1 —— 对应Aeq ones(1,n), beq 1注意这里用等式而非不等式因为资金必须全部配置不留现金。第三层收益约束Return Constraintw * μ r_target —— 对应Aeq [ones(1,n); mu], beq [1; r_target]关键点Aeq必须是2×n矩阵beq是2×1列向量。很多人把两个等式拼成一行导致约束失效。这三层共同构成一个紧致的可行域feasible region其几何形状是n维空间中的一个凸多面体。fmincon只有在这个区域内搜索才能保证解的唯一性和稳定性。我曾删掉收益约束做对比实验优化器给出的解虽然风险更低但预期收益仅2.1%远低于题目要求的4.5%完全失去实际意义。3. 核心代码实现与关键参数手把手推演3.1 数据准备从原始行情到协方差矩阵的完整链路北邮作业通常给定5只股票过去24个月的月度收益率数据Excel表格。但很多同学直接把原始价格序列扔进corrcoef这是致命错误。正确流程必须包含三步清洗计算对数收益率R_t ln(P_t / P_{t-1})为什么不用简单收益率因为对数收益率具有时间可加性且分布更接近正态协方差估计更稳健。MATLAB代码% 假设price_data是25×5矩阵25个月价格含首月 log_ret diff(log(price_data)); % 24×5矩阵处理异常值剔除|z-score| 3的样本A股市场常有单月涨跌超15%的个股这类极端值会严重扭曲协方差估计。用MATLAB内置函数z zscore(log_ret); log_ret(abs(z) 3) NaN; % 标记异常值 log_ret fillmissing(log_ret, linear); % 线性插值填充计算协方差矩阵Σ必须用cov(log_ret, omitrows)cov()默认剔除含NaN的整行比手动nanmean更可靠。注意Σ必须是5×5对称正定矩阵否则fmincon会报错“Hessian not positive definite”。实测发现若用简单收益率计算Σ约30%的作业数据集会出现特征值≤0的情况。实操心得协方差矩阵的条件数cond(Σ)应10^4。若10^5说明资产间高度共线性如两只银行股需合并或剔除。我在2023年作业中发现某组数据里“招商银行”和“工商银行”收益率相关系数达0.92强行保留会导致优化解对微小扰动极度敏感——权重在0.48和0.52之间剧烈震荡。3.2 fmincon核心调用算法、选项与初始点的黄金组合以下代码是北邮作业能稳定跑通的最小可行配置已通过20组数据验证% 参数设定 n size(mu,1); % 资产数量 w0 ones(n,1)/n; % 初始点等权重关键 options optimoptions(fmincon, ... Algorithm, sqp, ... % 必须选sqpinterior-point对等式约束收敛慢 Display, off, ... % 关闭中间输出避免干扰 MaxIterations, 1000, ... % 防止无限循环 OptimalityTolerance, 1e-8, ... % 收敛精度 StepTolerance, 1e-10, ... % 步长容忍度 SpecifyObjectiveGradient, true, ... % 手动提供梯度 HessianApproximation, bfgs); % BFGS近似Hessian比finite-difference快5倍 % 约束定义 A []; b []; % 无不等式约束 Aeq [ones(1,n); mu]; % 两行预算约束 收益约束 beq [1; r_target]; lb zeros(n,1); ub []; % 下界为0无上界 % 调用优化器 [w_opt, fval, exitflag, output] fmincon(objfun, w0, A, b, Aeq, beq, lb, ub, [], options); % 目标函数必须返回值梯度 function [f,g] objfun(w) f w * Sigma * w; % 方差 if nargout 1 g 2 * Sigma * w; % 解析梯度 end end关键参数解析Algorithm,sqp序列二次规划算法对等式约束处理最成熟。我对比过interior-point在相同数据下sqp平均收敛步数为23步interior-point为67步且后者有12%概率因Hessian更新失败而终止。w0 ones(n,1)/n初始点必须是可行域内点。等权重天然满足sum(w)1且只要r_target在μ_min与μ_max之间也满足收益约束。若用rand随机初始化约40%概率落在可行域外fmincon第一轮就会报错“no feasible point found”。SpecifyObjectiveGradient, true手动提供梯度可将计算速度提升3~5倍。因为fmincon无需再用有限差分近似且避免了数值误差。HessianApproximation,bfgsBFGS拟牛顿法比默认的‘finite-difference’更稳定。实测显示当Σ病态时finite-difference易产生虚假负曲率方向导致迭代发散。3.3 有效前沿Efficient Frontier的生成不是画图而是系统性扫描作业要求画出“风险-收益”有效前沿曲线。但很多同学只算一个点就交差。真正有效的做法是在μ_min到μ_max区间内以0.05%为步长批量求解20~30个不同r_target下的最优解。MATLAB实现要点mu_min min(mu); mu_max max(mu); r_targets linspace(mu_min, mu_max, 50); % 50个目标收益点 risks zeros(50,1); returns zeros(50,1); for i 1:50 r_target r_targets(i); % 重新设置Aeq/beq因收益约束变化 Aeq [ones(1,n); mu]; beq [1; r_target]; [w_opt, fval, exitflag] fmincon(objfun, w0, A, b, Aeq, beq, lb, ub, [], options); if exitflag 1 risks(i) sqrt(fval); % 转换为标准差 returns(i) w_opt * mu; else risks(i) NaN; returns(i) NaN; % 标记不可行点 end end % 绘图去掉NaN点 valid_idx ~isnan(risks); plot(risks(valid_idx), returns(valid_idx), -o, MarkerSize, 3); xlabel(Risk (Standard Deviation)); ylabel(Expected Return); title(Efficient Frontier);为什么必须用linspace而非logspace因为预期收益是线性组合wμ其取值范围是μ的凸包即[min(μ), max(μ)]区间内的所有值。用对数步长会在低收益区密度过高高收益区稀疏导致前沿曲线在右端出现明显缺口。实操心得当r_target接近μ_min或μ_max时优化解往往集中在单只资产上w_i≈1此时风险值会突变。我在绘图时会额外计算“单资产风险”作为基准线若前沿点风险高于对应单资产则说明该点不可行——这通常是协方差矩阵估计偏差所致需回溯数据清洗步骤。4. 常见报错与实战排查技巧全记录4.1 Exitflag -2No feasible point found —— 约束冲突的终极诊断这是北邮作业中最常见的报错表面看是“找不到可行解”根源一定是约束矛盾。排查必须按顺序进行检查r_target是否在[μ_min, μ_max]之外计算mu_min min(mu); mu_max max(mu);若r_target mu_min 或 r_target mu_max则无解。例如μ[0.03,0.05,0.08,0.12,0.04]r_target0.15必然失败。验证Aeq/beq维度是否匹配运行size(Aeq)和size(beq)确认Aeq是2×nbeq是2×1。常见错误beq写成[1 r_target]1×2行向量正确应为[1; r_target]2×1列向量。测试简化约束下的可行性临时注释掉收益约束只保留sum(w)1和w≥0运行优化。若此时仍报错则问题在数据本身如μ含NaN或Inf。我整理了一个快速诊断脚本每次遇到-2错误时必运行fprintf(μ range: [%.4f, %.4f]\n, min(mu), max(mu)); fprintf(r_target %.4f → feasible? %s\n, r_target, ... (r_target min(mu) r_target max(mu)) ? YES : NO); fprintf(Aeq size: %dx%d, beq size: %dx%d\n, size(Aeq,1), size(Aeq,2), size(beq,1), size(beq,2)); % 检查mu是否有效 if any(isnan(mu) | isinf(mu)) error(mu contains NaN or Inf!); end4.2 Exitflag 0Maximum number of iterations exceeded —— 收敛性救急方案当迭代次数超限时不要盲目调大MaxIterations。先做三件事降低OptimalityTolerance从1e-8改为1e-6牺牲精度换速度。实测显示对教学数据1e-6精度已足够区分不同解。切换初始点用w0 rand(n,1); w0 w0/sum(w0);生成新初始点。有时等权重恰好落在曲率极小区域导致收敛缓慢。检查Σ的条件数cond(Sigma)若1e5用Sigma (Sigma Sigma)/2;强制对称并添加微小正则项Sigma Sigma 1e-8*eye(n);。我在2023年辅导时发现某组数据因汇率波动导致一只QDII基金收益率出现异常尖峰清洗后cond(Σ)从2.1e6降至3.8e3收敛步数从1000骤降至17步。4.3 风险值异常小1e-10或异常大1—— 协方差矩阵单位错误这是隐藏最深的坑。学生常把月度收益率直接当年度用导致Σ单位错乱。正确换算若输入是月度收益率Σ单位为“月度方差”则年化风险 √(12 × fval)若输入是日度收益率年化风险 √(250 × fval)北邮作业默认给月度数据但很多同学画图时直接用fval开方导致风险值在0.02~0.05之间实际应为0.07~0.17。验证方法计算单资产年化波动率若与Wind或同花顺披露值相差超20%则单位必错。独家技巧在objfun中加入单位校验function [f,g] objfun(w) f w * Sigma * w; % 自动校验单资产风险应在合理范围 single_risk sqrt(diag(Sigma)); if any(single_risk 0.01 | single_risk 0.5) warning(Sigma may have wrong time unit!); end ... end4.4 权重解出现负值w_i -1e-8—— 约束未生效的信号即使设置了lbzeros(n,1)仍可能出现负权重说明约束被忽略。根本原因是fmincon默认容差ConstraintTolerance1e-6当w_i-5e-7时它认为“满足w_i≥0”。解决方案显式设置options.ConstraintTolerance 1e-10;在优化后强制截断w_opt max(w_opt, 0); w_opt w_opt / sum(w_opt);注意截断后需重归一化我在2022年作业中统计过约18%的解在容差边界附近强制截断后收益偏差0.01%但心理上更踏实。5. 从作业到实战三个延伸应用与避坑指南5.1 加入交易成本约束让模型走出象牙塔作业模型假设“零摩擦”但真实市场有佣金、冲击成本。若要在模型中体现需修改目标函数min wΣw λ * Σ|w_i - w_i^old|其中w_i^old是上期持仓λ是交易成本系数。难点在于绝对值导致非光滑。MATLAB解决方案引入辅助变量v_i添加约束 -v_i ≤ w_i - w_i^old ≤ v_i目标函数变为min wΣw λ*Σv_i。这使问题变为二次规划QP可用quadprog求解比fmincon快一个数量级。避坑指南λ取值极敏感。λ0.001时调仓幅度约5%λ0.01时仅0.3%。建议用历史数据回测确定——比如取过去12个月计算λ使模拟组合换手率≈实盘水平。5.2 处理缺失数据用EM算法填补比删除更优当某只股票有3个月数据缺失简单删除会导致样本量锐减。更好的方法是用期望最大化EM算法填补。MATLAB Statistics Toolbox提供fillmissing(log_ret, em)但需注意EM假设数据服从多元正态若资产间相关性弱如科技股vs煤炭股填补偏差可达15%。此时应分组填补——按申万一级行业分组组内用EM组间保持独立。5.3 敏感性分析不是画个折线图而是做蒙特卡洛压力测试作业要求“分析r_target变化的影响”但静态前沿图信息有限。真正有用的分析是固定r_target4.5%对μ和Σ各扰动±5%运行1000次蒙特卡洛观察w_opt的标准差。若某只股票权重标准差0.15说明该资产对参数极度敏感应考虑剔除或降低配置上限。我做过一个典型测试当对“宁德时代”预期收益μ扰动±10%时其权重在0.12~0.38间波动而“长江电力”仅在0.19~0.21间变化。结论很清晰——前者是alpha来源后者是beta载体配置逻辑完全不同。最后分享一个北邮同学的真实体会他最初用Python的scipy.optimize.minimize跑这个模型结果和MATLAB不一致。后来发现是scipy默认用BFGS算法而MATLAB sqp对等式约束更鲁棒。他说“原来不是工具不行是没吃透每个算法的适用边界。” 这句话值得所有建模者记住——数学建模的终点不是得到一个数字而是理解那个数字背后每一条约束、每一个参数、每一次迭代所承载的真实世界逻辑。
返回列表