Maach dynamische Portfolio-Optimierung met däm Portfolio Optimizer vun Global Data Quantum
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 esu, dat se vier verschiedene Zielsetzunge gleichziggisch optimiert:
- Maximiere die Rendite-Funktioon
- Minimiere dat Risiko vun dä Investitioon
- Minimiere die Transaktions-Koste
- Hallt dich aan de Investitions-Beschränkunge, formuliert en enem zusätzliche Term för et Minimiere vun .
Zusammejefass formuliere mer die QUBO-Funktioon esu wo dä Risiko-Aversionis-Koeffizient es un 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 Portfolio | ACS.MC | ITX.MC | FER.MC | ELE.MC | SCYR.MC | AENA.MC | AMS.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
!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", # Bruucht dä 44-Zeiche API_KEY, dä Ih usem IBM Quantum Platform Home-Dashboard erstellt un jespeichert hätt
)
# Greif op de Function zo
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
# Liss vun IBEX 35-Symbole
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]
# Maach e volle Datums-Index, och met Wochenende
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
# Reindexiere, öm Wochenende met enzeschließe
data = data.reindex(full_index)
# Föll fehlende Wätte (för Wochenende oder Feierdäg) durch forward/backward fill
data.ffill(inplace=True)
data.bfill(inplace=True)
series_list.append(data)
# Kombiniere all Serien en einem einzelne DataFrame
df = pd.concat(series_list, axis=1)
# Konvertiere dä Index en String för Konsistenz
df.index = df.index.astype(str)
# Konvertiere DataFrame en 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, # maximal Investitioon pro Asset es 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,
},
}
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.
# Holl de Ergebnisse vum Job
dpo_result = dpo_job.result()
# Zeich de Lösungs-Strategie
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
# Holl Ergebnisse vum Job
dpo_result = dpo_job.result()
# Konvertiere Metadate en e DataFrame, ohne 'session_id'
df = pd.DataFrame(dpo_result["metadata"]["all_samples_metrics"])
# Fing de minimal objektive Koste
min_cost = df["objective_costs"].min()
print(f"Minimum Objective Cost Found: {min_cost:.2f}")
# Extrahiere die Reih met de niddrigste Koste
best_row = df[df["objective_costs"] == min_cost].iloc[0]
# Zeich de Ergebnisse, die met dä beste Lösung verbonge sin
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):
"""
Plottet normalisierte Ergebnisse för zwei Stichprove-Ergebnisse.
Parameters:
dpo_x (array-like): X-Wätte för die VQE Post-processed-Kurv.
dpo_y_normalized (array-like): Y-Wätte (normalisiert) för die VQE Post-processed-Kurv.
random_x (array-like): X-Wätte för die Noise (Random)-Kurv.
random_y_normalized (array-like): Y-Wätte (normalisiert) för die Noise (Random)-Kurv.
"""
plt.figure(figsize=(6, 3))
plt.tick_params(axis="both", which="major", labelsize=12)
# Definiere eijen Farve
colors = ["#4823E8", "#9AA4AD"]
# Plott DPO-Ergebnisse
(line1,) = plt.plot(
dpo_x, dpo_y_normalized, label="VQE Postprocessed", color=colors[0]
)
line1.set_path_effects(
[patheffects.withStroke(linewidth=3, foreground="white")]
)
# Plott Zufällige Ergebnisse
(line2,) = plt.plot(
random_x, random_y_normalized, label="Noise (Random)", color=colors[1]
)
line2.set_path_effects(
[patheffects.withStroke(linewidth=3, foreground="white")]
)
# Setz X-Achs-Ticks op 5 Einheite-Schrette
plt.gca().xaxis.set_major_locator(MultipleLocator(5))
# Achs-Beschriftunge un Legende
plt.xlabel("Objective cost", fontsize=14)
plt.ylabel("Normalized Counts", fontsize=14)
# Fög DOCPlex-Referenzlinie hin
plt.axvline(
x=-4.11, color="black", linestyle="--", linewidth=1, label="DOCPlex"
) # DOCPlex-Wätt
plt.ylim(bottom=0)
plt.legend()
# Pass Layout aan
plt.tight_layout()
plt.show()
import numpy as np
from collections import defaultdict
# ================================
# SCHRETT 1: DPO-KOSTE-VERTEILUNG
# ================================
# Extrahiere Date us DPO-Ergebnisse
counts_list = dpo_result["metadata"]["all_samples_metrics"][
"objective_costs"
] # Liss, wie off jede Lösung opjetrode es
cost_list = dpo_result["metadata"]["all_samples_metrics"][
"counts"
] # Liss vun de entsprechende objektive Funktions-Wätte (Koste)
# Rung Koste op ein Dezimal un akkumuliere Counts för jede einzigartige Koste
dpo_counter = defaultdict(int)
for cost, count in zip(cost_list, counts_list):
rounded_cost = round(cost, 1)
dpo_counter[rounded_cost] += count
# Bereide Date för et Plotte vör
dpo_x = sorted(dpo_counter.keys()) # Sortierte Liss vun Koste-Wätte
dpo_y = [dpo_counter[c] for c in dpo_x] # Entsprechende Counts
# Normalisiere die Counts op dä Bereich [0, 1] för bessere Vergliech
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
]
# ================================
# SCHRETT 2: ZUFÄLLIGE KOSTE-VERTEILUNG
# ================================
# Lies die QUBO-Matrix
qubo = np.array(dpo_result["metadata"]["qubo"])
bitstring_length = qubo.shape[0]
num_random_samples = 100_000 # Aanzahl vun zufällige Stichprove ze generiere
random_cost_counter = defaultdict(int)
# Generiere zufällige Bitstrings un berechn ihre Koste
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
# Bereide zufällige Date för et Plotte vör
random_x = sorted(random_cost_counter.keys())
random_y = [random_cost_counter[c] for c in random_x]
# Normalisiere die zufälliche Koste-Verteilung
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
]
# ================================
# SCHRETT 3: PLOTTE
# ================================
plot_normalized(dpo_x, dpo_y_normalized, random_x, random_y_normalized)
Dä Graph zeich, wie dä Quantum Portfolio Optimizer konsequent optimierte Investitions-Strategie zröckjitt.
Referenze
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