Mook dynamische Portfolio-Optimierung mit den Portfolio Optimizer vun Global Data Quantum
Qiskit Functions sünd en experimentell Feature un blots för IBM Quantum® Premium Plan, Flex Plan un On-Prem (över IBM Quantum Platform API) Plan-Brukers verfögbor. Se sünd in Preview-Status un künnt sik noch ännern.
Verbruuks-Schattung: Üngefähr 55 Minuten op enen Heron r2-Prozessor. (Oppasst: Dat is blots en Schattung. De wirkliche Tiet kann anners utfallen.)
Achtergrund
Dat dynamische Portfolio-Optimierungs-Problem hett dat Teel, de optimal Investitions-Strategie över mehr Tieten to finnen, üm den verwachten Rendite vun dat Portfolio to maximeren un de Risiken to minimeren — oft ünner bestimmte Bedingungen as Budget, Transaktions-Kosten oder Risiko-Aversion. Anners as de standard Portfolio-Optimierung, de blots een Moment för dat Rebalanceren vun dat Portfolio betrach, beröcksichtigt de dynamische Versioon de Verännerungen vun den Weert vun de Assets un passt de Investitionen nipp un nau an, as sik de Performance vun de Assets över de Tiet verännert.
Disse Tutorial hier wiest, as wi dynamische Portfolio-Optimierung mit de Quantum Portfolio Optimizer Qiskit Function maken künnt. Speziell wiesen wi, as wi disse Application-Function bruken künnt, üm en Investitions-Verdeelings-Problem över mehr Tiet-Schreed to lösen.
De Ansatz formulert de Portfolio-Optimierung as en Multi-Objective Quadratic Unconstrained Binary Optimization (QUBO) Problem. Speziell formuleren wi de QUBO-Funkschoon so, dat se veer verschedene Teelsettungen gliektierig optimeert:
- Maximeren de Rendite-Funkschoon
- Minimeren dat Risiko vun de Investitionen
- Minimeren de Transaktions-Kosten
- Holl di an de Investitions-Beschränkungen, formulert in enen tausätzlichen Term för dat Minimeren vun .
Tosamenfatt formuleren wi de QUBO-Funkschoon so wo de Risiko-Aversions-Koëffizient is un de Beschränkungs-Verstärkungs-Koëffizient (Lagrange-Multiplikator). De explizite Formulierung finnt wi in Gl. (15) vun uns Veröffentlichung [1].
Wi löst dat mit ene hybride Quantum-Klassische Methode, de op den Variational Quantum Eigensolver (VQE) baseert. In dit Setup schattet de Quantenschaltkreis de Kosten-Funkschoon, wohrend de klassische Optimierung mit den Differential Evolution-Algorithmus dörchdröven wurr, wat dat möglich maakt, dat Lösungs-Landschapp effizient to dörsöken. De Antall vun Qubits, de wi bruukt, hangt vun dree Hooptfaktoren af: de Antall vun Assets na, de Antall vun Tiet-Perioden nt un de Bit-Uplösung för de Dorstellen vun de Investitionen nq. Konkret is de minimale Antall vun Qubits in uns Problem na*nt*nq.
In dit Tutorial konzentriert wi uns op de Optimierung vun en regionaal Portfolio, dat op den spaanschen IBEX 35-Index baseert. Speziell bruukt wi en Söven-Asset-Portfolio as in de Tabell hier:
| IBEX 35 Portfolio | ACS.MC | ITX.MC | FER.MC | ELE.MC | SCYR.MC | AENA.MC | AMS.MC |
|---|
Wi rebalanceren uns Portfolio in veer Tiet-Schreed, elk 30 Dag utenanner, anfangend an 1. November 2022. Elk Investitions-Variable wurr mit twee Bits kodert. Dat föhrt to en Problem, dat 56 Qubits bruukt, üm dat to lösen.
Wi bruukt den Optimized Real Amplitudes-Ansatz, en anpasste un hardware-effiziente Adaptatioon vun den standard Real Amplitudes-Ansatz, speziell anpasst, üm de Performance för disse Oort vun finanziell Optimierung to verbetern.
De Quantum-Utföhrung wurr op dat ibm_torino-Backend dörchdröven. För ene detaillerte Verkloren vun de Problem-Formulierung, Methodologie un Performance-Evaluierung kiek na dat veröffentlichte Manuskript [1].
Vöruttsetungen
# 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üsten
Üm den Quantum Portfolio Optimizer to bruken, wähl dat Function-Objekt över den Qiskit Functions Catalog ut. Du bruukst en IBM Quantum Premium Plan- oder Flex Plan-Konto mit ene Lizenz vun Global Data Quantum, üm disse Function to bruken.
Eerst authentifizerst du di mit dien API-Slötel. Denn laad dat wünschte Function-Objekt ut den Qiskit Functions Catalog. Hier griepst du op de quantum_portfolio_optimizer-Function ut den Catalog to, indem du de QiskitFunctionsCatalog-Klass bruukst. Disse Function verlöövt dat uns, den vördefineerten Quantum Portfolio Optimization-Solver to bruken.
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")
Schritt 1: Lees dat Input-Portfolio
In dit Schritt laadt wi historische Daten för de söven utwählten Assets ut den IBEX 35-Index, speziell vun 1. November 2022 bit 1. April 2023.
Wi haalt de Daten över de Yahoo Finance-API un konzentriert uns op de Sluttkoersen. De Daten wurrt denn so bearbeidt, dat alle Assets de glieksülvige Antall vun Dag mit Daten hebbt. Fehlende Daten (Nich-Handels-Dag) wurrt passend behannelt, so dat alle Assets op de glieksülvigen Datums utrücht sünd.
De Daten sünd in en DataFrame mit konsistente Formateren för alle Assets struktureert.
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...
Schritt 2: Definer de Problem-Ingaven
De Parameters, de wi bruukt, üm dat QUBO-Problem to defineren, wurrt in dat qubo_settings-Dictionary konfiguert. Wi definert de Antall vun Tiet-Schreed (nt), de Antall vun Bits för de Investitions-Spezifikatschoon (nq) un dat Tiet-Finster för elk Tiet-Schritt (dt). Tausätzlich sett wi de maximale Investitionen pro Asset, den Risiko-Aversions-Koëffizient, de Transaktions-Gebühr un den Beschränkungs-Koëffizient (kiek na uns Paper för Details över de Problem-Formulierung). Disse Instellungen verlöövt dat uns, dat QUBO-Problem an dat speziell Investitions-Szenario antopassen.
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 konfiguert den Optimierungs-Prozess, mit Parameters as num_generations för de Antall vun Iteratioons un population_size för de Antall vun Kandidaten-Lösungen pro Generatschoon. Anner Instellungen kontrolleert Aspekten as de Rekombinations-Rat, parallele Jobs, Batch-Grött un Mutations-Bereich. Tausätzlich definiern de Primitive-Instellungen as estimator_shots, estimator_precision un sampler_shots de Quantum-Estimator- un Sampler-Konfiguratschonen för den 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 Gesamt-Antall vun Schaltkreesen hangt vun de optimizer_settings-Parameters af un wurr berekent as (num_generations + 1) * population_size.
Dat ansatz_settings-Dictionary konfiguert den Quantum-Schaltkreis-Ansatz. De ansatz-Parameter spezifizeert de Bruuk vun den "optimized_real_amplitudes"-Ansatz, wat en hardware-effiziente Ansatz is, de för finanziell Optimierungs-Problemen desigent is. Tausätzlich is de multiple_passmanager-Instellen aktiveert, üm mehr Pass-Manager (inklusiv den Standard lokaal Qiskit Pass-Manager un den Qiskit AI-andrevene Transpiler-Service) während den Optimierungs-Prozess to verlöven, wat de Gesamt-Performance un Effizienz vun de Schaltkreis-Utföhrung verbetert.
ansatz_settings = {
"ansatz": "optimized_real_amplitudes",
"multiple_passmanager": False,
}
To'n Sluss föhrt wi de Optimierung ut, indem wi de dpo_solver.run()-Funkschoon utföhrt un de vörbereide Ingaven dörgeven. Dat ümfatt dat Asset-Daten-Dictionary (assets), de QUBO-Konfiguratschoon (qubo_settings), Optimierungs-Parameters (optimizer_settings) un de Quantum-Schaltkreis-Ansatz-Instellungen (ansatz_settings). Tausätzlich spezifizert wi de Utföhrungs-Details as dat Backend un of wi Post-Processing op de Resultaten anwennt. Dat startert den dynamischen Portfolio-Optimierungs-Prozess op dat utwä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,
)
Schritt 3: Analyser de Optimierungs-Resultaten
In dit Afsnitt extrahert wi un wiest de Lösung mit de neddrigsten objektiven Kosten ut de Optimierungs-Resultaten. Neben de minimalen objektiven Kosten präsentert wi ok Slötel-Metriken, de mit de tohören Lösung verbunnen sünd, as de Beschränkungs-Afwieken, 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
De folgend Code wiest, as wi de Kosten-Verdeelen vun en Optimierungs-Algorithmus mit ene tofällige Stickproov-Verdeelen visualiseren un verglieken künnt. Ähnlich utforscht wi de Landschapp vun de QUBO-objektive Funkschoon (de ut den Function-Output laadt wurrn kann), indem wi se mit tofällige Investitionen evalueert. Wi plott beide Verdeelungen, normaliseert in Amplitude, för en eenfacheren Verglieck, as sik de Optimierungs-Prozess vun tofällige Stickprooven in Bezug op Kosten unnerscheidt. Tausätzlich wurr dat Resultat, dat wi mit DOCPlex kregen hebbt, as stippelte vertikale Referenzlinie mitopnahmen, üm as klassische Benchmark to deenen. Wi bruukt de free Versioon vun DOCPlex — de IBM® Open-Source-Bibliothek för mathematische Optimierung in Python — üm dat glieksülvige Problem klassisch to 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"][
"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)
De Graph wiest, as de Quantum Portfolio Optimizer konsistent optimeerte Investitions-Strategien torüchgifft.
Referenzen
Tutorial-Ümfraag
Nehm di bitte en Minut Tiet, üm Feedback över dit Tutorial to geven. Dien Insichten helpt uns, uns Content-Anbod un User-Experience to verbetern. Link to de Ümfraag