Portfolio Optimization
Portfolio-Optimisation Toolbox
This package groups three single-period convex allocation models and exposes an identical, numerically stable API for each of them. All back-end calls rely exclusively on CVXOPT 1.3+; hence global optimality is guaranteed provided the solver returns a status flag “optimal”.
Acronym |
Risk measure / Objective functional \(f(w)\) |
Cone class |
CVXOPT routine |
|---|---|---|---|
|
\(\tfrac12\,w^{\top}\Sigma w\) |
PSD |
|
|
\(\operatorname{CVaR}_{\alpha}\bigl(-R\,w\bigr)\) |
LP |
|
|
\(\displaystyle \max_{\mu\in\mathcal U}\bigl[-w^{\top}\mu + \lambda\|S^{1/2}w\|_2\bigr]\) |
SOC |
|
Global symbols
\(N\) — number of risky assets.
\(T\) — number of Monte-Carlo or historical scenarios.
\(R\in\mathbb R^{T\times N}\) — scenario matrix of excess returns.
\(p\in\Delta^{T}\) — probability vector, \(\mathbf1^{\top}p=1\).
\(\mu:=R^{\top}p\), \(\Sigma:=(R-\mu^{\top})^{\top}\!\mathrm{diag}(p)(R-\mu^{\top})\).
\(w\in\mathbb R^{N}\) — portfolio after re-balancing, \(\mathbf1^{\top}w=1\).
Optional affine trading rules
encode leverage caps, tracking constraints, minimum/maximum holdings, etc.
Transaction-cost primitives
Quadratic impact : \(\Lambda=\operatorname{diag}(\lambda)\) (QP only)
\[(w-w_0)^{\top}\Lambda\,(w-w_0).\]Proportional turnover : \(c^{+},c^{-}\ge0\) (LP/SOCP)
\[\sum_{i=1}^{N}\bigl(c^{+}_iu^{+}_i+c^{-}_iu^{-}_i\bigr), \qquad w = w_0 + u^{+}-u^{-},\;u^{+},u^{-}\ge0.\]
Primary references
Markowitz (1952); Rockafellar & Uryasev (2000); Meucci (2005); Lobo et al. (2007).
Credits
MeanVariance and MeanCVaR classes are adapted from fortitudo-tech package (https://github.com/fortitudo-tech/fortitudo.tech)
- class pyvallocation.optimization.MeanCVaR(R: npt.ArrayLike, p: npt.ArrayLike, alpha: float, G: npt.ArrayLike | None = None, h: npt.ArrayLike | None = None, A: npt.ArrayLike | None = None, b: npt.ArrayLike | None = None, *, initial_weights: npt.NDArray[np.floating] | None = None, proportional_costs: npt.NDArray[np.floating] | None = None)[source]
Bases:
_FrontierMixin,_BaseOptimizationRockafellar–Uryasev CVaR optimisation with optional proportional costs.
Formulation
For level \(\alpha\in(0,1)\) define the conditional value-at-risk
\[\operatorname{CVaR}_{\alpha}(Z)= \min_{c\in\mathbb R}\; c+\frac1\alpha\,\mathbb E[(Z-c)_{+}].\]Substituting \(Z=-R\,w\) and applying the sample average approximation produces the LP
\[\begin{split}\begin{aligned} \min_{w,c,\xi}\quad & c + \tfrac1\alpha\,p^{\top}\xi + c^{\top}(u^{+}+u^{-}) \\[4pt] \text{s.t.}\quad & \xi \;\ge\; -R\,w - c\mathbf1, \\[2pt] & \xi \;\ge\; 0, \\ & w = w_0 + u^{+}-u^{-},\;u^{+},u^{-}\ge 0, \\ & \mathbf1^{\top}w = 1,\; G w \le h,\; A w = b. \end{aligned}\end{split}\]- param R:
Scenario matrix and probabilities.
- param p:
Scenario matrix and probabilities.
- param alpha:
Tail probability \(\alpha\) (e.g.
0.05= 95 % CVaR).- param initial_weights:
Activate linear turnover frictions iff both are given.
- param proportional_costs:
Activate linear turnover frictions iff both are given.
- efficient_frontier(num_portfolios: int) numpy.ndarray[source]
Return the CVaR efficient frontier with
num_portfoliosvertices.
- efficient_portfolio(return_target: float | None = None) np.ndarray
Solve the CVaR LP for a given target return
τ(or min-CVaR portfolio ifτ is None).
- class pyvallocation.optimization.MeanVariance(mean: npt.ArrayLike, covariance: npt.ArrayLike, G: npt.ArrayLike | None = None, h: npt.ArrayLike | None = None, A: npt.ArrayLike | None = None, b: npt.ArrayLike | None = None, *, initial_weights: npt.NDArray[np.floating] | None = None, market_impact_costs: npt.NDArray[np.floating] | None = None)[source]
Bases:
_FrontierMixin,_BaseOptimizationClassic mean–variance programme à la Markowitz (1952).
Problem statement
\[\begin{split}\begin{aligned} \min_{w}\;&\tfrac12\,w^{\top}\Sigma w + \tfrac12\,(w-w_0)^{\top}\Lambda(w-w_0) \\[3pt] \text{s.t. }& \mu^{\top}w \;\ge\; \tau, \quad \mathbf1^{\top}w = 1, \quad G w \le h,\; A w = b. \end{aligned}\end{split}\]τis supplied on the fly viaefficient_portfolio().Λ(quadratic impact) is optional; if omitted the model degenerates to the textbook QP with no trading costs.
Notes
The QP is strictly convex whenever
Σ≻ 0 or at least one positive λᵢ is present, hence the solution is unique.- param mean:
Mean vector \(\mu\) and covariance matrix \(\Sigma\).
- param covariance:
Mean vector \(\mu\) and covariance matrix \(\Sigma\).
- param G:
Optional affine constraints as defined in the module docstring.
- param h:
Optional affine constraints as defined in the module docstring.
- param A:
Optional affine constraints as defined in the module docstring.
- param b:
Optional affine constraints as defined in the module docstring.
- param initial_weights:
w0and diag-elements ofΛ– must be passed together.- param market_impact_costs:
w0and diag-elements ofΛ– must be passed together.
- efficient_frontier(num_portfolios: int) numpy.ndarray[source]
Return an
(N, num_portfolios)array whose columns trace the Markowitz efficient set between the variance minimiser and the return maximiser.
- efficient_portfolio(return_target: float | None = None) np.ndarray
Solve the QP once for a given target \(\tau\).
Passing
Noneyields the minimum-variance solution, i.e. the left-most point on the efficient frontier.
- class pyvallocation.optimization.OptimizationResult(weights: npt.NDArray[np.floating], nominal_return: float, risk: float)[source]
Bases:
objectImmutable result object returned by
RobustOptimizer.It stores the optimal post-trade weights, the associated point-estimate return and a model-specific risk proxy (σ⋆ for mean–variance, CVaR⋆ for mean-CVaR, or the robust radius t⋆).
- nominal_return: float
- risk: float
- weights: numpy.typing.NDArray.numpy.floating
- class pyvallocation.optimization.RobustOptimizer(expected_return: npt.NDArray[np.floating], uncertainty_cov: npt.NDArray[np.floating], G: npt.ArrayLike | None = None, h: npt.ArrayLike | None = None, A: npt.ArrayLike | None = None, b: npt.ArrayLike | None = None, *, initial_weights: npt.NDArray[np.floating] | None = None, proportional_costs: npt.NDArray[np.floating] | None = None)[source]
Bases:
_BaseOptimization
Single-period mean–variance allocator that immunises the portfolio against estimation error in the expected-return vector while leaving the covariance matrix untouched. The model is the direct implementation of the ellipsoidal framework put forward in
Goldfarb & Iyengar – “Robust Portfolio Selection Problems”, Math. Oper. Res. 28 (1), 1-38 (2003)
Meucci – Risk & Asset Allocation, Ch. 9 (Springer, 2005) and the robust-Bayesian extension in SSRN 681553 (2011)
1 Ellipsoidal ambiguity set
We assume the unknown mean \(\mu\) lies in
\[\mathcal U(\hat\mu,S,q) := \bigl\{\mu\in\mathbb R^{N}\;|\; \lVert S^{-1/2}(\mu-\hat\mu)\rVert_2 \le q\bigr\},\]- where
\(\hat\mu\) — point estimate (MLE, posterior mean, …)
\(S\succ0\) — scatter matrix (posterior covariance, shrinkage, …)
\(q\) — radius, usually \(\sqrt{\chi^2_N(1-\alpha)}\) for a \(100(1-\alpha)\%\) credible set.
2 SOCP reformulation
Goldfarb-Iyengar (Thm 3.1) show
\[\min_{\mu\in\mathcal U}w^{\top}\mu = \hat\mu^{\top}w-q\,\lVert S^{1/2}w\rVert_2,\]so the worst-case mean is a linear term minus a 2-norm penalty. Introducing an epigraph variable \(t\) gives the cone programme
\[\min_{w,t}\;t+\lambda\lVert S^{1/2}w\rVert_2 \;\;\text{s.t.}\;\;t\ge-\hat\mu^{\top}w,\;w\!\in\!C,\]which CVXOPT solves as a second-order cone programme (type “q”).
3 Two parameterisations
solve_lambda_variant(lam)Direct penalty \(\lambda=q\). Higher λ ⇒ stronger shrinkage towards the global minimum-variance portfolio.
solve_gamma_variant(gamma_mu, gamma_sigma_sq)Chance-constraint form (Ben-Tal & Nemirovski 2001). For tolerance \(\gamma_\mu\) and radius cap \(\gamma_{\sigma}^{2}\) we enforce
\[\Pr(\mu^{\top}w\le -t)\le\gamma_\mu,\quad t^2\le\gamma_{\sigma}^{2},\]implemented as a linear row
t ≤ √gamma_sigma_sq.
Both wrappers feed the same private routine
_solve_socp().4 Optional proportional turnover
Passing both
initial_weightsandproportional_costsactivates the linear-cost mechanism of Lobo et al. (2007). The decision vector becomes\[(w,\;t,\;u^{+},u^{-})\in\mathbb R^{\,3N+1},\]with inventory balance \(w=w_0+u^{+}-u^{-},\;u^{+},u^{-}\ge0\).
5 Limitations
Only mean uncertainty is modelled; covariance risk would require an SDP.
The ellipsoid is static; time-varying radii must be supplied upstream.
Extremely ill-conditioned
uncertainty_covcan trigger numerical warnings in CVXOPT.
- efficient_frontier(lambdas: Sequence[float]) Tuple[list[float], list[float], npt.NDArray[np.floating]][source]
Sweep a list of
lambdasand returnnominal returns,
robust radii (
t⋆),and a weight matrix
(N, len(lambdas)).
- solve_gamma_variant(gamma_mu: float, gamma_sigma_sq: float) OptimizationResult[source]
Solve the chance-constraint form (γ-variant). Arguments map onto
\[\Pr\bigl(\mu^{\top}w\le -t\bigr)\;\le\; γ_{\mu}, \qquad t\;\le\;\sqrt{γ_{\sigma}^{2}}.\]
- solve_lambda_variant(lam: float) OptimizationResult[source]
Solve the λ-variant:
\[\min_{w,t}\;t + λ\|S^{1/2}w\|_2 \quad\text{s.t.}\; t \ge -\hat\mu^{\top}w,\;\ldots\]