Note
Cette page a été générée à partir de tutorials/finance/2_portfolio_diversification.ipynb.
Exécuter en mode interactif dans le IBM Quantum lab.
** Diversification de portefeuille **¶
Introduction¶
Dans la gestion des actifs, il y a généralement deux approches : la gestion active et passive de l’investissement. Dans le cadre de la gestion passive des investissements, il existe des fonds de suivi des indices et des approches basées sur la diversification des portefeuilles, qui visent à représenter un portefeuille avec un grand nombre d’actifs par un plus petit nombre de stocks représentatifs. Ce bloc-notes illustre un problème de diversification de portefeuille, qui est récemment devenu populaire pour deux raisons: 1. Il permet d’imiter la performance d’un indice (ou d’un ensemble similaire d’actifs) avec un budget limité, à des coûts de transaction limités. C’est-à-dire que le fonds de suivi d’indice peut acheter tous les actifs de l’indice, idéalement avec les mêmes poids que ceux de l’indice. Cela peut être peu pratique pour un certain nombre de raisons: le total même d’un lot à quantité unique par actif peut dépasser la valeur de l’actif sous gestion, la grande échelle du problème de suivi de l’indice avec des contraintes d’intégralité peut rendre le problème d’optimisation difficile, et les coûts de transaction du rééquilibrage fréquent pour ajuster les positions aux pondérations de l’indice peuvent rendre l’approche coûteuse. Ainsi, une approche populaire consiste à sélectionner un portefeuille de \(q\) actifs qui représentent le marché avec \(n\) actifs, où \(q\) est significativement plus petit que \(n\), mais où le portefeuille reproduit le comportement du marché sous-jacent. Pour déterminer comment regrouper des actifs dans \(q\) groupes et comment déterminer quels sont les \(q\) actifs qui doivent les représenter, il s’agit de résoudre un problème d’optimisation à grande échelle. Dans ce qui suit, nous décrirons le modèle mathématique du problème de diversification des portefeuilles tel qu’il a été introduit dans [Cornuejols & Tutuncu, 2006] 2. Il permet des mesures de similarité entre les séries chronologiques au-delà de la matrice de covariance. Notez que, traditionnellement, la théorie moderne du portefeuille considère la matrice de covariance comme une mesure de similitude entre les actifs. Par conséquent, la matrice de covariance est imparfaite. Prenons par exemple une société cotée à la fois à Londres et à New York. Même si les deux listes devraient être très semblables, seules des parties de la série chronologique des prix des deux listes se chevaucheront, en raison du chevauchement partiel des périodes d’ouverture des marchés. Au lieu de covariance, on peut considérer, par exemple, un gauchissement dynamique de [Berndt et Clifford, 1994] comme une mesure de la similarité entre deux séries temporelles, ce qui permet au fait que pour certaines périodes, les données ne sont capturées que par une seule série temporelle, alors que pour d’autres, les deux séries temporelles présentent une similitude en raison de l’évolution parallèle du cours boursier.
Le workflow global que nous démontrons comprend :
le choix de l’ensemble des actifs de base. Dans notre cas, il s’agit d’un petit nombre d’actions américaines.
charger la série temporelle représentant l’évolution des prix des actifs. Dans notre cas, il s’agit de données simplistes de clôture journalière ajustées depuis Wikipédia ou Nasdaq ou LSE ou EuroNext, alors que dans une gestion réelle des actifs, une fréquence beaucoup plus élevée peut être envisagée.
calculer la similitude par paire au sein de la série chronologique. Dans notre cas, nous exécutons une approximation linéaire, toujours sur l’ordinateur classique.
calculer le portefeuille réel de \(q\) actifs représentatifs, en se basant sur la mesure de similarité. Cette étape est exécutée deux fois, en fait. Tout d’abord, nous obtenons une valeur de référence par l’exécution d’un solveur IBM (IBM ILOG CPLEX ou l’exact Eigensolver) sur l’ordinateur classique. Ensuite, nous exécutons un algorithme alternatif hybride en partie sur l’ordinateur quantique.
visualisation des résultats, dans notre cas, il s’agit à nouveau d’un graphique simpliste.
Dans ce qui suit, nous expliquons d’abord le modèle utilisé ci-dessus (4), avant de procéder à l’installation des pré-requis et au chargement des données.
Le Modèle¶
Comme discuté dans [Cornuejols & Tutuncu, 2006], nous décrivons un modèle mathématique qui regroupe les actifs en groupes d’actifs similaires et qui sélectionne un actif représentatif de chaque groupe à inclure dans le portefeuille de fonds. Le modèle est basé sur les données suivantes, dont nous discuterons plus en détail plus tard :
Par exemple, \(\rho_{ii} = 1\), \(\rho_{ij} \leq 1\) pour \(i \neq j\) et \(\rho_{ij}\) est d’autant plus grand que les actions sont similaires. Un exemple de ceci est la corrélation entre les retours des actions \(i\) et \(j\). Mais on pourrait choisir d’autres indices de similitude pour \(\rho_{ij}\).
Le problème que nous voulons résoudre est :
sous réserve de la contrainte de mise en cluster:
et aux contraintes de cohérence :
ainsi que des contraintes intégrales :
Les variables \(y_j\) décrivent quelles actions \(j\) sont dans le fonds d’index (\(y_j = 1\) si \(j\) est sélectionné dans le fonds, \(0\) autrement). Pour chaque action \(i = 1,\dots,n\), la variable \(x_{ij}\) indique quelle action \(j\) dans le fonds d’index est la plus similaire à \(i\) (\(x_{ij} = 1\) si \(j\) est le titre le plus similaire dans le fonds d’index, \(0\) autrement).
La première contrainte sélectionne \(q\) actions dans le fonds. La seconde contrainte impose que chaque stock \(i\) ait exactement une action représentative \(j\) dans le fonds. Les troisième et quatrième contraintes garantissent que le stock \(i\) peut être représenté par le stock \(j\) seulement si \(j\) est dans le fonds. L’objectif du modèle maximise la similitude entre les stocks \(n\) et leurs représentants dans le fonds. Différentes fonctions de coût peuvent également être envisagées.
Concaténons les variables de décision dans un seul vecteur
dont la dimension est \({ \bf z } \in \ { 0 ,1 \ } ^ N\), avec \(N = n (n + 1)\) et dénote la solution optimale avec \({ \bf z } ^ * \) f ^ * `.
Une approche hybride¶
Here, we demonstrate an approach that combines classical and quantum computing steps, following the quantum approximate optimization approach of Farhi, Goldstone, and Gutmann (2014).
Construction d’une optimisation polynomiale binaire¶
A partir de :math:` (M) ` on peut construire une optimisation polynomiale binaire avec des contraintes d’égalité seulement, en substituant les contraintes d’inégalité :math:` x_{ij} leq y_j` avec les contraintes d’égalité équivalentes :math:` x_{ij} (1-y_j) = 0 `. Le problème devient alors:
sous réserve de la contrainte de mise en cluster, des contraintes intégrales et des contraintes de cohérence modifiées suivantes :
Construction de l’hamiltonien Ising¶
Nous pouvons maintenant construire l’hamiltonien Ising (QUBO) par des méthodes de pénalité (introduisant un coefficient de pénalité \(A\) pour chaque contrainte d’égalité) en tant que
De l’Hamiltonien à la formulation de programmation quadratique (QP)¶
Dans le vecteur \({ \bf z }\), les éléments hamiltoniens d’Ising peuvent être réécrits comme suit :
Premier terme :
Deuxième terme :
Troisième terme :
qui est équivalent à :
Quatrième terme :
Cinquième terme :
Par conséquent, la formulation devient :
qui peuvent être transmis à l’eigensolver quantique variationnel.
Références¶
[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, pp. 359-370).
L’implémentation¶
Tout d’abord, nous importons les modules requis.
[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
Ensuite, nous téléchargerons les données de prix pour deux actions et calculerons leur matrice de similitude par pair (distance dynamique de déformation temporelle normalisée à (0,1] en prenant la réciproque). Si cela échoue, par exemple si vous êtes hors ligne ou que vous dépassez la limite quotidienne d’accès aux données boursières, nous considérerons plutôt une matrice constante.
[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
Nous décidons maintenant du nombre de groupes, qui doit être plus petit que le nombre de stocks que nous avons chargés.
[3]:
q = 1 # q less or equal than n
Solution classique utilisant IBM ILOG CPLEX¶
Pour une solution classique, nous utilisons IBM ILOG CPLEX. CPLEX est capable de trouver la solution exacte pour ce problème. Nous définissons d’abord une classe ClassicalOptimizer qui code le problème d’une manière que CPLEX peut résoudre, puis nous instancions la classe et nous résolvons le problème.
[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()
La solution montre les stocks sélectionnés avec les étoiles et en vert les liens (via les similitudes) avec d’autres stocks qui sont représentés dans le fonds par le stock lié.
Informatique quantique avec IBM Q¶
Pour la solution quantique, nous utilisons Qiskit. Nous avons d’abord défini une classe QuantumOptimizer qui code l’approche quantique pour résoudre le problème, puis nous l’instancions et la résolvons. Nous définissons les méthodes suivantes dans la classe :
exact_solution
: pour s’assurer que le Hamiltonien Ising est correctement encodé dans la base \(Z\), nous pouvons calculer sa position propre de façon classique, c’est-à-dire en considérant une matrice symétrique de dimension \(2^N \times 2^N\). Pour le problème en question :math:` n=3 , c’est-à-dire :math: N = 12 `, semble être la limite pour de nombreux ordinateurs portables ;vqe_solution
: résout le problème :math:` (M) ` via l’eigensolver quantique variationnel (VQE) ;qaoa_solution
: résout le problème :math:` (M) ` via un algorithme QAOA (Quantum Approximate Optimization Algorithm).
[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
Étape 1¶
Instancions la classe d’optimiseur quantique avec les paramètres suivants : - la matrice de similarité : math:rho ; - le nombre de stocks et de groupes n
et q
;
[8]:
# Instantiate the quantum optimizer class with parameters:
quantum_optimizer = QuantumOptimizer(rho, n, q)
Étape 2¶
Encodons le problème en tant que formulation binaire (IH-QP).
Vérification : assurons-nous que la formulation binaire dans l’optimiseur quantique est correcte (c’est-à-dire qu’elle génère le même coût compte tenu de la même solution).
[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
Étape 3¶
Encode le problème en tant qu’hamiltonien Ising dans la base Z.
Vérification : assurons-nous que la formulation binaire dans l’optimiseur quantique est correcte (c’est-à-dire qu’elle génère le même coût compte tenu de la même solution)
[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
Étape 4¶
Résolvons le problème via VQE. Notez que selon le nombre de qubits, cela peut prendre un certain temps: pour 6 qubits il faut 15 minutes pour un Macbook Pro 2015, pour 12 qubits il faut plus de 12 heures. Pour les exécutions plus longues, le journaling peut être utile pour observer le fonctionnement ; sinon, il suffit d’attendre que la solution soit affichée.
[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.
Étape 5¶
Visualisons la solution
[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')

La solution montre les stocks sélectionnés via les étoiles et en vert les liens (via les similitudes) avec d’autres stocks qui sont représentés dans le fonds. Gardez à l’esprit que VQE est un travail heuristique sur la formulation QP du hamiltonien Ising. Pour les choix appropriés de A, l’optimisation locale de la formulation QP sera une solution réalisable pour l’ILP. Bien que pour quelques petites instances, comme ci-dessus, nous pouvons trouver des solutions optimales de la formulation QP qui coïncident avec optima de l’ILP, il est en général plus difficile de trouver des solutions optimales de l’ILP que de trouver les optima locales de la formulation QP. Même dans le VQE, on peut fournir des garanties plus fortes, pour des formes variationnelles spécifiques (fonctions d’onde d’essai).
[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.
[ ]: