CVQP is a Python solver for CVaR-constrained quadratic programs. It also provides an efficient projection onto CVaR constraints. Both scale to millions of scenarios. For details, see our paper.
pip install cvqpThe sample Conditional Value-at-Risk (CVaR) at level
where
The CVaR constraint
The CVQP solver handles problems of the form
where
import numpy as np
import scipy.sparse as sp
import cvqp
# Portfolio: n assets, m historical return scenarios
n, m = 10, 5000
np.random.seed(0)
R = np.random.randn(m, n) * 0.05 + 0.01 # return scenarios
mu = R.mean(axis=0) # expected returns
Sigma = np.cov(R.T) # covariance
P = Sigma
q = -mu
A = -R # losses = negative returns
B = sp.vstack([np.ones(n), sp.eye(n)]) # sum-to-one + long-only
l = np.concatenate([[1.0], np.zeros(n)])
u = np.concatenate([[1.0], np.full(n, np.inf)])
result = cvqp.solve(P, q, A, B, l, u, beta=0.95, kappa=0.1)
print(result.status, result.value)The CVaR projection finds the closest point to a vector
import numpy as np
import cvqp
v = np.random.randn(100_000)
z = cvqp.proj_cvar(v, beta=0.95, kappa=1.0)Since
and is available directly as proj_sum_largest.
import numpy as np
import cvqp
v = np.random.randn(100_000)
k = int(0.05 * len(v)) # same as ceil((1 - 0.95) * m)
d = 1.0 * k # same as kappa * k
z = cvqp.proj_sum_largest(v, k, d)The two projection functions are equivalent: proj_cvar(v, beta, kappa) converts to proj_sum_largest(v, k, d) internally.
See experiments/ to reproduce the numerical results from the paper.
python experiments/plot.py # generate figures from existing results
python experiments/run.py portfolio # re-run CVQP benchmarks
python experiments/run.py projection # re-run projection benchmarks@article{luxenberg2025operator,
title={An operator splitting method for large-scale CVaR-constrained quadratic programs},
author={Luxenberg, Eric and P{\'e}rez-Pi{\~n}eiro, David and Diamond, Steven and Boyd, Stephen},
journal={arXiv preprint arXiv:2504.10814},
year={2025}
}