Nota

Esta página fue generada a partir de docs/tutorials/01_portfolio_optimization.ipynb.

Optimización de Cartera#

Introducción#

Este tutorial muestra cómo resolver el siguiente problema de optimización de cartera de varianza media para \(n\) activos:

\[\begin{split}\begin{aligned} \min_{x \in \{0, 1\}^n} q x^T \Sigma x - \mu^T x\\ \text{subject to: } 1^T x = B \end{aligned}\end{split}\]

donde usamos la siguiente notación:

  • \(x \in \{0, 1\}^n\) denota el vector de variables de decisión binarias, que indican qué activos elegir (\(x[i] = 1\)) y cuáles no elegir (\(x[i] = 0\)),

  • \(\mu \in \mathbb{R}^n\) define los rendimientos esperados de los activos,

  • \(\Sigma \in \mathbb{R}^{n \times n}\) especifica las covarianzas entre los activos,

  • \(q > 0\) controla el apetito por el riesgo de quien toma las decisiones,

  • y \(B\) denota el presupuesto, es decir, el número de activos a ser seleccionados de \(n\).

Suponemos las siguientes simplificaciones: - todos los activos tienen el mismo precio (normalizado a 1), - el presupuesto completo \(B\) tiene que gastarse, es decir, uno tiene que seleccionar exactamente \(B\) activos.

La restricción de igualdad \(1^T x = B\) se mapea a un término de penalización \((1^T x - B)^2\) que se escala mediante un parámetro y se resta de la función objetivo. El problema resultante se puede mapear a un Hamiltoniano cuyo estado fundamental corresponde a la solución óptima. Este cuaderno muestra cómo usar el Solucionador Propio Variacional Cuántico de Muestreo (Sampling Variational Quantum Eigensolver, SamplingVQE) o el Algoritmo Cuántico de Optimización Aproximada (Quantum Approximate Optimization Algorithm, QAOA) de Qiskit Algorithms para encontrar la solución óptima para un conjunto de parámetros dado.

Experimentos en hardware cuántico real para este problema se informan, por ejemplo, en el siguiente artículo: Improving Variational Quantum Optimization using CVaR. Barkoutsos et al. 2019.

[1]:
from qiskit.circuit.library import TwoLocal
from qiskit.result import QuasiDistribution
from qiskit_aer.primitives import Sampler
from qiskit_algorithms import NumPyMinimumEigensolver, QAOA, SamplingVQE
from qiskit_algorithms.optimizers import COBYLA
from qiskit_finance.applications.optimization import PortfolioOptimization
from qiskit_finance.data_providers import RandomDataProvider
from qiskit_optimization.algorithms import MinimumEigenOptimizer
import numpy as np
import matplotlib.pyplot as plt
import datetime

Definir la instancia del problema#

Aquí se crea una instancia de Operator para nuestro Hamiltoniano. En este caso, los paulis son de un Hamiltoniano de Ising traducido del problema de cartera. Usamos un problema de cartera aleatorio para este cuaderno. Es sencillo extender esto al uso de datos financieros reales como se ilustra aquí: Carga y Procesamiento de Datos de Series Temporales del Mercado de Valores

[2]:
# set number of assets (= number of qubits)
num_assets = 4
seed = 123

# Generate expected return and covariance matrix from (random) time-series
stocks = [("TICKER%s" % i) for i in range(num_assets)]
data = RandomDataProvider(
    tickers=stocks,
    start=datetime.datetime(2016, 1, 1),
    end=datetime.datetime(2016, 1, 30),
    seed=seed,
)
data.run()
mu = data.get_period_return_mean_vector()
sigma = data.get_period_return_covariance_matrix()
[3]:
# plot sigma
plt.imshow(sigma, interpolation="nearest")
plt.show()
../_images/tutorials_01_portfolio_optimization_5_0.png
[4]:
q = 0.5  # set risk factor
budget = num_assets // 2  # set budget
penalty = num_assets  # set parameter to scale the budget penalty term

portfolio = PortfolioOptimization(
    expected_returns=mu, covariances=sigma, risk_factor=q, budget=budget
)
qp = portfolio.to_quadratic_program()
qp
[4]:
<QuadraticProgram: minimize 0.001270694296030004*x_0^2 + 7.340221669347328e-05..., 4 variables, 1 constraints, 'Portfolio optimization'>

Definimos algunos métodos de utilidad para imprimir los resultados en un formato agradable.

[5]:
def print_result(result):
    selection = result.x
    value = result.fval
    print("Optimal: selection {}, value {:.4f}".format(selection, value))

    eigenstate = result.min_eigen_solver_result.eigenstate
    probabilities = (
        eigenstate.binary_probabilities()
        if isinstance(eigenstate, QuasiDistribution)
        else {k: np.abs(v) ** 2 for k, v in eigenstate.to_dict().items()}
    )
    print("\n----------------- Full result ---------------------")
    print("selection\tvalue\t\tprobability")
    print("---------------------------------------------------")
    probabilities = sorted(probabilities.items(), key=lambda x: x[1], reverse=True)

    for k, v in probabilities:
        x = np.array([int(i) for i in list(reversed(k))])
        value = portfolio.to_quadratic_program().objective.evaluate(x)
        print("%10s\t%.4f\t\t%.4f" % (x, value, v))

NumPyMinimumEigensolver (como una referencia clásica)#

Resolvamos el problema. Primero clásicamente…

Ahora podemos usar el Operator que construimos anteriormente sin tener en cuenta los detalles de cómo fue creado. Configuramos el algoritmo para el NumPyMinimumEigensolver para que podamos tener una referencia clásica. El problema está configurado para “ising”. No se requiere de un backend, ya que se calcula de forma clásica sin utilizar computación cuántica. El resultado se devuelve como un diccionario.

[6]:
exact_mes = NumPyMinimumEigensolver()
exact_eigensolver = MinimumEigenOptimizer(exact_mes)

result = exact_eigensolver.solve(qp)

print_result(result)
Optimal: selection [1. 0. 0. 1.], value -0.0149

----------------- Full result ---------------------
selection       value           probability
---------------------------------------------------
 [1 0 0 1]      -0.0149         1.0000

Solución usando SamplingVQE#

Ahora podemos usar el Solucionador Propio Variacional Cuántico de Muestreo (Sampling Variational Quantum Eigensolver, SamplingVQE) para resolver el problema. Especificaremos el optimizador y la forma variacional que se utilizará.

[7]:
from qiskit_algorithms.utils import algorithm_globals

algorithm_globals.random_seed = 1234

cobyla = COBYLA()
cobyla.set_options(maxiter=500)
ry = TwoLocal(num_assets, "ry", "cz", reps=3, entanglement="full")
svqe_mes = SamplingVQE(sampler=Sampler(), ansatz=ry, optimizer=cobyla)
svqe = MinimumEigenOptimizer(svqe_mes)
result = svqe.solve(qp)

print_result(result)
Optimal: selection [1. 0. 0. 1.], value -0.0149

----------------- Full result ---------------------
selection       value           probability
---------------------------------------------------
 [0 1 1 0]      0.0008          0.8525
 [1 0 0 1]      -0.0149         0.0410
 [0 0 1 1]      -0.0010         0.0312
 [0 0 0 1]      -0.0008         0.0215
 [1 0 1 1]      -0.0150         0.0195
 [1 0 0 0]      -0.0140         0.0088
 [0 1 1 1]      -0.0000         0.0078
 [0 1 0 1]      0.0002          0.0078
 [0 1 0 0]      0.0009          0.0059
 [1 0 1 0]      -0.0140         0.0020
 [1 1 0 1]      -0.0139         0.0010
 [0 0 0 0]      0.0000          0.0010

Solución usando QAOA#

También mostramos aquí un resultado utilizando el Algoritmo Cuántico de Optimización Aproximada (Quantum Approximate Optimization Algorithm, QAOA). Este es otro algoritmo variacional y utiliza una forma variacional interna que se crea en función del problema.

[8]:
algorithm_globals.random_seed = 1234

cobyla = COBYLA()
cobyla.set_options(maxiter=250)
qaoa_mes = QAOA(sampler=Sampler(), optimizer=cobyla, reps=3)
qaoa = MinimumEigenOptimizer(qaoa_mes)
result = qaoa.solve(qp)

print_result(result)
Optimal: selection [1. 0. 0. 1.], value -0.0149

----------------- Full result ---------------------
selection       value           probability
---------------------------------------------------
 [1 0 0 1]      -0.0149         0.1797
 [1 0 1 0]      -0.0140         0.1729
 [1 1 0 0]      -0.0130         0.1641
 [0 0 1 1]      -0.0010         0.1592
 [0 1 1 0]      0.0008          0.1553
 [0 1 0 1]      0.0002          0.1445
 [0 1 0 0]      0.0009          0.0049
 [1 1 0 1]      -0.0139         0.0039
 [1 1 1 1]      -0.0139         0.0039
 [0 0 0 0]      0.0000          0.0029
 [1 0 0 0]      -0.0140         0.0029
 [0 0 1 0]      -0.0001         0.0020
 [0 1 1 1]      -0.0000         0.0010
 [1 1 1 0]      -0.0130         0.0010
 [0 0 0 1]      -0.0008         0.0010
 [1 0 1 1]      -0.0150         0.0010
[9]:
import qiskit.tools.jupyter

%qiskit_version_table
%qiskit_copyright

Version Information

SoftwareVersion
qiskit0.45.0.dev0+ea871e0
qiskit_optimization0.6.0
qiskit_finance0.4.0
qiskit_aer0.12.2
qiskit_ibm_provider0.7.0
qiskit_algorithms0.3.0
System information
Python version3.9.7
Python compilerGCC 7.5.0
Python builddefault, Sep 16 2021 13:09:58
OSLinux
CPUs2
Memory (Gb)5.7784271240234375
Tue Sep 05 15:03:52 2023 EDT

This code is a part of Qiskit

© Copyright IBM 2017, 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.

[ ]: