A modular Python risk engine for equity portfolio VaR, Expected Shortfall, backtesting, stress testing, optimization, transaction costs, and automated HTML reporting.
The project is designed as a recruiter-facing quantitative engineering portfolio: notebook
research has been extracted into reusable package modules, tested with deterministic unit and
regression tests, and exposed through a reproducible portfolio-risk CLI.
- Data validation for price, return, and portfolio-weight inputs.
- Historical and Gaussian Value at Risk and Expected Shortfall.
- Rolling VaR forecasts with Kupiec and Christoffersen validation tests.
- GARCH volatility modeling and Monte Carlo portfolio simulation.
- Deterministic market shocks, volatility shocks, covariance stress, and correlation stress.
- Static portfolio optimization: equal weight, minimum variance, maximum Sharpe, and minimum ES.
- Rolling portfolio backtesting with weight drift, turnover, gross/net returns, and cost drag.
- Local HTML risk report generation with CSV, JSON, and PNG artifacts.
- Unified CLI with typed YAML configuration, run manifests, logs, and overwrite protection.
Market data snapshot
-> data validation
-> return calculation
-> risk estimation
-> VaR backtesting
-> stress testing
-> portfolio optimization
-> rolling portfolio backtesting
-> report generation
The package lives under src/portfolio_risk_engine and keeps quantitative domains separated:
risk/: scalar VaR and Expected Shortfall estimators.backtesting/: rolling VaR forecasts, exceptions, Kupiec, and Christoffersen tests.volatility/: GARCH fitting, forecasts, and residual diagnostics.simulation/: multivariate and portfolio Monte Carlo simulation.stress/: deterministic and covariance-based stress engines.optimization/: constraints, objectives, solvers, and result summaries.portfolio_backtesting/: rebalancing schedules, drift, turnover, costs, and performance.reporting/: report payload builders, tables, figures, and HTML rendering.cli/: reproducible command orchestration from YAML configs.
git clone https://github.com/FEDERICOLANCINI/Quant-Portfolio-Risk-Lab.git
cd Quant-Portfolio-Risk-Lab
python -m venv .venv
source .venv/bin/activate # Windows: .\.venv\Scripts\Activate.ps1
python -m pip install -e ".[dev]"Using uv:
uv sync --extra devportfolio-risk --help
portfolio-risk validate --config configs/base.yaml
portfolio-risk run --config configs/base.yaml
portfolio-risk backtest --config configs/backtest.yaml
portfolio-risk stress --config configs/stress.yaml
portfolio-risk optimize --config configs/optimization.yaml
portfolio-risk report --config configs/report.yamlBy default, CLI commands write to reports/runs/<UTC-run-id>/ and include:
config.yaml: exact config snapshot;manifest.json: generated artifacts and command metadata;portfolio-risk.log: command log;- command-specific CSV/JSON/HTML outputs.
report is intentionally separate from run, so report CSV exports do not overwrite pipeline
artifacts in the same directory.
View the example HTML risk report
GitHub may display the HTML source instead of rendering it. In that case, download the file and
open it locally in a browser. The same example folder includes summary_metrics.json, CSV table
exports, and figures under reports/example/figures/.
The repository uses a loss-positive convention:
loss = -portfolio_value * portfolio_return
VaR is computed as a loss quantile. Expected Shortfall is computed as the average loss beyond the
VaR threshold. Core scalar risk functions do not impose a zero floor unless floor_at_zero=True
is explicitly requested.
The saved equity universe is AAPL, MSFT, NVDA, AMZN, META, JPM, XOM, JNJ, UNH,
and PG. SPY is used as a benchmark in reporting and validation, not as an optimized asset.
Portfolio optimization uses long-only full-investment constraints with bounded per-asset weights. Rolling backtests rebalance out of sample, apply pre-trade weight drift, measure half-turnover, and report transaction-cost drag separately from gross returns.
Stress tests are deterministic what-if scenarios, not forecasts. They are reported beside baseline VaR/ES metrics to make scenario sensitivity explicit.
Run the release checks locally:
uv run ruff check .
uv run ruff format --check .
uv run mypy src/portfolio_risk_engine
uv run pytest --cov=portfolio_risk_engine --cov-report=term-missing --cov-report=xmlThe test suite covers validation, returns, VaR, Expected Shortfall, rolling VaR backtesting, Kupiec and Christoffersen tests, GARCH, Monte Carlo simulation, stress testing, optimization, rolling portfolio backtesting, reporting, and CLI orchestration.
configs/ # YAML configs for CLI workflows
data/
raw/ # committed market-data snapshot
processed/ # committed derived data used by tests/configs
notebooks/ # research notebooks used to develop the methodology
reports/
example/ # lightweight committed demo report
runs/ # ignored local CLI outputs, keeps .gitkeep
scripts/
generate_report.py # backward-compatible wrapper for portfolio-risk report
src/portfolio_risk_engine/ # importable package
tests/unit/ # deterministic unit and regression tests
The following files are intentionally kept for notebook and legacy import compatibility:
src/portfolio_risk_engine/risk_metrics.pysrc/portfolio_risk_engine/simulator.pysrc/portfolio_risk_engine/volatility_model.pyscripts/generate_report.py
They delegate to the canonical package modules and should be removed only in a future breaking release after notebook imports are updated.
Install pre-commit hooks:
uv run pre-commit install
uv run pre-commit run --all-filesBuild the package:
uv build- This is a research and educational project, not institutional production risk infrastructure.
- The CLI uses saved local CSV artifacts and does not fetch live financial data.
- Rerunning the notebooks can change the market-data snapshot because
yfinancedata evolves. - The portfolio universe is a small US equity sample and uses long-only bounded optimization.
- GARCH forecasts use empirical correlations; DCC and copula dependence models are not implemented.
- VaR backtesting uses sampled one-day-ahead observations with a configured step, not every trading day.
- Stress scenarios are deterministic and should be interpreted as scenario analysis, not predictions.
- No dashboard, API service, cloud deployment, or live-trading integration is included.
This repository is released under the MIT License. See LICENSE.
The project is intended for research and educational purposes and does not constitute financial advice, investment advice, or a trading recommendation.