{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "d31e7ada-7707-4f25-9058-c895d1b205fc",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Dynamische Portfoliooptimierung mit dem Portfolio Optimizer von Global Data Quantum\"\n",
        "description: \"Lösen Sie ein dynamisches Optimierungsproblem im Versorgungsmaßstab mit dem Quantum Portfolio Optimizer von 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",
        "# Dynamische Portfoliooptimierung mit dem Portfolio Optimizer von Global Data Quantum\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "36a8cc7a-179a-45f7-b28e-a2b4bbeaabd3",
      "metadata": {},
      "source": [
        "<Admonition type=\"note\" title=\"Anmerkung\">\n",
        "  Qiskit-Funktionen sind eine experimentelle Funktion, die nur für Benutzer des IBM Quantum® Premium Plan, Flex Plan und On-Prem (über IBM Quantum Platform API) Plan verfügbar ist. Sie befinden sich im Status einer Vorabversion und können sich noch ändern.\n",
        "</Admonition>\n",
        "\n",
        "*Geschätzte Nutzungsdauer: Ungefähr 55 Minuten auf einem Heron r2 Prozessor. (HINWEIS: Dies ist nur eine Schätzung. Die tatsächliche Laufzeit kann variieren)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "16f9751a-3f70-4c1c-a384-78f402567279",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Hintergrund\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "80d86936-8073-415e-a024-2adbd0efef2a",
      "metadata": {},
      "source": [
        "Das Problem der dynamischen Portfolio-Optimierung zielt darauf ab, die optimale Anlagestrategie über mehrere Zeiträume zu finden, um die erwartete Portfoliorendite zu maximieren und die Risiken zu minimieren, oft unter bestimmten Einschränkungen wie Budget, Transaktionskosten oder Risikoaversion. Im Gegensatz zur Standard-Portfolio-Optimierung, bei der ein einziger Zeitpunkt für die Neugewichtung des Portfolios berücksichtigt wird, berücksichtigt die dynamische Version die sich entwickelnde Natur von Vermögenswerten und passt die Investitionen auf der Grundlage von Änderungen der Vermögensleistung im Laufe der Zeit an.\n",
        "\n",
        "Dieses Tutorial zeigt, wie man eine dynamische Portfolio-Optimierung mit Hilfe der Quantum Portfolio Optimizer Qiskit Funktion durchführt. Wir zeigen insbesondere, wie diese Anwendungsfunktion zur Lösung eines Investitionsallokationsproblems über mehrere Zeitschritte verwendet werden kann.\n",
        "\n",
        "Der Ansatz beinhaltet die Formulierung der Portfolio-Optimierung als ein Mehrzielproblem der quadratischen, uneingeschränkten binären Optimierung (QUBO). Konkret formulieren wir die QUBO-Funktion $O$ zur gleichzeitigen Optimierung von vier verschiedenen Zielen:\n",
        "\n",
        "* Maximierung der Rückgabefunktion $F$\n",
        "* Minimieren Sie das Risiko der Investition $R$\n",
        "* Minimieren Sie die Transaktionskosten $C$\n",
        "* Beachten Sie die Anlagebeschränkungen, die in einer zusätzlichen Klausel formuliert sind, um $P$ zu minimieren.\n",
        "\n",
        "Um diese Ziele zu erreichen, formulieren wir die QUBO-Funktion wie folgt $O = -F + \\frac{\\gamma}{2} R + C + \\rho P,$ wobei $\\gamma$ der Risikoaversionskoeffizient und $\\rho$ der Restriktionsverstärkungskoeffizient (Lagrange-Multiplikator) ist. Die explizite Formulierung ist in Gleichung zu finden. (15) aus unserem Manuskript [\\[1\\]](#references).\n",
        "\n",
        "Wir lösen mit einer hybriden quantenklassischen Methode, die auf dem Variational Quantum Eigensolver (VQE) basiert. In diesem Aufbau schätzt der Quantenschaltkreis die Kostenfunktion, während die klassische Optimierung mit dem Algorithmus der Differentialevolution durchgeführt wird, was eine effiziente Navigation durch die Lösungslandschaft ermöglicht. Die Anzahl der benötigten Qubits hängt von drei Hauptfaktoren ab: der Anzahl der Vermögenswerte `na`, der Anzahl der Zeiträume `nt` und der Bitauflösung, die zur Darstellung der Anlage verwendet wird `nq`. Konkret lautet die minimale Anzahl von Qubits in unserem Problem `na*nt*nq`.\n",
        "\n",
        "In diesem Tutorial konzentrieren wir uns auf die Optimierung eines regionalen Portfolios auf der Grundlage des spanischen IBEX 35 Index. Im Einzelnen verwenden wir ein Portfolio mit sieben Vermögenswerten, wie in der nachstehenden Tabelle angegeben:\n",
        "\n",
        "| **IBEX 35 Portfolio** | ACS.MC | ITX.MC | FER.MC | ELE.MC | SCYR.MC | AENA.MC | AMS.MC |\n",
        "| --------------------- | ------ | ------ | ------ | ------ | ------- | ------- | ------ |\n",
        "\n",
        "Wir nehmen eine Neugewichtung unseres Portfolios in vier Zeitschritten vor, die jeweils durch ein 30-Tage-Intervall ab dem 1. November 2022 getrennt sind. Jede Investitionsvariable wird mit zwei Bits kodiert. Daraus ergibt sich ein Problem, für dessen Lösung 56 Qubits erforderlich sind.\n",
        "\n",
        "Wir verwenden den Optimized Real Amplitudes-Ansatz, eine angepasste und hardware-effiziente Adaption des Standard-Real-Amplitudes-Ansatzes, die speziell darauf zugeschnitten ist, die Leistung für diese Art von finanziellen Optimierungsproblemen zu verbessern.\n",
        "\n",
        "Die Quantum-Ausführung wird auf dem Backend `ibm_torino` durchgeführt. Eine ausführliche Erläuterung der Problemstellung, der Methodik und der Leistungsbewertung finden Sie in dem veröffentlichten Manuskript [\\[1\\]](#references).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3716659d-a44f-448b-a131-e0044b05d2ed",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Anforderungen\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",
        "## Einrichtung\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "689bf5f3-8332-4a7c-b62e-047566c1971f",
      "metadata": {},
      "source": [
        "Um den Quantum Portfolio Optimizer zu verwenden, wählen Sie die Funktion über den Qiskit-Funktionskatalog aus. Sie benötigen ein IBM Quantum Premium Plan oder Flex Plan Konto mit einer Lizenz von Global Data Quantum, um diese Funktion auszuführen.\n",
        "\n",
        "Melden Sie sich zunächst mit Ihrem [API-Schlüssel]() an. Laden Sie anschließend die gewünschte Funktion aus dem Verzeichnis „ Qiskit Functions Catalog “. Hier rufen Sie die `quantum_portfolio_optimizer` Funktion aus dem Katalog mithilfe der `QiskitFunctionsCatalog` Klasse auf. Mit dieser Funktion können wir den vordefinierten Quantum- Portfolio Optimization -Löser verwenden.\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",
        "## Schritt 1: Lesen Sie das Eingabeportfolio\n",
        "\n",
        "In diesem Schritt laden wir historische Daten für die sieben ausgewählten Vermögenswerte aus dem IBEX 35 Index, und zwar vom **1. November 2022** bis zum **1. April 2023**.\n",
        "\n",
        "Wir beziehen die Daten über die Yahoo Finance API, wobei wir uns auf die Schlusskurse konzentrieren. Die Daten werden dann verarbeitet, um sicherzustellen, dass alle Anlagen die gleiche Anzahl von Tagen mit Daten haben. Fehlende Daten (Nicht-Handelstage) werden entsprechend behandelt, um sicherzustellen, dass alle Vermögenswerte an denselben Daten ausgerichtet sind.\n",
        "\n",
        "Die Daten sind in DataFrame strukturiert und für alle Assets einheitlich formatiert.\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=\"Warnung\">\n",
        "  Wir empfehlen, die folgende Zelle auszuführen, um zu überprüfen, ob die Daten korrekt von der Yahoo Finance API heruntergeladen wurden. Wenn der Download fehlschlägt, wird eine Benachrichtigung angezeigt, dass die Daten stattdessen aus einer alternativen Online-Datenbank abgerufen werden.\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",
        "## Schritt 2: Definieren Sie die Problem-Eingaben\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8f066377-62fb-46fd-ae1a-1058d99c5f7f",
      "metadata": {},
      "source": [
        "Die für die Definition des QUBO-Problems erforderlichen Parameter werden im Wörterbuch `qubo_settings` konfiguriert. Wir definieren die Anzahl der Zeitschritte (`nt`), die Anzahl der Bits für die Investitionsspezifikation (`nq`) und das Zeitfenster für jeden Zeitschritt (`dt`). Außerdem legen wir die maximale Investition pro Vermögenswert, den Risikoaversionskoeffizienten, die Transaktionsgebühr und den Restriktionskoeffizienten fest (Einzelheiten zur Problemformulierung finden Sie in [unserem Papier](https://arxiv.org/pdf/2412.19150) ). Diese Einstellungen ermöglichen es uns, das QUBO-Problem an das jeweilige Investitionsszenario anzupassen.\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": [
        "Das `optimizer_settings` Wörterbuch konfiguriert den Optimierungsprozess, einschließlich Parametern wie `num_generations` für die Anzahl der Iterationen und `population_size` für die Anzahl der Lösungsvorschläge pro Generation. Weitere Einstellungen regeln Aspekte wie die Rekombinationsrate, die Anzahl paralleler Jobs, die Batchgröße und den Mutationsbereich. Darüber hinaus legen die primitiven Einstellungen wie `estimator_shots`, `estimator_precision`, und die Konfigurationen des Quanten-Schätzers und des `sampler_shots`Quanten-Samplers für den Optimierungsprozess fest.\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",
        "  Die Gesamtzahl der Schaltungen hängt von den Parametern von `optimizer_settings` ab und wird wie folgt berechnet: `(num_generations + 1) * population_size`.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "866224f8-c27e-43c6-8906-dab87d7a4219",
      "metadata": {},
      "source": [
        "Das `ansatz_settings` Wörterbuch konfiguriert den Quanten-Schaltkreis-Ansatz. Der `ansatz` Parameter gibt die Verwendung des `\"optimized_real_amplitudes\"` Ansatzes an, bei dem es sich um einen hardwareeffizienten Ansatz handelt, der für finanzielle Optimierungsprobleme entwickelt wurde. Die `multiple_passmanager` Option, die die Verwendung mehrerer Pass-Manager (einschließlich des standardmäßigen lokalen Qiskit-Pass-Managers und des Qiskit-Transpiler-Dienstes mit KI-Unterstützung) ermöglicht, um die Leistung und Effizienz der Schaltungsoptimierung zu verbessern, ist derzeit auf „false“ gesetzt, da diese Funktion vorübergehend deaktiviert ist.\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": [
        "Schließlich führen wir die Optimierung aus, indem wir die Funktion `dpo_solver.run()` ausführen und die vorbereiteten Eingaben übergeben. Dazu gehören das Asset-Datenverzeichnis (`assets`), die QUBO-Konfiguration (`qubo_settings`), die Optimierungsparameter (`optimizer_settings`) und die Einstellungen für den Quantenschaltkreis-Ansatz (`ansatz_settings`). Außerdem geben wir die Ausführungsdetails an, wie z. B. das Backend, und ob die Ergebnisse nachbearbeitet werden sollen. Damit wird der dynamische Portfolio-Optimierungsprozess auf dem ausgewählten Quanten-Backend eingeleitet.\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",
        "## Schritt 3: Analysieren Sie die Optimierungsergebnisse\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0c248fb7-0605-4456-a5ac-ced65b7593e7",
      "metadata": {},
      "source": [
        "In diesem Abschnitt wird die Lösung mit den niedrigsten Zielkosten aus den Optimierungsergebnissen extrahiert und angezeigt. Neben den minimalen Zielkosten stellen wir auch die wichtigsten Kennzahlen der entsprechenden Lösung vor, darunter die Restriktionsabweichung, die Sharpe-Ratio und die Anlagerendite.\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": [
        "Der folgende Code zeigt, wie die Kostenverteilung eines Optimierungsalgorithmus mit einer Zufallsstichprobenverteilung visualisiert und verglichen werden kann. Ebenso untersuchen wir die Landschaft der QUBO-Zielfunktion (die aus der Funktionsausgabe geladen werden kann), indem wir sie mit zufälligen Investitionen bewerten. Wir stellen beide Verteilungen in normalisierter Amplitude dar, um den Unterschied zwischen dem Optimierungsprozess und der Zufallsstichprobe hinsichtlich der Kosten besser vergleichen zu können. Zusätzlich wird das mit DOCPlex erzielte Ergebnis als gestrichelte vertikale Referenzlinie dargestellt, die als klassischer Referenzwert dient. Wir verwenden die [kostenlose Version von DOCPlex](https://ibmdecisionoptimization.github.io/docplex-doc/) – die IBM® Open-Source-Bibliothek für mathematische Optimierung in Python – um dasselbe Problem klassisch zu lösen.\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": [
        "Die Grafik zeigt, wie der Quantum Portfolio Optimizer konsequent optimierte Anlagestrategien liefert.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4f996a6d-b696-4d33-b832-97bda30b3969",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Referenzen\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. „Skalierung des Variations-Quanten-Eigenlösers für die dynamische Portfolio Optimization.“ arXiv Vorabdruck 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",
        "## Tutorial-Umfrage\n",
        "\n",
        "Bitte nehmen Sie sich eine Minute Zeit, um uns Ihr Feedback zu diesem Tutorial zu geben. Ihre Erkenntnisse helfen uns, unser Inhaltsangebot und unsere Nutzererfahrung zu verbessern.\n",
        "[Link zur Umfrage](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
}