Dynamische Portfoliooptimierung mit dem Portfolio Optimizer von Global Data Quantum
Qiskit-Funktionen sind eine experimentelle Funktion, die nur für Benutzer des IBM Quantum® Premium Plan, Flex Plan und On-Prem (über IBM Quantum Platform API) Plan verfügbar ist. Sie befinden sich im Status einer Vorabversion und können sich noch ändern.
Geschätzte Nutzungsdauer: Ungefähr 55 Minuten auf einem Heron r2 Prozessor. (HINWEIS: Dies ist nur eine Schätzung. Die tatsächliche Laufzeit kann variieren)
Hintergrund
Das Problem der dynamischen Portfolio-Optimierung zielt darauf ab, die optimale Anlagestrategie über mehrere Zeiträume zu finden, um die erwartete Portfoliorendite zu maximieren und die Risiken zu minimieren, oft unter bestimmten Einschränkungen wie Budget, Transaktionskosten oder Risikoaversion. Im Gegensatz zur Standard-Portfolio-Optimierung, bei der ein einziger Zeitpunkt für die Neugewichtung des Portfolios berücksichtigt wird, berücksichtigt die dynamische Version die sich entwickelnde Natur von Vermögenswerten und passt die Investitionen auf der Grundlage von Änderungen der Vermögensleistung im Laufe der Zeit an.
Dieses Tutorial zeigt, wie man eine dynamische Portfolio-Optimierung mit Hilfe der Quantum Portfolio Optimizer Qiskit Funktion durchführt. Wir zeigen insbesondere, wie diese Anwendungsfunktion zur Lösung eines Investitionsallokationsproblems über mehrere Zeitschritte verwendet werden kann.
Der Ansatz beinhaltet die Formulierung der Portfolio-Optimierung als ein Mehrzielproblem der quadratischen, uneingeschränkten binären Optimierung (QUBO). Konkret formulieren wir die QUBO-Funktion zur gleichzeitigen Optimierung von vier verschiedenen Zielen:
- Maximierung der Rückgabefunktion
- Minimieren Sie das Risiko der Investition
- Minimieren Sie die Transaktionskosten
- Beachten Sie die Anlagebeschränkungen, die in einer zusätzlichen Klausel formuliert sind, um zu minimieren.
Um diese Ziele zu erreichen, formulieren wir die QUBO-Funktion wie folgt wobei der Risikoaversionskoeffizient und der Restriktionsverstärkungskoeffizient (Lagrange-Multiplikator) ist. Die explizite Formulierung ist in Gleichung zu finden. (15) aus unserem Manuskript [1].
Wir lösen mit einer hybriden quantenklassischen Methode, die auf dem Variational Quantum Eigensolver (VQE) basiert. In diesem Aufbau schätzt der Quantenschaltkreis die Kostenfunktion, während die klassische Optimierung mit dem Algorithmus der Differentialevolution durchgeführt wird, was eine effiziente Navigation durch die Lösungslandschaft ermöglicht. Die Anzahl der benötigten Qubits hängt von drei Hauptfaktoren ab: der Anzahl der Vermögenswerte na, der Anzahl der Zeiträume nt und der Bitauflösung, die zur Darstellung der Anlage verwendet wird nq. Konkret lautet die minimale Anzahl von Qubits in unserem Problem na*nt*nq.
In diesem Tutorial konzentrieren wir uns auf die Optimierung eines regionalen Portfolios auf der Grundlage des spanischen IBEX 35 Index. Im Einzelnen verwenden wir ein Portfolio mit sieben Vermögenswerten, wie in der nachstehenden Tabelle angegeben:
IBEX 35 Portfolio | ACS.MC | ITX.MC | FER.MC | ELE.MC | SCYR.MC | AENA.MC | AMS.MC |
|---|
Wir nehmen eine Neugewichtung unseres Portfolios in vier Zeitschritten vor, die jeweils durch ein 30-Tage-Intervall ab dem 1. November 2022 getrennt sind. Jede Investitionsvariable wird mit zwei Bits kodiert. Daraus ergibt sich ein Problem, für dessen Lösung 56 Qubits erforderlich sind.
Wir verwenden den Optimized Real Amplitudes-Ansatz, eine angepasste und hardware-effiziente Adaption des Standard-Real-Amplitudes-Ansatzes, die speziell darauf zugeschnitten ist, die Leistung für diese Art von finanziellen Optimierungsproblemen zu verbessern.
Die Quantum-Ausführung wird auf dem Backend ibm_torino durchgeführt. Eine ausführliche Erläuterung der Problemstellung, der Methodik und der Leistungsbewertung finden Sie in dem veröffentlichten Manuskript [1].
Anforderungen
!pip install qiskit-ibm-catalog
!pip install pandas
!pip install matplotlib
!pip install yfinanceEinrichtung
Um den Quantum Portfolio Optimizer zu verwenden, wählen Sie die Funktion über den Qiskit-Funktionskatalog aus. Sie benötigen ein IBM Quantum Premium Plan oder Flex Plan Konto mit einer Lizenz von Global Data Quantum, um diese Funktion auszuführen.
Melden Sie sich zunächst mit Ihrem API-Schlüssel an. Laden Sie anschließend die gewünschte Funktion aus dem Verzeichnis „ Qiskit Functions Catalog “. Hier rufen Sie die quantum_portfolio_optimizer Funktion aus dem Katalog mithilfe der QiskitFunctionsCatalog Klasse auf. Mit dieser Funktion können wir den vordefinierten Quantum- Portfolio Optimization -Löser verwenden.
from qiskit_ibm_catalog import QiskitFunctionsCatalog
catalog = QiskitFunctionsCatalog(
channel="ibm_quantum_platform",
instance="INSTANCE_CRN",
# For `token`, use the 44-character API_KEY you created
# and saved from the IBM Quantum Platform Home dashboard
token="YOUR_API_KEY",
)
# Access function
dpo_solver = catalog.load("global-data-quantum/quantum-portfolio-optimizer")Schritt 1: Lesen Sie das Eingabeportfolio
In diesem Schritt laden wir historische Daten für die sieben ausgewählten Vermögenswerte aus dem IBEX 35 Index, und zwar vom 1. November 2022 bis zum 1. April 2023.
Wir beziehen die Daten über die Yahoo Finance API, wobei wir uns auf die Schlusskurse konzentrieren. Die Daten werden dann verarbeitet, um sicherzustellen, dass alle Anlagen die gleiche Anzahl von Tagen mit Daten haben. Fehlende Daten (Nicht-Handelstage) werden entsprechend behandelt, um sicherzustellen, dass alle Vermögenswerte an denselben Daten ausgerichtet sind.
Die Daten sind in DataFrame strukturiert und für alle Assets einheitlich formatiert.
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
assets = pd.concat(series_list, axis=1)
# Convert index to string for consistency
assets.index = assets.index.astype(str)Output:
[*********************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...
Wir empfehlen, die folgende Zelle auszuführen, um zu überprüfen, ob die Daten korrekt von der Yahoo Finance API heruntergeladen wurden. Wenn der Download fehlschlägt, wird eine Benachrichtigung angezeigt, dass die Daten stattdessen aus einer alternativen Online-Datenbank abgerufen werden.
if (
assets.isna().values.any()
): # If has nan values, load the data from the fallback online database
print(
"WARNING: Asset data format is invalid or incomplete. "
"Loading data from the remote backup database."
)
url = "https://raw.githubusercontent.com/Global-Data-Quantum/PortfolioData/1f3ebb95fdef245014a04d4273f688a2951e0061/data/qpo_tutorial_ibex.csv"
assets = pd.read_csv(url, index_col=0)Schritt 2: Definieren Sie die Problem-Eingaben
Die für die Definition des QUBO-Problems erforderlichen Parameter werden im Wörterbuch qubo_settings konfiguriert. Wir definieren die Anzahl der Zeitschritte (nt), die Anzahl der Bits für die Investitionsspezifikation (nq) und das Zeitfenster für jeden Zeitschritt (dt). Außerdem legen wir die maximale Investition pro Vermögenswert, den Risikoaversionskoeffizienten, die Transaktionsgebühr und den Restriktionskoeffizienten fest (Einzelheiten zur Problemformulierung finden Sie in unserem Papier ). Diese Einstellungen ermöglichen es uns, das QUBO-Problem an das jeweilige Investitionsszenario anzupassen.
qubo_settings = {
"nt": 4,
"nq": 2,
"dt": 30,
# maximum investment per asset is 2**nq/max_investment = 80%
"max_investment": 5,
"risk_aversion": 1000.0,
"transaction_fee": 0.01,
"restriction_coeff": 1.0,
}Das optimizer_settings Wörterbuch konfiguriert den Optimierungsprozess, einschließlich Parametern wie num_generations für die Anzahl der Iterationen und population_size für die Anzahl der Lösungsvorschläge pro Generation. Weitere Einstellungen regeln Aspekte wie die Rekombinationsrate, die Anzahl paralleler Jobs, die Batchgröße und den Mutationsbereich. Darüber hinaus legen die primitiven Einstellungen wie estimator_shots, estimator_precision, und die Konfigurationen des Quanten-Schätzers und des sampler_shotsQuanten-Samplers für den Optimierungsprozess fest.
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 Gesamtzahl der Schaltungen hängt von den Parametern von optimizer_settings ab und wird wie folgt berechnet: (num_generations + 1) * population_size.
Das ansatz_settings Wörterbuch konfiguriert den Quanten-Schaltkreis-Ansatz. Der ansatz Parameter gibt die Verwendung des "optimized_real_amplitudes" Ansatzes an, bei dem es sich um einen hardwareeffizienten Ansatz handelt, der für finanzielle Optimierungsprobleme entwickelt wurde. Die multiple_passmanager Option, die die Verwendung mehrerer Pass-Manager (einschließlich des standardmäßigen lokalen Qiskit-Pass-Managers und des Qiskit-Transpiler-Dienstes mit KI-Unterstützung) ermöglicht, um die Leistung und Effizienz der Schaltungsoptimierung zu verbessern, ist derzeit auf „false“ gesetzt, da diese Funktion vorübergehend deaktiviert ist.
ansatz_settings = {
"ansatz": "optimized_real_amplitudes",
"multiple_passmanager": False,
}Schließlich führen wir die Optimierung aus, indem wir die Funktion dpo_solver.run() ausführen und die vorbereiteten Eingaben übergeben. Dazu gehören das Asset-Datenverzeichnis (assets), die QUBO-Konfiguration (qubo_settings), die Optimierungsparameter (optimizer_settings) und die Einstellungen für den Quantenschaltkreis-Ansatz (ansatz_settings). Außerdem geben wir die Ausführungsdetails an, wie z. B. das Backend, und ob die Ergebnisse nachbearbeitet werden sollen. Damit wird der dynamische Portfolio-Optimierungsprozess auf dem ausgewählten Quanten-Backend eingeleitet.
dpo_job = dpo_solver.run(
assets=assets.to_dict(), # Insert assets as a dictionary
qubo_settings=qubo_settings,
optimizer_settings=optimizer_settings,
ansatz_settings=ansatz_settings,
backend_name="ibm_torino",
previous_session_id=[],
apply_postprocess=True,
)Schritt 3: Analysieren Sie die Optimierungsergebnisse
In diesem Abschnitt wird die Lösung mit den niedrigsten Zielkosten aus den Optimierungsergebnissen extrahiert und angezeigt. Neben den minimalen Zielkosten stellen wir auch die wichtigsten Kennzahlen der entsprechenden Lösung vor, darunter die Restriktionsabweichung, die Sharpe-Ratio und die Anlagerendite.
# Get the results of the job
dpo_result = dpo_job.result()
# Show the solution strategy
dpo_result["result"]Output:
{'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}")Output:
Minimum Objective Cost Found: -3.67
Best Solution:
- Restriction Deviation: 40.0%
- Sharpe Ratio: 14.54
- Return: 0.28
Der folgende Code zeigt, wie die Kostenverteilung eines Optimierungsalgorithmus mit einer Zufallsstichprobenverteilung visualisiert und verglichen werden kann. Ebenso untersuchen wir die Landschaft der QUBO-Zielfunktion (die aus der Funktionsausgabe geladen werden kann), indem wir sie mit zufälligen Investitionen bewerten. Wir stellen beide Verteilungen in normalisierter Amplitude dar, um den Unterschied zwischen dem Optimierungsprozess und der Zufallsstichprobe hinsichtlich der Kosten besser vergleichen zu können. Zusätzlich wird das mit DOCPlex erzielte Ergebnis als gestrichelte vertikale Referenzlinie dargestellt, die als klassischer Referenzwert dient. Wir verwenden die kostenlose Version von DOCPlex – die IBM® Open-Source-Bibliothek für mathematische Optimierung in Python – um dasselbe Problem klassisch zu lösen.
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"][
"counts"
] # List of how many times each solution occurred
cost_list = dpo_result["metadata"]["all_samples_metrics"][
"objective_costs"
] # 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:
Die Grafik zeigt, wie der Quantum Portfolio Optimizer konsequent optimierte Anlagestrategien liefert.
Referenzen
Tutorial-Umfrage
Bitte nehmen Sie sich eine Minute Zeit, um uns Ihr Feedback zu diesem Tutorial zu geben. Ihre Erkenntnisse helfen uns, unser Inhaltsangebot und unsere Nutzererfahrung zu verbessern. Link zur Umfrage