{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "d31e7ada-7707-4f25-9058-c895d1b205fc",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Ottimizzazione dinamica del portafoglio con Portfolio Optimizer di Global Data Quantum\"\n",
        "description: \"Risolvi un problema di ottimizzazione dinamica su scala industriale utilizzando il Quantum Portfolio Optimizer di 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",
        "# Ottimizzazione dinamica del portafoglio con Portfolio Optimizer di Global Data Quantum\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "36a8cc7a-179a-45f7-b28e-a2b4bbeaabd3",
      "metadata": {},
      "source": [
        "<Admonition type=\"note\" title=\"Nota\">\n",
        "  Le funzioni Qiskit sono una funzione sperimentale disponibile solo per gli utenti di IBM Quantum® Premium Plan, Flex Plan e On-Prem (tramite IBM Quantum Platform API) Plan. Sono in stato di anteprima e sono soggetti a modifiche.\n",
        "</Admonition>\n",
        "\n",
        "*Stima di utilizzo: Circa 55 minuti con un processore Heron r2. (NOTA: questa è solo una stima. Il tempo di esecuzione effettivo potrebbe variare)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "16f9751a-3f70-4c1c-a384-78f402567279",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Sfondo\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "80d86936-8073-415e-a024-2adbd0efef2a",
      "metadata": {},
      "source": [
        "Il problema dell'ottimizzazione dinamica del portafoglio mira a trovare la strategia di investimento ottimale su più periodi di tempo per massimizzare il rendimento atteso del portafoglio e minimizzare i rischi, spesso sotto certi vincoli come il budget, i costi di transazione o l'avversione al rischio. A differenza dell'ottimizzazione di portafoglio standard, che considera un unico momento per riequilibrare il portafoglio, la versione dinamica tiene conto della natura evolutiva degli asset e adatta gli investimenti in base alle variazioni della performance degli asset nel tempo.\n",
        "\n",
        "Questa esercitazione mostra come eseguire l'ottimizzazione dinamica del portafoglio utilizzando la funzione Qiskit del Quantum Portfolio Optimizer. In particolare, illustriamo come utilizzare questa funzione applicativa per risolvere un problema di allocazione degli investimenti su più fasi temporali.\n",
        "\n",
        "L'approccio prevede la formulazione dell'ottimizzazione del portafoglio come un problema multi-obiettivo di ottimizzazione binaria quadratica non vincolata (QUBO). In particolare, formuliamo la funzione QUBO $O$ per ottimizzare simultaneamente quattro diversi obiettivi:\n",
        "\n",
        "* Massimizzare la funzione di ritorno $F$\n",
        "* Ridurre al minimo il rischio dell'investimento $R$\n",
        "* Ridurre al minimo i costi di transazione $C$\n",
        "* Rispettare le restrizioni all'investimento, formulate in un termine aggiuntivo per minimizzare $P$.\n",
        "\n",
        "In sintesi, per affrontare questi obiettivi formuliamo la funzione QUBO come $O = -F + \\frac{\\gamma}{2} R + C + \\rho P,$ dove $\\gamma$ è il coefficiente di avversione al rischio e $\\rho$ è il coefficiente di rafforzamento delle restrizioni (moltiplicatore di Lagrange). La formulazione esplicita si trova in Eq. (15) del nostro manoscritto [\\[1\\]](#references).\n",
        "\n",
        "Risolviamo utilizzando un metodo ibrido quantistico-classico basato sul Variational Quantum Eigensolver (VQE). In questa configurazione, il circuito quantistico stima la funzione di costo, mentre l'ottimizzazione classica viene eseguita utilizzando l'algoritmo di evoluzione differenziale, consentendo una navigazione efficiente nel paesaggio delle soluzioni. Il numero di qubit necessari dipende da tre fattori principali: il numero di attività `na`, il numero di periodi di tempo `nt` e la risoluzione in bit utilizzata per rappresentare l'investimento `nq`. In particolare, il numero minimo di qubit nel nostro problema è `na*nt*nq`.\n",
        "\n",
        "In questa esercitazione ci concentreremo sull'ottimizzazione di un portafoglio regionale basato sull'indice IBEX 35 spagnolo. In particolare, utilizziamo un portafoglio di sette attività, come indicato nella tabella seguente:\n",
        "\n",
        "| **Portafoglio IBEX 35** | ACS.MC | ITX.MC | FER.MC | ELE.MC | SCYR.MC | AENA.MC | AMS.MC |\n",
        "| ----------------------- | ------ | ------ | ------ | ------ | ------- | ------- | ------ |\n",
        "\n",
        "Riequilibriamo il nostro portafoglio in quattro fasi temporali, ciascuna separata da un intervallo di 30 giorni a partire dal 1° novembre 2022. Ogni variabile di investimento è codificata con due bit. Il risultato è un problema che richiede 56 qubit per essere risolto.\n",
        "\n",
        "Utilizziamo l'Ansatz delle Ampiezze Reali Ottimizzate, un adattamento personalizzato ed efficiente dal punto di vista hardware dell'Ansatz delle Ampiezze Reali standard, studiato appositamente per migliorare le prestazioni di questo tipo di problemi di ottimizzazione finanziaria.\n",
        "\n",
        "L'esecuzione quantistica viene eseguita sul backend `ibm_torino` . Per una spiegazione dettagliata della formulazione del problema, della metodologia e della valutazione delle prestazioni, si rimanda al manoscritto pubblicato [\\[1\\]](#references).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3716659d-a44f-448b-a131-e0044b05d2ed",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisiti\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",
        "## Configura\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "689bf5f3-8332-4a7c-b62e-047566c1971f",
      "metadata": {},
      "source": [
        "Per utilizzare Quantum Portfolio Optimizer, selezionare la funzione tramite il catalogo delle funzioni di Qiskit. Per eseguire questa funzione è necessario un account IBM Quantum Premium Plan o Flex Plan con una licenza di Global Data Quantum.\n",
        "\n",
        "Per prima cosa, effettua l'autenticazione con [la]() tua chiave API. Quindi, carica la funzione desiderata dall' Qiskit Functions Catalog In questo caso, stai richiamando la `quantum_portfolio_optimizer` funzione dal catalogo utilizzando la `QiskitFunctionsCatalog` classe. Questa funzione ci permette di utilizzare il risolutore predefinito Quantum Portfolio Optimization.\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",
        "## Passaggio 1: Leggi il portafoglio di input\n",
        "\n",
        "In questa fase, carichiamo i dati storici dei sette asset selezionati dall'indice IBEX 35, in particolare dal **1° novembre 2022** al **1° aprile 2023**.\n",
        "\n",
        "I dati vengono recuperati utilizzando l'API di Yahoo Finance, concentrandosi sui prezzi di chiusura. I dati vengono quindi elaborati per garantire che tutti gli asset abbiano lo stesso numero di giorni con dati. I dati mancanti (giorni di non negoziazione) vengono gestiti in modo appropriato, garantendo che tutti gli asset siano allineati alle stesse date.\n",
        "\n",
        "I dati sono strutturati in un sito DataFrame con una formattazione coerente per tutti gli asset.\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=\"Avvertenza\">\n",
        "  Si consiglia di eseguire la seguente cella per verificare che i dati siano stati scaricati correttamente dall'API di Yahoo Finance. Se il download non riesce, una notifica indicherà che i dati saranno invece recuperati da un database 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",
        "## Fase 2: Definire gli input del problema\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8f066377-62fb-46fd-ae1a-1058d99c5f7f",
      "metadata": {},
      "source": [
        "I parametri necessari per definire il problema QUBO sono configurati nel dizionario `qubo_settings` . Definiamo il numero di passi temporali (`nt`), il numero di bit per la specificazione dell'investimento (`nq`) e la finestra temporale per ogni passo temporale (`dt`). Inoltre, fissiamo l'investimento massimo per attività, il coefficiente di avversione al rischio, la commissione di transazione e il coefficiente di restrizione (si veda il [nostro articolo](https://arxiv.org/pdf/2412.19150) per i dettagli sulla formulazione del problema). Queste impostazioni ci permettono di adattare il problema QUBO allo specifico scenario di investimento.\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": [
        "Il `optimizer_settings` dizionario configura il processo di ottimizzazione, inclusi parametri quali `num_generations` per il numero di iterazioni e `population_size` per il numero di soluzioni candidate per generazione. Altre impostazioni regolano aspetti quali il tasso di ricombinazione, i processi in parallelo, la dimensione dei batch e l'intervallo di mutazione. Inoltre, le impostazioni primitive, quali `estimator_shots`, `estimator_precision`, e `sampler_shots`, definiscono le configurazioni dello stimatore e del campionatore quantistico per il processo di ottimizzazione.\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",
        "  Il numero totale di circuiti dipende dai parametri di `optimizer_settings` ed è calcolato come `(num_generations + 1) * population_size`.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "866224f8-c27e-43c6-8906-dab87d7a4219",
      "metadata": {},
      "source": [
        "Il `ansatz_settings` dizionario configura l'ansatz del circuito quantistico. Il `ansatz` parametro specifica l'uso `\"optimized_real_amplitudes\"` dell'approccio, che è un approccio efficiente dal punto di vista hardware progettato per problemi di ottimizzazione finanziaria. L'opzione `multiple_passmanager` , che consente l'uso di più gestori di passaggi (incluso il gestore di passaggi locale predefinito Qiskit e il servizio di transpilazione basato sull'intelligenza artificiale Qiskit) per migliorare le prestazioni e l'efficienza dell'ottimizzazione dei circuiti, è attualmente impostata su false perché questa funzione è temporaneamente disabilitata.\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": [
        "Infine, eseguiamo l'ottimizzazione eseguendo la funzione `dpo_solver.run()` , passando gli input preparati. Questi includono il dizionario dei dati delle risorse (`assets`), la configurazione di QUBO (`qubo_settings`), i parametri di ottimizzazione (`optimizer_settings`) e le impostazioni dell'ansatz dei circuiti quantistici (`ansatz_settings`). Inoltre, si specificano i dettagli dell'esecuzione, come il backend e l'eventuale applicazione della post-elaborazione ai risultati. Questo avvia il processo di ottimizzazione dinamica del portafoglio sul backend quantistico selezionato.\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",
        "## Fase 3: Analizzare i risultati dell'ottimizzazione\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0c248fb7-0605-4456-a5ac-ced65b7593e7",
      "metadata": {},
      "source": [
        "In questa sezione, estraiamo e visualizziamo la soluzione con il costo obiettivo più basso dai risultati dell'ottimizzazione. Oltre al costo obiettivo minimo, presentiamo anche le metriche chiave associate alla soluzione associata, tra cui la deviazione della restrizione, il rapporto Sharpe e il rendimento dell'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": [
        "Il codice seguente mostra come visualizzare e confrontare la distribuzione dei costi di un algoritmo di ottimizzazione con una distribuzione campionaria casuale. Allo stesso modo, esploriamo il panorama della funzione obiettivo QUBO (che può essere caricata dall'output della funzione) valutandola con investimenti casuali. Tracciamo entrambe le distribuzioni normalizzate in ampiezza per facilitare il confronto tra il processo di ottimizzazione e il campionamento casuale in termini di costo. Inoltre, il risultato ottenuto utilizzando DOCPlex è incluso come linea di riferimento verticale tratteggiata che funge da benchmark classico. Utilizziamo la [versione gratuita di DOCPlex](https://ibmdecisionoptimization.github.io/docplex-doc/) — la IBM® libreria open source per l'ottimizzazione matematica in Python — per risolvere lo stesso problema in modo classico.\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": [
        "Il grafico mostra come l'ottimizzatore di portafoglio quantistico restituisca costantemente strategie di investimento ottimizzate.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4f996a6d-b696-4d33-b832-97bda30b3969",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Riferimenti\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. \"Scalabilità dell'Eigensolver quantistico variazionale per l' Portfolio Optimization dinamica.\" arXiv preprint 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",
        "## Sondaggio tutorial\n",
        "\n",
        "Dedicate un minuto a fornire un feedback su questa esercitazione. Le vostre indicazioni ci aiuteranno a migliorare la nostra offerta di contenuti e l'esperienza degli utenti.\n",
        "[Collegamento al sondaggio](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
}