From 449eb36a2fbcbcdf9f01c9e15805b504ea5946db Mon Sep 17 00:00:00 2001 From: HiradFakouri Date: Thu, 5 Mar 2026 13:50:46 +0000 Subject: [PATCH 1/2] Add requirements.txt for easier setup --- requirements.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..90d94b9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +numpy +pandas +scipy +matplotlib +qpsolvers \ No newline at end of file From 906e1c143fa3ea9367b2bdeb87da7153c3b77448 Mon Sep 17 00:00:00 2001 From: HiradFakouri Date: Thu, 5 Mar 2026 14:45:45 +0000 Subject: [PATCH 2/2] Add interactive Jupyter notebook UI (Stage 1 & 2) --- notebook/porqua_ui.ipynb | 329 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 notebook/porqua_ui.ipynb diff --git a/notebook/porqua_ui.ipynb b/notebook/porqua_ui.ipynb new file mode 100644 index 0000000..cdc7002 --- /dev/null +++ b/notebook/porqua_ui.ipynb @@ -0,0 +1,329 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "128a655d", + "metadata": {}, + "outputs": [], + "source": [ + "#Imports and path setup\n", + "import sys\n", + "import os\n", + "sys.path.insert(0, os.path.abspath('../src'))\n", + "\n", + "import io\n", + "import ipywidgets as widgets\n", + "import pandas as pd\n", + "import numpy as np\n", + "import plotly.graph_objects as go\n", + "\n", + "from optimization import MeanVariance, QEQW, LeastSquares, LAD\n", + "from optimization_data import OptimizationData\n", + "from constraints import Constraints" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "3f148839", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "9efc37a43e264d8398f501adef9cbf28", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "FileUpload(value=(), accept='.csv', description='Upload CSV')" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#File upload\n", + "uploader = widgets.FileUpload(\n", + " description='Upload CSV',\n", + " accept='.csv',\n", + " multiple=False\n", + ")\n", + "uploader" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "8f7540f8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loaded data: 6338 rows, 1 assets\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
NDDLWI
Index
01-01-1999-0.000072
04-01-19990.008686
05-01-19990.009618
06-01-19990.020803
07-01-1999-0.003080
\n", + "
" + ], + "text/plain": [ + " NDDLWI\n", + "Index \n", + "01-01-1999 -0.000072\n", + "04-01-1999 0.008686\n", + "05-01-1999 0.009618\n", + "06-01-1999 0.020803\n", + "07-01-1999 -0.003080" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#Load and preview data\n", + "try:\n", + " uploaded_file = uploader.value[0]\n", + "except IndexError:\n", + " print(\"No file uploaded. Please upload a CSV file to proceed.\")\n", + " raise\n", + "content = uploaded_file['content']\n", + "df = pd.read_csv(io.BytesIO(content), index_col=0, parse_dates=True)\n", + "print(f\"Loaded data: {df.shape[0]} rows, {df.shape[1]} assets\")\n", + "df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "51cb53e4", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "1e6c01f880e84e57a422c630b0af613b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "VBox(children=(Dropdown(description='Strategy:', options=('Least Squares', 'Weighted Least Squares', 'LAD', 'M…" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#Optimisation controls\n", + "strategy_dropdown = widgets.Dropdown(\n", + " options=['Least Squares', 'Weighted Least Squares', 'LAD', 'Mean Variance'],\n", + " value='Least Squares',\n", + " description='Strategy:',\n", + " style={'description_width': 'initial'}\n", + ")\n", + "\n", + "n_assets_slider = widgets.IntSlider(\n", + " value=10,\n", + " min=2,\n", + " max=24,\n", + " step=1,\n", + " description='Number of Assets:',\n", + " style={'description_width': 'initial'}\n", + ")\n", + "\n", + "risk_aversion_slider = widgets.FloatSlider(\n", + " value=1.0,\n", + " min=0.1,\n", + " max=5.0,\n", + " step=0.1,\n", + " description='Risk Aversion:',\n", + " style={'description_width': 'initial'}\n", + ")\n", + "\n", + "controls = widgets.VBox([strategy_dropdown, n_assets_slider, risk_aversion_slider])\n", + "controls" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "3521b24e", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d356443274274dfea207f5fe92007165", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "VBox(children=(Button(button_style='success', description='Run Optimisation', icon='check', style=ButtonStyle(…" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#Run optimisation and visualise results\n", + "\n", + "STRATEGY_MAP = {\n", + " 'Least Squares': LeastSquares,\n", + " 'LAD': LAD,\n", + " 'Mean Variance': MeanVariance,\n", + " 'Weighted Least Squares': None # requires extra params, coming soon\n", + "}\n", + "\n", + "def plot_weights(weights_dict):\n", + " \"\"\"Render an interactive Plotly pie chart of portfolio weights.\"\"\"\n", + " filtered = {k: v for k, v in weights_dict.items() if v > 0.001}\n", + " fig = go.Figure(data=[go.Pie(\n", + " labels=list(filtered.keys()),\n", + " values=list(filtered.values()),\n", + " hole=0.3,\n", + " textinfo='label+percent'\n", + " )])\n", + " fig.update_layout(title='Portfolio Allocation', showlegend=True)\n", + " fig.show()\n", + "\n", + "def on_run_clicked(b):\n", + " with output:\n", + " output.clear_output()\n", + "\n", + " strategy_name = strategy_dropdown.value\n", + " StrategyClass = STRATEGY_MAP.get(strategy_name)\n", + "\n", + " if StrategyClass is None:\n", + " print(f\"'{strategy_name}' is not yet supported.\")\n", + " return\n", + "\n", + " print(f\"Running {strategy_name} optimisation...\")\n", + "\n", + " n = n_assets_slider.value\n", + " selection = list(df.columns[:n])\n", + "\n", + " opt_data = OptimizationData(\n", + " return_series=df[selection].iloc[1:].astype(float),\n", + " bm_series=df.iloc[1:, 0].astype(float)\n", + " )\n", + "\n", + " constraints = Constraints(selection=selection)\n", + " constraints.add_budget()\n", + " constraints.add_box(box_type='LongOnly')\n", + "\n", + " if strategy_name == 'Mean Variance':\n", + " model = StrategyClass(risk_aversion=risk_aversion_slider.value, constraints=constraints)\n", + " else:\n", + " model = StrategyClass(constraints=constraints)\n", + "\n", + " model.set_objective(opt_data)\n", + " model.solve()\n", + "\n", + " weights = model.results['weights']\n", + " print(\"\\nPortfolio Weights:\")\n", + " for asset, w in weights.items():\n", + " print(f\" {asset}: {w:.4f}\")\n", + "\n", + " plot_weights(weights)\n", + "\n", + "run_button = widgets.Button(\n", + " description='Run Optimisation',\n", + " button_style='success',\n", + " icon='check'\n", + ")\n", + "output = widgets.Output()\n", + "run_button.on_click(on_run_clicked)\n", + "widgets.VBox([run_button, output])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6789c89d", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.10.5)", + "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.10.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}