Cost-Aware Portfolio Optimization
Cost-aware portfolio optimization treats trading costs as part of the objective, not as an afterthought applied to a frictionless Markowitz solution. Without costs, optimizers recommend extreme turnover and tiny positions that die live. With costs, the optimal portfolio is stickier, closer to the current book, and sized for capacity. This article builds from mean-variance with a cost penalty to practical multi-period heuristics.
The frictionless baseline fails
Classic Markowitz:
max w'μ − (γ/2) w'Σw
s.t. 1'w = 1
If μ̂ is noisy, the optimizer amplifies estimation error into wild weights. Adding a turnover or cost term is both economically correct and a regularizer.
Single-period with linear + impact costs
Let w₀ be current weights, w the new targets:
max w'μ − (γ/2) w'Σw − 1'c(|Δw|) − impact(Δw)
Δw = w − w₀
Common cost models:
| Model | Form | When |
|---|---|---|
| Linear | c × \ | Δw\ |
| Quadratic | λ (Δw)' A (Δw) | Temporary impact approximation |
| Square-root | y σ √(\ | Δw\ |
import numpy as np
def net_utility(w, mu, Sigma, gamma, w0, lin_cost, impact_coef, adv):
"""Simplified single-period objective (to maximize)."""
dw = w - w0
alpha = w @ mu
risk = 0.5 * gamma * (w @ Sigma @ w)
linear = lin_cost * np.abs(dw).sum()
# square-root impact proxy per name
part = np.abs(dw) / np.maximum(adv, 1e-8)
impact = impact_coef * np.sum(np.sqrt(part))
return alpha - risk - linear - impact
Optimize with constraints: long-only, max weight, sector caps, factor exposures.
Turnover constraints as a practical proxy
Full impact optimization is hard (non-convex with square-root costs). Industry shortcut:
||w − w₀||₁ ≤ τ_max
or soft penalty κ ‖w − w₀‖₁. This captures "do not trade unless α clears costs."
Multi-period insight: if signal half-life is long, τ_max should be small; if signal is fast, allow more turnover but demand higher gross α.
Multi-period and the no-trade zone
With proportional costs, optimal policy often has a no-trade zone: do not rebalance until weights drift outside a band around the ideal frictionless target.
if |w_current − w_ideal| < band:
do nothing
else:
trade toward ideal (sometimes only to the band edge)
This is the portfolio analogue of Almgren-Chriss patience — you accept tracking error vs ideal to save costs.
Linking α, cost, and trade size
Trade name i only if expected alpha gain exceeds cost:
|α_i| × |Δw_i| > lin_cost_i × |Δw_i| + impact_i(Δw_i)
For square-root impact, optimal trade size solves a closed form in simple cases:
Δw* ∝ (α / σ)^2 × ADV (schematic)
Large α and high ADV → larger trades; high σ → smaller. This is how execution and portfolio construction meet.
Estimation: costs are first-class data
You need:
- Per-name ADV and volatility
- Fee schedule and typical spread
- Impact coefficient Y calibrated on your TCA
- Borrow fees for shorts
Using a flat 5 bps for everything overstates edge in midcaps and understates it in mega-caps.
Regularization dual view
Cost penalties look like:
- L1 on trades → sparse rebalancing
- L2 on weights → shrink toward zero / benchmark
- Factor exposure penalties → shrink toward risk parity
or Black-Litterman equilibrium
Black-Litterman + cost penalty is a common institutional stack: equilibrium prior, view overlay, then trade only when views clear costs.
Backtest requirements
- Start from realistic w₀ (not zero each day)
- Apply costs on trades, not on positions
- Cap participation vs ADV
- Delay signals by the horizon you can actually trade
- Compare to a no-rebalance and monthly rebalance baseline
A cost-aware optimizer that barely beats "do nothing" after costs may still be correct — high turnover was never free alpha.
Key takeaways
- Frictionless Markowitz overtrades; add linear and impact costs to the objective
- Turnover caps and no-trade zones are practical approximations to optimal policies
- Trade size should scale with α, ADV, and inverse vol
- Calibrate impact from TCA — do not invent a constant bps
- Evaluate against low-turnover baselines, not against zero-cost fantasy P&L
