7.3. CVXPY 的建模与优化¶
CVXPY 是一个基于 Python 的领域特定语言(DSL),用于建模和求解凸优化问题,支持多种求解器(如 ECOS、SCS)。它以简洁的数学语法和自动微分著称,广泛应用于机器学习、金融和工程优化领域。
目前,使用我们提供的 CVXPY 插件,用户可通过 CVXPY 进行建模并调用 MindOpt 进行求解。关于 CVXPY 的更多详细内容,请参考 CVXPY 官方文档库。
在本节中,我们将介绍如何使用 CVXPY API 实现一个 混合整数线性规划(MILP) 模型,用于在预算、风险约束和最小交易手数限制下优化股票投资组合。
7.3.1. 安装 CVXPY¶
用户需首先安装 mindoptpy 。关于 mindoptpy 的安装与配置请参考 安装mindoptpy库。mindoptpy 安装后,用户可以通过以下 pip 的命令方式来安装 CVXPY:
pip install cvxpy
关于 CVXPY 的详细安装方式,请参考 CVXPY Installation。
7.3.2. CVXPY 调用接口¶
MindOpt 为 CVXPY 提供两个不同的插件接口,分别是 mindopt_qpif.py 和 mindopt_conif.py
基于以上插件,用户可以实现线性规划问题(LP)、混合整数线性规划问题(MILP)、凸二次规划问题(convex QP)、二阶锥规划问题(SOCP)以及混合整数凸二次规划问题(convex MIQP)的建模与求解。
7.3.2.1. 调用 CVXPY 接口建模求解¶
MindOpt 在 mindopt_qpif.py 和 mindopt_conif.py 接口文件( mindopt_qpif.py, mindopt_conif.py)内定义了 CVXPY 调用 MindOpt 所需的相关实现。此接口继承自 CVXPY 的 QPSolver 和 ConicSolver 类,实现细节见安装包内的接口文件:
<MDOHOME>/<VERSION>/<PLATFORM>/lib/cvxpy/mindopt_conif.py
<MDOHOME>/<VERSION>/<PLATFORM>/lib/cvxpy/mindopt_qpif.py
用户在使用时需首先将这两个接口文件拷贝到项目目录作为源码的一部分,并在 Python 代码中导入相关实现:
6from mindopt_conif import *
7from mindopt_qpif import *
接着,我们调用 CVXPY API 来建立股票投资组合优化问题。关于 CVXPY API 的详细说明,请参考 CVXPY API 官方文档。
10stocks = ["AAPL", "MSFT", "GOOG", "AMZN", "META"] # Stock tickers
11n_assets = len(stocks)
12
13current_prices = np.array([180, 320, 140, 130, 350]) # Current stock prices (USD/share)
14
15expected_returns = np.array(
16 [0.08, 0.12, 0.10, 0.15, 0.18]
17) # Expected annual returns (decimal)
18
19betas = np.array([1.2, 0.9, 1.1, 1.4, 1.3]) # Stock beta coefficients (systematic risk)
20
21total_budget = 100000 # Total investment budget (USD)
22
23min_lot_size = (
24 100 # # Minimum trading lot size (shares per lot, standard for Chinese A-shares)
25)
26
27max_beta = 1.2 # Maximum portfolio beta threshold
28
29x = cp.Variable(
30 n_assets, integer=True
31) # Integer variable: number of lots to purchase per stock
32y = cp.Variable(nonneg=True) # Auxiliary variable for linearization (if needed)
33
34invest_amount = cp.multiply(
35 current_prices * min_lot_size, x
36) # Investment amount per stock (USD)
37
38total_return = cp.sum(
39 cp.multiply(expected_returns, invest_amount)
40) # Objective: Maximize portfolio return
41objective = cp.Maximize(total_return)
42
43constraints = [
44 # Budget constraint
45 cp.sum(invest_amount) <= total_budget,
46 # Risk constraint (linearized form)
47 cp.sum(cp.multiply(betas, invest_amount)) <= max_beta * cp.sum(invest_amount),
48 # Diversification: single asset limit
49 invest_amount <= 0.4 * total_budget,
50 # Non-negativity
51 x >= 0,
52]
求解时,我们指定使用 MindOpt 求解器,并对求解的相关参数进行设置(求解器参数请查阅 参数):
56prob = cp.Problem(objective, constraints)
57solver_options = {'MaxTime': 3, 'NumThreads': 2}
58prob.solve(solver=cp.MINDOPT, verbose=True, **solver_options) # Solve using MindOpt solver
最后,检查求解状态并获取相关的结果:
58if prob.status not in [cp.OPTIMAL, cp.OPTIMAL_INACCURATE]:
59 raise ValueError("Solver failed to find solution")
60
61print("\nSolver status:", prob.status)
62print("Expected annual return: ${:.2f}".format(prob.value))
63print("\nOptimal Portfolio:")
64total_investment = 0
65for i in range(n_assets):
66 shares = x.value[i] * min_lot_size
67 if shares > 1e-4: # Filter trivial positions
68 position_value = shares * current_prices[i]
69 print(
70 f"{stocks[i]}: {int(x.value[i])} lots ({shares} shares), "
71 f"${position_value:,.2f} ({position_value/total_budget:.1%})"
72 )
73 total_investment += position_value
74# Calculate actual portfolio beta
75portfolio_beta = (
76 np.sum(betas * (current_prices * min_lot_size * x.value)) / total_investment
77)
7.3.3. 建模示例: cvxpy_mip_finance¶
文件链接 cvxpy_mip_finance.py 中提供了完整代码:
1"""
2Portfolio Optimization Model with MindOpt (MILP Example)
3Objective: Maximize expected return under risk constraints
4"""
5
6from mindopt_conif import *
7from mindopt_qpif import *
8import numpy as np
9
10stocks = ["AAPL", "MSFT", "GOOG", "AMZN", "META"] # Stock tickers
11n_assets = len(stocks)
12
13current_prices = np.array([180, 320, 140, 130, 350]) # Current stock prices (USD/share)
14
15expected_returns = np.array(
16 [0.08, 0.12, 0.10, 0.15, 0.18]
17) # Expected annual returns (decimal)
18
19betas = np.array([1.2, 0.9, 1.1, 1.4, 1.3]) # Stock beta coefficients (systematic risk)
20
21total_budget = 100000 # Total investment budget (USD)
22
23min_lot_size = (
24 100 # # Minimum trading lot size (shares per lot, standard for Chinese A-shares)
25)
26
27max_beta = 1.2 # Maximum portfolio beta threshold
28
29x = cp.Variable(
30 n_assets, integer=True
31) # Integer variable: number of lots to purchase per stock
32y = cp.Variable(nonneg=True) # Auxiliary variable for linearization (if needed)
33
34invest_amount = cp.multiply(
35 current_prices * min_lot_size, x
36) # Investment amount per stock (USD)
37
38total_return = cp.sum(
39 cp.multiply(expected_returns, invest_amount)
40) # Objective: Maximize portfolio return
41objective = cp.Maximize(total_return)
42
43constraints = [
44 # Budget constraint
45 cp.sum(invest_amount) <= total_budget,
46 # Risk constraint (linearized form)
47 cp.sum(cp.multiply(betas, invest_amount)) <= max_beta * cp.sum(invest_amount),
48 # Diversification: single asset limit
49 invest_amount <= 0.4 * total_budget,
50 # Non-negativity
51 x >= 0,
52]
53
54prob = cp.Problem(objective, constraints)
55solver_options = {'MaxTime': 3, 'NumThreads': 2}
56prob.solve(solver=cp.MINDOPT, verbose=True, **solver_options) # Solve using MindOpt solver
57
58if prob.status not in [cp.OPTIMAL, cp.OPTIMAL_INACCURATE]:
59 raise ValueError("Solver failed to find solution")
60
61print("\nSolver status:", prob.status)
62print("Expected annual return: ${:.2f}".format(prob.value))
63print("\nOptimal Portfolio:")
64total_investment = 0
65for i in range(n_assets):
66 shares = x.value[i] * min_lot_size
67 if shares > 1e-4: # Filter trivial positions
68 position_value = shares * current_prices[i]
69 print(
70 f"{stocks[i]}: {int(x.value[i])} lots ({shares} shares), "
71 f"${position_value:,.2f} ({position_value/total_budget:.1%})"
72 )
73 total_investment += position_value
74# Calculate actual portfolio beta
75portfolio_beta = (
76 np.sum(betas * (current_prices * min_lot_size * x.value)) / total_investment
77)
78print(f"\nTotal invested: ${total_investment:,.2f}")
79print(f"Portfolio beta: {portfolio_beta:.2f}")
80print(f"Cash remaining: ${total_budget - total_investment:,.2f}")
其他 CVXPY 的示例请参考 CVXPY Examples。