Nota
Esta página foi gerada a partir de tutorials/finance/02_portfolio_diversification.ipynb.
Execute interativamente no IBM Quantum lab.
Diversificação do Portfólio¶
Introdução¶
Na gestão de ativos, existem basicamente duas abordagens: gestão de investimentos ativa e passiva. Na gestão passiva de investimentos, existem fundos index-tracking e abordagens baseadas na diversificação de carteiras, que visam representar uma carteira com um grande número de ativos por um menor número de ações representativas. Este caderno ilustra um problema de diversificação de portfólio, que recentemente se tornou popular por dois motivos: 1. torna possível imitar o desempenho de um índice (ou um conjunto igualmente grande de ativos) com um orçamento limitado, com custos de transação limitados. Ou seja: o rastreamento de índice tradicional pode comprar todos os ativos do índice, de preferência com os mesmos pesos do índice. Isso pode ser impraticável por uma série de razões: o total de até mesmo um único lote por ativo pode equivaler a mais do que os ativos sob gestão, a grande escala do problema de rastreamento de índice com restrições de integralidade pode tornar o problema de otimização difícil e os custos de transação do rebalanceamento frequente para ajustar o posições para os pesos no índice podem tornar a abordagem cara. Assim, uma abordagem popular é selecionar um portfólio de \(q\) ativos que representam o mercado com \(n\) ativos, onde :math: q é significativamente menor que \(n\), mas onde a carteira replica o comportamento do mercado subjacente. Para determinar como agrupar ativos em clusters :math: q e como determinar quais ativos \(q\) devem representar os clusters :math: q equivalem a resolver um problema de otimização em grande escala. A seguir, descrevemos o modelo matemático para o problema de diversificação de portfólio, conforme apresentado em [Cornuejols & Tutuncu, 2006] 2. ele permite medidas de similaridade entre séries temporais além da matriz de covariância. Observe que, tradicionalmente, a teoria de portfólio moderna considera a matriz de covariância como uma medida de similaridade entre os ativos. Como tal, no entanto, a matriz de covariância é imperfeita. Considere, por exemplo, uma empresa listada em Londres e Nova York. Embora ambas as listagens devam ser muito semelhantes, apenas partes da série temporal dos preços das duas listagens se sobreporão, devido à sobreposição parcial dos horários de abertura dos mercados. Em vez de covariância, pode-se considerar, por exemplo, a sincronização temporal dinâmica de [Berndt e Clifford, 1994] como uma medida de similaridade entre duas séries temporais, o que permite o fato de que, em alguns períodos de tempo, os dados são capturados por apenas um das séries temporais, enquanto para as demais, ambas as séries temporais apresentam a similaridade devido à evolução paralela do preço das ações.
O fluxo de trabalho global que demonstramos inclui:
escolha o conjunto de ativos. No nosso caso, este é um pequeno número de ações dos EUA.
carregue uma série temporal capturando a evolução dos preços dos ativos. No nosso caso, esta é uma carga simplista de dados de preços de fechamento diário ajustados da Wikipedia ou Nasdaq ou LSE ou EuroNext, Enquanto numa verdadeira gestão de ativos, pode considerar-se uma frequência muito mais elevada.
calcule a semelhança entre pares ao longo do tempo. No nosso caso, rodamos uma aproximação linear da sincronização temporal dinâmica, ainda no computador clássico.
execute o portfólio atual de ativos representativos do \(q\), com base na medida de similaridade. Esta etapa é executada duas vezes, na verdade. Primeiro, obtemos um valor de referência pelo método de execução de um solver IBM (IBM ILOG CPLEX ou o Exact Eigensolver) no computador clássico. Depois, executamos um algoritmo alternativo, híbrido, em parte no computador quântico.
visualização dos resultados. No nosso caso, este é novamente um plot simplista.
A seguir, primeiro explicamos o modelo usado em (4) acima, antes de prosseguirmos com a instalação dos pré-requisitos e o carregamento de dados.
O Modelo¶
Conforme discutido em [Cornuejols e Tutuncu, 2006], descrevemos um modelo matemático que agrupa ativos em grupos similares e seleciona um ativo representativo de cada grupo a ser incluído no portfólio de fundos de índice. O modelo é baseado nos seguintes dados, que discutiremos em mais detalhes depois:
Por exemplo, \(\rho_{ii} = 1\), \(\rho_{ij} \leq 1\) para \(i \neq j\) e \(\rho_{ij}\) é maior para ações mais similares. Um exemplo disso é a correlação entre os retornos das ações \(i\) e \(j\). Mas poderíamos escolher outros índices de similaridade \(\rho_{ij}\).
O problema que estamos interessados em resolver é:
sujeito à seguinte restrição de clustering:
às restrições de consistência:
e restrições integrais:
As variáveis \(y_j\) descrevem quais ações \(j\) estão no índice de fundos (\(y_j = 1\) se \(j\) estiver selecionado no fundo, \(0\) caso contrário). Para cada ação \(i = 1,\dots,n\), a variável \(x_{ij}\) indica qual ação \(j\) no fundo do índice é mais semelhante a \(i\) (\(x_{ij} = 1\) se \(j\) é a ação mais semelhante do índice de fundos, \(0\) caso contrário).
A primeira restrição seleciona \(q\) ações no fundo. A segunda restrição impõe que cada ação \(i\) tenha exatamente uma ação representativa \(j\) no fundo. A terceira e quarta restrições garantem que o estoque \(i\) pode ser representado pelo estoque \(j\) somente se \(j\) estiver no fundo. O objetivo do modelo maximiza a semelhança entre os \(n\) ações e seus representantes no fundo. As diferentes funções de custo também podem ser consideradas.
Vamos concatenar as variáveis da decisão em um vetor
cuja dimensão é \({\bf z} \in \{0,1\}^N\), com \(N = n (n+1)\) e denotar a solução ideal com \({\bf z}^*\), e o custo ideal \(f^*\).
Uma Abordagem Híbrida¶
Aqui, demonstramos uma abordagem que combina passos de computação clássica e quântica, seguindo a abordagem de otimização quântica aproximativa de Farhi, Goldstone e Gutman (2014).
Construir uma otimização binária de polinômios¶
A partir de \((M)\) pode-se construir uma otimização polinomial binária com restrições de igualdade apenas, por substituir as restrições de desigualdade \(x_{ij} \leq y_j\) com as restrições de igualdade equivalentes \(x_{ij} (1- y_j) = 0\). Então, o problema torna-se:
sujeito às restrições de agrupamento, às restrições integrais, e às seguintes restrições de consistência modificadas:
Construa o Ising Hamiltonian¶
Agora podemos construir um Ising Hamiltonian (QUBO) por métodos de penalização (introduzindo um coeficiente de penalidade \(A\) para cada restrição de igualdade) como
Da formulação de Hamiltonian para Quadratic Programming (QP)¶
No vetor \({\bf z}\), os elementos Ising Hamiltonian podem ser reescritos da seguinte forma,
Primeiro termo:
Segundo termo:
Terceiro termo:
que é equivalente a:
Quarto termo:
Quinto termo:
Portanto, a formulação torna-se,
que pode ser passado para o variational quantum eigensolver.
Referências¶
[1] G. Cornuejols, M. L. Fisher, and G. L. Nemhauser, Location of bank accounts to optimize float: an analytical study of exact and approximate algorithms, Management Science, vol. 23(8), 1997
[2] E. Farhi, J. Goldstone, S. Gutmann e-print arXiv 1411.4028, 2014
[3] G. Cornuejols and R. Tutuncu, Optimization methods in finance, 2006
[4] DJ. Berndt and J. Clifford, Using dynamic time warping to find patterns in time series. In KDD workshop 1994 (Vol. 10, No. 16, pág. 359-370).
A Implementação¶
Em primeiro lugar, importamos os módulos necessários.
[1]:
# Import requisite modules
import math
import operator
import logging
import traceback
import datetime
import sys
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# Import Qiskit packages
import qiskit
from qiskit import Aer
from qiskit.circuit.library import TwoLocal
from qiskit.aqua import QuantumInstance
from qiskit.aqua.algorithms import VQE, QAOA, NumPyMinimumEigensolver
from qiskit.aqua.components.optimizers import COBYLA
# setup aqua logging
from qiskit.aqua._logging import set_logging_config, build_logging_config
# set_logging_config(build_logging_config(logging.DEBUG)) # choose INFO, DEBUG to see the log
# The data providers of stock-market data
from qiskit.finance.data_providers import *
from qiskit.finance.applications.ising import portfolio_diversification
Em seguida, baixamos os dados de preço de duas ações e calculamos sua matriz de similaridade por pares (distância de deslocamento dinâmico normalizada para (0,1] tomando a reciprocidade). Se isso falhar, por exemplo, devido a você estar offline ou exceder o limite diário para acessos aos dados do mercado de ações, nós consideramos uma matriz constante em vez disso.
[2]:
# Generate a pairwise time-series similarity matrix
stocks = ["TICKER1", "TICKER2"]
n = len(stocks)
rho = np.ones((n,n))
rho[0,1] = 0.8
rho[1,0] = 0.8
data = RandomDataProvider(tickers = stocks,
start = datetime.datetime(2016,1,1),
end = datetime.datetime(2016,1,30))
data.run()
rho = data.get_similarity_matrix()
# Actually, we consider the additive inverse to invert the direction of optimisation.
rho = -1 * rho
Agora tomamos decisões sobre o número de agrupamentos, o que tem de ser menor do que o número de unidades de ação que temos carregado.
[3]:
q = 1 # q less or equal than n
Solução clássica usando o IBM ILOG CPLEX¶
Para uma solução clássica, nós usamos o IBM CPLEX. O CPLEX é capaz de encontrar a solução exata deste problema. Nós definimos uma classe ClassicalOptimizer que codifica o problema de uma forma que o CPLEX possa resolver e, em seguida, instanciamos a classe e a resolvemos.
[4]:
class ClassicalOptimizer:
def __init__(self, rho, n, q):
self.rho = rho
self.n = n # number of inner variables
self.q = q # number of required selection
def compute_allowed_combinations(self):
f = math.factorial
return int(f(self.n) / f(self.q) / f(self.n - self.q))
def cplex_solution(self):
# refactoring
rho = self.rho
n = self.n
q = self.q
my_obj = list(rho.reshape(1, n ** 2)[0]) + [0. for x in range(0, n)]
my_ub = [1 for x in range(0, n ** 2 + n)]
my_lb = [0 for x in range(0, n ** 2 + n)]
my_ctype = "".join(['I' for x in range(0, n ** 2 + n)])
my_rhs = [q] + [1 for x in range (0, n)] +[0 for x in range (0, n)] + [0.1 for x in range(0, n ** 2)]
my_sense = "".join(['E' for x in range(0, 1+n)]) + "".join(['E' for x in range(0, n)]) + "".join(
['L' for x in range(0, n ** 2)])
try:
my_prob = cplex.Cplex()
self.populatebyrow(my_prob, my_obj, my_ub, my_lb, my_ctype, my_sense, my_rhs)
my_prob.solve()
except CplexError as exc:
print(exc)
return
x = my_prob.solution.get_values()
x = np.array(x)
cost = my_prob.solution.get_objective_value()
return x, cost
def populatebyrow(self, prob, my_obj, my_ub, my_lb, my_ctype, my_sense, my_rhs):
n = self.n
prob.objective.set_sense(prob.objective.sense.minimize)
prob.variables.add(obj=my_obj, lb=my_lb, ub=my_ub, types=my_ctype)
prob.set_log_stream(None)
prob.set_error_stream(None)
prob.set_warning_stream(None)
prob.set_results_stream(None)
rows = []
col = [x for x in range(n**2, n**2+n)]
coef = [1 for x in range(0, n)]
rows.append([col, coef])
for ii in range(0, n):
col = [x for x in range(0+n*ii, n+n*ii)]
coef = [1 for x in range(0, n)]
rows.append([col, coef])
for ii in range(0, n):
col = [ii * n + ii, n ** 2 + ii]
coef = [1, -1]
rows.append([col, coef])
for ii in range(0, n):
for jj in range(0, n):
col = [ii*n + jj, n ** 2 + jj]
coef = [1, -1]
rows.append([col, coef])
prob.linear_constraints.add(lin_expr=rows, senses=my_sense, rhs=my_rhs)
[5]:
# Instantiate the classical optimizer class
classical_optimizer = ClassicalOptimizer(rho, n, q)
# Compute the number of feasible solutions:
print('Number of feasible combinations= ' + str(classical_optimizer.compute_allowed_combinations()))
# Compute the total number of possible combinations (feasible + unfeasible)
print('Total number of combinations= ' + str(2 ** (n*(n+1))))
Number of feasible combinations= 2
Total number of combinations= 64
[6]:
# Visualize the solution
def visualize_solution(xc, yc, x, C, n, K, title_str):
plt.figure()
plt.scatter(xc, yc, s=200)
for i in range(len(xc)):
plt.annotate(i, (xc[i] + 0.015, yc[i]), size=16, color='r')
plt.grid()
for ii in range(n ** 2, n **2 + n):
if x[ii] > 0:
plt.plot(xc[ii-n**2], yc[ii-n**2], 'r*', ms=20)
for ii in range(0, n ** 2):
if x[ii] > 0:
iy = ii // n
ix = ii % n
plt.plot([xc[ix], xc[iy]], [yc[ix], yc[iy]], 'C2')
plt.title(title_str +' cost = ' + str(int(C * 100) / 100.))
plt.show()
Solução mostra os estoques selecionados através das estrelas e em verde os links (via semelhanças) com outros estoques que são representados no fundo pelo estoque vinculado.
Computação quântica com IBM Q¶
Para a solução quântica, nós usamos o Qiskit. Definimos primeiro uma classe com QuantumOptimizer que codifica a abordagem quântica para resolver o problema e, em seguida, vamos instanciá-la e resolvê-la. Definimos os seguintes métodos dentro da classe:
exact_solution
: para se certificar de que a Ising Hamiltonian esteja corretamente codificada na base de \(Z\), podemos calcular sua eigendecomposition clássica, ou seja, considerando uma matriz simétrica de dimensão \(2^N \times 2^N\). Para o problema que temos \(n=3\), ou seja, \(N = 12\), parece ser o limite para muitos laptops;vqe_solution
: resolve o problema \((M)\) através da variational quantum eigensolver (VQE);qaoa_solution
: resolve o problema \((M)\) através de um Quantum Approximate Optimization Algorithm (QAOA).
[7]:
from qiskit.aqua.operators import StateFn
class QuantumOptimizer:
def __init__(self, rho, n, q):
self.rho = rho
self.n = n
self.q = q
# Obtains the least eigenvalue of the Hamiltonian classically
def exact_solution(self):
qubitOp = portfolio_diversification.get_operator(self.rho, self.n, self.q)
result = NumPyMinimumEigensolver(qubitOp).run()
return self.decode_result(result)
def vqe_solution(self):
qubitOp = portfolio_diversification.get_operator(self.rho, self.n, self.q)
backend = Aer.get_backend('statevector_simulator')
seed = 50
cobyla = COBYLA()
cobyla.set_options(maxiter=250)
ry = TwoLocal(qubitOp.num_qubits, 'ry', 'cz', reps=5, 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)
return self.decode_result(result)
def qaoa_solution(self):
qubitOp = portfolio_diversification.get_operator(self.rho, self.n, self.q)
backend = Aer.get_backend('statevector_simulator')
seed = 50
cobyla = COBYLA()
cobyla.set_options(maxiter=250)
qaoa = QAOA(qubitOp, cobyla, 3, 'matrix')
qaoa.random_seed = seed
quantum_instance = QuantumInstance(backend=backend, seed_simulator=seed, seed_transpiler=seed)
result = qaoa.run(quantum_instance)
return self.decode_result(result)
def get_portfoliodiversification_solution(self, n, result):
v = result.eigenstate
if isinstance(v, StateFn):
v = v.to_matrix()
N = n ** 2 + n
index_value = [x for x in range(len(v)) if v[x] == max(v)][0]
string_value = "{0:b}".format(index_value)
while len(string_value) < N:
string_value = '0' + string_value
x_state = list()
for elements in string_value:
if elements == '0':
x_state.append(0)
else:
x_state.append(1)
x_state = np.flip(x_state, axis=0)
return x_state
def decode_result(self, result, offset = 0):
quantum_solution = self.get_portfoliodiversification_solution(self.n, result)
ground_level = portfolio_diversification.get_portfoliodiversification_value(self.rho, self.n, self.q, quantum_solution)
return quantum_solution, ground_level
Passo 1¶
Instancie a classe otimizadora quântica com parâmetros: - a matriz de similaridade rho
; - o número de ativos e agrupamentos n
e q
;
[8]:
# Instantiate the quantum optimizer class with parameters:
quantum_optimizer = QuantumOptimizer(rho, n, q)
Passo 2¶
Codifique o problema como uma formulação binária (IH-QP).
Verificação de sanidade: certifique-se de que a formulação binária no otimizador quântico esteja correta (ou seja, produz o mesmo custo dada a mesma solução).
[9]:
# Check if the binary representation is correct. This requires CPLEX
try:
import cplex
#warnings.filterwarnings('ignore')
quantum_solution, quantum_cost = quantum_optimizer.exact_solution()
classical_solution, classical_cost = classical_optimizer.cplex_solution()
print(quantum_cost, classical_cost)
if np.abs(quantum_cost - classical_cost) < 0.01:
print('Binary formulation is correct')
else: print('Error in the formulation of the Hamiltonian')
except Exception as ex:
print(ex)
-1.0008499151717842 -1.0008499151721015
Binary formulation is correct
Passo 3¶
Codifique o problema como um Ising Hamiltonian na base Z.
Verificação de sanidade: certifique-se de que a formulação esteja correta (ou seja, produz o mesmo custo dada a mesma solução)
[10]:
ground_state, ground_level = quantum_optimizer.exact_solution()
print(ground_state)
try:
if np.abs(ground_level - classical_cost)<0.01:
print('Ising Hamiltonian in Z basis is correct')
else: print('Error in the Ising Hamiltonian formulation')
except Exception as ex:
print(ex)
[0 1 0 1 0 1]
Ising Hamiltonian in Z basis is correct
Passo 4¶
Resolva o problema via VQE. Observe que dependendo do número de qubits, isso pode demorar um pouco: para 6 qubits pode demorar 15 minutos em um Macbook Pro 2015, para 12 qubits demora mais de 12 horas. Para execuções mais longas, ligar logs pode ser útil para observar os trabalhos; caso contrário, você só tem que esperar até que a solução seja impressa.
[11]:
vqe_state, vqe_level = quantum_optimizer.vqe_solution()
print(vqe_state)
try:
if np.linalg.norm(ground_state - vqe_state)<0.01:
print('VQE produces the same solution as the exact eigensolver.')
else: print('VQE does not produce the same solution as the exact eigensolver, but that is to be expected.')
except Exception as ex:
print(ex)
[0 1 0 1 0 1]
VQE produces the same solution as the exact eigensolver.
Passo 5¶
Visualize a solução
[12]:
xc, yc = data.get_coordinates()
[13]:
visualize_solution(xc, yc, ground_state, ground_level, n, q, 'Classical')

[14]:
visualize_solution(xc, yc, vqe_state, vqe_level, n, q, 'VQE')

A solução mostra as ações selecionadas através das estrelas e de verde os links (via similaridades) com outras ações que são representadas no fundo pelas ações vinculadas. Tenha em mente que o VQE é uma heurística que trabalha na formulação do PQ do Ising Hamiltonian. Para escolhas adequadas de A, ótimos locais da formulação da CPQ serão soluções viáveis para o ILP. Enquanto em algumas pequenas instâncias, como acima, podemos encontrar soluções ideais para a formulação da QP que coincidem com a opacidade do ILP, encontrar soluções ideais do ILP é mais difícil do que encontrar a opacidade local na formulação da PQ, em geral. Mesmo dentro do VQE, pode-se oferecer garantias mais fortes, para formulários variacionais específicos (funções de ondas de teste).
[15]:
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:38:32 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.
[ ]: