好的,我们来深入解析 PyPortfolioOpt 和 Riskfolio-Lib 这两个专注于投资组合优化的 Python 库,并提供具体的代码示例。
目录
- 引言
- 核心对比维度
- PyPortfolioOpt 深入解析
- 3.1 核心优势与设计哲学
- 3.2 主要功能模块详解
- 3.3 优点
- 3.4 缺点
- 3.5 代码示例:最大化夏普比率
- Riskfolio-Lib 深入解析
- 4.1 核心优势与设计哲学
- 4.2 主要功能模块详解
- 4.3 优点
- 4.4 缺点
- 4.5 代码示例:最小化条件风险价值 (CVaR)
- 总结对比
- 选择建议
- 注意事项
1. 引言
PyPortfolioOpt 和 Riskfolio-Lib 是 Python 量化金融生态中进行投资组合优化的两大主力军。它们都旨在将复杂的优化理论转化为易于使用的代码接口,但各自的设计哲学、功能侧重和模型覆盖范围有所不同。本篇将对两者进行更深入的剖析和比较。
2. 核心对比维度
- 模型覆盖范围: 支持的优化理论和具体模型。
- 功能深度: 在收益/风险估计、约束处理、后处理等方面的细节功能。
- API 设计与易用性: 代码接口的直观性、学习曲线。
- 灵活性与扩展性: 自定义和集成的便利程度。
- 风险管理侧重: 对不同风险度量(如波动率、CVaR、回撤)的支持程度。
- 可视化: 内置图形化展示能力。
- 社区与维护: 项目活跃度和支持情况。
3. PyPortfolioOpt 深入解析
3.1 核心优势与设计哲学
PyPortfolioOpt 的核心设计哲学是易用性和对经典模型的良好实现。它旨在降低使用门槛,让用户能够快速、便捷地应用 Markowitz 均值-方差模型及其变种、Black-Litterman 模型以及 HRP 等常用方法。其 API 设计力求直观,贴近金融实践中的概念。
3.2 主要功能模块详解
-
预期收益估计 (
expected_returns
):- 提供多种计算预期年化收益率的方法:
mean_historical_return
: 历史算术平均收益率。ema_historical_return
: 指数加权移动平均历史收益率。capm_return
: 基于资本资产定价模型 (CAPM) 的预期收益。
- 允许用户传入自定义的预期收益向量。
- 提供多种计算预期年化收益率的方法:
-
风险模型 (
risk_models
):- 计算资产的协方差矩阵,这是风险度量(波动率)的基础。
- 提供多种协方差矩阵估计方法:
sample_cov
: 样本协方差矩阵(简单但可能不稳定)。exp_cov
: 指数加权移动协方差矩阵。- 收缩估计 (Shrinkage): 如
ledoit_wolf
,oracle_approximating
,通过向特定结构(如等相关矩阵)收缩来提高样本外稳定性,非常实用。 CovarianceShrinkage
类提供统一接口。
- 支持半协方差 (semicovariance) 用于计算下行风险。
- 支持协方差矩阵的各种修复方法(如处理非正定矩阵)。
-
优化器 (
efficient_frontier
,cla
,hierarchical_portfolio
,black_litterman
):EfficientFrontier
: 核心模块,用于均值-方差优化。- 内置目标函数:
max_sharpe()
,min_volatility()
,efficient_risk()
,efficient_return()
。 - 底层使用
scipy.optimize
进行二次规划求解。 - 支持添加多种约束:权重上下限 (
add_constraint
,add_objective
),头寸数量限制,行业/分组约束等。
- 内置目标函数:
CLA
(Critical Line Algorithm): 精确计算整个有效前沿,无需预设风险或收益目标。hierarchical_portfolio
: 实现分层风险平价 (HRP) 算法。black_litterman
: 实现 Black-Litterman 模型,结合市场均衡和主观观点。
-
后处理 (
discrete_allocation
):- 将优化得到的理想(连续)权重转换为实际可交易的离散股数。
- 提供基于整数规划 (
cvxpy
) 或贪心算法的方法。 - 计算剩余未分配的现金。
3.3 优点
- 易学易用: API 设计直观,文档清晰,示例丰富,学习曲线相对平缓。
- 经典模型覆盖: 很好地实现了最常用、最经典的组合优化模型(MVO, BL, HRP)。
- 健壮的协方差估计: 内置多种实用的协方差收缩方法,提高了优化结果的稳定性。
- 良好的集成性: 易于与 Pandas 等数据科学库配合使用。
- 实用的后处理: 离散分配功能非常贴合实际交易需求。
3.4 缺点
- 模型相对有限: 对一些更现代或特定的风险模型(如 CVaR, CDaR, Omega Ratio 优化)支持不足。
- 约束灵活性: 虽然支持多种约束,但在定义非常复杂的自定义约束时可能不如通用优化库灵活。
- 风险度量单一: 主要围绕波动率(或下行偏差)作为风险度量。
3.5 代码示例:最大化夏普比率
import pandas as pd
import numpy as np
from pypfopt import EfficientFrontier, risk_models, expected_returns
# 1. 准备数据 (假设已有包含调整后收盘价的 DataFrame `prices`)
# 创建示例价格数据 (实际应加载真实数据)
dates = pd.date_range(start='2020-01-01', end='2022-12-31', freq='B') # 工作日
n_assets = 5
np.random.seed(0)
prices_data = np.random.randn(len(dates), n_assets).cumsum(axis=0) + 100
assets = [f'Asset_{i}' for i in range(n_assets)]
prices = pd.DataFrame(prices_data, index=dates, columns=assets)
# 2. 计算预期收益率和协方差矩阵
# 使用简单的历史均值和样本协方差
mu = expected_returns.mean_historical_return(prices)
# 使用更稳健的 Ledoit-Wolf 收缩协方差矩阵
S = risk_models.CovarianceShrinkage(prices).ledoit_wolf()
print("预期年化收益率:")
print(mu)
print("\n年化协方差矩阵 (Ledoit-Wolf):")
print(S.round(4))
# 3. 初始化 EfficientFrontier 对象
# 可以设置权重上下限,例如每个资产权重在 0 到 0.3 之间
ef = EfficientFrontier(mu, S, weight_bounds=(0, 0.3))
# 4. 执行优化:最大化夏普比率
# 假设无风险利率为 0.02
raw_weights = ef.max_sharpe(risk_free_rate=0.02)
# 清理权重(例如,去除极小的权重并重新归一化)
cleaned_weights = ef.clean_weights()
print("\n优化得到的原始权重:")
print(raw_weights) # 字典形式
print("\n清理后的权重:")
print(cleaned_weights) # 字典形式,更易读
# 5. 查看预期组合表现
expected_annual_return, annual_volatility, sharpe_ratio = ef.portfolio_performance(verbose=True, risk_free_rate=0.02)
# 6. (可选)离散分配
# 假设投资组合总金额为 100,000 美元,获取最近一天的价格
from pypfopt import DiscreteAllocation
latest_prices = prices.iloc[-1]
total_portfolio_value = 100000
da = DiscreteAllocation(cleaned_weights, latest_prices, total_portfolio_value=total_portfolio_value)
allocation, leftover = da.greedy_portfolio() # 使用贪心算法
print("\n离散分配 (股数):")
print(allocation)
print(f"剩余现金: ${leftover:.2f}")
4. Riskfolio-Lib 深入解析
4.1 核心优势与设计哲学
Riskfolio-Lib 的核心优势在于其模型的多样性和对风险的深度关注。它不仅包含经典模型,更着重于实现多种基于不同风险度量的优化方法(如 CVaR, CDaR)、风险平价策略以及基于聚类的现代组合构建技术 (HRP, HERC)。其设计围绕一个核心的 Portfolio
类展开,通过设置不同的参数来调用不同的模型和风险度量。
4.2 主要功能模块详解
-
核心类 (
Portfolio
):- 几乎所有的操作都围绕这个类进行。初始化时通常传入收益率 DataFrame (
returns=...
)。 - 封装了数据处理、参数设置、优化执行和结果访问等功能。
- 几乎所有的操作都围绕这个类进行。初始化时通常传入收益率 DataFrame (
-
资产分配模型 (
model=...
参数):'Classic'
: 均值-方差类模型。可以通过obj
参数指定目标函数(如'Sharpe'
,'MinRisk'
,'Utility'
,'MaxRet'
),并通过rm
参数指定风险度量。'BL'
: Black-Litterman 模型。'FM'
: 因子模型(需要提供因子数据)。'RP'
: 风险平价 (Risk Parity)。'RB'
: 风险预算 (Risk Budgeting),允许为资产或组别设定风险贡献目标。'HRP'
: 分层风险平价 (Hierarchical Risk Parity)。'HERC'
: 分层等风险贡献 (Hierarchical Equal Risk Contribution)。'NCO'
: Nested Clustered Optimization。
-
风险度量 (
rm=...
参数):- 这是 Riskfolio-Lib 的一大特色,支持多种风险度量用于优化目标或约束:
'MV'
(Mean-Variance): 标准差/方差。'MAD'
(Mean Absolute Deviation): 平均绝对离差。'MSV'
(Mean Semi-Variance): 半方差(下行风险)。'FLPM'
和'SLPM'
(First/Second Lower Partial Moment): 低阶局部矩。'CVaR'
(Conditional Value at Risk / Expected Shortfall): 条件风险价值,关注尾部损失均值。'CDaR'
(Conditional Drawdown at Risk): 条件最大回撤风险。'EDaR'
(Entropic Drawdown at Risk)。'UCI'
(Ulcer Index): 溃疡指数,衡量回撤深度和持续时间。- 还有基于 Gini Mean Difference (GMD) 等的度量。
- 这是 Riskfolio-Lib 的一大特色,支持多种风险度量用于优化目标或约束:
-
目标函数 (
obj=...
参数):'MinRisk'
: 最小化指定的风险度量 (rm
)。'Utility'
: 最大化效用函数(通常是 收益 - lambda * 风险)。'Sharpe'
: 最大化(广义)夏普比率(收益 / 风险度量)。'MaxRet'
: 在给定风险约束下最大化收益。
-
其他重要参数/方法:
hist
: 是否使用历史情景法(对非正态分布、复杂风险度量如 CVaR 很重要)。rf
: 无风险利率。l
: 风险厌恶系数(用于 Utility 目标)。method_mu
: 预期收益估计方法。method_cov
: 协方差矩阵估计方法(同样支持收缩等)。constraints
: 添加各种约束。plot_...
方法: 提供多种可视化功能,如有效前沿、风险贡献、聚类树状图等。
4.3 优点
- 模型极其丰富: 覆盖范围远超 PyPortfolioOpt,尤其是在风险度量和现代组合理论方面。
- 风险管理强大: 对 CVaR、CDaR 等尾部风险和回撤风险的优化支持是其核心优势。
- 灵活性高: 通过参数组合可以实现非常多样化的优化目标和策略。
- 因子模型支持: 内置了因子模型优化功能。
- 聚类方法先进: 提供了 HRP, HERC, NCO 等基于聚类的先进配置方法。
- 可视化能力强: 内置了多种实用的绘图功能。
4.4 缺点
- 学习曲线较陡: 由于功能和参数众多,初学者可能需要更多时间来熟悉其 API 和概念。
- API 封装度高: 有时不如 PyPortfolioOpt 的模块化设计直观,所有功能集中在
Portfolio
类中。 - 文档有时需细读: 虽然文档也比较全,但理解各种参数组合的效果可能需要仔细阅读和实践。
4.5 代码示例:最小化条件风险价值 (CVaR)
import pandas as pd
import numpy as np
import riskfolio as rp # 通常导入为 rp
# 1. 准备数据 (Riskfolio 通常直接使用收益率 DataFrame)
# 创建示例 **日收益率** 数据 (实际应加载真实数据)
dates = pd.date_range(start='2020-01-01', end='2022-12-31', freq='B')
n_assets = 5
np.random.seed(0)
# 模拟日收益率,均值略为正,有波动
returns_data = np.random.normal(loc=0.0005, scale=0.01, size=(len(dates), n_assets))
assets = [f'Asset_{i}' for i in range(n_assets)]
returns = pd.DataFrame(returns_data, index=dates, columns=assets)
print("日收益率数据 (前5行):")
print(returns.head())
# 2. 初始化 Portfolio 对象
port = rp.Portfolio(returns=returns)
# 3. 设置参数
# 计算预期收益和协方差(可选,不设置则使用历史数据)
# Riskfolio 提供了内置方法,也可以自己算好传入
port.assets_stats(method_mu='hist', method_cov='hist', d=0.94) # d 用于指数加权
# 4. 执行优化:使用经典模型框架,但目标是最小化 CVaR
# model='Classic': 使用均值-方差类框架
# rm='CVaR': 指定风险度量为条件风险价值 (Expected Shortfall)
# obj='MinRisk': 目标是最小化所选的风险度量 (CVaR)
# rf=0: 无风险利率(如果计算夏普类指标时需要)
# l=0: 风险厌恶系数(最小化风险时通常不需要)
# alpha=0.05: CVaR 的置信水平 (例如,关注最差的 5% 情况)
model = 'Classic'
rm = 'CVaR'
obj = 'MinRisk'
hist = True # 使用历史情景法计算 CVaR
alpha = 0.05 # 置信水平 for CVaR
# 添加权重约束(可选,例如多头,总和为1)
port.upper_risk_t = None # 如果需要约束组合风险上限
# port.lower_risk_t = None
# port.budget = 1 # 默认权重和为1
port.lower_w = np.array([0]*n_assets) # 权重下限为0 (多头)
port.upper_w = np.array([1]*n_assets) # 权重上限为1
# 运行优化
weights = port.optimization(model=model, rm=rm, obj=obj, rf=0, l=0, hist=hist, alpha=alpha)
print(f"\n优化得到的权重 (最小化 {alpha*100:.1f}% CVaR):")
print(weights.T) # 显示权重 DataFrame
# 5. (可选)查看其他结果或绘图
# 例如,绘制有效前沿(基于 CVaR)
# 注意:绘制有效前沿通常需要计算一系列点,这里只做了单点优化
# 可以通过 port.efficient_frontier() 计算前沿点
# rp.plot_frontier(w_frontier=port.frontier, mu=port.mu, cov=port.cov, returns=port.returns, rm=rm, rf=0, alpha=alpha, cmap='viridis', w=weights, label='Min CVaR Portfolio', marker='*', s=16, c='r', font_size=10)
# rp.plot_pie(w=weights, title='Portfolio Weights (Min CVaR)', others=0.05, nrow=25, cmap = "tab20", height=6, width=10, ax=None)
# (绘图代码需要 matplotlib 环境)
5. 总结对比
特性 | PyPortfolioOpt | Riskfolio-Lib |
---|---|---|
核心优势 | 易用性,经典模型(MVO, BL, HRP)实现好 | 模型丰富度(CVaR, CDaR, HERC等),风险管理深度 |
主要模型 | MVO, Black-Litterman, HRP, CLA | MVO, BL, RP, RB, HRP, HERC, NCO, FM, 多种风险模型 |
风险度量 | 主要基于波动率/半方差 | 极其丰富 (StdDev, MAD, SemiVar, CVaR, CDaR, UCI…) |
API 设计 | 模块化,相对直观 | 基于中心 Portfolio 类,参数驱动,功能强大但稍复杂 |
易用性 | 较高,学习曲线平缓 | 中等,需要时间熟悉参数和模型 |
协方差估计 | 提供多种收缩方法 | 也支持收缩等方法 |
因子模型 | 不直接支持 | 内置支持 |
聚类/分层模型 | HRP | HRP, HERC, NCO |
后处理 | 离散分配 功能 | 主要关注权重计算 |
可视化 | 基本绘图(有效前沿、权重) | 更丰富(风险贡献、聚类树状图、密度图等) |
6. 选择建议
- 入门学习/教学/快速应用经典模型: 选择 PyPortfolioOpt。它的 API 更易上手,文档对经典模型解释清晰,可以快速实现常见的优化任务。
- 需要高级风险管理/应用现代组合理论: 选择 Riskfolio-Lib。如果你需要用到 CVaR、CDaR 等尾部风险度量,或者对风险平价、因子模型、分层聚类方法(HERC, NCO)感兴趣,Riskfolio-Lib 提供了无与伦比的功能深度。
- 研究导向/探索不同风险度量: Riskfolio-Lib 更适合,因为它提供了研究不同风险偏好下组合构建的可能性。
- 需要将优化结果直接用于交易(股数分配): PyPortfolioOpt 的离散分配功能更方便。当然,也可以用 Riskfolio-Lib 算权重,再用 PyPortfolioOpt 或自己写逻辑做分配。
实践中,了解两者并根据具体项目的需求选用或结合使用是最佳策略。
7. 注意事项
- 数据质量是关键: 任何优化库的输出质量都高度依赖于输入的预期收益和风险(协方差)数据的质量。“Garbage in, garbage out.”
- 模型假设与局限: 理解每个优化模型背后的金融和数学假设至关重要。例如,均值-方差优化假设收益率为正态分布或投资者只有二次效用函数。
- 参数敏感性: 优化结果对输入参数(尤其是预期收益)非常敏感。进行敏感性分析或使用更鲁棒的估计方法(如收缩协方差)和优化技术(如 Riskfolio-Lib 的鲁棒优化选项)非常重要。
- 过拟合风险: 仅基于历史数据进行优化可能导致模型过拟合历史特定情况,样本外表现可能不佳。Black-Litterman 或因子模型等方法试图缓解这个问题。
- 实际约束: 考虑交易成本、流动性、最小交易单位、税收等现实约束,这些通常需要在优化步骤之外或通过自定义约束加入(如果库支持)。
希望这份更深入的解析和代码示例能帮助你更好地理解和选择 PyPortfolioOpt 与 Riskfolio-Lib!