Scipy Minimize无法稳定求解风险平价组合权重问题
解决Scipy Minimize求解风险平价权重失败的问题
我之前在做风险平价策略时也踩过Scipy Minimize求解失败的坑,结合实践经验和对风险平价逻辑的梳理,整理了几个核心的解决方向,应该能帮你搞定这个问题:
先明确核心目标(避免方向错了)
风险平价的本质是让每只资产的**风险贡献(RC)**均等——也就是每只资产给组合带来的风险占比一致。我们的优化目标通常是最小化各资产风险贡献的平方差之和,同时满足权重和为1、非负(如果不允许做空)的约束。如果目标函数或约束定义错了,肯定会出问题。
常见失败原因及对应解法
1. 初始值选得太离谱,算法找不到收敛路径
Scipy的优化是迭代式的,初始值的选择直接决定了算法能不能找到最优解。
- 推荐用等权重(
np.ones(n)/n,n是资产数量)或者逆波动率权重(1/vol / sum(1/vol),vol是单资产波动率)作为初始值,这两种都很接近风险平价的真实解,能让算法快速收敛。 - 绝对别用全0、极端值(比如某只资产权重设为1,其他为0)当初始值,很容易让算法陷入局部最优或者直接发散。
2. 目标函数或约束写得有问题
目标函数的正确实现
一定要用风险贡献的平方差之和作为目标,这是最稳定的形式:
import numpy as np def risk_parity_obj(weights, cov_matrix): # 计算组合波动率 port_vol = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights))) # 边际风险贡献(MRC) mrc = np.dot(cov_matrix, weights) / port_vol # 单资产风险贡献(RC) rc = weights * mrc # 最小化各RC与平均RC的平方差 target_rc = np.mean(rc) return np.sum((rc - target_rc)**2)
别搞花里胡哨的对数或者其他形式,平方差的数值稳定性最好,不容易出现计算溢出。
约束条件要准确
约束必须明确,不能漏:
- 等式约束:权重总和为1(
{'type': 'eq', 'fun': lambda w: np.sum(w) - 1}) - 不等式约束:权重非负(如果不允许做空,
{'type': 'ineq', 'fun': lambda w: w})
如果允许做空,去掉非负约束就行,但大部分实盘场景都是不允许做空的。
3. 优化器选得不对,参数没调好
Scipy Minimize有多个求解器,默认的L-BFGS-B处理带约束的问题不如SLSQP稳定,尤其是有非负约束的时候。
- 直接用
SLSQP求解器,同时调整容差和迭代次数:
from scipy.optimize import minimize n_assets = cov_matrix.shape[0] init_weights = np.ones(n_assets) / n_assets # 等权重初始值 constraints = [ {'type': 'eq', 'fun': lambda w: np.sum(w) - 1}, {'type': 'ineq', 'fun': lambda w: w} ] result = minimize( risk_parity_obj, init_weights, args=(cov_matrix,), method='SLSQP', constraints=constraints, options={'ftol': 1e-8, 'maxiter': 1000, 'disp': True} )
- 打开
disp=True可以看优化日志,能快速判断是迭代次数不够、收敛阈值太松还是其他问题。比如如果日志显示Maximum iterations reached,就把maxiter调大一点。
4. 协方差矩阵出问题了
如果你的协方差矩阵是奇异的(比如资产之间完全线性相关),计算时会出现除以0或者数值爆炸的情况:
- 给协方差矩阵加个小的正则项,比如
cov_matrix + 1e-6 * np.eye(n_assets),增加矩阵的稳定性,避免奇异值导致的计算错误。 - 确保协方差矩阵是用收益率数据计算的,别用价格数据!可以用
pandas.DataFrame.cov()或者np.cov(returns.T)来计算,注意维度要对。
5. 数值稳定性问题
计算过程中如果出现极小值或者极大值,会导致算法崩溃:
- 在计算组合波动率时,可以加个极小的epsilon避免除以0:
port_vol = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights))) + 1e-10 - 确保权重始终在合理范围内,约束里的非负和和为1已经帮你限制了,主要还是协方差的正则化。
验证解是否正确
优化完成后,一定要验证结果是否真的满足风险平价:
if result.success: opt_weights = result.x # 计算各资产风险贡献 port_vol = np.sqrt(np.dot(opt_weights.T, np.dot(cov_matrix, opt_weights))) mrc = np.dot(cov_matrix, opt_weights) / port_vol rc = opt_weights * mrc print("各资产风险贡献:", rc.round(4)) print("风险贡献的标准差:", np.std(rc).round(6)) # 标准差应该接近0 else: print("优化失败,原因:", result.message)
如果标准差很大(比如超过1e-3),说明解不达标,得回去调整优化参数或者初始值。
替代方案:用凸优化库更稳定
如果Scipy的通用优化器还是不稳定,可以试试cvxpy——专门的凸优化库,而风险平价问题是凸的,所以求解会更可靠:
import cvxpy as cp n = cov_matrix.shape[0] w = cp.Variable(n) port_vol = cp.sqrt(cp.quad_form(w, cov_matrix)) mrc = cp.multiply(cov_matrix @ w, 1/port_vol) rc = cp.multiply(w, mrc) target_rc = cp.mean(rc) objective = cp.sum_squares(rc - target_rc) constraints = [cp.sum(w) == 1, w >= 0] prob = cp.Problem(cp.Minimize(objective), constraints) prob.solve(solver=cp.ECOS, verbose=True) opt_weights = w.value.round(4)
cvxpy的求解器会利用问题的凸性,收敛性和稳定性比Scipy的通用优化器好很多。
内容的提问来源于stack exchange,提问作者WhitneyChia
相关产品推荐
相关产品推荐

