investing-quant
A linear programming (LP) based quantitative portfolio optimization system using the Rockafellar-Uryasev CVaR framework.
Purpose
This project implements a multi-constraint portfolio optimizer that allocates capital across a universe of financial instruments — including equity ETFs, fixed income ETFs, and individual stocks — spanning US, EU, and Emerging Markets regions.
The optimizer maximizes expected net return (after transaction fees) subject to a layered set of risk and diversification constraints:
- Budget constraint — fully invested capital
- Concentration cap — per-instrument maximum allocation
- Asset-class bands — minimum and maximum exposure to ETFs, bonds, and stocks
- Geographic bands — minimum and maximum exposure to US, EU, and Emerging Markets
- Beta ceiling — portfolio-level market sensitivity limit
- Weighted volatility ceiling — linear proxy for portfolio risk
- Liquidity constraint — weighted average days to full liquidation
- CVaR bound — tail risk limit via Conditional Value at Risk (95% confidence), linearized using Monte Carlo scenarios and the Rockafellar-Uryasev reformulation
The full mathematical formulation (constraints C-1 through C-9) is documented in docs/Investment Portfolio Optimization.pdf.
Disclaimer: This project is intended for educational and analytical purposes only. It does not constitute financial advice.
Two ways to run this: package or notebook
There are two independent, self-contained ways to run the optimizer, each with its own instrument universe and tuned parameters:
- The
iqCLI package (src/investing_quant/) — headless, reads its universe fromdata/universe.csv(39 instruments) and its parameters from a YAML config (configs/base.yaml). Has a test suite. This is the one to script against or automate. - The notebook (
notebooks/quant-lp-model.ipynb) — self-contained and Colab-ready, with its own hardcoded 43-instrument universe, sensitivity-sweep experiments, and matplotlib charts that the package does not (yet) reproduce.
They share the same model (ModelParameters, the LP constraints, CVaR scenario generation) but are maintained in parallel, not kept in sync — a correctness fix to one (like the C-9 slack-variable fix below) needs to be applied to the other by hand. A data/parameter change to one (e.g. editing data/universe.csv) does not affect the other.
Content Structure
investing-quant/
├── docs/
│ ├── Investment Portfolio Optimization.pdf # Technical/math reference (constraints C-1..C-9)
│ └── Investment Portfolio Optimization.pptx
├── notebooks/
│ └── quant-lp-model.ipynb # Self-contained notebook, its own 43-instrument universe
├── data/
│ └── universe.csv # Package's instrument universe (39 instruments)
├── configs/
│ └── base.yaml # Package's tuned model parameters
├── src/investing_quant/ # The iq package (see below)
├── tests/ # pytest suite for the package
├── scripts/
│ └── extract_universe.py # One-off script used to generate data/universe.csv
├── LICENSE
└── README.md
Notebook Sections (quant-lp-model.ipynb)
| Section | Description |
|---|---|
| 1 — Setup | Imports, solver configuration (GLPK via PuLP) |
| 2 — Instrument Universe | Definition of 43 instruments with expected return, beta, volatility, liquidity, and transaction fee |
| 3 — Model Parameters | ModelParameters dataclass: capital, concentration caps, asset-class/region bands, risk ceilings |
| 4 — CVaR Scenario Generation | Monte Carlo correlated monthly return scenarios |
| 5 — LP Formulation | Decision variables, objective function, and constraints C-1 through C-9 |
| 6 — Solve | GLPK solver execution via PuLP |
| 7 — Extract Results | Parsing optimal allocations, VaR/CVaR, and risk metrics from solver output |
| 8 — Portfolio Reports | Styled tables, allocation bar and pie charts, constraint compliance, P&L distribution |
| 9 — Sensitivity Analysis | CVaR limit, beta ceiling, and concentration cap sweep experiments |
| 10 — Custom Sandbox | Template for user-defined parameter experiments |
| 11 — Export | Writes portfolio_allocations.csv and portfolio_full_results.csv |
Requirements
- Python 3.11+
- GLPK system solver (used by both the package and the notebook, via PuLP)
# Debian / Ubuntu
sudo apt install glpk-utils
# macOS (Homebrew)
brew install glpk
# Windows
# Download from https://winglpk.sourceforge.net or use WSL
Running the package (iq CLI)
The package uses uv for dependency management.
uv sync # installs into .venv, using uv.lock
uv run iq --help
Validate the bundled universe and config, then solve:
uv run iq validate -u data/universe.csv -c configs/base.yaml
uv run iq optimize -c configs/base.yaml -u data/universe.csv
iq optimize prints the parameter summary, executive summary, active positions, allocation breakdown, constraint compliance, and risk analysis. Add -o DIR to also write DIR/portfolio_allocations.csv and DIR/portfolio_full_results.csv. Override individual parameters without editing the YAML:
uv run iq optimize -c configs/base.yaml -u data/universe.csv --budget 5000 --seed 7 -o out/
To use your own universe or parameters, edit data/universe.csv (same columns as the shipped file) and/or configs/base.yaml, or point -u/-c at different files entirely.
Run the test suite:
uv run pytest # fast suite
uv run pytest -m slow # + the full-size golden regression test
uv run ruff check .
Running the notebook
1. Create and activate a virtual environment
python -m venv .venv
source .venv/bin/activate # Linux / macOS
.venv\Scripts\activate # Windows
2. Install Python dependencies
pip install numpy pandas polars pulp matplotlib jupyterlab
(Alternatively, uv sync --extra notebook installs these into the package's own .venv.)
3. Launch Jupyter and open the notebook
jupyter lab notebooks/quant-lp-model.ipynb
4. Configure the model
Edit the ModelParameters dataclass in Section 3 to set:
W— total capital to allocateU_default— per-instrument concentration cap- Asset-class and region allocation bands
- Risk ceilings:
beta_max,sigma_max,liquidity_max_days,cvar_max
5. Run all cells
Execute all notebook cells in order. The solver will find the optimal allocation and render portfolio reports.
6. Export results
Section 11 writes two CSV files to the working directory:
portfolio_allocations.csv— active positions with weights and risk metricsportfolio_full_results.csv— full instrument universe with solver outputs
7. Sensitivity experiments (optional)
Sections 9 and 10 contain pre-built and template experiments for exploring how the optimal portfolio changes as individual parameters vary. (These sweeps, and the notebook's matplotlib charts, are not yet reproduced by the iq package.)
Contact
Ramiro Chaparro ramiro@datup.ai
License
MIT License — Copyright 2026 Datup AI
See LICENSE for full terms.
Metadata
Release files for investing-quant 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| investing_quant-0.1.0.tar.gz | 1.1 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| investing_quant-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.1 MB
Release files / investing_quant-0.1.0.tar.gz
| Download URL | investing_quant-0.1.0.tar.gz |
|---|---|
| Size | 1.1 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2c8ae144da59bb254161ed11cc53c6f09da5b439eb5009de4b96f327e79e69b5
|
|
BLAKE2b-256 checksum How to use checksums |
252c49460642f6c7509e2b0f6a6a315ebd5c63d98ca63a93696a5eedde2abdd6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"43","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / investing_quant-0.1.0-py3-none-any.whl
| Download URL | investing_quant-0.1.0-py3-none-any.whl |
|---|---|
| Size | 18.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f0d97054365d7875a6c94ae50aa8119da666b87d2ed9e7d9ccc1e3c1cb53c1b0
|
|
BLAKE2b-256 checksum How to use checksums |
1408fdf29f25f5784932f919c922a4499af890acdeeb9ed6c05d35d9d7e7d326
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"43","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|