Proveď dynamickou optimalizaci portfolia pomocí Portfolio Optimizeru od Global Data Quantum
Qiskit Functions jsou experimentální funkce dostupné pouze uživatelům IBM Quantum® Premium Plan, Flex Plan a On-Prem (přes IBM Quantum Platform API) Plan. Jsou ve stavu náhledu a mohou se změnit.
Odhadovaný čas využití: přibližně 55 minut na procesoru Heron r2. (POZNÁMKA: Jedná se pouze o odhad. Skutečný čas běhu se může lišit.)
Pozadí
Cílem dynamické optimalizace portfolia je najít optimální investiční strategii napříč více časovými obdobími tak, aby se maximalizoval očekávaný výnos portfolia a minimalizovalo riziko – často za určitých omezení, jako je rozpočet, transakční náklady nebo averze k riziku. Na rozdíl od standardní optimalizace portfolia, která uvažuje jediný okamžik pro rebalancování portfolia, dynamická verze bere v úvahu vývoj aktiv a přizpůsobuje investice na základě změn výkonnosti aktiv v průběhu času.
Tento tutoriál ukazuje, jak provést dynamickou optimalizaci portfolia pomocí funkce Qiskit – Quantum Portfolio Optimizeru. Konkrétně ilustrujeme, jak tuto aplikační funkci použít k řešení problému alokace investic napříč více časovými kroky.
Přístup spočívá ve formulaci optimalizace portfolia jako víceúčelového problému kvadratické nekonstrantní bin ární optimalizace (QUBO). Konkrétně formulujeme funkci QUBO tak, aby současně optimalizovala čtyři různé cíle:
- Maximalizuj funkci výnosu
- Minimalizuj riziko investice
- Minimalizuj transakční náklady
- Dodržuj investiční omezení formulovaná jako dodatečný člen k minimalizaci .
Shrnuto, k dosažení těchto cílů formulujeme funkci QUBO jako kde je koeficient averze k riziku a je koeficient vynucení omezení (Lagrangeův multiplikátor). Explicitní formulaci najdeš v rovnici (15) našeho rukopisu [1].
Řešení hledáme hybridní kvantově-klasickou metodou založenou na Variatonálním Kvantovém Eigensolveru (VQE). V tomto nastavení kvantový Circuit odhaduje nákladovou funkci, zatímco klasická optimalizace se provádí algoritmem Diferenciální Evoluce, což umožňuje efektivní prozkoumání prostoru řešení. Počet potřebných Qubitů závisí na třech hlavních faktorech: počtu aktiv na, počtu časových období nt a bitovém rozlišení pro reprezentaci investice nq. Minimální počet Qubitů v našem problému je konkrétně na*nt*nq.
Pro tento tutoriál se zaměříme na optimalizaci regionálního portfolia na základě španělského indexu IBEX 35. Konkrétně používáme portfolio sedmi aktiv, jak je uvedeno v tabulce níže:
| Portfolio IBEX 35 | ACS.MC | ITX.MC | FER.MC | ELE.MC | SCYR.MC | AENA.MC | AMS.MC |
|---|
Portfolio rebalancujeme ve čtyřech časových krocích, přičemž každý krok je oddělen 30denním intervalem začínajícím 1. listopadu 2022. Každá investiční proměnná je zakódována pomocí dvou bitů. To vede k problému vyžadujícímu 56 Qubitů k řešení.
Používáme ansatz Optimized Real Amplitudes – přizpůsobenou a hardwarově efektivní adaptaci standardního ansatzu Real Amplitudes, speciálně navrženou pro zlepšení výkonu u tohoto typu finančních optimalizačních problémů.
Kvantové výpočty se provádějí na Backend ibm_torino. Podrobné vysvětlení formulace problému, metodologie a hodnocení výkonu najdeš v publikovaném rukopisu [1].
Požadavky
# 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
Nastavení
Pro použití Quantum Portfolio Optimizeru vyber funkci prostřednictvím katalogu Qiskit Functions. K spuštění této funkce potřebuješ účet IBM Quantum Premium Plan nebo Flex Plan s licencí od Global Data Quantum.
Nejprve se ověř svým API klíčem. Poté načti požadovanou funkci z katalogu Qiskit Functions. Zde přistupuješ k funkci quantum_portfolio_optimizer z katalogu pomocí třídy QiskitFunctionsCatalog. Tato funkce nám umožňuje použít předdefinovaný solver Quantum Portfolio Optimization.
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")
Krok 1: Načti vstupní portfolio
V tomto kroku načteme historická data pro sedm vybraných aktiv z indexu IBEX 35, konkrétně od 1. listopadu 2022 do 1. dubna 2023.
Data stahujeme pomocí Yahoo Finance API se zaměřením na závěrečné ceny. Data jsou poté zpracována tak, aby všechna aktiva měla stejný počet dnů s daty. Případná chybějící data (neobchodní dny) jsou ošetřena odpovídajícím způsobem a všechna aktiva jsou zarovnána na stejná data.
Data jsou strukturována v DataFrame s konzistentním formátováním napříč všemi aktivy.
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...
Krok 2: Definuj vstupní parametry problému
Parametry potřebné k definování problému QUBO jsou nakonfigurovány ve slovníku qubo_settings. Definujeme počet časových kroků (nt), počet bitů pro specifikaci investice (nq) a časové okno pro každý časový krok (dt). Dále nastavíme maximální investici na aktivum, koeficient averze k riziku, transakční poplatek a koeficient omezení (viz náš článek pro podrobnosti o formulaci problému). Tato nastavení nám umožňují přizpůsobit problém QUBO konkrétnímu investičnímu scénáři.
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,
}
Slovník optimizer_settings konfiguruje optimalizační proces, včetně parametrů jako num_generations pro počet iterací a population_size pro počet kandidátních řešení v každé generaci. Další nastavení ovládají aspekty jako míra rekombinace, paralelní úlohy, velikost dávky a rozsah mutace. Navíc nastavení primitivů, jako jsou estimator_shots, estimator_precision a sampler_shots, definují konfigurace kvantového Estimatoru a Sampleru pro optimalizační proces.
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,
},
}
Celkový počet Circuit závisí na parametrech optimizer_settings a je vypočítán jako (num_generations + 1) * population_size.
Slovník ansatz_settings konfiguruje ansatz kvantového Circuit. Parametr ansatz specifikuje použití přístupu "optimized_real_amplitudes", což je hardwarově efektivní ansatz navržený pro finanční optimalizační problémy. Nastavení multiple_passmanager je aktivováno, aby umožnilo použití více pass managerů (včetně výchozího lokálního pass manageru Qiskit a transpilační služby Qiskit s podporou AI) během optimalizačního procesu, čímž se zlepšuje celkový výkon a efektivita spouštění Circuit.
ansatz_settings = {
"ansatz": "optimized_real_amplitudes",
"multiple_passmanager": False,
}
Nakonec spustíme optimalizaci voláním funkce dpo_solver.run(), přičemž předáme připravené vstupy. Ty zahrnují slovník dat aktiv (assets), konfiguraci QUBO (qubo_settings), optimalizační parametry (optimizer_settings) a nastavení ansatzu kvantového Circuit (ansatz_settings). Dále určujeme podrobnosti spuštění, jako je Backend, a to, zda se mají na výsledky aplikovat dodatečné zpracování. Tím se zahájí proces dynamické optimalizace portfolia na vybraném kvantovém 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,
)
Krok 3: Analýza výsledků optimalizace
V této části extrahujeme a zobrazíme řešení s nejnižšími náklady cílové funkce z výsledků optimalizace. Společně s minimálními náklady cílové funkce prezentujeme také klíčové metriky spojené s daným řešením, včetně odchylky od omezení, Sharpeho poměru a výnosu z investic.
# 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
Následující kód ukazuje, jak vizualizovat a porovnat rozdělení nákladů optimalizačního algoritmu s rozdělením náhodného vzorkování. Podobně prozkoumáváme krajinu cílové funkce QUBO (kterou lze načíst z výstupu funkce) tak, že ji vyhodnocujeme s náhodnými investicemi. Obě rozdělení vykreslujeme normalizovaná v amplitudě pro snazší porovnání toho, jak se optimalizační proces liší od náhodného vzorkování z hlediska nákladů. Navíc je výsledek získaný pomocí DOCPlex zahrnut jako přerušovaná svislá referenční čára sloužící jako klasický benchmark. Používáme bezplatnou verzi DOCPlex — open-source knihovnu IBM® pro matematickou optimalizaci v Pythonu — k řešení stejného problému klasickým způsobem.
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)
Graf ukazuje, jak kvantový optimalizátor portfolia konzistentně vrací optimalizované investiční strategie.
Reference
Průzkum k tutoriálu
Věnuj prosím chvilku zpětné vazbě k tomuto tutoriálu. Tvoje postřehy nám pomohou zlepšit obsah a uživatelský zážitek. Odkaz na průzkum
Note: This survey is provided by IBM Quantum and relates to the original English content. To give feedback on doQumentation's website, translations, or code execution, please open a GitHub issue.