Machs dynamsche Portfolio-Optimierung mit'n Portfolio Optimizer von Global Data Quantum
Qiskit Functions sinn e experimentelles Feature un bloß fer IBM Quantum® Premium Plan, Flex Plan un On-Prem (übern IBM Quantum Platform API) Plan-Benutzer verfügbar. Se sinn im Preview-Status un könn sich noch ännern.
Verbrauchs-Schätzung: Ungefähr 55 Minuten uffm Heron r2-Prozessor. (Obacht: Das is bloß e Schätzung. De wirkliche Zeit kann andersch ausfallen.)
Hindergrunnd
Das dynamsche Portfolio-Optimierungs-Problem hat das Ziel, de optimale Investitions-Strategie über mehrer Zeiten ze finne, um de erwardete Rendite vom Portfolio ze maximiern un de Risiken ze minimiern — oft unner bestimmdn Beding'ngen wie Budget, Transaktions-Kostn odder Risiko-Aversion. Annersch als de standard Portfolio-Optimierung, de bloß eenen Moment fern Rebalancieren vom Portfolio betracht, berücksichtigt de dynamsche Version de Ännerungen vom Wert von de Assets un passt de Investitionn genau an, wie sich de Performance von de Assets über de Zeit verännert.
Das Tutorial hier zeigt, wie mer dynamsche Portfolio-Optimierung mit de Quantum Portfolio Optimizer Qiskit Function machn könn. Speziell zeichn mer, wie mer disse Application-Function nutzn könn, um e Investitions-Verteilungs-Problem über mehrer Zeit-Schritte ze lösn.
De Ansatz formuliert de Portfolio-Optimierung als e Multi-Objective Quadratic Unconstrained Binary Optimization (QUBO) Problem. Speziell formuliern mer de QUBO-Funkschon so, dass se vier verschiedene Zielsetzungen gleichzeitisch optimiert:
- Maximiern de Rendite-Funkschon
- Minimiern das Risiko von de Investitionn
- Minimiern de Transaktions-Kostn
- Haltet euch an de Investitions-Beschränkungen, formuliert in enem zusätzlichn Term zum Minimiern von .
Zusammgefasst formuliern mer de QUBO-Funkschon so wo de Risiko-Aversions-Koeffizient is un de Beschränkungs-Verstärkungs-Koeffizient (Lagrange-Multiplikator). De explizite Formulierung finnt mer in Gl. (15) von unserer Veröffentlichung [1].
Mer lösn das mit ner hybriden Quantum-Klassischn Methode, de uffm Variational Quantum Eigensolver (VQE) basiert. In däm Setup schätzt de Quantenschaltkreis de Kostn-Funkschon, während de klasssche Optimierung mit'n Differential Evolution-Algorithmus durchjeführt wird, was es möglich macht, de Lösungs-Landschaft effizient ze durchsuchn. De Anzahl von Qubits, de mer brauchn, hängt von drei Hauptfaktorn ab: de Anzahl von Assets na, de Anzahl von Zeit-Periodn nt un de Bit-Auflösung fer de Darstellung von de Investitionn nq. Konkret is de minimale Anzahl von Qubits in unserm Problem na*nt*nq.
In däm Tutorial konzentriern mer uns uff de Optimierung von enem regionalen Portfolio, das uffm spanischn IBEX 35-Index basiert. Speziell brauchn mer e Sibn-Asset-Portfolio wie in de Tabelle hier:
| IBEX 35 Portfolio | ACS.MC | ITX.MC | FER.MC | ELE.MC | SCYR.MC | AENA.MC | AMS.MC |
|---|
Mer rebalanciern unser Portfolio in vier Zeit-Schritte, jeweilsch 30 Tage auseinanner, anfangend am 1. November 2022. Jede Investitions-Variable wird mit zweie Bits kodiert. Das führt ze nem Problem, das 56 Qubits braucht, um's ze lösn.
Mer nutzn de Optimized Real Amplitudes-Ansatz, e anjepasste un hardware-effiziente Adaptierung vom standard Real Amplitudes-Ansatz, speziell anjepasst, um de Performance fer disse Art von finanzieller Optimierung ze verbessern.
De Quantum-Ausführung wird uffm ibm_torino-Backend durchjeführt. Fer e detaillierte Erklärung von de Problem-Formulierung, Methodologie un Performance-Evaluierung guckt nach de veröffentlichtn Manuskript [1].
Voraussetzngen
!pip install qiskit-ibm-catalog
!pip install pandas
!pip install matplotlib
!pip install yfinance
Einrichtung
Um de Quantum Portfolio Optimizer ze nutzn, wählt das Function-Objekt übern Qiskit Functions Catalog aus. Ihr braucht e IBM Quantum Premium Plan- odder Flex Plan-Konto mit ner Lizenz von Global Data Quantum, um disse Function ze nutzn.
Zuerst authentifiziert euch mit euerm API-Schlüssel. Dann laadt das gewünschte Function-Objekt ausm Qiskit Functions Catalog. Hier greift ihr uff de quantum_portfolio_optimizer-Function ausm Catalog zu, indem ihr de QiskitFunctionsCatalog-Klasse nutzt. Disse Function erlaubt es uns, de vordefiniertn Quantum Portfolio Optimization-Solver ze nutzn.
from qiskit_ibm_catalog import QiskitFunctionsCatalog
catalog = QiskitFunctionsCatalog(
channel="ibm_quantum_platform",
instance="INSTANCE_CRN",
token="YOUR_API_KEY", # Nutzt de 44-Zeichn API_KEY, de ihr ausm IBM Quantum Platform Home-Dashboard erstellt un jespeichert habt
)
# Greift uff de Function zu
dpo_solver = catalog.load("global-data-quantum/quantum-portfolio-optimizer")
Schritt 1: Leest das Input-Portfolio
In däm Schritt ladn mer historsche Datn fer de sibn ausgewähltn Assets ausm IBEX 35-Index, speziell vom 1. November 2022 bis 1. April 2023.
Mer holn de Datn übern Yahoo Finance-API un konzentriern uns uff de Schlusskurse. De Datn wern dann so bearbeitet, dass alle Assets de gleiche Anzahl von Tagen mit Datn ham. Fehlende Datn (Nich-Handels-Tage) wern passend behandelt, so dass alle Assets uff de gleichn Datn ausjerichtet sinn.
De Datn sinn in enem DataFrame mit konsistentr Formatierung fer alle Assets strukturiert.
import yfinance as yf
import pandas as pd
# Liste von IBEX 35-Symboln
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]
# Macht en volln Datums-Index, ooch mit 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
# Reindexiert, um Wochenende mitzuschließn
data = data.reindex(full_index)
# Füllt fehlende Werte (fer Wochenende odder Feierdage) durch forward/backward fill
data.ffill(inplace=True)
data.bfill(inplace=True)
series_list.append(data)
# Kombiniert alle Serieen in enem einzeln DataFrame
df = pd.concat(series_list, axis=1)
# Konvertiert de Index zu String fer Konsistenz
df.index = df.index.astype(str)
# Konvertiert DataFrame zu 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...
Schritt 2: Definiert de Problem-Eingabn
De Parameter, de mer brauchn, um das QUBO-Problem ze definiern, wern im qubo_settings-Dictionary konfiguriert. Mer definiern de Anzahl von Zeit-Schrittn (nt), de Anzahl von Bits fer de Investitions-Spezifikation (nq) un das Zeit-Fenster fer jedn Zeit-Schritt (dt). Zusätzlich setzen mer de maximale Investitionn pro Asset, de Risiko-Aversions-Koeffizienten, de Transaktions-Jebühr un de Beschränkungs-Koeffizienten (guckt nach unserm Paper fer Details über de Problem-Formulierung). Disse Einstellungen erlaubn es uns, das QUBO-Problem an das spezielle Investitions-Szenario anzupassn.
qubo_settings = {
"nt": 4,
"nq": 2,
"dt": 30,
"max_investment": 5, # maximale Investitionn pro Asset is 2**nq/max_investment = 80%
"risk_aversion": 1000.0,
"transaction_fee": 0.01,
"restriction_coeff": 1.0,
}
Das optimizer_settings-Dictionary konfiguriert de Optimierungs-Prozess, mit Parametern wie num_generations fer de Anzahl von Iterationn un population_size fer de Anzahl von Kandidatn-Lösungen pro Generation. Andere Einstellungen kontrolliern Aspekte wie de Rekombinations-Rate, parallele Jobs, Batch-Größe un Mutations-Bereich. Zusätzlich definiern de Primitive-Einstellungen wie estimator_shots, estimator_precision un sampler_shots de Quantum-Estimator- un Sampler-Konfigurationn fer de 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,
},
}
De Jesamt-Anzahl von Schaltkreisn hängt von de optimizer_settings-Parametern ab un wird berechnet als (num_generations + 1) * population_size.
Das ansatz_settings-Dictionary konfiguriert de Quantum-Schaltkreis-Ansatz. De ansatz-Parameter spezifiziert de Nutzung vom "optimized_real_amplitudes"-Ansatz, was e hardware-effizienter Ansatz is, de fer finanzielle Optimierungs-Probleme entwickelt wurde. Zusätzlich is de multiple_passmanager-Einstellung aktiviert, um mehrer Pass-Manager (inklusive de Standard lokal Qiskit Pass-Manager un de Qiskit AI-aangetriebene Transpiler-Service) während däm Optimierungs-Prozess zu erlaubn, was de Jesamt-Performance un Effizienz von de Schaltkreis-Ausführung verbessert.
ansatz_settings = {
"ansatz": "optimized_real_amplitudes",
"multiple_passmanager": False,
}
Schließlich führn mer de Optimierung aus, indem mer de dpo_solver.run()-Funkschon ausführn un de vorbereiteten Eingabn durchgevn. Das umfasst das Asset-Datn-Dictionary (assets), de QUBO-Konfiguration (qubo_settings), Optimierungs-Parameter (optimizer_settings) un de Quantum-Schaltkreis-Ansatz-Einstellungen (ansatz_settings). Zusätzlich spezifiziern mer de Ausführungs-Details wie das Backend un ob mer Post-Processing uff de Resultatn anwendn. Das startet de dynamschn Portfolio-Optimierungs-Prozess uffm ausgewähltn 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,
)
Schritt 3: Analysiert de Optimierungs-Resultatn
In däm Abschnitt extrahiern mer un zeichn de Lösung mit de niedrigsten objektiven Kostn aus de Optimierungs-Resultatn. Nebn de minimalen objektiven Kostn präsentiern mer ooch Schlüssel-Metriken, de mit de dazujeherigen Lösung verbundn sinn, wie de Beschränkungs-Abweichung, Sharpe-Ratio un Investitions-Rendite.
# Holt de Resultatn vom Job
dpo_result = dpo_job.result()
# Zeicht 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
# Holt Resultatn vom Job
dpo_result = dpo_job.result()
# Konvertiert Metadatn zu enem DataFrame, ohne 'session_id'
df = pd.DataFrame(dpo_result["metadata"]["all_samples_metrics"])
# Findet de minimalen objektiven Kostn
min_cost = df["objective_costs"].min()
print(f"Minimum Objective Cost Found: {min_cost:.2f}")
# Extrahiert de Reih mit de niedrigsten Kostn
best_row = df[df["objective_costs"] == min_cost].iloc[0]
# Zeicht de Resultatn, de mit de bestn Lösung verbundn sinn
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
De folgend Code zeicht, wie mer de Kostn-Verteilung von enem Optimierungs-Algorithmus mit ner zufälligen Stichprobn-Verteilung visualisiern un verglichn könn. Ähnlich erforschen mer de Landschaft von de QUBO-objektiven Funkschon (de ausm Function-Output jeladn wern kann), indem mer se mit zufälligen Investitionn evaluiern. Mer plottn beide Verteilungen, normalisiert in Amplitude, fer en eenfacheren Verglich, wie sich de Optimierungs-Prozess von zufälligen Stichprobn in Bezug uff Kostn ungerscheidet. Zusätzlich wird das Resultat, das mer mit DOCPlex kriegt ham, als jestrichelte vertikale Referenzlinie mitaufjenomm, um als klasssche Benchmark ze dienn. Mer nutzn de freie Version von DOCPlex — de IBM® Open-Source-Bibliothek fer mathematsche Optimierung in Python — um das gleiche Problem klasssch ze lösn.
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 Resultatn fer zweie Stichprobn-Resultatn.
Parameters:
dpo_x (array-like): X-Werte fer de VQE Post-processed-Kurve.
dpo_y_normalized (array-like): Y-Werte (normalisiert) fer de VQE Post-processed-Kurve.
random_x (array-like): X-Werte fer de Noise (Random)-Kurve.
random_y_normalized (array-like): Y-Werte (normalisiert) fer de Noise (Random)-Kurve.
"""
plt.figure(figsize=(6, 3))
plt.tick_params(axis="both", which="major", labelsize=12)
# Definiert eigne Farbn
colors = ["#4823E8", "#9AA4AD"]
# Plottet DPO-Resultatn
(line1,) = plt.plot(
dpo_x, dpo_y_normalized, label="VQE Postprocessed", color=colors[0]
)
line1.set_path_effects(
[patheffects.withStroke(linewidth=3, foreground="white")]
)
# Plottet Zufällige Resultatn
(line2,) = plt.plot(
random_x, random_y_normalized, label="Noise (Random)", color=colors[1]
)
line2.set_path_effects(
[patheffects.withStroke(linewidth=3, foreground="white")]
)
# Setzt X-Achsn-Ticks uff 5 Einheitn-Schritte
plt.gca().xaxis.set_major_locator(MultipleLocator(5))
# Achsn-Beschriftungen un Legende
plt.xlabel("Objective cost", fontsize=14)
plt.ylabel("Normalized Counts", fontsize=14)
# Fügt DOCPlex-Referenzlinie hinzu
plt.axvline(
x=-4.11, color="black", linestyle="--", linewidth=1, label="DOCPlex"
) # DOCPlex-Wert
plt.ylim(bottom=0)
plt.legend()
# Passt Layout an
plt.tight_layout()
plt.show()
import numpy as np
from collections import defaultdict
# ================================
# SCHRITT 1: DPO-KOSTN-VERTEILUNG
# ================================
# Extrahiert Datn aus DPO-Resultatn
counts_list = dpo_result["metadata"]["all_samples_metrics"][
"objective_costs"
] # Liste, wie oft jede Lösung vorjekomm is
cost_list = dpo_result["metadata"]["all_samples_metrics"][
"counts"
] # Liste von de dazujeherigen objektiven Funktions-Werten (Kostn)
# Rundt Kostn uff ei Dezimal un akkumuliert Counts fer jede einzigartige Kostn
dpo_counter = defaultdict(int)
for cost, count in zip(cost_list, counts_list):
rounded_cost = round(cost, 1)
dpo_counter[rounded_cost] += count
# Bereitet Datn fern Plottn vor
dpo_x = sorted(dpo_counter.keys()) # Sortierte Liste von Kostn-Werten
dpo_y = [dpo_counter[c] for c in dpo_x] # Dazujeherige Counts
# Normalisiert de Counts uffn Bereich [0, 1] fer besseren Verglich
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
]
# ================================
# SCHRITT 2: ZUFÄLLIGE KOSTN-VERTEILUNG
# ================================
# Liest de QUBO-Matrix
qubo = np.array(dpo_result["metadata"]["qubo"])
bitstring_length = qubo.shape[0]
num_random_samples = 100_000 # Anzahl von zufälligen Stichprobn zum jenerieren
random_cost_counter = defaultdict(int)
# Jeneriert zufällige Bitstrings un berechnet ihre Kostn
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
# Bereitet zufällige Datn fern Plottn vor
random_x = sorted(random_cost_counter.keys())
random_y = [random_cost_counter[c] for c in random_x]
# Normalisiert de zufällige Kostn-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
]
# ================================
# SCHRITT 3: PLOTTN
# ================================
plot_normalized(dpo_x, dpo_y_normalized, random_x, random_y_normalized)
De Graph zeicht, wie de Quantum Portfolio Optimizer konsequent optimierte Investitions-Strategien zurückjiebt.
Referenzn
Tutorial-Umfrach
Nehmt euch bitte e Minute Zeit, um Feedback über das Tutorial ze gevn. Eure Einsichtn helfn uns, unser Content-Anjebodt un User-Experience ze verbessern. Link zur Umfrach