diff --git a/Pyomo_integration_example/README.md b/Pyomo_integration_example/README.md
new file mode 100644
index 0000000..6446e33
--- /dev/null
+++ b/Pyomo_integration_example/README.md
@@ -0,0 +1,27 @@
+# Pyomo Integration Example
+
+This folder contains a Jupyter Notebook demonstrating how to integrate NVIDIA cuOpt as a solver backend for optimization problems modeled with Pyomo.
+
+## About Pyomo
+
+[Pyomo](https://www.pyomo.org/) is a Python-based open-source software package that supports a diverse set of optimization capabilities for formulating, solving, and analyzing optimization models.
+
+## Using cuOpt with Pyomo
+
+Pyomo supports cuOpt as a backend solver, allowing you to leverage GPU-accelerated optimization while using Pyomo's intuitive modeling syntax. This integration provides:
+
+- **Familiar API**: Use Pyomo's pythonic syntax for modeling
+- **GPU Acceleration**: Benefit from cuOpt's high-performance GPU-based solving
+- **Easy Solver Switching**: Compare different solvers by simply changing the solver parameter
+
+## Example Notebook
+
+### `p_median_problem.ipynb`
+
+This notebook demonstrates the classic p-median problem:
+- **Problem**: Choosing facility locations to minimize the weighted distance while meeting assignment constraints.
+- **Approach**: Model the problem using Pyomo and solve with cuOpt
+- **Features**:
+ - Setting up decision variables and constraints with Pyomo
+ - Solving with setting `solver = pyo.SolverFactory("cuopt")` parameter
+ - Analyzing and visualizing results
\ No newline at end of file
diff --git a/Pyomo_integration_example/p_median_problem.ipynb b/Pyomo_integration_example/p_median_problem.ipynb
new file mode 100644
index 0000000..f0df362
--- /dev/null
+++ b/Pyomo_integration_example/p_median_problem.ipynb
@@ -0,0 +1,429 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "fMaKbZo6Afgd"
+ },
+ "source": [
+ "# P-median Problem Example with Pyomo\n",
+ "\n",
+ "cuOpt is NVIDIA's GPU accelerated solver that delivers massive speedups for real-world LP, MIP, and VRP workloads.\n",
+ "\n",
+ "cuOpt seemlessly integrates with modeling languages. You can drop cuOpt into existing models built with pyomo, PuLP, cvxpy and AMPL, with minimal refactoring. Let's take a look at an example solving a simple MIP problem with cuOpt.\n",
+ "\n",
+ "To run this in Google Colab, download the notebook and upload it to Google Colab. Make sure you are running this on a T4 GPU.\n",
+ "\n",
+ "If you are running this in the cuOpt container, you are good to go!\n",
+ "\n",
+ "\n",
+ "## 1. Install Dependencies\n",
+ "\n",
+ "To make sure we are good to go, let's install Pyomo and cuOpt.\n",
+ "\n",
+ "__[Pyomo](https://github.com/Pyomo/pyomo)__ is a popular linear and mixed integer programming modeler written in Python.\n",
+ "\n",
+ "\n",
+ "If you are running this notebook in Google Colab, or elsewhere outside the container where cuOpt is not yet installed, uncomment the pip install command to install cuOpt."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import subprocess\n",
+ "import html\n",
+ "from IPython.display import display, HTML\n",
+ "\n",
+ "def check_gpu():\n",
+ " try:\n",
+ " result = subprocess.run([\"nvidia-smi\"], capture_output=True, text=True, timeout=5)\n",
+ " result.check_returncode()\n",
+ " lines = result.stdout.splitlines()\n",
+ " gpu_info = lines[2] if len(lines) > 2 else \"GPU detected\"\n",
+ " gpu_info_escaped = html.escape(gpu_info)\n",
+ " display(HTML(f\"\"\"\n",
+ "
\n",
+ "
✅ GPU is enabled
\n",
+ "
{gpu_info_escaped}\n",
+ "
\n",
+ " \"\"\"))\n",
+ " return True\n",
+ " except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError, IndexError) as e:\n",
+ " display(HTML(\"\"\"\n",
+ " \n",
+ "
⚠️ GPU not detected!
\n",
+ "
This notebook requires a GPU runtime.
\n",
+ " \n",
+ "
If running in Google Colab:
\n",
+ "
\n",
+ " - Click on Runtime → Change runtime type
\n",
+ " - Set Hardware accelerator to GPU
\n",
+ " - Then click Save and Runtime → Restart runtime.
\n",
+ "
\n",
+ " \n",
+ "
If running in Docker:
\n",
+ "
\n",
+ " - Ensure you have NVIDIA Docker runtime installed (
nvidia-docker2) \n",
+ " - Run container with GPU support:
docker run --gpus all ... \n",
+ " - Or use:
docker run --runtime=nvidia ... for older Docker versions \n",
+ " - Verify GPU access:
docker run --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi \n",
+ "
\n",
+ " \n",
+ "
Additional resources:
\n",
+ "
\n",
+ "
\n",
+ " \"\"\"))\n",
+ " return False\n",
+ "\n",
+ "check_gpu()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "T2L7jTld2Qqj"
+ },
+ "outputs": [],
+ "source": [
+ "!pip install pyomo==6.10.0"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "tFLzH53z2Qoc"
+ },
+ "outputs": [],
+ "source": [
+ "# Enable this in case you are running this in google colab or such places where cuOpt is not yet installed\n",
+ "#!pip uninstall -y cuda-python cuda-bindings cuda-core\n",
+ "#!pip install --upgrade --extra-index-url=https://pypi.nvidia.com cuopt-cu12 nvidia-nvjitlink-cu12 rapids-logger==0.1.19\n",
+ "#!pip install --upgrade --extra-index-url=https://pypi.nvidia.com cuopt-cu13 nvidia-nvjitlink-cu13 rapids-logger==0.1.19"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "VeTiQIUJEQbR"
+ },
+ "source": [
+ "## 2. Problem Setup\n",
+ "\n",
+ "A city wants to place p = 6 ambulance stations to serve 50 demand zones (neighborhood centroids). Every zone must be assigned to exactly one open station, and we want to minimize total (weighted) travel distance.\n",
+ "\n",
+ "This is the classic p-median: choose facility locations + assign demand points to them.\n",
+ "\n",
+ "- We have N=50 demand points (clients)\n",
+ "\n",
+ "- M=20 candidate facility sites\n",
+ "\n",
+ "- Open exactly p=6 sites\n",
+ "\n",
+ "- Assign every client to one open site\n",
+ "\n",
+ "- Objective: Minimize total weighted distance"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Generate problem parameters\n",
+ "\n",
+ "import random, math\n",
+ "random.seed(7)\n",
+ "\n",
+ "# sizes\n",
+ "N = 50 # clients\n",
+ "M = 20 # candidate sites\n",
+ "p = 6 # number of facilities to open\n",
+ "\n",
+ "clients = list(range(N))\n",
+ "sites = list(range(M))\n",
+ "\n",
+ "# coordinates\n",
+ "client_xy = {i: (random.uniform(0, 20), random.uniform(0, 20)) for i in clients}\n",
+ "site_xy = {j: (random.uniform(0, 20), random.uniform(0, 20)) for j in sites}\n",
+ "\n",
+ "# demand weights (e.g., expected calls)\n",
+ "w = {i: random.randint(1, 10) for i in clients}\n",
+ "\n",
+ "def euclid(a, b):\n",
+ " return math.hypot(a[0] - b[0], a[1] - b[1])\n",
+ "\n",
+ "# distance matrix d[i,j]\n",
+ "d = {(i, j): euclid(client_xy[i], site_xy[j]) for i in clients for j in sites}\n",
+ "\n",
+ "print(\"Example weights:\", list(w.items())[:5])\n",
+ "print(\"Example distance d[0,0]:\", d[(0,0)])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Decision variables\n",
+ "\n",
+ "- yj ∈ {0,1} : open station at site j\n",
+ "\n",
+ "- xij ∈ {0,1} : assign client i to site j\n",
+ "\n",
+ "\n",
+ "### Objective\n",
+ "Minimize weighted distance: \n",
+ "\n",
+ " min ∑i∈I ∑j∈J wij dij xij\n",
+ "\n",
+ "\n",
+ "### Constraints\n",
+ "1. Every client assigned to exactly one site:\n",
+ " ∑j xij = 1 ∀i\n",
+ " \n",
+ "2. Assign only to opened sites:\n",
+ " xij ≤ yj ∀i,j\n",
+ "\n",
+ "3. Open exactly p sites:\n",
+ " ∑j yj = p"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "0Xw4x3_W14TU"
+ },
+ "outputs": [],
+ "source": [
+ "# Model the problem\n",
+ "import pyomo.environ as pyo\n",
+ "\n",
+ "m = pyo.ConcreteModel()\n",
+ "\n",
+ "# Sets\n",
+ "m.I = pyo.Set(initialize=clients) # clients\n",
+ "m.J = pyo.Set(initialize=sites) # sites\n",
+ "\n",
+ "# Params\n",
+ "m.w = pyo.Param(m.I, initialize=w, within=pyo.PositiveIntegers)\n",
+ "m.d = pyo.Param(m.I, m.J, initialize=d, within=pyo.NonNegativeReals)\n",
+ "m.p = pyo.Param(initialize=p)\n",
+ "\n",
+ "# Decision variables\n",
+ "m.x = pyo.Var(m.I, m.J, domain=pyo.Binary) # assignment\n",
+ "m.y = pyo.Var(m.J, domain=pyo.Binary) # open facility\n",
+ "\n",
+ "# Objective\n",
+ "def obj_rule(m):\n",
+ " return sum(m.w[i] * m.d[i, j] * m.x[i, j] for i in m.I for j in m.J)\n",
+ "m.obj = pyo.Objective(rule=obj_rule, sense=pyo.minimize)\n",
+ "\n",
+ "# Constraints\n",
+ "def assign_once_rule(m, i):\n",
+ " return sum(m.x[i, j] for j in m.J) == 1\n",
+ "m.assign_once = pyo.Constraint(m.I, rule=assign_once_rule)\n",
+ "\n",
+ "def open_link_rule(m, i, j):\n",
+ " return m.x[i, j] <= m.y[j]\n",
+ "m.open_link = pyo.Constraint(m.I, m.J, rule=open_link_rule)\n",
+ "\n",
+ "def open_p_rule(m):\n",
+ " return sum(m.y[j] for j in m.J) == m.p\n",
+ "m.open_p = pyo.Constraint(rule=open_p_rule)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "OG02AqK2LpZ1"
+ },
+ "source": [
+ "## 3. Problem Solution\n",
+ "\n",
+ "Pyomo calls on the cuOpt solver, which finds the optimal site locations. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "UL0TM5pTLp_m"
+ },
+ "outputs": [],
+ "source": [
+ "# Solve the problem using CUOPT\n",
+ "solver = pyo.SolverFactory(\"cuopt\")\n",
+ "results = solver.solve(m)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%matplotlib inline\n",
+ "\n",
+ "# Print summary and plot results\n",
+ "\n",
+ "def print_summary(m):\n",
+ " print(\"\\n=============== Summary ===============\")\n",
+ " # opened sites\n",
+ " opened = [j for j in m.J if pyo.value(m.y[j]) > 0.5]\n",
+ " print(\"Opened sites:\", opened)\n",
+ " \n",
+ " # assignment per client\n",
+ " assign = {}\n",
+ " for i in m.I:\n",
+ " for j in m.J:\n",
+ " if pyo.value(m.x[i, j]) > 0.5:\n",
+ " assign[i] = j\n",
+ " break\n",
+ " \n",
+ " obj_val = pyo.value(m.obj)\n",
+ " total_weight = sum(w.values())\n",
+ " wavg_dist = sum(w[i] * d[(i, assign[i])] for i in clients) / total_weight\n",
+ " max_dist = max(d[(i, assign[i])] for i in clients)\n",
+ " \n",
+ " print(f\"Objective (weighted distance): {obj_val:.4f}\")\n",
+ " print(f\"Weighted avg distance: {wavg_dist:.4f}\")\n",
+ " print(f\"Max distance: {max_dist:.4f}\")\n",
+ " return opened, assign\n",
+ "\n",
+ "def plot_result(opened, assign):\n",
+ " import matplotlib.pyplot as plt\n",
+ " \n",
+ " plt.figure()\n",
+ " \n",
+ " # clients (size by weight)\n",
+ " cx = [client_xy[i][0] for i in clients]\n",
+ " cy = [client_xy[i][1] for i in clients]\n",
+ " cs = [25 + 10*w[i] for i in clients]\n",
+ " plt.scatter(cx, cy, s=cs, marker=\"o\", label=\"Clients\")\n",
+ " \n",
+ " # all candidate sites\n",
+ " sx = [site_xy[j][0] for j in sites]\n",
+ " sy = [site_xy[j][1] for j in sites]\n",
+ " plt.scatter(sx, sy, s=60, marker=\"^\", label=\"Candidate sites\")\n",
+ " \n",
+ " # opened sites\n",
+ " ox = [site_xy[j][0] for j in opened]\n",
+ " oy = [site_xy[j][1] for j in opened]\n",
+ " plt.scatter(ox, oy, s=200, marker=\"^\", label=\"Opened\", edgecolors=\"black\")\n",
+ " \n",
+ " # assignment lines\n",
+ " for i in clients:\n",
+ " j = assign[i]\n",
+ " plt.plot([client_xy[i][0], site_xy[j][0]],\n",
+ " [client_xy[i][1], site_xy[j][1]],\n",
+ " linewidth=0.5)\n",
+ " \n",
+ " plt.title(\"Pyomo p-Median: assignments to opened sites\")\n",
+ " plt.legend()\n",
+ " plt.show()\n",
+ "\n",
+ "opened, assign = print_summary(m)\n",
+ "plot_result(opened, assign)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 4. Extension: Capacitated p-median\n",
+ "\n",
+ "If each station can handle at most K total demand weight:\n",
+ "\n",
+ " ∑iwixij ≤ Kyj ∀j"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "K = 50\n",
+ "\n",
+ "# Add Constraint\n",
+ "def cap_rule(m, j):\n",
+ " return sum(m.w[i] * m.x[i, j] for i in m.I) <= K * m.y[j]\n",
+ "m.capacity = pyo.Constraint(m.J, rule=cap_rule)\n",
+ "\n",
+ "# Re-optimize\n",
+ "result = solver.solve(m)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%matplotlib inline\n",
+ "# Print summary and plot results\n",
+ "opened, assign = print_summary(m)\n",
+ "plot_result(opened, assign)"
+ ]
+ },
+ {
+ "cell_type": "raw",
+ "metadata": {
+ "vscode": {
+ "languageId": "raw"
+ }
+ },
+ "source": [
+ "SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n",
+ "\n",
+ "SPDX-License-Identifier: Apache-2.0\n",
+ "\n",
+ "Licensed under the Apache License, Version 2.0 (the \"License\");\n",
+ "you may not use this file except in compliance with the License.\n",
+ "You may obtain a copy of the License at\n",
+ "\n",
+ "http://www.apache.org/licenses/LICENSE-2.0\n",
+ "\n",
+ "Unless required by applicable law or agreed to in writing, software\n",
+ "distributed under the License is distributed on an \"AS IS\" BASIS,\n",
+ "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
+ "See the License for the specific language governing permissions and\n",
+ "limitations under the License.\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "accelerator": "GPU",
+ "colab": {
+ "gpuType": "T4",
+ "provenance": []
+ },
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "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.13.11"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/portfolio_optimization/QP_portfolio_optimization.ipynb b/portfolio_optimization/QP_portfolio_optimization.ipynb
new file mode 100644
index 0000000..93f009b
--- /dev/null
+++ b/portfolio_optimization/QP_portfolio_optimization.ipynb
@@ -0,0 +1,700 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "8055cf5b-4eb4-422a-b6fc-af885c4749c2",
+ "metadata": {},
+ "source": [
+ "# Portfolio Optimizer for Everyday Investors\n",
+ "\n",
+ "You have $10,000 in savings and want to invest across a few familiar assets. How do you balance higher expected return with the risk of large losses?\n",
+ "\n",
+ "This notebook builds a simple, realistic portfolio optimization workflow using **NVIDIA cuOpt's QP solver** for quadratic programming. We'll start with a small set of assets, compare equal-weight vs. optimized portfolios, and visualize the efficient frontier.\n",
+ "\n",
+ "**Key Concepts:**\n",
+ "- Mean-variance optimization (Markowitz portfolio theory)\n",
+ "- Efficient frontier visualization\n",
+ "- Interactive constraint exploration\n",
+ "\n",
+ "References:\n",
+ "- cuOpt LP/QP/MILP API Reference: https://docs.nvidia.com/cuopt/user-guide/latest/cuopt-python/lp-qp-milp/lp-qp-milp-api.html\n",
+ "- Portfolio Optimization: https://en.wikipedia.org/wiki/Portfolio_optimization"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fc98925c-ff8c-4767-bba0-16e193f97e23",
+ "metadata": {},
+ "source": [
+ "## Environment Setup and Installation\n",
+ "\n",
+ "### Install Required Dependencies"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "96ea34e7-e559-47ce-93f4-daf2f6c34281",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import subprocess\n",
+ "import html\n",
+ "from IPython.display import display, HTML\n",
+ "\n",
+ "def check_gpu():\n",
+ " try:\n",
+ " result = subprocess.run([\"nvidia-smi\"], capture_output=True, text=True, timeout=5)\n",
+ " result.check_returncode()\n",
+ " lines = result.stdout.splitlines()\n",
+ " gpu_info = lines[2] if len(lines) > 2 else \"GPU detected\"\n",
+ " gpu_info_escaped = html.escape(gpu_info)\n",
+ " display(HTML(f\"\"\"\n",
+ " \n",
+ "
✅ GPU is enabled
\n",
+ "
{gpu_info_escaped}\n",
+ "
\n",
+ " \"\"\"))\n",
+ " return True\n",
+ " except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError, IndexError) as e:\n",
+ " display(HTML(\"\"\"\n",
+ " \n",
+ "
⚠️ GPU not detected!
\n",
+ "
This notebook requires a GPU runtime.
\n",
+ " \n",
+ "
If running in Google Colab:
\n",
+ "
\n",
+ " - Click on Runtime → Change runtime type
\n",
+ " - Set Hardware accelerator to GPU
\n",
+ " - Then click Save and Runtime → Restart runtime.
\n",
+ "
\n",
+ " \n",
+ "
If running in Docker:
\n",
+ "
\n",
+ " - Ensure you have NVIDIA Docker runtime installed (
nvidia-docker2) \n",
+ " - Run container with GPU support:
docker run --gpus all ... \n",
+ " - Or use:
docker run --runtime=nvidia ... for older Docker versions \n",
+ " - Verify GPU access:
docker run --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi \n",
+ "
\n",
+ " \n",
+ "
Additional resources:
\n",
+ "
\n",
+ "
\n",
+ " \"\"\"))\n",
+ " return False\n",
+ "\n",
+ "check_gpu()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "18003f14-af20-44e8-b72a-381cc0312d3a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Enable this in case you are running this in google colab or such places where cuOpt is not yet installed\n",
+ "#!pip uninstall -y cuda-python cuda-bindings cuda-core\n",
+ "#!pip install --upgrade --extra-index-url=https://pypi.nvidia.com cuopt-cu12 nvidia-nvjitlink-cu12 rapids-logger==0.1.19\n",
+ "#!pip install --upgrade --extra-index-url=https://pypi.nvidia.com cuopt-cu13 nvidia-nvjitlink-cu13 rapids-logger==0.1.19"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "1a56950d-d08c-43cd-a135-29aa17830223",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "!pip install --extra-index-url https://pypi.nvidia.com \"numpy>=1.24.4\" \"pandas>=2.2.1\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e0987b2f-dbb0-4411-9a54-d1567247398c",
+ "metadata": {},
+ "source": [
+ "### Import Required Libraries"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "449f51ca-33a0-463e-902f-2a1eecbedee7",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "from cuopt.linear_programming.problem import (\n",
+ " Problem,\n",
+ " QuadraticExpression,\n",
+ " MINIMIZE,\n",
+ ")\n",
+ "\n",
+ "%config InlineBackend.figure_format = \"retina\"\n",
+ "\n",
+ "print(\"Imports ready (using cuOpt QP solver)\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b68072a1-8497-4d93-b275-ce7b3729673f",
+ "metadata": {},
+ "source": [
+ "## **Step 1:** Data Setup - A Small, Realistic Universe\n",
+ "\n",
+ "We'll work with a compact set of assets an everyday investor might recognize:\n",
+ "- Cash or money market\n",
+ "- US equity ETF proxy\n",
+ "- International equity ETF proxy\n",
+ "- Bond ETF proxy\n",
+ "- Real-asset/REIT or gold ETF proxy\n",
+ "\n",
+ "We'll simulate monthly returns with realistic annual return/volatility assumptions and correlations, then estimate the annualized mean returns and covariance matrix.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4beac288-fe86-40ee-9412-4b4004f233a3",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Simulate monthly returns with realistic assumptions\n",
+ "np.random.seed(7)\n",
+ "\n",
+ "assets = [\"Cash\", \"US Equity\", \"Intl Equity\", \"Bond\", \"REIT/Gold\"]\n",
+ "\n",
+ "annual_mean = np.array([0.02, 0.08, 0.075, 0.04, 0.06])\n",
+ "annual_vol = np.array([0.005, 0.16, 0.18, 0.06, 0.14])\n",
+ "\n",
+ "corr = np.array([\n",
+ " [1.00, 0.05, 0.05, 0.10, 0.05],\n",
+ " [0.05, 1.00, 0.80, -0.10, 0.55],\n",
+ " [0.05, 0.80, 1.00, -0.05, 0.50],\n",
+ " [0.10, -0.10, -0.05, 1.00, 0.00],\n",
+ " [0.05, 0.55, 0.50, 0.00, 1.00],\n",
+ "])\n",
+ "\n",
+ "monthly_mean = annual_mean / 12.0\n",
+ "monthly_vol = annual_vol / np.sqrt(12.0)\n",
+ "monthly_cov = np.outer(monthly_vol, monthly_vol) * corr\n",
+ "\n",
+ "n_months = 120\n",
+ "returns = np.random.multivariate_normal(monthly_mean, monthly_cov, size=n_months)\n",
+ "\n",
+ "# Estimate annualized mean and covariance from the simulated data\n",
+ "mean_returns = returns.mean(axis=0) * 12.0\n",
+ "cov_matrix = np.cov(returns, rowvar=False) * 12.0\n",
+ "\n",
+ "summary = pd.DataFrame(\n",
+ " {\n",
+ " \"Annualized Return\": mean_returns,\n",
+ " \"Annualized Volatility\": np.sqrt(np.diag(cov_matrix)),\n",
+ " },\n",
+ " index=assets,\n",
+ ")\n",
+ "\n",
+ "summary.style.format({\"Annualized Return\": \"{:.2%}\", \"Annualized Volatility\": \"{:.2%}\"})"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "68a3cddf-0a15-4dd2-9477-715b475b3c01",
+ "metadata": {},
+ "source": [
+ "## **Step 2:** Baseline Portfolio - Equal-Weight\n",
+ "\n",
+ "Before optimizing, let's compute the equal-weight portfolio. This gives a simple baseline to compare against the optimized portfolios."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d6342042-e364-46e9-971b-344e3850766a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def portfolio_stats(weights, mean_vec, cov_mat):\n",
+ " exp_return = float(weights @ mean_vec)\n",
+ " variance = float(weights @ cov_mat @ weights)\n",
+ " volatility = np.sqrt(variance)\n",
+ " return exp_return, volatility, variance\n",
+ "\n",
+ "n_assets = len(assets)\n",
+ "weights_equal = np.repeat(1.0 / n_assets, n_assets)\n",
+ "\n",
+ "ret_eq, vol_eq, var_eq = portfolio_stats(weights_equal, mean_returns, cov_matrix)\n",
+ "\n",
+ "baseline_df = pd.DataFrame(\n",
+ " {\n",
+ " \"Weight\": weights_equal,\n",
+ " \"Asset\": assets,\n",
+ " }\n",
+ ").set_index(\"Asset\")\n",
+ "\n",
+ "baseline_df, ret_eq, vol_eq\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "660bc66a-6cbc-41b6-a0da-9c7eae657c11",
+ "metadata": {},
+ "source": [
+ "## **Step 3:** The Optimization Problem\n",
+ "\n",
+ "We translate the investor's goals into a quadratic program (QP):\n",
+ "\n",
+ "**Decision variables:** Portfolio weights $w_i$ for each asset $i$.\n",
+ "\n",
+ "**Objective:** Minimize portfolio variance (risk):\n",
+ "$$\\min \\frac{1}{2} w^\\top \\Sigma w$$\n",
+ "where $\\Sigma$ is the covariance matrix.\n",
+ "\n",
+ "**Constraints:**\n",
+ "- Fully invested: $\\sum_i w_i = 1$\n",
+ "- Long-only (no shorting): $w_i \\geq 0$\n",
+ "- Optional: target minimum return $\\mu^\\top w \\geq r_{target}$\n",
+ "- Optional: max allocation per asset $w_i \\leq w_{max}$\n",
+ "\n",
+ "### cuOpt QP Implementation\n",
+ "\n",
+ "cuOpt's QP solver allows us to express quadratic objectives using `Variable * Variable` syntax.\n",
+ "For the portfolio variance $w^\\top \\Sigma w$, we construct it as:\n",
+ "$$\\sum_i \\sum_j w_i \\cdot \\Sigma_{ij} \\cdot w_j$$"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "04dd2427-69da-4a4e-80fd-2d1e5f796a74",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def solve_min_variance_qp(\n",
+ " cov_matrix,\n",
+ " mean_returns,\n",
+ " target_return=None,\n",
+ " max_weight=None,\n",
+ " min_safe_alloc=None,\n",
+ " safe_indices=None,\n",
+ "):\n",
+ " \"\"\"\n",
+ " Solve the minimum-variance portfolio problem using cuOpt QP solver.\n",
+ " \n",
+ " Parameters\n",
+ " ----------\n",
+ " cov_matrix : ndarray (n x n)\n",
+ " Annualized covariance matrix\n",
+ " mean_returns : ndarray (n,)\n",
+ " Annualized expected returns\n",
+ " target_return : float, optional\n",
+ " Minimum expected return constraint\n",
+ " max_weight : float, optional\n",
+ " Maximum weight for any single asset\n",
+ " min_safe_alloc : float, optional\n",
+ " Minimum combined allocation to safe assets\n",
+ " safe_indices : list of int, optional\n",
+ " Indices of safe assets (e.g., Cash, Bonds)\n",
+ " \n",
+ " Returns\n",
+ " -------\n",
+ " weights : ndarray\n",
+ " Optimal portfolio weights\n",
+ " portfolio_return : float\n",
+ " Expected return of the optimal portfolio\n",
+ " portfolio_vol : float\n",
+ " Volatility of the optimal portfolio\n",
+ " status : str\n",
+ " Solver status\n",
+ " \"\"\"\n",
+ " n = len(mean_returns)\n",
+ " \n",
+ " # Create cuOpt Problem\n",
+ " prob = Problem(\"Portfolio_Optimization\")\n",
+ " \n",
+ " # Decision variables: portfolio weights with bounds [0, upper_bound]\n",
+ " upper_bound = max_weight if max_weight is not None else 1.0\n",
+ " w = [prob.addVariable(lb=0.0, ub=upper_bound, name=f\"w_{i}\") for i in range(n)]\n",
+ " \n",
+ " # Build quadratic objective: minimize w' * cov_matrix * w\n",
+ " quad_expr = None\n",
+ " for i in range(n):\n",
+ " for j in range(n):\n",
+ " if abs(cov_matrix[i, j]) > 1e-12:\n",
+ " if(i==0 and j==0):\n",
+ " quad_expr = float(cov_matrix[i, j]) * w[i] * w[j]\n",
+ " else:\n",
+ " quad_expr += float(cov_matrix[i, j]) * w[i] * w[j]\n",
+ " \n",
+ " prob.setObjective(quad_expr, sense=MINIMIZE)\n",
+ " \n",
+ " # Constraint: Fully invested (sum of weights = 1)\n",
+ " sum_weights = sum(w)\n",
+ " prob.addConstraint(sum_weights == 1, name=\"fully_invested\")\n",
+ " \n",
+ " # Constraint: Minimum expected return\n",
+ " if target_return is not None:\n",
+ " expected_return_expr = sum(mean_returns[i] * w[i] for i in range(n))\n",
+ " prob.addConstraint(expected_return_expr >= target_return, name=\"min_return\")\n",
+ " \n",
+ " # Constraint: Minimum allocation to safe assets\n",
+ " if min_safe_alloc is not None and safe_indices is not None:\n",
+ " safe_sum = sum(w[i] for i in safe_indices)\n",
+ " prob.addConstraint(safe_sum >= min_safe_alloc, name=\"min_safe\")\n",
+ " \n",
+ " # Solve the problem\n",
+ " prob.solve()\n",
+ " \n",
+ " # Extract solution\n",
+ " weights = np.array([w[i].Value for i in range(n)])\n",
+ " \n",
+ " # Compute portfolio statistics\n",
+ " portfolio_return = float(mean_returns @ weights)\n",
+ " portfolio_vol = np.sqrt(float(weights @ cov_matrix @ weights))\n",
+ " \n",
+ " # Get solver status\n",
+ " status = \"optimal\" if prob.Status == 1 else f\"status_{prob.Status}\"\n",
+ " \n",
+ " return weights, portfolio_return, portfolio_vol, status\n",
+ "\n",
+ "print(\"Solver function defined (using cuOpt QP)\")\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "519655b6-c792-4d52-842c-933e62632696",
+ "metadata": {},
+ "source": [
+ "## **Step 4:** Minimum-Variance Portfolio (No Return Target)\n",
+ "\n",
+ "First, let's find the portfolio with the lowest possible risk, without any constraint on expected return. This is the leftmost point on the efficient frontier."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "16df1281-0a32-48b0-bcbb-4ffcf6fc48c7",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "weights_mv, ret_mv, vol_mv, status_mv = solve_min_variance_qp(cov_matrix, mean_returns)\n",
+ "\n",
+ "print(f\"Status: {status_mv}\")\n",
+ "print(f\"\\nMinimum-Variance Portfolio:\")\n",
+ "print(f\" Expected Return: {ret_mv:.2%}\")\n",
+ "print(f\" Volatility: {vol_mv:.2%}\")\n",
+ "print(f\"\\nWeights:\")\n",
+ "for asset, wt in zip(assets, weights_mv):\n",
+ " print(f\" {asset:<12}: {wt:6.1%}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "07ec7a0a-1521-4bea-94eb-38722be27218",
+ "metadata": {},
+ "source": [
+ "## **Step 5:** Efficient Frontier\n",
+ "\n",
+ "The efficient frontier shows all portfolios that offer the highest expected return for each level of risk. We trace it by solving QP problems for a range of target returns."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f8947c1d-a409-4b54-98f9-a171e845d961",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Compute efficient frontier by sweeping target returns\n",
+ "min_ret = ret_mv\n",
+ "max_ret = mean_returns.max()\n",
+ "target_returns = np.linspace(min_ret, max_ret, 20)\n",
+ "\n",
+ "frontier_vols = []\n",
+ "frontier_rets = []\n",
+ "frontier_weights = []\n",
+ "\n",
+ "for target in target_returns:\n",
+ " w, r, v, _ = solve_min_variance_qp(cov_matrix, mean_returns, target_return=target)\n",
+ " frontier_vols.append(v)\n",
+ " frontier_rets.append(r)\n",
+ " frontier_weights.append(w)\n",
+ "\n",
+ "frontier_vols = np.array(frontier_vols)\n",
+ "frontier_rets = np.array(frontier_rets)\n",
+ "\n",
+ "print(f\"Computed {len(frontier_rets)} points on the efficient frontier.\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ed46fea8-2410-4f87-9bcb-00099eda9300",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, ax = plt.subplots(figsize=(9, 6))\n",
+ "\n",
+ "# Efficient frontier\n",
+ "ax.plot(frontier_vols * 100, frontier_rets * 100, \"b-\", lw=2, label=\"Efficient Frontier\")\n",
+ "\n",
+ "# Individual assets\n",
+ "asset_vols = np.sqrt(np.diag(cov_matrix))\n",
+ "ax.scatter(asset_vols * 100, mean_returns * 100, s=80, c=\"gray\", marker=\"o\", zorder=3, label=\"Individual Assets\")\n",
+ "for i, asset in enumerate(assets):\n",
+ " ax.annotate(asset, (asset_vols[i] * 100 + 0.3, mean_returns[i] * 100), fontsize=9)\n",
+ "\n",
+ "# Equal-weight portfolio\n",
+ "ax.scatter(vol_eq * 100, ret_eq * 100, s=120, c=\"orange\", marker=\"s\", zorder=4, label=\"Equal-Weight\")\n",
+ "\n",
+ "# Minimum-variance portfolio\n",
+ "ax.scatter(vol_mv * 100, ret_mv * 100, s=120, c=\"green\", marker=\"^\", zorder=4, label=\"Min-Variance\")\n",
+ "\n",
+ "ax.set_xlabel(\"Volatility (%)\")\n",
+ "ax.set_ylabel(\"Expected Return (%)\")\n",
+ "ax.set_title(\"Efficient Frontier: Risk vs. Return (cuOpt QP)\")\n",
+ "ax.legend(loc=\"lower right\")\n",
+ "ax.grid(True, alpha=0.3)\n",
+ "plt.tight_layout()\n",
+ "plt.show()\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2abad3d4-f354-4ffb-b516-74819d6a967f",
+ "metadata": {},
+ "source": [
+ "## **Step 6:** Constrained Portfolio - 5% Target Return + Diversification\n",
+ "\n",
+ "Now let's add practical constraints:\n",
+ "- Target at least 5% annual return\n",
+ "- No single asset > 50% (diversification)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ba32fe1e-fed7-4220-a678-3ef709441f22",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "target_5pct = 0.05\n",
+ "max_wt = 0.50\n",
+ "\n",
+ "weights_5pct, ret_5pct, vol_5pct, status_5pct = solve_min_variance_qp(\n",
+ " cov_matrix, mean_returns,\n",
+ " target_return=target_5pct,\n",
+ " max_weight=max_wt,\n",
+ ")\n",
+ "\n",
+ "print(f\"Status: {status_5pct}\")\n",
+ "print(f\"\\nConstrained Portfolio (Target >= 5%, Max Weight 50%):\")\n",
+ "print(f\" Expected Return: {ret_5pct:.2%}\")\n",
+ "print(f\" Volatility: {vol_5pct:.2%}\")\n",
+ "print(f\"\\nWeights:\")\n",
+ "for asset, wt in zip(assets, weights_5pct):\n",
+ " print(f\" {asset:<12}: {wt:6.1%}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "241d2789-6009-4e8f-aa5c-90dd516a09bb",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, axes = plt.subplots(1, 2, figsize=(12, 5))\n",
+ "\n",
+ "# Bar chart of weights\n",
+ "x = np.arange(len(assets))\n",
+ "width = 0.35\n",
+ "\n",
+ "axes[0].bar(x - width/2, weights_equal * 100, width, label=\"Equal-Weight\", color=\"orange\")\n",
+ "axes[0].bar(x + width/2, weights_5pct * 100, width, label=\"Optimized (5%+ target)\", color=\"steelblue\")\n",
+ "axes[0].set_ylabel(\"Weight (%)\")\n",
+ "axes[0].set_xticks(x)\n",
+ "axes[0].set_xticklabels(assets, rotation=15)\n",
+ "axes[0].legend()\n",
+ "axes[0].set_title(\"Portfolio Allocation Comparison\")\n",
+ "\n",
+ "# Scatter: return vs vol\n",
+ "axes[1].scatter(vol_eq * 100, ret_eq * 100, s=150, c=\"orange\", marker=\"s\", label=f\"Equal-Weight ({ret_eq:.1%}, {vol_eq:.1%})\")\n",
+ "axes[1].scatter(vol_5pct * 100, ret_5pct * 100, s=150, c=\"steelblue\", marker=\"^\", label=f\"Optimized ({ret_5pct:.1%}, {vol_5pct:.1%})\")\n",
+ "axes[1].plot(frontier_vols * 100, frontier_rets * 100, \"k--\", alpha=0.4, label=\"Frontier\")\n",
+ "axes[1].set_xlabel(\"Volatility (%)\")\n",
+ "axes[1].set_ylabel(\"Expected Return (%)\")\n",
+ "axes[1].legend()\n",
+ "axes[1].grid(True, alpha=0.3)\n",
+ "\n",
+ "axes[1].set_title(\"Return vs. Volatility\")\n",
+ "\n",
+ "plt.tight_layout()\n",
+ "plt.show()\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d0bd5fc4-037e-4c02-b8cc-927051b61de0",
+ "metadata": {},
+ "source": [
+ "## **Step 7:** Interactive Exploration - Adjust Your Preferences\n",
+ "\n",
+ "Now let's explore how changing your preferences affects the optimal portfolio. You can adjust:\n",
+ "- **Target return**: How much growth do you want?\n",
+ "- **Maximum weight per asset**: Avoid over-concentration\n",
+ "- **Minimum safe allocation**: Keep a floor in Cash + Bonds"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "02c36d23-ae18-46fe-a4b3-c9bc65452424",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Interactive exploration function\n",
+ "# In Jupyter, uncomment the interact() call at the bottom to enable sliders\n",
+ "\n",
+ "def explore_portfolio(target_return_pct=5.0, max_weight_pct=60.0, min_safe_pct=10.0):\n",
+ " \"\"\"Explore portfolio with given parameters.\"\"\"\n",
+ " target_return = target_return_pct / 100.0\n",
+ " max_weight = max_weight_pct / 100.0\n",
+ " min_safe = min_safe_pct / 100.0\n",
+ " safe_indices = [0, 3] # Cash and Bond\n",
+ " \n",
+ " w, r, v, status = solve_min_variance_qp(\n",
+ " cov_matrix, mean_returns,\n",
+ " target_return=target_return,\n",
+ " max_weight=max_weight,\n",
+ " min_safe_alloc=min_safe,\n",
+ " safe_indices=safe_indices,\n",
+ " )\n",
+ " \n",
+ " fig, axes = plt.subplots(1, 2, figsize=(12, 4))\n",
+ " \n",
+ " # Weights\n",
+ " colors = [\"#2ecc71\" if i in safe_indices else \"#3498db\" for i in range(len(assets))]\n",
+ " axes[0].barh(assets, w * 100, color=colors)\n",
+ " axes[0].set_xlabel(\"Weight (%)\")\n",
+ " axes[0].set_xlim(0, 70)\n",
+ " axes[0].set_title(\"Optimal Allocation\")\n",
+ " axes[0].axvline(max_weight * 100, color=\"red\", linestyle=\"--\", label=f\"Max {max_weight_pct:.0f}%\")\n",
+ " axes[0].legend()\n",
+ " \n",
+ " # Frontier with current point\n",
+ " axes[1].plot(frontier_vols * 100, frontier_rets * 100, \"b-\", lw=2, alpha=0.5)\n",
+ " axes[1].scatter(v * 100, r * 100, s=200, c=\"red\", marker=\"*\", zorder=5, label=\"Your Portfolio\")\n",
+ " axes[1].scatter(vol_eq * 100, ret_eq * 100, s=80, c=\"orange\", marker=\"s\", zorder=4, label=\"Equal-Weight\")\n",
+ " axes[1].set_xlabel(\"Volatility (%)\")\n",
+ " axes[1].set_ylabel(\"Expected Return (%)\")\n",
+ " axes[1].set_title(\"Your Portfolio on the Frontier\")\n",
+ " axes[1].legend()\n",
+ " axes[1].grid(True, alpha=0.3)\n",
+ " \n",
+ " plt.tight_layout()\n",
+ " plt.show()\n",
+ " \n",
+ " print(f\"Status: {status}\")\n",
+ " print(f\"Expected Return: {r:.2%} | Volatility: {v:.2%}\")\n",
+ " print(f\"Safe Assets (Cash + Bond): {(w[0] + w[3]):.1%}\")\n",
+ "\n",
+ "# Demo with default parameters (static execution)\n",
+ "explore_portfolio(target_return_pct=5.0, max_weight_pct=60.0, min_safe_pct=10.0)\n",
+ "\n",
+ "# To enable interactive sliders in Jupyter, uncomment:\n",
+ "# from ipywidgets import interact, FloatSlider\n",
+ "# interact(\n",
+ "# explore_portfolio,\n",
+ "# target_return_pct=FloatSlider(value=5.0, min=2.0, max=8.0, step=0.5, description=\"Target Return %\"),\n",
+ "# max_weight_pct=FloatSlider(value=60.0, min=25.0, max=100.0, step=5.0, description=\"Max Weight %\"),\n",
+ "# min_safe_pct=FloatSlider(value=10.0, min=0.0, max=50.0, step=5.0, description=\"Min Safe %\"),\n",
+ "# )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e7b32527-5e24-4b71-afab-e075e80bfafb",
+ "metadata": {},
+ "source": [
+ "## **Step 8:** Interpretation - What It Means for You\n",
+ "\n",
+ "Let's put one scenario in plain English. Suppose you target a 6% annual return with at least 20% in safe assets."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "48f11500-0894-47a1-8dda-e5c06151379d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "target_scenario = 0.06\n",
+ "min_safe_scenario = 0.20\n",
+ "safe_indices = [0, 3]\n",
+ "\n",
+ "w_scenario, r_scenario, v_scenario, _ = solve_min_variance_qp(\n",
+ " cov_matrix, mean_returns,\n",
+ " target_return=target_scenario,\n",
+ " min_safe_alloc=min_safe_scenario,\n",
+ " safe_indices=safe_indices,\n",
+ ")\n",
+ "\n",
+ "print(\"=\" * 60)\n",
+ "print(\"YOUR RECOMMENDED PORTFOLIO\")\n",
+ "print(\"=\" * 60)\n",
+ "print(f\"\\nTarget: At least {target_scenario:.0%} annual return\")\n",
+ "print(f\"Constraint: Keep at least {min_safe_scenario:.0%} in Cash + Bonds\\n\")\n",
+ "\n",
+ "for asset, wt in zip(assets, w_scenario):\n",
+ " bar = \"█\" * int(wt * 40)\n",
+ " print(f\" {asset:<12} {wt:5.1%} {bar}\")\n",
+ "\n",
+ "print(f\"\\n→ Expected Return: {r_scenario:.2%}\")\n",
+ "print(f\"→ Volatility: {v_scenario:.2%}\")\n",
+ "print(f\"→ Safe Allocation: {(w_scenario[0] + w_scenario[3]):.1%}\")\n",
+ "\n",
+ "print(\"\\n\" + \"=\" * 60)\n",
+ "print(\"WHAT THIS MEANS\")\n",
+ "print(\"=\" * 60)\n",
+ "print(f\"\"\"\n",
+ "If you invest $10,000 according to this allocation:\n",
+ " - Cash: ${10000 * w_scenario[0]:,.0f}\n",
+ " - US Equity: ${10000 * w_scenario[1]:,.0f}\n",
+ " - Intl Equity: ${10000 * w_scenario[2]:,.0f}\n",
+ " - Bonds: ${10000 * w_scenario[3]:,.0f}\n",
+ " - REIT/Gold: ${10000 * w_scenario[4]:,.0f}\n",
+ "\n",
+ "You can expect ~{r_scenario:.1%} growth per year on average,\n",
+ "with a typical annual swing of about +/-{v_scenario:.1%}.\n",
+ "\"\"\")\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "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.13.11"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/portfolio_optimization/README.md b/portfolio_optimization/README.md
index 9bdf4a7..830e952 100644
--- a/portfolio_optimization/README.md
+++ b/portfolio_optimization/README.md
@@ -10,7 +10,11 @@ The portfolio optimization notebook solves a portfolio optimization problem wher
- The goal is to maximize the expected return of a portfolio while minimizing the risk.
-### 2. Advanced Portfolio Optimization
+### 2. Portfolio Optimization using QP
+
+- The aim is to balance expected return with the risk of losses
+
+### 3. Advanced Portfolio Optimization
For advanced portfolio optimization examples including:
- Efficient frontier construction
@@ -18,4 +22,4 @@ For advanced portfolio optimization examples including:
- Turnover optimization
- Mean-CVaR optimization with comprehensive workflows
-Please visit the **[NVIDIA Quantitative Portfolio Optimization repository](https://github.com/NVIDIA-AI-Blueprints/quantitative-portfolio-optimization)**
\ No newline at end of file
+Please visit the **[NVIDIA Quantitative Portfolio Optimization repository](https://github.com/NVIDIA-AI-Blueprints/quantitative-portfolio-optimization)**
diff --git a/portfolio_optimization/cvar_portfolio_optimization.ipynb b/portfolio_optimization/cvar_portfolio_optimization.ipynb
index ced1112..e07f534 100644
--- a/portfolio_optimization/cvar_portfolio_optimization.ipynb
+++ b/portfolio_optimization/cvar_portfolio_optimization.ipynb
@@ -663,7 +663,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "cuopt",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@@ -677,9 +677,9 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.12.12"
+ "version": "3.13.11"
}
},
"nbformat": 4,
- "nbformat_minor": 2
+ "nbformat_minor": 4
}