Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
329 changes: 329 additions & 0 deletions notebook/porqua_ui.ipynb
Original file line number Diff line number Diff line change
@@ -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": [
"<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>NDDLWI</th>\n",
" </tr>\n",
" <tr>\n",
" <th>Index</th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>01-01-1999</th>\n",
" <td>-0.000072</td>\n",
" </tr>\n",
" <tr>\n",
" <th>04-01-1999</th>\n",
" <td>0.008686</td>\n",
" </tr>\n",
" <tr>\n",
" <th>05-01-1999</th>\n",
" <td>0.009618</td>\n",
" </tr>\n",
" <tr>\n",
" <th>06-01-1999</th>\n",
" <td>0.020803</td>\n",
" </tr>\n",
" <tr>\n",
" <th>07-01-1999</th>\n",
" <td>-0.003080</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"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
}
5 changes: 5 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
numpy
pandas
scipy
matplotlib
qpsolvers