Skip to content

Portfolio Optimization

Construct optimal portfolios beyond simple equal-weighting.

Overview

Portfolio optimization finds weights that maximize objectives subject to constraints:

\[\max_w \quad f(w) \quad \text{subject to} \quad g(w) \leq 0\]

Mean-Variance Optimization

Classic Markowitz

Text Only
portfolio mean_variance:
  weights = optimize(
    objective = maximize("sharpe"),
    expected_returns = signal,
    covariance = rolling_cov(ret(prices, 1), 60),
    constraints:
      sum_weights = 1.0
      min_weight = 0.0
      max_weight = 0.10
  )

With Risk Aversion

Text Only
portfolio risk_averse:
  weights = optimize(
    objective = maximize("utility"),
    utility = expected_return - 0.5 * risk_aversion * variance,
    risk_aversion = 3.0,  // Higher = more risk averse
    constraints:
      sum_weights = 1.0
  )

Risk-Based Optimization

Minimum Variance

Text Only
portfolio min_variance:
  weights = optimize(
    objective = minimize("variance"),
    covariance = rolling_cov(ret(prices, 1), 60),
    constraints:
      sum_weights = 1.0
      min_weight = 0.0
  )

Risk Parity

Equal risk contribution from each asset:

Text Only
portfolio risk_parity:
  // Each asset contributes equally to portfolio risk
  weights = optimize(
    objective = equalize("risk_contribution"),
    covariance = rolling_cov(ret(prices, 1), 60),
    constraints:
      sum_weights = 1.0
      min_weight = 0.01
  )

Simplified Risk Parity

Text Only
signal risk_parity_weights:
  vol = rolling_std(ret(prices, 1), 60)
  inv_vol = 1 / vol
  weights = inv_vol / sum(inv_vol)
  emit weights

Maximum Diversification

Text Only
portfolio max_diversification:
  weights = optimize(
    objective = maximize("diversification_ratio"),
    // DR = weighted avg vol / portfolio vol
    covariance = rolling_cov(ret(prices, 1), 60),
    constraints:
      sum_weights = 1.0
      min_weight = 0.0
  )

Constrained Optimization

Long-Short with Constraints

Text Only
portfolio constrained:
  weights = optimize(
    objective = maximize("expected_return"),
    expected_returns = signal,
    covariance = cov_matrix,
    constraints:
      // Exposure constraints
      gross_exposure = 2.0      // 200% gross
      net_exposure = 0.0        // Dollar neutral

      // Position constraints
      min_weight = -0.05
      max_weight = 0.05

      // Sector constraints
      max_sector_exposure = 0.25

      // Risk constraints
      max_volatility = 0.15
  )

Tracking Error Constraint

Text Only
portfolio tracking:
  weights = optimize(
    objective = maximize("information_ratio"),
    benchmark = spy_weights,
    constraints:
      max_tracking_error = 0.05  // 5% TE
      max_active_weight = 0.03
  )

Black-Litterman

Combine market equilibrium with views:

Text Only
portfolio black_litterman:
  // Market equilibrium returns (from CAPM)
  equilibrium = market_cap_weights * risk_aversion * cov_matrix

  // Your views
  views:
    - asset: AAPL
      return: 0.15
      confidence: 0.8
    - asset: MSFT
      return: 0.12
      confidence: 0.6

  weights = optimize(
    method = "black_litterman",
    equilibrium_returns = equilibrium,
    views = views,
    covariance = cov_matrix,
    tau = 0.05  // Uncertainty in equilibrium
  )

Hierarchical Risk Parity

Text Only
portfolio hrp:
  weights = optimize(
    method = "hierarchical_risk_parity",
    returns = ret(prices, 1),
    lookback = 252,
    linkage = "ward"  // Clustering method
  )

Robust Optimization

Uncertainty in Returns

Text Only
portfolio robust:
  weights = optimize(
    method = "robust_mean_variance",
    expected_returns = signal,
    return_uncertainty = 0.02,  // ±2% uncertainty
    covariance = cov_matrix,
    constraints:
      max_weight = 0.10
  )

Resampled Optimization

Text Only
portfolio resampled:
  weights = optimize(
    method = "resampled_efficient_frontier",
    expected_returns = signal,
    covariance = cov_matrix,
    n_samples = 1000,
    constraints:
      sum_weights = 1.0
  )

Transaction Cost Aware

Text Only
portfolio turnover_penalized:
  weights = optimize(
    objective = maximize("return - turnover_cost"),
    expected_returns = signal,
    current_weights = previous_weights,
    turnover_cost = 0.001,  // 10 bps per unit turnover
    constraints:
      sum_weights = 1.0
  )

Multi-Period Optimization

Text Only
portfolio multi_period:
  weights = optimize(
    method = "multi_period",
    horizon = 5,  // 5 periods ahead
    expected_returns = [signal_t1, signal_t2, signal_t3, signal_t4, signal_t5],
    transaction_costs = tc.bps(10),
    constraints:
      max_turnover_per_period = 0.25
  )

Factor-Constrained

Text Only
portfolio factor_constrained:
  weights = optimize(
    objective = maximize("alpha"),
    alpha = signal,
    factor_exposures:
      market: [0.9, 1.1]     // Beta between 0.9 and 1.1
      size: [-0.1, 0.1]      // Size neutral
      value: [0.0, 0.3]      // Slight value tilt
    constraints:
      gross_exposure = 2.0
      net_exposure = 0.0
  )

Complete Example

Text Only
data:
  source = "prices_fundamentals.parquet"
  format = parquet

// Alpha signal
signal alpha:
  momentum = zscore(ret(prices, 60))
  value = zscore(book_to_market)
  quality = zscore(roe)
  combined = 0.4 * momentum + 0.4 * value + 0.2 * quality
  emit neutralize(combined, by=sectors)

// Covariance estimation
signal covariance:
  returns = ret(prices, 1)
  // Ledoit-Wolf shrinkage
  cov = shrunk_covariance(returns, 252, method="ledoit_wolf")
  emit cov

// Optimized portfolio
portfolio optimized:
  weights = optimize(
    objective = maximize("sharpe"),
    expected_returns = alpha,
    covariance = covariance,

    constraints:
      // Exposure
      gross_exposure = 2.0
      net_exposure = 0.0

      // Position limits
      min_weight = -0.05
      max_weight = 0.05

      // Sector limits
      max_sector = 0.25

      // Risk limits
      max_volatility = 0.12

      // Factor limits
      beta: [0.8, 1.2]

      // Turnover
      max_turnover = 0.25
  )

  costs = tc.bps(10)

  backtest rebal=21 from 2015-01-01 to 2024-12-31

Optimization Methods

Method Best For Complexity
Mean-Variance Simple cases Low
Risk Parity Equal risk Low
Min Variance Risk reduction Medium
Black-Litterman Incorporating views Medium
HRP Robustness Medium
Robust Uncertain inputs High

Best Practices

1. Use Shrinkage Estimation

Text Only
cov = shrunk_covariance(returns, 252)

2. Add Position Limits

Unconstrained optimization produces extreme weights.

3. Penalize Turnover

Text Only
objective = return - turnover_cost * turnover

4. Test Robustness

Small input changes shouldn't cause large weight changes.

5. Monitor Factor Exposures

Ensure optimization doesn't create unintended bets.

Next Steps