{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "d31e7ada-7707-4f25-9058-c895d1b205fc",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Otimização dinâmica de carteiras com o Portfolio Optimizer da Global Data Quantum\"\n",
        "description: \"Resolva um problema de otimização dinâmica em escala utilitária usando o Otimizador de Portfólio Quântico da Global Data Quantum.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore fontsize rebalance patheffects labelsize */}\n",
        "\n",
        "<span id=\"dynamic-portfolio-optimization-with-global-data-quantums-portfolio-optimizer\" />\n",
        "\n",
        "# Otimização dinâmica de carteiras com o Portfolio Optimizer da Global Data Quantum\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "36a8cc7a-179a-45f7-b28e-a2b4bbeaabd3",
      "metadata": {},
      "source": [
        "<Admonition type=\"note\" title=\"Nota\">\n",
        "  As funções do Qiskit são um recurso experimental disponível apenas para usuários dos planos IBM Quantum® Premium Plan, Flex Plan e On-Prem (via IBM Quantum Platform API). Eles estão no status de versão prévia e estão sujeitos a alterações.\n",
        "</Admonition>\n",
        "\n",
        "*Estimativa de uso: Aproximadamente 55 minutos em um processador Heron r2. (OBSERVAÇÃO: essa é apenas uma estimativa. O tempo de execução real pode variar)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "16f9751a-3f70-4c1c-a384-78f402567279",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Segundo plano\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "80d86936-8073-415e-a024-2adbd0efef2a",
      "metadata": {},
      "source": [
        "O problema de otimização dinâmica de portfólio tem como objetivo encontrar a estratégia de investimento ideal em vários períodos de tempo para maximizar o retorno esperado do portfólio e minimizar os riscos, geralmente sob certas restrições, como orçamento, custos de transação ou aversão ao risco. Diferentemente da otimização de portfólio padrão, que considera um único momento para reequilibrar o portfólio, a versão dinâmica leva em conta a natureza evolutiva dos ativos e adapta os investimentos com base nas mudanças no desempenho dos ativos ao longo do tempo.\n",
        "\n",
        "Este tutorial demonstra como executar a otimização dinâmica de portfólio usando a função Qiskit do Quantum Portfolio Optimizer. Especificamente, ilustramos como usar essa função de aplicativo para resolver um problema de alocação de investimentos em várias etapas de tempo.\n",
        "\n",
        "A abordagem envolve a formulação da otimização do portfólio como um problema de Otimização Binária Quadrática Sem Restrições (QUBO) com múltiplos objetivos. Especificamente, formulamos a função QUBO $O$ para otimizar simultaneamente quatro objetivos diferentes:\n",
        "\n",
        "* Maximizar a função de retorno $F$\n",
        "* Minimizar o risco do investimento $R$\n",
        "* Minimizar os custos de transação $C$\n",
        "* Cumprir as restrições de investimento, formuladas em um termo adicional para minimizar $P$.\n",
        "\n",
        "Resumindo, para atingir esses objetivos, formulamos a função QUBO como $O = -F + \\frac{\\gamma}{2} R + C + \\rho P,$ em que $\\gamma$ é o coeficiente de aversão ao risco e $\\rho$ é o coeficiente de reforço de restrições (multiplicador de Lagrange). A formulação explícita pode ser encontrada na Eq. (15) de nosso manuscrito [\\[1\\]](#references).\n",
        "\n",
        "Resolvemos usando um método híbrido quântico-clássico baseado no Variational Quantum Eigensolver (VQE). Nessa configuração, o circuito quântico estima a função de custo, enquanto a otimização clássica é realizada usando o algoritmo de evolução diferencial, permitindo a navegação eficiente do cenário de soluções. O número de qubits necessários depende de três fatores principais: o número de ativos `na`, o número de períodos de tempo `nt` e a resolução de bits usada para representar o investimento `nq`. Especificamente, o número mínimo de qubits em nosso problema é `na*nt*nq`.\n",
        "\n",
        "Neste tutorial, vamos nos concentrar na otimização de um portfólio regional com base no índice espanhol IBEX 35. Especificamente, usamos um portfólio de sete ativos, conforme indicado na tabela abaixo:\n",
        "\n",
        "| **Portfólio IBEX 35** | ACS.MC | ITX.MC | FER.MC | ELE.MC | SCYR.MC | AENA.MC | AMS.MC |\n",
        "| --------------------- | ------ | ------ | ------ | ------ | ------- | ------- | ------ |\n",
        "\n",
        "Rebalanceamos nosso portfólio em quatro etapas, cada uma separada por um intervalo de 30 dias a partir de 1º de novembro de 2022. Cada variável de investimento é codificada usando dois bits. Isso resulta em um problema que requer 56 qubits para ser resolvido.\n",
        "\n",
        "Usamos o ansatz Optimized Real Amplitudes, uma adaptação personalizada e eficiente em termos de hardware do ansatz Real Amplitudes padrão, especificamente adaptado para melhorar o desempenho desse tipo de problema de otimização financeira.\n",
        "\n",
        "A execução quântica é realizada no backend `ibm_torino` . Para obter uma explicação detalhada da formulação do problema, da metodologia e da avaliação de desempenho, consulte o manuscrito publicado [\\[1\\]](#references).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3716659d-a44f-448b-a131-e0044b05d2ed",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisitos\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "49cddc2b-6759-4d18-aa1c-c38c344139d7",
      "metadata": {},
      "outputs": [],
      "source": [
        "!pip install qiskit-ibm-catalog\n",
        "!pip install pandas\n",
        "!pip install matplotlib\n",
        "!pip install yfinance"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ab6114f0-fad3-4f44-809d-3ff966f2b2ea",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Instalação\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "689bf5f3-8332-4a7c-b62e-047566c1971f",
      "metadata": {},
      "source": [
        "Para usar o Quantum Portfolio Optimizer, selecione a função por meio do Catálogo de Funções do Qiskit. Você precisa de uma conta IBM Quantum Premium Plan ou Flex Plan com uma licença da Global Data Quantum para executar essa função.\n",
        "\n",
        "Primeiro, faça a autenticação com sua [chave de API](). Em seguida, carregue a função desejada do `Qiskit Functions Catalog`. Aqui, você está acessando a `quantum_portfolio_optimizer` função do catálogo usando a `QiskitFunctionsCatalog` classe. Esta função permite-nos utilizar o solucionador Quantum Portfolio Optimization predefinido.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "301a8a04-3106-478b-816e-5d5f0a3c0737",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit_ibm_catalog import QiskitFunctionsCatalog\n",
        "\n",
        "catalog = QiskitFunctionsCatalog(\n",
        "    channel=\"ibm_quantum_platform\",\n",
        "    instance=\"INSTANCE_CRN\",\n",
        "    # For `token`, use the 44-character API_KEY you created\n",
        "    # and saved from the IBM Quantum Platform Home dashboard\n",
        "    token=\"YOUR_API_KEY\",\n",
        ")\n",
        "\n",
        "# Access function\n",
        "dpo_solver = catalog.load(\"global-data-quantum/quantum-portfolio-optimizer\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0856b7ca-8cf5-457d-ac08-df83a02852af",
      "metadata": {},
      "source": [
        "<span id=\"step-1-read-the-input-portfolio\" />\n",
        "\n",
        "## Passo 1: Leia a carteira de entrada\n",
        "\n",
        "Nessa etapa, carregamos os dados históricos dos sete ativos selecionados do índice IBEX 35, especificamente de **1º de novembro de 2022** a **1º de abril de 2023**.\n",
        "\n",
        "Obtemos os dados usando a API do Yahoo Finance, com foco nos preços de fechamento. Os dados são então processados para garantir que todos os ativos tenham o mesmo número de dias com dados. Todos os dados ausentes (dias não comerciais) são tratados adequadamente, garantindo que todos os ativos estejam alinhados nas mesmas datas.\n",
        "\n",
        "Os dados são estruturados em um site DataFrame com formatação consistente em todos os ativos.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "57ba1c16-4670-4557-9a16-2034be98f471",
      "metadata": {},
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "[*********************100%***********************]  1 of 1 completed"
          ]
        },
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "\n",
            "[*********************100%***********************]  1 of 1 completed\n",
            "[*********************100%***********************]  1 of 1 completed\n",
            "[*********************100%***********************]  1 of 1 completed\n",
            "[*********************100%***********************]  1 of 1 completed\n",
            "[*********************100%***********************]  1 of 1 completed\n",
            "[*********************100%***********************]  1 of 1 completed"
          ]
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Downloading data for ACS.MC...\n",
            "Downloading data for ITX.MC...\n",
            "Downloading data for FER.MC...\n",
            "Downloading data for ELE.MC...\n",
            "Downloading data for SCYR.MC...\n",
            "Downloading data for AENA.MC...\n",
            "Downloading data for AMS.MC...\n"
          ]
        },
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "\n"
          ]
        }
      ],
      "source": [
        "import yfinance as yf\n",
        "import pandas as pd\n",
        "\n",
        "# List of IBEX 35 symbols\n",
        "symbols = [\n",
        "    \"ACS.MC\",\n",
        "    \"ITX.MC\",\n",
        "    \"FER.MC\",\n",
        "    \"ELE.MC\",\n",
        "    \"SCYR.MC\",\n",
        "    \"AENA.MC\",\n",
        "    \"AMS.MC\",\n",
        "]\n",
        "\n",
        "start_date = \"2022-11-01\"\n",
        "end_date = \"2023-4-01\"\n",
        "\n",
        "series_list = []\n",
        "symbol_names = [symbol.replace(\".\", \"_\") for symbol in symbols]\n",
        "\n",
        "# Create a full date index including weekends\n",
        "full_index = pd.date_range(start=start_date, end=end_date, freq=\"D\")\n",
        "\n",
        "for symbol, name in zip(symbols, symbol_names):\n",
        "    print(f\"Downloading data for {symbol}...\")\n",
        "    data = yf.download(symbol, start=start_date, end=end_date)[\"Close\"]\n",
        "    data.name = name\n",
        "\n",
        "    # Reindex to include weekends\n",
        "    data = data.reindex(full_index)\n",
        "\n",
        "    # Fill missing values (for example, weekends or holidays)\n",
        "    # by forward/backward fill\n",
        "    data.ffill(inplace=True)\n",
        "    data.bfill(inplace=True)\n",
        "\n",
        "    series_list.append(data)\n",
        "\n",
        "# Combine all series into a single DataFrame\n",
        "assets = pd.concat(series_list, axis=1)\n",
        "\n",
        "# Convert index to string for consistency\n",
        "assets.index = assets.index.astype(str)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "574e52d6-c87e-4eb4-a2b6-eee8954231aa",
      "metadata": {},
      "source": [
        "<Admonition type=\"caution\" title=\"Aviso\">\n",
        "  Recomendamos executar a seguinte célula para verificar se os dados foram baixados corretamente da API do Yahoo Finance. Se o download falhar, uma notificação indicará que os dados serão recuperados de um banco de dados online alternativo.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "be78a997-678f-4bd0-a0c5-1a04c8a94afb",
      "metadata": {},
      "outputs": [],
      "source": [
        "if (\n",
        "    assets.isna().values.any()\n",
        "):  # If has nan values, load the data from the fallback online database\n",
        "    print(\n",
        "        \"WARNING: Asset data format is invalid or incomplete. \"\n",
        "        \"Loading data from the remote backup database.\"\n",
        "    )\n",
        "\n",
        "    url = \"https://raw.githubusercontent.com/Global-Data-Quantum/PortfolioData/1f3ebb95fdef245014a04d4273f688a2951e0061/data/qpo_tutorial_ibex.csv\"\n",
        "    assets = pd.read_csv(url, index_col=0)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "fbd9f5aa-dc2d-48bb-805f-ae0d55aedc54",
      "metadata": {},
      "source": [
        "<span id=\"step-2-define-the-problem-inputs\" />\n",
        "\n",
        "## Etapa 2: Defina os dados do problema\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8f066377-62fb-46fd-ae1a-1058d99c5f7f",
      "metadata": {},
      "source": [
        "Os parâmetros necessários para definir o problema QUBO são configurados no dicionário `qubo_settings` . Definimos o número de etapas de tempo (`nt`), o número de bits para especificação do investimento (`nq`) e a janela de tempo para cada etapa de tempo (`dt`). Além disso, definimos o investimento máximo por ativo, o coeficiente de aversão ao risco, a taxa de transação e o coeficiente de restrição (consulte [nosso artigo](https://arxiv.org/pdf/2412.19150) para obter detalhes sobre a formulação do problema). Essas configurações nos permitem adaptar o problema QUBO ao cenário de investimento específico.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d5e8137c-e3ef-45a7-928f-3bd6d02402cd",
      "metadata": {},
      "outputs": [],
      "source": [
        "qubo_settings = {\n",
        "    \"nt\": 4,\n",
        "    \"nq\": 2,\n",
        "    \"dt\": 30,\n",
        "    # maximum investment per asset is 2**nq/max_investment = 80%\n",
        "    \"max_investment\": 5,\n",
        "    \"risk_aversion\": 1000.0,\n",
        "    \"transaction_fee\": 0.01,\n",
        "    \"restriction_coeff\": 1.0,\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "06608c0a-34ac-47b4-84c5-da98458e6dab",
      "metadata": {},
      "source": [
        "O `optimizer_settings` dicionário configura o processo de otimização, incluindo parâmetros como `num_generations` para o número de iterações e `population_size` para o número de soluções candidatas por geração. Outras configurações controlam aspectos como a taxa de recombinação, tarefas paralelas, tamanho do lote e intervalo de mutação. Além disso, as configurações básicas, como `estimator_shots`, `estimator_precision`, e `sampler_shots`, definem as configurações do Estimador e do Amostrador quânticos para o processo de otimização.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d4946f9e-91bf-49a2-9970-2239c877b544",
      "metadata": {},
      "outputs": [],
      "source": [
        "optimizer_settings = {\n",
        "    \"de_optimizer_settings\": {\n",
        "        \"num_generations\": 20,\n",
        "        \"population_size\": 40,\n",
        "        \"recombination\": 0.4,\n",
        "        \"max_parallel_jobs\": 5,\n",
        "        \"max_batchsize\": 4,\n",
        "        \"mutation_range\": [0.0, 0.25],\n",
        "    },\n",
        "    \"optimizer\": \"differential_evolution\",\n",
        "    \"primitive_settings\": {\n",
        "        \"estimator_shots\": 25_000,\n",
        "        \"estimator_precision\": None,\n",
        "        \"sampler_shots\": 100_000,\n",
        "    },\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "fd9b0940-54b2-4ad1-833e-36b61b90cb46",
      "metadata": {},
      "source": [
        "<Admonition type=\"Note\">\n",
        "  O número total de circuitos depende dos parâmetros do site `optimizer_settings` e é calculado como `(num_generations + 1) * population_size`.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "866224f8-c27e-43c6-8906-dab87d7a4219",
      "metadata": {},
      "source": [
        "O `ansatz_settings` dicionário configura o ansatz do circuito quântico. O `ansatz` parâmetro especifica o uso da `\"optimized_real_amplitudes\"` abordagem, que é uma abordagem eficiente em termos de hardware projetada para problemas de otimização financeira. A `multiple_passmanager` opção, que permite o uso de vários gerenciadores de passagem (incluindo o gerenciador de passagem local padrão do Qiskit e o serviço transpiler com tecnologia de IA do Qiskit) para melhorar o desempenho e a eficiência da otimização do circuito, está atualmente definida como falsa porque esse recurso está temporariamente desativado.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "0054fbb0-75f0-468a-acc9-0066eabb85dd",
      "metadata": {},
      "outputs": [],
      "source": [
        "ansatz_settings = {\n",
        "    \"ansatz\": \"optimized_real_amplitudes\",\n",
        "    \"multiple_passmanager\": False,\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "76087a8d-a1f5-4adb-a96d-7c17b7c67cdf",
      "metadata": {},
      "source": [
        "Por fim, executamos a otimização executando a função `dpo_solver.run()` , passando as entradas preparadas. Isso inclui o dicionário de dados de ativos (`assets`), a configuração do QUBO (`qubo_settings`), os parâmetros de otimização (`optimizer_settings`) e as configurações de ansatz do circuito quântico (`ansatz_settings`). Além disso, especificamos os detalhes da execução, como o backend, e se devemos aplicar o pós-processamento aos resultados. Isso inicia o processo de otimização dinâmica do portfólio no backend quântico selecionado.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c24af785-105d-45db-a242-3f215c845cf6",
      "metadata": {},
      "outputs": [],
      "source": [
        "dpo_job = dpo_solver.run(\n",
        "    assets=assets.to_dict(),  # Insert assets as a dictionary\n",
        "    qubo_settings=qubo_settings,\n",
        "    optimizer_settings=optimizer_settings,\n",
        "    ansatz_settings=ansatz_settings,\n",
        "    backend_name=\"ibm_torino\",\n",
        "    previous_session_id=[],\n",
        "    apply_postprocess=True,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7bc6ac05-5c0b-418e-9978-8dd5bdcd97c4",
      "metadata": {},
      "source": [
        "<span id=\"step-3-analyze-the-optimization-results\" />\n",
        "\n",
        "## Etapa 3: Analise os resultados da otimização\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0c248fb7-0605-4456-a5ac-ced65b7593e7",
      "metadata": {},
      "source": [
        "Nesta seção, extraímos e exibimos a solução com o menor custo objetivo dos resultados da otimização. Juntamente com o custo objetivo mínimo, também apresentamos as principais métricas associadas à solução associada, incluindo o desvio de restrição, o índice de Sharpe e o retorno do investimento.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "3b41e88f-9f5e-4663-a225-ec732544a36a",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "{'time_step_0': {'ACS.MC': 0.11764705882352941,\n",
              "  'ITX.MC': 0.20588235294117646,\n",
              "  'FER.MC': 0.38235294117647056,\n",
              "  'ELE.MC': 0.058823529411764705,\n",
              "  'SCYR.MC': 0.0,\n",
              "  'AENA.MC': 0.058823529411764705,\n",
              "  'AMS.MC': 0.17647058823529413},\n",
              " 'time_step_1': {'ACS.MC': 0.11428571428571428,\n",
              "  'ITX.MC': 0.14285714285714285,\n",
              "  'FER.MC': 0.2,\n",
              "  'ELE.MC': 0.02857142857142857,\n",
              "  'SCYR.MC': 0.42857142857142855,\n",
              "  'AENA.MC': 0.0,\n",
              "  'AMS.MC': 0.08571428571428572},\n",
              " 'time_step_2': {'ACS.MC': 0.0,\n",
              "  'ITX.MC': 0.09375,\n",
              "  'FER.MC': 0.3125,\n",
              "  'ELE.MC': 0.34375,\n",
              "  'SCYR.MC': 0.0,\n",
              "  'AENA.MC': 0.0,\n",
              "  'AMS.MC': 0.25},\n",
              " 'time_step_3': {'ACS.MC': 0.3939393939393939,\n",
              "  'ITX.MC': 0.09090909090909091,\n",
              "  'FER.MC': 0.12121212121212122,\n",
              "  'ELE.MC': 0.18181818181818182,\n",
              "  'SCYR.MC': 0.0,\n",
              "  'AENA.MC': 0.0,\n",
              "  'AMS.MC': 0.21212121212121213}}"
            ]
          },
          "execution_count": 8,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Get the results of the job\n",
        "dpo_result = dpo_job.result()\n",
        "\n",
        "# Show the solution strategy\n",
        "dpo_result[\"result\"]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "48b545ed-5350-4a72-b792-ad4c4400c833",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Minimum Objective Cost Found: -3.67\n",
            "Best Solution:\n",
            "  - Restriction Deviation: 40.0%\n",
            "  - Sharpe Ratio: 14.54\n",
            "  - Return: 0.28\n"
          ]
        }
      ],
      "source": [
        "import pandas as pd\n",
        "\n",
        "# Get results from the job\n",
        "dpo_result = dpo_job.result()\n",
        "\n",
        "# Convert metadata to a DataFrame, excluding 'session_id'\n",
        "df = pd.DataFrame(dpo_result[\"metadata\"][\"all_samples_metrics\"])\n",
        "\n",
        "# Find the minimum objective cost\n",
        "min_cost = df[\"objective_costs\"].min()\n",
        "print(f\"Minimum Objective Cost Found: {min_cost:.2f}\")\n",
        "\n",
        "# Extract the row with the lowest cost\n",
        "best_row = df[df[\"objective_costs\"] == min_cost].iloc[0]\n",
        "\n",
        "# Display the results associated with the best solution\n",
        "print(\"Best Solution:\")\n",
        "print(f\"  - Restriction Deviation: {best_row['rest_breaches']}%\")\n",
        "print(f\"  - Sharpe Ratio: {best_row['sharpe_ratios']:.2f}\")\n",
        "print(f\"  - Return: {best_row['returns']:.2f}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "09116415-afbc-4e89-b3fd-9464b8f1d5df",
      "metadata": {},
      "source": [
        "O código a seguir mostra como visualizar e comparar a distribuição de custos de um algoritmo de otimização com uma distribuição de amostragem aleatória. Da mesma forma, exploramos o panorama da função objetivo QUBO (que pode ser carregada a partir da saída da função) avaliando-a com investimentos aleatórios. Traçamos ambas as distribuições normalizadas em amplitude para facilitar a comparação de como o processo de otimização difere da amostragem aleatória em termos de custo. Além disso, o resultado obtido com o DOCPlex é incluído como uma linha de referência vertical tracejada para servir como referência clássica. Usamos a [versão gratuita do DOCPlex](https://ibmdecisionoptimization.github.io/docplex-doc/) — a biblioteca IBM® de código aberto para otimização matemática em Python — para resolver o mesmo problema de forma clássica.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "2dd1d015-3015-409f-b5d9-016c8ff592ce",
      "metadata": {},
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "from matplotlib.ticker import MultipleLocator\n",
        "import matplotlib.patheffects as patheffects\n",
        "\n",
        "\n",
        "def plot_normalized(dpo_x, dpo_y_normalized, random_x, random_y_normalized):\n",
        "    \"\"\"\n",
        "    Plots normalized results for two sampling results.\n",
        "\n",
        "    Parameters:\n",
        "        dpo_x (array-like): X-values for the VQE Post-processed curve.\n",
        "        dpo_y_normalized (array-like): Y-values (normalized) for the\n",
        "        VQE Post-processed curve.\n",
        "        random_x (array-like): X-values for the Noise (Random) curve.\n",
        "        random_y_normalized (array-like): Y-values (normalized) for the\n",
        "        Noise (Random) curve.\n",
        "    \"\"\"\n",
        "    plt.figure(figsize=(6, 3))\n",
        "    plt.tick_params(axis=\"both\", which=\"major\", labelsize=12)\n",
        "\n",
        "    # Define custom colors\n",
        "    colors = [\"#4823E8\", \"#9AA4AD\"]\n",
        "\n",
        "    # Plot DPO results\n",
        "    (line1,) = plt.plot(\n",
        "        dpo_x, dpo_y_normalized, label=\"VQE Postprocessed\", color=colors[0]\n",
        "    )\n",
        "    line1.set_path_effects(\n",
        "        [patheffects.withStroke(linewidth=3, foreground=\"white\")]\n",
        "    )\n",
        "\n",
        "    # Plot Random results\n",
        "    (line2,) = plt.plot(\n",
        "        random_x, random_y_normalized, label=\"Noise (Random)\", color=colors[1]\n",
        "    )\n",
        "    line2.set_path_effects(\n",
        "        [patheffects.withStroke(linewidth=3, foreground=\"white\")]\n",
        "    )\n",
        "\n",
        "    # Set X-axis ticks to increment by 5 units\n",
        "    plt.gca().xaxis.set_major_locator(MultipleLocator(5))\n",
        "\n",
        "    # Axis labels and legend\n",
        "    plt.xlabel(\"Objective cost\", fontsize=14)\n",
        "    plt.ylabel(\"Normalized Counts\", fontsize=14)\n",
        "\n",
        "    # Add DOCPLEX reference line\n",
        "    plt.axvline(\n",
        "        x=-4.11, color=\"black\", linestyle=\"--\", linewidth=1, label=\"DOCPlex\"\n",
        "    )  # DOCPlex value\n",
        "    plt.ylim(bottom=0)\n",
        "\n",
        "    plt.legend()\n",
        "\n",
        "    # Adjust layout\n",
        "    plt.tight_layout()\n",
        "    plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6b662682-279b-48b5-bc61-681846cf3c00",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/global-data-quantum-optimizer/extracted-outputs/6b662682-279b-48b5-bc61-681846cf3c00-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "import numpy as np\n",
        "from collections import defaultdict\n",
        "\n",
        "# ================================\n",
        "# STEP 1: DPO COST DISTRIBUTION\n",
        "# ================================\n",
        "\n",
        "# Extract data from DPO results\n",
        "counts_list = dpo_result[\"metadata\"][\"all_samples_metrics\"][\n",
        "    \"counts\"\n",
        "]  # List of how many times each solution occurred\n",
        "cost_list = dpo_result[\"metadata\"][\"all_samples_metrics\"][\n",
        "    \"objective_costs\"\n",
        "]  # List of corresponding objective function values (costs)\n",
        "\n",
        "# Round costs to one decimal and accumulate counts for each unique cost\n",
        "dpo_counter = defaultdict(int)\n",
        "for cost, count in zip(cost_list, counts_list):\n",
        "    rounded_cost = round(cost, 1)\n",
        "    dpo_counter[rounded_cost] += count\n",
        "\n",
        "# Prepare data for plotting\n",
        "dpo_x = sorted(dpo_counter.keys())  # Sorted list of cost values\n",
        "dpo_y = [dpo_counter[c] for c in dpo_x]  # Corresponding counts\n",
        "\n",
        "# Normalize the counts to the range [0, 1] for better comparison\n",
        "dpo_min = min(dpo_y)\n",
        "dpo_max = max(dpo_y)\n",
        "dpo_y_normalized = [\n",
        "    (count - dpo_min) / (dpo_max - dpo_min) for count in dpo_y\n",
        "]\n",
        "\n",
        "# ================================\n",
        "# STEP 2: RANDOM COST DISTRIBUTION\n",
        "# ================================\n",
        "\n",
        "# Read the QUBO matrix\n",
        "qubo = np.array(dpo_result[\"metadata\"][\"qubo\"])\n",
        "\n",
        "bitstring_length = qubo.shape[0]\n",
        "num_random_samples = 100_000  # Number of random samples to generate\n",
        "random_cost_counter = defaultdict(int)\n",
        "\n",
        "# Generate random bitstrings and calculate their cost\n",
        "for _ in range(num_random_samples):\n",
        "    x = np.random.randint(0, 2, size=bitstring_length)\n",
        "    cost = float(x @ qubo @ x.T)\n",
        "    rounded_cost = round(cost, 1)\n",
        "    random_cost_counter[rounded_cost] += 1\n",
        "\n",
        "# Prepare random data for plotting\n",
        "random_x = sorted(random_cost_counter.keys())\n",
        "random_y = [random_cost_counter[c] for c in random_x]\n",
        "\n",
        "# Normalize the random cost distribution\n",
        "random_min = min(random_y)\n",
        "random_max = max(random_y)\n",
        "random_y_normalized = [\n",
        "    (count - random_min) / (random_max - random_min) for count in random_y\n",
        "]\n",
        "\n",
        "# ================================\n",
        "# STEP 3: PLOTTING\n",
        "# ================================\n",
        "\n",
        "plot_normalized(dpo_x, dpo_y_normalized, random_x, random_y_normalized)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "457ecd2e-50e5-49ea-b435-fb26e43a3266",
      "metadata": {},
      "source": [
        "O gráfico mostra como o otimizador de portfólio quântico retorna consistentemente estratégias de investimento otimizadas.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4f996a6d-b696-4d33-b832-97bda30b3969",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Referências\n",
        "\n",
        "\\[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. \"Dimensionamento do solucionador variacional de eigenvalores quânticos para o método dinâmico de Portfolio Optimization \" arXiv pré-impressão arXiv:2412.19150 (2024)](https://arxiv.org/pdf/2412.19150).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "819996e9-e98d-4753-9d6a-be0ab443088f",
      "metadata": {},
      "source": [
        "<span id=\"tutorial-survey\" />\n",
        "\n",
        "## Pesquisa tutorial\n",
        "\n",
        "Reserve um minuto para fazer comentários sobre este tutorial. Suas percepções nos ajudarão a melhorar nossas ofertas de conteúdo e a experiência do usuário.\n",
        "[Link para a pesquisa](https://your.feedback.ibm.com/jfe/form/SV_3BLFkNVEuh0QBWm)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    },
    "hours": 1.5,
    "qpuSeconds": 3300
  },
  "nbformat": 4,
  "nbformat_minor": 2
}