portlab
A portfolio risk and optimization library for Python, built around one principle: a risk number you cannot backtest is an opinion. Every estimator here is cross-checked against an independent implementation and ships with the tests that pin its conventions down — see VALIDATION.md for the verification record, including the defects that process found and fixed.
Install
pip install portlab
Requires Python 3.10+. Core dependencies: numpy, pandas, scipy, scikit-learn. The CVaR/CDaR LP solvers use scipy; optional solver backends (cvxpy, OSQP, Gurobi) are imported lazily when requested.
From source instead:
pip install git+https://github.com/Realosunboy6/portlab.git
Quick start
import pandas as pd
from portlab.risk.var import historical_var
from portlab.risk.backtest import exceptions, kupiec_pof, christoffersen_ind
rets = pd.Series(...) # daily simple returns
var = historical_var(rets, alpha=0.05, window=250)
exc = exceptions(rets, var) # nullable boolean Series
print(kupiec_pof(exc, 0.05)) # coverage test
print(christoffersen_ind(exc)) # gap-aware independence test
Reproduce the published validation anchor
import pandas as pd
from portlab.risk.var import historical_var
from portlab.risk.backtest import exceptions, kupiec_pof, christoffersen_ind
px = pd.read_csv("https://stooq.com/q/d/l/?s=spy.us&i=d",
parse_dates=["Date"], index_col="Date")["Close"].sort_index()
rets = px.loc["2019-01-02":].pct_change().dropna()
exc = exceptions(rets, historical_var(rets, alpha=0.05, window=250))
print(kupiec_pof(exc, 0.05))
print(christoffersen_ind(exc))
Recorded on the validation dataset (through 2026-10-09): n=1,703, 86 exceptions, Kupiec LR = 0.0089, Christoffersen LR_ind = 10.589773. Live data adds recent days, so counts grow slightly — the LR values should still land on the anchors to ~3 decimals.
SPY's VaR passes the coverage test but fails independence — the exceptions bunch in Feb–Apr 2020. That distinction is the point of the library: cite conditional coverage, not Kupiec alone.
What's inside
| Area | Modules | Highlights |
|---|---|---|
| Metrics | portlab.metrics, portlab.returns |
CAGR, vol, Sharpe/Sortino/Calmar, drawdowns, rolling stats |
| VaR / ES | portlab.risk.var |
Historical, parametric (normal / Student-t / Cornish–Fisher), EWMA, GARCH(1,1)-filtered VaR and Expected Shortfall, with explicit mean conventions (mean="zero" | "constant") |
| VaR backtesting | portlab.risk.backtest |
Exception series with a nullable-boolean contract (unscored days are <NA>, never silently passed), Kupiec POF, Christoffersen independence (gap-aware: transitions never join across data holes), joint conditional-coverage test, Basel traffic light |
| Tail diagnostics | portlab.risk.tail |
Jarque–Bera, Hill tail index, GPD extreme VaR/TVaR |
| Regime | portlab.risk.regime |
HMM-style regime detection, EWMA vol, regime-conditional VaR |
| Optimization | portlab.optimize |
Max-Sharpe / GMV / target-return frontiers, min-CVaR and min-CDaR (LP), Black–Litterman, HRP, risk parity, resampled efficiency, robust (box-uncertainty) weights, constraint attribution |
| Covariance | portlab.covariance |
Sample, Ledoit–Wolf shrinkage, semicovariance |
| Backtesting | portlab.backtest |
Portfolio backtests with rebalancing, rolling backtests, historical-episode stress |
| Attribution | portlab.attribution |
Brinson allocation/selection/interaction |
Conventions (read this before trusting a number)
- Inputs are simple returns (positive = gain). VaR/ES are reported
as positive numbers = losses. An exception is
returns < -VaR. alphameans two things, by module family — inportlab.risk.*it is the tail probability (0.05= 95% VaR); inportlab.metrics.*and the CVaR optimizer it is the confidence level (0.95). Every docstring states which.- Rolling estimators use forecast alignment: the value stamped at day t is built only from data strictly before t. Backtests score exactly the stamped days — no look-ahead, no warmup padding.
exceptions()returns a nullable-boolean Series. Days that cannot be scored (VaR warmup, missing returns, gaps) are<NA>; Kupiec counts only scored days and Christoffersen counts only transitions between genuinely adjacent scored days.
Scope
This is the public core of a larger private toolkit. Not included: live portfolio monitoring, broker integrations, tax-lot optimization, and forecasting/indicator tooling. The algorithms here are standard textbook methods (Kupiec 1995; Christoffersen 1998; Rockafellar & Uryasev 2000; Black & Litterman 1992) — the contribution is the implementation discipline and the validation suite.
Citation
If you use portlab in research, please cite it via CITATION.cff (GitHub's "Cite this repository" button reads the same file).
License
MIT — see LICENSE.
Author
Ibrahim Oyeyinka — PhD candidate, Industrial & Systems Engineering, Northern Illinois University.
Metadata
Release files for portlab 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| portlab-0.1.1.tar.gz | 80.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| portlab-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 151.2 kB
Release files / portlab-0.1.1.tar.gz
| Download URL | portlab-0.1.1.tar.gz |
|---|---|
| Size | 80.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7ed7ac1000fee90de4e46df03aec1e419ec0ddca316219bc553e51438866d80c
|
|
BLAKE2b-256 checksum How to use checksums |
9c6838fe762a6af12e2a22fba6cb3750efa9457c53093dc9d1f71d286d64fb92
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
portlab-release/0.1.1
|
Release files / portlab-0.1.1-py3-none-any.whl
| Download URL | portlab-0.1.1-py3-none-any.whl |
|---|---|
| Size | 70.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e4cb906ca3f8fc4d2db5b1308f05ee3a76d6d3988ab64c491e58e232807547cd
|
|
BLAKE2b-256 checksum How to use checksums |
919ba55d4711c65ee2fb5ec05cd739e17d30e6b8565c87100564be2f95c7bc10
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
portlab-release/0.1.1
|