Skip to main content

Sizing under correlated regimes

Problem: Your factor produces a long basket of 10 tech stocks. They look diversified by ticker, but in a sell-off they correlate to ~1.0 and your portfolio takes a single concentrated drawdown.

Solution: Use HRP (or Black-Litterman with a correlation prior) instead of equal-weight.

The naive approach

# Equal-weight top-N — what every tutorial starts with
top_n_signal = top_n_by_score(z_scores, n=10)
weights = {sym: 1.0 / 10 for sym in top_n_signal}

When all 10 are highly correlated, this concentrates risk into a single factor.

HRP-weighted

from mts1b_quantkit.allocators import hrp_weights

# Get historical returns of the top-N basket
hist_returns = datalake.get_returns(top_n_signal, start="2023-01-01", end=asof)

# HRP weights — accounts for cluster structure
result = hrp_weights(hist_returns)
weights = result.weights.to_dict()

HRP clusters correlated assets together, then allocates between clusters first, within clusters second. Highly-correlated assets share the cluster's budget instead of each getting a full allocation.

Black-Litterman with correlation prior

from mts1b_quantkit.allocators import black_litterman

# Use your factor's z-scores as views
views = [
{"symbols": [sym], "weights": [1.0], "expected": z, "confidence": 0.5}
for sym, z in z_scores.items()
]

result = black_litterman(
hist_returns,
market_caps=market_caps_for_universe,
views=views,
risk_aversion=2.5,
tau=0.05,
)
weights = result["weights"]

BL combines the factor (views) with a market-implied prior (equilibrium weights). The covariance pulls correlated assets together, reducing concentration.

Vol-target overlay

After picking weights via HRP or BL, apply a vol-target overlay so total portfolio vol is bounded:

from mts1b_portfolio.sizers import vol_target_weights

scaled = vol_target_weights(
base_weights=hrp_weights_array,
asset_log_returns=log_returns,
target_ann_vol=0.12,
window=63,
max_scale=3.0,
)

If the basket realized vol is 18% annualized but you target 12%, this scales gross exposure to 12/18 ≈ 0.67x.

When NOT to use HRP/BL

  • If your basket has < 5 names: HRP linkage is unstable.
  • If returns are non-stationary (recent regime change): HRP weights reflect the past, which may not be the present. Consider a shorter lookback_days.
  • If you have very high turnover (intraday): the cluster structure changes too fast to track. Equal-weight or risk-parity is more robust.

See also