Position Sizing & Leverage: Kelly Criterion Strategy › Risk Parity and Volatility Targeting

Michael BrenndoerferJanuary 23, 20265 min read
Quantitative FinanceAll chapters
Part 14 of 15Strategy Development and Backtesting
Chapter 75Position Sizing & Leverage: Kelly Criterion Strategy › Risk Parity and Volatility Targeting
Reading section 5 of 9 · Risk Parity and Volatility Targeting · 5 minSection 5 of 9 · 5 min

Book contents

Section 5 of 9 · 5 min read

Position Sizing & Leverage: Kelly Criterion Strategy › Risk Parity and Volatility TargetingOne section · Sections in this chapter5 / 9 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Given the dangers of leverage and the difficulties of estimating expected returns, alternative allocation frameworks focus on risk rather than return.

Risk Parity Principles

Risk parity, pioneered by Ray Dalio's Bridgewater Associates, allocates capital so that each asset contributes equally to portfolio risk. The core insight is that expected returns are notoriously difficult to estimate, but volatilities and correlations are relatively stable and predictable. By focusing on what we can estimate reliably, risk parity sidesteps much of the estimation error that plagues return-based optimization.

For a long-only portfolio where each asset contributes equally to risk (TRCi=σp/n\text{TRC}_i = \sigma_p/n), we require:

wi(Σw)i=σp2nfor all iw_i (\boldsymbol{\Sigma} \mathbf{w})_i = \frac{\sigma_p^2}{n} \quad \text{for all } i

where:

  • wiw_i: weight of asset ii
  • Σ\boldsymbol{\Sigma}: covariance matrix of returns
  • (Σw)i(\boldsymbol{\Sigma} \mathbf{w})_i: marginal covariance of asset ii
  • σp2\sigma_p^2: portfolio variance
  • nn: number of assets

When assets are uncorrelated, this simplifies to inverse-volatility weighting:

wi∝1σiw_i \propto \frac{1}{\sigma_i}

where:

  • wiw_i: weight of asset ii
  • σi\sigma_i: volatility of asset ii

Lower-volatility assets receive higher weights, which for traditional portfolios means bonds receive much larger allocations than stocks. To achieve competitive returns, risk parity portfolios typically apply leverage to the entire portfolio, bringing total volatility to a target level (often 10-15% annually).

Volatility Targeting

A related approach is volatility targeting, where position sizes are adjusted dynamically to maintain constant portfolio volatility. This approach recognizes that volatility varies substantially over time, so static position sizing produces varying levels of actual risk. If target volatility is σ∗\sigma^* and current estimated volatility is σ^t\hat{\sigma}_t, the position multiplier is:

multipliert=σ∗σ^t\text{multiplier}_t = \frac{\sigma^*}{\hat{\sigma}_t}

where:

  • multipliert\text{multiplier}_t: leverage scaling factor at time tt
  • σ∗\sigma^*: target volatility
  • σ^t\hat{\sigma}_t: estimated current volatility

This approach automatically reduces positions during high-volatility periods (when losses are most likely) and increases positions during calm periods. Research shows this can improve risk-adjusted returns and reduce drawdowns compared to constant position sizing.

In[12]:
Code
import numpy as np


def simulate_volatility_targeting(returns, target_vol, lookback=20):
    """
    Simulate a volatility-targeting strategy.

    Parameters:
    - returns: array of daily returns
    - target_vol: target daily volatility
    - lookback: days for volatility estimation
    """
    n = len(returns)
    leverages = np.ones(n)
    targeted_returns = np.zeros(n)

    for t in range(lookback, n):
        # Estimate volatility from recent returns
        recent_vol = np.std(returns[t - lookback : t])

        # Calculate leverage to hit target volatility
        if recent_vol > 0:
            leverage = min(target_vol / recent_vol, 3.0)  # Cap at 3x
        else:
            leverage = 1.0

        leverages[t] = leverage
        targeted_returns[t] = leverage * returns[t]

    return targeted_returns, leverages


# Simulate a strategy with time-varying volatility
np.random.seed(123)
n_days = 1000

# Create volatility regime (low vol, then high vol, then low again)
vol_regime = np.concatenate(
    [
        np.ones(400) * 0.01,  # Low vol period
        np.ones(200) * 0.03,  # High vol period (crisis)
        np.ones(400) * 0.012,  # Return to normal
    ]
)

# Generate returns with constant expected return but time-varying vol
base_returns = np.random.normal(0.0003, 1, n_days) * vol_regime

# Apply volatility targeting
target_daily_vol = 0.01  # Target 1% daily vol (~16% annual)
targeted_returns, leverages = simulate_volatility_targeting(
    base_returns, target_daily_vol, lookback=20
)
Out[13]:
Visualization
Two cumulative-return lines over 1000 days with a pale coral band from day 400 to 600 marking the high-volatility regime.
Cumulative returns for static and volatility-targeted positions. Targeting dampens crisis-period swings, but this simulated path still finishes below the static strategy.
Two rolling annualized-volatility lines; the targeted line stays near a dashed 16% target while the static line spikes above 60% in the shaded regime.
Rolling 20-day annualized volatility. The targeted strategy stays near the 16% annual target while static volatility spikes during the high-volatility regime.
Dynamic leverage line over 1000 days, dropping from near 1x to roughly 0.3x in the shaded high-volatility regime before recovering.
Dynamic leverage adjustment. Exposure falls well below 1x during days 400-600, then rises as estimated volatility subsides.

The visualization demonstrates volatility targeting's key benefit: automatic risk reduction during dangerous periods. During the simulated crisis (days 400-600), the strategy reduced leverage to well below 1x, limiting losses. After the crisis, leverage gradually increased as volatility estimates came down.