Scipy Optimize Basin Hopping在55只股票组合优化中失败求助
解决股票组合权重优化的规模扩展与约束失效问题
看起来你遇到了高维股票组合优化的两个核心问题:一是55只股票时优化失败,二是权重总和无法稳定达到1。我来帮你拆解问题并给出修复方案:
问题诊断
- 约束条件未生效:你定义了
check_sum等式约束,但原代码里的L-BFGS-B优化器不支持等式约束,只支持边界约束。15只股票时可能靠随机初始化和局部优化偶然满足权重和为1,但55只高维场景下,这种巧合很难维持,导致权重总和偏离。 - 随机步长未维护约束:自定义的
RandomDisplacementBounds只处理了单变量的边界,但没有保证调整后权重总和仍为1,高维下随机调整后总和很容易失控。 - 高维优化的稳定性问题:L-BFGS-B在高维空间的收敛性会下降,需要更适合处理约束的优化器配合全局搜索。
具体修复步骤
1. 替换优化器并添加显式等式约束
改用支持等式约束的SLSQP优化器,并将check_sum约束加入优化参数:
# 定义权重和为1的等式约束 constraints = ({'type': 'eq', 'fun': check_sum}) # 替换优化器为SLSQP,添加约束参数与迭代次数配置 minimizer_kwargs = {"method":"SLSQP", "bounds": bnds, "constraints": constraints, "options": {"maxiter": 1000}}
2. 修正随机步长生成逻辑,确保权重总和始终为1
修改RandomDisplacementBounds类,在生成新权重后强制归一化并修复边界:
class RandomDisplacementBounds(object): def __init__(self, xmin, xmax, stepsize=0.1): # 高维下调小步长,避免权重波动过大 self.xmin = xmin self.xmax = xmax self.stepsize = stepsize def __call__(self, x): min_step = np.maximum(self.xmin - x, -self.stepsize) max_step = np.minimum(self.xmax - x, self.stepsize) random_step = np.random.uniform(low=min_step, high=max_step, size=x.shape) xnew = x + random_step # 第一步:归一化权重,确保总和为1 xnew = xnew / np.sum(xnew) # 第二步:修正边界,防止归一化后个别权重超出[0,1]范围 xnew = np.clip(xnew, self.xmin, self.xmax) # 第三步:再次归一化,因为clip操作可能改变总和 xnew = xnew / np.sum(xnew) return xnew
3. 在指标计算中添加权重归一化保险
即使优化过程出现偏差,在get_metrics开头强制归一化权重,确保后续计算的合理性:
def get_metrics(weights): weights = np.array(weights) # 强制归一化,确保权重和为1 weights = weights / np.sum(weights) returnsR = np.dot(returns_annualR, weights ) volatilityR = np.sqrt(np.dot(weights.T, np.dot(cov_matrixR, weights))) sharpeR = returnsR / volatilityR # 注意:这里检查dailyDD的维度,若dailyDD是(n_days, num_assets),sum(axis=1)会得到每日组合回撤,再取最小值 # 若dailyDD是(num_assets,)的单值回撤,应改为sum(axis=0) drawdownR = np.multiply(weights, dailyDD).sum(axis=1, skipna=True).min() drawdownR = f(drawdownR) calmarR = returnsR / drawdownR results = (sharpeR * 0.3) + (calmarR * 0.7) return np.array([returnsR, volatilityR, sharpeR, drawdownR, calmarR, results])
4. 调整全局搜索参数适配高维
增大basinhopping的迭代次数,提高全局搜索的覆盖性:
globmin = sco.basinhopping( objective, x0=num_assets*[1./num_assets], minimizer_kwargs=minimizer_kwargs, take_step=bounded_step, disp=True, niter=100 # 增加迭代次数,高维需要更多全局探索 )
额外优化建议
- 检查
dailyDD的维度与权重的匹配性,确保矩阵乘法和求和的axis参数正确,避免计算错误影响优化目标。 - 高维场景下可以考虑加入L1正则化(比如在目标函数中加入小权重的惩罚项),防止个别权重过度集中或趋近于0,提高组合稳定性。
内容的提问来源于stack exchange,提问作者user9853666




