问题描述¶
在投资组合选择优化问题 中,我们必须在一组股票中分配投资组合。每只股票的收益和风险协方差矩阵已知。问题在于决定投资组合中投入到每只股票的比例。我们必须使用全部投资组合,并达到给定的预期利润。问题的目标是最小化与所选投资相关的风险。该问题是对 马科维茨投资组合选择优化问题 的简化。
建模要点¶
- 添加 浮点决策变量 来建模投入到每只股票的投资组合比例
- 使用 非线性算子 来计算风险
- 了解 OptAgent 的建模方式:区分决策变量与中间表达式
建模思路¶
投资组合选择优化问题的 OptAgent 模型使用浮点决策变量,表示投入到每只股票的投资组合比例。由于我们必须投入全部投资组合,我们约束这些比例之和等于 1。
投资组合的收益是所有股票利润的加权和,其中权重是分配到每只股票的投资组合比例。我们将其约束为大于或等于预期利润。
问题的目标是最小化风险。投资组合风险是一个凸二次函数,通过对每对股票的投资比例与对应协方差的乘积求和得到。
Python 实现¶
from pathlib import Path
from optagent import OptModel, solve
def read_instance(filename):
lines = Path(filename).read_text(encoding="utf-8").splitlines()
first_line = lines[0].split()
# Expected profit, in percentage of the portfolio
expected_profit = float(first_line[0])
second_line = lines[2].split()
# Number of stocks
nb_stocks = int(second_line[0])
# Covariance among the stocks
sigma_stocks = [[0 for i in range(nb_stocks)] for j in range(nb_stocks)]
for s in range(nb_stocks):
line = lines[s+4].split()
for t in range(nb_stocks):
sigma_stocks[s][t] = float(line[t])
# Variation of the price of each stock
delta_stock = [0 for i in range(nb_stocks)]
line = lines[nb_stocks+5].split()
for s in range(nb_stocks):
delta_stock[s] = float(line[s])
return expected_profit, nb_stocks, sigma_stocks, delta_stock
def main(instance_file, output_file=None, time_limit=60):
expected_profit, nb_stocks, sigma_stocks, delta_stock = read_instance(
instance_file)
model = OptModel()
# Proportion of the portfolio invested in each stock
portfolio_stock = [
model.float(0, 1)
for s in range(nb_stocks)
]
# Risk of the portfolio
risk = model.sum(
portfolio_stock[s] * portfolio_stock[t] * sigma_stocks[s][t]
for t in range(nb_stocks)
for s in range(nb_stocks)
)
# Return of the portfolio in percentage
profit = model.sum(
portfolio_stock[s] * delta_stock[s] for s in range(nb_stocks)
)
# All the portfolio is used
model.constraint(
model.sum(portfolio_stock[s] for s in range(nb_stocks)) == 1.0,
)
# The profit is at least the expected profit
model.constraint(profit >= expected_profit)
# Minimize the risk
model.minimize(risk)
solution = solve(model, time_limit_s=float(time_limit))
if not solution.feasible:
print(f"No feasible portfolio found; Status = {solution.feasible}")
return solution
lines = [
f"Stock {s + 1}: {portfolio_stock[s].value * 100:.1f}%"
for s in range(nb_stocks)
]
lines.append(f"Profit: {profit.value:.4f}%")
result_text = "\n".join(lines)
print(result_text)
if output_file is not None:
Path(output_file).write_text(result_text + "\n", encoding="utf-8")
return solution
本地运行¶
在 notebook 所在目录执行以下 cell,即可调用一个投资组合实例。
INSTANCE_DIR = Path.cwd() / "instances"
print("Instances:", INSTANCE_DIR)
solution_portfolio = main(INSTANCE_DIR / "small_01.txt", time_limit=1)