Mean-Variance Optimization with a Budget Constraint
Summary
The document explains a mismatch between an analytical mean-variance portfolio solution and a numerical optimizer. The portfolio objective combines covariance-based risk with expected return, while the weights must sum to one. An unconstrained formula that omits the budget condition does not enforce that requirement, so it should not be expected to match a constrained optimization result. The discussion derives the budget-constrained solution with a Lagrange multiplier.
The key correction is consistency in the objective’s scaling: the stated analytical derivation uses one half of the quadratic variance term, while the code initially minimizes the full quadratic term. That changes the relative balance between risk and return for a fixed risk-aversion parameter. The reply gives corrected derivatives and implementation guidance, then compares the analytical weights with a numerical approach that also includes nonnegative bounds. The example is illustrative; correctness still depends on consistent vector and matrix conventions, covariance inputs, and optimizer convergence.
Key ideas
- A budget constraint requires the portfolio weights to sum to one.
- The unconstrained inverse-covariance formula does not by itself enforce a fully invested portfolio.
- A Lagrange multiplier can incorporate the budget condition in the analytical solution.
- The analytical derivation and numerical objective must use the same scaling of the variance term.
- Nonnegative bounds add a separate no-short constraint and can change the optimal weights.
Tags
Full text
# Mean-variance optimisation with and without constraints
# Mean-variance optimisation with and without constraints
I wrote the following code for a portfolio project but I have an issue.
- The first function calculates the mean-variance optimisation based on the lambda (risk propensity), the vector of expected returns mu and the covariance matrix. The only constraint here is a budget constraint so that the sum of weights = 1
- The second function maximises the target function (in this case minimises the negative of the target function) where lambda represents a risk propensity factor. I can set the bounds to (0,1) to specify no shorts.
However if I specify bounds (None, None), I would expect to get the same results as the mvo function, but I do not. Results are completely different. Can anybody help? Thanks in advance!! *Edit: added imports
```
import numpy as np
from numpy.linalg import inv
from scipy.optimize import minimize
# mean-variance optimisation no constraints
def mvo(lambda_risk, mu, cov_m):
w = (1/(2*lambda_risk)) * inv(cov_m) @ mu
return w
# Objective function used for optimisation under "no-shorts" constraint (see below)
def portfolio_objective(w, lambda_risk, mu, cov_m):
return w.T @ cov_m @ w - lambda_risk * mu.T @ w
# Mean-variance optimisation with constraint = no short positions (non negative weights only)
def mvo_no_short(lambda_risk, mu, cov_m):
n_assets = len(mu)
# Constraints: Sum of weights equals 1
constraints = {'type': 'eq', 'fun': lambda w: np.sum(w) - 1}
# Bounds: No short selling (weights >= 0)
bounds = [(0, 1) for _ in range(n_assets)]
# Initial guess (e.g., equal weights)
initial_weights = np.ones(n_assets) / n_assets
# Solve the optimization problem
result = minimize(
portfolio_objective,
initial_weights,
args=(lambda_risk, mu, cov_m),
bounds=bounds,
constraints=constraints
)
return result.x
```
## Answer by palliativo (score 0)
https://quant.stackexchange.com/a/81488
Thank you so much for your kind reply and explanation. Indeed bad oversight from my side. I tried to do things step by step adding a budget constraint to the mean-variance optimisation formula.
$\mathcal{L} = \mathbf{w}^T \Sigma \mathbf{w} - \lambda \mathbf{w}^T \mu + \gamma \left( \mathbf{1}^T \mathbf{w} - 1 \right)$
$\dfrac{\partial{\mathcal{L}}}{\partial{w}}= 2\Sigma\mathbf{w}\ -\lambda \mathbf{\mu} - \gamma\mathbf{1^T} $
$\dfrac{\partial{\mathcal{L}}}{\partial{\gamma}}= 1- \mathbf{1}\mathbf{w} $
from which I get:
$\gamma = \dfrac{\mathbf{1^T}\Sigma^{-1}\lambda\mathbf{\mu}-1}{\mathbf{1^T}\Sigma^{-1}\mathbf{1}}$
and replacing this into the equation above I get the optimal w weights enforcing the budget constraint that the sum of all weights must be one. Implementing it in Python:
```
def new_mvo_budget_constraint(lambda_risk, mu, cov_m):
ones = np.ones(len(cov_m))
gamma = (lambda_risk * ones.T @ inv(cov_m) @ mu - 1) / multi_dot([ones.T, inv(cov_m), ones])
w = (lambda_risk * mu - gamma * ones.T)@ inv(cov_m)
return w
```
Unfortunately even now, I get substantially different results than the other optimisation function using scipy. Cleary I am making a mistake in the calculations, but I spent a day looking for the error and can't find it. Also tried chatgpt, but his answers are all over the place. Anyone can spot an obvious error in my derivatives?
## Answer by Serene He (score 0)
https://quant.stackexchange.com/a/82427
You missed a multiple of 1/2 in your objective function. The correct objective function should be:
```
def portfolio_objective(w, lambda_risk, mu, cov_m):
return w.T @ cov_m @ w/2 - lambda_risk * mu.T @ w
```
The analytical solution is:
$L=w^TΣw/2−λw^Tμ+γ(\textbf1^Tw−1)$
$\dfrac{∂L}{∂w}=Σw −λμ+γ\textbf1^T$
$\dfrac{∂L}{∂γ}=1−\textbf1w$
then you get:
$γ=\dfrac{\textbf1^TΣ^{-1}λμ-1}{\textbf1^TΣ^{−1}\textbf1}$
Complete code for you the verify the two approaches give the same result:
```
import numpy as np
from numpy.linalg import inv
from numpy.linalg import multi_dot
from scipy.optimize import minimize
lambda_risk=2
mu=np.array([0.05,0.08])
cov_m=np.matrix([[0.2,0.1],[0.1,0.3]])
def new_mvo_budget_constraint(lambda_risk, mu, cov_m):
ones = np.ones(len(cov_m))
gamma = (lambda_risk * ones.T @ inv(cov_m) @ mu - 1) / multi_dot([ones.T,inv(cov_m), ones])
w = (lambda_risk * mu - gamma * ones.T)@ inv(cov_m)
return w
print(new_mvo_budget_constraint(lambda_risk,mu,cov_m))
# Objective function used for optimisation under "no-shorts" constraint (see below)
def portfolio_objective(w, lambda_risk, mu, cov_m):
return w.T @ cov_m @ w/2 - lambda_risk * mu.T @ w
# Mean-variance optimisation with constraint = no short positions (non negative weights only)
def mvo_no_short(lambda_risk, mu, cov_m):
n_assets = len(mu)
# Constraints: Sum of weights equals 1
constraints = {'type': 'eq', 'fun': lambda w: np.sum(w) - 1}
# Bounds: No short selling (weights >= 0)
bounds = [(0, 1) for _ in range(n_assets)]
# Initial guess (e.g., equal weights)
initial_weights = np.ones(n_assets) / n_assets
# Solve the optimization problem
result = minimize(
portfolio_objective,
initial_weights,
args=(lambda_risk, mu, cov_m),
bounds=bounds,
constraints=constraints
)
return result.x
print(mvo_no_short(lambda_risk, mu, cov_m))
```Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.