ข้ามไปยังเนื้อหาหลัก

ทำการปรับแต่งพอร์ตการลงทุนแบบไดนามิกด้วย Portfolio Optimizer ของ Global Data Quantum

หมายเหตุ

Qiskit Functions เป็นฟีเจอร์ทดลองที่ให้บริการเฉพาะผู้ใช้ IBM Quantum® Premium Plan, Flex Plan และ On-Prem (ผ่าน IBM Quantum Platform API) Plan เท่านั้น ขณะนี้อยู่ในสถานะ preview release และอาจมีการเปลี่ยนแปลง

ประมาณการการใช้งาน: ประมาณ 55 นาทีบนโปรเซสเซอร์ Heron r2 (หมายเหตุ: นี่เป็นเพียงการประมาณเท่านั้น เวลาจริงอาจแตกต่างออกไป)

ภูมิหลัง

ปัญหาการปรับแต่งพอร์ตการลงทุนแบบไดนามิกมีเป้าหมายเพื่อค้นหากลยุทธ์การลงทุนที่เหมาะสมที่สุดในหลายช่วงเวลา เพื่อให้ได้ผลตอบแทนพอร์ตสูงสุดและลดความเสี่ยงให้น้อยที่สุด ภายใต้ข้อจำกัดบางอย่างเช่น งบประมาณ ค่าธรรมเนียมการทำธุรกรรม หรือความเกลียดความเสี่ยง ต่างจากการปรับแต่งพอร์ตมาตรฐานที่พิจารณาการปรับสมดุลพอร์ตเพียงครั้งเดียว เวอร์ชันไดนามิกคำนึงถึงลักษณะที่เปลี่ยนแปลงของสินทรัพย์และปรับการลงทุนตามการเปลี่ยนแปลงในผลการดำเนินงานของสินทรัพย์ตลอดเวลา

บทแนะนำนี้แสดงให้เห็นวิธีการปรับแต่งพอร์ตการลงทุนแบบไดนามิกโดยใช้ฟังก์ชัน Qiskit ชื่อ Quantum Portfolio Optimizer โดยเฉพาะอย่างยิ่ง เราจะแสดงวิธีใช้ฟังก์ชันแอปพลิเคชันนี้เพื่อแก้ปัญหาการจัดสรรการลงทุนในหลายช่วงเวลา

แนวทางนี้เกี่ยวข้องกับการกำหนดสูตรการปรับแต่งพอร์ตเป็นปัญหา Quadratic Unconstrained Binary Optimization (QUBO) แบบหลายวัตถุประสงค์ โดยเฉพาะอย่างยิ่ง เรากำหนดฟังก์ชัน QUBO ว่า OO เพื่อปรับแต่งวัตถุประสงค์สี่ข้อพร้อมกัน ได้แก่:

  • เพิ่มฟังก์ชันผลตอบแทน FF ให้สูงสุด
  • ลดความเสี่ยงของการลงทุน RR ให้น้อยที่สุด
  • ลดค่าธรรมเนียมการทำธุรกรรม CC ให้น้อยที่สุด
  • ปฏิบัติตามข้อจำกัดการลงทุน ซึ่งกำหนดในพจน์เพิ่มเติมเพื่อลด PP ให้น้อยที่สุด

โดยสรุป เพื่อจัดการกับวัตถุประสงค์เหล่านี้ เรากำหนดฟังก์ชัน QUBO เป็น O=F+γ2R+C+ρP,O = -F + \frac{\gamma}{2} R + C + \rho P, โดยที่ γ\gamma คือสัมประสิทธิ์ความเกลียดความเสี่ยง และ ρ\rho คือสัมประสิทธิ์การเสริมแรงข้อจำกัด (ตัวคูณ Lagrange) สูตรชัดเจนสามารถดูได้ใน Eq. (15) ของต้นฉบับของเรา [1]

เราแก้ปัญหาโดยใช้วิธีไฮบริดควอนตัม-คลาสสิก ที่อิงจาก Variational Quantum Eigensolver (VQE) ในการตั้งค่านี้ Circuit ควอนตัมประมาณค่าฟังก์ชันต้นทุน ในขณะที่การปรับแต่งแบบคลาสสิกทำโดยใช้อัลกอริธึม Differential Evolution ซึ่งช่วยให้นำทางในพื้นที่โซลูชันได้อย่างมีประสิทธิภาพ จำนวน Qubit ที่ต้องการขึ้นอยู่กับสามปัจจัยหลัก ได้แก่ จำนวนสินทรัพย์ na จำนวนช่วงเวลา nt และความละเอียดบิตที่ใช้แทนการลงทุน nq โดยเฉพาะอย่างยิ่ง จำนวน Qubit ขั้นต่ำในปัญหาของเราคือ na*nt*nq

สำหรับบทแนะนำนี้ เราเน้นการปรับแต่งพอร์ตระดับภูมิภาคตามดัชนี Spanish IBEX 35 โดยเฉพาะอย่างยิ่ง เราใช้พอร์ตเจ็ดสินทรัพย์ตามที่ระบุในตารางด้านล่าง:

IBEX 35 PortfolioACS.MCITX.MCFER.MCELE.MCSCYR.MCAENA.MCAMS.MC

เราปรับสมดุลพอร์ตในสี่ช่วงเวลา โดยแต่ละช่วงห่างกัน 30 วัน เริ่มตั้งแต่วันที่ 1 พฤศจิกายน 2022 ตัวแปรการลงทุนแต่ละตัวถูกเข้ารหัสโดยใช้สองบิต ซึ่งส่งผลให้ปัญหาต้องใช้ 56 Qubit ในการแก้

เราใช้ ansatz แบบ Optimized Real Amplitudes ซึ่งเป็นการปรับแต่ง ansatz แบบ Real Amplitudes มาตรฐานให้มีประสิทธิภาพด้านฮาร์ดแวร์ โดยออกแบบมาเฉพาะเพื่อปรับปรุงประสิทธิภาพสำหรับปัญหาการปรับแต่งทางการเงินประเภทนี้

การประมวลผลควอนตัมดำเนินการบน Backend ibm_torino สำหรับคำอธิบายโดยละเอียดเกี่ยวกับการกำหนดสูตรปัญหา วิธีการ และการประเมินประสิทธิภาพ โปรดดูต้นฉบับที่เผยแพร่ [1]

ข้อกำหนด

# 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

การตั้งค่า

ในการใช้ Quantum Portfolio Optimizer ให้เลือกฟังก์ชันผ่าน Qiskit Functions Catalog คุณต้องมีบัญชี IBM Quantum Premium Plan หรือ Flex Plan พร้อมใบอนุญาตจาก Global Data Quantum เพื่อรันฟังก์ชันนี้

ขั้นแรก ยืนยันตัวตนด้วย API key ของคุณ จากนั้นโหลดฟังก์ชันที่ต้องการจาก Qiskit Functions Catalog ที่นี่คุณกำลังเข้าถึงฟังก์ชัน quantum_portfolio_optimizer จาก catalog โดยใช้คลาส QiskitFunctionsCatalog ฟังก์ชันนี้ช่วยให้เราใช้ตัวแก้ปัญหา 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")

ขั้นตอนที่ 1: อ่านพอร์ตการลงทุนที่ป้อนเข้า

ในขั้นตอนนี้ เราโหลดข้อมูลย้อนหลังสำหรับสินทรัพย์เจ็ดตัวที่เลือกจากดัชนี IBEX 35 โดยเฉพาะอย่างยิ่งตั้งแต่ 1 พฤศจิกายน 2022 ถึง 1 เมษายน 2023

เราดึงข้อมูลโดยใช้ Yahoo Finance API โดยเน้นที่ราคาปิด จากนั้นข้อมูลจะถูกประมวลผลเพื่อให้แน่ใจว่าสินทรัพย์ทั้งหมดมีจำนวนวันที่มีข้อมูลเท่ากัน ข้อมูลที่ขาดหาย (วันที่ไม่ได้ทำการซื้อขาย) จะถูกจัดการอย่างเหมาะสม เพื่อให้สินทรัพย์ทั้งหมดอยู่ในวันที่เดียวกัน

ข้อมูลถูกจัดโครงสร้างใน DataFrame ด้วยรูปแบบที่สม่ำเสมอในทุกสินทรัพย์

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...

ขั้นตอนที่ 2: กำหนดพารามิเตอร์ป้อนเข้าของปัญหา

พารามิเตอร์ที่จำเป็นในการกำหนดปัญหา QUBO ถูกตั้งค่าใน dictionary qubo_settings เรากำหนดจำนวนช่วงเวลา (nt) จำนวนบิตสำหรับการระบุการลงทุน (nq) และช่วงเวลาสำหรับแต่ละช่วงเวลา (dt) นอกจากนี้ เรายังกำหนดการลงทุนสูงสุดต่อสินทรัพย์ สัมประสิทธิ์ความเกลียดความเสี่ยง ค่าธรรมเนียมการทำธุรกรรม และสัมประสิทธิ์ข้อจำกัด (ดูรายละเอียดการกำหนดสูตรปัญหาใน บทความของเรา) การตั้งค่าเหล่านี้ช่วยให้เราปรับแต่งปัญหา QUBO ให้เข้ากับสถานการณ์การลงทุนเฉพาะได้

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,
}

dictionary optimizer_settings ตั้งค่ากระบวนการปรับแต่ง รวมถึงพารามิเตอร์เช่น num_generations สำหรับจำนวนรอบการวนซ้ำ และ population_size สำหรับจำนวนโซลูชันผู้สมัครต่อรุ่น การตั้งค่าอื่นควบคุมด้านต่างๆ เช่น อัตราการรวมกัน งานคู่ขนาน ขนาดชุด และช่วงการกลายพันธุ์ นอกจากนี้ การตั้งค่า primitive เช่น estimator_shots, estimator_precision และ sampler_shots กำหนดการตั้งค่า Estimator และ Sampler ควอนตัมสำหรับกระบวนการปรับแต่ง

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,
},
}
หมายเหตุ

จำนวน Circuit ทั้งหมดขึ้นอยู่กับพารามิเตอร์ optimizer_settings และคำนวณเป็น (num_generations + 1) * population_size

dictionary ansatz_settings ตั้งค่า ansatz ของ Circuit ควอนตัม พารามิเตอร์ ansatz ระบุการใช้แนวทาง "optimized_real_amplitudes" ซึ่งเป็น ansatz ที่มีประสิทธิภาพด้านฮาร์ดแวร์ที่ออกแบบมาสำหรับปัญหาการปรับแต่งทางการเงิน นอกจากนี้ การตั้งค่า multiple_passmanager ถูกเปิดใช้งานเพื่ออนุญาตให้ใช้ pass manager หลายตัว (รวมถึง pass manager ในเครื่องของ Qiskit และบริการ Transpiler ที่ขับเคลื่อนด้วย AI ของ Qiskit) ในระหว่างกระบวนการปรับแต่ง ซึ่งช่วยปรับปรุงประสิทธิภาพโดยรวมและประสิทธิภาพการประมวลผล Circuit

ansatz_settings = {
"ansatz": "optimized_real_amplitudes",
"multiple_passmanager": False,
}

สุดท้าย เราดำเนินการปรับแต่งโดยการรันฟังก์ชัน dpo_solver.run() และส่งข้อมูลป้อนเข้าที่เตรียมไว้ ซึ่งรวมถึง dictionary ข้อมูลสินทรัพย์ (assets) การตั้งค่า QUBO (qubo_settings) พารามิเตอร์การปรับแต่ง (optimizer_settings) และการตั้งค่า ansatz ของ Circuit ควอนตัม (ansatz_settings) นอกจากนี้ เรายังระบุรายละเอียดการดำเนินการเช่น Backend และว่าจะใช้การประมวลผลหลังกับผลลัพธ์หรือไม่ การดำเนินการนี้จะเริ่มต้นกระบวนการปรับแต่งพอร์ตการลงทุนแบบไดนามิกบน 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,
)

ขั้นตอนที่ 3: วิเคราะห์ผลลัพธ์การปรับปรุง

ในส่วนนี้ เราจะดึงและแสดงผลลัพธ์ที่มีต้นทุนเชิงวัตถุประสงค์ต่ำที่สุดจากผลการปรับปรุง นอกจากต้นทุนเชิงวัตถุประสงค์ขั้นต่ำแล้ว เรายังแสดงเมตริกสำคัญที่เกี่ยวข้องกับผลลัพธ์นั้น ได้แก่ ค่าเบี่ยงเบนจากข้อจำกัด อัตราส่วน Sharpe และผลตอบแทนการลงทุน

# 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

โค้ดด้านล่างแสดงวิธีการแสดงภาพและเปรียบเทียบการกระจายของต้นทุนจากอัลกอริทึมการปรับปรุงกับการกระจายของการสุ่มตัวอย่างแบบสุ่ม นอกจากนี้ เรายังสำรวจภูมิทัศน์ของฟังก์ชันเชิงวัตถุประสงค์ QUBO (ซึ่งสามารถโหลดได้จากเอาต์พุตของฟังก์ชัน) โดยประเมินด้วยการลงทุนแบบสุ่ม เราพล็อตการกระจายทั้งสองในรูปแบบที่ปรับมาตรฐานด้านแอมพลิจูดเพื่อให้เปรียบเทียบได้ง่ายขึ้นว่ากระบวนการปรับปรุงแตกต่างจากการสุ่มตัวอย่างอย่างไรในแง่ของต้นทุน นอกจากนี้ ผลลัพธ์จาก DOCPlex ยังแสดงเป็นเส้นแนวตั้งประเพื่อใช้เป็นเกณฑ์อ้างอิงแบบคลาสสิก เราใช้ DOCPlex เวอร์ชันฟรี — ไลบรารีโอเพ่นซอร์สของ IBM® สำหรับการปรับปรุงทางคณิตศาสตร์ใน Python — เพื่อแก้ปัญหาเดียวกันแบบคลาสสิก

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)

Output of the previous code cell

กราฟแสดงให้เห็นว่าตัวปรับปรุงพอร์ตโฟลิโอควอนตัมส่งคืนกลยุทธ์การลงทุนที่ผ่านการปรับปรุงแล้วอย่างสม่ำเสมอ

อ้างอิง

[1] Nodar, Álvaro, Irene De León, Danel Arias, Ernesto Mamedaliev, María Esperanza Molina, Manuel Martín-Cordero, Senaida Hernández-Santana et al. "Scaling the Variational Quantum Eigensolver for Dynamic Portfolio Optimization." arXiv preprint arXiv:2412.19150 (2024).

แบบสำรวจบทแนะนำ

กรุณาใช้เวลาสักครู่เพื่อให้ข้อเสนอแนะเกี่ยวกับบทแนะนำนี้ ความคิดเห็นของคุณจะช่วยให้เราปรับปรุงเนื้อหาและประสบการณ์ผู้ใช้ให้ดียิ่งขึ้น Link to survey

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.

Source: IBM Quantum docs — updated 27 เม.ย. 2569
English version on doQumentation — updated 7 พ.ค. 2569
This translation based on the English version of 9 เม.ย. 2569