跳转至

一般有效边界

作者:Robert Andrew Martin

译者:片刻小哥哥

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

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

只要有预期收益向量和协方差矩阵,就可以使用前面描述的均值-方差优化方法。 目标和约束将是投资组合回报和投资组合波动性的某种组合。

但是,您可能希望为完全不同类型的风险模型(不依赖于协方差矩阵的模型)构建有效边界,或者优化与投资组合回报无关的目标(例如跟踪误差)。 PyPortfolioOpt 附带了几种流行的替代方案,并为自定义优化问题提供支持。

有效半方差

均值半方差优化不是惩罚波动性,而是只惩罚下行波动性,因为上行波动性可能是可取的。

均值半方差优化问题有两种方法。 第一种是使用启发式(即“快速而肮脏”)解决方案:假装半协方差矩阵(在 risk_models 中实现)是典型的协方差矩阵并进行标准均值方差优化。 可以证明,这不会产生在均值半方差空间中有效的投资组合(尽管它可能是一个足够好的近似值)。

幸运的是,可以将均值半方差优化写成凸问题(尽管有许多变量),并且可以解决该问题以给出“精确”解决方案。 例如,为了最大化目标半方差 \(s^*\)(仅限长)的回报,我们将解决以下问题:

\[ \begin{split}\begin{equation*} \begin{aligned} & \underset{w}{\text{maximise}} & & w^T \mu \\ & \text{subject to} & & n^T n \leq s^* \\ &&& B w - p + n = 0 \\ &&& w^T \mathbf{1} = 1 \\ &&& n \geq 0 \\ &&& p \geq 0. \\ \end{aligned} \end{equation*}\end{split} \]

这里,B 是超额收益的 \(T \times N\)(缩放)矩阵:B = (returns - benchmark) / sqrt(T)。 可以添加额外的线性等式约束和凸不等式约束。

PyPortfolioOpt 允许用户通过 EfficientSemivariance 类沿着有效半方差前沿进行优化。 EfficientSemivariance 继承自 EfficientFrontier,因此它具有相同的实用方法(例如 add_constraint()portfolio_performance()),但在均值半方差前沿上查找投资组合。 请注意,某些父方法(例如 max_sharpe()min_volatility())不适用于均值半方差投资组合,因此调用它们会返回 NotImplementedError

EfficientSemivariance 的 API 与 EfficientFrontier 略有不同。 您不应传入协方差矩阵,而应传入历史/模拟收益的 dataframe(这可以使用辅助方法 expected_returns.returns_from_prices() 从价格 dataframe 构建)。 这是一个完整的示例,其中我们寻求最小化半方差的投资组合,目标年回报率为 20%:

from pypfopt import expected_returns, EfficientSemivariance

df = ... # your dataframe of prices
mu = expected_returns.mean_historical_returns(df)
historical_returns = expected_returns.returns_from_prices(df)

es = EfficientSemivariance(mu, historical_returns)
es.efficient_return(0.20)

# We can use the same helper methods as before
weights = es.clean_weights()
print(weights)
es.portfolio_performance(verbose=True)

portfolio_performance 方法输出预期投资组合回报、半方差和 Sortino 比率(类似于夏普比率,但针对下行偏差)。

有兴趣的读者应参阅 Estrada (2007) [1] 了解更多详细信息。 我要感谢 Philipp Schiele 创作了大量有效的半方差功能和文档(所有错误都是我自己造成的)。 该实现基于 Markowitz 等人 (2019) [2]

警告

在均值半方差前沿上查找投资组合在计算上比标准均值方差优化更困难:我们的实现使用 2T + N 优化变量,这意味着对于 50 个资产和 3 年的数据,大约有 1500 个变量。 虽然 EfficientSemivariance 原则上允许额外的约束/目标,但您更有可能遇到求解器错误。 我建议您将 EfficientSemivariance 问题保持在较小的范围内并对其进行最低程度的限制。

class pypfopt.efficient_frontier.EfficientSemivariance(expected_returns, returns, frequency=252, benchmark=0, weight_bounds=(0, 1), solver=None, verbose=False, solver_options=None) [来源]

EfficientSemivariance 对象允许沿均值半方差前沿进行优化。 这可能与更关心下行偏差的用户相关。

实例变量:

  • 输入:
  • n_assets - int
  • tickers - str list
  • bounds - float tuple 或(float tuple)list
  • returns - pd.DataFrame
  • expected_returns - np.ndarray
  • solver - str
  • solver_options - {str: str} dict
  • 输出:weights - np.ndarray

公共方法:

  • min_semivariance() 最小化投资组合半方差(下行偏差)
  • max_quadratic_utility() 考虑到一定的风险厌恶情绪,最大化“下行二次效用”。
  • efficient_risk() 最大化给定目标半偏差的回报
  • efficient_return() 最小化给定目标回报的半偏差
  • add_objective() 向优化问题添加(凸)目标
  • add_constraint() 为优化问题添加约束
  • convex_objective() 求解具有线性约束的通用凸目标
  • portfolio_performance() 计算预期回报、半偏差和 Sortino 比率 优化的投资组合。
  • set_weights() 从权重字典创建 self.weights (np.ndarray)
  • clean_weights() 将 weights and clips 四舍五入到接近零。
  • save_weights_to_file() 将权重保存为 csv、json 或 txt。

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: 有序字典

efficient_risk(target_semideviation, market_neutral=False) [来源]

最大化目标半偏差(下行标准偏差)的回报。 由此产生的投资组合的半偏差将小于目标 (但不保证相等)。

Parameters:

  • target_semideviation ( float ) – 所得投资组合所需的最大半偏差。
  • market_neutral – 投资组合是否应该是市场中性的(权重总和为零), 默认为 False。需要负权重下限。
  • market_neutral – bool,可选

Returns: 有效风险投资组合的资产权重

Return type: 有序字典

max_quadratic_utility(risk_aversion=1, market_neutral=False) [来源]

使用投资组合半方差来最大化给定的二次效用 的方差。

Parameters:

  • risk_aversion ( 正float ) – 风险厌恶参数(必须大于0), 默认为 1
  • market_neutral – 投资组合是否应该是市场中性的(权重总和为零),默认为 False。需要负权重下限。
  • market_neutral – bool,可选

Returns: 最大效用投资组合的资产权重

Return type: 有序字典

min_semivariance(market_neutral=False) [来源]

最小化投资组合半方差(有关进一步说明,请参阅文档)。

Parameters:

  • market_neutral – 投资组合是否应该是市场中性的(权重总和为零),默认为 False。需要负权重下限。
  • market_neutral – bool,可选

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

Return type: 有序字典

portfolio_performance(verbose=False, risk_free_rate=0.02) [来源]

优化后,计算(并可选择打印)最佳性能 投资组合,具体为:预期收益、半偏差、Sortino 比率。

Parameters:

  • verbose ( bool, optional ) – 是否应该打印性能,默认为 False
  • risk_free_rate ( float, optional ) – 无风险借贷利率,默认为0.02。无风险利率的期限应与预期回报的频率。

Raises: ValueError – 如果尚未计算权重

Returns: 预期收益、半偏差、Sortino 比率。

Return type: (float、float、float)

有效的CVaR

条件风险价值(conditional value-at-risk)(又称 预期缺口(expected shortfall))是衡量尾部风险的常用方法。 CVaR 可以被认为是“非常糟糕的日子”发生的平均损失,其中“非常糟糕”由参数 \(\beta\) 量化。

例如,如果我们计算 \(\beta = 0.95\) 时的 CVaR 为 10%,我们可以 95% 地确信最坏情况下的平均每日损失将为 3%。 换句话说,CVaR 是所有严重损失的平均值,以至于它们仅在 \((1-\beta)\%\) 的时间发生。

虽然 CVaR 是一个非常直观的概念,但需要大量新的符号来以数学方式表述它(有关更多详细信息,请参阅 wiki 页面 )。 我们将采用以下表示法:

  • w 为投资组合权重向量
  • r 表示资产收益向量(每日),概率分布 \(p(r)\)
  • \(L(w, r) = - w^T r\) 表示投资组合的损失
  • \(\alpha\) 对投资组合风险价值 (VaR) 充满信心 \(\beta\)

CVaR 可以写为:

\[ CVaR(w, \beta) = \frac{1}{1-\beta} \int_{L(w, r) \geq \alpha (w)} L(w, r) p(r)dr. \]

这是一个难以优化的表达式,因为我们本质上是对 VaR 值进行积分。 Rockafellar 和 Uryasev (2001) [3] 的关键见解是我们可以等效地优化以下凸函数:

\[ F_\beta (w, \alpha) = \alpha + \frac{1}{1-\beta} \int [-w^T r - \alpha]^+ p(r) dr, \]

其中 \([x]^+ = \max(x, 0)\)。 作者证明,最小化 \(F_\beta(w, \alpha)\) 总体上的 \(w, \alpha\) 可以最小化 CVaR。 假设我们有 T 个日收益样本(可以是历史收益或模拟收益)。 表达式中的积分变成了和,因此 CVaR 优化问题简化为线性程序:

\[ \begin{split}\begin{equation*} \begin{aligned} & \underset{w, \alpha}{\text{minimise}} & & \alpha + \frac{1}{1-\beta} \frac 1 T \sum_{i=1}^T u_i \\ & \text{subject to} & & u_i \geq 0 \\ &&& u_i \geq -w^T r_i - \alpha. \\ \end{aligned} \end{equation*}\end{split} \]

此公式为每个数据点引入一个新变量(类似于有效半方差),因此您可能会遇到长回报 dataframe 的性能问题。 同时,您应该提供足够大的数据样本以包含尾部事件。

我感谢 Nicolas Knudde 的初稿(所有错误都是我自己造成的)。 该实现基于 Rockafellar 和 Uryasev (2001) [3]

class pypfopt.efficient_frontier.EfficientCVaR(expected_returns, returns, beta=0.95, weight_bounds=(0, 1), solver=None, verbose=False, solver_options=None) [来源]

EfficientCVaR 类允许沿平均 CVaR 前沿进行优化,使用 Rockafellar 和 Ursayev (2001) 的表述。

实例变量:

  • 输入:
  • n_assets - int
  • tickers - str list
  • bounds - float tuple或(float tuple)列表
  • returns - pd.DataFrame
  • expected_returns - np.ndarray
  • solver - str
  • solver_options - {str: str} dict
  • 输出:weights - np.ndarray

公共方法:

  • min_cvar() 最小化CVaR
  • efficient_risk() 最大化给定 CVaR 的回报
  • efficient_return() 最小化给定目标回报的 CVaR
  • add_objective() 向优化问题添加(凸)目标
  • add_constraint() 为优化问题添加约束
  • portfolio_performance() 计算投资组合的预期回报和CVaR
  • set_weights() 从权重字典创建 self.weights (np.ndarray)
  • clean_weights() 将 weights and clips 四舍五入到接近零。
  • save_weights_to_file() 将权重保存为 csv、json 或 txt。

efficient_return(target_return, market_neutral=False) [来源]

最小化给定目标回报的 CVaR。

Parameters:

  • target_return ( float ) – 最终投资组合的期望回报。
  • market_neutral ( bool, optional ) – 投资组合是否应该是市场中性的(权重总和为零),默认为 False。需要负权重下限。

Raises:

  • ValueError - 如果 target_return 不是正float
  • ValueError – 如果找不到回报等于 target_return 的投资组合

Returns: 最优投资组合的资产权重

Return type: 有序字典

efficient_risk(target_cvar, market_neutral=False) [来源]

最大化目标 CVaR 的回报。 由此产生的投资组合的 CVaR 将低于目标 (但不保证相等)。

Parameters:

  • target_cvar ( float ) – 最终投资组合的期望条件风险价值。
  • market_neutral – 投资组合是否应该是市场中性的(权重总和为零),默认为 False。需要负权重下限。
  • market_neutral – bool,可选

Returns: 有效风险投资组合的资产权重

Return type: 有序字典

min_cvar(market_neutral=False) [来源]

最小化投资组合 CVaR(有关进一步说明,请参阅文档)。

Parameters:

  • market_neutral – 投资组合是否应该是市场中性的(权重总和为零),默认为 False。需要负权重下限。
  • market_neutral – bool,可选

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

Return type: 有序字典

portfolio_performance(verbose=False) [来源]

优化后,计算(并可选择打印)最佳性能 投资组合,具体为:预期回报、CVaR

Parameters:

verbose ( bool, optional ) – 是否应该打印性能,默认为 False

Raises: ValueError – 如果尚未计算权重

Returns: 预期回报,CVaR。

Return type: (float,float)

set_weights(input_weights) [来源]

根据用户输入设置权重属性(np.array)的实用函数

Parameters:

input_weights ( dict ) – {ticker: weight} dict

有效的CDaR

有条件回撤风险(conditional drawdown at risk) (CDaR) 是尾部风险的一种更为奇特的衡量标准。 它试图缓解有效半方差和有效 CVaR 的问题,因为它考虑了材料价值下降的时间跨度。 CDaR 可以被认为是“非常糟糕时期”发生的平均损失,其中“非常糟糕”由参数 \(\beta\) 量化。 回撤被定义为与前一个峰值的非复合回报之间的差异。

换句话说,CDaR 是所有严重亏损的平均值,以至于它们仅在 \((1-\beta)\%\) 的时间发生。 当 \(\beta = 1\) 时,CDaR 就是最大回撤。

虽然回撤是一个非常直观的概念,但需要大量新的符号来以数学方式表述它(有关更多详细信息,请参阅 wiki 页面 )。 我们将采用以下表示法:

  • w 为投资组合权重向量
  • r 为累积资产收益向量(每日),概率分布 \(p(r(t))\)
  • \(D(w, r, t) = \max_{\tau<t}(w^T r(\tau))-w^T r(t)\) 投资组合的回撤
  • \(\alpha\) 对投资组合回撤 (DaR) 充满信心 \(\beta\)

CDaR 可以写为:

\[ CDaR(w, \beta) = \frac{1}{1-\beta} \int_{D(w, r, t) \geq \alpha (w)} D(w, r, t) p(r(t))dr(t). \]

这是一个难以优化的表达式,因为我们本质上是对 VaR 值进行积分。 Chekhlov、Rockafellar 和 Uryasev (2005) [4] 的关键见解是,我们可以等效地优化凸函数,该函数可以转换为线性问题(与 CVaR 的方式相同)。

class pypfopt.efficient_frontier.EfficientCDaR(expected_returns, returns, beta=0.95, weight_bounds=(0, 1), solver=None, verbose=False, solver_options=None) [来源]

EfficientCDaR 类允许沿平均 CDaR 前沿进行优化,使用 Chekhlov、Ursayev 和 Zabarankin (2005) 的表述。

实例变量:

  • 输入:
  • n_assets - int
  • tickers - str list
  • bounds - float tuple或(float tuple)列表
  • returns - pd.DataFrame
  • expected_returns - np.ndarray
  • solver - str
  • solver_options - {str: str} dict
  • 输出:weights - np.ndarray

公共方法:

  • min_cdar() 最小化 CDaR
  • efficient_risk() 最大化给定 CDaR 的回报
  • efficient_return() 最小化给定目标回报的 CDaR
  • add_objective() 向优化问题添加(凸)目标
  • add_constraint() 向优化问题添加(线性)约束
  • portfolio_performance() 计算投资组合的预期回报和 CDaR
  • set_weights() 从权重字典创建 self.weights (np.ndarray)
  • clean_weights() 将 weights and clips 四舍五入到接近零。
  • save_weights_to_file() 将权重保存为 csv、json 或 txt。

efficient_return(target_return, market_neutral=False) [来源]

最小化给定目标回报的 CDaR。

Parameters:

  • target_return ( float ) – 最终投资组合的期望回报。
  • market_neutral ( bool, optional ) – 投资组合是否应该是市场中性的(权重总和为零),默认为 False。需要负权重下限。

Raises:

  • ValueError - 如果 target_return 不是正float
  • ValueError – 如果找不到回报等于 target_return 的投资组合

Returns: 最优投资组合的资产权重

Return type: 有序字典

efficient_risk(target_cdar, market_neutral=False) [来源]

最大化目标 CDaR 的回报。 由此产生的投资组合的 CDaR 将低于目标 (但不保证相等)。

Parameters:

  • target_cdar ( float ) – 所得投资组合所需的最大 CDaR。
  • market_neutral – 投资组合是否应该是市场中性的(权重总和为零),默认为 False。需要负权重下限。
  • market_neutral – bool,可选

Returns: 有效风险投资组合的资产权重

Return type: 有序字典

min_cdar(market_neutral=False) [来源]

最小化投资组合 CDaR(有关进一步说明,请参阅文档)。

Parameters:

  • market_neutral – 投资组合是否应该是市场中性的(权重总和为零),默认为 False。需要负权重下限。
  • market_neutral – bool,可选

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

Return type: 有序字典

portfolio_performance(verbose=False) [来源]

优化后,计算(并可选择打印)最佳性能 投资组合,具体为:预期回报、CDaR

Parameters: verbose ( bool, optional ) – 是否应该打印性能,默认为 False

Raises: ValueError – 如果尚未计算权重

Returns: 预期回报,CDaR。

Return type: (float,float)

set_weights(input_weights) [来源]

根据用户输入设置权重属性(np.array)的实用函数

Parameters: input_weights ( dict ) – {ticker: weight} dict

我很感激 尼古拉斯·克努德​​ 实现了此功能。

自定义优化问题

我们之前已经看到,向 EfficientFrontier 对象(以及扩展至其他通用有效前沿对象,如 EfficientSemivariance)添加约束是很容易的。 但是,如果您对与 max_sharpe()min_volatility()efficient_risk() 等相关的任何内容不感兴趣,并且想要设置一个全新的问题来优化某些自定义目标,该怎么办?

例如,也许我们的目标是构建最能复制特定指数的一篮子资产,换句话说,就是最大限度地减少跟踪误差。 这不适合均值方差优化范例,但我们仍然可以在 PyPortfolioOpt 中实现它:

from pypfopt.base_optimizer import BaseConvexOptimizer
from pypfopt.objective_functions import ex_post_tracking_error

historic_rets = ... # dataframe of historic asset returns
benchmark_rets = ... # pd.Series of historic benchmark returns (same index as historic)

opt = BaseConvexOptimizer(
    n_assets=len(historic_returns.columns),
    tickers=historic_returns.columns,
    weight_bounds=(0, 1)
)
opt.convex_objective(
    ex_post_tracking_error,
    historic_returns=historic_rets,
    benchmark_returns=benchmark_rets,
)
weights = opt.clean_weights()

EfficientFrontier 类继承自 BaseConvexOptimizer。 从 EfficientFrontier 实例调用 convex_objective 可能比从 BaseConvexOptimizer 更方便,特别是当您的目标取决于平均收益或协方差矩阵时。

您可以优化一些通用的 convex_objective(必须使用 cvxpy 原子函数构建 - 请参阅此处)或 nonconvex_objective,它使用 scipy.optimize 作为后端,因此具有完全不同的 API。 有关更多示例,请查看这本 cookbook recipe

class pypfopt.base_optimizer.BaseConvexOptimizer

BaseConvexOptimizer.convex_objective(custom_objective, weights_sum_to_one=True, **kwargs) [来源]

优化自定义凸目标函数。 应使用 ef.add_constraint() 添加约束。 优化器参数必须作为关键字参数传递。 例子:

# Could define as a lambda function instead
def logarithmic_barrier(w, cov_matrix, k=0.1):
    # 60 Years of Portfolio Optimization, Kolm et al (2014)
    return cp.quad_form(w, cov_matrix) - k * cp.sum(cp.log(w))

w = ef.convex_objective(logarithmic_barrier, cov_matrix=ef.cov_matrix)

Parameters:

  • custom_objective (带有签名的函数 (cp.Variable, kwargs) -> cp.Expression) – 要最小化的目标函数。 这应该使用 cvxpy 原子编写 应该映射 (w, kwargs) -> float。
  • weights_sum_to_one (bool, optional) – 是否添加默认目标,默认为 True

Raises: OptimizationError – 如果目标是非凸的或约束是非线性的。

Returns: 有效风险投资组合的资产权重

Return type: 有序字典

BaseConvexOptimizer.nonconvex_objective(custom_objective, objective_args=None, weights_sum_to_one=True, constraints=None, solver='SLSQP', initial_guess=None) [来源]

使用 scipy 后端优化一些目标函数。 这可以支持非凸目标和非线性约束,但可能会陷入局部最小值。 例子:

# Market-neutral efficient risk
constraints = [
    {"type": "eq", "fun": lambda w: np.sum(w)},  # weights sum to zero
    {
        "type": "eq",
        "fun": lambda w: target_risk ** 2 - np.dot(w.T, np.dot(ef.cov_matrix, w)),
    },  # risk = target_risk
]
ef.nonconvex_objective(
    lambda w, mu: -w.T.dot(mu),  # min negative return (i.e maximise return)
    objective_args=(ef.expected_returns,),
    weights_sum_to_one=False,
    constraints=constraints,
)

Parameters:

  • objective_function (带有签名的函数 (np.ndarray, args) -> float) — 要最小化的目标函数。 该函数应该映射 (weight, args) -> cost
  • objective_args (np.ndarrays 的元组) – 目标函数的参数(不包括权重)
  • weights_sum_to_one (bool, optional) – 是否添加默认目标,默认为 True
  • constraints (dict list) – scipy 格式的约束列表(即 dicts)
  • solver (string) – 使用哪个 SCIPY 求解器,例如“SLSQP”、“COBYLA”、“BFGS”。 用户注意:不同的优化器需要不同的输入。
  • initial_guess (np.ndarray) – weights的初始猜测,shape (n,) 或 (n, 1)

Returns: 优化自定义目标的资产权重

Return type: 有序字典

参考文献



回到顶部