第 26 讲:风险平价(Risk Parity)与层级风险平价(HRP)
本讲核心目标
并非所有买方量化策略都以跟踪跑赢沪深 300 或中证 1000 等特定指数为目标。在 FOF 母基金配置、CTA 大宗商品多品种配置或**全天候绝对收益(All Weather)**投资中,投资人追求的是“穿越牛熊周期的平稳净值曲线”。
本讲将深入剖析传统市值加权与“股债 60/40”配置中严重的名义资金平衡 vs 真实风险独裁陷阱;基于欧拉齐次函数定理严密推导**边际风险贡献(MRC)**与等风险贡献平价(ERC)的凸对数障碍求解;掌握由量化先驱 Marcos López de Prado 提出、彻底摆脱协方差矩阵求逆数值病态难题的层级风险平价(Hierarchical Risk Parity, HRP)算法。
1. 经典文献与工业界理论基石
构建全天候大类资产配置与风险平价体系的核心参考文献:
- Dalio, Ray (1996). "The All Weather Strategy", Bridgewater Associates Research Papers.
桥水基金创始人瑞·达利欧创立的全天候策略白皮书,首次提出按经济增长与通胀的四象限宏观环境平价分配风险资产。
- Qian, Edward (2005). "Risk Parity Portfolios: Efficient Portfolios Through True Diversification", PanAgora Asset Management.
首次正式提出“风险平价(Risk Parity)”术语,推导了边际风险贡献(MRC)与资金权重、资产波动率及相关性的解析关系。
- Maillard, S., Roncalli, T., & Teïletche, J. (2010). "The Properties of Equally Weighted Risk Contribution Portfolios", The Journal of Portfolio Management, 36(4): 60-70.
严格证明了等风险贡献(ERC)投资组合解的存在性、唯一性,及其在最小方差组合与等权组合之间的优美几何性质。
- Spinu, Florin (2013). "An Algorithm for Computing Risk Parity Weights", SSRN Electronic Journal, 2297383.
创新性地将非凸的 ERC 方程组转化为严格凸的对数障碍优化问题,使风险平价权重求解实现毫秒级数值收敛。
- López de Prado, Marcos (2016). "Building Diversified Portfolios that Outperform Out of Sample", The Journal of Portfolio Management, 42(4): 59-69.
提出层级风险平价(HRP),利用图论树状聚类与拟对角化重排彻底颠覆马科维茨矩阵求逆,在多重共线性资产池中展现出无与伦比的样本外稳健性。
2. 传统资产配置的谎言:名义资金平衡 vs 真实风险独裁
全球最知名的经典多元配置当属“股债 60/40 组合”(
但在现代波动率显微镜下,这其实是一个严重的风险错配:
- 股票年化波动率通常在
左右; - 国债年化波动率仅有
左右; - 设股债相关系数
。
组合的总年化方差为:
其中股票方差贡献占比高达:
名义上四成资金买入了安全债券,实质上整个组合
桥水基金全天候策略的核心理念应运而生:放弃资金等额配置的执念,追求各资产对投资组合总风险的等额贡献(Risk Parity)!
3. 经典等风险贡献(ERC)数学原理推导
设资产池中有
组合总年化波动率为:
3.1 边际风险贡献(Marginal Risk Contribution, MRC)
第
3.2 总风险贡献(Total Risk Contribution, TRC)
将第
根据欧拉齐次函数定理(Euler's Homogeneous Function Theorem),由于组合波动率
所有资产的总风险贡献累加之和,严丝合缝、完全等于组合的总波动率!
3.3 等风险平价(ERC)均衡条件
等风险贡献平价要求每一个资产为组合贡献的绝对风险完全一致:
即对任意两个资产
- 直观规律:低波动资产(如国债)被赋予极高的资金权重,高波动资产(如股票、商品期货)被赋予极低的资金权重,从而实现真正的风险平衡!
3.4 Spinu (2013) 对数障碍凸优化高效求解法
直接求解非线性方程组极为困难。Spinu (2013) 证明了 ERC 等价于求解一个无约束凸规划问题:
对其求一阶偏导数并令其等于零:
求得最优解
4. 层级风险平价(Hierarchical Risk Parity, HRP)算法
尽管 ERC 解决了风险独裁,但在高维资产池或存在高相关性资产时,协方差矩阵近奇异,任何依赖矩阵乘法逆向求解的方法都会遭遇数值病态放大(Ill-Conditioned Inversion)。
2016 年,量化金融先驱 Marcos López de Prado 提出层级风险平价(HRP),将机器学习无监督树聚类与现代投资组合理论深度融合,彻底摒弃了矩阵求逆!
4.1 第一步:层次树状聚类(Tree Clustering)
- 将两两资产的相关系数
映射为严格符合度量空间公理的相关距离(Correlation Distance): - 计算距离向量间的欧氏距离构建仿射相似度:
- 使用 SciPy 的
linkage层次聚类算法构建二叉聚类树(Dendrogram)。
4.2 第二步:拟对角化重排(Quasi-Diagonalization)
按照聚类树的叶子节点遍历顺序,重新排列协方差矩阵的行与列。使相似度极高、相关性最强的资产在矩阵对角线上紧挨在一起,将原本杂乱无章的协方差矩阵重塑为清晰的分块对角矩阵结构。
4.3 第三步:递归二分反方差分配(Recursive Bisection)
从聚类树的根节点开始,递归地将当前资产集合
- 分别计算两个子簇的等价合成方差: 对子簇
,其内部基于反方差法初始化权重: 子簇 的综合风险方差为: - 同样计算子簇
的综合方差 ; - 计算两个子簇间的权重分配比例(方差越小,分配权重越大):
- 将当前父簇的总权重按
递归向下分配,直到叶子节点得到单资产的绝对权重!
5. 四大经典权重配置方案横向大比拼
| 配置算法 | 依赖输入 | 是否需要矩阵求逆 | 高相关资产表现 | 样本外夏普稳定性 | 核心适用场景 |
|---|---|---|---|---|---|
| 等权(EW / 1/N) | 仅资产列表 | ❌ 否 | 容易聚集同质风险 | 极稳健基准(常击败 Markowitz) | 零预测信息下的全盲配置 |
| 反方差(IVP) | 仅单资产方差 | ❌ 否 | 忽视跨资产相关性 | 较好,但可能超配高相关资产群 | 资产两两独立假设下的极简模型 |
| 等风险贡献(ERC) | 完整协方差矩阵 | 需迭代逼近 | 风险等分,但受高维估计误差影响 | 中等偏上 | 全天候宏观大类资产平衡配置 |
| 层级风险平价(HRP) | 完整协方差矩阵 | ❌ 完全免求逆 | 树聚类隔离同质资产,自动分层风控 | 极高(Prado 论文实证超越 Markowitz) | CTA 多品种池、多因子策略群与 FOF 绝对收益母基金 |
6. 工业级 HRP 资产配置引擎实战
在 tutorial/examples/lecture_26_risk_parity_and_hrp.py 中,我们实现了生产级 AshareHierarchicalRiskParity:
- 接收历史收益率,自动计算相关系数距离并调用 SciPy 层次聚类;
- 执行递归二分法确定各个股票或策略的 HRP 权重;
- 准确计算并验证各项资产的边际风险贡献(MRC)与总风险贡献(TRC);
- 提供等权(EW)、反方差(IVP)与 HRP 的风险分解报告。
可在终端直接运行验证:
python3 -m examples.lecture_26_risk_parity_and_hrp"""
教学用层级风险平价配置引擎 (AshareHierarchicalRiskParity)
采用相关距离直接 single-linkage 的 HRP 变体;不保证 ERC
包含:距离矩阵聚类、拟对角化重排、递归二分风险反比分配与 TRC 风险贡献度量
"""
from typing import Dict, List, Optional, Tuple
import numpy as np
import polars as pl
from scipy.cluster.hierarchy import linkage, to_tree
from scipy.spatial.distance import squareform
class AshareHierarchicalRiskParity:
"""
层级风险平价 (HRP) 资产配置优化器
"""
def __init__(self, annual_factor: float = 250.0):
if annual_factor <= 0:
raise ValueError("annual_factor 必须为正")
self.annual_factor = annual_factor
def _correl_distance(self, corr: np.ndarray) -> np.ndarray:
"""
计算相关系数距离矩阵 d_ij = sqrt(0.5 * (1 - rho_ij))
"""
dist = np.sqrt(0.5 * np.maximum(0.0, 1.0 - corr))
np.fill_diagonal(dist, 0.0)
return dist
def _get_quasi_diag_order(self, link_matrix: np.ndarray) -> List[int]:
"""
从层次聚类树链接矩阵中提取叶子节点的拟对角化顺序
"""
root = to_tree(link_matrix, rd=False)
order = []
def _traverse(node):
if node.is_leaf():
order.append(node.id)
else:
_traverse(node.left)
_traverse(node.right)
_traverse(root)
return order
def _get_cluster_var(self, cov: np.ndarray, cluster_items: List[int]) -> float:
"""
计算某个子簇的反方差加权综合风险方差
"""
sub_cov = cov[np.ix_(cluster_items, cluster_items)]
# 反方差加权
diag_inv = 1.0 / np.maximum(np.diag(sub_cov), 1e-8)
w = diag_inv / np.sum(diag_inv)
cluster_var = float(w.T @ sub_cov @ w)
return cluster_var
def _recursive_bisection(
self,
cov: np.ndarray,
ordered_indices: List[int]
) -> np.ndarray:
"""
自顶向下递归二分分配权重
"""
weights = np.ones(len(ordered_indices))
clusters = [ordered_indices]
while len(clusters) > 0:
next_clusters = []
for cluster in clusters:
if len(cluster) > 1:
# 将当前簇二分为前半段与后半段
mid = len(cluster) // 2
c1 = cluster[:mid]
c2 = cluster[mid:]
# 计算两子簇综合方差
v1 = self._get_cluster_var(cov, c1)
v2 = self._get_cluster_var(cov, c2)
# 计算反方差权重分配系数 alpha
alpha1 = v2 / (v1 + v2 + 1e-12)
alpha2 = 1.0 - alpha1
# 缩放子簇对应原始位置的权重
weights[c1] *= alpha1
weights[c2] *= alpha2
next_clusters.append(c1)
next_clusters.append(c2)
clusters = next_clusters
return weights
def compute_hrp_weights(
self,
cov_matrix: np.ndarray,
asset_names: Optional[List[str]] = None
) -> Dict[str, float]:
"""
计算全套 HRP 配置权重
"""
cov_matrix = np.asarray(cov_matrix, dtype=float)
if cov_matrix.ndim != 2 or not len(cov_matrix) or cov_matrix.shape[0] != cov_matrix.shape[1]:
raise ValueError("协方差须为非空方阵")
if not np.isfinite(cov_matrix).all() or not np.allclose(cov_matrix, cov_matrix.T) or np.linalg.eigvalsh(cov_matrix).min() < -1e-10 or np.any(np.diag(cov_matrix) <= 0):
raise ValueError("协方差须对称半正定,各资产方差为正")
N = cov_matrix.shape[0]
if asset_names is None:
asset_names = [f"Asset_{i}" for i in range(N)]
if len(asset_names) != N or len(set(asset_names)) != N:
raise ValueError("资产名须唯一且与矩阵维度一致")
if N == 1:
return {asset_names[0]: 1.}
# 1. 计算相关系数矩阵与相关距离
vols = np.sqrt(np.diag(cov_matrix))
outer_vols = np.outer(vols, vols)
corr = cov_matrix / np.maximum(outer_vols, 1e-8)
corr = np.clip(corr, -1.0, 1.0)
np.fill_diagonal(corr, 1.0)
dist_mat = self._correl_distance(corr)
# 2. 层次聚类 (Single Linkage)
condensed_dist = squareform(dist_mat, checks=False)
link = linkage(condensed_dist, method="single")
# 3. 拟对角化叶子节点排序
ordered_ids = self._get_quasi_diag_order(link)
# 4. 递归二分风险反比赋权
raw_weights = self._recursive_bisection(cov_matrix, ordered_ids)
# 映射回原始资产顺序并归一化
final_weights = raw_weights / np.sum(raw_weights)
weight_dict = {
asset_names[i]: float(final_weights[i])
for i in range(N)
}
return weight_dict
def evaluate_risk_contributions(
self,
weights: np.ndarray,
cov_matrix: np.ndarray
) -> Tuple[np.ndarray, np.ndarray, float]:
"""
计算边际风险贡献 (MRC) 与总风险贡献 (TRC)
输入日频协方差;返回全部年化的 (MRC向量, TRC向量, 组合波动率)
"""
port_var = float(weights.T @ cov_matrix @ weights)
if port_var <= 0:
raise ValueError("零或负组合方差时波动率风险贡献未定义")
port_vol = np.sqrt(port_var)
# MRC = (Σ w) / sigma_p
mrc = (cov_matrix @ weights) / port_vol
# TRC = w * MRC
trc = weights * mrc
ann_port_vol = port_vol * np.sqrt(self.annual_factor)
return mrc * np.sqrt(self.annual_factor), trc * np.sqrt(self.annual_factor), ann_port_vol
# ================= 单元测试与全天候资产配置演示 =================
if __name__ == "__main__":
np.random.seed(42)
# 模拟包含 6 大经典资产类别的全天候多资产池:
# 1. 股票资产 (A股大盘 300, 中证 1000) - 年化波动 ~22%
# 2. 债券资产 (十年期国债 ETF, 信用债) - 年化波动 ~4%
# 3. 大宗商品 (黄金 ETF, 南华商品指数) - 年化波动 ~15%
asset_classes = [
"沪深300_ETF", "中证1000_ETF",
"十年国债_ETF", "企业信用债_ETF",
"黄金_ETF", "南华商品_ETF"
]
N = len(asset_classes)
# 设定逼近真实市场的年化波动率与相关性
vols_annual = np.array([0.20, 0.26, 0.035, 0.045, 0.16, 0.18])
corr_matrix = np.array([
[ 1.00, 0.82, -0.15, -0.05, 0.08, 0.35],
[ 0.82, 1.00, -0.18, -0.08, 0.05, 0.42],
[-0.15, -0.18, 1.00, 0.65, 0.25, -0.12],
[-0.05, -0.08, 0.65, 1.00, 0.18, -0.08],
[ 0.08, 0.05, 0.25, 0.18, 1.00, 0.30],
[ 0.35, 0.42, -0.12, -0.08, 0.30, 1.00]
])
# 重构日频协方差矩阵
outer_v = np.outer(vols_annual / np.sqrt(250.0), vols_annual / np.sqrt(250.0))
daily_cov = corr_matrix * outer_v
# 1. 运行 HRP 引擎
hrp_engine = AshareHierarchicalRiskParity(annual_factor=250.0)
hrp_weights_dict = hrp_engine.compute_hrp_weights(daily_cov, asset_classes)
hrp_w = np.array(list(hrp_weights_dict.values()))
# 2. 对比等权组合 (Equal Weight)
ew_w = np.ones(N) / N
ivp_w = 1 / np.diag(daily_cov)
ivp_w /= ivp_w.sum()
_, ivp_trc, ivp_vol = hrp_engine.evaluate_risk_contributions(ivp_w, daily_cov)
# 3. 评估风险贡献
_, ew_trc, ew_vol = hrp_engine.evaluate_risk_contributions(ew_w, daily_cov)
_, hrp_trc, hrp_vol = hrp_engine.evaluate_risk_contributions(hrp_w, daily_cov)
print("=" * 80)
print(f"📊 全天候多资产 HRP 风险平价对比报告")
print("=" * 80)
print(f"{'资产名称':<16} | {'等权资金比例':<12} | {'等权风险占比':<12} | {'HRP资金权重':<12} | {'HRP风险占比':<12}")
print("-" * 80)
for i, name in enumerate(asset_classes):
ew_r_share = ew_trc[i] / np.sum(ew_trc) * 100
hrp_r_share = hrp_trc[i] / np.sum(hrp_trc) * 100
print(f"{name:<16} | {ew_w[i]*100:10.2f}% | {ew_r_share:10.2f}% | {hrp_w[i]*100:10.2f}% | {hrp_r_share:10.2f}%")
print("-" * 80)
print(f"IVP 权重: {np.round(ivp_w, 4)};风险占比: {np.round(ivp_trc / ivp_vol, 4)};年化波动: {ivp_vol:.2%}")
np.testing.assert_allclose(hrp_trc.sum(), hrp_vol)
print("HRP 不保证资产风险贡献相等;未提供收益序列,故不计算夏普比率。")
print(f"【组合总年化波动率对比】")
print(f" • 传统等权组合 (EW) 年化波动 : {ew_vol * 100:6.2f}%")
print(f" • 层级风险平价 (HRP) 年化波动 : {hrp_vol * 100:6.2f}%")
print("=" * 80)7. 第六模块核心交付 Checklist
学完本模块,你已经彻底跨过了量化投资中最深的一道数学护城河——现代投资组合与风险优化管理:
- [x] Barra 结构化风险解构(第 23 讲):
- 破解了
时样本协方差矩阵的维度灾难; - 掌握了
的低维投影、CNE6 十大风格因子、EWMA 协方差估计与特异性方差贝叶斯收缩。
- 破解了
- [x] 指数增强凸优化求解(第 24 讲):
- 建立了基于 cvxpy / Clarabel 的凸优化生产级参考实现;
- 实现了 行业硬中性、市值偏离容差 与 预测跟踪误差(TE)硬上限约束。
- [x] 换手率约束与交易成本惩罚(第 25 讲):
- 洞悉了优化器过度换手的病理根源;
- 融合了 L1 线性税费与 L2 市场冲击成本,绘制了扣费净超额的帕累托前沿曲线。
- [x] 全天候层级风险平价(第 26 讲):
- 推导了边际风险贡献(MRC)与欧拉齐次函数定理;
- 掌握了免矩阵求逆、基于图聚类的 HRP 递归二分算法,为大类资产配置提供了强悍武器。
至此,我们的选股信号已通过凸优化器,被炼制为经受住严密风控约束的目标股票持仓权重
在接下来的 第七模块:严苛全真回测与执行摩擦建模 中,我们将把优化生成的仓位送入最残酷的实盘仿真战场——事件驱动回测引擎、涨跌停真实撮合限制与滑点冲击精算!