Scipy Minimize函数约束问题:股票组合权重优化第二约束失效
问题分析
你的第三个约束失效的核心原因是:约束函数不满足连续可微性。x[x>=0.05]是硬阈值操作,当权重刚好等于0.05时,函数导数会突变,而Scipy的SLSQP优化器要求约束函数必须连续可微,这种非光滑性会导致优化器无法正确识别和强制执行约束。
同时原目标函数存在逻辑偏差:你用minimize去优化fund_return/fund_risk,但实际需求是最大化夏普比率,应该将目标函数取负,让minimize执行最大化逻辑。
解决方案
以下提供两种可行的修复方案:
方法1:用平滑近似替代硬阈值
通过可微的sigmoid函数近似“权重≥0.05”的判断逻辑,让约束函数满足可微性,同时修正目标函数符号:
import pandas as pd import numpy as np from scipy.optimize import minimize # 生成数据 n = 40 codes = [''.join(np.random.choice(list('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 2)) + str(np.random.randint(10, 100)) for _ in range(n)] returns = np.random.uniform(0.05, 0.2, n) risks = np.random.uniform(0.15, 0.39, n) df = pd.DataFrame({"Code_title": codes, "Return": returns, "Risk": risks}) # 平滑参数,ε越小越接近硬阈值,数值稳定性会稍降 epsilon = 1e-4 def smooth_indicator(x): # 用sigmoid函数近似"x≥0.05"的指示逻辑 return 1 / (1 + np.exp(-(x - 0.05)/epsilon)) # 定义约束 cons = [ {"type":"eq", "fun": lambda x: np.sum(x)-1}, # 平滑后的约束:权重≥0.05的资产总和≤0.4 → 0.4 - 近似总和 ≥0 {"type": "ineq", "fun": lambda x: 0.4 - np.sum(x * smooth_indicator(x))} ] # 调整初始猜测:让部分权重超过0.05,帮助优化器感知约束 guess = np.full(df.shape[0], 1.0/df.shape[0]) guess[np.random.choice(df.shape[0], 8, replace=False)] = 0.06 bounds = tuple((0, 0.1) for _ in range(df.shape[0])) def function_obj(W): W = np.array(W) fund_return = df['Return'].values @ W fund_risk = df['Risk'].values @ W # 取负后,minimize等价于最大化夏普比率 return -(fund_return / fund_risk) result = minimize(function_obj, guess, method="SLSQP", bounds=bounds, constraints=cons) # 验证约束 sum_over_threshold = np.sum(result.x[result.x >= 0.05]) print(f"权重≥0.05的资产总权重:{sum_over_threshold:.4f}") print(f"是否满足≤0.4约束:{sum_over_threshold <= 0.4 + 1e-3}") # 允许微小数值误差
方法2:转化为线性约束组合(严格硬约束)
如果需要严格的硬阈值约束,可引入二进制辅助变量,使用支持混合整数规划的scipy.optimize.milp实现:
import pandas as pd import numpy as np from scipy.optimize import milp, LinearConstraint, Bounds # 生成数据 n = 40 codes = [''.join(np.random.choice(list('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 2)) + str(np.random.randint(10, 100)) for _ in range(n)] returns = np.random.uniform(0.05, 0.2, n) risks = np.random.uniform(0.15, 0.39, n) df = pd.DataFrame({"Code_title": codes, "Return": returns, "Risk": risks}) # 变量:前40个是权重w_i,后40个是二进制辅助变量z_i(z_i=1表示w_i≥0.05) n_vars = 2 * n # 目标函数:最大化风险调整后收益(等价于近似最大化夏普比率) c = np.concatenate([-(df['Return'].values - df['Risk'].values), np.zeros(n)]) # 构造线性约束 constraints = [] # 约束1:权重总和=1 constraints.append(LinearConstraint(np.concatenate([np.ones(n), np.zeros(n)]), lb=1, ub=1)) # 约束2:若z_i=1,则w_i≥0.05;若z_i=0,w_i无下限(由bounds控制≥0) for i in range(n): row = np.zeros(n_vars) row[i] = 1 row[n+i] = -0.05 constraints.append(LinearConstraint(row, lb=0, ub=np.inf)) # 约束3:若z_i=1,则w_i≤0.1;若z_i=0,则w_i≤0.05 for i in range(n): row = np.zeros(n_vars) row[i] = 1 row[n+i] = -0.05 constraints.append(LinearConstraint(row, lb=-np.inf, ub=0.05)) # 约束4:权重≥0.05的资产总和≤0.4 sum_w = np.concatenate([np.ones(n), np.zeros(n)]) sum_z = np.concatenate([np.zeros(n), np.ones(n)]) constraints.append(LinearConstraint(sum_w + 0.05*sum_z, lb=-np.inf, ub=0.4 + 0.05*n)) # 变量边界:w_i∈[0,0.1],z_i∈{0,1} bounds = Bounds( lb=np.concatenate([np.zeros(n), np.zeros(n)]), ub=np.concatenate([np.full(n, 0.1), np.full(n, 1)]), integrality=np.concatenate([np.zeros(n), np.ones(n)]) # z_i为整数变量 ) result = milp(c=c, constraints=constraints, bounds=bounds) # 提取权重并验证约束 w = result.x[:n] sum_over_threshold = np.sum(w[w>=0.05]) print(f"权重≥0.05的资产总权重:{sum_over_threshold:.4f}") print(f"是否满足≤0.4约束:{sum_over_threshold <=0.4 +1e-3}")
关键注意事项
- 可微性要求:基于梯度的优化器(如SLSQP)对约束函数的可微性有严格要求,避免使用硬阈值、条件判断这类非光滑操作。
- 初始猜测调整:初始权重全为0.025时,优化器可能无法感知约束,让部分初始权重超过0.05能帮助优化器更快识别约束逻辑。
- 目标函数逻辑:使用
minimize实现最大化需求时,需将目标函数取负。
内容的提问来源于stack exchange,提问作者PatrickHigadere
相关产品推荐
相关产品推荐

