Mean-Variance Framework
\[\max_w \quad w^T \mu - \frac{\lambda}{2} w^T \Sigma w - \gamma \lvert\lvert w - w_0 \rvert\rvert\]where:
- $\mu$ are expected returns (from signals)
- $\Sigma$ is covariance matrix
- $\lambda$ is risk aversion
- $\gamma$ is turnover penalty
- $w_0$ are current holdings
Constraints
Common constraints:
- Weights sum to 1: $\sum w_i = 1$
- No short selling: $w_i \geq 0$ (or allow shorts with limits)
- Position limits: $w_i \leq w_{max}$
- Factor neutral: $w^T f = 0$
Python Implementation
from scipy.optimize import minimize
def optimize_portfolio(expected_returns, cov_matrix,
current_weights=None,
risk_aversion=1.0,
turnover_penalty=0.0):
"""
Mean-variance portfolio optimization.
Returns
-------
dict with keys: weights, expected_return, volatility, sharpe_ratio
"""
n_assets = len(expected_returns)
# Objective function
def objective(w):
ret = w @ expected_returns
risk = w @ cov_matrix @ w
turnover = 0
if current_weights is not None:
turnover = np.sum(np.abs(w - current_weights))
return -(ret - risk_aversion * risk - turnover_penalty * turnover)
# Constraints
constraints = [
{'type': 'eq', 'fun': lambda w: np.sum(w) - 1}
]
# Bounds
bounds = tuple((0, 1) for _ in range(n_assets))
# Initial guess
x0 = np.ones(n_assets) / n_assets
# Optimize
result = minimize(objective, x0, method='SLSQP',
bounds=bounds, constraints=constraints)
if not result.success:
raise ValueError(f"Optimization failed: {result.message}")
weights = result.x
return {
'weights': weights,
'expected_return': weights @ expected_returns,
'volatility': np.sqrt(weights @ cov_matrix @ weights)
}
Turnover Penalty
The turnover penalty $\gamma$ should approximately equal your transaction cost:
- If cost is 5 bps per turn, set $\gamma \approx 0.0005$
- Higher penalty reduces turnover but may miss alpha
Robustness
Optimization is sensitive to estimation error. To improve robustness:
- Shrink expected returns toward zero
- Use robust covariance estimates
- Add position limits
- Include turnover penalty