Quantum Portfolio Optimizer: Una Qiskit Function di Global Data Quantum
Consulta il riferimento API
Le Qiskit Functions sono una funzionalità sperimentale disponibile solo per gli utenti dei piani IBM Quantum® Premium, Flex e On-Prem (tramite IBM Quantum Platform API). Sono in stato di anteprima e soggette a modifiche.
Panoramica​
Il Quantum Portfolio Optimizer è una Qiskit Function che affronta il problema dell'ottimizzazione dinamica del portafoglio, un problema standard in finanza che mira a ribilanciare periodicamente gli investimenti su un insieme di asset, massimizzando i rendimenti e minimizzando i rischi. Grazie all'impiego di tecniche di ottimizzazione quantistica all'avanguardia, questa funzione semplifica il processo in modo che gli utenti, anche senza competenze nel calcolo quantistico, possano beneficiare dei suoi vantaggi nella ricerca di traiettorie di investimento ottimali. Ideale per gestori di portafoglio, ricercatori in finanza quantitativa e investitori individuali, questo strumento consente il back-testing di strategie di trading nell'ottimizzazione del portafoglio.
Descrizione della funzione​
La funzione Quantum Portfolio Optimizer utilizza l'algoritmo Variational Quantum Eigensolver (VQE) per risolvere un problema di ottimizzazione binaria quadratica non vincolata (QUBO), affrontando i problemi di ottimizzazione dinamica del portafoglio. Gli utenti devono semplicemente fornire i dati sui prezzi degli asset e definire il vincolo di investimento; la funzione esegue quindi il processo di ottimizzazione quantistica che restituisce un insieme di traiettorie di investimento ottimizzate.
Il processo si articola in quattro fasi principali. Prima di tutto, i dati di input vengono mappati su un problema compatibile con il calcolo quantistico, costruendo il QUBO del problema di ottimizzazione dinamica del portafoglio e trasformandolo in un operatore quantistico (Hamiltoniano di Ising). Successivamente, il problema di input e l'algoritmo VQE vengono adattati per essere eseguiti sull'hardware quantistico. L'algoritmo VQE viene poi eseguito sull'hardware quantistico e, infine, i risultati vengono post-elaborati per fornire le traiettorie di investimento ottimali. Il sistema include anche un post-processing noise-aware (basato su SQD) per massimizzare la qualità dell'output.
Questa Qiskit Function è basata sul manoscritto pubblicato da Global Data Quantum.
Per iniziare
Autenticati usando la tua chiave API e seleziona la Qiskit Function come segue. (Questo snippet presuppone che tu abbia già salvato il tuo account nell'ambiente locale.)
# Added by doQumentation — required packages for this notebook
!pip install -q pandas qiskit-ibm-catalog
from qiskit_ibm_catalog import QiskitFunctionsCatalog
catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")
# Access function
dpo_solver = catalog.load("global-data-quantum/quantum-portfolio-optimizer")
Esempio: ottimizzazione dinamica del portafoglio con sette asset​
Questo esempio mostra come eseguire la funzione di ottimizzazione dinamica del portafoglio (DPO) e regolare le sue impostazioni per ottenere le prestazioni ottimali. Include passaggi dettagliati per la messa a punto dei parametri al fine di ottenere i risultati desiderati.
Questo caso prevede sette asset, quattro step temporali e quattro qubit di risoluzione, per un requisito totale di 112 qubit.
1. Leggi gli asset inclusi nel portafoglio​
Se tutti gli asset del portafoglio sono memorizzati in una cartella a un percorso specifico, puoi caricarli in un pandas.DataFrame e convertirlo in un oggetto in formato dict usando la seguente funzione.
import os
import glob
import pandas as pd
def read_and_join_csv(file_pattern):
"""
Reads multiple CSV files matching the file pattern and combines them into a single DataFrame.
Parameters:
file_pattern (str): The pattern to match CSV files.
Returns:
pd.DataFrame: Combined DataFrame with data from all CSV files.
"""
# Find all files matching the pattern
csv_files = glob.glob(file_pattern)
# Get the base file names without the .csv extension
file_names = [os.path.basename(f).replace(".csv", "") for f in csv_files]
# Read each CSV file into a DataFrame and set the first column as the index
df_list = [pd.read_csv(f).set_index("Unnamed: 0") for f in csv_files]
# Rename columns in each DataFrame to the base file names
for df, name in zip(df_list, file_names):
df.columns = [name]
# Combine all DataFrames into one by merging them side by side
combined_df = pd.concat(df_list, axis=1)
return combined_df
file_pattern = "route/to/folder/with/assets/data/*.csv"
assets = read_and_join_csv(file_pattern).to_dict()
Per questo esempio, abbiamo utilizzato gli asset 8801.T, CLF, GBPJPY, ITX.MC, META, TMBMKDE-10Y e XS2239553048. La figura seguente illustra i dati usati in questo esempio, mostrando l'evoluzione giornaliera del prezzo di chiusura degli asset dal 1° gennaio al 1° settembre 2023.
In questo esempio, per garantire l'uniformità tra le date, abbiamo compilato i giorni non di negoziazione con il prezzo di chiusura della data precedente disponibile. Applichiamo questo passaggio perché gli asset selezionati provengono da mercati diversi con giorni di negoziazione variabili, rendendo essenziale standardizzare il dataset per coerenza.
2. Definisci il problema​
Definisci le specifiche del problema configurando i parametri nel dizionario qubo_settings.
qubo_settings = {
"nt": 4,
"nq": 4,
"dt": 30,
"max_investment": 25,
"risk_aversion": 1000.0,
"transaction_fee": 0.01,
"restriction_coeff": 1.0,
}
3. Definisci le impostazioni dell'ottimizzatore e dell'ansatz (Facoltativo)​
Facoltativamente, definisci i requisiti specifici per il processo di ottimizzazione, inclusa la selezione dell'ottimizzatore e dei suoi parametri, nonché la specifica della primitiva e delle sue configurazioni.
Per il Tailored Ansatz, la dimensione della popolazione scelta si basa su esperimenti precedenti che hanno dimostrato che questo valore produce un'ottimizzazione stabile ed efficiente.
Nel caso del Real Amplitudes Ansatz, puoi seguire una relazione lineare tra population_size e il numero di qubit nel circuito. Come regola empirica approssimativa, si consiglia di usare un population_size minimo ~ 0.8 * n_qubits per l'ansatz real_amplitudes.
Si prevede che l'Optimized Real Amplitudes abbia prestazioni di ottimizzazione migliori rispetto all'ansatz Real Amplitudes. Tuttavia, il numero di variabili da ottimizzare in questo ansatz cresce molto più rapidamente rispetto al caso Real Amplitudes (vedi il manoscritto). Pertanto, per problemi di grandi dimensioni, l'Optimized Real Amplitudes richiede più esecuzioni di circuito. L'Optimized Real Amplitudes è probabilmente utile per problemi fino a 100 qubit, ma si consiglia di essere prudenti nell'impostare i parametri population_size. Come esempio di questo aumento di scala in population_size, la tabella precedente mostra che per un problema a 84 qubit, l'Optimize Real Amplitudes richiede 120 di population_size, mentre per un problema a 56 qubit è sufficiente un population_size di 40.
optimizer_settings = {
"de_optimizer_settings": {
"num_generations": 20,
"population_size": 90,
"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,
},
}
È anche possibile scegliere un ansatz specifico. Il seguente usa l'ansatz 'Tailored'.
ansatz_settings = {
"ansatz": "tailored",
"multiple_passmanager": False,
}
4. Esegui il problema​
dpo_job = dpo_solver.run(
assets=assets,
qubo_settings=qubo_settings,
optimizer_settings=optimizer_settings,
ansatz_settings=ansatz_settings,
backend_name="<backend name>",
previous_session_id=[],
apply_postprocess=True,
)
5. Recupera i risultati​
La funzione restituisce un dizionario con le traiettorie di investimento ordinate dalla più bassa alla più alta in base al valore della funzione obiettivo (vedi la sezione Output del riferimento API). Questo insieme di risultati consente di identificare la traiettoria con il costo più basso e le relative valutazioni di investimento. Inoltre, permette l'analisi di diverse traiettorie, facilitando la selezione di quelle più adatte alle esigenze o agli obiettivi specifici. Questa flessibilità garantisce che le scelte possano essere personalizzate per adattarsi a una varietà di preferenze o scenari. Inizia presentando la strategia risultante che ha raggiunto il costo obiettivo più basso trovato durante il processo.
# Get the results of the job
dpo_result = dpo_job.result()
# Show the solution strategy
dpo_result["result"]
{'time_step_0': {'8801.T': 0.11764705882352941,
'ITX.MC': 0.20588235294117646,
'META': 0.38235294117647056,
'GBPJPY=X': 0.058823529411764705,
'TMBMKDE-10Y': 0.0,
'CLF': 0.058823529411764705,
'XS2239553048': 0.17647058823529413},
'time_step_1': {'8801.T': 0.11428571428571428,
'ITX.MC': 0.14285714285714285,
'META': 0.2,
'GBPJPY=X': 0.02857142857142857,
'TMBMKDE-10Y': 0.42857142857142855,
'CLF': 0.0,
'XS2239553048': 0.08571428571428572},
'time_step_2': {'8801.T': 0.0,
'ITX.MC': 0.09375,
'META': 0.3125,
'GBPJPY=X': 0.34375,
'TMBMKDE-10Y': 0.0,
'CLF': 0.0,
'XS2239553048': 0.25},
'time_step_3': {'8801.T': 0.3939393939393939,
'ITX.MC': 0.09090909090909091,
'META': 0.12121212121212122,
'GBPJPY=X': 0.18181818181818182,
'TMBMKDE-10Y': 0.0,
'CLF': 0.0,
'XS2239553048': 0.21212121212121213}}
Successivamente, usando i metadati, puoi accedere ai risultati di tutte le strategie campionate. Puoi quindi analizzare ulteriormente le traiettorie alternative restituite dall'ottimizzatore. Per farlo, leggi il dizionario memorizzato in dpo_result['metadata']['all_samples_metrics'], che contiene non solo informazioni aggiuntive sulla strategia ottimale, ma anche i dettagli delle altre strategie candidate valutate durante l'ottimizzazione.
Il seguente esempio mostra come leggere queste informazioni usando pandas per estrarre le metriche chiave associate alla strategia ottimale. Queste includono la Deviazione dal Vincolo, il Rapporto di Sharpe e il rendimento di investimento corrispondente.
# Convert metadata to a DataFrame
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']}")
Minimum Objective Cost Found: -3.78
Best Solution:
- Restriction Deviation: 40.0
- Sharpe Ratio: 24.82
- Return: 0.46
6. Analisi delle prestazioni​
Infine, analizza le prestazioni della tua applicazione di ottimizzazione. In particolare, confronta i tuoi risultati, ottenuti nell'esempio precedente, con una baseline casuale per valutare l'efficacia del nostro approccio. Se l'algoritmo quantistico produce in modo dimostrabile e coerente risultati con valori di costo inferiori, ciò indica un processo di ottimizzazione efficace.
La figura presenta le distribuzioni di probabilità dei costi obiettivo. Per generare queste distribuzioni, prendi la lista dei costi obiettivo dal risultato della funzione e conta le occorrenze di ogni valore di costo (valori arrotondati alla seconda cifra decimale). Quindi, aggiorna la colonna dei conteggi di conseguenza unendo i conteggi dei valori arrotondati identici. Nota che, per una migliore comparazione visiva, i conteggi delle occorrenze sono stati normalizzati in modo che ogni distribuzione sia visualizzata tra 0 e 1.
Come mostrato nella figura (linea continua blu), la distribuzione dei costi per il nostro approccio Variational Quantum Eigensolver (post-elaborato con SQD) è concentrata nettamente su valori di costo obiettivo più bassi, indicando buone prestazioni di ottimizzazione. Al contrario, la baseline rumorosa mostra una distribuzione più ampia, centrata intorno a valori di costo più elevati. La linea verticale tratteggiata grigia rappresenta il valore medio della distribuzione casuale, evidenziando ulteriormente la coerenza della funzione nel restituire strategie di investimento ottimizzate. Come confronto aggiuntivo, la linea tratteggiata nera nella figura corrisponde alla soluzione ottenuta con l'ottimizzatore Gurobi (versione gratuita). Tutti questi risultati vengono ulteriormente esplorati nei benchmark seguenti per l'esempio "Mixed Assets" valutato con l'ansatz "Tailored".
Benchmark
Questa funzione è stata testata in diverse configurazioni di qubit di risoluzione, circuito ansatz e raggruppamenti di asset di vari settori: una combinazione di asset diversi (Set 1), derivati del petrolio (Set 2) e IBEX35 (Set 3). Vedi ulteriori dettagli nella tabella seguente.
| Set | Data | Asset |
|---|---|---|
| Set 1 | 01/01/2023 | 8801.T, CL=F, GBPJPY=X, ITX.MC, META, TMBMKDE-10Y, XS2239553048 |
| Set 2 | 01/06/2023 | CL=F, BZ=F, HO=F, NG=F, XOM, RB=F, 2222.SR |
| Set 3 | 01/11/2022 | ACS.MC, ITX.MC, FER.MC, ELE.MC, SCYR.MC, AENA.MC, AMS.MC |
Sono state utilizzate due metriche chiave per valutare la qualità della soluzione.
- Il costo obiettivo, che misura l'efficienza dell'ottimizzazione confrontando il valore della funzione di costo di ogni esperimento con i risultati di Gurobi (versione gratuita).
- L'indice di Sharpe, che cattura il rendimento corretto per il rischio di ogni portafoglio, offrendo una visione delle prestazioni finanziarie delle soluzioni.
Insieme, queste metriche confrontano sia gli aspetti computazionali che quelli finanziari dei portafogli generati con il metodo quantistico.
| Esempio | qubit | Ansatz | Depth | Utilizzo Runtime (s) | Utilizzo totale (s) | Costo obiettivo | Sharpe | Costo obiettivo Gurobi | Sharpe Gurobi |
|---|---|---|---|---|---|---|---|---|---|
| Mixed Assets (Set 1, 4 step temporali, 4-bit) | 112 | Tailored | 83 | 12735 | 13095 | -3.78 | 24.82 | -4.25 | 24.71 |
| Mixed Assets (Set 1, 4 step temporali, 4 step temporali, 4-bit) | 112 | Real Amplitudes | 359 | 11739 | 11903 | -3.39 | 23.64 | -4.25 | 24.71 |
| Oil Derivatives (Set 2, 4 step temporali, 3-bit) | 84 | Optimized Real Amplitudes | 78 | 6180 | 6350 | -3.73 | 19.13 | -4.19 | 21.71 |
| IBEX35 (Set 3, 4 step temporali, 2-bit) | 56 | Optimized Real Amplitudes | 96 | 3314 | 3523 | -3.67 | 14.48 | -4.11 | 16.44 |
I risultati mostrano che l'ottimizzatore quantistico, con ansatz specifici per il problema, identifica efficacemente strategie di investimento efficienti in diversi tipi di portafoglio.
Di seguito sono riportati i dettagli relativi alla dimensione della popolazione e al numero di generazioni specificati nel dizionario optimizer_options. Tutti gli altri parametri sono stati impostati ai valori predefiniti.
| Esempio | population_size | num_generations |
|---|---|---|
| Mixed Assets Portfolio | 90 | 20 |
| Mixed Assets Portfolio | 92 | 20 |
| Oil Derivatives Portfolio | 120 | 20 |
| IBEX35 Portfolio | 40 | 20 |
Il numero di generazioni è stato impostato a 20, poiché questo valore è risultato sufficiente per raggiungere la convergenza. Inoltre, i valori predefiniti per i parametri interni dell'ottimizzatore sono stati lasciati invariati, poiché forniscono costantemente buone prestazioni e sono generalmente raccomandati dalla letteratura e dalle linee guida di implementazione.
Supporto
Se hai bisogno di aiuto, puoi inviare un'e-mail a qpo.support@globaldataquantum.com. Nel messaggio, fornisci l'ID del job della funzione.
Passi successivi​
- Leggi il documento di ricerca associato.
-
- Visita il riferimento API per questa Qiskit Function.
- Richiedi l'accesso alla funzione compilando questo modulo.
- Prova il tutorial Dynamic Portfolio Optimization.