You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Scipy Optimize Basin Hopping在55只股票组合优化中失败求助

解决股票组合权重优化的规模扩展与约束失效问题

看起来你遇到了高维股票组合优化的两个核心问题:一是55只股票时优化失败,二是权重总和无法稳定达到1。我来帮你拆解问题并给出修复方案:

问题诊断

  1. 约束条件未生效:你定义了check_sum等式约束,但原代码里的L-BFGS-B优化器不支持等式约束,只支持边界约束。15只股票时可能靠随机初始化和局部优化偶然满足权重和为1,但55只高维场景下,这种巧合很难维持,导致权重总和偏离。
  2. 随机步长未维护约束:自定义的RandomDisplacementBounds只处理了单变量的边界,但没有保证调整后权重总和仍为1,高维下随机调整后总和很容易失控。
  3. 高维优化的稳定性问题: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

火山引擎 最新活动