7.3. Modeling and Optimization with CVXPY

CVXPY is a domain-specific language (DSL) based on Python for modeling and solving convex optimization problems. It supports multiple solvers (e.g., ECOS, SCS) and is renowned for its concise mathematical syntax and automatic differentiation. It is widely used in fields such as machine learning, finance, and engineering optimization.

Currently, with the CVXPY plugin we provide, users can model problems using CVXPY and invoke MindOpt for solving. For more details about CVXPY, please refer to the CVXPY Official Documentation.

In this section, we will demonstrate how to use the CVXPY API to implement a Mixed-Integer Linear Programming (MILP) model for optimizing a stock portfolio under budget constraints, risk constraints, and minimum trade lot restrictions.

7.3.1. Installing CVXPY

Users must first install mindoptpy. For installation and configuration of mindoptpy, please refer to Install mindoptpy Library. After installing mindoptpy, users can install CVXPY via the following pip command:

pip install cvxpy

For detailed CVXPY installation instructions, refer to CVXPY Installation.

7.3.2. CVXPY Interface

MindOpt provides two distinct plugin interfaces for CVXPY: mindopt_qpif.py and mindopt_conif.py.

Using these plugins, users can model and solve linear programming problems (LP), mixed-integer linear programming problems (MILP), convex quadratic programming problems (convex QP), second-order cone programming problems (SOCP), and mixed-integer convex quadratic programming problems (convex MIQP).

7.3.2.1. Modeling and Solving with CVXPY Interface

MindOpt defines the required implementations for CVXPY to call MindOpt in the interface files (mindopt_qpif.py, mindopt_conif.py). These interfaces inherit from CVXPY’s QPSolver and ConicSolver classes. For implementation details, refer to the interface files in the installation package:

<MDOHOME>/<VERSION>/<PLATFORM>/lib/cvxpy/mindopt_conif.py
<MDOHOME>/<VERSION>/<PLATFORM>/lib/cvxpy/mindopt_qpif.py

To use them, users must first copy these two interface files to the project directory as part of the source code and import the relevant implementations in Python:

6from mindopt_conif import *
7from mindopt_qpif import *

Next, we use the CVXPY API to build a stock portfolio optimization problem. For detailed CVXPY API documentation, refer to the CVXPY API Official Documentation.

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]

When solving, we specify the use of the MindOpt solver and configure relevant solver parameters (for solver parameters, see Parameters):

56prob = cp.Problem(objective, constraints)
57solver_options = {'MaxTime': 3, 'NumThreads': 2}
58prob.solve(solver=cp.MINDOPT, verbose=True, **solver_options) # Solve using MindOpt solver

Finally, check the solution status and retrieve the results:

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. Example: cvxpy_mip_finance

The full code is provided in the file 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}")

For other CVXPY examples, refer to CVXPY Examples.