跳至正文
MODULE 06风险模型与组合优化4 讲 · PORTFOLIO CONSTRUCTION
LECTURE 26共 31 讲预计阅读 55 分钟

第 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. 经典文献与工业界理论基石 ​

构建全天候大类资产配置与风险平价体系的核心参考文献:

  1. Dalio, Ray (1996). "The All Weather Strategy", Bridgewater Associates Research Papers.

    桥水基金创始人瑞·达利欧创立的全天候策略白皮书,首次提出按经济增长与通胀的四象限宏观环境平价分配风险资产。

  2. Qian, Edward (2005). "Risk Parity Portfolios: Efficient Portfolios Through True Diversification", PanAgora Asset Management.

    首次正式提出“风险平价(Risk Parity)”术语,推导了边际风险贡献(MRC)与资金权重、资产波动率及相关性的解析关系。

  3. 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)投资组合解的存在性、唯一性,及其在最小方差组合与等权组合之间的优美几何性质。

  4. Spinu, Florin (2013). "An Algorithm for Computing Risk Parity Weights", SSRN Electronic Journal, 2297383.

    创新性地将非凸的 ERC 方程组转化为严格凸的对数障碍优化问题,使风险平价权重求解实现毫秒级数值收敛。

  5. 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 组合”(60% 资金配置股票指数,40% 资金配置国债指数)。

但在现代波动率显微镜下,这其实是一个严重的风险错配:

  • 股票年化波动率通常在 σequity≈18% 左右;
  • 国债年化波动率仅有 σbond≈4% 左右;
  • 设股债相关系数 ρ≈0。

组合的总年化方差为:

σp2=wE2σE2+wB2σB2=0.62×0.182+0.42×0.042=0.011664+0.000256=0.011920

其中股票方差贡献占比高达:

0.0116640.011920≈97.85%!
课程图示
可放大查看细节,点击百分比恢复原始大小。

名义上四成资金买入了安全债券,实质上整个组合 98% 的风险全由股票单独支配!一旦股票遭遇熊市,债券杯水车薪的收益根本无力回天。

桥水基金全天候策略的核心理念应运而生:放弃资金等额配置的执念,追求各资产对投资组合总风险的等额贡献(Risk Parity)!


3. 经典等风险贡献(ERC)数学原理推导 ​

设资产池中有 N 种资产(如股票指数、国债、黄金、CTA 商品、中性 Alpha),其年化协方差矩阵为 Σ∈RN×N,配置权重向量为 w∈RN(满足 1Tw=1,wi≥0)。

组合总年化波动率为:

σp(w)=wTΣw

3.1 边际风险贡献(Marginal Risk Contribution, MRC) ​

第 i 种资产权重的微小变动对总组合波动率的偏导数定义为边际风险贡献:

MRCi=∂σp∂wi=∂wTΣw∂wi=(Σw)iwTΣw=(Σw)iσp

3.2 总风险贡献(Total Risk Contribution, TRC) ​

将第 i 种资产的权重乘以其边际风险贡献,即为该资产向组合注入的绝对风险贡献值:

TRCi=wi⋅MRCi=wi(Σw)iσp

根据欧拉齐次函数定理(Euler's Homogeneous Function Theorem),由于组合波动率 σp(w) 是权重向量 w 的 1 次正齐次函数(即 σp(cw)=cσp(w)):

∑i=1NTRCi=∑i=1Nwi∂σp∂wi=wTΣwσp=σp2σp=σp

所有资产的总风险贡献累加之和,严丝合缝、完全等于组合的总波动率!

3.3 等风险平价(ERC)均衡条件 ​

等风险贡献平价要求每一个资产为组合贡献的绝对风险完全一致:

TRC1=TRC2=⋯=TRCN=σpN

即对任意两个资产 i,j,必须严格满足:

wi(Σw)i=wj(Σw)j,∀i,j
  • 直观规律:低波动资产(如国债)被赋予极高的资金权重,高波动资产(如股票、商品期货)被赋予极低的资金权重,从而实现真正的风险平衡!

3.4 Spinu (2013) 对数障碍凸优化高效求解法 ​

直接求解非线性方程组极为困难。Spinu (2013) 证明了 ERC 等价于求解一个无约束凸规划问题:

minyf(y)=12yTΣy−c∑i=1Nln⁡(yi)

对其求一阶偏导数并令其等于零:

∇f(y)=Σy−c(1/y1⋮1/yN)=0⟹yi(Σy)i=c,∀i

求得最优解 y∗ 后,归一化即可得到完美的 ERC 权重:

w∗=y∗∑j=1Nyj∗

4. 层级风险平价(Hierarchical Risk Parity, HRP)算法 ​

尽管 ERC 解决了风险独裁,但在高维资产池或存在高相关性资产时,协方差矩阵近奇异,任何依赖矩阵乘法逆向求解的方法都会遭遇数值病态放大(Ill-Conditioned Inversion)。

2016 年,量化金融先驱 Marcos López de Prado 提出层级风险平价(HRP),将机器学习无监督树聚类与现代投资组合理论深度融合,彻底摒弃了矩阵求逆!

课程图示
可放大查看细节,点击百分比恢复原始大小。

4.1 第一步:层次树状聚类(Tree Clustering) ​

  1. 将两两资产的相关系数 ρi,j 映射为严格符合度量空间公理的相关距离(Correlation Distance):di,j=12(1−ρi,j)∈[0,1]
  2. 计算距离向量间的欧氏距离构建仿射相似度:d~i,j=∑k=1N(dk,i−dk,j)2
  3. 使用 SciPy 的 linkage 层次聚类算法构建二叉聚类树(Dendrogram)。

4.2 第二步:拟对角化重排(Quasi-Diagonalization) ​

按照聚类树的叶子节点遍历顺序,重新排列协方差矩阵的行与列。使相似度极高、相关性最强的资产在矩阵对角线上紧挨在一起,将原本杂乱无章的协方差矩阵重塑为清晰的分块对角矩阵结构。

4.3 第三步:递归二分反方差分配(Recursive Bisection) ​

从聚类树的根节点开始,递归地将当前资产集合 V 拆分为两个互不相交的子簇 V1 和 V2:

  1. 分别计算两个子簇的等价合成方差: 对子簇 V1,其内部基于反方差法初始化权重:w~1=diag(Σ1)−1Tr(diag(Σ1)−1)子簇 V1 的综合风险方差为:v~1=w~1TΣ1w~1
  2. 同样计算子簇 V2 的综合方差 v~2;
  3. 计算两个子簇间的权重分配比例(方差越小,分配权重越大):α1=1−v~1v~1+v~2=v~2v~1+v~2,α2=1−α1
  4. 将当前父簇的总权重按 α1,α2 递归向下分配,直到叶子节点得到单资产的绝对权重!

5. 四大经典权重配置方案横向大比拼 ​

配置算法依赖输入是否需要矩阵求逆高相关资产表现样本外夏普稳定性核心适用场景
等权(EW / 1/N)仅资产列表❌ 否容易聚集同质风险极稳健基准(常击败 Markowitz)零预测信息下的全盲配置
反方差(IVP)仅单资产方差❌ 否忽视跨资产相关性较好,但可能超配高相关资产群资产两两独立假设下的极简模型
等风险贡献(ERC)完整协方差矩阵 Σ需迭代逼近风险等分,但受高维估计误差影响中等偏上全天候宏观大类资产平衡配置
层级风险平价(HRP)完整协方差矩阵 Σ❌ 完全免求逆树聚类隔离同质资产,自动分层风控极高(Prado 论文实证超越 Markowitz)CTA 多品种池、多因子策略群与 FOF 绝对收益母基金

6. 工业级 HRP 资产配置引擎实战 ​

在 tutorial/examples/lecture_26_risk_parity_and_hrp.py 中,我们实现了生产级 AshareHierarchicalRiskParity:

  1. 接收历史收益率,自动计算相关系数距离并调用 SciPy 层次聚类;
  2. 执行递归二分法确定各个股票或策略的 HRP 权重;
  3. 准确计算并验证各项资产的边际风险贡献(MRC)与总风险贡献(TRC);
  4. 提供等权(EW)、反方差(IVP)与 HRP 的风险分解报告。

可在终端直接运行验证:

bash
python3 -m examples.lecture_26_risk_parity_and_hrp
py
"""
教学用层级风险平价配置引擎 (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 讲):
    • 破解了 N≫T 时样本协方差矩阵的维度灾难;
    • 掌握了 Σ=XFXT+Δ 的低维投影、CNE6 十大风格因子、EWMA 协方差估计与特异性方差贝叶斯收缩。
  • [x] 指数增强凸优化求解(第 24 讲):
    • 建立了基于 cvxpy / Clarabel 的凸优化生产级参考实现;
    • 实现了 行业硬中性、市值偏离容差 与 预测跟踪误差(TE)硬上限约束。
  • [x] 换手率约束与交易成本惩罚(第 25 讲):
    • 洞悉了优化器过度换手的病理根源;
    • 融合了 L1 线性税费与 L2 市场冲击成本,绘制了扣费净超额的帕累托前沿曲线。
  • [x] 全天候层级风险平价(第 26 讲):
    • 推导了边际风险贡献(MRC)与欧拉齐次函数定理;
    • 掌握了免矩阵求逆、基于图聚类的 HRP 递归二分算法,为大类资产配置提供了强悍武器。

至此,我们的选股信号已通过凸优化器,被炼制为经受住严密风控约束的目标股票持仓权重 w∗。

在接下来的 第七模块:严苛全真回测与执行摩擦建模 中,我们将把优化生成的仓位送入最残酷的实盘仿真战场——事件驱动回测引擎、涨跌停真实撮合限制与滑点冲击精算!

QUANTML · Quantitative Research Handbook