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
!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", # Bruuk den 44-Teken API_KEY, den du ut dat IBM Quantum Platform Home-Dashboard erstellt un siekert hest
)
# Griep op de Function to
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 vun IBEX 35-Symbolen
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]
# Mook en vollen Datums-Index, ok mit Wekenenn
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
# Reindexer, üm Wekenenn mit introsluten
data = data.reindex(full_index)
# Föll fehlende Weerten (för Wekenenn oder Fierdage) dörch forward/backward fill
data.ffill(inplace=True)
data.bfill(inplace=True)
series_list.append(data)
# Kombiner alle Serien in een enkelt DataFrame
df = pd.concat(series_list, axis=1)
# Konverter den Index na String för Konsistenz
df.index = df.index.astype(str)
# Konverter DataFrame na 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, # maximale Investitionen pro 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.
# Haal de Resultaten vun den Job
dpo_result = dpo_job.result()
# Wies 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
# Haal Resultaten vun den Job
dpo_result = dpo_job.result()
# Konverter Metadaten na en DataFrame, ahn 'session_id'
df = pd.DataFrame(dpo_result["metadata"]["all_samples_metrics"])
# Finn de minimalen objektiven Kosten
min_cost = df["objective_costs"].min()
print(f"Minimum Objective Cost Found: {min_cost:.2f}")
# Extraher de Reeg mit de neddrigsten Kosten
best_row = df[df["objective_costs"] == min_cost].iloc[0]
# Wies de Resultaten, de mit de beste Lösung verbunnen sünd
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):
"""
Plottet normaliseerte Resultaten för twee Stickproov-Resultaten.
Parameters:
dpo_x (array-like): X-Weerten för de VQE Post-processed-Kurv.
dpo_y_normalized (array-like): Y-Weerten (normaliseert) för de VQE Post-processed-Kurv.
random_x (array-like): X-Weerten för de Noise (Random)-Kurv.
random_y_normalized (array-like): Y-Weerten (normaliseert) för de Noise (Random)-Kurv.
"""
plt.figure(figsize=(6, 3))
plt.tick_params(axis="both", which="major", labelsize=12)
# Definer egen Farven
colors = ["#4823E8", "#9AA4AD"]
# Plott DPO-Resultaten
(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 Tofällige Resultaten
(line2,) = plt.plot(
random_x, random_y_normalized, label="Noise (Random)", color=colors[1]
)
line2.set_path_effects(
[patheffects.withStroke(linewidth=3, foreground="white")]
)
# Sett X-Achs-Ticks op 5 Eenheden-Schreed
plt.gca().xaxis.set_major_locator(MultipleLocator(5))
# Achs-Beschriftungen un Legende
plt.xlabel("Objective cost", fontsize=14)
plt.ylabel("Normalized Counts", fontsize=14)
# Föög DOCPlex-Referenzlinie hen
plt.axvline(
x=-4.11, color="black", linestyle="--", linewidth=1, label="DOCPlex"
) # DOCPlex-Weert
plt.ylim(bottom=0)
plt.legend()
# Pass Layout an
plt.tight_layout()
plt.show()
import numpy as np
from collections import defaultdict
# ================================
# SCHRITT 1: DPO-KOSTEN-VERDEELEN
# ================================
# Extraher Daten ut DPO-Resultaten
counts_list = dpo_result["metadata"]["all_samples_metrics"][
"objective_costs"
] # List, as oft elk Lösung optreden is
cost_list = dpo_result["metadata"]["all_samples_metrics"][
"counts"
] # List vun de tohören objektiven Funktions-Weerten (Kosten)
# Runn Kosten op een Dezimal un akkumuler Counts för elk eenmalige Kosten
dpo_counter = defaultdict(int)
for cost, count in zip(cost_list, counts_list):
rounded_cost = round(cost, 1)
dpo_counter[rounded_cost] += count
# Bereid Daten för dat Plotten vör
dpo_x = sorted(dpo_counter.keys()) # Sorteerte List vun Kosten-Weerten
dpo_y = [dpo_counter[c] for c in dpo_x] # Tohören Counts
# Normaliser de Counts op den Bereich [0, 1] för beteren Verglieck
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: TOFÄLLIGE KOSTEN-VERDEELEN
# ================================
# Lees de QUBO-Matrix
qubo = np.array(dpo_result["metadata"]["qubo"])
bitstring_length = qubo.shape[0]
num_random_samples = 100_000 # Antall vun tofällige Stickprooven to genereren
random_cost_counter = defaultdict(int)
# Generer tofällige Bitstrings un berekne ehr Kosten
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
# Bereid tofällige Daten för dat Plotten vör
random_x = sorted(random_cost_counter.keys())
random_y = [random_cost_counter[c] for c in random_x]
# Normaliser de tofällige Kosten-Verdeelen
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: PLOTTEN
# ================================
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