You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:02:27