pypfopt.objective_functions 源代码

"""
``objective_functions`` 模块提供优化目标,包括 ``EfficientFrontier`` 对象的优化方法所调用的实际目标函数。
这些方法主要是为优化过程中的内部使用而设计的,每个方法都需要不同的参数(这就是为什么它们没有被归入一个类的原因)。
由于显而易见的原因,任何目标函数都必须接受 ``weights`` 作为参数,并且必须至少有 ``expected_returns`` 或 ``cov_matrix`` 中的一个。

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

``objective_functions`` 默认为用于最小化的优化。
在明显应该最大化的目标的情况下(例如夏普比率,投资组合收益),目标函数实际上返回负数,因为最小化负数等同于最大化正数。
这种行为可由 ``negative=True`` 的可选参数控制。

目前已经实现:

- 投资组合方差(即波动率的平方)。
- 投资组合收益率
- 夏普比率
- L2 正则化(最小化它可以减少非零权重)。
- 二次方效用
- 交易成本模型(简单的模型)
- 事前(平方)跟踪误差
- 事后(平方)跟踪误差
"""

import cvxpy as cp
import numpy as np


def _objective_value(w, obj):
    """
    Helper method to return either the value of the objective function
    or the objective function as a cvxpy object depending on whether
    w is a cvxpy variable or np array.

    :param w: weights
    :type w: np.ndarray OR cp.Variable
    :param obj: objective function expression
    :type obj: cp.Expression
    :return: value of the objective function OR objective function expression
    :rtype: float OR cp.Expression
    """
    if isinstance(w, np.ndarray):
        if np.isscalar(obj):
            return obj
        elif np.isscalar(obj.value):
            return obj.value
        else:
            return obj.value.item()
    else:
        return obj


[文档] def portfolio_variance(w, cov_matrix): """ 计算投资组合的总方差(即平方波动率)。 :param w: asset weights in the portfolio :type w: np.ndarray OR cp.Variable :param cov_matrix: covariance matrix :type cov_matrix: np.ndarray :return: value of the objective function OR objective function expression :rtype: float OR cp.Expression """ variance = cp.quad_form(w, cov_matrix) return _objective_value(w, variance)
[文档] def portfolio_return(w, expected_returns, negative=True): """ 计算一个投资组合的(负)平均收益率 :param w: asset weights in the portfolio :type w: np.ndarray OR cp.Variable :param expected_returns: expected return of each asset :type expected_returns: np.ndarray :param negative: whether quantity should be made negative (so we can minimise) :type negative: boolean :return: negative mean return :rtype: float """ sign = -1 if negative else 1 mu = w @ expected_returns return _objective_value(w, sign * mu)
[文档] def sharpe_ratio(w, expected_returns, cov_matrix, risk_free_rate=0.02, negative=True): """ 计算一个投资组合的(负)夏普比率 :param w: asset weights in the portfolio :type w: np.ndarray OR cp.Variable :param expected_returns: expected return of each asset :type expected_returns: np.ndarray :param cov_matrix: covariance matrix :type cov_matrix: np.ndarray :param risk_free_rate: risk-free rate of borrowing/lending, defaults to 0.02. The period of the risk-free rate should correspond to the frequency of expected returns. :type risk_free_rate: float, optional :param negative: whether quantity should be made negative (so we can minimise) :type negative: boolean :return: (negative) Sharpe ratio :rtype: float """ mu = w @ expected_returns sigma = cp.sqrt(cp.quad_form(w, cov_matrix)) sign = -1 if negative else 1 sharpe = (mu - risk_free_rate) / sigma return _objective_value(w, sign * sharpe)
[文档] def L2_reg(w, gamma=1): r""" L2 正则化,即 :math:`\gamma ||w||^2` ,来增加非零权重的数量。 例如:: ef = EfficientFrontier(mu, S) ef.add_objective(objective_functions.L2_reg, gamma=2) ef.min_volatility() :param w: asset weights in the portfolio :type w: np.ndarray OR cp.Variable :param gamma: L2 regularisation parameter, defaults to 1. Increase if you want more non-negligible weights :type gamma: float, optional :return: value of the objective function OR objective function expression :rtype: float OR cp.Expression """ L2_reg = gamma * cp.sum_squares(w) return _objective_value(w, L2_reg)
[文档] def quadratic_utility(w, expected_returns, cov_matrix, risk_aversion, negative=True): r""" 二次方效用函数,即 :math:`\mu - \frac 1 2 \delta w^T \Sigma w` 。 :param w: asset weights in the portfolio :type w: np.ndarray OR cp.Variable :param expected_returns: expected return of each asset :type expected_returns: np.ndarray :param cov_matrix: covariance matrix :type cov_matrix: np.ndarray :param risk_aversion: risk aversion coefficient. Increase to reduce risk. :type risk_aversion: float :param negative: whether quantity should be made negative (so we can minimise). :type negative: boolean :return: value of the objective function OR objective function expression :rtype: float OR cp.Expression """ sign = -1 if negative else 1 mu = w @ expected_returns variance = cp.quad_form(w, cov_matrix) risk_aversion_par = cp.Parameter( value=risk_aversion, name="risk_aversion", nonneg=True ) utility = mu - 0.5 * risk_aversion_par * variance return _objective_value(w, sign * utility)
[文档] def transaction_cost(w, w_prev, k=0.001): """ 一个非常简单的交易成本模型:将所有的权重变化相加,再乘以一个给定的分数(默认为 10bps)。这模拟了经纪人的固定百分比佣金。 :param w: asset weights in the portfolio :type w: np.ndarray OR cp.Variable :param w_prev: previous weights :type w_prev: np.ndarray :param k: fractional cost per unit weight exchanged :type k: float :return: value of the objective function OR objective function expression :rtype: float OR cp.Expression """ return _objective_value(w, k * cp.norm(w - w_prev, 1))
[文档] def ex_ante_tracking_error(w, cov_matrix, benchmark_weights): """ 计算事前跟踪误差的(平方),即 :math:`(w - w_b)^T \\Sigma (w-w_b)` 。 :param w: asset weights in the portfolio :type w: np.ndarray OR cp.Variable :param cov_matrix: covariance matrix :type cov_matrix: np.ndarray :param benchmark_weights: asset weights in the benchmark :type benchmark_weights: np.ndarray :return: value of the objective function OR objective function expression :rtype: float OR cp.Expression """ relative_weights = w - benchmark_weights tracking_error = cp.quad_form(relative_weights, cov_matrix) return _objective_value(w, tracking_error)
[文档] def ex_post_tracking_error(w, historic_returns, benchmark_returns): """ 计算事后跟踪误差的(平方),即 :math:`Var(r - r_b)` 。 :param w: asset weights in the portfolio :type w: np.ndarray OR cp.Variable :param historic_returns: historic asset returns :type historic_returns: np.ndarray :param benchmark_returns: historic benchmark returns :type benchmark_returns: pd.Series or np.ndarray :return: value of the objective function OR objective function expression :rtype: float OR cp.Expression """ if not isinstance(historic_returns, np.ndarray): historic_returns = np.array(historic_returns) if not isinstance(benchmark_returns, np.ndarray): benchmark_returns = np.array(benchmark_returns) x_i = w @ historic_returns.T - benchmark_returns mean = cp.sum(x_i) / len(benchmark_returns) tracking_error = cp.sum_squares(x_i - mean) return _objective_value(w, tracking_error)