"""
The ``efficient_cvar`` submodule houses the EfficientCVaR class, which
generates portfolios along the mean-CVaR frontier.
"""
import warnings
import cvxpy as cp
import numpy as np
from .. import objective_functions
from .efficient_frontier import EfficientFrontier
[文档]
class EfficientCVaR(EfficientFrontier):
"""
EfficientCVaR 类允许沿平均 CVaR 前沿进行优化,使用R ockafellar and Ursayev(2001)的表述。
可用变量:
- 输入:
- ``n_assets`` - int
- ``tickers`` - str list
- ``bounds`` - float tuple OR (float tuple) list
- ``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()`` 从 weights dict 创建 self.weights (np.ndarray)。
- ``clean_weights()`` 对权重进行四舍五入,并剪掉接近零的部分。
- ``save_weights_to_file()`` 将权重保存为 csv、json 或 txt。
"""
def __init__(
self,
expected_returns,
returns,
beta=0.95,
weight_bounds=(0, 1),
solver=None,
verbose=False,
solver_options=None,
):
"""
:param expected_returns: expected returns for each asset. Can be None if
optimising for conditional value at risk only.
:type expected_returns: pd.Series, list, np.ndarray
:param returns: (historic) returns for all your assets (no NaNs).
See ``expected_returns.returns_from_prices``.
:type returns: pd.DataFrame or np.array
:param beta: confidence level, defauls to 0.95 (i.e expected loss on the worst (1-beta) days).
:param weight_bounds: minimum and maximum weight of each asset OR single min/max pair
if all identical, defaults to (0, 1). Must be changed to (-1, 1)
for portfolios with shorting.
:type weight_bounds: tuple OR tuple list, optional
:param solver: name of solver. list available solvers with: `cvxpy.installed_solvers()`
:type solver: str
:param verbose: whether performance and debugging info should be printed, defaults to False
:type verbose: bool, optional
:param solver_options: parameters for the given solver
:type solver_options: dict, optional
:raises TypeError: if ``expected_returns`` is not a series, list or array
"""
super().__init__(
expected_returns=expected_returns,
cov_matrix=np.zeros((returns.shape[1],) * 2), # dummy
weight_bounds=weight_bounds,
solver=solver,
verbose=verbose,
solver_options=solver_options,
)
self.returns = self._validate_returns(returns)
self._beta = self._validate_beta(beta)
self._alpha = cp.Variable()
self._u = cp.Variable(len(self.returns))
[文档]
def set_weights(self, input_weights):
raise NotImplementedError("Method not available in EfficientCVaR.")
@staticmethod
def _validate_beta(beta):
if not (0 <= beta < 1):
raise ValueError("beta must be between 0 and 1")
if beta <= 0.2:
warnings.warn(
"Warning: beta is the confidence-level, not the quantile. Typical values are 80%, 90%, 95%.",
UserWarning,
)
return beta
def min_volatility(self):
raise NotImplementedError("Please use min_cvar instead.")
def max_sharpe(self, risk_free_rate=0.02):
raise NotImplementedError("Method not available in EfficientCVaR.")
def max_quadratic_utility(self, risk_aversion=1, market_neutral=False):
raise NotImplementedError("Method not available in EfficientCVaR.")
[文档]
def min_cvar(self, market_neutral=False):
"""
尽量减少投资组合的 CVaR(进一步解释见文件)。
:param market_neutral: whether the portfolio should be market neutral (weights sum to zero),
defaults to False. Requires negative lower weight bound.
:param market_neutral: bool, optional
:return: asset weights for the volatility-minimising portfolio
:rtype: OrderedDict
"""
self._objective = self._alpha + 1.0 / (
len(self.returns) * (1 - self._beta)
) * cp.sum(self._u)
for obj in self._additional_objectives:
self._objective += obj
self.add_constraint(lambda _: self._u >= 0.0)
self.add_constraint(
lambda w: self.returns.values @ w + self._alpha + self._u >= 0.0
)
self._make_weight_sum_constraint(market_neutral)
return self._solve_cvxpy_opt_problem()
[文档]
def efficient_return(self, target_return, market_neutral=False):
"""
在给定的目标收益下,使 CVaR 最小化。
:param target_return: the desired return of the resulting portfolio.
:type target_return: float
:param market_neutral: whether the portfolio should be market neutral (weights sum to zero),
defaults to False. Requires negative lower weight bound.
:type market_neutral: bool, optional
:raises ValueError: if ``target_return`` is not a positive float
:raises ValueError: if no portfolio can be found with return equal to ``target_return``
:return: asset weights for the optimal portfolio
:rtype: OrderedDict
"""
update_existing_parameter = self.is_parameter_defined("target_return")
if update_existing_parameter:
self._validate_market_neutral(market_neutral)
self.update_parameter_value("target_return", target_return)
else:
self._objective = self._alpha + 1.0 / (
len(self.returns) * (1 - self._beta)
) * cp.sum(self._u)
for obj in self._additional_objectives:
self._objective += obj
self.add_constraint(lambda _: self._u >= 0.0)
self.add_constraint(
lambda w: self.returns.values @ w + self._alpha + self._u >= 0.0
)
ret = self.expected_returns.T @ self._w
target_return_par = cp.Parameter(name="target_return", value=target_return)
self.add_constraint(lambda _: ret >= target_return_par)
self._make_weight_sum_constraint(market_neutral)
return self._solve_cvxpy_opt_problem()
[文档]
def efficient_risk(self, target_cvar, market_neutral=False):
"""
使目标 CVaR 的收益最大化。最终的投资组合的 CVaR 将低于目标值(但不保证相等)。
:param target_cvar: the desired conditional value at risk of the resulting portfolio.
:type target_cvar: float
:param market_neutral: whether the portfolio should be market neutral (weights sum to zero),
defaults to False. Requires negative lower weight bound.
:param market_neutral: bool, optional
:return: asset weights for the efficient risk portfolio
:rtype: OrderedDict
"""
update_existing_parameter = self.is_parameter_defined("target_cvar")
if update_existing_parameter:
self._validate_market_neutral(market_neutral)
self.update_parameter_value("target_cvar", target_cvar)
else:
self._objective = objective_functions.portfolio_return(
self._w, self.expected_returns
)
for obj in self._additional_objectives:
self._objective += obj
cvar = self._alpha + 1.0 / (len(self.returns) * (1 - self._beta)) * cp.sum(
self._u
)
target_cvar_par = cp.Parameter(
value=target_cvar, name="target_cvar", nonneg=True
)
self.add_constraint(lambda _: cvar <= target_cvar_par)
self.add_constraint(lambda _: self._u >= 0.0)
self.add_constraint(
lambda w: self.returns.values @ w + self._alpha + self._u >= 0.0
)
self._make_weight_sum_constraint(market_neutral)
return self._solve_cvxpy_opt_problem()