Py学习  »  Python

告别传统方差!Python 实现现代投资组合优化的 7 种风险度量方法

数据科学实战 • 6 天前 • 51 次点击  

欢迎加入专注于财经数据与量化投研的【数据科学实战】知识星球!在这里,您将获取持续更新的《财经数据宝典》和《量化投研宝典》,这两部宝典相辅相成,为您在量化投研道路上提供明确指引。 我们提供了精选的国内外量化投研的 180+ 篇高质量文章,并每日更新最新研究成果,涵盖策略开发、因子分析、风险管理等核心领域。 无论您是量化投资新手还是经验丰富的研究者,星球社区都能帮您少走弯路,事半功倍,共同探索数据驱动的投资世界!

引言

自 1952 年 Harry Markowitz 提出现代投资组合理论以来,均值-方差框架一直主导着投资组合构建领域。然而,传统方差作为风险度量存在致命缺陷:它同等惩罚收益和损失,忽视极端尾部事件,且假设收益呈正态分布。

本文将介绍如何使用 Python 实现更先进的风险度量方法,包括半方差(Semivariance)、条件风险价值(CVaR)、最大回撤(Maximum Drawdown)等,帮助你构建真正能抵御灾难性损失的投资组合。

为什么方差是有缺陷的风险度量?

传统均值-方差优化存在四个核心问题:

第一,对称性问题。方差同等对待上涨和下跌,10% 的下跌和 10% 的上涨在方差计算中权重相同,这与投资者的实际感受完全不符。

第二,聚焦中间值。方差衡量的是围绕均值的离散程度,而投资者真正关心的是尾部风险——那些可能抹去多年收益的灾难性损失。

第三,分布假设。方差假设收益呈正态分布,但真实市场收益具有肥尾特征和偏斜性。

第四,路径无关性。方差不关心收益的顺序,但稳步下跌和突然崩盘给投资者的感受截然不同。

方法一:下行风险组合优化

下偏矩(Lower Partial Moment,LPM)是下行风险的广义形式,其数学定义为:

LPM_α = E[(τ - R)₊^α]

其中 τ 是灾难阈值,α 是风险态度参数。当 α = 2 时,即为半方差(Semivariance)。

import cvxpy as cp
import numpy as np

def optimize_semivariance_portfolio (returns, tau=0, alpha=2, lambda_risk=1.0):
    """
    使用下行风险度量优化投资组合
    
    参数说明:
    returns: 历史收益率矩阵,形状为 (T, N),T 为时间周期数,N 为资产数量
    tau: 灾难阈值(最低可接受收益率)
    alpha: 风险态度参数(1=线性,2=二次,3=三次)
    lambda_risk: 风险厌恶系数
    
    返回:
    最优投资组合权重
    """

    T, N = returns.shape
    mu = returns.mean(axis=0)  # 计算预期收益率
    
    # 定义决策变量
    w = cp.Variable(N)  # 投资组合权重
    s = cp.Variable(T)  # 下行偏差的辅助变量
    
    # 目标函数:最大化收益 - λ * 下行风险
    expected_return = mu @ w
    downside_risk = (1/T) * cp.sum(cp.power(s, alpha))
    objective = cp.Maximize(expected_return - lambda_risk * downside_risk)
    
    # 约束条件
    constraints = [
        cp.sum(w) == 1,           # 满仓约束
        w >= 0,                    # 仅做多约束
        s >= 0,                    # 非负偏差
        s >= tau - returns @ w     # 捕捉下行偏差
    ]
    
    # 求解优化问题
    problem = cp.Problem(objective, constraints)
    problem.solve(solver=cp.ECOS)
    
    return w.value

# 示例:模拟 252 个交易日、10 种资产的收益率数据
np.random.seed(42)
returns = np.random.randn(25210) * 0.01 + 0.0003

# 使用不同的 α 值进行优化
weights_linear = optimize_semivariance_portfolio(returns, alpha=1)  # 线性惩罚
weights_semi = optimize_semivariance_portfolio(returns, alpha=2)    # 半方差
weights_cubic = optimize_semivariance_portfolio(returns, alpha=3)   # 三次惩罚

print("半方差优化权重:")
print(np.round(weights_semi, 4))

方法二:尾部风险组合优化(CVaR)

条件风险价值(CVaR)也称为预期损失(Expected Shortfall),它衡量的是超过 VaR 阈值后的平均损失。CVaR 的优势在于它是凸函数,可以高效求解。

def optimize_cvar_portfolio(returns, alpha=0.95, lambda_risk=1.0):
    """
    使用条件风险价值(CVaR)优化投资组合
    
    CVaR 表示在最差的 (1-α)% 情况下的平均损失
    例如 α=0.95 时,我们最小化最差 5% 情景下的平均损失
    
    参数说明:
    returns: 历史收益率矩阵,形状为 (T, N)
    alpha: 置信水平(常用 0.90、0.95 或 0.99)
    lambda_risk: 风险厌恶系数
    
    返回:
    最优权重、VaR 值、CVaR 值
    """

    T, N = returns.shape
    mu = returns.mean(axis=0)
    
    # 决策变量
    w = cp.Variable(N)      # 投资组合权重
    tau = cp.Variable()     # VaR 阈值
    u = cp.Variable(T)      # 超额损失变量
    
    # CVaR 目标函数
    expected_return = mu @ w
    cvar = tau + (1/(1 -alpha)) * (1/T) * cp.sum(u)
    objective = cp.Maximize(expected_return - lambda_risk * cvar)
    
    # 约束条件
    constraints = [
        cp.sum(w) == 1,              # 预算约束
        w >= 0,                       # 仅做多
        u >= 0,                       # 非负超额
        u >= -returns @ w - tau       # 捕捉超过 VaR 的损失
    ]
    
    # 求解
    problem = cp.Problem(objective, constraints)
    problem.solve(solver=cp.ECOS)
    
    # 提取结果
    optimal_w = w.value
    optimal_tau = tau.value
    optimal_cvar = tau.value + (1/(1-alpha)) * (1/T) * np.sum(u.value)
    
    return optimal_w, optimal_tau, optimal_cvar

# 示例:比较不同置信水平
np.random.seed(42)
returns = np.random.randn(25220) * 0.015 + 0.0004

w_90, var_90, cvar_90 = optimize_cvar_portfolio(returns, alpha=0.90)
w_95, var_95, cvar_95 = optimize_cvar_portfolio(returns, alpha=0.95)
w_99, var_99, cvar_99 = optimize_cvar_portfolio(returns, alpha=0.99)

print(f"CVaR 90%: {cvar_90:.4f}")
print(f"CVaR 95%: {cvar_95:.4f}")  # 最常用的置信水平
print(f"CVaR 99%: {cvar_99:.4f}")

方法三:回撤风险组合优化

回撤(Drawdown)捕捉的是投资者真正经历的痛苦:看着财富从峰值下跌。一个关键的心理洞察是:50% 的损失需要 100% 的收益才能恢复。

def optimize_cdar_portfolio(returns, alpha=0.95, lambda_risk=1.0):
    """
    最小化条件回撤风险(CDaR)
    
    CDaR 是最差回撤的平均值,比最大回撤更稳定
    因为它考虑多个糟糕情景,而不仅仅是单一最差情况
    
    参数说明:
    returns: 历史收益率矩阵
    alpha: 置信水平
    lambda_risk: 风险厌恶系数
    """

    T, N = returns.shape
    mu = returns.mean(axis=0)
    cum_returns = np.cumsum(returns, axis=0)  # 计算累计收益
    
    # 决策变量
    w = cp.Variable(N)       # 投资组合权重
    u = cp.Variable(T)       # 历史最高水位标记
    tau = cp.Variable()      # 回撤 VaR
    z = cp.Variable(T)       # 超额回撤
    
    # CDaR 目标函数
    expected_return = mu @ w
    cdar = tau + (1/(1-alpha)) * (1/T) * cp.sum(z)
    objective = cp.Maximize(expected_return - lambda_risk * cdar)
    
    # 约束条件
    constraints = [
        cp.sum(w) == 1,
        w >=  0,
        z >= 0,
    ]
    
    # 回撤计算约束
    for t in range(T):
        drawdown_t = u[t] - cum_returns[t, :] @ w
        constraints.append(u[t] >= cum_returns[t, :] @ w)
        if t > 0:
            constraints.append(u[t] >= u[t-1])  # 单调递增
        constraints.append(z[t] >= drawdown_t - tau)
    
    problem = cp.Problem(objective, constraints)
    problem.solve(solver=cp.ECOS)
    
    return w.value, tau.value

# 示例
np.random.seed(42)
returns = np.random.randn(25215) * 0.012 + 0.0003

w_cdar, dd_var = optimize_cdar_portfolio(returns, alpha=0.95, lambda_risk=5.0)
print(f"CDaR 95% 组合的回撤 VaR: {dd_var:.2%}")
print("优化权重:")
print(np.round(w_cdar, 4))

实测对比:哪种风险度量最优?

根据原文对 S&P 500 成分股的回测结果(2015-2020 年,每月再平衡,1 年回望期),不同风险度量的表现如下:

半方差(α=2)组合表现最佳,夏普比率达到 1.24,同时最大回撤比传统均值-方差组合降低了近 10%。熵风险价值(EVaR)在相同置信水平下持续优于 CVaR。极端度量如最大回撤组合表现较差,因为它们由单个数据点决定,导致不稳定。

如何选择风险度量?

对于大多数量化交易者,建议采用以下策略:

首先,将半方差作为均值-方差的默认升级方案。其次,在核心配置之上叠加 CVaR 或 EVaR 作为尾部风险覆盖层。最后,即使回撤不在优化目标中,也应将其作为健康指标进行监控。

选择时可参考以下决策流程:如果收益分布对称,使用均值-方差即可;如果主要担心尾部风险,选择 CVaR 或 EVaR;如果需要每日监控投资组合,选择 CDaR;其他情况下,半方差是最稳妥的选择。

总结

均值-方差优化仍是有用的基准方法,但在肥尾、结构性断裂和杠杆约束主导的市场中,它已不再足够。替代风险度量让你能够表达真正关心的内容:避免深度水下期、在尾部事件中存活、以及提供投资者能够坚持的更平滑的权益曲线。

本文介绍的三类方法——下行风险(半方差)、尾部风险(CVaR/EVaR)和路径相关风险(回撤)——都可以通过凸优化高效求解。如果你已经熟悉凸优化,现在就拥有了替换方差作为默认风险度量的所有要素,将风险管理从事后补救变成首要的设计选择。

参考文章

加入专注于财经数据与量化投研的知识星球【数据科学实战】,获取本文完整研究解析、代码实现细节。

财经数据与量化投研知识社区

核心权益如下:

  1. 赠送《财经数据宝典》完整文档,汇集多年财经数据维护经验
  2. 赠送《量化投研宝典》完整文档,汇集多年量化投研领域经验
  3. 赠送《PyBroker-入门及实战》视频课程,手把手学习量化策略开发
  4. 每日分享高质量量化投研文章(已更新 180+篇)、代码和相关资料
  5. 定期更新高频财经数据
  6. 参与年度不少于 10 次专属直播与录播课程
  7. 与核心开发者直接交流,解决实际问题
  8. 获取专业微信群交流机会和课程折扣

星球已有丰富内容积累,包括量化投研论文、财经高频数据、 PyBroker 视频教程、定期直播、数据分享和答疑解难。适合对量化投研和财经数据分析有兴趣的学习者及从业者。欢迎加入我们!

好文推荐

1. 用 Python 打造股票预测系统:Transformer 模型教程(一)

2. 用 Python 打造股票预测系统:Transformer 模型教程(二)

3. 用 Python 打造股票预测系统:Transformer 模型教程(三)

4. 用 Python 打造股票预测系统:Transformer 模型教程(完结)

5. 揭秘隐马尔可夫模型:因子投资的制胜武器

6. YOLO 也能预测股市涨跌?计算机视觉在股票市场预测中的应用

7. 金融 AI 助手:FinGPT 让你轻松掌握市场分析

8. 量化交易秘籍:为什么专业交易员都在用对数收益率?

9. Python 量化投资利器:Ridge、Lasso 和 Elastic Net 回归详解

10. 掌握金融波动率模型:完整 Python 实现指南

好书推荐



Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/189817