Global Data Quantum의 포트폴리오 최적화 도구를 활용한 동적 포트폴리오 최적화
키스킷 기능은 IBM 퀀텀 프리미엄 요금제, 플렉스 요금제 및 온프레미스( IBM 퀀텀 플랫폼 API를 통해) 요금제 사용자에게만 제공되는 실험적 기능입니다. 프리뷰 릴리스 상태이며 변경될 수 있습니다.
사용 예상 시간: Heron r2 프로세서에서 약 55분. (참고: 이는 추정치일 뿐입니다. 실제 런타임은 다를 수 있습니다.)
배경
동적 포트폴리오 최적화 문제는 예산, 거래 비용 또는 위험 회피와 같은 특정 제약 조건 하에서 예상 포트폴리오 수익을 극대화하고 위험을 최소화하기 위해 여러 기간에 걸쳐 최적의 투자 전략을 찾는 것을 목표로 합니다. 포트폴리오 리밸런싱을 한 번만 고려하는 표준 포트폴리오 최적화와 달리 동적 버전은 자산의 진화하는 특성을 고려하고 시간에 따른 자산 성과 변화에 따라 투자를 조정합니다.
이 튜토리얼에서는 퀀텀 포트폴리오 옵티마이저 키스킷 함수를 사용하여 동적 포트폴리오 최적화를 수행하는 방법을 설명합니다. 특히 이 애플리케이션 함수를 사용하여 여러 시간 단계에 걸친 투자 할당 문제를 해결하는 방법을 설명합니다.
이 접근 방식에는 포트폴리오 최적화를 다목적 이차 제약 없는 이진 최적화(QUBO) 문제로 공식화하는 것이 포함됩니다. 구체적으로는 네 가지 목표를 동시에 최적화하는 QUBO 함수( )를 공식화합니다:
- 반환 함수 최대화
- 투자 위험 최소화
- 거래 비용 최소화
- 투자 제한을 최소화하기 위해 추가 약관에 명시된 투자 제한을 준수하십시오 .
요약하면, 이러한 목표를 달성하기 위해 QUBO 함수를 다음과 같이 공식화합니다 여기서 은 위험 회피 계수이고 은 제한 강화 계수(라그랑주 승수)입니다. 명시적인 공식은 Eq. (15) 원고 [1] 의 일부입니다.
저희는 변형 양자 아이겐솔버(VQE)를 기반으로 한 하이브리드 양자-클래식 방법을 사용하여 해결합니다. 이 설정에서는 양자 회로가 비용 함수를 추정하는 반면, 기존 최적화는 미분 진화 알고리즘을 사용하여 수행되므로 솔루션 환경을 효율적으로 탐색할 수 있습니다. 필요한 큐비트 수는 자산 수 na, 기간 수 nt, 투자를 나타내는 데 사용되는 비트 해상도 nq 의 세 가지 주요 요소에 따라 달라집니다. 구체적으로, 이 문제의 최소 큐비트 수는 na*nt*nq 입니다.
이 튜토리얼에서는 스페인 IBEX 35 지수를 기반으로 지역별 포트폴리오를 최적화하는 데 중점을 둡니다. 구체적으로는 아래 표에 표시된 대로 7가지 자산 포트폴리오를 사용합니다:
IBEX 35 포트폴리오 | ACS.MC | ITX.MC | FER.MC | ELE.MC | SCYR.MC | AENA.MC | AMS.MC |
|---|
2022년 11월 1일부터 30일 간격으로 네 차례에 걸쳐 포트폴리오를 재조정합니다. 각 투자 변수는 두 비트를 사용하여 인코딩됩니다. 그 결과 56개의 큐비트를 풀어야 하는 문제가 발생합니다.
이러한 유형의 재무 최적화 문제에 대한 성능을 개선하기 위해 특별히 맞춤화된 표준 실제 진폭 분석을 하드웨어적으로 효율적으로 조정한 최적화 실제 진폭 분석(Optimized Real Amplitudes ansatz)을 사용합니다.
퀀텀 실행은 ibm_torino 백엔드에서 수행됩니다. 문제 구성, 방법론 및 성능 평가에 대한 자세한 설명은 공개된 원고 [1] 를 참조하세요.
요구사항
!pip install qiskit-ibm-catalog
!pip install pandas
!pip install matplotlib
!pip install yfinance설정
퀀텀 포트폴리오 옵티마이저를 사용하려면 키스킷 함수 카탈로그에서 기능을 선택하세요. 이 기능을 실행하려면 글로벌 데이터 퀀텀의 라이선스가 있는 IBM 퀀텀 프리미엄 요금제 또는 플렉스 요금제 계정이 필요합니다.
먼저, API 키 로 인증하십시오. 그런 다음, Qiskit Functions Catalog에서 원하는 함수를 불러옵니다. 여기서는 QiskitFunctionsCatalog 클래스를 사용하여 카탈로그의 함수에 quantum_portfolio_optimizer 접근하고 있습니다. 이 함수를 사용하면 미리 정의된 Quantum Portfolio Optimization 솔버를 활용할 수 있습니다.
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")1단계: 입력 포트폴리오 읽기
이 단계에서는 IBEX 35 지수에서 선택한 7개 자산에 대한 과거 데이터, 특히 2022년 11월 1일부터 2023년 4월 1일까지의 데이터를 로드합니다.
종가를 중심으로 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
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...
다음 셀을 실행하여 Yahoo Finance API에서 데이터가 올바르게 다운로드되었는지 확인하는 것을 권장합니다. 다운로드가 실패할 경우, 알림이 표시되어 데이터가 대신 대체 온라인 데이터베이스에서 검색될 것임을 알려줍니다.
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)2단계: 문제 입력값 정의하기
QUBO 문제를 정의하는 데 필요한 매개 변수는 qubo_settings 사전에서 구성할 수 있습니다. 시간 단계 수(nt), 투자 사양을 위한 비트 수(nq), 각 시간 단계의 시간 창(dt)을 정의합니다. 또한 자산당 최대 투자, 위험 회피 계수, 거래 수수료, 제한 계수를 설정합니다(문제 공식화에 대한 자세한 내용은 백서를 참조하세요). 이러한 설정을 통해 특정 투자 시나리오에 맞게 QUBO 문제를 조정할 수 있습니다.
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,
}이 optimizer_settings 사전은 반복 횟수를 나타내는 및 세대당 후보 num_generations``population_size 해의 수를 나타내는 와 같은 매개변수를 포함하여 최적화 과정을 구성합니다. 기타 설정에서는 재조합 비율, 병렬 작업 수, 배치 크기, 돌연변이 범위 등의 항목을 제어할 수 있습니다. 또한,, estimator_precision sampler_shots및 과 estimator_shots 같은 기본 설정은 최적화 과정을 위한 양자 추정기 및 샘플러 구성을 정의합니다.
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,
},
}총 회로 수는 optimizer_settings 매개 변수에 따라 다르며 (num_generations + 1) * population_size 으로 계산됩니다.
사전 ansatz_settings (dictionary)은 양자 회로 안자츠를 구성합니다. 이 ansatz 매개변수는 금융 최적화 문제를 위해 설계된 하드웨어 효율적인 접근법인 "optimized_real_amplitudes" 접근법의 사용을 지정합니다. 이 multiple_passmanager 옵션은 회로 최적화 성능과 효율성을 향상시키기 위해 여러 패스 관리자(기본 로컬 Qiskit 패스 관리자 및 Qiskit AI 기반 트랜스파일러 서비스 포함)의 사용을 가능하게 합니다. 현재 이 기능이 일시적으로 비활성화되어 있으므로 해당 옵션은 false로 설정되어 있습니다.
ansatz_settings = {
"ansatz": "optimized_real_amplitudes",
"multiple_passmanager": False,
}마지막으로 준비된 입력을 전달하여 dpo_solver.run() 함수를 실행하여 최적화를 실행합니다. 여기에는 에셋 데이터 사전(assets), QUBO 구성(qubo_settings), 최적화 파라미터(optimizer_settings), 양자 회로 안사츠 설정(ansatz_settings)이 포함됩니다. 또한 백엔드, 결과에 대한 후처리 적용 여부와 같은 실행 세부 사항을 지정합니다. 이렇게 하면 선택한 퀀텀 백엔드에서 동적 포트폴리오 최적화 프로세스가 시작됩니다.
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,
)3단계: 최적화 결과 분석
이 섹션에서는 최적화 결과에서 객관적 비용이 가장 낮은 솔루션을 추출하여 표시합니다. 최소 객관적 비용과 함께 제한 편차, 샤프 비율, 투자 수익률 등 관련 솔루션과 관련된 주요 메트릭도 제시합니다.
# 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
다음 코드는 최적화 알고리즘의 비용 분포를 무작위 표본 추출 분포와 시각화하고 비교하는 방법을 보여줍니다. 마찬가지로, 우리는 랜덤 투자로 이를 평가함으로써 QUBO 목적 함수(함수 출력에서 불러올 수 있음)의 경관을 탐색합니다. 최적화 과정이 무작위 샘플링과 비용 측면에서 어떻게 다른지 쉽게 비교할 수 있도록 두 분포를 진폭 기준으로 정규화하여 플롯합니다. 또한 DOCPlex를 사용하여 얻은 결과는 고전적 기준점 역할을 하는 점선 수직 기준선으로 포함됩니다. 우리는 동일한 문제를 전통적인 방식으로 해결하기 위해 수학적 최적화를 위한 오픈소스 IBM®Python 라이브러리인 DOCPlex의 무료 버전을 사용합니다.
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:
그래프는 퀀텀 포트폴리오 옵티마이저가 지속적으로 최적화된 투자 전략을 제시하는 방법을 보여줍니다.
참조
튜토리얼 설문조사
잠시 시간을 내어 이 튜토리얼에 대한 피드백을 제공해 주세요. 여러분의 인사이트는 콘텐츠 제공과 사용자 경험을 개선하는 데 도움이 됩니다. 설문조사 링크