第 8 章 · 01 层次风险平价 HRP 本节摘要:均值-方差、BL 都依赖协方差可逆,但金融协方差矩阵常常病态(资产高度相关导致近奇异),求逆放大噪声,权重剧烈跳变。Marcos López de Prado 提出的层次风险平价(Hierarchical Risk Parity,HRP)彻底绕开求逆:用相关性算距离矩阵,做层次聚类把相似资产分组,再按树递归地分配风险。HRP 不需要协方差可逆、不需要收益估计,对噪声鲁棒,在样本外表现稳定。本节讲清 类的四步算法——相关性→距离→聚类→递归分配,并与 cookbook 5 配套。读完本节,你能用 HRP 出权重并画 dendrogram 解释聚类结构。 内容来源:源码 、文档 、cookbook ,汉化并套用体系化模板。
本节摘要:均值-方差、BL 都依赖协方差可逆,但金融协方差矩阵常常病态(资产高度相关导致近奇异),求逆放大噪声,权重剧烈跳变。Marcos López de Prado 提出的层次风险平价(Hierarchical Risk Parity,HRP)彻底绕开求逆:用相关性算距离矩阵,做层次聚类把相似资产分组,再按树递归地分配风险。HRP 不需要协方差可逆、不需要收益估计,对噪声鲁棒,在样本外表现稳定。本节讲清
HRPOpt类的四步算法——相关性→距离→聚类→递归分配,并与 cookbook 5 配套。读完本节,你能用 HRP 出权重并画 dendrogram 解释聚类结构。
内容来源:源码
pypfopt/hierarchical_portfolio.py、文档docs/OtherOptimizers.rst、cookbook5-Hierarchical-Risk-Parity.ipynb,汉化并套用体系化模板。
阅读完本节,你应当能够:
均值-方差优化的解析解 w ∝ Σ⁻¹μ 依赖 Σ 可逆。但金融场景下 Σ 经常病态:
HRP 的洞见:根本不求逆。改用聚类 + 递归分配,从结构上分散风险,数学上对噪声鲁棒。
💡 核心心法:HRP 不算「最优」权重,它算「结构上分散」的权重。不追求理论最优,换取工程鲁棒——尤其在资产多、样本少、相关性高时,这恰是均值-方差最脆弱的场景。
源码 HRPOpt.optimize 把算法拆得很清楚:
def optimize(self, linkage_method="single"): if self.returns is None: cov = self.cov_matrix corr = cov_to_corr(self.cov_matrix).round(6) else: corr, cov = self.returns.corr(), self.returns.cov() # Step 1: 距离矩阵 matrix = np.sqrt(np.clip((1.0 - corr) / 2.0, a_min=0.0, a_max=1.0)) dist = ssd.squareform(matrix, checks=False) # Step 2: 层次聚类 self.clusters = sch.linkage(dist, linkage_method) # Step 3: 准对角化(按聚类顺序排叶节点) sort_ix = HRPOpt._get_quasi_diag(self.clusters) ordered_tickers = corr.index[sort_ix].tolist() # Step 4: 递归分配 hrp = HRPOpt._raw_hrp_allocation(cov, ordered_tickers) weights = collections.OrderedDict(hrp.sort_index()) self.set_weights(weights) return weights
相关性 0~1 转成「距离」:d = √((1-corr)/2)。完全正相关 → d=0(无法区分);完全负相关 → d=1(最远)。开根号是为了满足三角不等式。
用 scipy 的 sch.linkage 默认 single 链接法,自底向上把相似资产合并成一棵树。高度相关的资产聚到同一簇(如 AMD 和 NVDA 同属半导体)。
sch.to_tree(link).pre_order() 把叶节点按聚类结构排序——相似的资产相邻。这一步把协方差矩阵重排成准对角形态,让相似的资产在矩阵中聚成块。
源码 _raw_hrp_allocation 是 HRP 的灵魂——从顶部开始,把当前簇对半切成两子簇,按「逆方差组合」的方差比分配权重:
@staticmethod def _raw_hrp_allocation(cov, ordered_tickers): w = pd.Series(1.0, index=ordered_tickers) cluster_items = [ordered_tickers] while len(cluster_items) > 0: cluster_items = [...] # 二分 for i in range(0, len(cluster_items), 2): first_cluster = cluster_items[i] second_cluster = cluster_items[i + 1] first_variance = HRPOpt._get_cluster_var(cov, first_cluster) second_variance = HRPOpt._get_cluster_var(cov, second_cluster) alpha = 1 - first_variance / (first_variance + second_variance) w[first_cluster] *= alpha # 方差小的簇拿更多 w[second_cluster] *= 1 - alpha return w
_get_cluster_var 用簇内逆方差权重算组合方差(等价于簇内的最小方差组合):
@staticmethod def _get_cluster_var(cov, cluster_items): cov_slice = cov.loc[cluster_items, cluster_items] weights = 1 / np.diag(cov_slice) # 逆方差 weights /= weights.sum() return np.linalg.multi_dot((weights, cov_slice, weights))
💡 直觉:方差小的子簇「更稳」,分到更多权重;方差大的子簇「更险」,少给。沿树递归到底,叶节点(单个资产)的权重就出来了。整个过程只求簇内方差,从不求 Σ⁻¹。
HRP 接受两种输入:历史收益(推荐)或协方差矩阵。
from pypfopt import HRPOpt, expected_returns returns = expected_returns.returns_from_prices(prices) hrp = HRPOpt(returns) weights = hrp.optimize() weights = hrp.clean_weights()
hrp = HRPOpt(cov_matrix=S) hrp.optimize()
⚠️ 协方差用法无 returns:此时
portfolio_performance无法算收益和 Sharpe(源码里mu=None)。要算收益,还是用历史收益输入。
from pypfopt import HRPOpt, expected_returns, plotting # 准备数据 tickers = ["BLK", "BAC", "AAPL", "TM", "WMT", "JD", "INTU", "MA", "UL", "CVS", "DIS", "AMD", "NVDA", "PBI", "TGT"] prices = yf.download(tickers, period="max")["Close"] rets = expected_returns.returns_from_prices(prices) # HRP 优化 hrp = HRPOpt(rets) hrp.optimize() weights = hrp.clean_weights() pd.Series(weights).plot.pie(figsize=(10, 10)) # 性能 hrp.portfolio_performance(verbose=True) # Expected annual return: 24.5% # Annual volatility: 19.2% # Sharpe Ratio: 1.27
注意 portfolio_performance 的源码——它从 returns 自己算 μ 和 Σ(忽略你外面传的协方差):
def portfolio_performance(self, verbose=False, risk_free_rate=0.0, frequency=252): if self.returns is None: cov = self.cov_matrix mu = None else: cov = self.returns.cov() * frequency mu = self.returns.mean() * frequency return portfolio_performance(self.weights, mu, cov, verbose, risk_free_rate)
HRP 的最大优势之一是可解释性——能画出资产聚类树:
from pypfopt import plotting plotting.plot_dendrogram(hrp)
dendrogram 把每个资产的合并高度画出来:合并越早(高度越低)说明相关性越强。cookbook 5 提到 AMD 和 NVDA(都是半导体)被聚到一起——这种「同行业先聚类」的结构验证了算法的合理性。
💡 dendrogram 的实战价值:不仅用于验证,还可反向——你看着 dendrogram 决定要不要剔除高度相关的冗余资产,精简宇宙。
| 对比维度 | 均值-方差 | HRP |
|---|---|---|
| 协方差可逆 | 必须 | 不需要 |
| 收益估计 | 需要 μ | 不需要 |
| 算法 | 凸优化 | 聚类+递归 |
| 约束/目标 | 灵活(add_constraint) |
不支持(全自动化) |
| 样本外 | 不稳定 | 稳定 |
| 可解释 | 弱 | 强(dendrogram) |
| 适用 | 资产少、数据多 | 资产多、相关性高、数据少 |
⚠️ HRP 的代价:全自动化,不支持
add_constraint/add_objective,无法加 sector 约束、无法做市场中性。你需要精确约束时,HRP 不是首选。
d=√((1-corr)/2)→层次聚类→准对角化→递归二分分配。alpha=1-σ²₁/(σ²₁+σ²₂) 分权重,只算簇内逆方差组合方差,不求 Σ⁻¹。plotting.plot_dendrogram(hrp) 画聚类树,验证资产分组合理性。下一节,我们看 CLA 临界线算法——它不求逆也不聚类,而是精确求解带不等式约束的有效前沿拐点。