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
mts1b-quantkitallocators — HRP, BL, Ledoit-Wolfmts1b-portfoliosizers — vol-target wrapper- Concept: Factor system — turning signals into weights