Gurobi绝对值处理报错解决:投资组合优化模型问题
投资组合优化LP模型:Gurobi中绝对值约束的错误修复
问题场景
用Gurobi实现多期投资组合线性规划优化,基于48只股票120天的预期收益率计算每日股价,目标是最大化第120天的投资组合总价值。模型定义了bought(每日买卖数量)、holding(每日持仓)等变量,核心约束为单只股票价值占组合总价值至少1%。运行时触发错误:
AttributeError: 'GenExprAbs' object has no attribute 'getValue'
错误根源
getValue()调用时机错误:该方法仅能在模型求解完成后,用于获取变量/表达式的最优结果,建模阶段不能将其嵌入约束表达式中。- 绝对值的非线性问题:
gp.abs_()生成的是非线性表达式,而当前模型是线性规划(LP),求解器无法直接处理非线性约束,必须将绝对值逻辑线性化。
解决方案
将bought变量拆分为买入量和卖出量两个非负整数变量,彻底避免绝对值的使用:
- 新增
buy[i,j]:第i天买入第j只股票的数量,非负整数 - 新增
sell[i,j]:第i天卖出第j只股票的数量,非负整数 - 关联关系:
当日持仓 = 前日持仓 + 买入 - 卖出,交易总价值为买入价值与卖出价值之和,直接计算交易成本即可。
额外说明:若需严格禁止同一股票同时买卖,可引入0-1变量添加互斥约束,但由于同时买卖会额外增加交易成本,对最大化收益的目标不利,求解器通常会自动规避这类无意义操作,可根据模型规模决定是否添加。
修正后的完整代码
# data num_periods = 121 # first period no trade num_stocks = 48 starting_capital = 100000 diversification_constraint = 0.01 transaction_cost = 0.02 estimated_prices = price # 假设price是预先计算好的121×48股价矩阵 # Create model m = gp.Model('Portfolio Optimization') # Create variables # 拆分买入/卖出变量,替代原bought的绝对值逻辑 buy = m.addVars(num_periods, num_stocks, vtype=gp.GRB.INTEGER, lb=0, name='buy') sell = m.addVars(num_periods, num_stocks, vtype=gp.GRB.INTEGER, lb=0, name='sell') holding = m.addVars(num_periods, num_stocks, vtype=gp.GRB.INTEGER, lb=0, name='holding') # 持仓量非负 transaction = m.addVars(num_periods, name='transaction') portfolio_value = m.addVars(num_periods, name='portfolio_value') cash = m.addVars(num_periods, name='cash', lb=0) # 现金非负 # Set objective: 最大化最后一期组合价值 m.setObjective(portfolio_value[num_periods-1], gp.GRB.MAXIMIZE) # Set constraints # 初始现金约束 m.addConstr(cash[0] == starting_capital) # 初始无交易、无持仓 m.addConstrs((buy[0, j] == 0 for j in range(num_stocks))) m.addConstrs((sell[0, j] == 0 for j in range(num_stocks))) m.addConstrs((holding[0, j] == 0 for j in range(num_stocks))) # 持仓变化约束:当日持仓 = 前日持仓 + 买入 - 卖出 m.addConstrs((holding[i, j] == holding[i-1, j] + buy[i,j] - sell[i,j] for i in range(1, num_periods) for j in range(num_stocks))) # 交易成本约束:按当日交易总价值的2%计算 m.addConstrs((transaction[i] == transaction_cost * gp.quicksum( buy[i,j]*estimated_prices[i,j] + sell[i,j]*estimated_prices[i,j] for j in range(num_stocks)) for i in range(1, num_periods))) # 组合价值约束:组合价值 = 持仓总价值 + 现金 m.addConstrs((portfolio_value[i] == gp.quicksum(holding[i, j] * estimated_prices[i,j] for j in range(num_stocks)) + cash[i] for i in range(num_periods))) # 现金平衡约束:当日现金 = 前日现金 - 交易成本 - 买入支出 + 卖出收入 m.addConstrs((cash[i] == cash[i-1] - transaction[i] - gp.quicksum(buy[i,j]*estimated_prices[i,j] for j in range(num_stocks)) + gp.quicksum(sell[i,j]*estimated_prices[i,j] for j in range(num_stocks)) for i in range(1, num_periods))) # 分散化约束:单只股票价值至少占组合总价值的1% m.addConstrs((holding[i, j] * estimated_prices[i,j] >= diversification_constraint * portfolio_value[i] for i in range(num_periods) for j in range(num_stocks))) # Solve model m.optimize() # Print solution if m.status == gp.GRB.OPTIMAL: print('Optimal solution found!') print('Final portfolio value:', portfolio_value[num_periods-1].x) else: print('No solution found.')
额外优化点
- 给
holding和cash添加非负约束(lb=0),符合实际投资逻辑(不能持有负股票、不能透支现金) - 修正现金平衡约束:卖出股票会增加现金,原代码中遗漏了这部分收入
内容的提问来源于stack exchange,提问作者Jing Heng Lim
相关产品推荐
相关产品推荐

