{ "cells": [ { "cell_type": "markdown", "id": "kju0kmcn62", "metadata": {}, "source": [ "# Portfolio Ensembles via Stacking\n", "\n", "This notebook shows how to blend multiple portfolio specifications into a\n", "single **ensemble allocation**. Three models -- sample mean-variance,\n", "shrinkage mean-variance, and CVaR -- are aligned by risk percentile and\n", "combined via both simple averaging and exposure stacking." ] }, { "cell_type": "code", "execution_count": 1, "id": "ywsxde2ipn", "metadata": { "execution": { "iopub.execute_input": "2026-03-30T19:38:39.164450Z", "iopub.status.busy": "2026-03-30T19:38:39.164193Z", "iopub.status.idle": "2026-03-30T19:38:39.811041Z", "shell.execute_reply": "2026-03-30T19:38:39.810733Z" } }, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from pyvallocation import (\n", " assemble_portfolio_ensemble,\n", " make_portfolio_spec,\n", ")" ] }, { "cell_type": "markdown", "id": "4toyw6xqjhw", "metadata": {}, "source": [ "## Load return data\n", "\n", "We use the last 750 daily returns from the bundled ETF prices." ] }, { "cell_type": "code", "execution_count": 2, "id": "ifw9uik4bb", "metadata": { "execution": { "iopub.execute_input": "2026-03-30T19:38:39.812363Z", "iopub.status.busy": "2026-03-30T19:38:39.812269Z", "iopub.status.idle": "2026-03-30T19:38:39.819984Z", "shell.execute_reply": "2026-03-30T19:38:39.819779Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Assets : ['DBC', 'GLD', 'SPY', 'TLT']\n", "Periods: 750\n" ] } ], "source": [ "from pathlib import Path\n", "\n", "_candidates = [\n", " Path(\"examples/ETF_prices.csv\"),\n", " Path(\"../examples/ETF_prices.csv\"),\n", " Path(\"../../examples/ETF_prices.csv\"),\n", " Path(\"../../../examples/ETF_prices.csv\"),\n", "]\n", "_csv = next((p for p in _candidates if p.exists()), None)\n", "if _csv is None:\n", " raise FileNotFoundError(\"ETF_prices.csv not found\")\n", "prices = pd.read_csv(_csv, index_col=\"Date\", parse_dates=True)\n", "prices = prices.dropna(how=\"all\").ffill()\n", "returns = prices.pct_change().dropna(how=\"any\").iloc[-750:]\n", "\n", "print(f\"Assets : {list(returns.columns)}\")\n", "print(f\"Periods: {len(returns)}\")" ] }, { "cell_type": "markdown", "id": "s2sbr6plsxq", "metadata": {}, "source": [ "## Define three portfolio specifications\n", "\n", "Each spec uses a different model but is aligned at the **median risk percentile**\n", "so that the selected portfolios are comparable:\n", "\n", "1. **Sample MV** -- sample mean and covariance.\n", "2. **Shrinkage MV** -- Jorion mean + Ledoit-Wolf covariance.\n", "3. **CVaR** -- scenario-based CVaR at alpha=5%." ] }, { "cell_type": "code", "execution_count": 3, "id": "cn6np2kj767", "metadata": { "execution": { "iopub.execute_input": "2026-03-30T19:38:39.820978Z", "iopub.status.busy": "2026-03-30T19:38:39.820918Z", "iopub.status.idle": "2026-03-30T19:38:39.822692Z", "shell.execute_reply": "2026-03-30T19:38:39.822485Z" } }, "outputs": [], "source": [ "selector_kwargs = {\"percentile\": 0.5, \"risk_label\": \"Volatility\"}\n", "\n", "specs = [\n", " make_portfolio_spec(\n", " \"Sample MV\",\n", " returns=returns,\n", " mean_estimator=\"sample\",\n", " cov_estimator=\"sample\",\n", " optimiser=\"mean_variance\",\n", " selector=\"risk_percentile\",\n", " selector_kwargs=selector_kwargs,\n", " ),\n", " make_portfolio_spec(\n", " \"Shrinkage MV\",\n", " returns=returns,\n", " mean_estimator=\"jorion\",\n", " cov_estimator=\"ledoit_wolf\",\n", " optimiser=\"mean_variance\",\n", " selector=\"risk_percentile\",\n", " selector_kwargs=selector_kwargs,\n", " ),\n", "]" ] }, { "cell_type": "code", "execution_count": 4, "id": "yare2i03vgs", "metadata": { "execution": { "iopub.execute_input": "2026-03-30T19:38:39.823566Z", "iopub.status.busy": "2026-03-30T19:38:39.823511Z", "iopub.status.idle": "2026-03-30T19:38:39.825050Z", "shell.execute_reply": "2026-03-30T19:38:39.824875Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Defined 3 specs: ['Sample MV', 'Shrinkage MV', 'CVaR']\n" ] } ], "source": [ "specs.append(\n", " make_portfolio_spec(\n", " \"CVaR\",\n", " returns=returns,\n", " use_scenarios=True,\n", " optimiser=\"cvar\",\n", " optimiser_kwargs={\"alpha\": 0.05},\n", " selector=\"risk_percentile\",\n", " selector_kwargs=selector_kwargs,\n", " ),\n", ")\n", "\n", "print(f\"Defined {len(specs)} specs: {[s.name for s in specs]}\")" ] }, { "cell_type": "markdown", "id": "m29g7pg83lm", "metadata": {}, "source": [ "## Assemble the ensemble\n", "\n", "`assemble_portfolio_ensemble` runs each spec, selects portfolios at the\n", "target risk percentile, and combines them via averaging and stacking." ] }, { "cell_type": "code", "execution_count": 5, "id": "kv4eofw16rs", "metadata": { "execution": { "iopub.execute_input": "2026-03-30T19:38:39.825956Z", "iopub.status.busy": "2026-03-30T19:38:39.825883Z", "iopub.status.idle": "2026-03-30T19:38:40.115916Z", "shell.execute_reply": "2026-03-30T19:38:40.115624Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "No constraints set; using default long-only, fully-invested.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "No constraints set; using default long-only, fully-invested.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Estimating mu and cov from scenarios (no explicit values provided).\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "No constraints set; using default long-only, fully-invested.\n" ] } ], "source": [ "result = assemble_portfolio_ensemble(\n", " specs,\n", " ensemble=(\"average\", \"stack\"),\n", " combine=\"selected\",\n", ")" ] }, { "cell_type": "markdown", "id": "rfwgv2smubg", "metadata": {}, "source": [ "## Print stacked vs averaged weights" ] }, { "cell_type": "code", "execution_count": 6, "id": "weymdt4a7n", "metadata": { "execution": { "iopub.execute_input": "2026-03-30T19:38:40.117140Z", "iopub.status.busy": "2026-03-30T19:38:40.117075Z", "iopub.status.idle": "2026-03-30T19:38:40.120306Z", "shell.execute_reply": "2026-03-30T19:38:40.120112Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "=== Selected portfolios (aligned by risk percentile) ===\n", " Sample MV Shrinkage MV CVaR\n", "DBC 0.1026 0.1027 0.0000\n", "GLD 0.4400 0.4399 0.5327\n", "SPY 0.2782 0.2781 0.2526\n", "TLT 0.1793 0.1792 0.2147\n", "\n", "=== Ensemble portfolios ===\n", "\n", "[average]\n", "DBC 0.0684\n", "GLD 0.4709\n", "SPY 0.2696\n", "TLT 0.1911\n", "Name: Average Exposure, dtype: float64\n", "\n", "[stack]\n", "DBC 0.0745\n", "GLD 0.4654\n", "SPY 0.2711\n", "TLT 0.1890\n", "Name: Exposure Stacking (L=3), dtype: float64\n" ] } ], "source": [ "print(\"=== Selected portfolios (aligned by risk percentile) ===\")\n", "print(result.selections.round(4))\n", "\n", "print(\"\\n=== Ensemble portfolios ===\")\n", "for name, w in result.ensembles.items():\n", " print(f\"\\n[{name}]\")\n", " print(w.round(4))" ] }, { "cell_type": "markdown", "id": "ww0i1b1qnpj", "metadata": {}, "source": [ "## Compare ensemble weights visually\n", "\n", "A grouped bar chart highlights how stacking dampens idiosyncratic tilts\n", "compared to simple averaging." ] }, { "cell_type": "code", "execution_count": 7, "id": "gokz8z3hecr", "metadata": { "execution": { "iopub.execute_input": "2026-03-30T19:38:40.121278Z", "iopub.status.busy": "2026-03-30T19:38:40.121217Z", "iopub.status.idle": "2026-03-30T19:38:40.173709Z", "shell.execute_reply": "2026-03-30T19:38:40.173500Z" } }, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxYAAAHqCAYAAACZcdjsAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAAQt5JREFUeJzt3Qd4VFX+//FvqCFAQk+ARboCShOEBQuICIhdVxFREBXXwoqLqLAqRVTsooKLZUFFEOyuoigiIE0R0BUL7KIgrEhTCU0CJPN/Pue3M/+ZMCHBk2SSzPv1PGMyd+7MnFuC53NPuQmBQCBgAAAAAOChlM+bAQAAAEAIFgAAAAC8ESwAAAAAeCNYAAAAAPBGsAAAAADgjWABAAAAwBvBAgAAAIA3ggUAAAAAbwQLAAAAAN4IFgDi0nPPPWcJCQm2fPnyXNft2rWre5Q0U6dOtWbNmlnZsmWtSpUqR/TeK664who0aBCxTPtz9OjR+VxKIH/o/Bw8eHCsiwGUaAQLAIeteOf0+OSTT2JdxGJLlfLwfZmcnGytW7e2hx9+2DIyMvLtezZt2uQq+l988cUhr61evdqVo3HjxvbMM8/Y008/bSXRrbfe6vZxnz59Yl2UuLNt2zYbMmSIC68VKlSwWrVqWYcOHey2226z3bt3h9abPn26jR8/PqZlBZA/yuTT5wAooe666y5r2LDhIcubNGkSk/KUFOXLl7dnn33W/b5jxw577bXXbNiwYfbZZ5/ZjBkz8i1YjBkzxrUstGnTJuK1+fPnW1ZWlj322GP5dix/++03K1Om6PxvJRAI2EsvveS2/+2337Zdu3ZZ5cqVY12suPDLL79Y+/btbefOnXbllVe6cPHzzz/bl19+aX//+9/tuuuus0qVKoWCxVdffWU33XRTrIsNwFPR+T8AgCLpjDPOcBUE5C9VwC+77LLQ8+uvv946duxoM2fOtEceecTq1Knzuz/74MGDLjQcztatW93PI+0CdTiJiYlWlCg8/fe//7WPPvrIevbsaa+//roNGDCgUMsQPBblypWzePKPf/zDNmzYYIsXL7bOnTtHvKawEW/7A4gXdIUC4GX9+vWuq8lDDz3kutOoa42uxp9wwgnu6nu4zZs328CBA+0Pf/iDW6d27dp27rnnus8I995779nJJ59sFStWdFeYzzzzTPv6668j1lE3Hl3xVOXlrLPOcr/XrVvXJk6c6F5ftWqVdevWzX1G/fr13VXRaPbu3Wt//vOfrXr16q5LUv/+/e3XX3/NdbvVZWnUqFHuar+2pV69eq7bze/tylSqVKnQOI7g/lDl/6qrrrLU1FRXaVd3qeeffz7H/a/uJMH9/+STT7pjINrnwW5X6uKmK/gqu9SsWfOQsRF677HHHus+RwHnhhtucK0quYk2xuLzzz934VT7VsfotNNOO6Qb3YEDB1zLStOmTd126licdNJJNmfOnIh11H3rp59+yvM+nTZtmrVo0cJOPfVU6969u3setGXLFhfu9L3ZrVmzxm3LhAkTQsu0/bqiruOs/aLjfv/990cEuJyOxTfffGP79++3kSNHWrt27SwlJcWdlzrH582bd8j368r+5Zdf7vaZgp/C0L/+9a/Q8QunffKnP/3JqlWr5vadLgL885//POx+0b7U+jovslOlX5+j1rOgJ554wp0PSUlJVrVqVfcdOf09BX333XdWunRp++Mf/3jIa9quYAjVOT9r1iz74YcfQudocOzOkeyzYOtby5Yt3WfrvO7Vq1euY6juvvtu97enbQTgjxYLAIeVnp5u27dvj1im//mr8hdOFQ11NVElXa8/8MADdsEFF9j333/vBgfLhRde6ALCX/7yF1d5UMVZlUeFg2BlQgOKVZHSFWZV3FTxV9cJVTRVSQ0fMJyZmekqraeccor7PlUcNThTFZDbb7/d+vXr58owadIkFxg6dep0SLcura/KmyrEqlDqu1TJ0dVubUc0qsScc845tmjRIrvmmmusefPmLsg8+uij9u9//9vefPPN37WvVRkT7Vt1K1Kla+3ata6MKvcrr7ziApUqueq7Hm7KlCm2b98+Vx5VZs8//3x3PFQx0zJVyERXj1XpfeGFF+yNN95w26sKf6tWrdzr2g+qbKsiru4qwX2ikKirz8FjmRc61vpeVSQVuvTep556ym3XggULXAtN8DvHjRtnV199teuDr8qtKoQrV660008/3a3z448/uv2scyN75ToaBTx1L7v55pvd8759+7qKtMJtWlqaC2tdunSxl19+ORSygtRqpErxRRdd5J7rHNS6KoPO76OOOsqWLFliI0aMcEEn+/iA7MdClXhtk7q+qRyDBg1yx0ZX9XWeL1u2LNRVTefW2Wef7ZZp/6sL0VtvvRW1pUX798QTT3SBevjw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AADeCBYAAADAEejatavddNNN+f65o0ePtjZt2lhxRbAAAABAiXHFFVe4rj3XXnvtIa/dcMMN7jWtkxfz58936zNdbt4QLAAAAFCi1KtXz2bMmGG//fZbaNm+ffts+vTpdtRRR8W0bCUZwQIAAAAlyvHHH+/Cxeuvvx5apt8VKtq2bRtalpWVZePGjbOGDRu6aVdbt25tr776qntt/fr1duqpp7rfq1atekhLh9576623WrVq1dzN5UaPHh1Rhg0bNti5555rlSpVsuTkZLv44otty5YtEevcd999lpqaapUrV7arrrrKhZ/ijGABAACAEufKK6+0KVOmhJ5PnjzZBg4cGLGOQsULL7xgkyZNsq+//tr++te/2mWXXWYLFixwweS1115z661Zs8Z++ukne+yxx0Lvff75561ixYr26aef2gMPPGB33XWXzZkzJxQ6FCp++eUX91la/v3331ufPn1C73/55ZddGLn33ntt+fLl7uZ0Tz75pBVn3CAPAAAAJY4CwogRI+yHH35wzxcvXuy6R2nchGRkZLhK/YcffmidOnVyyxo1amSLFi2yp556yrp06eJaI0Q3jqtSpUrE57dq1cpGjRrlfm/atKlNmDDB5s6da6effrr7uWrVKlu3bp0LKKIAc+yxx9pnn31mJ5xwgo0fP961Uughd999tytLcW61IFgAAACgxKlZs6adeeaZ9txzz1kgEHC/16hRI/T62rVrbe/evS4IhNu/f39Ed6mcKFiEq127trt7tXz77bcuUARDhbRo0cKFE72mYKGf2QeYK+DMmzfPiiuCBYB80WD4LIsn6+87M9ZFAADkoTvU4MGD3e8TJ06MeG337t3u56xZs6xu3boRr5UvXz7Xzy5btmzE84SEBNcFKp4xxgIAAAAlUq9evVwLxIEDB6xnz54Rr6kFQQFCg6ybNGkS8Qi2NJQrV879zMzMPKLvbd68uW3cuNE9gr755hs3ba2+N7iOxmeE++STT6w4o8UCAAAAJVLp0qVdl6Pg7+E0E9OwYcPcgG21NJx00kmWnp7uxmJoFqcBAwZY/fr1XUvEO++8Y71793YzR2mWp9x0797dWrZsaf369XNjKQ4ePGjXX3+9G7fRvn17t86QIUPcLFN6fuKJJ9q0adPcAHKN8yiuaLEAAABAiaWQoEc0Y8eOtTvvvNPNDqUWBLVwqGuUpp8V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woLFwYMH7YILLrD69eu7ZWq9ELVgZGRkWFpaWsR77rjjjoiB2MOGDbMZM2aEgsU999xjl1xyiY0ZMya0XuvWrQ/5brV8TJ061RYsWGDHHnusFTcECwAAACCswn/aaae5MNGzZ0/r0aOH/elPf7KqVavm+J6ZM2fa448/bt99953t3r3bBZPk5OTQ61988YVrxTichx9+2Pbs2WPLly933amKI8ZYAAAAAP9TunRpmzNnjr333nvWokULe+KJJ+yYY46xdevWRV1/6dKl1q9fP+vdu7e988479vnnn7suT+ruFKSWjtycfPLJlpmZaS+//LIVVwQLAAAAIIwGZp944omu65KCQrly5eyNN95wP1X5D7dkyRLXZer222+39u3bW9OmTe2HH36IWKdVq1a5DsTWeAqFmXvvvdceeughK47oCgUAAAD8z6effupCgLpA1apVyz3ftm2bNW/e3Pbt22fvv/++rVmzxs0WlZKS4oKEBnXPmDHDzfqkgdoKIeFGjRrlulc1btzYjbVQVykN3taYinCdO3d2y8844wwrU6aM3XTTTVac0GIBAAAA/I/GRnz88ceua5OmkNXAbI1/UGVf4yTULUotEzVr1rTFixfbOeecY3/9619t8ODB1qZNG9eCoelmw3Xt2tVeeeUV++c//+nW6datmy1btizq95900kkunOh71Q2rOEkIBAIBiyOaIkzpMj09PWJQDQA/DYbPsniyPvFSiyv5eOdaAPFBV/c1LqFhw4bu3g0onsfqSOrOtFgAAAAA8EawAAAAAOCNYAEAAADAG8ECAAAAgDeCBQAAAABvBAsAAAAUmKysrFgXAYV0jLhBHgAAAPKd7lJdqlQp27Rpk7vng57rjtYoOnTXif3797sbAOpY6RgV+2AxceJEe/DBB23z5s3WunVrdzMQ3dY8N7rDYd++fe3cc8+1N998s1DKCgAAgNypoqr7Ivz0008uXKDoSkpKsqOOOsods2IdLGbOnGlDhw61SZMmWceOHW38+PHWs2dPd6t03UY9J+vXr7dhw4bZySefXKjlBQAAQN7oCrgqrAcPHrTMzMxYFwdRlC5d2sqUKZMvrUkxDxaPPPKIuz36wIED3XMFDN3GfPLkyTZ8+PCo79GJ2a9fPxszZowtXLjQduzYUcilBgAAQF6owlq2bFn3QMkW08Hb6tO1YsUK6969+/8v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KCLB4pFHHrFBgwbZwIEDrUWLFjZp0iRLSkqyyZMnR13/scces169etktt9xizZs3t7Fjx9rxxx9vEyZMKPSyAwAAAPg/ZSyG9u/fbytWrLARI0aElpUqVcq6d+9uS5cujfoeLVcLRzi1cLz55ptR18/IyHCPoPT0dPdz586dVhxkZey1eLIzIWBxpZich3nBuVrCca4WW5yrxRfnagm3s3icq8E6cyAQKNrBYvv27ZaZmWmpqakRy/V89erVUd+zefPmqOtreTTjxo2zMWPGHLK8Xr16XmVHwUixOHNf3G1xiRF3R45ztdiKuyPHuVpsxd2Ru694bfGuXbssJSWl6AaLwqDWkPAWjqysLPvll1+sevXqlpCQENOy4dBErMC3ceNGS05OjnVxgBxxrqK44FxFccG5WnSppUKhok6dOrmuG9NgUaNGDStdurRt2bIlYrmep6WlRX2Plh/J+uXLl3ePcFWqVPEuOwqO/kHhHxUUB5yrKC44V1FccK4WTbm1VBSJwdvlypWzdu3a2dy5cyNaFPS8U6dOUd+j5eHry5w5c3JcHwAAAEDBi3lXKHVTGjBggLVv3946dOhg48ePtz179rhZoqR///5Wt25dN1ZChgwZYl26dLGHH37YzjzzTJsxY4YtX77cnn766RhvCQAAABC/Yh4s+vTpY9u2bbORI0e6Adht2rSx2bNnhwZob9iwwc0UFdS5c2ebPn263XHHHfa3v/3NmjZt6maEOu6442K4FcgP6rKm+5lk77oGFDWcqyguOFdRXHCulgwJgbzMHQUAAAAARfkGeQAAAACKP4IFAAAAAG8ECwAAAADeCBYAAAAAvBEsAAAoATRVO1DUNWrUyH7++edYFwMldbpZACgONIHeihUrbP369ZaQkGANGza0tm3but+BoqBVq1b2/PPP20knnRTrogA50r+hmZmZsS4GCggtFoiZnTt3ujutZ6d/cPQaUFTMmzfPGjdubB07drSLL77YLrroIjvhhBPcfXQ+/vjjWBcPcC688ELr1q2b3XLLLbZ///5YFwdAHOI+FoiJN954w2677Tb74osvLCkp6ZDm/OOPP94eeughO/vss2NWRkDWrl1rrVu3dqFiyJAh1qxZM9d68c0339jjjz9uy5cvty+//NI17wOx9sknn9iVV17pbiw7depU16oGFCU6N9WylpKSctj1zjnnnEIrE/IPwQIx0aNHD3fl9+qrr476+uTJk23mzJn2/vvvF3rZgHCDBw+2b7/91ubOnXvIa/rns3v37taiRQt74oknYlI+ILuMjAy74447bMKECXb66adbmTKRvZ5ff/31mJUNULDIjbqY0l2qeKIrFGLiq6++sq5du+b4+imnnGKrVq0q1DIB0cyfP99uuummHP/np9fUVQooSsFi69at7vzUVeHsDyDWNm/e7LpC5/QgVBRfDN5GTPz666928ODBHF8/cOCAWweItQ0bNljLli1zfP24446zH374oVDLBORkzpw5ritU7dq13WQDzZs3j3WRgAi5TXihULFlyxarU6dOoZUJ+YcWC8REgwYNXN/0nOi1+vXrF2qZgGh27959yDigcHpt7969hVomIJo///nPdtZZZ9mgQYNs6dKlhAoUSbn1wFePhnr16hVaeZC/aLFATFxwwQV2++23u/6/qamphzSRqn/wZZddFrPyAeE0UFvnZTTbt28v9PIA0SxevNgFCk1+ARRVAwYMsAoVKsS6GCggDN5GTOzatcs6derkupkoQBxzzDFu+erVq23atGnuaoVmN6lcuXKsi4o4p4GGarqP9k9lcDkDDVEUaIrZlStX2ttvv+1+P+2006xXr16xLhZwRP71r3+5cMy/qcUTwQIxk56ebiNGjHCzPwXHU1SpUsUuueQSu+eee6xq1aqxLiKQ5/ETdN1DrL366qvWp08fdzW4bNmy7n5A999/vw0bNizWRQPyjGBRvBEsEHM6BdWdRD9r1qzJnYxRrOzYscPeffddu/TSS2NdFMS5du3auRs3Tpw40UqXLm3jxo2zBx980H755ZdYFw0I0X1/Dkc9F/r27UuwKKYIFigydCpq2s7ffvvNOnfuTIsFigWurqGoqFSpkrvpaJMmTdxzdYeqWLGi/fjjj1arVq1YFw9w6F5asjF4GzG7yqu7GKs/8B//+Ed7+OGHrXfv3rZkyRL3uv4n+MEHH1irVq1iXVQAKBY0O1lycnLoebly5SwxMdHNbEawQFGxbt26WBcBBYhggZhQn1/NXqLZITTQUAMMdZVCy3Q149Zbb3WzRuk1AEDePPvss67lIkj3C3ruueesRo0aoWU33nhjjEoHmD3//POuDnC4abxRfNEVCjFRt25dmz59unXp0sU102sWqI8++ih0N+5ly5bZOeeck+MUn0BRQVcoFKX7A+U2Rk2vf//994VWJiA7jf/56aefaEUroWixQEzorppHH310KGSouT78hjhHHXWUbdu2LYYlBP7P448/ftjXFYyBomD9+vWxLgKQK65nl2wEC8REVlaWu2oRpN/Dr7QxMxSKikcffTTXdRSEgVhTV9Kff/7Z3X076IUXXrBRo0bZnj177LzzzrMnnnjCypcvH9NyAvw/vuQiWKBI9AXO3g9YN9ADigIGGqK4GDNmjJ166qmhYLFq1Sq76qqr7IorrrDmzZu7qWfr1Kljo0ePjnVREefUYyG3cME0ycUTYyxQZPsCC5U6xNq+ffvsww8/DFXWdFPHjIyM0OtlypSxu+66y3XnA2Kpdu3absKL9u3bu+eaAGPBggW2aNEi9/yVV15xrRfffPNNjEuKeKYJWsaPH28pKSmHXU+Tu6D4ocUCMUFfYBQXakmbNWtWKFhMmDDBjj32WHd34+DNnNLS0mzo0KExLini3a+//mqpqamh5woVZ5xxRui5bp63cePGGJUO+P8uueQSBm+XUKViXQDE9ziLyZMnuwrbcccdZy1btrRzzz3X9QmmIQ1FxbRp0+yaa66JWKYZzXQzRz3UvURXgoFYU6gItvLq5njB+wQFqYtp2bJlY1hCgPEVJR3BAjGh4HD22Wfb1Vdf7WbVUajQVWC1ZKg/8Pnnnx/rIgLO2rVr3fkZpC5PasoP6tChA11LUCToJqPDhw+3hQsXui57uk/AySefHHr9yy+/tMaNG8e0jAAXDks2ukIhZt1L9D+/uXPnusGG4XQ/C81eopaL/v37x6yMQPAu8eFjKrJPg6yWt/DXgVgZO3asXXDBBe7+QJoYQzci0923g9RC3KNHj5iWEdC/mSi5CBaIiZdeesn+9re/HRIqpFu3bu6qm7qgECwQa3/4wx/sq6++smOOOSbq67oKrHWAWNOseh9//LGlp6e7YBE+pbeoy174XbkBIL/RFQoxocpYr169cnxdAw51R2OgKHQvGTlypJsdKrvffvvNTfF55plnxqRsQDSabSd7qJBq1apFtGAAQH5julnEhP7n9sMPP7jpEaPZtGmTNWzYkC4mKBJ3iW/Tpo07ZwcPHhy6Y/yaNWvcDFG6B8vnn38eMRsPAADxiGCBmNDVtM2bN1vNmjVzrMzpRk6ZmZmFXjYgO820c91119mcOXNCAw81s8npp59uTz75pDVq1CjWRQQAIOYIFogJzaqj7k7ly5eP+rpaKmbPnk2wQJGiO8Fqlihp0qSJ61oCAAD+D8ECMTFw4MA8rTdlypQCLwsAAAD8ESwAAAAAeGNWKAAAAADeCBYAAAAAvBEsAAAAAHgjWAAAAADwRrAAAAAA4I1gAQAAAMAbwQIAAACAN4IFAAAAAPP1/wB/plk7I93aEwAAAABJRU5ErkJggg==", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ensemble_df = pd.DataFrame(\n", " {name: w for name, w in result.ensembles.items()}\n", ")\n", "\n", "fig, ax = plt.subplots(figsize=(8, 5))\n", "ensemble_df.plot.bar(ax=ax, width=0.7)\n", "ax.set_ylabel(\"Weight\")\n", "ax.set_title(\"Ensemble Portfolios: Average vs Stack\")\n", "ax.legend(title=\"Method\")\n", "fig.tight_layout()\n", "plt.show()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.9" } }, "nbformat": 4, "nbformat_minor": 5 }