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

基于多重约束的最优8只股票投资组合求解技术问询

嘿,这是一个典型的带约束的整数优化问题,核心是在给定的资金、选股数量和单只持仓限制下,最大化投资组合的总盈利评分。下面我会从建模到具体实现一步步给你梳理:

问题建模

先把问题转化成数学语言,方便后续求解:

  • 变量定义:设第i只股票的购买数量为x_i(必须是正整数,选中的8只x_i ≥1,未选中的为0),它的盈利评分为s_i,股价为p_i
  • 目标函数:我们要最大化总评分 → Maximize Σ(s_i * x_i)(这里假设盈利评分越高,单位仓位的收益潜力越强,线性加权是合理的简化)
  • 约束条件:
    • 总资金上限:Σ(p_i * x_i) ≤ 10000美元
    • 选股数量固定:恰好选8只 → Σ(1 if x_i > 0 else 0) = 8
    • 单只持仓上限:每只股票的投资金额不能超过2500美元 → p_i * x_i ≤ 2500(对所有i)
    • 股价范围:题目给定100 ≤ p_i ≤ 1000,这个是已知条件,不用额外处理
解法思路

直接暴力枚举200只股票里选8只的所有组合完全不现实(组合数是C(200,8),属于天文数字),所以得用更高效的方法:

1. 贪心预筛选缩小范围

首先优先选单位资金性价比最高的股票——也就是盈利评分/股价(每美元能拿到的评分),这个指标越高,股票的“评分性价比”越强。先把200只股票按这个指标从高到低排序,选前30-50只作为候选池,这样后续优化的计算量就可控了。

2. 整数规划精确求解

对于缩小后的候选池,我们可以用整数规划工具来找到全局最优解。Python的PuLP库是个不错的选择,它能轻松构建和求解线性整数规划问题。

3. 启发式局部优化(可选)

如果整数规划的计算时间还是太长,可以用启发式方法:从贪心得到的初始解出发,尝试替换其中一只股票为候选池里的其他股票,看总评分是否提升,迭代到无法优化为止。这种方法速度快,但可能得不到绝对最优解,适合追求效率的场景。

代码实现(Python + PuLP)

假设你已经有了股票数据列表,格式类似stocks = [{"symbol": "AAPL", "price": 180, "score": 90}, ...],下面是具体的求解代码:

import pulp

# 第一步:预处理,筛选高性价比候选股票(这里选前30只)
stocks_sorted = sorted(stocks, key=lambda stock: stock["score"] / stock["price"], reverse=True)
candidate_stocks = stocks_sorted[:30]  # 缩小范围,平衡计算量和最优性

# 第二步:构建整数规划问题
portfolio_problem = pulp.LpProblem("MaximizePortfolioScore", pulp.LpMaximize)

# 定义变量:x_i 是第i只候选股票的购买数量(整数,≥0)
x = [pulp.LpVariable(f"stock_{i}_shares", lowBound=0, cat="Integer") for i in range(len(candidate_stocks))]

# 目标函数:最大化总盈利评分
portfolio_problem += pulp.lpSum([candidate_stocks[i]["score"] * x[i] for i in range(len(candidate_stocks))])

# 约束1:总投资金额不超过10000美元
portfolio_problem += pulp.lpSum([candidate_stocks[i]["price"] * x[i] for i in range(len(candidate_stocks))]) <= 10000

# 约束2:恰好选中8只股票(引入二进制变量y_i,y_i=1表示选中该股票)
y = [pulp.LpVariable(f"stock_{i}_selected", cat="Binary") for i in range(len(candidate_stocks))]
for i in range(len(candidate_stocks)):
    # 如果买了至少1股,说明选中了该股票(y_i=1)
    portfolio_problem += x[i] >= y[i]
    # 单只股票最多买的数量:2500美元除以股价,取整数
    max_shares_per_stock = 2500 // candidate_stocks[i]["price"]
    portfolio_problem += x[i] <= y[i] * max_shares_per_stock
# 选中的股票数量必须是8
portfolio_problem += pulp.lpSum(y) == 8

# 约束3:单只股票投资金额不超过2500美元(其实上面的max_shares已经隐含了这个约束,这里再明确写出来更稳妥)
for i in range(len(candidate_stocks)):
    portfolio_problem += candidate_stocks[i]["price"] * x[i] <= 2500

# 第三步:求解问题(用CBC求解器,关闭日志输出)
portfolio_problem.solve(pulp.PULP_CBC_CMD(msg=False))

# 第四步:输出最优结果
print("✅ 最优投资组合结果:")
total_cost = 0.0
total_score = 0
selected_count = 0

for i in range(len(candidate_stocks)):
    shares = pulp.value(x[i])
    if shares > 0:
        selected_count += 1
        stock = candidate_stocks[i]
        cost = stock["price"] * shares
        score_contribution = stock["score"] * shares
        total_cost += cost
        total_score += score_contribution
        print(f"- 股票 {stock['symbol']}: 购买{int(shares)}股,花费${cost:.2f},贡献评分{score_contribution}")

print(f"\n📊 汇总信息:")
print(f"总投资金额:${total_cost:.2f}")
print(f"总盈利评分:{total_score}")
print(f"选中股票数量:{selected_count}")
关键注意事项
  • 候选池大小的权衡:如果想更接近绝对最优,可以把候选池扩大到前50只,但计算时间会相应增加。如果追求速度,前20只也能得到不错的次优解。
  • 盈利评分的假设:代码里默认盈利评分和收益是线性相关的,也就是每多买1股,收益按评分比例增加。如果实际中评分是非线性的,你需要调整目标函数,但题目里没给出更多信息,线性假设是合理的。
  • 求解器选择:代码用的是PuLP自带的CBC求解器,开源免费,适合小规模问题。如果是更大规模的问题,可以考虑商业求解器比如Gurobi,速度会快很多。

内容的提问来源于stack exchange,提问作者Kael Eppcohen

火山引擎 最新活动