Pular para o conteúdo principal

Quantum Portfolio Optimizer: Uma Função Qiskit da Global Data Quantum

Consulte a referência da API

nota

As Funções Qiskit são um recurso experimental disponível apenas para usuários dos planos IBM Quantum® Premium, Flex e On-Prem (via IBM Quantum Platform API). Elas estão em status de pré-lançamento e sujeitas a alterações.

Visão geral

O Quantum Portfolio Optimizer é uma Função Qiskit que aborda o problema de otimização dinâmica de portfólio, um problema padrão em finanças que visa rebalancear investimentos periódicos em um conjunto de ativos, maximizando retornos e minimizando riscos. Ao empregar técnicas de otimização quântica de ponta, esta função simplifica o processo para que usuários, sem expertise em computação quântica, possam se beneficiar de suas vantagens na busca de trajetórias de investimento ideais. Ideal para gestores de portfólio, pesquisadores em finanças quantitativas e investidores individuais, esta ferramenta permite o back-testing de estratégias de negociação em otimização de portfólio.

Descrição da função

A função Quantum Portfolio Optimizer usa o algoritmo Variational Quantum Eigensolver (VQE) para resolver um problema de Otimização Binária Irrestrita Quadrática (QUBO), abordando problemas de otimização dinâmica de portfólio. Os usuários precisam apenas fornecer os dados de preço dos ativos e definir a restrição de investimento; em seguida, a função executa o processo de otimização quântica que retorna um conjunto de trajetórias de investimento otimizadas.

O processo consiste em quatro etapas principais. Primeiro, os dados de entrada são mapeados para um problema compatível com quantum, construindo o QUBO do problema de otimização dinâmica de portfólio e transformando-o em um operador quântico (Hamiltoniano de Ising). Em seguida, o problema de entrada e o algoritmo VQE são adaptados para execução no hardware quântico. O algoritmo VQE é então executado no hardware quântico e, por fim, os resultados são pós-processados para fornecer as trajetórias de investimento ideais. O sistema também inclui um pós-processamento ciente de ruído (baseado em SQD) para maximizar a qualidade da saída.

Esta Função Qiskit é baseada no artigo publicado pela Global Data Quantum. Visualização do fluxo de trabalho da função

Como começar

Autentique-se usando sua chave de API e selecione a Função Qiskit conforme descrito a seguir. (Este trecho assume que você já salvou sua conta no seu ambiente local.)

# 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")

Exemplo: Otimização dinâmica de portfólio com sete ativos

Este exemplo demonstra como executar a função de otimização dinâmica de portfólio (DPO) e ajustar suas configurações para desempenho ideal. Ele inclui etapas detalhadas para ajustar os parâmetros e alcançar os resultados desejados.

Este caso envolve sete ativos, quatro passos de tempo e quatro qubits de resolução, resultando em um requisito total de 112 qubits.

1. Leia os ativos incluídos no portfólio.

Se todos os ativos do portfólio estiverem armazenados em uma pasta em um caminho específico, você pode carregá-los em um pandas.DataFrame e convertê-los para um objeto no formato dict usando a seguinte função.

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()

Para este exemplo, usamos os ativos 8801.T, CLF, GBPJPY, ITX.MC, META, TMBMKDE-10Y e XS2239553048. A figura a seguir ilustra os dados usados neste exemplo, mostrando a evolução diária do preço de fechamento dos ativos de 1º de janeiro a 1º de setembro de 2023.

Neste exemplo, para garantir uniformidade entre as datas, preenchemos os dias sem negociação com o preço de fechamento da data disponível anterior. Aplicamos esta etapa porque os ativos selecionados provêm de diferentes mercados com dias de negociação variados, tornando essencial padronizar o conjunto de dados para consistência. Visualização dos dados históricos dos ativos

2. Defina o problema.

Defina as especificações do problema configurando os parâmetros no dicionário 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. Defina as configurações do otimizador e do ansatz (Opcional)

Opcionalmente, defina requisitos específicos para o processo de otimização, incluindo a seleção do otimizador e seus parâmetros, bem como a especificação da primitiva e suas configurações.

Para o Ansatz Tailored, o tamanho de população escolhido foi baseado em experimentos anteriores que mostraram que este valor produz uma otimização estável e eficiente.

No caso do Ansatz Real Amplitudes, você pode seguir uma relação linear entre o population_size e o número de qubits no Circuit. Como regra geral aproximada, é recomendado usar um population_size mínimo de ~ 0.8 * n_qubits para o ansatz real_amplitudes.

Espera-se que o Optimized Real Amplitudes tenha um desempenho de otimização melhor do que o ansatz Real Amplitudes. No entanto, o número de variáveis a otimizar neste ansatz aumenta muito mais rapidamente do que no caso do Real Amplitudes (veja o artigo). Portanto, para problemas grandes, o Optimized Real Amplitudes requer mais execuções de Circuit. O Optimized Real Amplitudes provavelmente será útil para problemas que requerem até 100 qubits, mas é recomendado ter cuidado ao definir os parâmetros population_size. Como exemplo desse aumento de escala em population_size, a tabela anterior mostra que para um problema de 84 qubits, o Optimized Real Amplitudes requer population_size de 120, enquanto para um problema de 56 qubits, um population_size de 40 é suficiente.

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

Também é possível escolher um ansatz específico. O exemplo a seguir usa o ansatz 'Tailored'.

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

4. Execute o 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. Recupere os resultados

A função retorna um dicionário com as trajetórias de investimento ordenadas do menor para o maior de acordo com o valor de sua função objetivo (veja a seção Saída da referência da API). Este conjunto de resultados permite a identificação da trajetória com o menor custo e suas correspondentes avaliações de investimento. Além disso, fornece a análise de diferentes trajetórias, facilitando a seleção das que melhor se alinham com necessidades ou objetivos específicos. Essa flexibilidade garante que as escolhas possam ser adaptadas para atender a uma variedade de preferências ou cenários. Comece apresentando a estratégia resultante que alcançou o menor custo objetivo encontrado durante o 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}}

Posteriormente, usando os metadados, você pode acessar os resultados de todas as estratégias amostradas. Você pode, assim, analisar mais a fundo as trajetórias alternativas retornadas pelo otimizador. Para isso, leia o dicionário armazenado em dpo_result['metadata']['all_samples_metrics'], que contém não apenas informações adicionais sobre a estratégia ideal, mas também detalhes das outras estratégias candidatas avaliadas durante a otimização.

O exemplo a seguir mostra como ler essas informações usando pandas para extrair métricas-chave associadas à estratégia ideal. Estas incluem Desvio de Restrição, Índice de Sharpe e o retorno de investimento correspondente.

# 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. Análise de desempenho

Por fim, analise o desempenho da sua aplicação de otimização. Especificamente, compare seus resultados, obtidos no exemplo anterior, com uma linha de base aleatória para avaliar a eficácia da nossa abordagem. Se o algoritmo quântico produzir demonstravelmente e consistentemente resultados com valores de custo mais baixos, isso indica um processo de otimização eficaz.

A figura apresenta as distribuições de probabilidade dos custos objetivo. Para gerar essas distribuições, pegue a lista de custos objetivo do resultado da função e conte as ocorrências de cada valor de custo (valores arredondados para a segunda casa decimal). Em seguida, atualize a coluna de contagem adequadamente, juntando as contagens de valores arredondados idênticos. Observe que, para melhor comparação visual, as contagens de ocorrências foram normalizadas para que cada distribuição seja exibida entre 0 e 1. Visualização da solução da otimização Como mostrado na figura (linha sólida azul), a distribuição de custos para a nossa abordagem com o Variational Quantum Eigensolver (pós-processado com SQD) está concentrada de forma acentuada em valores de custo objetivo mais baixos, indicando bom desempenho de otimização. Em contraste, a linha de base com ruído exibe uma distribuição mais ampla, centrada em torno de valores de custo mais altos. A linha vertical tracejada cinza representa o valor médio da distribuição aleatória, destacando ainda mais a consistência da função em retornar estratégias de investimento otimizadas. Para uma comparação adicional, a linha tracejada preta na figura corresponde à solução obtida com o otimizador Gurobi (versão gratuita). Todos esses resultados são explorados mais detalhadamente nos benchmarks abaixo para o exemplo de "Ativos Mistos" avaliado com o ansatz "Tailored".

Benchmarks

Esta função foi testada em diferentes configurações de qubits de resolução, circuits ansatz e agrupamentos de ativos de vários setores: uma mistura de ativos diferentes (Conjunto 1), derivativos de petróleo (Conjunto 2) e IBEX35 (Conjunto 3). Veja mais detalhes na tabela a seguir.

ConjuntoDataAtivos
Conjunto 101/01/20238801.T, CL=F, GBPJPY=X, ITX.MC, META, TMBMKDE-10Y, XS2239553048
Conjunto 201/06/2023CL=F, BZ=F, HO=F, NG=F, XOM, RB=F, 2222.SR
Conjunto 301/11/2022ACS.MC, ITX.MC, FER.MC, ELE.MC, SCYR.MC, AENA.MC, AMS.MC

Duas métricas-chave foram usadas para avaliar a qualidade da solução.

  1. O custo objetivo, que mede a eficiência da otimização comparando o valor da função de custo de cada experimento com os resultados do Gurobi (versão gratuita).
  2. O índice de Sharpe, que captura o retorno ajustado ao risco de cada portfólio, oferecendo perspectiva sobre o desempenho financeiro das soluções.

Juntas, essas métricas estabelecem benchmarks dos aspectos computacionais e financeiros dos portfólios gerados pelo quantum.

ExemploQubitsAnsatzProfundidadeUso de Runtime (s)Uso total (s)Custo objetivoSharpeCusto objetivo GurobiSharpe Gurobi
Ativos Mistos (Conjunto 1, 4 passos de tempo, 4-bit)112Tailored831273513095-3.7824.82-4.2524.71
Ativos Mistos (Conjunto 1, 4 passos de tempo, 4 passos de tempo, 4-bit)112Real Amplitudes3591173911903-3.3923.64-4.2524.71
Derivativos de Petróleo (Conjunto 2, 4 passos de tempo, 3-bit)84Optimized Real Amplitudes7861806350-3.7319.13-4.1921.71
IBEX35 (Conjunto 3, 4 passos de tempo, 2-bit)56Optimized Real Amplitudes9633143523-3.6714.48-4.1116.44

Os resultados mostram que o otimizador quântico, com ansatzes específicos do problema, identifica efetivamente estratégias de investimento eficientes em vários tipos de portfólio. Abaixo detalhamos tanto o tamanho da população quanto o número de gerações especificados no dicionário optimizer_options. Todos os outros parâmetros foram definidos com seus valores padrão.

Exemplopopulation_sizenum_generations
Portfólio de Ativos Mistos9020
Portfólio de Ativos Mistos9220
Portfólio de Derivativos de Petróleo12020
Portfólio IBEX354020

O número de gerações foi definido como 20, pois esse valor foi considerado suficiente para atingir a convergência. Além disso, os valores padrão para os parâmetros internos do otimizador foram mantidos inalterados, pois consistentemente forneceram bom desempenho e são geralmente recomendados pela literatura e pelas diretrizes de implementação.

Obter suporte

Se você precisar de ajuda, pode enviar um e-mail para qpo.support@globaldataquantum.com. Na sua mensagem, forneça o ID do job da função.

Próximos passos

Recomendações