Nota
Esta página foi gerada, a partir de tutorials/finance/01_portfolio_optimization.ipynb.
Execute interativamente no IBM Quantum lab.
Otimização de Portfólio¶
Introdução¶
Este tutorial mostra como resolver o seguinte problema de otimização de portfólio de média-variância para \(n\) ativos:
\(\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}\)
onde utilizamos a seguinte notação:
\(x \in \{0, 1\}^n\) denota um vetor de decisões binárias variáveis, que indica quais ativos escolher (\(x[i] = 1\)) e quais ativos não escolher (\(x[i] = 0\)),
\(\mu \in \mathbb{R}^n\) define os retornos esperados para os ativos,
\(\Sigma \in \mathbb{R}^{n \times n}\) especifica as covariâncias entre os ativos,
\(q > 0\) controla o apetite de risco do tomador de decisão,
e \(B\) denota o orçamento, ou seja, o número de ativos a serem selecionados de \(n\).
Assumimos as seguintes simplificações: - todos os ativos têm o mesmo preço (normalizado para 1), - todo o orçamento \(B\) tem de ser gasto, ou seja, devemos selecionar exatamente \(B\) ativos.
A restrição de igualdade :math:` 1 ^ T x = B ` é mapeada para um termo de pênalti :math:` (1 ^ T x-B) ^ 2 ` que é escalonado por um parâmetro e subtraído da função objetiva. O problema resultante pode ser mapeado para um Hamiltonian cujo estado fundamental corresponde à solução ideal. Este notebook mostra como utilizar o Variational Quantum Eigensolver (VQE) ou o Quantum Approximate Optimization Algorithm (QAOA) para encontrar a solução ideal para um determinado conjunto de parâmetros.
Experimentos realizados em hardware quântico real para este problema são relatados por exemplo no seguinte artigo: Improving Variational Quantum Optimization using CVaR. Barkoutsos et al. 2019.
[1]:
from qiskit import Aer
from qiskit.circuit.library import TwoLocal
from qiskit.aqua import QuantumInstance
from qiskit.finance.applications.ising import portfolio
from qiskit.optimization.applications.ising.common import sample_most_likely
from qiskit.finance.data_providers import RandomDataProvider
from qiskit.aqua.algorithms import VQE, QAOA, NumPyMinimumEigensolver
from qiskit.aqua.components.optimizers import COBYLA
import numpy as np
import matplotlib.pyplot as plt
import datetime
[Optional] Configuração do token para executar o experimento em um dispositivo real¶
Caso deseje executar o experimento em um dispositivo real, você precisa configurar a sua conta primeiro.
Nota: Se você ainda não armazenou seu token, use IBMQ.save_account('MY_API_TOKEN')
para armazená-lo primeiro.
Definindo uma instância do problema¶
Aqui, uma instância da Operator é criada para o nosso Hamiltoniano. Neste caso, os paulis são de um Hamiltoniano de Ising traduzido do problema de portfólio. Utilizamos um problema de portfólio aleatório para este notebook. É fácil estender isto para usar dados financeiros reais como ilustrado aqui: Carregando e Processando Dados de Séries Temporais do Mercado de Ações
[2]:
# set number of assets (= number of qubits)
num_assets = 4
# 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))
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()

[4]:
q = 0.5 # set risk factor
budget = num_assets // 2 # set budget
penalty = num_assets # set parameter to scale the budget penalty term
qubitOp, offset = portfolio.get_operator(mu, sigma, q, budget, penalty)
Definimos alguns métodos utilitários para imprimir os resultados em um formato agradável.
[5]:
def index_to_selection(i, num_assets):
s = "{0:b}".format(i).rjust(num_assets)
x = np.array([1 if s[i]=='1' else 0 for i in reversed(range(num_assets))])
return x
def print_result(result):
selection = sample_most_likely(result.eigenstate)
value = portfolio.portfolio_value(selection, mu, sigma, q, budget, penalty)
print('Optimal: selection {}, value {:.4f}'.format(selection, value))
eigenvector = result.eigenstate if isinstance(result.eigenstate, np.ndarray) else result.eigenstate.to_matrix()
probabilities = np.abs(eigenvector)**2
i_sorted = reversed(np.argsort(probabilities))
print('\n----------------- Full result ---------------------')
print('selection\tvalue\t\tprobability')
print('---------------------------------------------------')
for i in i_sorted:
x = index_to_selection(i, num_assets)
value = portfolio.portfolio_value(x, mu, sigma, q, budget, penalty)
probability = probabilities[i]
print('%10s\t%.4f\t\t%.4f' %(x, value, probability))
NumPyMinimumEigensolver (como referência clássica)¶
Vamos resolver o problema. Primeiro, classicamente…
Podemos, agora, utilizar o Operator que criamos acima, sem considerar as particularidades de como foi criado. Definimos o algoritmo como o NumPyMinimumEigensolver, para que possamos ter uma referência clássica. O problema está configurado como ‘ising’. Um backend não é necessário, uma vez que isto é computado classicamente, e não usando computação quântica. O resultado é retornado como um dicionário.
[6]:
exact_eigensolver = NumPyMinimumEigensolver(qubitOp)
result = exact_eigensolver.run()
print_result(result)
Optimal: selection [0 1 0 1], value -0.0005
----------------- Full result ---------------------
selection value probability
---------------------------------------------------
[0 1 0 1] -0.0005 1.0000
[1 1 1 1] 16.0040 0.0000
[0 1 1 1] 4.0013 0.0000
[1 0 1 1] 4.0052 0.0000
[0 0 1 1] 0.0025 0.0000
[1 1 0 1] 4.0023 0.0000
[1 0 0 1] 0.0034 0.0000
[0 0 0 1] 4.0007 0.0000
[1 1 1 0] 4.0033 0.0000
[0 1 1 0] 0.0007 0.0000
[1 0 1 0] 0.0045 0.0000
[0 0 1 0] 4.0018 0.0000
[1 1 0 0] 0.0016 0.0000
[0 1 0 0] 3.9988 0.0000
[1 0 0 0] 4.0027 0.0000
[0 0 0 0] 16.0000 0.0000
Solução usando VQE¶
Agora, podemos usar o Variational Quantum Eigensolver (VQE) para resolver o problema. Vamos especificar o otimizador e a forma variacional a serem usados.
Observação: Você pode mudar para serviços internos diferentes fornecendo o nome do serviço.
[7]:
backend = Aer.get_backend('statevector_simulator')
seed = 50
cobyla = COBYLA()
cobyla.set_options(maxiter=500)
ry = TwoLocal(qubitOp.num_qubits, 'ry', 'cz', reps=3, entanglement='full')
vqe = VQE(qubitOp, ry, cobyla)
vqe.random_seed = seed
quantum_instance = QuantumInstance(backend=backend, seed_simulator=seed, seed_transpiler=seed)
result = vqe.run(quantum_instance)
print_result(result)
Optimal: selection [0. 0. 1. 1.], value 0.0025
----------------- Full result ---------------------
selection value probability
---------------------------------------------------
[0 0 1 1] 0.0025 0.7519
[0 1 1 0] 0.0007 0.2480
[1 1 0 1] 4.0023 0.0000
[1 1 0 0] 0.0016 0.0000
[1 0 0 1] 0.0034 0.0000
[0 1 0 1] -0.0005 0.0000
[1 1 1 0] 4.0033 0.0000
[1 0 0 0] 4.0027 0.0000
[1 0 1 1] 4.0052 0.0000
[1 0 1 0] 0.0045 0.0000
[0 0 1 0] 4.0018 0.0000
[0 1 0 0] 3.9988 0.0000
[0 0 0 1] 4.0007 0.0000
[0 1 1 1] 4.0013 0.0000
[0 0 0 0] 16.0000 0.0000
[1 1 1 1] 16.0040 0.0000
Solução usando QAOA¶
Também mostramos, aqui, um resultado usando o Algoritmo de Otimização Aproximada Quântico (QAOA). Este é outro algoritmo variacional e ele usa uma forma variacional interna, que é criada com base no problema.
[8]:
backend = Aer.get_backend('statevector_simulator')
seed = 50
cobyla = COBYLA()
cobyla.set_options(maxiter=250)
qaoa = QAOA(qubitOp, cobyla, 3)
qaoa.random_seed = seed
quantum_instance = QuantumInstance(backend=backend, seed_simulator=seed, seed_transpiler=seed)
result = qaoa.run(quantum_instance)
print_result(result)
Optimal: selection [0. 1. 0. 1.], value -0.0005
----------------- Full result ---------------------
selection value probability
---------------------------------------------------
[0 1 0 1] -0.0005 0.1673
[0 1 1 0] 0.0007 0.1670
[1 1 0 0] 0.0016 0.1668
[0 0 1 1] 0.0025 0.1666
[1 0 0 1] 0.0034 0.1663
[1 0 1 0] 0.0045 0.1661
[1 1 1 1] 16.0040 0.0000
[0 0 0 0] 16.0000 0.0000
[0 1 1 1] 4.0013 0.0000
[0 1 0 0] 3.9988 0.0000
[1 1 0 1] 4.0023 0.0000
[1 0 1 1] 4.0052 0.0000
[1 0 0 0] 4.0027 0.0000
[1 1 1 0] 4.0033 0.0000
[0 0 0 1] 4.0007 0.0000
[0 0 1 0] 4.0018 0.0000
[9]:
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright
Version Information
Qiskit Software | Version |
---|---|
Qiskit | 0.19.1 |
Terra | 0.14.1 |
Aer | 0.5.1 |
Ignis | 0.3.0 |
Aqua | 0.7.0 |
IBM Q Provider | 0.7.0 |
System information | |
Python | 3.7.4 (default, Aug 13 2019, 15:17:50) [Clang 4.0.1 (tags/RELEASE_401/final)] |
OS | Darwin |
CPUs | 6 |
Memory (Gb) | 16.0 |
Fri Jul 17 17:36:55 2020 CEST |
This code is a part of Qiskit
© Copyright IBM 2017, 2020.
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.
[ ]: