均值-方差优化 ¶
作者:Robert Andrew Martin
译者:片刻小哥哥
项目地址:https://www.dafeiyang.cn/finance/stock/tools/PyPortfolioOpt/MeanVariance
原始地址:https://pyportfolioopt.readthedocs.io/en/latest/MeanVariance.html
一般来说,数学优化是一个非常困难的问题,特别是当我们处理 具有复杂的目标和约束。然而, 凸优化 问题是很好理解的 这类问题恰好对金融非常有用。凸问题具有以下形式:
其中 \(\mathbf{x} \in \mathbb{R}^n\) 和 \(f(\mathbf{x}), g_i(\mathbf{x})\) 是凸函数。 [1]
幸运的是,投资组合优化问题(具有标准目标和约束)是凸的。这 使我们能够立即应用大量的理论以及精致的解决例程 - 因此,主要的困难是将我们的具体问题输入到求解器中。
PyPortfolioOpt 旨在为您完成艰苦的工作,允许像 ef.min_volatility()
生成一个将波动性降至最低的投资组合,同时允许更多由模块化单元构建的复杂问题。这一切都归功于 cvxpy , 这 极好的 python 嵌入式建模 PyPortfolioOpt 有效前沿功能所依赖的凸优化语言。
提示
您可以在相关 recipe 中找到完整的示例。
结构 ¶
正如凸问题的定义所示,我们本质上需要指定两件事: 优化目标和优化约束。例如,经典的投资组合 优化问题是 最小化风险 受 返回约束(即投资组合必须返回超过一定金额)。但从实施的角度来看,目标和约束之间没有太大区别。考虑一个类似的问题,即 回报最大化 受 风险约束 – 现在,风险和回报的角色已经互换。
为此,PyPortfolioOpt 定义了一个 objective_functions
模块,其中包含目标函数(也可以充当约束,正如我们刚刚看到的)。 实际的优化发生在 efficient_frontier.EfficientFrontier
类中。 此类提供了用于优化不同目标的简单方法(全部记录如下)。
然而,PyPortfolioOpt 的设计使您可以轻松地向现有问题添加新的约束或客观术语。 例如,将正则化目标(如下所述)添加到最小波动率目标非常简单:
ef = EfficientFrontier(expected_returns, cov_matrix) # setup
ef.add_objective(objective_functions.L2_reg) # add a secondary objective
ef.min_volatility() # find the portfolio that minimises volatility and L2_reg
!!! tip "提示"
如果您想绘制有效边界,请查看 [绘图](../Plotting#plotting) 模块。
基本用法 ¶
effective_frontier
模块包含 EfficientFrontier 类及其子类,它们为各种可能的目标函数和参数生成最佳投资组合。
class pypfopt.efficient_frontier.EfficientFrontier(expected_returns, cov_matrix, weight_bounds=(0, 1), solver=None, verbose=False, solver_options=None)
[来源] ¶
一个EfficientFrontier对象(继承自BaseConvexOptimizer)包含多个 可调用的优化方法(对应不同的目标 函数)具有各种参数。注意:新的 EfficientFrontier 对象应该 如果您想对目标/约束/边界/参数进行任何更改,请实例化。
实例变量:
- 输入:
n_assets
- inttickers
- str 列表bounds
- float 元组或(float 元组)列表cov_matrix
- np.ndarrayexpected_returns
- np.ndarraysolver
- strsolver_options
- {str: str} dict- 输出:
weights
- np.ndarray
公共方法:
min_volatility()
优化以最小化波动性max_sharp()
优化最大夏普比率(又称切线投资组合)max_quadratic_utility()
考虑到一定的风险规避,最大化二次效用。efficient_risk()
最大化给定目标风险的回报efficient_return()
最大限度地降低给定目标回报的风险add_objective()
向优化问题添加(凸)目标add_constraint()
为优化问题添加约束convex_objective()
求解具有线性约束的通用凸目标portfolio_performance()
计算预期回报、波动率和夏普比率 优化的投资组合。set_weights()
从权重字典创建 self.weights (np.ndarray)clean_weights()
将 weights and clips 四舍五入到接近零。save_weights_to_file()
将权重保存为 csv、json 或 txt。
__init__(expected_returns, cov_matrix, weight_bounds=(0, 1), solver=None, verbose=False, solver_options=None)
[来源] ¶
Parameters:
- expected_returns ( pd.Series, list, np.ndarray ) – 每项资产的预期回报。 如果仅针对波动性进行优化,则可以为 None(但不推荐)。
- cov_matrix ( pd.DataFrame 或者 np.array ) – 每项资产收益的协方差。这 必须 是正半定,否则优化将失败。
- weight_bounds ( uple 或 tuple list, optional ) – 每个资产的最小和最大权重或单个最小/最大对(如果全部相同)默认为 (0, 1)。 对于做空的投资组合,必须更改为 (-1, 1)。
- solver ( str ) – 求解器的名称。列出可用的求解器:
cvxpy.installed_solvers()
- verbose ( bool, optional ) – 是否应该打印性能和调试信息,默认为 False
- solver_options ( dict, optional ) – 给定求解器的参数
Raises:
- TypeError – 如果 Expected_returns 不是series, list 或array
- TypeError – 如果 cov_matrix 不是 dataframe 或 array
笔记
从 v0.5.0 开始,您可以传递表示不同资产的不同边界的(最小、最大)对的集合(列表或元组)。
!!! tip "提示"
如果您想生成只做空的投资组合,有一个快速技巧。乘 您的预期回报为 -1,然后优化多头投资组合。
最大限度地减少波动性。
Returns: 波动性最小化投资组合的资产权重
Return type: 有序字典
最大化夏普比率。结果也称为切线组合, 因为它是资本市场线与有效边界相切的投资组合。
这是进行一定变量替换后的凸优化问题。看 Cornuejols 和 Tutuncu (2006) 了解更多。
Parameters: risk_free_rate ( float, optional ) – 无风险借贷利率,默认为0.02。无风险利率的期限应与预期回报的频率。
Raises: ValueError - 如果 无风险率
是非数字的
Returns: 夏普最大化投资组合的资产权重
Return type: 有序字典
警告
由于 max_sharp()
进行了变量替换,因此其他目标可能无法按预期工作。
max_quadratic_utility(risk_aversion=1, market_neutral=False)
[来源] ¶
最大化给定的二次效用,即:
Parameters:
- risk_aversion ( 正float ) – 风险厌恶参数(必须大于0), 默认为 1
- market_neutral – 投资组合是否应该是市场中性的(权重总和为零), 默认为 False。需要负权重下限。
- market_neutral – bool,可选
Returns: 最大效用投资组合的资产权重
Return type: 有序字典
笔记
pypfopt.black_litterman
提供了计算市场隐含的方法风险规避参数,在没有其他参数的情况下给出有用的估计信息!
efficient_risk(target_volatility, market_neutral=False)
[来源] ¶
最大化目标风险的回报。由此产生的投资组合将具有波动性 小于目标(但不保证等于)。
Parameters:
- target_volatility ( float ) – 最终投资组合所需的最大波动性。
- market_neutral – 投资组合是否应该是市场中性的(权重总和为零), 默认为 False。需要负权重下限。
- market_neutral – bool,可选
Raises:
- ValueError - 如果
target_volatility
不是正float 数 - ValueError – 如果找不到波动率等于
target_volatility
的投资组合 - ValueError - 如果
risk_free_rate
是非数字的
Returns: 有效风险投资组合的资产权重
Return type: 有序字典
警告
如果将不合理的目标传递给 efficient_risk()
或 efficient_return()
,优化器将默默地失败并返回奇怪的权重。 买者自负适用!
efficient_return(target_return, market_neutral=False)
[来源] ¶
计算“马科维茨投资组合”,最大限度地减少给定目标回报的波动性。
Parameters:
- target_return ( float ) – 最终投资组合的期望回报。
- market_neutral ( bool, optional ) – 投资组合是否应该是市场中性的(权重总和为零),默认为 False。需要负权重下限。
Raises:
ValueError - 如果 target_return
不是正float 数 * ValueError* – 如果找不到回报等于 target_return
的投资组合
Returns: 马科维茨投资组合的资产权重
Return type: 有序字典
portfolio_performance(verbose=False, risk_free_rate=0.02)
[来源] ¶
优化后,计算(并可选择打印)最佳性能 文件夹。目前计算预期回报、波动率和夏普比率。
Parameters:
- verbose ( bool, optional ) – 是否应该打印性能,默认为 False
- risk_free_rate ( float, optional ) – 无风险借贷利率,默认0.02。 无风险利率的期限应与预期收益的频率相对应。
Raises: ValueError – 如果尚未计算权重
Returns: 预期回报、波动性、夏普比率。
Return type: (float, float, float)
提示
如果您想独立于任何优化器使用 portfolio_performance
函数(例如出于调试目的),您可以使用:
from pypfopt import base_optimizer
base_optimizer.portfolio_performance(
weights, expected_returns, cov_matrix, verbose=True, risk_free_rate=0.02
)
笔记
PyPortfolioOpt 遵循 cvxpy 求解器的默认选择。 如果您想显式选择求解器,只需将可选的solver = "ECOS"
kwarg 传递给构造函数即可。 您可以从任何受 支持的求解器 中进行选择,并通过 solver_options
(一个 dict)传入求解器参数。
添加目标和约束 ¶
EfficientFrontier 继承自 BaseConvexOptimizer 类。特别是,这些功能 添加约束和目标记录如下:
class pypfopt.base_optimizer.BaseConvexOptimizer
BaseConvexOptimizer.add_constraint(new_constraint)
¶
向优化问题添加新的约束。该约束必须满足 DCP 规则, 即是线性等式约束或凸不等式约束。
例子:
ef.add_constraint(lambda x : x[0] == 0.02)
ef.add_constraint(lambda x : x >= 0.01)
ef.add_constraint(lambda x: x <= np.array([0.01, 0.08, ..., 0.5]))
Parameters: new_constraint ( 可调用(例如 lambda 函数)* ) – 要添加的约束
BaseConvexOptimizer.add_sector_constraints(sector_mapper, sector_lower, sector_upper)
¶
添加对不同资产组的权重总和的约束。 最常见的是,这些将是行业限制,例如投资组合的风险敞口 tech 必须小于 x%:
sector_mapper = {
"GOOG": "tech",
"FB": "tech",,
"XOM": "Oil/Gas",
"RRC": "Oil/Gas",
"MA": "Financials",
"JPM": "Financials",
}
sector_lower = {"tech": 0.1} # at least 10% to tech
sector_upper = {
"tech": 0.4, # less than 40% tech
"Oil/Gas": 0.1 # less than 10% oil and gas
}
Parameters:
- sector_mapper ( {str: str} dict ) – 将股票代码映射到sector的字典
- sector_lower ( {str: float} dict ) – 每个sector的下限
- sector_upper ( {str:float} dict ) – 每个sector的上限
BaseConvexOptimizer.add_objective(new_objective, **kwargs)
¶
在目标函数中添加一个新项。该项必须是凸的,并由 cvxpy 原子函数构建。
例子:
def L1_norm(w, k=1):
return k * cp.norm(w, 1)
ef.add_objective(L1_norm, k=2)
Parameters: new_objective ( cp.Expression(即 cp.Variable 的函数) ) – 要添加的目标
目标函数 ¶
objective_functions
模块提供优化目标,包括 EfficientFrontier
对象的优化方法调用的实际目标函数。 这些方法主要设计用于优化期间的内部使用,并且每个方法都需要不同的签名(这就是它们没有被分解到类中的原因)。 出于显而易见的原因,任何目标函数都必须接受 weights
作为参数,并且还必须至少具有 expected_returns
或 cov_matrix
之一。
目标函数要么计算给定权重的 numpy 数组的目标,要么当权重是 cp.Variable
时返回 cvxpy 表达式。 这样,相同的目标函数既可以在内部用于优化,也可以在外部用于计算目标给定权重。 _objective_value()
自动在两种行为之间进行选择。
objective_functions
默认为最小化目标。 在明确应该最大化的目标(例如夏普比率、投资组合回报)的情况下,目标函数实际上返回负值,因为最小化负值相当于最大化正值。 此行为由 negative=True
可选参数控制。
目前实施:
- 投资组合方差(即波动率的平方)
- 投资组合回报
- 夏普比率
- L2 正则化(最小化这会减少非零权重)
- 二次效用
- 交易成本模型(简单)
- Ex-ante(平方)跟踪误差
- Ex-post(平方)跟踪误差
L2 正则化,即 \(\gamma ||w||^2\) ,增加非零权重的数量。
例子:
ef = EfficientFrontier(mu, S)
ef.add_objective(objective_functions.L2_reg, gamma=2)
ef.min_volatility()
Parameters:
- w ( np.ndarray 或 cp.Variable ) – 投资组合中的资产权重
- gamma ( float, optional ) – L2 正则化参数,默认为 1。如果需要更多,请增加 不可忽略的权重
Returns: 目标函数值或目标函数表达式
Return type: float 或 cp.Expression
pypfopt.objective_functions.ex_ante_tracking_error(w, cov_matrix, benchmark_weights)
¶
计算事前跟踪误差(的平方),即 \((w - w_b)^T \Sigma (w-w_b)\)。
Parameters:
- w ( np.ndarray 或 cp.Variable ) – 投资组合中的资产权重
- cov_matrix ( np.ndarray ) – 协方差矩阵
- benchmark_weights ( np.ndarray ) – 基准中的资产权重
Returns: 目标函数值或目标函数表达式
Return type: float OR cp.Expression
pypfopt.objective_functions.ex_post_tracking_error(w, historic_returns, benchmark_returns)
[来源] ¶
计算事后跟踪误差(的平方),即 \(Var(r - r_b)\) 。
Parameters:
- w ( np.ndarray 或 cp.Variable ) – 投资组合中的资产权重
- historic_returns ( np.ndarray ) – 历史资产回报率
- benchmark_returns ( pd.Series 或者 np.ndarray ) – 历史基准回报
Returns: 目标函数值或目标函数表达式
Return type: float 或 cp.Expression
pypfopt.objective_functions.portfolio_return(w, expected_returns, negative=True)
[来源] ¶
计算投资组合的(负)平均回报
Parameters:
- w ( np.ndarray 或 cp.Variable ) – 投资组合中的资产权重
- expected_returns ( np.ndarray ) – 每项资产的预期回报
- negative ( boolean ) – 数量是否应设为负数(以便我们可以最小化)
Returns: 平均回报为负
Return type: float
pypfopt.objective_functions.portfolio_variance(w, cov_matrix)
[来源] ¶
计算总投资组合方差(即波动率平方)。
Parameters:
- w ( np.ndarray 或 cp.Variable ) – 投资组合中的资产权重
- cov_matrix ( np.ndarray ) – 协方差矩阵
Returns: 目标函数值或目标函数表达式
Return type: float 或 cp.Expression
pypfopt.objective_functions.quadratic_utility(w, expected_returns, cov_matrix, risk_aversion, negative=True)
[来源] ¶
二次效用函数,即 \(\mu - \frac 1 2 \delta w^T \Sigma w\) 。
Parameters:
- w ( np.ndarray 或 cp.Variable ) – 投资组合中的资产权重
- expected_returns ( np.ndarray ) – 每项资产的预期回报
- cov_matrix ( np.ndarray ) – 协方差矩阵
- risk_aversion ( float )——风险厌恶系数。增加以降低风险。
- negative ( boolean ) – 数量是否应设为负数(以便我们可以最小化)。
Returns: 目标函数值或目标函数表达式
Return type: float 或 cp.Expression
pypfopt.objective_functions.sharpe_ratio(w, expected_returns, cov_matrix, risk_free_rate=0.02, negative=True)
[来源] ¶
计算投资组合的(负)夏普比率
Parameters:
- w ( np.ndarray 或 cp.Variable ) – 投资组合中的资产权重
- expected_returns ( np.ndarray ) – 每项资产的预期回报
- cov_matrix ( np.ndarray ) – 协方差矩阵
- risk_free_rate ( float, optional ) – 无风险借贷利率,默认为0.02。无风险利率的期限应与预期回报的频率。
- negative ( boolean ) – 数量是否应设为负数(以便我们可以最小化)
Returns: (负)夏普比率
Return type: float
pypfopt.objective_functions.transaction_cost(w, w_prev, k=0.001)
[来源] ¶
一个非常简单的交易成本模型:将所有权重变化相加 并乘以给定分数(默认为 10bps)。这模拟了 来自经纪人的固定百分比佣金。
Parameters:
- w ( np.ndarray 或 cp.Variable ) – 投资组合中的资产权重
- w_prev ( np.ndarray ) – 之前的权重
- k ( float ) – 每单位权重交换的分数成本
Returns: 目标函数值或目标函数表达式
Return type: float OR cp.Expression
有关 L2 正则化的更多信息 ¶
正如 用户指南 中所讨论的,均值方差优化通常会导致许多权重可以忽略不计,即有效的投资组合最终不会包含大部分资产。 这是预期的行为,但如果您的投资组合中需要一定数量的资产,则可能不希望出现这种情况。
为了强制均值方差优化器产生更多不可忽略的权重,我们向所有目标函数添加了可被视为“小权重惩罚”的内容,并由 \(\gamma\)(gamma
)参数化。 例如,考虑最小方差目标,我们有:
请注意,\(w^T w\) 与权重平方和相同(我没有明确编写此内容是为了减少 \(\Sigma\) 表示协方差矩阵和求和运算符造成的混乱)。 该术语减少了可忽略的权重数量,因为当所有权重均等分配时,它具有最小值,而在整个投资组合分配给一项资产的极限情况下,它具有最大值。 我将其称为 L2 正则化,因为它与机器学习中的 L2 正则化项具有完全相同的形式,尽管目的略有不同(在 ML 中,它用于保持较小的权重,而在这里,它用于使权重变大)。
笔记
在实践中,必须调整 \(\gamma\) 以达到您想要的正则化水平。 但是,如果资产范围较小(少于 20 个资产),则 gamma=1
是一个很好的起点。 对于更大的宇宙,或者如果您希望最终投资组合中有更多不可忽略的权重,请增加 gamma
。
参考文献 ¶
- [1] Boyd, S.; Vandenberghe, L. (2004). Convex Optimization