跳转至

均值-方差优化

作者:Robert Andrew Martin

译者:片刻小哥哥

项目地址:https://www.dafeiyang.cn/finance/stock/tools/PyPortfolioOpt/MeanVariance

原始地址:https://pyportfolioopt.readthedocs.io/en/latest/MeanVariance.html

一般来说,数学优化是一个非常困难的问题,特别是当我们处理 具有复杂的目标和约束。然而, 凸优化 问题是很好理解的 这类问题恰好对金融非常有用。凸问题具有以下形式:

\[ \begin{split}\begin{equation*} \begin{aligned} & \underset{\mathbf{x}}{\text{minimise}} & & f(\mathbf{x}) \\ & \text{subject to} & & g_i(\mathbf{x}) \leq 0, i = 1, \ldots, m\\ &&& A\mathbf{x} = b,\\ \end{aligned} \end{equation*}\end{split} \]

其中 \(\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 - int
  • tickers - str 列表
  • bounds - float 元组或(float 元组)列表
  • cov_matrix - np.ndarray
  • expected_returns - np.ndarray
  • solver - str
  • solver_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,然后优化多头投资组合。

min_volatility() [来源]

最大限度地减少波动性。

Returns: 波动性最小化投资组合的资产权重

Return type: 有序字典

max_sharpe(risk_free_rate=0.02) [来源]

最大化夏普比率。结果也称为切线组合, 因为它是资本市场线与有效边界相切的投资组合。

这是进行一定变量替换后的凸优化问题。看 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) [来源]

最大化给定的二次效用,即:

\[ \max_w w^T \mu - \frac \delta 2 w^T \Sigma w \]

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_returnscov_matrix 之一。

目标函数要么计算给定权重的 numpy 数组的目标,要么当权重是 cp.Variable 时返回 cvxpy 表达式。 这样,相同的目标函数既可以在内部用于优化,也可以在外部用于计算目标给定权重。 _objective_value() 自动在两种行为之间进行选择。

objective_functions 默认为最小化目标。 在明确应该最大化的目标(例如夏普比率、投资组合回报)的情况下,目标函数实际上返回负值,因为最小化负值相当于最大化正值。 此行为由 negative=True 可选参数控制。

目前实施:

  • 投资组合方差(即波动率的平方)
  • 投资组合回报
  • 夏普比率
  • L2 正则化(最小化这会减少非零权重)
  • 二次效用
  • 交易成本模型(简单)
  • Ex-ante(平方)跟踪误差
  • Ex-post(平方)跟踪误差

pypfopt.objective_functions.L2_reg(w, gamma=1) [来源]

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)参数化。 例如,考虑最小方差目标,我们有:

\[ \underset{w}{\text{minimise}} ~ \left\{w^T \Sigma w \right\} ~~~ \longrightarrow ~~~ \underset{w}{\text{minimise}} ~ \left\{w^T \Sigma w + \gamma w^T w \right\} \]

请注意,\(w^T w\) 与权重平方和相同(我没有明确编写此内容是为了减少 \(\Sigma\) 表示协方差矩阵和求和运算符造成的混乱)。 该术语减少了可忽略的权重数量,因为当所有权重均等分配时,它具有最小值,而在整个投资组合分配给一项资产的极限情况下,它具有最大值。 我将其称为 L2 正则化,因为它与机器学习中的 L2 正则化项具有完全相同的形式,尽管目的略有不同(在 ML 中,它用于保持较小的权重,而在这里,它用于使权重变大)。

笔记

在实践中,必须调整 \(\gamma\) 以达到您想要的正则化水平。 但是,如果资产范围较小(少于 20 个资产),则 gamma=1 是一个很好的起点。 对于更大的宇宙,或者如果您希望最终投资组合中有更多不可忽略的权重,请增加 gamma

参考文献



回到顶部