{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# This cell is added by sphinx-gallery\n",
    "# It can be customized to whatever you like\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 3-qubit Ising model in PyTorch\n",
    "\n",
    "\n",
    "\n",
    "The interacting spins with variable coupling strengths of an [Ising model](https://en.wikipedia.org/wiki/Ising_model) can be used to simulate various machine learning concepts like [Hopfield networks](https://en.wikipedia.org/wiki/Hopfield_network) and [Boltzmann machines](https://en.wikipedia.org/wiki/Boltzmann_machine) ([Schuld & Petruccione (2018)](https://www.springer.com/gp/book/9783319964232)). They also closely imitate the underlying mathematics of a subclass of computational problems called [Quadratic Unconstrained Binary Optimization (QUBO)](https://en.wikipedia.org/wiki/Quadratic_unconstrained_binary_optimization) problems.\n",
    "\n",
    "Ising models are commonly encountered in the subject area of adiabatic quantum computing. Quantum annealing algorithms (for example, as performed on a D-wave system) are often used to find low-energy configurations of Ising problems. The optimization landscape of the Ising model is non-convex, which can make finding global minima challenging. In this tutorial, we get a closer look at this phenomenon by applying gradient descent techniques to a toy Ising model.\n",
    "\n",
    "## PennyLane implementation\n",
    "\n",
    "\n",
    "This basic tutorial optimizes a 3-qubit Ising model using the PennyLane `default.qubit` device with PyTorch. In the absence of external fields, the Hamiltonian for this system is given by:\n",
    "\n",
    "$$H=-\\sum_{<i,j>} J_{ij} \\sigma_i \\sigma_{j},$$\n",
    "\n",
    "where each spin can be in the +1 or -1 spin state and $J_{ij}$ are the nearest-neighbour coupling strengths.\n",
    "\n",
    "For simplicity, the first spin can be assumed to be in the \\\"up\\\" state (+1 eigenstate of Pauli-Z operator) and the coupling matrix can be set to $J = [1,-1]$. The rotation angles for the other two spins are then\n",
    "optimized so that the energy of the system is minimized for the given couplings.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch.autograd import Variable\n",
    "import pennylane as qml\n",
    "from pennylane import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A three-qubit quantum circuit is initialized to represent the three\n",
    "spins:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "dev = qml.device(\"default.qubit\", wires=3)\n",
    "\n",
    "@qml.qnode(dev, interface=\"torch\") \n",
    "def circuit(p1, p2):\n",
    "    # We use the general Rot(phi,theta,omega,wires) single-qubit operation\n",
    "    qml.Rot(p1[0], p1[1], p1[2], wires=1)\n",
    "    qml.Rot(p2[0], p2[1], p2[2], wires=2)\n",
    "    return [qml.expval(qml.PauliZ(i)) for i in range(3)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The cost function to be minimized is defined as the energy of the spin\n",
    "configuration:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def cost(var1, var2):\n",
    "    # the circuit function returns a numpy array of Pauli-Z expectation values\n",
    "    spins = circuit(var1, var2)\n",
    "\n",
    "    # the expectation value of Pauli-Z is +1 for spin up and -1 for spin down\n",
    "    energy = -(1 * spins[0] * spins[1]) - (-1 * spins[1] * spins[2])\n",
    "    return energy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Sanity check\n",
    "\n",
    "\n",
    "Let\\'s test the functions above using the\n",
    "$[s_1, s_2, s_3] = [1, -1, -1]$ spin configuration and the given\n",
    "coupling matrix. The total energy for this Ising model should be:\n",
    "\n",
    "$$H = -1(J_1 s_1 \\otimes s_2 + J_2 s_2 \\otimes s3) = 2$$\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Energy for [1, -1, -1] spin configuration: tensor(2.0000, dtype=torch.float64)\n"
     ]
    }
   ],
   "source": [
    "test1 = torch.tensor([0, np.pi, 0])\n",
    "test2 = torch.tensor([0, np.pi, 0])\n",
    "\n",
    "cost_check = cost(test1, test2)\n",
    "print(\"Energy for [1, -1, -1] spin configuration:\", cost_check)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Random initialization\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Randomly initialized angles:\n",
      "tensor([1.9632, 2.6022, 2.3277], dtype=torch.float64, requires_grad=True)\n",
      "tensor([0.6521, 2.8474, 2.4300], dtype=torch.float64, requires_grad=True)\n",
      "Corresponding cost before optimization:\n",
      "tensor(1.6792, dtype=torch.float64, grad_fn=<SubBackward0>)\n"
     ]
    }
   ],
   "source": [
    "torch.manual_seed(56)\n",
    "p1 = Variable((np.pi * torch.rand(3, dtype=torch.float64)), requires_grad=True)\n",
    "p2 = Variable((np.pi * torch.rand(3, dtype=torch.float64)), requires_grad=True)\n",
    "\n",
    "var_init = [p1, p2]\n",
    "cost_init = cost(p1, p2)\n",
    "\n",
    "print(\"Randomly initialized angles:\")\n",
    "print(p1)\n",
    "print(p2)\n",
    "print(\"Corresponding cost before optimization:\")\n",
    "print(cost_init)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Optimization\n",
    "\n",
    "\n",
    "Now we use the PyTorch gradient descent optimizer to minimize the cost:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Energy after step     5:  0.6846474 | Angles: [array([1.96323939, 1.93604492, 2.32767565]), array([0.65212549, 2.73080219, 2.4299563 ])] \n",
      "\n",
      "Energy after step    10: -1.0138530 | Angles: [array([1.96323939, 1.0136468 , 2.32767565]), array([0.65212549, 2.73225282, 2.4299563 ])] \n",
      "\n",
      "Energy after step    15: -1.8171995 | Angles: [array([1.96323939, 0.38483073, 2.32767565]), array([0.65212549, 2.85992571, 2.4299563 ])] \n",
      "\n",
      "Energy after step    20: -1.9686584 | Angles: [array([1.96323939, 0.13026452, 2.32767565]), array([0.65212549, 2.97097572, 2.4299563 ])] \n",
      "\n",
      "Energy after step    25: -1.9930403 | Angles: [array([1.96323939, 0.04302756, 2.32767565]), array([0.65212549, 3.04042222, 2.4299563 ])] \n",
      "\n",
      "Energy after step    30: -1.9980133 | Angles: [array([1.96323939, 0.01413292, 2.32767565]), array([0.65212549, 3.08179844, 2.4299563 ])] \n",
      "\n",
      "Energy after step    35: -1.9993550 | Angles: [array([1.96323939, 0.00463472, 2.32767565]), array([0.65212549, 3.10627578, 2.4299563 ])] \n",
      "\n",
      "Energy after step    40: -1.9997802 | Angles: [array([1.96323939e+00, 1.51911413e-03, 2.32767565e+00]), array([0.65212549, 3.12073668, 2.4299563 ])] \n",
      "\n",
      "Energy after step    45: -1.9999239 | Angles: [array([1.96323939e+00, 4.97829828e-04, 2.32767565e+00]), array([0.65212549, 3.12927707, 2.4299563 ])] \n",
      "\n",
      "Energy after step    50: -1.9999735 | Angles: [array([1.96323939e+00, 1.63134183e-04, 2.32767565e+00]), array([0.65212549, 3.13432035, 2.4299563 ])] \n",
      "\n",
      "Energy after step    55: -1.9999908 | Angles: [array([1.96323939e+00, 5.34564150e-05, 2.32767565e+00]), array([0.65212549, 3.13729842, 2.4299563 ])] \n",
      "\n",
      "Energy after step    60: -1.9999968 | Angles: [array([1.96323939e+00, 1.75166673e-05, 2.32767565e+00]), array([0.65212549, 3.13905695, 2.4299563 ])] \n",
      "\n",
      "Energy after step    65: -1.9999989 | Angles: [array([1.96323939e+00, 5.73986944e-06, 2.32767565e+00]), array([0.65212549, 3.14009534, 2.4299563 ])] \n",
      "\n",
      "Energy after step    70: -1.9999996 | Angles: [array([1.96323939e+00, 1.88084132e-06, 2.32767565e+00]), array([0.65212549, 3.14070851, 2.4299563 ])] \n",
      "\n",
      "Energy after step    75: -1.9999999 | Angles: [array([1.96323939e+00, 6.16314188e-07, 2.32767565e+00]), array([0.65212549, 3.14107057, 2.4299563 ])] \n",
      "\n",
      "Energy after step    80: -2.0000000 | Angles: [array([1.96323939e+00, 2.01953845e-07, 2.32767565e+00]), array([0.65212549, 3.14128437, 2.4299563 ])] \n",
      "\n",
      "Energy after step    85: -2.0000000 | Angles: [array([1.96323939e+00, 6.61762372e-08, 2.32767565e+00]), array([0.65212549, 3.14141062, 2.4299563 ])] \n",
      "\n",
      "Energy after step    90: -2.0000000 | Angles: [array([1.96323939e+00, 2.16846296e-08, 2.32767565e+00]), array([0.65212549, 3.14148516, 2.4299563 ])] \n",
      "\n",
      "Energy after step    95: -2.0000000 | Angles: [array([1.96323939e+00, 7.10561943e-09, 2.32767565e+00]), array([0.65212549, 3.14152918, 2.4299563 ])] \n",
      "\n",
      "Energy after step   100: -2.0000000 | Angles: [array([1.96323939e+00, 2.32836938e-09, 2.32767565e+00]), array([0.65212549, 3.14155517, 2.4299563 ])] \n",
      "\n"
     ]
    }
   ],
   "source": [
    "opt = torch.optim.SGD(var_init, lr=0.1)\n",
    "\n",
    "def closure():\n",
    "    opt.zero_grad()\n",
    "    loss = cost(p1, p2)\n",
    "    loss.backward()\n",
    "    return loss\n",
    "\n",
    "var_pt = [var_init]\n",
    "cost_pt = [cost_init]\n",
    "x = [0]\n",
    "\n",
    "for i in range(100):\n",
    "    opt.step(closure)\n",
    "    if (i + 1) % 5 == 0:\n",
    "        x.append(i)\n",
    "        p1n, p2n = opt.param_groups[0][\"params\"]\n",
    "        costn = cost(p1n, p2n)\n",
    "        var_pt.append([p1n, p2n])\n",
    "        cost_pt.append(costn)\n",
    "\n",
    "        # for clarity, the angles are printed as numpy arrays\n",
    "        print(\"Energy after step {:5d}: {: .7f} | Angles: {}\".format(\n",
    "            i+1, costn, [p1n.detach().numpy(), p2n.detach().numpy()]),\"\\n\"\n",
    "        )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "**Note**\n",
    "\n",
    "When using the *PyTorch* optimizer, keep in mind that:\n",
    "\n",
    "1.  `loss.backward()` computes the gradient of the cost function with\n",
    "    respect to all parameters with `requires_grad=True`.\n",
    "2.  `opt.step()` performs the parameter update based on this *current*\n",
    "    gradient and the learning rate.\n",
    "3.  `opt.zero_grad()` sets all the gradients back to zero. It\\'s\n",
    "    important to call this before `loss.backward()` to avoid the\n",
    "    accumulation of gradients from multiple passes.\n",
    "\n",
    "Hence, its standard practice to define the `closure()` function that\n",
    "clears up the old gradient, evaluates the new gradient and passes it\n",
    "onto the optimizer in each step.\n",
    "\n",
    "The minimum energy is -2 for the spin configuration $[s_1, s_2, s_3] = [1, 1, -1]$ which corresponds to $(\\phi, \\theta, \\omega) = (0, 0, 0)$ for the second spin and $(\\phi, \\theta, \\omega) = (0, \\pi, 0)$ for the third spin. Note that gradient descent optimization might not find this global minimum due to the non-convex cost function, as is shown in the next section.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimized angles:\n",
      "tensor([1.9632e+00, 2.3284e-09, 2.3277e+00], dtype=torch.float64,\n",
      "       requires_grad=True)\n",
      "tensor([0.6521, 3.1416, 2.4300], dtype=torch.float64, requires_grad=True)\n",
      "Final cost after optimization:\n",
      "tensor(-2.0000, dtype=torch.float64, grad_fn=<SubBackward0>)\n"
     ]
    }
   ],
   "source": [
    "p1_final, p2_final = opt.param_groups[0][\"params\"]\n",
    "print(\"Optimized angles:\")\n",
    "print(p1_final)\n",
    "print(p2_final)\n",
    "print(\"Final cost after optimization:\")\n",
    "print(cost(p1_final, p2_final))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": "<Figure size 600x400 with 1 Axes>",
      "image/png": "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\n"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "fig = plt.figure(figsize=(6, 4))\n",
    "\n",
    "# Enable processing the Torch trainable tensors\n",
    "with torch.no_grad():\n",
    "    plt.plot(x, cost_pt, label = 'global minimum')\n",
    "    plt.xlabel(\"Optimization steps\")\n",
    "    plt.ylabel(\"Cost / Energy\")\n",
    "    plt.legend()\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Local minimum\n",
    "\n",
    "If the spins are initialized close to the local minimum of zero energy, the optimizer is likely to get stuck here and never find the global minimum at -2.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Corresponding cost before optimization:\n",
      "tensor(0.0082, dtype=torch.float64, grad_fn=<SubBackward0>)\n"
     ]
    }
   ],
   "source": [
    "torch.manual_seed(9)\n",
    "p3 = Variable((np.pi*torch.rand(3, dtype = torch.float64)), requires_grad = True)\n",
    "p4 = Variable((np.pi*torch.rand(3, dtype = torch.float64)), requires_grad = True)\n",
    "\n",
    "var_init_loc = [p3, p4]\n",
    "cost_init_loc = cost(p3, p4)\n",
    "\n",
    "print(\"Corresponding cost before optimization:\")\n",
    "print(cost_init_loc)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Energy after step     5:  0.0032761 | Angles: [array([0.77369911, 2.63471297, 1.07981163]), array([0.26038622, 0.08659858, 1.91060734])] \n",
      "\n",
      "Energy after step    10:  0.0013137 | Angles: [array([0.77369911, 2.63406019, 1.07981163]), array([0.26038622, 0.05483683, 1.91060734])] \n",
      "\n",
      "Energy after step    15:  0.0005266 | Angles: [array([0.77369911, 2.63379816, 1.07981163]), array([0.26038622, 0.03471974, 1.91060734])] \n",
      "\n",
      "Energy after step    20:  0.0002111 | Angles: [array([0.77369911, 2.63369307, 1.07981163]), array([0.26038622, 0.02198151, 1.91060734])] \n",
      "\n",
      "Energy after step    25:  0.0000846 | Angles: [array([0.77369911, 2.63365094, 1.07981163]), array([0.26038622, 0.01391648, 1.91060734])] \n",
      "\n",
      "Energy after step    30:  0.0000339 | Angles: [array([0.77369911, 2.63363405, 1.07981163]), array([0.26038622, 0.00881044, 1.91060734])] \n",
      "\n",
      "Energy after step    35:  0.0000136 | Angles: [array([0.77369911, 2.63362729, 1.07981163]), array([0.26038622, 0.00557782, 1.91060734])] \n",
      "\n",
      "Energy after step    40:  0.0000054 | Angles: [array([0.77369911, 2.63362457, 1.07981163]), array([0.26038622, 0.00353126, 1.91060734])] \n",
      "\n",
      "Energy after step    45:  0.0000022 | Angles: [array([0.77369911, 2.63362348, 1.07981163]), array([0.26038622, 0.00223561, 1.91060734])] \n",
      "\n",
      "Energy after step    50:  0.0000009 | Angles: [array([0.77369911, 2.63362305, 1.07981163]), array([2.60386222e-01, 1.41534339e-03, 1.91060734e+00])] \n",
      "\n",
      "Energy after step    55:  0.0000004 | Angles: [array([0.77369911, 2.63362287, 1.07981163]), array([2.60386222e-01, 8.96040252e-04, 1.91060734e+00])] \n",
      "\n",
      "Energy after step    60:  0.0000001 | Angles: [array([0.77369911, 2.6336228 , 1.07981163]), array([2.60386222e-01, 5.67274421e-04, 1.91060734e+00])] \n",
      "\n",
      "Energy after step    65:  0.0000001 | Angles: [array([0.77369911, 2.63362278, 1.07981163]), array([2.60386222e-01, 3.59135947e-04, 1.91060734e+00])] \n",
      "\n",
      "Energy after step    70:  0.0000000 | Angles: [array([0.77369911, 2.63362276, 1.07981163]), array([2.60386222e-01, 2.27365491e-04, 1.91060734e+00])] \n",
      "\n",
      "Energy after step    75:  0.0000000 | Angles: [array([0.77369911, 2.63362276, 1.07981163]), array([2.60386222e-01, 1.43942891e-04, 1.91060734e+00])] \n",
      "\n",
      "Energy after step    80:  0.0000000 | Angles: [array([0.77369911, 2.63362276, 1.07981163]), array([2.60386222e-01, 9.11288509e-05, 1.91060734e+00])] \n",
      "\n",
      "Energy after step    85:  0.0000000 | Angles: [array([0.77369911, 2.63362276, 1.07981163]), array([2.60386222e-01, 5.76927932e-05, 1.91060734e+00])] \n",
      "\n",
      "Energy after step    90:  0.0000000 | Angles: [array([0.77369911, 2.63362276, 1.07981163]), array([2.60386222e-01, 3.65247488e-05, 1.91060734e+00])] \n",
      "\n",
      "Energy after step    95:  0.0000000 | Angles: [array([0.77369911, 2.63362276, 1.07981163]), array([2.60386222e-01, 2.31234648e-05, 1.91060734e+00])] \n",
      "\n",
      "Energy after step   100:  0.0000000 | Angles: [array([0.77369911, 2.63362276, 1.07981163]), array([2.60386222e-01, 1.46392417e-05, 1.91060734e+00])] \n",
      "\n"
     ]
    }
   ],
   "source": [
    "opt = torch.optim.SGD(var_init_loc, lr = 0.1)\n",
    "\n",
    "def closure():\n",
    "    opt.zero_grad()\n",
    "    loss = cost(p3, p4)\n",
    "    loss.backward()\n",
    "    return loss\n",
    "\n",
    "var_pt_loc = [var_init_loc]\n",
    "cost_pt_loc = [cost_init_loc]\n",
    "\n",
    "for j in range(100):\n",
    "    opt.step(closure)\n",
    "    if (j + 1) % 5 == 0:\n",
    "        p3n, p4n = opt.param_groups[0]['params']\n",
    "        costn = cost(p3n, p4n)\n",
    "        var_pt_loc.append([p3n, p4n])\n",
    "        cost_pt_loc.append(costn)\n",
    "\n",
    "        # for clarity, the angles are printed as numpy arrays\n",
    "        print('Energy after step {:5d}: {: .7f} | Angles: {}'.format(\n",
    "            j+1, costn, [p3n.detach().numpy(), p4n.detach().numpy()]),\"\\n\"\n",
    "        )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": "<Figure size 600x400 with 1 Axes>",
      "image/png": "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Ps31woULJu1+H1KOHj0qAIgffvhBWl5RUSFcXFzEJ598IoQwhhQA4vjx41KblJQU4ePj02wt999/v5g6dWqTy5r7nv379xcbN240mffKK6+IyMhIk/VWrFghLa+rqxO9e/cWf/3rX2+4X6LOjJd7iLowYcFDziMjI6/7fOjQIYv3eeutt8LO7rd/Wnx8fDB06FDps729PXr27CldomlOYWEhnnjiCbz11lu48847pfn5+fm4//770adPH7i5uWHUqFEAgOLiYrNrPHToEBwcHKDRaKR5PXv2xMCBA02+s6urK/r37y999vX1bbHuZ555BmlpaQgNDcWCBQuwe/fuFuuorq7GiRMnMG3aNHTv3l2ali1bhhMnTpi0/f2fj4ODA8LDw6VaLd0vUWfBkELUBQ0YMAAKhaLZkHHo0CF4enqiV69ebb5vR0dHk88KhaLJeQaDodlt6HQ6jB8/Hk899RSmTZsmza+uroZWq4W7uzs+/vhj7Nu3D5999hkAoLa2tg2/hVFTdbcU/O699178+uuveO6553D27FmMGTMGzz//fLPtL126BAB49913sX//fmk6ePAg9uzZY3adlu6XqLNgSCHqgnr27Il77rkHb7/9Nq5cuWKyTKfT4eOPP8akSZOgUCik+df+KO7ZsweDBg2SPjs6OqKhoaF9Cwdw9epVTJgwASEhIXj99ddNlh0+fBjnzp3DihUrcNdddyEkJOS6MxtOTk4A0GKtgwYNQn19PXJzc6V5586dw5EjRzB48OCbqr9Xr16Ij4/HRx99hDVr1mDdunXN1uXj4wM/Pz+cPHkSAwYMMJkaO9o2+v2fT319PfLz803+fJrbL1Fn1r73/BGRbN566y1ERUVBq9Vi2bJlCAoKws8//4z58+fD398fy5cvN2n/ww8/YOXKlYiNjUVWVhbS09PxxRdfSMsDAwORnZ2NO++8E87OzvD09GyXumfOnInTp08jOzsb5eXl0vwePXqgT58+cHJywptvvomnn34aBw8evG7sk759+0KhUGD79u0YN24cXFxc0L17d5M2wcHBmDBhAqZPn4533nkHbm5ueOGFF+Dv748JEya0uvakpCSEhYXh1ltvRU1NDbZv3y4FCW9vb7i4uCAzMxO9e/eGUqmESqXCSy+9hGeffRYqlQpjx45FTU0N8vLycOHCBSQkJEjbTklJQXBwMAYNGoQ33ngDFy5cwJNPPnnD/RJ1ZjyTQtRFBQcHIy8vD/369cPDDz+M/v37Y8aMGRg9ejRycnLQo0cPk/Z//vOfkZeXhxEjRmDZsmV4/fXXodVqpeWrV69GVlYWAgICMGLEiHar+7vvvkNJSQkGDx4MX19fadq9ezd69eqFDRs2ID09HYMHD8aKFSvw2muvmazv7++Pl156CS+88AJ8fHwwe/bsJvezfv16hIWF4b777kNkZCSEENixY8d1l3gs4eTkhMTERAwbNgx/+MMfYG9vj7S0NADGfiRr167FO++8Az8/PykMPfXUU3jvvfewfv16DB06FKNGjcKGDRuuO5OyYsUKrFixAsOHD8euXbuwbds2eHl53XC/RJ2ZQljSs46IuqTAwEDMmzePw8ZboVOnTiEoKAiFhYU3/QgCos6GZ1KIiIjIKjGkEBERkVXi5R4iIiKySjyTQkRERFaJIYWIiIisEkMKERERWSWGFCIiIrJKDClERERklRhSiIiIyCoxpBAREZFVYkghIiIiq8SQQkRERFbp/wFo8pjFfsMLowAAAABJRU5ErkJggg==\n"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig = plt.figure(figsize=(6, 4))\n",
    "\n",
    "# Enable processing the Torch trainable tensors\n",
    "with torch.no_grad():\n",
    "    plt.plot(x, cost_pt_loc, 'r', label = 'local minimum')\n",
    "    plt.xlabel(\"Optimization steps\")\n",
    "    plt.ylabel(\"Cost / Energy\")\n",
    "    plt.legend()\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Send it after class |\n",
    "\n",
    "Try with different initialization parameters and see how the results change.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "source": [
    "# Two Types of Quantum Computers\n",
    "\n",
    "It is not well-known what problems quantum computers can solve. We can classify quantum computers into “gate model” and “quantum annealing”. They have different use cases and architecture.\n",
    "\n",
    "## Gate Model\n",
    "\n",
    "The quantum gate model computers are designed for universal purposes by applying gates to build complex algorithms. They can be considered the upward compatible machines of classical computers but exponentially fast if appropriate quantum algorithms like Shor and Grover are used. The applications include prime factorisation, which becomes a threat to the existing secure data transmission methods over the internet using RSA (Rivest–Shamir–Adleman) keys, quantum simulations which have the potential for finding new medicines, and machine learning. However, the gate model requires high-quality qubit processors; the number of qubits is limited to two digits (except for IBM’s exploratory 127-qubit Eagle system.)\n",
    "\n",
    "## Quantum Annealing\n",
    "\n",
    "On the other hand, quantum annealers shine when they are used to search for the best from a vast number of possible combinations. Such challenges are called combinatorial optimisation problems. Many applications of quantum annealing have been reported recently. There are also researches to develop novel machine learning algorithms using quantum annealers. Amin et al. showed that there were possibilities to use quantum annealing hardware as a sampler for Boltzmann Machine by exploiting its quantum nature.\n",
    "\n",
    "In physics, the minimum energy principle states that the internal energy decreases and approaches the minimum values. If we can formulate our problems in the form of an energy minimisation equation, the quantum annealer will be able to search for the best possible answer.\n",
    "\n",
    "Quantum annealer is relatively noise-tolerant compared to the gate model. At the time of this writing, the most considerable number of qubits on a quantum processing unit (QPU) is at least five thousand provided by D-Wave’s Advantage solver. It is expected to increase more in the coming years. Five thousand qubits is a large number compared to what is achieved by the gate model. However, what we obtain from the annealer is as limited as a set of 5k-bit values per sampling. Furthermore, not all qubits are connected. Therefore, a hybrid system of classical computing and the quantum annealer is being developed to complement the capabilities of the two systems.\n",
    "\n",
    "Annealing is a technique used to make quality iron products by heating the material to a certain degree of temperature and cooling it down slowly. The iron’s mechanical characteristics are altered through the heat treatment, which reduces the hardness and increases the elasticity.\n",
    "\n",
    "Simulated annealing (SA) is a probabilistic technique whose name and idea are derived from annealing in material science. SA is a meta-heuristic in which algorithms approximate the global minimum or maximum of a given function. It uses a parameter called temperature that is initialised with a high value and decreases as the algorithm proceeds, like the physical annealing. High-temperature value promotes the algorithm to look for the various parts of the problem space, which helps to avoid being stuck in local minima or maxima. When the temperature goes down to zero, the state reaches the optimal point if successful. Going through the SA algorithm is outside the scope of this article, and the details on how it works can be found, for example, on the MathWorks website.\n",
    "\n",
    "Quantum annealing (QA) is an algorithm to solve combinatorial optimisation problems mainly. Instead of using temperature to explore the problem space, QA uses the laws of quantum mechanics to measure the energy state. Starting from the qubits’ dual state, where all the possible combinations of the values are equally likely to happen, we can gradually reduce such quantum mechanical effects by the process called quantum fluctuation to reach the most optimal point where the lowest energy state is realised.\n",
    "\n",
    "Kadowaki and Nishimori proved that QA converges to the optimal state with a much larger probability than SA in almost all cases on classical computers. The actual quantum annealing machines were developed by D-Wave and built on the ground of their theoretical framework.\n",
    "\n",
    "## Formulating Problem for QA\n",
    "\n",
    "QA machines are specialised hardware to solve combinatorial optimisation problems. These problems can be found in many places in our life. For example, an Amazon delivery person needs to find the most optimal route to deliver all parcels given for the day. The objective is to find a route with the shortest distance to visit all the delivery addresses. We can treat the distance as the energy in the QA context and find the path that reaches the ground state. We can also think of a futuristic example where multiple autonomous cars are navigated to optimise the traffic in a specific city area with heavy traffic jams. The objective is to reduce the traffic by minimising the number of overlaps of cars taking the same route simultaneously while each car aims to optimise the travel time to a destination.\n",
    "\n",
    "Quantum annealers require an objective function to be defined in the Ising model or Quadratic Unconstrained Binary Optimisation (QUBO). Ising model is a mathematical model in statistical mechanics. QUBO is mathematically equivalent to the Ising model and is much simpler to formulate a problem.\n",
    "\n",
    "QUBO is a minimisation objective function expressed as follows:\n",
    "\n",
    "$$ H_{QUBO}(x)=\\sum_{i=1}^N \\sum_{j=1}^N Q_{ij}x_ix_j $$\n",
    "\n",
    "\n",
    "where $x_i$ and $x_j\\_ {i,j =0,1,…,N}$ takes 0 or 1. $Q_{ij}$ is a coefficient matrix of size $n \\times n$, which we need to prepare for a QA machine. In case of a problem to search for a global maximum, we can flip the sign to minus to convert the problem into a minimisation problem.\n",
    "\n",
    "In reality, a problem must be satisfied with one or more constraints. For example, a knapsack problem has a constraint of how many weights you can pack things in the bag. Such constraints are multiplied by weight parameters and are added to the objective function as penalty terms.\n",
    "\n",
    "## Implementation Using Quantum Annealer\n",
    "\n",
    "In this section, we would like to code and solve a simple combinatorial optimisation problem in Python and the real Quantum Annealer by D-Wave.\n",
    "\n",
    "## Preparation\n",
    "\n",
    "Before starting, we need to sign up for a cloud computing service. D-wave offers trial access to QA solvers with a sixty-second computational time allowance for one month. Please go to D-Wave Leap to find out more. After successful registration, the API token is available on the dashboard."
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Problem Setting\n",
    "\n",
    "We would like to challenge a simplified portfolio optimisation problem. Suppose there are twenty candidate stocks from FTSE100 to invest in. Our objective is to maximise an expected return rate at the minimum risk while keeping more or less ten stocks in our portfolio.\n",
    "\n",
    "We will take the following six steps to solve the problem:\n",
    "\n",
    "1. Import historical stock price data from the internet\n",
    "2. Prepare data by cleansing and feature engineering\n",
    "3. Formulate the cost functions and constraints to obtain a QUBO matrix\n",
    "4. Sample optimisation results from a D-Wave solver\n",
    "5. Sample optimisation results from a simulated quantum annealing solver\n",
    "6. Evaluate the outcomes\n",
    "\n",
    "Each of the above steps is demonstrated in a Colab notebook found in Python Coding in Colab Notebook section.\n",
    "\n",
    "## Dependencies\n",
    "\n",
    "Some notes on the Python requirements:\n",
    "\n",
    "* yfinance is used to retrieve stock price data from Yahoo! Finance.\n",
    "* Prophet is a python package used to make time-series forecasting. It is possible to do multivariate time series forecasting and add holiday effects easily.\n",
    "* D-Wave Ocean SDK needs to be installed. The SDK includes packages that are necessary to communicate with D-Wave resources.\n",
    "* PyQUBO is a library that can construct QUBO from cost and constraints functions.\n",
    "* OpenJIJ is an open-source meta-heuristics library for the Ising model and QUBO. It simulates the quantum annealers and can be used to experiment without the D-Wave computers. It accepts QUBOs stored in a Python dictionary type.\n",
    "\n",
    "## Cost and Constraint Function\n",
    "\n",
    "Our objective in this example is to minimise the sum of two cost functions and one constraint term, namely:\n",
    "\n",
    "1. the maximisation of return rates\n",
    "2. the minimisation of risks by lowering portfolio variance\n",
    "3. the minimisation of the penalty term\n",
    "\n",
    "1. The maximisation of the return rates can be formulated as a minimisation cost function:\n",
    "\n",
    "$$ H_{cost1}=-\\sum_{i=1}^N R_ix_i $$ (cost function 1)\n",
    "\n",
    "\n",
    "where $R_i$ is the return rate of the $i$th stock $x_i$. Because $x_i$ only takes a binary value $0$ or $1$, $x_i$ is equivalent with the quadratic term $x_i \\times x_i$. Therefore, $H_{cost1}$ function can be equivalent to the following QUBO:\n",
    "\n",
    "$$ H_{cost1_{QUBO}} = -\\sum_{i=j\\, i,j=1}^NR_{ij}x_ix_j $$ (cost function as QUBO)\n",
    "\n",
    "\n",
    "2. The minimisation of portfolio variance is represented as :\n",
    "\n",
    "$$ H_{cost2}=\\sum_{i=1}^N\\sum_{j=1}^NW_{ij}x_ix_j $$ (cost function 2)\n",
    "\n",
    "where W denotes a covariance matrix.\n",
    "\n",
    "3. The minimisation of the penalty can be formed in mean squared error (MSE) as a constraint term:\n",
    "\n",
    "$$ H_{const}=(\\sum_{i=1}^N x_i- K)^2 $$ (constraint term)\n",
    "\n",
    "\n",
    "where K is the number of stocks to be selected for the portfolio.\n",
    "\n",
    "$H_{const}$ can also be transformed to the equivalent QUBOs by expanding the formula:\n",
    "\n",
    "$$ H_{const}=(\\sum_{i=1}^N x_i- K)^2\\\\ =(\\sum_{i=1}^N x_i- K)(\\sum_{j=1}^N x_j- K)\\\\=\\sum_{i,j=1}^N x_ix_j-2K\\sum_{i=1}^Nx_i + K^2 $$\n",
    "(Constraint term expanded for QUBO formulation)\n",
    "\n",
    "$-2K\\sum_{i=1}^Nx_i$ part can also be equivalent to the quadratic form, as seen in the above $H_{cost1}$ function. $K^2$ is a constant value called offset which is not included in the QUBO matrix.\n",
    "\n",
    "We will use the `PyQUBO` library in Python code to generate the QUBO matrix from functions for simplicity.\n",
    "\n",
    "Thus, we can solve the problem by finding such x that minimises the function H(x) = (H_cost1 + H_cost2 + lambda*H_const), where lambda is a weight parameter for the constraint"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "aQbHUjMPD3GZ"
   },
   "source": [
    "# 1. Import historical stock price data from the internet\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "slZHyufhIy_m"
   },
   "source": [
    "First of all, Install Yahoo finance library to retrieve stock data.\n",
    "We will import randomly selected 20 stocks from FTSE100 and extract the historical closing prices for the past 2 years from July 2020."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "id": "mnsygle_Dpw6"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Collecting yfinance\r\n",
      "  Downloading yfinance-0.2.3-py2.py3-none-any.whl (50 kB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m50.4/50.4 kB\u001B[0m \u001B[31m674.3 kB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m \u001B[36m0:00:01\u001B[0m\r\n",
      "\u001B[?25hRequirement already satisfied: requests>=2.26 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from yfinance) (2.28.1)\r\n",
      "Collecting pytz>=2022.5\r\n",
      "  Downloading pytz-2022.7-py2.py3-none-any.whl (499 kB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m499.4/499.4 kB\u001B[0m \u001B[31m3.5 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m00:01\u001B[0m00:01\u001B[0m\r\n",
      "\u001B[?25hCollecting html5lib>=1.1\r\n",
      "  Downloading html5lib-1.1-py2.py3-none-any.whl (112 kB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m112.2/112.2 kB\u001B[0m \u001B[31m26.3 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m\r\n",
      "\u001B[?25hRequirement already satisfied: pandas>=1.3.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from yfinance) (1.5.1)\r\n",
      "Requirement already satisfied: cryptography>=3.3.2 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from yfinance) (37.0.1)\r\n",
      "Collecting lxml>=4.9.1\r\n",
      "  Downloading lxml-4.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (7.1 MB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m7.1/7.1 MB\u001B[0m \u001B[31m29.2 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0ma \u001B[36m0:00:01\u001B[0m\r\n",
      "\u001B[?25hRequirement already satisfied: beautifulsoup4>=4.11.1 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from yfinance) (4.11.1)\r\n",
      "Requirement already satisfied: frozendict>=2.3.4 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from yfinance) (2.3.4)\r\n",
      "Collecting multitasking>=0.0.7\r\n",
      "  Downloading multitasking-0.0.11-py3-none-any.whl (8.5 kB)\r\n",
      "Requirement already satisfied: appdirs>=1.4.4 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from yfinance) (1.4.4)\r\n",
      "Requirement already satisfied: numpy>=1.16.5 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from yfinance) (1.23.4)\r\n",
      "Requirement already satisfied: soupsieve>1.2 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from beautifulsoup4>=4.11.1->yfinance) (2.3.1)\r\n",
      "Requirement already satisfied: cffi>=1.12 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from cryptography>=3.3.2->yfinance) (1.15.1)\r\n",
      "Requirement already satisfied: webencodings in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from html5lib>=1.1->yfinance) (0.5.1)\r\n",
      "Requirement already satisfied: six>=1.9 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from html5lib>=1.1->yfinance) (1.16.0)\r\n",
      "Requirement already satisfied: python-dateutil>=2.8.1 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from pandas>=1.3.0->yfinance) (2.8.2)\r\n",
      "Requirement already satisfied: urllib3<1.27,>=1.21.1 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from requests>=2.26->yfinance) (1.26.12)\r\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from requests>=2.26->yfinance) (2022.9.24)\r\n",
      "Requirement already satisfied: idna<4,>=2.5 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from requests>=2.26->yfinance) (3.4)\r\n",
      "Requirement already satisfied: charset-normalizer<3,>=2 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from requests>=2.26->yfinance) (2.1.1)\r\n",
      "Requirement already satisfied: pycparser in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from cffi>=1.12->cryptography>=3.3.2->yfinance) (2.21)\r\n",
      "Installing collected packages: pytz, multitasking, lxml, html5lib, yfinance\r\n",
      "  Attempting uninstall: pytz\r\n",
      "    Found existing installation: pytz 2022.1\r\n",
      "    Uninstalling pytz-2022.1:\r\n",
      "      Successfully uninstalled pytz-2022.1\r\n",
      "Successfully installed html5lib-1.1 lxml-4.9.2 multitasking-0.0.11 pytz-2022.7 yfinance-0.2.3\r\n",
      "\r\n",
      "\u001B[1m[\u001B[0m\u001B[34;49mnotice\u001B[0m\u001B[1;39;49m]\u001B[0m\u001B[39;49m A new release of pip available: \u001B[0m\u001B[31;49m22.3\u001B[0m\u001B[39;49m -> \u001B[0m\u001B[32;49m22.3.1\u001B[0m\r\n",
      "\u001B[1m[\u001B[0m\u001B[34;49mnotice\u001B[0m\u001B[1;39;49m]\u001B[0m\u001B[39;49m To update, run: \u001B[0m\u001B[32;49mpip install --upgrade pip\u001B[0m\r\n"
     ]
    }
   ],
   "source": [
    "!pip install yfinance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "id": "VXCVsQj_uTcO"
   },
   "outputs": [],
   "source": [
    "import yfinance as yf  \n",
    "import numpy as np\n",
    "import pandas as pd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "B8fQ4MloD_zP",
    "outputId": "20fffb52-6657-48f5-d747-d45588eac329"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[*********************100%***********************]  20 of 20 completed\n",
      "[*********************100%***********************]  20 of 20 completed\n"
     ]
    }
   ],
   "source": [
    "# list of FTSE100 stocks available at \n",
    "# https://www.londonstockexchange.com/indices/ftse-100/constituents/table\n",
    "# below are randomly selected stocks\n",
    "\n",
    "stocks_dict = {\n",
    "    'FLTR.L':'FLUTTER ENTERTAINMENT PLC',\n",
    "    'SVT.L':'SEVERN TRENT PLC', \n",
    "    'LSEG.L':'LONDON STOCK EXCHANGE GROUP PLC', \n",
    "    'BNZL.L':'BUNZL PLC', \n",
    "    'CPG.L':'COMPASS GROUP PLC', \n",
    "    'NXT.L':'NEXT PLC', \n",
    "    'ULVR.L':'UNILEVER PLC', \n",
    "    'SHEL.L':'SHELL PLC', \n",
    "    'CCH.L':'COCA-COLA HBC A', \n",
    "    'BRBY.L':'BURBERRY GROUP PLC', \n",
    "    'SSE.L':'SSE PLC', \n",
    "    'REL.L':'RELX PLC', \n",
    "    'EXPN.L':'EXPERIAN PLC', \n",
    "    'DCC.L':'DCC PLC ORD', \n",
    "    'AZN.L':'ASTRAZENECA PLC', \n",
    "    'GSK.L':'GSK PLC ORD', \n",
    "    'ADM.L':'ADMIRAL GROUP PLC', \n",
    "    'SDR.L':'SCHRODERS PLC', \n",
    "    'BATS.L':'BRITISH AMERICAN TOBACCO PLC', \n",
    "    'IHG.L':'INTERCONTINENTAL HOTELS GROUP PLC', \n",
    "    }\n",
    "    \n",
    "stocks = list(stocks_dict.keys()) # list of candidate stocks\n",
    "\n",
    "data = yf.download(stocks,'2020-07-01','2022-04-30')['Close'] # main data\n",
    "data_test = yf.download(stocks,'2022-05-01','2022-07-24')['Close'] # testing data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 364
    },
    "id": "kwMT3Yb_X1V5",
    "outputId": "3bd6d8ef-b5ec-4975-cb3b-3c96fe2e91d0"
   },
   "outputs": [
    {
     "data": {
      "text/plain": "             ADM.L         AZN.L       BATS.L       BNZL.L       BRBY.L  \\\ncount   464.000000    464.000000   463.000000   464.000000   464.000000   \nmean   2949.351293   8366.258621  2787.561555  2537.961207  1804.203375   \nstd     298.590229    753.326915   223.859266   228.969271   228.739954   \nmin    2240.000000   6794.000000  2448.000000  2131.000000  1252.500000   \n25%    2756.750000   7931.500000  2643.750000  2363.000000  1630.000000   \n50%    2962.000000   8413.000000  2734.000000  2479.500000  1820.250000   \n75%    3116.250000   8678.250000  2825.750000  2712.750000  1965.250000   \nmax    3688.000000  10930.000000  3439.000000  3163.000000  2264.000000   \n\n             CCH.L        CPG.L        DCC.L       EXPN.L        FLTR.L  \\\ncount   464.000000   463.000000   463.000000   463.000000    464.000000   \nmean   2294.758621  1466.997840  6042.596112  2933.794816  12754.031149   \nstd     314.297710   176.851449   420.743959   288.931273   2003.651264   \nmin    1460.500000  1050.500000  5006.000000  2273.000000   7922.000000   \n25%    2073.000000  1393.250000  5785.000000  2738.000000  11400.000000   \n50%    2372.500000  1497.500000  6030.000000  2893.000000  12890.000000   \n75%    2532.250000  1586.250000  6268.000000  3109.000000  14282.500000   \nmax    2784.000000  1820.500000  7204.000000  3667.000000  16915.000000   \n\n             GSK.L        IHG.L       LSEG.L        NXT.L        REL.L  \\\ncount   464.000000   463.000000   464.000000   464.000000   464.000000   \nmean   1475.618385  4722.868251  7975.392241  7204.778017  1997.713362   \nstd     128.447452   399.235132   794.814741   952.486937   248.250476   \nmin    1199.561523  3516.000000  6370.000000  4673.000000  1527.500000   \n25%    1385.821991  4549.000000  7371.500000  6282.000000  1788.625000   \n50%    1446.565674  4812.000000  7939.000000  7660.000000  1918.250000   \n75%    1579.234680  5026.000000  8596.000000  7980.500000  2227.250000   \nmax    1823.720459  5338.000000  9910.000000  8426.000000  2449.000000   \n\n            SDR.L       SHEL.L        SSE.L        SVT.L       ULVR.L  \ncount  464.000000   463.000000   464.000000   464.000000   464.000000  \nmean   568.022027  1489.860042  1503.168103  2601.586207  4142.657884  \nstd     52.854523   299.833511   144.853407   241.375319   357.982299  \nmin    442.510010   900.000000  1171.000000  2168.000000  3328.000000  \n25%    519.009995  1333.299988  1384.875000  2413.250000  3911.125000  \n50%    587.179993  1440.400024  1524.250000  2511.000000  4124.500000  \n75%    606.942520  1660.300049  1612.000000  2823.000000  4358.500000  \nmax    658.070007  2228.000000  1868.500000  3211.000000  4892.000000  ",
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>ADM.L</th>\n      <th>AZN.L</th>\n      <th>BATS.L</th>\n      <th>BNZL.L</th>\n      <th>BRBY.L</th>\n      <th>CCH.L</th>\n      <th>CPG.L</th>\n      <th>DCC.L</th>\n      <th>EXPN.L</th>\n      <th>FLTR.L</th>\n      <th>GSK.L</th>\n      <th>IHG.L</th>\n      <th>LSEG.L</th>\n      <th>NXT.L</th>\n      <th>REL.L</th>\n      <th>SDR.L</th>\n      <th>SHEL.L</th>\n      <th>SSE.L</th>\n      <th>SVT.L</th>\n      <th>ULVR.L</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>count</th>\n      <td>464.000000</td>\n      <td>464.000000</td>\n      <td>463.000000</td>\n      <td>464.000000</td>\n      <td>464.000000</td>\n      <td>464.000000</td>\n      <td>463.000000</td>\n      <td>463.000000</td>\n      <td>463.000000</td>\n      <td>464.000000</td>\n      <td>464.000000</td>\n      <td>463.000000</td>\n      <td>464.000000</td>\n      <td>464.000000</td>\n      <td>464.000000</td>\n      <td>464.000000</td>\n      <td>463.000000</td>\n      <td>464.000000</td>\n      <td>464.000000</td>\n      <td>464.000000</td>\n    </tr>\n    <tr>\n      <th>mean</th>\n      <td>2949.351293</td>\n      <td>8366.258621</td>\n      <td>2787.561555</td>\n      <td>2537.961207</td>\n      <td>1804.203375</td>\n      <td>2294.758621</td>\n      <td>1466.997840</td>\n      <td>6042.596112</td>\n      <td>2933.794816</td>\n      <td>12754.031149</td>\n      <td>1475.618385</td>\n      <td>4722.868251</td>\n      <td>7975.392241</td>\n      <td>7204.778017</td>\n      <td>1997.713362</td>\n      <td>568.022027</td>\n      <td>1489.860042</td>\n      <td>1503.168103</td>\n      <td>2601.586207</td>\n      <td>4142.657884</td>\n    </tr>\n    <tr>\n      <th>std</th>\n      <td>298.590229</td>\n      <td>753.326915</td>\n      <td>223.859266</td>\n      <td>228.969271</td>\n      <td>228.739954</td>\n      <td>314.297710</td>\n      <td>176.851449</td>\n      <td>420.743959</td>\n      <td>288.931273</td>\n      <td>2003.651264</td>\n      <td>128.447452</td>\n      <td>399.235132</td>\n      <td>794.814741</td>\n      <td>952.486937</td>\n      <td>248.250476</td>\n      <td>52.854523</td>\n      <td>299.833511</td>\n      <td>144.853407</td>\n      <td>241.375319</td>\n      <td>357.982299</td>\n    </tr>\n    <tr>\n      <th>min</th>\n      <td>2240.000000</td>\n      <td>6794.000000</td>\n      <td>2448.000000</td>\n      <td>2131.000000</td>\n      <td>1252.500000</td>\n      <td>1460.500000</td>\n      <td>1050.500000</td>\n      <td>5006.000000</td>\n      <td>2273.000000</td>\n      <td>7922.000000</td>\n      <td>1199.561523</td>\n      <td>3516.000000</td>\n      <td>6370.000000</td>\n      <td>4673.000000</td>\n      <td>1527.500000</td>\n      <td>442.510010</td>\n      <td>900.000000</td>\n      <td>1171.000000</td>\n      <td>2168.000000</td>\n      <td>3328.000000</td>\n    </tr>\n    <tr>\n      <th>25%</th>\n      <td>2756.750000</td>\n      <td>7931.500000</td>\n      <td>2643.750000</td>\n      <td>2363.000000</td>\n      <td>1630.000000</td>\n      <td>2073.000000</td>\n      <td>1393.250000</td>\n      <td>5785.000000</td>\n      <td>2738.000000</td>\n      <td>11400.000000</td>\n      <td>1385.821991</td>\n      <td>4549.000000</td>\n      <td>7371.500000</td>\n      <td>6282.000000</td>\n      <td>1788.625000</td>\n      <td>519.009995</td>\n      <td>1333.299988</td>\n      <td>1384.875000</td>\n      <td>2413.250000</td>\n      <td>3911.125000</td>\n    </tr>\n    <tr>\n      <th>50%</th>\n      <td>2962.000000</td>\n      <td>8413.000000</td>\n      <td>2734.000000</td>\n      <td>2479.500000</td>\n      <td>1820.250000</td>\n      <td>2372.500000</td>\n      <td>1497.500000</td>\n      <td>6030.000000</td>\n      <td>2893.000000</td>\n      <td>12890.000000</td>\n      <td>1446.565674</td>\n      <td>4812.000000</td>\n      <td>7939.000000</td>\n      <td>7660.000000</td>\n      <td>1918.250000</td>\n      <td>587.179993</td>\n      <td>1440.400024</td>\n      <td>1524.250000</td>\n      <td>2511.000000</td>\n      <td>4124.500000</td>\n    </tr>\n    <tr>\n      <th>75%</th>\n      <td>3116.250000</td>\n      <td>8678.250000</td>\n      <td>2825.750000</td>\n      <td>2712.750000</td>\n      <td>1965.250000</td>\n      <td>2532.250000</td>\n      <td>1586.250000</td>\n      <td>6268.000000</td>\n      <td>3109.000000</td>\n      <td>14282.500000</td>\n      <td>1579.234680</td>\n      <td>5026.000000</td>\n      <td>8596.000000</td>\n      <td>7980.500000</td>\n      <td>2227.250000</td>\n      <td>606.942520</td>\n      <td>1660.300049</td>\n      <td>1612.000000</td>\n      <td>2823.000000</td>\n      <td>4358.500000</td>\n    </tr>\n    <tr>\n      <th>max</th>\n      <td>3688.000000</td>\n      <td>10930.000000</td>\n      <td>3439.000000</td>\n      <td>3163.000000</td>\n      <td>2264.000000</td>\n      <td>2784.000000</td>\n      <td>1820.500000</td>\n      <td>7204.000000</td>\n      <td>3667.000000</td>\n      <td>16915.000000</td>\n      <td>1823.720459</td>\n      <td>5338.000000</td>\n      <td>9910.000000</td>\n      <td>8426.000000</td>\n      <td>2449.000000</td>\n      <td>658.070007</td>\n      <td>2228.000000</td>\n      <td>1868.500000</td>\n      <td>3211.000000</td>\n      <td>4892.000000</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data.describe()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 364
    },
    "id": "qP_ey5ncNfmv",
    "outputId": "15310f68-22a7-48ed-e0f7-c56d0ef7daf2"
   },
   "outputs": [
    {
     "data": {
      "text/plain": "             ADM.L         AZN.L       BATS.L       BNZL.L       BRBY.L  \\\ncount    57.000000     57.000000    57.000000    57.000000    57.000000   \nmean   2185.973684  10508.228070  3466.447368  2814.035088  1629.964912   \nstd     173.503948    403.405858    81.319788   146.057225    60.288581   \nmin    1729.000000   9750.000000  3300.000000  2575.000000  1482.000000   \n25%    2157.000000  10244.000000  3426.000000  2671.000000  1586.000000   \n50%    2228.000000  10506.000000  3478.000000  2837.000000  1636.500000   \n75%    2267.000000  10800.000000  3528.500000  2919.000000  1678.500000   \nmax    2561.000000  11232.000000  3628.000000  3108.000000  1743.000000   \n\n             CCH.L        CPG.L        DCC.L       EXPN.L       FLTR.L  \\\ncount    57.000000    57.000000    57.000000    57.000000    57.000000   \nmean   1777.026316  1710.345614  5494.614035  2555.052632  8403.048070   \nstd     101.134432   237.407090   409.703802   152.041397  1243.157730   \nmin    1548.000000    16.700001  4889.000000  2285.000000    81.739998   \n25%    1712.000000  1691.500000  5162.000000  2424.000000  8200.000000   \n50%    1775.500000  1730.000000  5308.000000  2578.000000  8468.000000   \n75%    1837.500000  1796.500000  5710.000000  2661.000000  8944.000000   \nmax    1945.500000  1854.000000  6268.000000  2835.000000  9760.000000   \n\n             GSK.L        IHG.L       LSEG.L        NXT.L        REL.L  \\\ncount    57.000000    57.000000    57.000000    57.000000    57.000000   \nmean   1758.225729  4650.421053  7403.684211  6209.087719  2244.596491   \nstd      34.193845   256.656103   264.537545   239.741846    75.721260   \nmin    1684.302124  4193.000000  6738.000000  5764.000000  2081.000000   \n25%    1732.252319  4404.000000  7220.000000  5986.000000  2210.000000   \n50%    1758.242188  4695.000000  7390.000000  6148.000000  2265.000000   \n75%    1783.400024  4841.000000  7632.000000  6428.000000  2299.000000   \nmax    1815.863037  5150.000000  7894.000000  6686.000000  2375.000000   \n\n            SDR.L       SHEL.L        SSE.L        SVT.L       ULVR.L  \ncount   57.000000    57.000000    57.000000    57.000000    57.000000  \nmean   471.872281  2205.994736  1753.149123  2853.773158  3699.675439  \nstd     16.497939   148.555222    89.980502   401.790333   129.866329  \nmin    438.260010  1936.400024  1587.500000    28.070000  3451.500000  \n25%    458.320007  2044.000000  1677.500000  2794.000000  3609.500000  \n50%    469.200012  2225.000000  1761.000000  2872.000000  3690.500000  \n75%    486.200012  2338.000000  1821.000000  2996.000000  3802.500000  \nmax    505.239990  2440.000000  1920.000000  3148.000000  3943.000000  ",
      "text/html": "<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>ADM.L</th>\n      <th>AZN.L</th>\n      <th>BATS.L</th>\n      <th>BNZL.L</th>\n      <th>BRBY.L</th>\n      <th>CCH.L</th>\n      <th>CPG.L</th>\n      <th>DCC.L</th>\n      <th>EXPN.L</th>\n      <th>FLTR.L</th>\n      <th>GSK.L</th>\n      <th>IHG.L</th>\n      <th>LSEG.L</th>\n      <th>NXT.L</th>\n      <th>REL.L</th>\n      <th>SDR.L</th>\n      <th>SHEL.L</th>\n      <th>SSE.L</th>\n      <th>SVT.L</th>\n      <th>ULVR.L</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>count</th>\n      <td>57.000000</td>\n      <td>57.000000</td>\n      <td>57.000000</td>\n      <td>57.000000</td>\n      <td>57.000000</td>\n      <td>57.000000</td>\n      <td>57.000000</td>\n      <td>57.000000</td>\n      <td>57.000000</td>\n      <td>57.000000</td>\n      <td>57.000000</td>\n      <td>57.000000</td>\n      <td>57.000000</td>\n      <td>57.000000</td>\n      <td>57.000000</td>\n      <td>57.000000</td>\n      <td>57.000000</td>\n      <td>57.000000</td>\n      <td>57.000000</td>\n      <td>57.000000</td>\n    </tr>\n    <tr>\n      <th>mean</th>\n      <td>2185.973684</td>\n      <td>10508.228070</td>\n      <td>3466.447368</td>\n      <td>2814.035088</td>\n      <td>1629.964912</td>\n      <td>1777.026316</td>\n      <td>1710.345614</td>\n      <td>5494.614035</td>\n      <td>2555.052632</td>\n      <td>8403.048070</td>\n      <td>1758.225729</td>\n      <td>4650.421053</td>\n      <td>7403.684211</td>\n      <td>6209.087719</td>\n      <td>2244.596491</td>\n      <td>471.872281</td>\n      <td>2205.994736</td>\n      <td>1753.149123</td>\n      <td>2853.773158</td>\n      <td>3699.675439</td>\n    </tr>\n    <tr>\n      <th>std</th>\n      <td>173.503948</td>\n      <td>403.405858</td>\n      <td>81.319788</td>\n      <td>146.057225</td>\n      <td>60.288581</td>\n      <td>101.134432</td>\n      <td>237.407090</td>\n      <td>409.703802</td>\n      <td>152.041397</td>\n      <td>1243.157730</td>\n      <td>34.193845</td>\n      <td>256.656103</td>\n      <td>264.537545</td>\n      <td>239.741846</td>\n      <td>75.721260</td>\n      <td>16.497939</td>\n      <td>148.555222</td>\n      <td>89.980502</td>\n      <td>401.790333</td>\n      <td>129.866329</td>\n    </tr>\n    <tr>\n      <th>min</th>\n      <td>1729.000000</td>\n      <td>9750.000000</td>\n      <td>3300.000000</td>\n      <td>2575.000000</td>\n      <td>1482.000000</td>\n      <td>1548.000000</td>\n      <td>16.700001</td>\n      <td>4889.000000</td>\n      <td>2285.000000</td>\n      <td>81.739998</td>\n      <td>1684.302124</td>\n      <td>4193.000000</td>\n      <td>6738.000000</td>\n      <td>5764.000000</td>\n      <td>2081.000000</td>\n      <td>438.260010</td>\n      <td>1936.400024</td>\n      <td>1587.500000</td>\n      <td>28.070000</td>\n      <td>3451.500000</td>\n    </tr>\n    <tr>\n      <th>25%</th>\n      <td>2157.000000</td>\n      <td>10244.000000</td>\n      <td>3426.000000</td>\n      <td>2671.000000</td>\n      <td>1586.000000</td>\n      <td>1712.000000</td>\n      <td>1691.500000</td>\n      <td>5162.000000</td>\n      <td>2424.000000</td>\n      <td>8200.000000</td>\n      <td>1732.252319</td>\n      <td>4404.000000</td>\n      <td>7220.000000</td>\n      <td>5986.000000</td>\n      <td>2210.000000</td>\n      <td>458.320007</td>\n      <td>2044.000000</td>\n      <td>1677.500000</td>\n      <td>2794.000000</td>\n      <td>3609.500000</td>\n    </tr>\n    <tr>\n      <th>50%</th>\n      <td>2228.000000</td>\n      <td>10506.000000</td>\n      <td>3478.000000</td>\n      <td>2837.000000</td>\n      <td>1636.500000</td>\n      <td>1775.500000</td>\n      <td>1730.000000</td>\n      <td>5308.000000</td>\n      <td>2578.000000</td>\n      <td>8468.000000</td>\n      <td>1758.242188</td>\n      <td>4695.000000</td>\n      <td>7390.000000</td>\n      <td>6148.000000</td>\n      <td>2265.000000</td>\n      <td>469.200012</td>\n      <td>2225.000000</td>\n      <td>1761.000000</td>\n      <td>2872.000000</td>\n      <td>3690.500000</td>\n    </tr>\n    <tr>\n      <th>75%</th>\n      <td>2267.000000</td>\n      <td>10800.000000</td>\n      <td>3528.500000</td>\n      <td>2919.000000</td>\n      <td>1678.500000</td>\n      <td>1837.500000</td>\n      <td>1796.500000</td>\n      <td>5710.000000</td>\n      <td>2661.000000</td>\n      <td>8944.000000</td>\n      <td>1783.400024</td>\n      <td>4841.000000</td>\n      <td>7632.000000</td>\n      <td>6428.000000</td>\n      <td>2299.000000</td>\n      <td>486.200012</td>\n      <td>2338.000000</td>\n      <td>1821.000000</td>\n      <td>2996.000000</td>\n      <td>3802.500000</td>\n    </tr>\n    <tr>\n      <th>max</th>\n      <td>2561.000000</td>\n      <td>11232.000000</td>\n      <td>3628.000000</td>\n      <td>3108.000000</td>\n      <td>1743.000000</td>\n      <td>1945.500000</td>\n      <td>1854.000000</td>\n      <td>6268.000000</td>\n      <td>2835.000000</td>\n      <td>9760.000000</td>\n      <td>1815.863037</td>\n      <td>5150.000000</td>\n      <td>7894.000000</td>\n      <td>6686.000000</td>\n      <td>2375.000000</td>\n      <td>505.239990</td>\n      <td>2440.000000</td>\n      <td>1920.000000</td>\n      <td>3148.000000</td>\n      <td>3943.000000</td>\n    </tr>\n  </tbody>\n</table>\n</div>"
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data_test.describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "W2Wy0fxgsPrz"
   },
   "source": [
    "# 2. Prepare data by cleansing and feature engineering\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "VIbKQ2AzvIAl"
   },
   "source": [
    "Check null values. We will drop a row with null in this example for simplicity  but please cosnider replacing the values if possible."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Q7UlzUv7ZpHS",
    "outputId": "7d52ff9e-99e9-4458-bebb-1227b8801db6"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ADM.L     0\n",
      "AZN.L     0\n",
      "BATS.L    1\n",
      "BNZL.L    0\n",
      "BRBY.L    0\n",
      "CCH.L     0\n",
      "CPG.L     1\n",
      "DCC.L     1\n",
      "EXPN.L    1\n",
      "FLTR.L    0\n",
      "GSK.L     0\n",
      "IHG.L     1\n",
      "LSEG.L    0\n",
      "NXT.L     0\n",
      "REL.L     0\n",
      "SDR.L     0\n",
      "SHEL.L    1\n",
      "SSE.L     0\n",
      "SVT.L     0\n",
      "ULVR.L    0\n",
      "dtype: int64\n"
     ]
    }
   ],
   "source": [
    "print(data.isna().sum()) # null row for each stock"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "kF5eB8_LN2Hz",
    "outputId": "aeb3f233-7292-4132-c4a5-3abc62dd112a"
   },
   "outputs": [
    {
     "data": {
      "text/plain": "ADM.L     0\nAZN.L     0\nBATS.L    0\nBNZL.L    0\nBRBY.L    0\nCCH.L     0\nCPG.L     0\nDCC.L     0\nEXPN.L    0\nFLTR.L    0\nGSK.L     0\nIHG.L     0\nLSEG.L    0\nNXT.L     0\nREL.L     0\nSDR.L     0\nSHEL.L    0\nSSE.L     0\nSVT.L     0\nULVR.L    0\ndtype: int64"
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data_test.isna().sum() # check for test data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "TrqWiGbzcmuC",
    "outputId": "0d5d9a6a-7c93-4857-87a5-89da4e033616"
   },
   "outputs": [
    {
     "data": {
      "text/plain": "(459, 20)"
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data.dropna(inplace=True) # drop the rows\n",
    "data.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "PFC3Fv6-c1ii",
    "outputId": "88282590-12a5-4cf7-deba-f4367ee770fd"
   },
   "outputs": [
    {
     "data": {
      "text/plain": "(57, 20)"
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data_test.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "XYuopxu2M16X"
   },
   "source": [
    "## Feature Engineering"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "7GSqM12zuK8s"
   },
   "source": [
    "To make prediction, we use Prophet. See more at https://facebook.github.io/prophet/\n",
    "\n",
    "We make time series prediction based on the historical data between July 2020 and April 2022. The period to predict is May 2022 till late July 2022, which is the same period of the data in test set.\n",
    "\n",
    "The predicted values are transformed to be the average return rate for each stock. We can use it for one of the objectives to maximise the sum of return rates."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "id": "p0bAaw8huJxJ"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Collecting prophet\r\n",
      "  Downloading prophet-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (8.9 MB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m8.9/8.9 MB\u001B[0m \u001B[31m18.5 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m00:01\u001B[0m00:01\u001B[0m\r\n",
      "\u001B[?25hCollecting convertdate>=2.1.2\r\n",
      "  Downloading convertdate-2.4.0-py3-none-any.whl (47 kB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m47.9/47.9 kB\u001B[0m \u001B[31m13.5 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m\r\n",
      "\u001B[?25hRequirement already satisfied: pandas>=1.0.4 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from prophet) (1.5.1)\r\n",
      "Collecting holidays>=0.14.2\r\n",
      "  Downloading holidays-0.17.2-py3-none-any.whl (187 kB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m187.5/187.5 kB\u001B[0m \u001B[31m42.2 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m\r\n",
      "\u001B[?25hCollecting tqdm>=4.36.1\r\n",
      "  Downloading tqdm-4.64.1-py2.py3-none-any.whl (78 kB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m78.5/78.5 kB\u001B[0m \u001B[31m6.9 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m\r\n",
      "\u001B[?25hRequirement already satisfied: numpy>=1.15.4 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from prophet) (1.23.4)\r\n",
      "Requirement already satisfied: matplotlib>=2.0.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from prophet) (3.6.0)\r\n",
      "Collecting LunarCalendar>=0.0.9\r\n",
      "  Downloading LunarCalendar-0.0.9-py2.py3-none-any.whl (18 kB)\r\n",
      "Requirement already satisfied: setuptools>=42 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from prophet) (63.4.1)\r\n",
      "Collecting setuptools-git>=1.2\r\n",
      "  Downloading setuptools_git-1.2-py2.py3-none-any.whl (10 kB)\r\n",
      "Requirement already satisfied: wheel>=0.37.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from prophet) (0.37.1)\r\n",
      "Requirement already satisfied: python-dateutil>=2.8.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from prophet) (2.8.2)\r\n",
      "Collecting cmdstanpy>=1.0.4\r\n",
      "  Downloading cmdstanpy-1.0.8-py3-none-any.whl (81 kB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m81.2/81.2 kB\u001B[0m \u001B[31m23.5 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m\r\n",
      "\u001B[?25hCollecting pymeeus<=1,>=0.3.13\r\n",
      "  Downloading PyMeeus-0.5.12.tar.gz (5.8 MB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m5.8/5.8 MB\u001B[0m \u001B[31m25.7 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m00:01\u001B[0m00:01\u001B[0m\r\n",
      "\u001B[?25h  Preparing metadata (setup.py) ... \u001B[?25ldone\r\n",
      "\u001B[?25hCollecting korean-lunar-calendar\r\n",
      "  Downloading korean_lunar_calendar-0.3.1-py3-none-any.whl (9.0 kB)\r\n",
      "Collecting hijri-converter\r\n",
      "  Downloading hijri_converter-2.2.4-py3-none-any.whl (14 kB)\r\n",
      "Requirement already satisfied: pytz in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from LunarCalendar>=0.0.9->prophet) (2022.7)\r\n",
      "Collecting ephem>=3.7.5.3\r\n",
      "  Downloading ephem-4.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.8 MB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m1.8/1.8 MB\u001B[0m \u001B[31m36.6 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m00:01\u001B[0m\r\n",
      "\u001B[?25hRequirement already satisfied: contourpy>=1.0.1 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from matplotlib>=2.0.0->prophet) (1.0.5)\r\n",
      "Requirement already satisfied: pyparsing>=2.2.1 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from matplotlib>=2.0.0->prophet) (3.0.9)\r\n",
      "Requirement already satisfied: pillow>=6.2.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from matplotlib>=2.0.0->prophet) (9.2.0)\r\n",
      "Requirement already satisfied: cycler>=0.10 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from matplotlib>=2.0.0->prophet) (0.11.0)\r\n",
      "Requirement already satisfied: kiwisolver>=1.0.1 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from matplotlib>=2.0.0->prophet) (1.4.4)\r\n",
      "Requirement already satisfied: packaging>=20.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from matplotlib>=2.0.0->prophet) (21.3)\r\n",
      "Requirement already satisfied: fonttools>=4.22.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from matplotlib>=2.0.0->prophet) (4.37.4)\r\n",
      "Requirement already satisfied: six>=1.5 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from python-dateutil>=2.8.0->prophet) (1.16.0)\r\n",
      "Building wheels for collected packages: pymeeus\r\n",
      "  Building wheel for pymeeus (setup.py) ... \u001B[?25ldone\r\n",
      "\u001B[?25h  Created wheel for pymeeus: filename=PyMeeus-0.5.12-py3-none-any.whl size=732001 sha256=d1adb90fefb904067f35650132f35c1462d0335672a32dac671aa903f3d5a864\r\n",
      "  Stored in directory: /home/obm/.cache/pip/wheels/c2/3a/3d/11734e652782d3f823a08aae1c452e887eb16349750cca3f8a\r\n",
      "Successfully built pymeeus\r\n",
      "Installing collected packages: setuptools-git, pymeeus, korean-lunar-calendar, ephem, tqdm, hijri-converter, convertdate, LunarCalendar, holidays, cmdstanpy, prophet\r\n",
      "Successfully installed LunarCalendar-0.0.9 cmdstanpy-1.0.8 convertdate-2.4.0 ephem-4.1.4 hijri-converter-2.2.4 holidays-0.17.2 korean-lunar-calendar-0.3.1 prophet-1.1.1 pymeeus-0.5.12 setuptools-git-1.2 tqdm-4.64.1\r\n",
      "\r\n",
      "\u001B[1m[\u001B[0m\u001B[34;49mnotice\u001B[0m\u001B[1;39;49m]\u001B[0m\u001B[39;49m A new release of pip available: \u001B[0m\u001B[31;49m22.3\u001B[0m\u001B[39;49m -> \u001B[0m\u001B[32;49m22.3.1\u001B[0m\r\n",
      "\u001B[1m[\u001B[0m\u001B[34;49mnotice\u001B[0m\u001B[1;39;49m]\u001B[0m\u001B[39;49m To update, run: \u001B[0m\u001B[32;49mpip install --upgrade pip\u001B[0m\r\n"
     ]
    }
   ],
   "source": [
    "!pip install prophet"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "id": "9kmSFx0mzU4x"
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "ERROR:prophet.plot:Importing plotly failed. Interactive plots will not work.\n"
     ]
    }
   ],
   "source": [
    "from prophet import Prophet"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "id": "rgmtGCvLzb7B"
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/04vgcbiz.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/gc74709l.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=76984', 'data', 'file=/tmp/tmpygu7z4jl/04vgcbiz.json', 'init=/tmp/tmpygu7z4jl/gc74709l.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_model6kmbl5_0/prophet_model-20221222121305.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:05 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:05 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/egnks3q5.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/zc53ldjl.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=4851', 'data', 'file=/tmp/tmpygu7z4jl/egnks3q5.json', 'init=/tmp/tmpygu7z4jl/zc53ldjl.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_modelt3ce1t5_/prophet_model-20221222121305.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:05 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:05 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/4jh1cdyq.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/2ophhnw5.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=74384', 'data', 'file=/tmp/tmpygu7z4jl/4jh1cdyq.json', 'init=/tmp/tmpygu7z4jl/2ophhnw5.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_modelrndl262l/prophet_model-20221222121305.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:05 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:06 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/ujh1q3dy.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/q6mmlqra.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=44191', 'data', 'file=/tmp/tmpygu7z4jl/ujh1q3dy.json', 'init=/tmp/tmpygu7z4jl/q6mmlqra.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_modelp3ozgbn3/prophet_model-20221222121306.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:06 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:06 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/v5dtfk74.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/gimcyzzu.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=37452', 'data', 'file=/tmp/tmpygu7z4jl/v5dtfk74.json', 'init=/tmp/tmpygu7z4jl/gimcyzzu.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_model061sru16/prophet_model-20221222121306.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:06 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:06 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/46qo06w9.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/ydyysnd7.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=34946', 'data', 'file=/tmp/tmpygu7z4jl/46qo06w9.json', 'init=/tmp/tmpygu7z4jl/ydyysnd7.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_modelrb2ph4h_/prophet_model-20221222121307.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:07 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:07 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/rc02zqjt.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/uf68zjd1.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=82036', 'data', 'file=/tmp/tmpygu7z4jl/rc02zqjt.json', 'init=/tmp/tmpygu7z4jl/uf68zjd1.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_model1kwl2y2p/prophet_model-20221222121307.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:07 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:07 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/5arfh7nt.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/8pu6qdvw.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=99799', 'data', 'file=/tmp/tmpygu7z4jl/5arfh7nt.json', 'init=/tmp/tmpygu7z4jl/8pu6qdvw.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_model_pyb1lvj/prophet_model-20221222121307.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:07 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:07 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/yp65v73z.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/24pzm_qc.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=62171', 'data', 'file=/tmp/tmpygu7z4jl/yp65v73z.json', 'init=/tmp/tmpygu7z4jl/24pzm_qc.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_modelqwg728oa/prophet_model-20221222121308.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:08 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:08 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/cggclqmk.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/mueyhxbw.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=32988', 'data', 'file=/tmp/tmpygu7z4jl/cggclqmk.json', 'init=/tmp/tmpygu7z4jl/mueyhxbw.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_modelf_6cde_4/prophet_model-20221222121308.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:08 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:08 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/uznmh7uf.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/v45lk1z3.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=64220', 'data', 'file=/tmp/tmpygu7z4jl/uznmh7uf.json', 'init=/tmp/tmpygu7z4jl/v45lk1z3.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_model2dasxdge/prophet_model-20221222121309.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:09 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:09 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/ljfxbpjs.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/qncou6o9.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=82641', 'data', 'file=/tmp/tmpygu7z4jl/ljfxbpjs.json', 'init=/tmp/tmpygu7z4jl/qncou6o9.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_modelm45vqm5w/prophet_model-20221222121309.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:09 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:09 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/t34tly9d.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/rlzpak_k.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=73431', 'data', 'file=/tmp/tmpygu7z4jl/t34tly9d.json', 'init=/tmp/tmpygu7z4jl/rlzpak_k.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_model8_r60rxn/prophet_model-20221222121309.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:09 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:09 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/t_uo3pvq.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/7_uxupaw.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=11204', 'data', 'file=/tmp/tmpygu7z4jl/t_uo3pvq.json', 'init=/tmp/tmpygu7z4jl/7_uxupaw.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_model73zaxpd9/prophet_model-20221222121310.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:10 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:10 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/k1bop_0p.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/rrmosmdb.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=7755', 'data', 'file=/tmp/tmpygu7z4jl/k1bop_0p.json', 'init=/tmp/tmpygu7z4jl/rrmosmdb.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_modelkcqeechv/prophet_model-20221222121310.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:10 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:10 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/8hxrifmp.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/6noe7zja.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=10513', 'data', 'file=/tmp/tmpygu7z4jl/8hxrifmp.json', 'init=/tmp/tmpygu7z4jl/6noe7zja.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_model9wovgma5/prophet_model-20221222121310.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:10 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:10 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/r66qiiav.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/mvhy0iqc.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=2844', 'data', 'file=/tmp/tmpygu7z4jl/r66qiiav.json', 'init=/tmp/tmpygu7z4jl/mvhy0iqc.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_modeleae4auck/prophet_model-20221222121311.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:11 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:11 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/st8l6xs8.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/6a4su4kl.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=90262', 'data', 'file=/tmp/tmpygu7z4jl/st8l6xs8.json', 'init=/tmp/tmpygu7z4jl/6a4su4kl.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_modelj8g6b8k_/prophet_model-20221222121311.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:11 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:11 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/vff38_6j.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/62dfxntq.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=37483', 'data', 'file=/tmp/tmpygu7z4jl/vff38_6j.json', 'init=/tmp/tmpygu7z4jl/62dfxntq.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_modelxx52_4cf/prophet_model-20221222121312.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:12 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:12 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n",
      "INFO:prophet:Disabling yearly seasonality. Run prophet with yearly_seasonality=True to override this.\n",
      "INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/5oofh9xr.json\n",
      "DEBUG:cmdstanpy:input tempfile: /tmp/tmpygu7z4jl/nhgg7dt0.json\n",
      "DEBUG:cmdstanpy:idx 0\n",
      "DEBUG:cmdstanpy:running CmdStan, num_threads: None\n",
      "DEBUG:cmdstanpy:CmdStan args: ['/home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages/prophet/stan_model/prophet_model.bin', 'random', 'seed=86598', 'data', 'file=/tmp/tmpygu7z4jl/5oofh9xr.json', 'init=/tmp/tmpygu7z4jl/nhgg7dt0.json', 'output', 'file=/tmp/tmpygu7z4jl/prophet_modelmo3oct1k/prophet_model-20221222121312.csv', 'method=optimize', 'algorithm=lbfgs', 'iter=10000']\n",
      "12:13:12 - cmdstanpy - INFO - Chain [1] start processing\n",
      "INFO:cmdstanpy:Chain [1] start processing\n",
      "12:13:12 - cmdstanpy - INFO - Chain [1] done processing\n",
      "INFO:cmdstanpy:Chain [1] done processing\n"
     ]
    }
   ],
   "source": [
    "pred_return_rate = []\n",
    "pred_return_rate_mean = []\n",
    "i = 0 # current index of stock\n",
    "N = 20 # number of candidate stocks\n",
    "periods = data_test.shape[0] # number of days to forecast, same as data_test length\n",
    "\n",
    "for i in range(N): # for each stock\n",
    "  model = Prophet()\n",
    "  data['ds'] = data.index # index values (dates) as ds \n",
    "  data['y'] = data.iloc[:,i] # stock close price as y\n",
    "  stock_data = data[['ds','y']] # training data\n",
    "  model.fit(stock_data)\n",
    "  future = model.make_future_dataframe(periods=periods) # predict \n",
    "  forecast = model.predict(future)['yhat'] # predicted values stored in yhat column\n",
    "  return_rate = np.zeros(len(forecast)) # return rate to be stored in numpy array\n",
    "  for j in range(len(forecast)-1): \n",
    "    return_rate[j+1] = (forecast[j+1] - forecast[j])/forecast[j] # calculate the return rate per day\n",
    "  pred_return_rate.append(return_rate)\n",
    "  pred_return_rate_mean.append(np.mean(return_rate)) # predicted mean return rate for the stock\n",
    "  data.drop(columns=['ds','y'], inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "M7_Ijdiqg8Aj",
    "outputId": "338dc52d-17e7-4406-b362-1b6364ead9b3"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[-3.295120211693522e-05, 0.0004212352164733634, 0.0006443323333763341, 0.0006180876152479546, 0.0005054489754747284, -0.0011332381597290778, 0.0009594389542370872, -0.0004696525374264205, -0.0002568726047600979, -0.0010414517228963223, 0.00016392271944240828, 0.0006151834219843348, -9.028002715718702e-05, -6.64373367560175e-05, 0.0005966905208675229, -0.00019014641894963337, 0.0011606326569417041, 0.0005490521838582731, 0.0004985535894805963, -0.0005879920495425485]\n"
     ]
    }
   ],
   "source": [
    "print(pred_return_rate_mean)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "aPGYYaL-diMU",
    "outputId": "af9a60bf-8b6a-414e-fb85-1c1ec20298bb"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.00014317780640250334\n"
     ]
    }
   ],
   "source": [
    "print(np.mean(pred_return_rate_mean))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "F2vo0ZanMwXh"
   },
   "source": [
    "We also calculate actual return rates from the historical stock price data. We will use the results for the calculation of covariance that will be used for the second objective function to reduce the risk by diversify the portfolio items to avoid losing it altogether."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "id": "_yAkNF2qM2qP"
   },
   "outputs": [],
   "source": [
    "actual_return_rate = []\n",
    "actual_return_rate_mean = []\n",
    "N = 20 # number of candidate stocks\n",
    "for i in range(N): # for each stock\n",
    "  stock_data = data.iloc[:,i]\n",
    "  return_rate = np.zeros(len(stock_data)) # return rate to be stored in numpy array\n",
    "  for j in range(len(stock_data)-1): \n",
    "    return_rate[j+1] = (stock_data[j+1] - stock_data[j])/stock_data[j] # calculate the return rate per day\n",
    "  actual_return_rate.append(return_rate)\n",
    "  actual_return_rate_mean.append(np.mean(return_rate)) # predicted mean return rate for the stock"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "gmgGmzeNWMcr",
    "outputId": "75d34b1b-befe-446a-838e-1c527ee07f1d"
   },
   "outputs": [
    {
     "data": {
      "text/plain": "0.00039797518633514584"
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "actual_return_rate_mean_mean = np.mean(actual_return_rate_mean)\n",
    "actual_return_rate_mean_mean"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "TfEthgUNW6IM"
   },
   "source": [
    "Finally, we calculate the actual return rates on the test set for the final evaluation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "id": "UdmpvABkW_ic"
   },
   "outputs": [],
   "source": [
    "actual_return_rate_test = []\n",
    "actual_return_rate_mean_test = []\n",
    "N = 20 # number of candidate stocks\n",
    "for i in range(N): # for each stock\n",
    "  stock_data = data_test.iloc[:,i]\n",
    "  return_rate = np.zeros(len(stock_data)) # return rate to be stored in numpy array\n",
    "  for j in range(len(stock_data)-1): \n",
    "    return_rate[j+1] = (stock_data[j+1] - stock_data[j])/stock_data[j] # calculate the return rate per day\n",
    "  actual_return_rate_test.append(return_rate)\n",
    "  actual_return_rate_mean_test.append(np.mean(return_rate)) # predicted mean return rate for the stock"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "o0vfvXq6Xh7Z",
    "outputId": "7b1470e5-cd79-4749-f9f4-029dca6099c3"
   },
   "outputs": [
    {
     "data": {
      "text/plain": "0.26481830738480705"
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "actual_return_rate_mean_mean_test = np.mean(actual_return_rate_mean_test)\n",
    "actual_return_rate_mean_mean_test"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "UvVp2ir5sfZJ"
   },
   "source": [
    "#3. Formulate the cost functions and constraints to obtain a QUBO matrix\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "cqZdX2OCNDs3"
   },
   "source": [
    "## Constraint Terms"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "qCrz-zttNG-z"
   },
   "source": [
    "In our portfolio optimisation example, let's assume 1 constraint: the number of stock to be included in the portfolio.\n",
    "\n",
    "In this example, we will use PyQubo, a python library that that can conver the cost function to a quadratic unconstraintbinary optimisation matrix that can be sent to D-wave qunatum annealer and JIJ simulated quantum annealer.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "id": "Z7pdl2fZPdFw"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Collecting pyqubo\r\n",
      "  Downloading pyqubo-1.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (244 kB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m244.8/244.8 kB\u001B[0m \u001B[31m1.6 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0ma \u001B[36m0:00:01\u001B[0m\r\n",
      "\u001B[?25hCollecting Deprecated>=1.2.12\r\n",
      "  Downloading Deprecated-1.2.13-py2.py3-none-any.whl (9.6 kB)\r\n",
      "Collecting dimod<0.13,>=0.9.14\r\n",
      "  Downloading dimod-0.12.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (15.9 MB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m15.9/15.9 MB\u001B[0m \u001B[31m33.3 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m00:01\u001B[0m00:01\u001B[0m\r\n",
      "\u001B[?25hRequirement already satisfied: six>=1.15.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from pyqubo) (1.16.0)\r\n",
      "Collecting dwave-neal>=0.5.7\r\n",
      "  Downloading dwave_neal-0.6.0-py3-none-any.whl (8.7 kB)\r\n",
      "Requirement already satisfied: numpy>=1.17.3 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from pyqubo) (1.23.4)\r\n",
      "Requirement already satisfied: wrapt<2,>=1.10 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from Deprecated>=1.2.12->pyqubo) (1.14.1)\r\n",
      "Collecting dwave-samplers<2.0.0,>=1.0.0\r\n",
      "  Downloading dwave_samplers-1.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (6.1 MB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m6.1/6.1 MB\u001B[0m \u001B[31m27.1 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m00:01\u001B[0m00:01\u001B[0m\r\n",
      "\u001B[?25hRequirement already satisfied: networkx>=2.4.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from dwave-samplers<2.0.0,>=1.0.0->dwave-neal>=0.5.7->pyqubo) (2.8.7)\r\n",
      "Installing collected packages: dimod, Deprecated, dwave-samplers, dwave-neal, pyqubo\r\n",
      "Successfully installed Deprecated-1.2.13 dimod-0.12.3 dwave-neal-0.6.0 dwave-samplers-1.0.0 pyqubo-1.4.0\r\n",
      "\r\n",
      "\u001B[1m[\u001B[0m\u001B[34;49mnotice\u001B[0m\u001B[1;39;49m]\u001B[0m\u001B[39;49m A new release of pip available: \u001B[0m\u001B[31;49m22.3\u001B[0m\u001B[39;49m -> \u001B[0m\u001B[32;49m22.3.1\u001B[0m\r\n",
      "\u001B[1m[\u001B[0m\u001B[34;49mnotice\u001B[0m\u001B[1;39;49m]\u001B[0m\u001B[39;49m To update, run: \u001B[0m\u001B[32;49mpip install --upgrade pip\u001B[0m\r\n"
     ]
    }
   ],
   "source": [
    "!pip install pyqubo # https://pyqubo.readthedocs.io/en/latest/index.html"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "id": "9GfTXQ-PPuiK"
   },
   "outputs": [],
   "source": [
    "from pyqubo import Array, Constraint, Placeholder"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "id": "Rdza3plZQsQ1"
   },
   "outputs": [],
   "source": [
    "x = Array.create('x',shape=N, vartype='BINARY') # array takes binary 0 or 1 indicate to exclude or include a stock in the portfolio, which we need to optimise"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {
    "id": "k13isroNsuNo"
   },
   "outputs": [],
   "source": [
    "# number of stocks constraint\n",
    "num_stocks = 10 # specify the desired number of stocks\n",
    "h_const1 = (num_stocks - np.dot(x,x))**2 # MSE from the budget. This lead to x values that makes the portfolio as close as the budget"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "gt4jvfQsjNE_"
   },
   "source": [
    "## Cost Functions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ualaqpV7BXFY"
   },
   "source": [
    "We have set two cost functions to optimise our portfolio. One is to maximise the sum of predicted growth rates that we predicted in the feature engineering section. Another is to minimise the covariance between each of the stock items to be selected in the portfolio. We then add up two cost functions for QUBO."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {
    "id": "4efqOsnhT_Gh"
   },
   "outputs": [],
   "source": [
    "# cost function 1 to aim the highest return\n",
    "h_cost1 = 0\n",
    "h_cost1 -= np.dot(x,pred_return_rate_mean) # maximisation problem. negative to make it minimisation problem"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {
    "id": "V5bd5p0HeFAY"
   },
   "outputs": [],
   "source": [
    "# cost function 2 to balance the portfolio not to put all eggs in one basket\n",
    "# minimising the sum of covariance leads to the safer choice\n",
    "h_cost2 = 0\n",
    "for i in range(N):\n",
    "  for j in range(N):\n",
    "    h_cost2 += x[i]*x[j]*np.sum((actual_return_rate[i]-actual_return_rate_mean[i])*(actual_return_rate[j]-actual_return_rate_mean[j]))/len(actual_return_rate[i])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {
    "id": "3TCBs2OKRpJ2"
   },
   "outputs": [],
   "source": [
    "h_cost = h_cost1 + h_cost2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "pUh02l4qMA-2"
   },
   "source": [
    "## Prepare QUBO"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "wiwvWGQHCaxr"
   },
   "source": [
    "The problme formulation and representation in QUBO format are habled by PyQubo applications. In the lines below, we compile the model using the objective function that needs to be minimised, then define the constraint coefficient. to_qubo function generates a QUBO matrix in the dictionary type and an offset value which is an costanct value not required for the search for the minimum."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {
    "id": "Oz6z5bXgXIZl"
   },
   "outputs": [],
   "source": [
    "h = h_cost + Placeholder('l1')*Constraint(h_const1,label='num_stocks')\n",
    "model = h.compile()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {
    "id": "QtbTdpEja8pM"
   },
   "outputs": [],
   "source": [
    "feed_dict = {'l1': 2}\n",
    "qubo, offset = model.to_qubo(feed_dict=feed_dict)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "4NgrOS7_v_zB",
    "outputId": "b656b259-1a94-4612-bc8c-5c29139b420b"
   },
   "outputs": [
    {
     "data": {
      "text/plain": "200.0"
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "offset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "s1HR5k4L_3Rm"
   },
   "source": [
    "# 4. Sample optimisation results from a D-Wave solver\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "m84Qzh9ZDZrU"
   },
   "source": [
    "Install D-Wave's Ocean SDK to request the computation to the quantum annealer. EmbeddingComposite is used to embed the QUBO to the physical quantum anneler in D-Wave.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {
    "id": "Wgkgc61O_t7P"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Collecting dwave-ocean-sdk\r\n",
      "  Downloading dwave_ocean_sdk-6.1.1-py3-none-any.whl (8.5 kB)\r\n",
      "Requirement already satisfied: dimod==0.12.3 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from dwave-ocean-sdk) (0.12.3)\r\n",
      "Requirement already satisfied: dwave-neal==0.6.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from dwave-ocean-sdk) (0.6.0)\r\n",
      "Collecting dwave-system==1.18.0\r\n",
      "  Downloading dwave_system-1.18.0-py3-none-any.whl (103 kB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m103.5/103.5 kB\u001B[0m \u001B[31m1.1 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0ma \u001B[36m0:00:01\u001B[0m\r\n",
      "\u001B[?25hCollecting dwavebinarycsp==0.2.0\r\n",
      "  Downloading dwavebinarycsp-0.2.0-py3-none-any.whl (35 kB)\r\n",
      "Collecting pyqubo==1.3.1\r\n",
      "  Downloading pyqubo-1.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (236 kB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m236.5/236.5 kB\u001B[0m \u001B[31m2.8 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m00:01\u001B[0m00:01\u001B[0m\r\n",
      "\u001B[?25hCollecting dwave-greedy==0.3.0\r\n",
      "  Downloading dwave_greedy-0.3.0-py3-none-any.whl (10 kB)\r\n",
      "Collecting dwave-preprocessing==0.5.3\r\n",
      "  Downloading dwave_preprocessing-0.5.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.3 MB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m2.3/2.3 MB\u001B[0m \u001B[31m13.2 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0ma \u001B[36m0:00:01\u001B[0m\r\n",
      "\u001B[?25hCollecting dwave-hybrid==0.6.9\r\n",
      "  Downloading dwave_hybrid-0.6.9-py3-none-any.whl (74 kB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m74.7/74.7 kB\u001B[0m \u001B[31m18.9 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m\r\n",
      "\u001B[?25hCollecting dwave-cloud-client==0.10.3\r\n",
      "  Downloading dwave_cloud_client-0.10.3-py3-none-any.whl (111 kB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m111.4/111.4 kB\u001B[0m \u001B[31m27.5 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m\r\n",
      "\u001B[?25hCollecting dwave-inspector==0.3.0\r\n",
      "  Downloading dwave_inspector-0.3.0-py3-none-any.whl (26 kB)\r\n",
      "Collecting minorminer==0.2.9\r\n",
      "  Downloading minorminer-0.2.9-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (5.6 MB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m5.6/5.6 MB\u001B[0m \u001B[31m27.0 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0ma \u001B[36m0:00:01\u001B[0m\r\n",
      "\u001B[?25hRequirement already satisfied: dwave-samplers==1.0.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from dwave-ocean-sdk) (1.0.0)\r\n",
      "Collecting dwave-tabu==0.5.0\r\n",
      "  Downloading dwave_tabu-0.5.0-py3-none-any.whl (9.2 kB)\r\n",
      "Collecting dwave-networkx==0.8.12\r\n",
      "  Downloading dwave_networkx-0.8.12-py3-none-any.whl (97 kB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m97.5/97.5 kB\u001B[0m \u001B[31m26.8 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m\r\n",
      "\u001B[?25hCollecting penaltymodel==1.0.2\r\n",
      "  Downloading penaltymodel-1.0.2-py3-none-any.whl (36 kB)\r\n",
      "Requirement already satisfied: numpy<2.0.0,>=1.17.3 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from dimod==0.12.3->dwave-ocean-sdk) (1.23.4)\r\n",
      "Collecting homebase>=1.0\r\n",
      "  Downloading homebase-1.0.1-py2.py3-none-any.whl (11 kB)\r\n",
      "Collecting plucky>=0.4.3\r\n",
      "  Downloading plucky-0.4.3-py2.py3-none-any.whl (10 kB)\r\n",
      "Collecting diskcache>=5.2.1\r\n",
      "  Downloading diskcache-5.4.0-py3-none-any.whl (44 kB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m45.0/45.0 kB\u001B[0m \u001B[31m15.0 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m\r\n",
      "\u001B[?25hCollecting pydantic>=1.7.3\r\n",
      "  Downloading pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (13.6 MB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m13.6/13.6 MB\u001B[0m \u001B[31m41.6 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m00:01\u001B[0m00:01\u001B[0m\r\n",
      "\u001B[?25hRequirement already satisfied: click>=7.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from dwave-cloud-client==0.10.3->dwave-ocean-sdk) (8.1.3)\r\n",
      "Requirement already satisfied: requests[socks]>=2.18 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from dwave-cloud-client==0.10.3->dwave-ocean-sdk) (2.28.1)\r\n",
      "Requirement already satisfied: python-dateutil>=2.7 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from dwave-cloud-client==0.10.3->dwave-ocean-sdk) (2.8.2)\r\n",
      "Requirement already satisfied: networkx in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from dwave-hybrid==0.6.9->dwave-ocean-sdk) (2.8.7)\r\n",
      "Collecting Flask>=1.1.1\r\n",
      "  Downloading Flask-2.2.2-py3-none-any.whl (101 kB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m101.5/101.5 kB\u001B[0m \u001B[31m28.2 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m\r\n",
      "\u001B[?25hRequirement already satisfied: importlib-resources>=3.2.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from dwave-inspector==0.3.0->dwave-ocean-sdk) (5.2.0)\r\n",
      "Requirement already satisfied: scipy>=1.7.3 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from dwave-system==1.18.0->dwave-ocean-sdk) (1.9.3)\r\n",
      "Collecting rectangle-packer>=2.0.1\r\n",
      "  Downloading rectangle_packer-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (302 kB)\r\n",
      "\u001B[2K     \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m302.6/302.6 kB\u001B[0m \u001B[31m21.2 MB/s\u001B[0m eta \u001B[36m0:00:00\u001B[0m\r\n",
      "\u001B[?25hCollecting fasteners\r\n",
      "  Downloading fasteners-0.18-py3-none-any.whl (18 kB)\r\n",
      "Requirement already satisfied: six>=1.15.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from pyqubo==1.3.1->dwave-ocean-sdk) (1.16.0)\r\n",
      "Requirement already satisfied: Deprecated>=1.2.12 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from pyqubo==1.3.1->dwave-ocean-sdk) (1.2.13)\r\n",
      "Requirement already satisfied: wrapt<2,>=1.10 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from Deprecated>=1.2.12->pyqubo==1.3.1->dwave-ocean-sdk) (1.14.1)\r\n",
      "Requirement already satisfied: Werkzeug>=2.2.2 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from Flask>=1.1.1->dwave-inspector==0.3.0->dwave-ocean-sdk) (2.2.2)\r\n",
      "Requirement already satisfied: Jinja2>=3.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from Flask>=1.1.1->dwave-inspector==0.3.0->dwave-ocean-sdk) (3.0.3)\r\n",
      "Requirement already satisfied: importlib-metadata>=3.6.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from Flask>=1.1.1->dwave-inspector==0.3.0->dwave-ocean-sdk) (5.0.0)\r\n",
      "Collecting itsdangerous>=2.0\r\n",
      "  Downloading itsdangerous-2.1.2-py3-none-any.whl (15 kB)\r\n",
      "Requirement already satisfied: zipp>=3.1.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from importlib-resources>=3.2.0->dwave-inspector==0.3.0->dwave-ocean-sdk) (3.9.0)\r\n",
      "Requirement already satisfied: typing-extensions>=4.1.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from pydantic>=1.7.3->dwave-cloud-client==0.10.3->dwave-ocean-sdk) (4.4.0)\r\n",
      "Requirement already satisfied: charset-normalizer<3,>=2 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from requests[socks]>=2.18->dwave-cloud-client==0.10.3->dwave-ocean-sdk) (2.1.1)\r\n",
      "Requirement already satisfied: idna<4,>=2.5 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from requests[socks]>=2.18->dwave-cloud-client==0.10.3->dwave-ocean-sdk) (3.4)\r\n",
      "Requirement already satisfied: urllib3<1.27,>=1.21.1 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from requests[socks]>=2.18->dwave-cloud-client==0.10.3->dwave-ocean-sdk) (1.26.12)\r\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from requests[socks]>=2.18->dwave-cloud-client==0.10.3->dwave-ocean-sdk) (2022.9.24)\r\n",
      "Requirement already satisfied: PySocks!=1.5.7,>=1.5.6 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from requests[socks]>=2.18->dwave-cloud-client==0.10.3->dwave-ocean-sdk) (1.7.1)\r\n",
      "Requirement already satisfied: MarkupSafe>=2.0 in /home/obm/Prog/miniconda3/envs/qml/lib/python3.8/site-packages (from Jinja2>=3.0->Flask>=1.1.1->dwave-inspector==0.3.0->dwave-ocean-sdk) (2.1.1)\r\n",
      "Installing collected packages: rectangle-packer, plucky, homebase, pydantic, itsdangerous, fasteners, diskcache, penaltymodel, Flask, dwave-preprocessing, dwave-networkx, minorminer, dwavebinarycsp, dwave-tabu, dwave-greedy, dwave-cloud-client, pyqubo, dwave-system, dwave-inspector, dwave-hybrid, dwave-ocean-sdk\r\n",
      "  Attempting uninstall: pyqubo\r\n",
      "    Found existing installation: pyqubo 1.4.0\r\n",
      "    Uninstalling pyqubo-1.4.0:\r\n",
      "      Successfully uninstalled pyqubo-1.4.0\r\n",
      "Successfully installed Flask-2.2.2 diskcache-5.4.0 dwave-cloud-client-0.10.3 dwave-greedy-0.3.0 dwave-hybrid-0.6.9 dwave-inspector-0.3.0 dwave-networkx-0.8.12 dwave-ocean-sdk-6.1.1 dwave-preprocessing-0.5.3 dwave-system-1.18.0 dwave-tabu-0.5.0 dwavebinarycsp-0.2.0 fasteners-0.18 homebase-1.0.1 itsdangerous-2.1.2 minorminer-0.2.9 penaltymodel-1.0.2 plucky-0.4.3 pydantic-1.10.2 pyqubo-1.3.1 rectangle-packer-2.0.1\r\n",
      "\r\n",
      "\u001B[1m[\u001B[0m\u001B[34;49mnotice\u001B[0m\u001B[1;39;49m]\u001B[0m\u001B[39;49m A new release of pip available: \u001B[0m\u001B[31;49m22.3\u001B[0m\u001B[39;49m -> \u001B[0m\u001B[32;49m22.3.1\u001B[0m\r\n",
      "\u001B[1m[\u001B[0m\u001B[34;49mnotice\u001B[0m\u001B[1;39;49m]\u001B[0m\u001B[39;49m To update, run: \u001B[0m\u001B[32;49mpip install --upgrade pip\u001B[0m\r\n"
     ]
    }
   ],
   "source": [
    "!pip install dwave-ocean-sdk # https://docs.ocean.dwavesys.com/en/stable/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {
    "id": "9slCue62bZeJ"
   },
   "outputs": [],
   "source": [
    "from dwave.system import DWaveSampler, EmbeddingComposite\n",
    "endpoint = 'https://cloud.dwavesys.com/sapi'\n",
    "token = '***' # Please relace with your own token"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "s0K_qwB2GbeE"
   },
   "source": [
    "Available annelers depend on the geographic location and machine's online status. The list of solvers can be found on the D-Wave Leap dashboard. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "Gv8-8NEZb7Ve",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "sampler_dw = DWaveSampler(solver='Advantage_system4.1',token=token)\n",
    "sampler_qa = EmbeddingComposite(sampler_dw)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Y8UAX9eNnKLU",
    "outputId": "7baceb91-3cf6-4a9d-ff5b-24b49030157b",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "sampleset_qa = sampler_qa.sample_qubo(qubo,num_reads=10)\n",
    "records_qa = sampleset_qa.record\n",
    "print(records_qa)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "FcnZ5rnlGx_F"
   },
   "source": [
    "Above is the result of 10 samples from the solver. \n",
    "\n",
    "First list items are the optimised combination of the stock items, 1 indicates the stock is included in the portfolio. \n",
    "\n",
    "The second value is the energy state where the lowest number is the best solution. The number shows close to minus two hundred, because the offset value is not included. \n",
    "\n",
    "Third number is the number of occurance of the solution. We have 10 reads and each read has unique stock selections.\n",
    "\n",
    "The last number indicate \"chain break\" that the connection between qubits are broken then fixed by the software. Ideal soliution should have 0. chain break ratio.\n",
    "\n",
    "Using records returned from the D-Wave solver, we can validate if the constraint terms are complied."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "f8CAuYfAoHWY",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "portfolio_candidates_qa = []\n",
    "num_stock_counts_qa = []\n",
    "record_len = len(records_qa)\n",
    "for i in range(record_len):\n",
    "  portfolio = []\n",
    "  num_stock_count = 0\n",
    "  for j in range(len(records_qa[i][0])):\n",
    "    if records_qa[i][0][j] == 1:\n",
    "      portfolio.append(stocks[j])\n",
    "      num_stock_count += 1\n",
    "  portfolio_candidates_qa.append(portfolio)\n",
    "  num_stock_counts_qa.append(num_stock_count)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "fbhM3LzcpfTT",
    "outputId": "165ba3db-46bb-428d-f916-dc42364624bd",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "num_stock_counts_qa # number of stocks selected for the portfolio"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "QYi817jh0VkQ"
   },
   "source": [
    "In our example, 4 of the 10 solutions fit the requirement. We may adjust the coeffient value by increasing the penalty."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "-rApXs6WKAWS"
   },
   "source": [
    "Finally, we can select one best solution and display the sotck codes.\n",
    "We will use 7th record where there was no chain break."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "UAQWDqxUqwxw",
    "outputId": "aceef0e2-1117-435d-d650-0fd040bef612",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "best_portfolio_qa = portfolio_candidates_qa[7]\n",
    "print(best_portfolio_qa)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "_O2vKFSj0s5v"
   },
   "source": [
    "D-Wave has hybrid solver that uses classical computer and quantum annealer. \n",
    "The solver 'hybrid_binary_quadratic_model_version2' can have up to 1,000,000 variables.\n",
    "\n",
    "To try the hybrid solver, we need to import LeapHybridSampler library. The difference from the quantum solver are: \n",
    "\n",
    "- it does not require embed composite\n",
    "- only one sample record is returned (no num_reads parameter)\n",
    "- it consumes more QPU time (different conversion rate applies QPU: Hybrid 1:20)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Ual7XMplxg0G",
    "outputId": "de83b02c-6a00-4f90-8f99-9531d27927f4",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "from dwave.system import LeapHybridSampler\n",
    "sampler_dw_hybrid = LeapHybridSampler(solver='hybrid_binary_quadratic_model_version2',token=token)\n",
    "sampleset_qa_hybrid = sampler_dw_hybrid.sample_qubo(qubo)\n",
    "records_qa_hybrid = sampleset_qa_hybrid.record\n",
    "print(records_qa_hybrid)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "fryOP91hMKGd"
   },
   "source": [
    "# 5. Sample optimisation results from a simulated quantum annealing solver\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "-SfZxfL4Klhu"
   },
   "source": [
    "There is publicly available open source QA simulators provided by [OpenJIJ](https://www.openjij.org/). It can be used as an alternative the real quantum annerler. We can use the same QUBO prepared by PyQubo.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "mlbr7BGVk1lG",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "!pip install openjij"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "EdLM1IcYcm9D",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "from openjij import SQASampler\n",
    "sampler_jij = SQASampler()\n",
    "sampleset_sqa = sampler_jij.sample_qubo(qubo,num_reads=10)\n",
    "records_sqa = sampleset_sqa.record"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "NxaMuv38x_A3",
    "outputId": "8d0d94db-8a5c-4c90-ef08-4597d25eb1d9",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "print(records_sqa)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "0Z-fhb_1kv9g",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "portfolio_candidates_sqa = []\n",
    "num_stock_counts_sqa = []\n",
    "record_len = len(records_sqa)\n",
    "for i in range(record_len):\n",
    "  portfolio = []\n",
    "  num_stock_count = 0\n",
    "  for j in range(len(records_sqa[i][0])):\n",
    "    if records_sqa[i][0][j] == 1:\n",
    "      portfolio.append(stocks[j])\n",
    "      num_stock_count += 1\n",
    "  portfolio_candidates_sqa.append(portfolio)\n",
    "  num_stock_counts_sqa.append(num_stock_count)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "NjiaDZPpyReI",
    "outputId": "a83ee422-0972-4ff1-d396-67725c3f2bb3",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "num_stock_counts_sqa"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Vy2OQFnzxvyJ",
    "outputId": "a4ae761d-1870-46b8-9032-a404efff5806",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "best_portfolio_sqa = portfolio_candidates_sqa[-1]\n",
    "print(best_portfolio_sqa)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "Ai4w_FVITS_K"
   },
   "source": [
    "#6. Evaluate the outcomes\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "QFCaegY63nlS"
   },
   "source": [
    "We can select the best portfolio selection from QA and SQA samples."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "PJIhTJps3mdB",
    "outputId": "64de0934-9d2a-460c-82e4-43a7f8e694e4",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "portfolio_qa = records_qa[7][0] # best combination from QA hybdrid solver\n",
    "print(portfolio_qa)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "MCAoXGr03_2f",
    "outputId": "dbaccaf6-90d9-426d-8d45-985d625d6a5a",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "portfolio_sqa = records_sqa[-1][0] # best combination from SQA hybdrid solver\n",
    "print(portfolio_sqa)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "5bE_Q0uI_1cK"
   },
   "source": [
    "We compare the cost 1: increases the return rate.\n",
    "The more return is achieved if the number is smaller than the other."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Hp29zsrE4KRc",
    "outputId": "d59a9744-eba7-4966-a5d6-2e9a49a85c09",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "h_cost1_qa = 0\n",
    "h_cost1_qa -= np.dot(portfolio_qa,actual_return_rate_mean_test) # maximisation problem. negative to make it minimisation problem\n",
    "print(h_cost1_qa) # return rate factor by QA"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "3YG-1hsE43S3",
    "outputId": "5d33ea5d-e464-4b06-84f9-b70ba58cb73f",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "h_cost1_sqa = 0\n",
    "h_cost1_sqa -= np.dot(portfolio_sqa,actual_return_rate_mean_test) # maximisation problem. negative to make it minimisation problem\n",
    "print(h_cost1_sqa) # return rate cost by SQA"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "WzwJDZyTAaIO"
   },
   "source": [
    "We also compare the cost 2: decrease the risk.\n",
    "The more return is achieved if the number is smaller than the other."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "QKwaUtMq4gLk",
    "outputId": "6b2d73d8-03f3-48f1-b7e0-33fba0c97e7b",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "N= 20\n",
    "h_cost2_qa = 0\n",
    "for i in range(N):\n",
    "  for j in range(N):\n",
    "    h_cost2_qa += portfolio_qa[i]*portfolio_qa[j]*np.sum((actual_return_rate_test[i]-actual_return_rate_mean_test[i])*(actual_return_rate_test[j]-actual_return_rate_mean_test[j]))/len(actual_return_rate_test[i])\n",
    "print(h_cost2_qa)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Qkyixcj75Ckz",
    "outputId": "c0aefee2-7e0f-4338-acd4-5c31bb1de561",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "N= 20\n",
    "h_cost2_sqa = 0\n",
    "for i in range(N):\n",
    "  for j in range(N):\n",
    "    h_cost2_sqa += portfolio_sqa[i]*portfolio_sqa[j]*np.sum((actual_return_rate_test[i]-actual_return_rate_mean_test[i])*(actual_return_rate_test[j]-actual_return_rate_mean_test[j]))/len(actual_return_rate_test[i])\n",
    "print(h_cost2_sqa)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "dehQVD1PBGMm"
   },
   "source": [
    "We can check the costs in case we selected all 20 stocks as benchmark.\n",
    "The numbers will be divided by two for comparison."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "yqtwWnx_-eRR",
    "outputId": "283a93ca-c173-44bd-cc5c-6d2ad12f99d3",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "portfolio_all = np.ones(20)\n",
    "h_cost1_all = 0\n",
    "h_cost1_all -= np.dot(portfolio_all,actual_return_rate_mean_test) # maximisation problem. negative to make it minimisation problem\n",
    "print(h_cost1_all/2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "9pZVXJmD_Ar7",
    "outputId": "52004dac-c26b-43e0-da64-f0ff3cc60f37",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "N= 20\n",
    "h_cost2_all = 0\n",
    "for i in range(N):\n",
    "  for j in range(N):\n",
    "    h_cost2_all += portfolio_all[i]*portfolio_all[j]*np.sum((actual_return_rate_test[i]-actual_return_rate_mean_test[i])*(actual_return_rate_test[j]-actual_return_rate_mean_test[j]))/len(actual_return_rate_test[i])\n",
    "print(h_cost2_all/2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "NXOrt37ECBSF"
   },
   "source": [
    "Finally, the costs from Hybrid solver as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Uv_kWSvpBWkB",
    "outputId": "d401de8e-8a1c-4194-8568-4024f2451317",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "portfolio_qa_hybrid = records_qa_hybrid[0][0] # best combination from QA hybdrid solver\n",
    "print(portfolio_qa_hybrid)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "L0mN6mj4BjjH",
    "outputId": "b9acacd9-6282-4c8e-d8b2-8dc9e074746b",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "h_cost1_qa_hybrid = 0\n",
    "h_cost1_qa_hybrid -= np.dot(portfolio_qa_hybrid,actual_return_rate_mean_test) # maximisation problem. negative to make it minimisation problem\n",
    "print(h_cost1_qa_hybrid) # return rate factor by QA Hybrid Solver"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "owuDyxIGBxa-",
    "outputId": "2629f91f-afce-43ba-a68b-ac8a91b0af79",
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "N= 20\n",
    "h_cost2_qa_hybrid = 0\n",
    "for i in range(N):\n",
    "  for j in range(N):\n",
    "    h_cost2_qa_hybrid += portfolio_qa_hybrid[i]*portfolio_qa_hybrid[j]*np.sum((actual_return_rate_test[i]-actual_return_rate_mean_test[i])*(actual_return_rate_test[j]-actual_return_rate_mean_test[j]))/len(actual_return_rate_test[i])\n",
    "print(h_cost2_qa_hybrid)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "4vc2E4sVCSMj"
   },
   "source": [
    "With compariso to the benchmark, we can observe sample reocrds from QA/SQA/Hybdrid solvers as: \n",
    "\n",
    "- QA solver showed high-return but high-risks.\n",
    "- SQA solver recommended slightly lower return but minimum risk\n",
    "- Hybrid solver showed very similar costs for both return and risk as SQA\n",
    "\n",
    "Above is just a comparison of 1 sample record. We could analyse more sample records and visualsing the outcome. We could also tune hyper-parameters, like constraint coefficient."
   ]
  }
 ],
 "metadata": {
  "colab": {
   "authorship_tag": "ABX9TyPipMoNtOXQNeppvssSErAZ",
   "collapsed_sections": [],
   "include_colab_link": true,
   "name": "TDS_QA_Example_Portfolio_Optimisation.ipynb",
   "provenance": []
  },
  "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.9.16"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}