Source code for qiskit_finance.applications.optimization.portfolio_diversification

# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2018, 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""An application class for a portfolio diversification problem."""
from typing import List, Union

import numpy as np
from docplex.mp.advmodel import AdvModel

from qiskit_optimization.algorithms import OptimizationResult
from qiskit_optimization.applications import OptimizationApplication
from qiskit_optimization.problems import QuadraticProgram
from qiskit_optimization.translators import from_docplex_mp


[docs]class PortfolioDiversification(OptimizationApplication): """Optimization application for the "portfolio diversification" problem introduced in [1]. References: [1]: GG. Cornuejols and R. Tutuncu, Optimization methods in finance, 2006 """ def __init__(self, similarity_matrix: np.ndarray, num_assets: int, num_clusters: int) -> None: """ Args: similarity_matrix: An asset-to-asset similarity matrix, such as the covariance matrix. num_assets: The number of assets. num_clusters: The number of clusters of assets to output. """ self._similarity_matrix = similarity_matrix self._num_assets = num_assets self._num_clusters = num_clusters
[docs] def to_quadratic_program(self) -> QuadraticProgram: """Convert a portfolio diversification problem instance into a :class:`~qiskit_optimization.QuadraticProgram`. Returns: The :class:`~qiskit_optimization.QuadraticProgram` created from the portfolio diversification problem instance. """ mdl = AdvModel(name="Portfolio diversification") x = { (i, j): mdl.binary_var(name=f"x_{i}_{j}") for i in range(self._num_assets) for j in range(self._num_assets) } y = {i: mdl.binary_var(name=f"y_{i}") for i in range(self._num_assets)} mdl.maximize( mdl.sum( self._similarity_matrix[i, j] * x[(i, j)] for i in range(self._num_assets) for j in range(self._num_assets) ) ) mdl.add_constraint(mdl.sum(y[j] for j in range(self._num_assets)) == self._num_clusters) for i in range(self._num_assets): mdl.add_constraint(mdl.sum(x[(i, j)] for j in range(self._num_assets)) == 1) for j in range(self._num_assets): mdl.add_constraint(x[(j, j)] == y[j]) for i in range(self._num_assets): for j in range(self._num_assets): mdl.add_constraint(x[(i, j)] <= y[j]) op = from_docplex_mp(mdl) return op
[docs] def interpret(self, result: Union[OptimizationResult, np.ndarray]) -> List[int]: """Interpret a result as a list of asset indices Args: result: The calculated result of the problem Returns: The list of asset indices whose corresponding variable is 1 """ x = self._result_to_x(result) return [i for i, x_i in enumerate(x[-self._num_assets :]) if x_i]
@property def similarity_matrix(self) -> np.ndarray: """Getter of similarity_matrix Returns: An asset-to-asset similarity matrix, such as the covariance matrix. """ return self._similarity_matrix @similarity_matrix.setter def similarity_matrix(self, similarity_matrix: np.ndarray): """Setter of similarity_matrix Args: similarity_matrix: An asset-to-asset similarity matrix, such as the covariance matrix. """ self._similarity_matrix = similarity_matrix @property def num_assets(self) -> int: """Getter of num_assets Returns: The number of assets. """ return self._num_assets @num_assets.setter def num_assets(self, num_assets: int): """Setter of num_assets Args: num_assets: The number of assets. """ self._num_assets = num_assets @property def num_clusters(self) -> int: """Getter of num_clusters Returns: The number of clusters of assets to output """ return self._num_clusters @num_clusters.setter def num_clusters(self, num_clusters: int) -> None: """Setter of num_clusters Args: num_clusters: The number of clusters of assets to output """ self._num_clusters = num_clusters