Skip to main content
IBM Quantum Platform

Quantum Portfolio Optimizer : une fonction Qiskit développée par Global Data Quantum

Consultez la documentation de l'API

Note

Les fonctions Qiskit sont une fonctionnalité expérimentale disponible uniquement pour IBM Quantum® Premium Plan, Flex Plan, et On-Prem (via IBM Quantum Platform API). Elles sont en cours de publication et peuvent être modifiées.


Aperçu

Le Quantum Portfolio Optimizer est une fonction Qiskit qui s'attaque au problème de l'optimisation dynamique de portefeuille, un problème standard en finance qui vise à rééquilibrer les investissements périodiques à travers un ensemble d'actifs, afin de maximiser les rendements et de minimiser les risques. En déployant des techniques d'optimisation quantique de pointe, cette fonction simplifie le processus de sorte que les utilisateurs, sans expertise en informatique quantique, puissent bénéficier de ses avantages pour trouver des trajectoires d'investissement optimales. Idéal pour les gestionnaires de portefeuille, les chercheurs en finance quantitative et les investisseurs individuels, cet outil permet de tester les stratégies de trading dans le cadre de l'optimisation de portefeuille.


Description de la fonction

La fonction Quantum Portfolio Optimizer utilise l'algorithme Variational Quantum Eigensolver (VQE) pour résoudre un problème d'optimisation binaire quadratique sans contrainte (QUBO), traitant des problèmes d'optimisation dynamique de portefeuille. Les utilisateurs doivent simplement fournir les données relatives au prix des actifs et définir la contrainte d'investissement, puis la fonction exécute le processus d'optimisation quantique qui renvoie un ensemble de trajectoires d'investissement optimisées.

Le processus comprend quatre étapes principales. Tout d'abord, les données d'entrée sont transposées en un problème compatible avec l'informatique quantique, ce qui permet de construire le QUBO du problème d'optimisation dynamique de portefeuille et de le transformer en un opérateur quantique (hamiltonien d'Ising). Ensuite, le problème d'entrée et l'algorithme VQE sont adaptés pour pouvoir être exécutés sur le matériel quantique. L'algorithme VQE est ensuite exécuté sur le matériel quantique, puis les résultats sont traités afin de déterminer les trajectoires d'investissement optimales. Le système intègre également un post-traitement tenant compte du bruit ( basé sur la technique SQD) afin d'optimiser la qualité du résultat.

Cette fonction Qiskit est basée sur le manuscrit publié par Global Data Quantum.

Visualisation du déroulement de la fonction

Premiers pas

Connectez-vous à l'aide de votre clé API, puis sélectionnez la fonction Qiskit comme suit. (Cet extrait de code part du principe que vous avez déjà enregistré votre compte dans votre environnement local.)

from qiskit_ibm_catalog import QiskitFunctionsCatalog

catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")

# Verify that you have access to the function
catalog.list()

Output:

[QiskitFunction(qunova/hivqe-chemistry),
 QiskitFunction(global-data-quantum/quantum-portfolio-optimizer),
 QiskitFunction(algorithmiq/tem),
 QiskitFunction(qedma/qesem),
 QiskitFunction(multiverse/singularity),
 QiskitFunction(ibm/circuit-function),
 QiskitFunction(q-ctrl/optimization-solver),
 QiskitFunction(colibritd/quick-pde),
 QiskitFunction(q-ctrl/performance-management),
 QiskitFunction(kipu-quantum/iskay-quantum-optimizer)]
# Access function
dpo_solver = catalog.load("global-data-quantum/quantum-portfolio-optimizer")

Exemple : optimisation dynamique d'un portefeuille comprenant sept actifs

Cet exemple montre comment exécuter la fonction d'optimisation dynamique de portefeuille (DPO) et ajuster ses paramètres pour une performance optimale. Il comprend des étapes détaillées pour affiner les paramètres afin d'obtenir les résultats souhaités.

Ce cas implique sept actifs, quatre pas de temps et quatre qubits de résolution, soit un besoin total de 112 qubits.

1. Consultez les éléments inclus dans le portefeuille

Si tous les actifs du portefeuille sont stockés dans un dossier à un emplacement spécifique, vous pouvez les charger dans un site pandas.DataFrame et les convertir en un objet au format dict à l'aide de la fonction suivante.

import os
import glob
import pandas as pd


def read_and_join_csv(file_pattern):
    """
    Reads multiple CSV files matching the file pattern and combines them
    into a single DataFrame.

    Parameters:
    file_pattern (str): The pattern to match CSV files.

    Returns:
    pd.DataFrame: Combined DataFrame with data from all CSV files.
    """
    # Find all files matching the pattern
    csv_files = glob.glob(file_pattern)
    # Get the base file names without the .csv extension
    file_names = [os.path.basename(f).replace(".csv", "") for f in csv_files]
    # Read each CSV file into a DataFrame and set the first column as the index
    df_list = [pd.read_csv(f).set_index("Unnamed: 0") for f in csv_files]

    # Rename columns in each DataFrame to the base file names
    for df, name in zip(df_list, file_names):
        df.columns = [name]

    # Combine all DataFrames into one by merging them side by side
    combined_df = pd.concat(df_list, axis=1)
    return combined_df


file_pattern = "route/to/folder/with/assets/data/*.csv"
assets = read_and_join_csv(file_pattern).to_dict()

Pour cet exemple, nous avons utilisé les actifs 8801.T, CLF, GBPJPY, ITX.MC, META, TMBMKDE-10Y et XS2239553048. La figure suivante illustre les données utilisées dans cet exemple, montrant l'évolution du prix de clôture quotidien des actifs du 1er janvier au 1er septembre 2023.

Dans cet exemple, pour assurer l'uniformité entre les dates, nous avons rempli les jours non négociés avec le prix de clôture de la date disponible précédente. Nous appliquons cette étape parce que les actifs sélectionnés proviennent de différents marchés dont les jours de négociation varient, ce qui rend essentielle la normalisation de l'ensemble de données à des fins de cohérence.

Visualisation des données historiques des actifs

2. Définir le problème

Définir les spécifications du problème en configurant les paramètres dans le dictionnaire qubo_settings .

qubo_settings = {
    "nt": 4,
    "nq": 4,
    "dt": 30,
    "max_investment": 25,
    "risk_aversion": 1000.0,
    "transaction_fee": 0.01,
    "restriction_coeff": 1.0,
}

3. Définir les paramètres de l'optimiseur et de l'ansatz (facultatif)

Définir éventuellement des exigences spécifiques pour le processus d'optimisation, y compris la sélection de l'optimiseur et de ses paramètres, ainsi que la spécification de la primitive et de ses configurations.

Pour l'Ansatz adapté, la taille de la population choisie est basée sur des expériences antérieures montrant que cette valeur permet une optimisation stable et efficace.

Dans le cas de l'Ansatz des amplitudes réelles, vous pouvez suivre une relation linéaire entre population_size et le nombre de qubits dans le circuit. En règle générale, il est recommandé d'utiliser un minimum de population_size ~ 0.8 * n_qubits pour l'ansatz real_amplitudes .

On s'attend à ce que les Amplitudes réelles optimisées aient une meilleure performance d'optimisation que l'ansatz des Amplitudes réelles. Cependant, le nombre de variables à optimiser dans cet ansatz augmente beaucoup plus rapidement que dans le cas des amplitudes réelles (voir le manuscrit ). Ainsi, pour les problèmes de grande taille, la méthode des amplitudes réelles optimisées nécessite un plus grand nombre d'exécutions de circuits. L'option Optimized Real Amplitudes sera probablement utile pour les problèmes nécessitant jusqu'à 100 qubits, mais il est recommandé de faire attention lors du réglage des paramètres population_size . Pour illustrer cette augmentation d'échelle dans population_size, le tableau précédent montre que pour un problème de 84 qubits, l'optimisation des amplitudes réelles nécessite 120 population_size, alors que pour un problème de 56 qubits, une population_size de 40 est suffisante.

optimizer_settings = {
    "de_optimizer_settings": {
        "num_generations": 20,
        "population_size": 90,
        "recombination": 0.4,
        "max_parallel_jobs": 5,
        "max_batchsize": 4,
        "mutation_range": [0.0, 0.25],
    },
    "optimizer": "differential_evolution",
    "primitive_settings": {
        "estimator_shots": 25_000,
        "estimator_precision": None,
        "sampler_shots": 100_000,
    },
}

Il est également possible de choisir un ansatz spécifique. La suite utilise l'ansatz 'Tailored' .

ansatz_settings = {
    "ansatz": "tailored",
    "multiple_passmanager": False,
}

4. Exécutez le programme

dpo_job = dpo_solver.run(
    assets=assets,
    qubo_settings=qubo_settings,
    optimizer_settings=optimizer_settings,
    ansatz_settings=ansatz_settings,
    backend_name="<backend name>",
    previous_session_id=[],
    apply_postprocess=True,
)

5. Afficher les résultats

La fonction renvoie un dictionnaire contenant les trajectoires d'investissement classées de la plus faible à la plus élevée en fonction de la valeur de leur fonction objectif (voir la section « Résultats » de la documentation de l'API). Cet ensemble de résultats permet d'identifier le scénario le moins coûteux et les analyses d'investissement qui s'y rapportent. De plus, il permet d'analyser différentes trajectoires, ce qui facilite le choix de celles qui correspondent le mieux à des besoins ou à des objectifs spécifiques. Cette flexibilité permet d'adapter les choix à une grande variété de préférences ou de situations.

Commencez par présenter la stratégie qui a permis d'atteindre le coût objectif le plus bas constaté au cours du processus.

# Get the results of the job
dpo_result = dpo_job.result()

# Show the solution strategy
dpo_result["result"]

Output:

{'time_step_0': {'8801.T': 0.11764705882352941,
  'ITX.MC': 0.20588235294117646,
  'META': 0.38235294117647056,
  'GBPJPY=X': 0.058823529411764705,
  'TMBMKDE-10Y': 0.0,
  'CLF': 0.058823529411764705,
  'XS2239553048': 0.17647058823529413},
 'time_step_1': {'8801.T': 0.11428571428571428,
  'ITX.MC': 0.14285714285714285,
  'META': 0.2,
  'GBPJPY=X': 0.02857142857142857,
  'TMBMKDE-10Y': 0.42857142857142855,
  'CLF': 0.0,
  'XS2239553048': 0.08571428571428572},
 'time_step_2': {'8801.T': 0.0,
  'ITX.MC': 0.09375,
  'META': 0.3125,
  'GBPJPY=X': 0.34375,
  'TMBMKDE-10Y': 0.0,
  'CLF': 0.0,
  'XS2239553048': 0.25},
 'time_step_3': {'8801.T': 0.3939393939393939,
  'ITX.MC': 0.09090909090909091,
  'META': 0.12121212121212122,
  'GBPJPY=X': 0.18181818181818182,
  'TMBMKDE-10Y': 0.0,
  'CLF': 0.0,
  'XS2239553048': 0.21212121212121213}}

Ensuite, en utilisant les métadonnées, vous pouvez accéder aux résultats de toutes les stratégies échantillonnées. Vous pouvez ainsi analyser plus en détail les trajectoires alternatives renvoyées par l'optimiseur. Pour ce faire, il faut lire le dictionnaire stocké dans dpo_result['metadata']['all_samples_metrics'], qui contient non seulement des informations supplémentaires sur la stratégie optimale, mais aussi des détails sur les autres stratégies candidates évaluées au cours de l'optimisation.

L'exemple suivant montre comment lire ces informations à l'aide de pandas pour extraire les mesures clés associées à la stratégie optimale. Il s'agit notamment de l'écart de restriction, du ratio de Sharpe et du rendement de l'investissement correspondant.

# Convert metadata to a DataFrame
df = pd.DataFrame(dpo_result["metadata"]["all_samples_metrics"])

# Find the minimum objective cost
min_cost = df["objective_costs"].min()
print(f"Minimum Objective Cost Found: {min_cost:.2f}")

# Extract the row with the lowest cost
best_row = df[df["objective_costs"] == min_cost].iloc[0]

# Display the results associated with the best solution
print("Best Solution:")
print(f"  - Restriction Deviation: {best_row['rest_breaches']}%")
print(f"  - Sharpe Ratio: {best_row['sharpe_ratios']:.2f}")
print(f"  - Return: {best_row['returns']}")

Output:

Minimum Objective Cost Found: -3.78
Best Solution:
  - Restriction Deviation: 40.0
  - Sharpe Ratio: 24.82
  - Return: 0.46

6. Analyse des performances

Enfin, analysez les performances de votre application d'optimisation. Plus précisément, comparez vos résultats, obtenus dans l'exemple précédent, à une référence aléatoire afin d'évaluer l'efficacité de notre approche. Si l'algorithme quantique produit de manière démontrable et cohérente des résultats avec des valeurs de coût inférieures, cela indique un processus d'optimisation efficace.

La figure présente les distributions de probabilité des coûts objectifs. Pour générer ces distributions, prenez la liste des coûts objectifs du résultat de la fonction et comptez les occurrences de chaque valeur de coût (valeurs arrondies à la deuxième décimale). Ensuite, mettez à jour la colonne de comptage en joignant les comptages des valeurs arrondies identiques. Pour une meilleure comparaison visuelle, les nombres d'occurrences ont été normalisés de manière à ce que chaque distribution soit affichée entre 0 et 1.

Visualisation de la solution du problème d'optimisation

Comme le montre la figure (ligne continue bleue), la distribution des coûts pour notre approche Variational Quantum Eigensolver (post-traitement avec SQD) est fortement concentrée sur les valeurs de coût objectif les plus faibles, ce qui indique de bonnes performances d'optimisation. En revanche, la ligne de base bruyante présente une distribution plus large, centrée sur les valeurs de coût les plus élevées. La ligne verticale en pointillés gris représente la valeur moyenne de la distribution aléatoire, ce qui met encore plus en évidence la cohérence de la fonction dans le retour à des stratégies d'investissement optimisées. Pour une comparaison supplémentaire, la ligne noire en pointillés de la figure correspond à la solution obtenue avec l'optimiseur Gurobi (version gratuite). Tous ces résultats sont examinés plus en détail dans les benchmarks ci-dessous pour l'exemple "Actifs mixtes" évalué avec l'ansatz "sur mesure".

Tests de performances

Cette fonction a été testée dans différentes configurations de qubits de résolution, de circuits d'ansatz et de groupements d'actifs provenant de divers secteurs : un mélange de différents actifs (ensemble 1), des dérivés du pétrole (ensemble 2) et IBEX35 (ensemble 3). Pour plus de détails, voir le tableau suivant.

Définir
Date :
Actifs
Jeu 101/01/20238801.T, CL=F, GBPJPY=X, ITX.MC, META, TMBMKDE-10Y, XS2239553048
Jeu 201/06/2023CL=F, BZ=F, HO=F, NG=F, XOM, RB=F, 2222.SR
Jeu 301/11/2022ACS.MC, ITX.MC, FER.MC, ELE.MC, SCYR.MC, AENA.MC, AMS.MC

Deux paramètres clés ont été utilisés pour évaluer la qualité de la solution.

  1. Le coût objectif, qui mesure l'efficacité de l'optimisation en comparant la valeur de la fonction de coût de chaque expérience avec les résultats de Gurobi (version gratuite).
  2. Le ratio de Sharpe, qui mesure le rendement ajusté au risque de chaque portefeuille et donne une idée de la performance financière des solutions.

Ensemble, ces mesures permettent d'évaluer les aspects informatiques et financiers des portefeuilles générés par la méthode quantique.

Exemple
Qubits
Ansatz
Profondeur
Utilisation de la durée d'exécution (s)
Utilisation totale (s)
Coût objectif
Sharpe
Coût objectif de Gurobi
Gurobi Sharpe
Actifs mixtes (ensemble 1, 4 pas de temps, 4 bits)112Bien ajustés831273513095-3.7824.82-4.2524.71
Actifs mixtes (Set 1, 4 pas de temps, 4 pas de temps, 4 bits)112Amplitudes réelles3591173911903-3.3923.64-4.2524.71
Dérivés du pétrole (ensemble 2, 4 pas de temps, 3 bits)84Amplitudes réelles optimisées7861806350-3.7319.13-4.1921.71
IBEX35 (Ensemble 3, 4 pas de temps, 2 bits)56Amplitudes réelles optimisées9633143523-3.6714.48-4.1116.44

Les résultats montrent que l'optimiseur quantique, avec des réponses spécifiques au problème, identifie efficacement des stratégies d'investissement efficaces pour différents types de portefeuilles.

Nous détaillons ci-dessous la taille de la population et le nombre de générations spécifiés dans le dictionnaire optimizer_options . Tous les autres paramètres ont été réglés sur leurs valeurs par défaut.

Exemple
population_size
num_generations
Portefeuille d'actifs mixtes7020
Portefeuille d'actifs mixtes9220
Portefeuille de produits dérivés du pétrole13020
IBEX35 Le portefeuille4020

Le nombre de générations a été fixé à 20, cette valeur ayant été jugée suffisante pour atteindre la convergence. En outre, les valeurs par défaut des paramètres internes de l'optimiseur ont été laissées inchangées, car elles ont toujours donné de bons résultats et sont généralement recommandées par la littérature et les lignes directrices de mise en œuvre.

Obtenir de l'aide

Si vous avez besoin d'aide, vous pouvez envoyer un e-mail globaldataquantum.com qpo.support. Dans votre message, indiquez l'identifiant de la fonction.


Etapes suivantes

Recommandations
Cette page a-t-elle été utile ?
Signaler un bogue, une coquille ou proposer du contenu sur GitHub.