Zom Hauptinhalt springe

Maach dynamische Portfolio-Optimierung met däm Portfolio Optimizer vun Global Data Quantum

Opjepass

Qiskit Functions sin e experimentelle Feature un nor för IBM Quantum® Premium Plan, Flex Plan un On-Prem (övver IBM Quantum Platform API) Plan-Usere verfögbar. Se sin em Preview-Status un künne sech noch ännere.

Verbruchs-Schätzung: Ungefähr 55 Minute op enem Heron r2-Prozessor. (Opjepass: Dat es nor en Schätzung. Die werkliche Zigg kann anders usfalle.)

Hintergrund

Dat dynamische Portfolio-Optimierungs-Problem hätt et Ziel, de optimal Investitions-Strategie övver mihr Zigge zo finge, öm dä erwartete Rendite vom Portfolio zo maximiere un de Risike zo minimiere — off unger bestemmpte Bedingunge wie Budget, Transaktions-Koste oder Risiko-Aversion. Angers wie die standard Portfolio-Optimierung, die nor ein Momänt för et Rebalanciere vom Portfolio betracht, beröcksechtigt die dynamische Versioon die Änderunge vun däm Wätt vun de Assets un passt de Investitione aanjenau an, wie sech die Performance vun de Assets övver de Zigg verändert.

Dat Tutorial hee zeich, wie mer dynamische Portfolio-Optimierung met dä Quantum Portfolio Optimizer Qiskit Function maache künne. Speziell zeije mer, wie mer dat Application-Function nutze künne, öm e Investitions-Verteilings-Problem övver mihr Zigg-Schrette zo löse.

Dä Ansatz formuliert de Portfolio-Optimierung als e Multi-Objective Quadratic Unconstrained Binary Optimization (QUBO) Problem. Speziell formuliere mer die QUBO-Funktioon OO esu, dat se vier verschiedene Zielsetzunge gleichziggisch optimiert:

  • Maximiere die Rendite-Funktioon FF
  • Minimiere dat Risiko vun dä Investitioon RR
  • Minimiere die Transaktions-Koste CC
  • Hallt dich aan de Investitions-Beschränkunge, formuliert en enem zusätzliche Term för et Minimiere vun PP.

Zusammejefass formuliere mer die QUBO-Funktioon esu O=F+γ2R+C+ρP,O = -F + \frac{\gamma}{2} R + C + \rho P, wo γ\gamma dä Risiko-Aversionis-Koeffizient es un ρ\rho dä Beschränkungs-Verstärkungs-Koeffizient (Lagrange-Multiplikator). Die explizite Formulierung fingk mer en Gl. (15) vun userer Veröffentlichung [1].

Mer löse dat met ener hybrid Quantum-Klassische Methode, die op däm Variational Quantum Eigensolver (VQE) basiert. En däm Setup schätzt dä Quantenschaltkreis die Koste-Funktioon, während die klassische Optimierung met däm Differential Evolution-Algorithmus durchjefööt weed, wat et möglich mäht, dat Lösungs-Landschaft effizient ze durchsööke. Die Aanzahl vun Qubits, die mer bruche, häng vun drei Hauptfaktore aav: die Aanzahl vun Assets na, die Aanzahl vun Zigg-Periode nt un die Bit-Auflösung för de Darstellung vun dä Investitioon nq. Konkret es die minimale Aanzahl vun Qubits en userem Problem na*nt*nq.

En däm Tutorial konzentriere mer uns op de Optimierung vun enem regionale Portfolio, dat op däm spannische IBEX 35-Index basiert. Speziell bruche mer e Sibbe-Asset-Portfolio wie en dä Tabell hee:

IBEX 35 PortfolioACS.MCITX.MCFER.MCELE.MCSCYR.MCAENA.MCAMS.MC

Mer rebalanciere us Portfolio en vier Zigg-Schrette, jeweils 30 Däg ussenander, aanfangend aam 1. November 2022. Jede Investitions-Variable weed met zwei Bits kodiert. Dat füürt zo enem Problem, dat 56 Qubits bruucht, öm et ze löse.

Mer nutze dä Optimized Real Amplitudes-Ansatz, en aanjepasste un hardware-effiziente Adaptatioon vum standard Real Amplitudes-Ansatz, speziell aanjepass, öm de Performance för dese Aat vun finanzieller Optimierung ze verbessere.

Die Quantum-Ausführung weed op däm ibm_torino-Backend durchjefööt. Ene detaillierte Erklärung vun dä Problem-Formulierung, Methodologie un Performance-Evaluierung fingk mer en dä veröffentlichte Manuskript [1].

Vorraussetzunge

# Added by doQumentation — required packages for this notebook
!pip install -q numpy
!pip install qiskit-ibm-catalog
!pip install pandas
!pip install matplotlib
!pip install yfinance

Oprüstung

Öm dä Quantum Portfolio Optimizer ze nutze, wählt dat Function-Objekt övver dä Qiskit Functions Catalog us. Ih bruucht e IBM Quantum Premium Plan- oder Flex Plan-Konto met ener Lizenz vun Global Data Quantum, öm dese Function ze nutze.

Zeerst authentifizeert Iech met ürem API-Schlössel. Dann laadt dat jewünschte Function-Objekt usem Qiskit Functions Catalog. Hee greift Ih op die quantum_portfolio_optimizer-Function usem Catalog zo, indem Ih die QiskitFunctionsCatalog-Klass bruucht. Dese Function erlaubt et uns, dä vordefinierte Quantum Portfolio Optimization-Solver ze nutze.

from qiskit_ibm_catalog import QiskitFunctionsCatalog

catalog = QiskitFunctionsCatalog(
channel="ibm_quantum_platform",
instance="INSTANCE_CRN",
token="YOUR_API_KEY", # Use the 44-character API_KEY you created and saved from the IBM Quantum Platform Home dashboard
)

# Access function
dpo_solver = catalog.load("global-data-quantum/quantum-portfolio-optimizer")

Schrett 1: Lies dat Input-Portfolio

En däm Schrett laade mer historische Date för die sibbe usgewählte Assets usem IBEX 35-Index, speziell vum 1. November 2022 bes 1. April 2023.

Mer holle de Date övver die Yahoo Finance-API un konzentriere uns op de Schlusskurse. De Date weede dann esu bearbeitet, dat all Assets die jleiich Aanzahl vun Däg met Date han. Fehlende Date (Net-Handels-Däg) weede passend behandelt, esu dat all Assets op de jleiiche Datume usgerecht sin.

De Date sin en enem DataFrame met konsistente Formatierung för all Assets strukturiert.

import yfinance as yf
import pandas as pd

# List of IBEX 35 symbols
symbols = [
"ACS.MC",
"ITX.MC",
"FER.MC",
"ELE.MC",
"SCYR.MC",
"AENA.MC",
"AMS.MC",
]

start_date = "2022-11-01"
end_date = "2023-4-01"

series_list = []
symbol_names = [symbol.replace(".", "_") for symbol in symbols]

# Create a full date index including weekends
full_index = pd.date_range(start=start_date, end=end_date, freq="D")

for symbol, name in zip(symbols, symbol_names):
print(f"Downloading data for {symbol}...")
data = yf.download(symbol, start=start_date, end=end_date)["Close"]
data.name = name

# Reindex to include weekends
data = data.reindex(full_index)

# Fill missing values (for example, weekends or holidays) by forward/backward fill
data.ffill(inplace=True)
data.bfill(inplace=True)

series_list.append(data)

# Combine all series into a single DataFrame
df = pd.concat(series_list, axis=1)

# Convert index to string for consistency
df.index = df.index.astype(str)

# Convert DataFrame to dictionary
assets = df.to_dict()
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************]  1 of 1 completed
[*********************100%***********************] 1 of 1 completed
[*********************100%***********************] 1 of 1 completed
[*********************100%***********************] 1 of 1 completed
[*********************100%***********************] 1 of 1 completed
[*********************100%***********************] 1 of 1 completed
Downloading data for ACS.MC...
Downloading data for ITX.MC...
Downloading data for FER.MC...
Downloading data for ELE.MC...
Downloading data for SCYR.MC...
Downloading data for AENA.MC...
Downloading data for AMS.MC...

Schrett 2: Definiere die Problem-Eingabe

Die Parameter, die mer bruche, öm dat QUBO-Problem ze definiere, weede em qubo_settings-Dictionary konfiguriert. Mer definiere die Aanzahl vun Zigg-Schrette (nt), die Aanzahl vun Bits för de Investitions-Spezifikazioon (nq) un dat Zigg-Finster för jede Zigg-Schrett (dt). Zosätzlich setze mer de maximal Investitioon pro Asset, dä Risiko-Aversionis-Koeffizient, die Transaktions-Jebühr un dä Beschränkungs-Koeffizient (luurt noh userem Paper för Details övver de Problem-Formulierung). Dese Enstellunge erlaube et uns, dat QUBO-Problem aan dat speziell Investitions-Szenario aanzepasse.

qubo_settings = {
"nt": 4,
"nq": 2,
"dt": 30,
"max_investment": 5, # maximum investment per asset is 2**nq/max_investment = 80%
"risk_aversion": 1000.0,
"transaction_fee": 0.01,
"restriction_coeff": 1.0,
}

Dat optimizer_settings-Dictionary konfiguriert dä Optimierungs-Prozess, met Parametre wie num_generations för die Aanzahl vun Iteratione un population_size för die Aanzahl vun Kandidate-Lösunge pro Generatioon. Ander Enstellunge kontrolliere Aspekte wie de Rekombinations-Rate, parallele Jobs, Batch-Größ un Mutations-Bereich. Zosätzlich definiere die Primitive-Enstellunge wie estimator_shots, estimator_precision un sampler_shots die Quantum-Estimator- un Sampler-Konfiguratione för dä Optimierungs-Prozess.

optimizer_settings = {
"de_optimizer_settings": {
"num_generations": 20,
"population_size": 40,
"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,
},
}
hendeis

Die Jesamt-Aanzahl vun Schaltkreise häng vun de optimizer_settings-Parameter aav un weed berechnet als (num_generations + 1) * population_size.

Dat ansatz_settings-Dictionary konfiguriert dä Quantum-Schaltkreis-Ansatz. Dä ansatz-Parameter spezifiziert de Nutzung vum "optimized_real_amplitudes"-Ansatz, wat en hardware-effiziente Ansatz es, dä för finanziell Optimierungs-Probleme designt es. Zosätzlich es die multiple_passmanager-Enstellung aktiviert, öm mihr Pass-Manager (inklusive däm Standard lokal Qiskit Pass-Manager un däm Qiskit AI-aanjedrivvene Transpiler-Service) während däm Optimierungs-Prozess ze erlaube, wat die Jesamt-Performance un Effizienz vun dä Schaltkreis-Usführung verbessert.

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

Schließlich föhre mer die Optimierung us, indem mer die dpo_solver.run()-Funktioon usföhre un die vorbereite Eingabe dörchjevve. Dat ömfasst dat Asset-Date-Dictionary (assets), die QUBO-Konfiguratioon (qubo_settings), Optimierungs-Parameter (optimizer_settings) un die Quantum-Schaltkreis-Ansatz-Enstellunge (ansatz_settings). Zosätzlich spezifiziere mer de Usführungs-Details wie dat Backend un ov mer Post-Processing op de Ergebnisse aawende. Dat statet dä dynamische Portfolio-Optimierungs-Prozess op däm usgewählte Quantum-Backend.

dpo_job = dpo_solver.run(
assets=assets,
qubo_settings=qubo_settings,
optimizer_settings=optimizer_settings,
ansatz_settings=ansatz_settings,
backend_name="ibm_torino",
previous_session_id=[],
apply_postprocess=True,
)

Schrett 3: Analysiere die Optimierungs-Ergebnisse

En däm Avschnett extrahiere mer un zeije die Lösung met de niddrigste objektive Koste us de Optimierungs-Ergebnisse. Nävve de minimale objektive Koste präsentiere mer och Schlössel-Metrike, die met dä zugehörige Lösung verbonge sin, wie die Beschränkungs-Abweichung, Sharpe-Ratio un Investitions-Rendite.

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

# Show the solution strategy
dpo_result["result"]
{'time_step_0': {'ACS.MC': 0.11764705882352941,
'ITX.MC': 0.20588235294117646,
'FER.MC': 0.38235294117647056,
'ELE.MC': 0.058823529411764705,
'SCYR.MC': 0.0,
'AENA.MC': 0.058823529411764705,
'AMS.MC': 0.17647058823529413},
'time_step_1': {'ACS.MC': 0.11428571428571428,
'ITX.MC': 0.14285714285714285,
'FER.MC': 0.2,
'ELE.MC': 0.02857142857142857,
'SCYR.MC': 0.42857142857142855,
'AENA.MC': 0.0,
'AMS.MC': 0.08571428571428572},
'time_step_2': {'ACS.MC': 0.0,
'ITX.MC': 0.09375,
'FER.MC': 0.3125,
'ELE.MC': 0.34375,
'SCYR.MC': 0.0,
'AENA.MC': 0.0,
'AMS.MC': 0.25},
'time_step_3': {'ACS.MC': 0.3939393939393939,
'ITX.MC': 0.09090909090909091,
'FER.MC': 0.12121212121212122,
'ELE.MC': 0.18181818181818182,
'SCYR.MC': 0.0,
'AENA.MC': 0.0,
'AMS.MC': 0.21212121212121213}}
import pandas as pd

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

# Convert metadata to a DataFrame, excluding 'session_id'
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']:.2f}")
Minimum Objective Cost Found: -3.67
Best Solution:
- Restriction Deviation: 40.0%
- Sharpe Ratio: 14.54
- Return: 0.28

Dä folgend Code zeich, wie mer de Koste-Verteilung vun enem Optimierungs-Algorithmus met ener zufällige Stichprove-Verteilung visualisiere un verglieche künne. Ähnlich erforsche mer de Landschaft vun dä QUBO-objektive Funktioon (die us dä Function-Output jelaad weede kann), indem mer se met zufällige Investitione evaluiere. Mer plotte beide Verteillunge, normalisiert en Amplitude, för e einfachere Vergliech, wie sech dä Optimierungs-Prozess vun zufällije Stichprove en Bezug op Koste ungerscheidet. Zosätzlich weed dat Ergebnis, dat mer met DOCPlex kriejen han, als jesträchelti vertikali Referenzlinie metopjenomme, öm als klassische Benchmark ze deene. Mer bruche die frei Versioon vun DOCPlex — die IBM® Open-Source-Bibliothek för mathematische Optimierung en Python — öm dat jleiiche Problem klassisch ze löse.

import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator
import matplotlib.patheffects as patheffects

def plot_normalized(dpo_x, dpo_y_normalized, random_x, random_y_normalized):
"""
Plots normalized results for two sampling results.

Parameters:
dpo_x (array-like): X-values for the VQE Post-processed curve.
dpo_y_normalized (array-like): Y-values (normalized) for the VQE Post-processed curve.
random_x (array-like): X-values for the Noise (Random) curve.
random_y_normalized (array-like): Y-values (normalized) for the Noise (Random) curve.
"""
plt.figure(figsize=(6, 3))
plt.tick_params(axis="both", which="major", labelsize=12)

# Define custom colors
colors = ["#4823E8", "#9AA4AD"]

# Plot DPO results
(line1,) = plt.plot(
dpo_x, dpo_y_normalized, label="VQE Postprocessed", color=colors[0]
)
line1.set_path_effects(
[patheffects.withStroke(linewidth=3, foreground="white")]
)

# Plot Random results
(line2,) = plt.plot(
random_x, random_y_normalized, label="Noise (Random)", color=colors[1]
)
line2.set_path_effects(
[patheffects.withStroke(linewidth=3, foreground="white")]
)

# Set X-axis ticks to increment by 5 units
plt.gca().xaxis.set_major_locator(MultipleLocator(5))

# Axis labels and legend
plt.xlabel("Objective cost", fontsize=14)
plt.ylabel("Normalized Counts", fontsize=14)

# Add DOCPLEX reference line
plt.axvline(
x=-4.11, color="black", linestyle="--", linewidth=1, label="DOCPlex"
) # DOCPlex value
plt.ylim(bottom=0)

plt.legend()

# Adjust layout
plt.tight_layout()
plt.show()
import numpy as np
from collections import defaultdict

# ================================
# STEP 1: DPO COST DISTRIBUTION
# ================================

# Extract data from DPO results
counts_list = dpo_result["metadata"]["all_samples_metrics"][
"objective_costs"
] # List of how many times each solution occurred
cost_list = dpo_result["metadata"]["all_samples_metrics"][
"counts"
] # List of corresponding objective function values (costs)

# Round costs to one decimal and accumulate counts for each unique cost
dpo_counter = defaultdict(int)
for cost, count in zip(cost_list, counts_list):
rounded_cost = round(cost, 1)
dpo_counter[rounded_cost] += count

# Prepare data for plotting
dpo_x = sorted(dpo_counter.keys()) # Sorted list of cost values
dpo_y = [dpo_counter[c] for c in dpo_x] # Corresponding counts

# Normalize the counts to the range [0, 1] for better comparison
dpo_min = min(dpo_y)
dpo_max = max(dpo_y)
dpo_y_normalized = [
(count - dpo_min) / (dpo_max - dpo_min) for count in dpo_y
]

# ================================
# STEP 2: RANDOM COST DISTRIBUTION
# ================================

# Read the QUBO matrix
qubo = np.array(dpo_result["metadata"]["qubo"])

bitstring_length = qubo.shape[0]
num_random_samples = 100_000 # Number of random samples to generate
random_cost_counter = defaultdict(int)

# Generate random bitstrings and calculate their cost
for _ in range(num_random_samples):
x = np.random.randint(0, 2, size=bitstring_length)
cost = float(x @ qubo @ x.T)
rounded_cost = round(cost, 1)
random_cost_counter[rounded_cost] += 1

# Prepare random data for plotting
random_x = sorted(random_cost_counter.keys())
random_y = [random_cost_counter[c] for c in random_x]

# Normalize the random cost distribution
random_min = min(random_y)
random_max = max(random_y)
random_y_normalized = [
(count - random_min) / (random_max - random_min) for count in random_y
]

# ================================
# STEP 3: PLOTTING
# ================================

plot_normalized(dpo_x, dpo_y_normalized, random_x, random_y_normalized)

Output of the previous code cell

Dä Graph zeich, wie dä Quantum Portfolio Optimizer konsequent optimierte Investitions-Strategie zröckjitt.

Referenze

[1] Nodar, Álvaro, Irene De León, Danel Arias, Ernesto Mamedaliev, María Esperanza Molina, Manuel Martín-Cordero, Senaida Hernández-Santana et al. "Scaling the Variational Quantum Eigensolver for Dynamic Portfolio Optimization." arXiv preprint arXiv:2412.19150 (2024).

Tutorial-Ömfrog

Nemmp üch bett en Menutt Zigg, öm Feedback övver dat Tutorial ze jevve. Ühr Einsichte helfe uns, us Content-Aanbod un User-Experience ze verbessere. Link zo dä Ömfrog

Source: IBM Quantum docs — updated 27. Apr. 2026
English version on doQumentation — updated 7. Mai. 2026
This translation based on the English version of 11. Mäz. 2026