RiskFunctions 源代码

""""""  #
"""
Copyright (c) 2020-2023, Dany Cajas
All rights reserved.
This work is licensed under BSD 3-Clause "New" or "Revised" License.
License available at https://github.com/dcajasn/Riskfolio-Lib/blob/master/LICENSE.txt
"""

import numpy as np
import cvxpy as cp
import riskfolio.src.OwaWeights as owa
from scipy.optimize import minimize
from scipy.optimize import Bounds
import warnings


__all__ = [
    "MAD",
    "SemiDeviation",
    "Kurtosis",
    "SemiKurtosis",
    "VaR_Hist",
    "CVaR_Hist",
    "WR",
    "LPM",
    "Entropic_RM",
    "EVaR_Hist",
    "RLVaR_Hist",
    "MDD_Abs",
    "ADD_Abs",
    "DaR_Abs",
    "CDaR_Abs",
    "EDaR_Abs",
    "RLDaR_Abs",
    "UCI_Abs",
    "MDD_Rel",
    "ADD_Rel",
    "DaR_Rel",
    "CDaR_Rel",
    "EDaR_Rel",
    "RLDaR_Rel",
    "UCI_Rel",
    "GMD",
    "TG",
    "RG",
    "CVRG",
    "TGRG",
    "L_Moment",
    "L_Moment_CRM",
    "Sharpe_Risk",
    "Sharpe",
    "Risk_Contribution",
]


[文档] def MAD(X): r""" Calculate the Mean Absolute Deviation (MAD) of a returns series. .. math:: \text{MAD}(X) = \frac{1}{T}\sum_{t=1}^{T} | X_{t} - \mathbb{E}(X_{t}) | Parameters ---------- X : 1d-array Returns series, must have Tx1 size. Returns ------- value : float MAD of a returns series. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") T, N = a.shape mu = np.mean(a, axis=0).reshape(1, -1) mu = np.repeat(mu, T, axis=0) value = a - mu value = np.mean(np.absolute(value), axis=0) value = np.array(value).item() return value
[文档] def SemiDeviation(X): r""" Calculate the Semi Deviation of a returns series. .. math:: \text{SemiDev}(X) = \left [ \frac{1}{T-1}\sum_{t=1}^{T} \min (X_{t} - \mathbb{E}(X_{t}), 0)^2 \right ]^{1/2} Parameters ---------- X : 1d-array Returns series, must have Tx1 size. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float Semi Deviation of a returns series. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") T, N = a.shape mu = np.mean(a, axis=0).reshape(1, -1) mu = np.repeat(mu, T, axis=0) value = mu - a value = np.sum(np.power(value[np.where(value >= 0)], 2)) / (T - 1) value = np.power(value, 0.5).item() return value
[文档] def Kurtosis(X): r""" Calculate the Square Root Kurtosis of a returns series. .. math:: \text{Kurt}(X) = \left [ \frac{1}{T}\sum_{t=1}^{T} (X_{t} - \mathbb{E}(X_{t}))^{4} \right ]^{1/2} Parameters ---------- X : 1d-array Returns series, must have Tx1 size. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float Square Root Kurtosis of a returns series. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") T, N = a.shape mu = np.mean(a, axis=0).reshape(1, -1) mu = np.repeat(mu, T, axis=0) value = mu - a value = np.sum(np.power(value, 4)) / T value = np.power(value, 0.5).item() return value
[文档] def SemiKurtosis(X): r""" Calculate the Semi Square Root Kurtosis of a returns series. .. math:: \text{SemiKurt}(X) = \left [ \frac{1}{T}\sum_{t=1}^{T} \min (X_{t} - \mathbb{E}(X_{t}), 0)^{4} \right ]^{1/2} Parameters ---------- X : 1d-array Returns series, must have Tx1 size. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float Semi Square Root Kurtosis of a returns series. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") T, N = a.shape mu = np.mean(a, axis=0).reshape(1, -1) mu = np.repeat(mu, T, axis=0) value = mu - a value = np.sum(np.power(value[np.where(value >= 0)], 4)) / T value = np.power(value, 0.5).item() return value
[文档] def VaR_Hist(X, alpha=0.05): r""" Calculate the Value at Risk (VaR) of a returns series. .. math:: \text{VaR}_{\alpha}(X) = -\inf_{t \in (0,T)} \left \{ X_{t} \in \mathbb{R}: F_{X}(X_{t})>\alpha \right \} Parameters ---------- X : 1d-array Returns series, must have Tx1 size. alpha : float, optional Significance level of VaR. The default is 0.05. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float VaR of a returns series. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") sorted_a = np.sort(a, axis=0) index = int(np.ceil(alpha * len(sorted_a)) - 1) value = -sorted_a[index] value = np.array(value).item() return value
[文档] def CVaR_Hist(X, alpha=0.05): r""" Calculate the Conditional Value at Risk (CVaR) of a returns series. .. math:: \text{CVaR}_{\alpha}(X) = \text{VaR}_{\alpha}(X) + \frac{1}{\alpha T} \sum_{t=1}^{T} \max(-X_{t} - \text{VaR}_{\alpha}(X), 0) Parameters ---------- X : 1d-array Returns series, must have Tx1 size. alpha : float, optional Significance level of CVaR. The default is 0.05. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float CVaR of a returns series. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") sorted_a = np.sort(a, axis=0) index = int(np.ceil(alpha * len(sorted_a)) - 1) sum_var = 0 for i in range(0, index + 1): sum_var = sum_var + sorted_a[i] - sorted_a[index] value = -sorted_a[index] - sum_var / (alpha * len(sorted_a)) value = np.array(value).item() return value
[文档] def WR(X): r""" Calculate the Worst Realization (WR) or Worst Scenario of a returns series. .. math:: \text{WR}(X) = \max(-X) Parameters ---------- X : 1d-array Returns series, must have Tx1 size. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float WR of a returns series. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") sorted_a = np.sort(a, axis=0) value = -sorted_a[0] value = np.array(value).item() return value
[文档] def LPM(X, MAR=0, p=1): r""" Calculate the First or Second Lower Partial Moment of a returns series. .. math:: \text{LPM}(X, \text{MAR}, 1) &= \frac{1}{T}\sum_{t=1}^{T} \max(\text{MAR} - X_{t}, 0) \\ \text{LPM}(X, \text{MAR}, 2) &= \left [ \frac{1}{T-1}\sum_{t=1}^{T} \max(\text{MAR} - X_{t}, 0)^{2} \right ]^{\frac{1}{2}} \\ Where: :math:`\text{MAR}` is the minimum acceptable return. :math:`p` is the order of the :math:`\text{LPM}`. Parameters ---------- X : 1d-array Returns series, must have Tx1 size. MAR : float, optional Minimum acceptable return. The default is 0. p : float, optional can be {1,2} order of the :math:`\text{LPM}`. The default is 1. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float p-th Lower Partial Moment of a returns series. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") if p not in [1, 2]: raise ValueError("p can only be 1 or 2") value = MAR - a if p == 2: n = value.shape[0] - 1 else: n = value.shape[0] value = np.sum(np.power(value[np.where(value >= 0)], p)) / n value = np.power(value, 1 / p).item() return value
[文档] def Entropic_RM(X, z=1, alpha=0.05): r""" Calculate the Entropic Risk Measure (ERM) of a returns series. .. math:: \text{ERM}_{\alpha}(X) = z\ln \left (\frac{M_X(z^{-1})}{\alpha} \right ) Where: :math:`M_X(z)` is the moment generating function of X. Parameters ---------- X : 1d-array Returns series, must have Tx1 size. theta : float, optional Risk aversion parameter, must be greater than zero. The default is 1. alpha : float, optional Significance level of EVaR. The default is 0.05. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float ERM of a returns series. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") value = np.mean(np.exp(-1 / z * a), axis=0) value = z * (np.log(value) + np.log(1 / alpha)) value = np.array(value).item() return value
def _Entropic_RM(z, X, alpha=0.05): a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") a = a.flatten() value = np.mean(np.exp(-1 / z * a), axis=0) value = z * (np.log(value) + np.log(1 / alpha)) value = np.array(value).item() return value
[文档] def EVaR_Hist(X, alpha=0.05): r""" Calculate the Entropic Value at Risk (EVaR) of a returns series. .. math:: \text{EVaR}_{\alpha}(X) = \inf_{z>0} \left \{ z \ln \left (\frac{M_X(z^{-1})}{\alpha} \right ) \right \} Where: :math:`M_X(t)` is the moment generating function of X. Parameters ---------- X : 1d-array Returns series, must have Tx1 size. alpha : float, optional Significance level of EVaR. The default is 0.05. Raises ------ ValueError When the value cannot be calculated. Returns ------- (value, z) : tuple EVaR of a returns series and value of z that minimize EVaR. """ warnings.filterwarnings("ignore") a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") bnd = Bounds([-1e-24], [np.inf]) result = minimize( _Entropic_RM, [1], args=(X, alpha), method="SLSQP", bounds=bnd, tol=1e-12 ) t = result.x t = t.item() value = _Entropic_RM(t, X, alpha) return (value, t)
[文档] def RLVaR_Hist(X, alpha=0.05, kappa=0.01, solver=None): r""" Calculate the Relativistic Value at Risk (RLVaR) of a returns series. I recommend only use this function with MOSEK solver. .. math:: \text{RLVaR}^{\kappa}_{\alpha}(X) & = \left \{ \begin{array}{ll} \underset{z, t, \psi, \theta, \varepsilon, \omega}{\text{inf}} & t + z \ln_{\kappa} \left ( \frac{1}{\alpha T} \right ) + \sum^T_{i=1} \left ( \psi_{i} + \theta_{i} \right ) \\ \text{s.t.} & -X - t + \varepsilon + \omega \leq 0\\ & z \geq 0 \\ & \left ( z \left ( \frac{1+\kappa}{2\kappa} \right ), \psi_{i} \left ( \frac{1+\kappa}{\kappa} \right ), \varepsilon_{i} \right) \in \mathcal{P}_3^{1/(1+\kappa),\, \kappa/(1+\kappa)} \\ & \left ( \omega_{i}\left ( \frac{1}{1-\kappa} \right ), \theta_{i}\left ( \frac{1}{\kappa} \right), -z \left ( \frac{1}{2\kappa} \right ) \right ) \in \mathcal{P}_3^{1-\kappa,\, \kappa} \\ Where: :math:`\mathcal{P}_3^{\alpha,\, 1-\alpha}` is the power cone 3D. :math:`\kappa` is the deformation parameter. Parameters ---------- X : 1d-array Returns series, must have Tx1 size. alpha : float, optional Significance level of EVaR. The default is 0.05. kappa : float, optional Deformation parameter of RLVaR, must be between 0 and 1. The default is 0.01. solver: str, optional Solver available for CVXPY that supports power cone programming. Used to calculate RLVaR and RLDaR. The default value is None. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : tuple RLVaR of a returns series. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") T, N = a.shape t = cp.Variable((1, 1)) z = cp.Variable((1, 1)) omega = cp.Variable((T, 1)) psi = cp.Variable((T, 1)) theta = cp.Variable((T, 1)) nu = cp.Variable((T, 1)) ones = np.ones((T, 1)) constraints = [ cp.constraints.power.PowCone3D( z * (1 + kappa) / (2 * kappa) * ones, psi * (1 + kappa) / kappa, nu, 1 / (1 + kappa), ), cp.constraints.power.PowCone3D( omega / (1 - kappa), theta / kappa, -z / (2 * kappa) * ones, (1 - kappa) ), -a * 1000 - t * 1000 + nu * 1000 + omega * 1000 <= 0, z >= 0, ] c = ((1 / (alpha * T)) ** kappa - (1 / (alpha * T)) ** (-kappa)) / (2 * kappa) risk = t + c * z + cp.sum(psi + theta) objective = cp.Minimize(risk * 1000) prob = cp.Problem(objective, constraints) try: if solver in ["CLARABEL", "MOSEK", "SCS"]: prob.solve(solver=solver) else: prob.solve() except: pass value = risk.value.item() return value
[文档] def MDD_Abs(X): r""" Calculate the Maximum Drawdown (MDD) of a returns series using uncompounded cumulative returns. .. math:: \text{MDD}(X) = \max_{j \in (0,T)} \left [\max_{t \in (0,j)} \left ( \sum_{i=0}^{t}X_{i} \right ) - \sum_{i=0}^{j}X_{i} \right ] Parameters ---------- X : 1d-array Returns series, must have Tx1 size. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float MDD of an uncompounded cumulative returns. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") prices = np.insert(np.array(a), 0, 1, axis=0) NAV = np.cumsum(np.array(prices), axis=0) value = 0 peak = -99999 for i in NAV: if i > peak: peak = i DD = peak - i if DD > value: value = DD value = np.array(value).item() return value
[文档] def ADD_Abs(X): r""" Calculate the Average Drawdown (ADD) of a returns series using uncompounded cumulative returns. .. math:: \text{ADD}(X) = \frac{1}{T}\sum_{j=0}^{T}\left [ \max_{t \in (0,j)} \left ( \sum_{i=0}^{t}X_{i} \right ) - \sum_{i=0}^{j}X_{i} \right ] Parameters ---------- X : 1d-array Returns series, must have Tx1 size. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float ADD of an uncompounded cumulative returns. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") prices = np.insert(np.array(a), 0, 1, axis=0) NAV = np.cumsum(np.array(prices), axis=0) value = 0 peak = -99999 n = 0 for i in NAV: if i > peak: peak = i DD = peak - i if DD > 0: value += DD n += 1 if n == 0: value = 0 else: value = value / (n - 1) value = np.array(value).item() return value
[文档] def DaR_Abs(X, alpha=0.05): r""" Calculate the Drawdown at Risk (DaR) of a returns series using uncompounded cumulative returns. .. math:: \text{DaR}_{\alpha}(X) & = \max_{j \in (0,T)} \left \{ \text{DD}(X,j) \in \mathbb{R}: F_{\text{DD}} \left ( \text{DD}(X,j) \right )< 1-\alpha \right \} \\ \text{DD}(X,j) & = \max_{t \in (0,j)} \left ( \sum_{i=0}^{t}X_{i} \right )- \sum_{i=0}^{j}X_{i} Parameters ---------- X : 1d-array Returns series, must have Tx1 size.. alpha : float, optional Significance level of DaR. The default is 0.05. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float DaR of an uncompounded cumulative returns series. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") prices = np.insert(np.array(a), 0, 1, axis=0) NAV = np.cumsum(np.array(prices), axis=0) DD = [] peak = -99999 for i in NAV: if i > peak: peak = i DD.append(-(peak - i)) del DD[0] sorted_DD = np.sort(np.array(DD), axis=0) index = int(np.ceil(alpha * len(sorted_DD)) - 1) value = -sorted_DD[index] value = np.array(value).item() return value
[文档] def CDaR_Abs(X, alpha=0.05): r""" Calculate the Conditional Drawdown at Risk (CDaR) of a returns series using uncompounded cumulative returns. .. math:: \text{CDaR}_{\alpha}(X) = \text{DaR}_{\alpha}(X) + \frac{1}{\alpha T} \sum_{j=0}^{T} \max \left [ \max_{t \in (0,j)} \left ( \sum_{i=0}^{t}X_{i} \right ) - \sum_{i=0}^{j}X_{i} - \text{DaR}_{\alpha}(X), 0 \right ] Where: :math:`\text{DaR}_{\alpha}` is the Drawdown at Risk of an uncompounded cumulated return series :math:`X`. Parameters ---------- X : 1d-array Returns series, must have Tx1 size.. alpha : float, optional Significance level of CDaR. The default is 0.05. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float CDaR of an uncompounded cumulative returns series. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") prices = np.insert(np.array(a), 0, 1, axis=0) NAV = np.cumsum(np.array(prices), axis=0) DD = [] peak = -99999 for i in NAV: if i > peak: peak = i DD.append(-(peak - i)) del DD[0] sorted_DD = np.sort(np.array(DD), axis=0) index = int(np.ceil(alpha * len(sorted_DD)) - 1) sum_var = 0 for i in range(0, index + 1): sum_var = sum_var + sorted_DD[i] - sorted_DD[index] value = -sorted_DD[index] - sum_var / (alpha * len(sorted_DD)) value = np.array(value).item() return value
[文档] def EDaR_Abs(X, alpha=0.05): r""" Calculate the Entropic Drawdown at Risk (EDaR) of a returns series using uncompounded cumulative returns. .. math:: \text{EDaR}_{\alpha}(X) & = \inf_{z>0} \left \{ z \ln \left (\frac{M_{\text{DD}(X)}(z^{-1})}{\alpha} \right ) \right \} \\ \text{DD}(X,j) & = \max_{t \in (0,j)} \left ( \sum_{i=0}^{t}X_{i} \right )- \sum_{i=0}^{j}X_{i} \\ Parameters ---------- X : 1d-array Returns series, must have Tx1 size.. alpha : float, optional Significance level of EDaR. The default is 0.05. Raises ------ ValueError When the value cannot be calculated. Returns ------- (value, z) : tuple EDaR of an uncompounded cumulative returns series and value of z that minimize EDaR. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") prices = np.insert(np.array(a), 0, 1, axis=0) NAV = np.cumsum(np.array(prices), axis=0) DD = [] peak = -99999 for i in NAV: if i > peak: peak = i DD.append(-(peak - i)) del DD[0] (value, t) = EVaR_Hist(np.array(DD), alpha=alpha) return (value, t)
[文档] def RLDaR_Abs(X, alpha=0.05, kappa=0.01, solver=None): r""" Calculate the Relativistic Drawdown at Risk (RLDaR) of a returns series using uncompounded cumulative returns. I recommend only use this function with MOSEK solver. .. math:: \text{RLDaR}^{\kappa}_{\alpha}(X) & = \text{RLVaR}^{\kappa}_{\alpha}(DD(X)) \\ \text{DD}(X,j) & = \max_{t \in (0,j)} \left ( \sum_{i=0}^{t}X_{i} \right )- \sum_{i=0}^{j}X_{i} \\ Parameters ---------- X : 1d-array Returns series, must have Tx1 size. alpha : float, optional Significance level of EVaR. The default is 0.05. kappa : float, optional Deformation parameter of RLDaR, must be between 0 and 1. The default is 0.01. solver: str, optional Solver available for CVXPY that supports power cone programming. Used to calculate RLVaR and RLDaR. The default value is None. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : tuple RLDaR of an uncompounded cumulative returns series. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") prices = np.insert(np.array(a), 0, 1, axis=0) NAV = np.cumsum(np.array(prices), axis=0) DD = [] peak = -99999 for i in NAV: if i > peak: peak = i DD.append(-(peak - i)) del DD[0] value = RLVaR_Hist(np.array(DD), alpha=alpha, kappa=kappa, solver=solver) return value
[文档] def UCI_Abs(X): r""" Calculate the Ulcer Index (UCI) of a returns series using uncompounded cumulative returns. .. math:: \text{UCI}(X) =\sqrt{\frac{1}{T}\sum_{j=0}^{T} \left [ \max_{t \in (0,j)} \left ( \sum_{i=0}^{t}X_{i} \right ) - \sum_{i=0}^{j}X_{i} \right ] ^2} Parameters ---------- X : 1d-array Returns series, must have Tx1 size. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float Ulcer Index of an uncompounded cumulative returns. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") prices = np.insert(np.array(a), 0, 1, axis=0) NAV = np.cumsum(np.array(prices), axis=0) value = 0 peak = -99999 n = 0 for i in NAV: if i > peak: peak = i DD = peak - i if DD > 0: value += DD**2 n += 1 if n == 0: value = 0 else: value = np.sqrt(value / (n - 1)) value = np.array(value).item() return value
[文档] def MDD_Rel(X): r""" Calculate the Maximum Drawdown (MDD) of a returns series using cumpounded cumulative returns. .. math:: \text{MDD}(X) = \max_{j \in (0,T)}\left[\max_{t \in (0,j)} \left ( \prod_{i=0}^{t}(1+X_{i}) \right ) - \prod_{i=0}^{j}(1+X_{i}) \right] Parameters ---------- X : 1d-array Returns series, must have Tx1 size. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float MDD of a cumpounded cumulative returns. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") prices = 1 + np.insert(np.array(a), 0, 0, axis=0) NAV = np.cumprod(prices, axis=0) value = 0 peak = -99999 for i in NAV: if i > peak: peak = i DD = (peak - i) / peak if DD > value: value = DD value = np.array(value).item() return value
[文档] def ADD_Rel(X): r""" Calculate the Average Drawdown (ADD) of a returns series using cumpounded cumulative returns. .. math:: \text{ADD}(X) = \frac{1}{T}\sum_{j=0}^{T} \left [ \max_{t \in (0,j)} \left ( \prod_{i=0}^{t}(1+X_{i}) \right )- \prod_{i=0}^{j}(1+X_{i}) \right ] Parameters ---------- X : 1d-array Returns series, must have Tx1 size. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float ADD of a cumpounded cumulative returns. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") prices = 1 + np.insert(np.array(a), 0, 0, axis=0) NAV = np.cumprod(prices, axis=0) value = 0 peak = -99999 n = 0 for i in NAV: if i > peak: peak = i DD = (peak - i) / peak if DD > 0: value += DD n += 1 if n == 0: value = 0 else: value = value / (n - 1) value = np.array(value).item() return value
[文档] def DaR_Rel(X, alpha=0.05): r""" Calculate the Drawdown at Risk (DaR) of a returns series using cumpounded cumulative returns. .. math:: \text{DaR}_{\alpha}(X) & = \max_{j \in (0,T)} \left \{ \text{DD}(X,j) \in \mathbb{R}: F_{\text{DD}} \left ( \text{DD}(X,j) \right )< 1 - \alpha \right \} \\ \text{DD}(X,j) & = \max_{t \in (0,j)} \left ( \prod_{i=0}^{t}(1+X_{i}) \right )- \prod_{i=0}^{j}(1+X_{i}) Parameters ---------- X : 1d-array Returns series, must have Tx1 size.. alpha : float, optional Significance level of DaR. The default is 0.05. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float DaR of a cumpounded cumulative returns series. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("X must have Tx1 size") prices = 1 + np.insert(np.array(a), 0, 0, axis=0) NAV = np.cumprod(prices, axis=0) DD = [] peak = -99999 for i in NAV: if i > peak: peak = i DD.append(-(peak - i) / peak) del DD[0] sorted_DD = np.sort(np.array(DD), axis=0) index = int(np.ceil(alpha * len(sorted_DD)) - 1) value = -sorted_DD[index] value = np.array(value).item() return value
[文档] def CDaR_Rel(X, alpha=0.05): r""" Calculate the Conditional Drawdown at Risk (CDaR) of a returns series using cumpounded cumulative returns. .. math:: \text{CDaR}_{\alpha}(X) = \text{DaR}_{\alpha}(X) + \frac{1}{\alpha T} \sum_{i=0}^{T} \max \left [ \max_{t \in (0,T)} \left ( \prod_{i=0}^{t}(1+X_{i}) \right )- \prod_{i=0}^{j}(1+X_{i}) - \text{DaR}_{\alpha}(X), 0 \right ] Where: :math:`\text{DaR}_{\alpha}` is the Drawdown at Risk of a cumpound cumulated return series :math:`X`. Parameters ---------- X : 1d-array Returns series, must have Tx1 size.. alpha : float, optional Significance level of CDaR. The default is 0.05. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float CDaR of a cumpounded cumulative returns series. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("X must have Tx1 size") prices = 1 + np.insert(np.array(a), 0, 0, axis=0) NAV = np.cumprod(prices, axis=0) DD = [] peak = -99999 for i in NAV: if i > peak: peak = i DD.append(-(peak - i) / peak) del DD[0] sorted_DD = np.sort(np.array(DD), axis=0) index = int(np.ceil(alpha * len(sorted_DD)) - 1) sum_var = 0 for i in range(0, index + 1): sum_var = sum_var + sorted_DD[i] - sorted_DD[index] value = -sorted_DD[index] - sum_var / (alpha * len(sorted_DD)) value = np.array(value).item() return value
[文档] def EDaR_Rel(X, alpha=0.05): r""" Calculate the Entropic Drawdown at Risk (EDaR) of a returns series using cumpounded cumulative returns. .. math:: \text{EDaR}_{\alpha}(X) & = \inf_{z>0} \left \{ z \ln \left (\frac{M_{\text{DD}(X)}(z^{-1})}{\alpha} \right ) \right \} \\ \text{DD}(X,j) & = \max_{t \in (0,j)} \left ( \prod_{i=0}^{t}(1+X_{i}) \right )- \prod_{i=0}^{j}(1+X_{i}) Parameters ---------- X : 1d-array Returns series, must have Tx1 size.. alpha : float, optional Significance level of EDaR. The default is 0.05. Raises ------ ValueError When the value cannot be calculated. Returns ------- (value, z) : tuple EDaR of a cumpounded cumulative returns series and value of z that minimize EDaR. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("X must have Tx1 size") prices = 1 + np.insert(np.array(a), 0, 0, axis=0) NAV = np.cumprod(prices, axis=0) DD = [] peak = -99999 for i in NAV: if i > peak: peak = i DD.append(-(peak - i) / peak) del DD[0] (value, t) = EVaR_Hist(np.array(DD), alpha=alpha) return (value, t)
[文档] def RLDaR_Rel(X, alpha=0.05, kappa=0.01, solver=None): r""" Calculate the Relativistic Drawdown at Risk (RLDaR) of a returns series using compounded cumulative returns. I recommend only use this function with MOSEK solver. .. math:: \text{RLDaR}^{\kappa}_{\alpha}(X) & = \text{RLVaR}^{\kappa}_{\alpha}(DD(X)) \\ \text{DD}(X,j) & = \max_{t \in (0,j)} \left ( \prod_{i=0}^{t}(1+X_{i}) \right )- \prod_{i=0}^{j}(1+X_{i}) \\ Parameters ---------- X : 1d-array Returns series, must have Tx1 size. alpha : float, optional Significance level of RLDaR. The default is 0.05. kappa : float, optional Deformation parameter of RLDaR, must be between 0 and 1. The default is 0.01. solver: str, optional Solver available for CVXPY that supports power cone programming. Used to calculate RLVaR and RLDaR. The default value is None. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : tuple RLDaR of a compounded cumulative returns series. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("X must have Tx1 size") prices = 1 + np.insert(np.array(a), 0, 0, axis=0) NAV = np.cumprod(prices, axis=0) DD = [] peak = -99999 for i in NAV: if i > peak: peak = i DD.append(-(peak - i) / peak) del DD[0] value = RLVaR_Hist(np.array(DD), alpha=alpha, kappa=kappa, solver=solver) return value
[文档] def UCI_Rel(X): r""" Calculate the Ulcer Index (UCI) of a returns series using cumpounded cumulative returns. .. math:: \text{UCI}(X) =\sqrt{\frac{1}{T}\sum_{j=0}^{T} \left [ \max_{t \in (0,j)} \left ( \prod_{i=0}^{t}(1+X_{i}) \right )- \prod_{i=0}^{j} (1+X_{i}) \right ] ^2} Parameters ---------- X : 1d-array Returns series, must have Tx1 size. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float Ulcer Index of a cumpounded cumulative returns. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") prices = 1 + np.insert(np.array(a), 0, 0, axis=0) NAV = np.cumprod(prices, axis=0) value = 0 peak = -99999 n = 0 for i in NAV: if i > peak: peak = i DD = (peak - i) / peak if DD > 0: value += DD**2 n += 1 if n == 0: value = 0 else: value = np.sqrt(value / (n - 1)) value = np.array(value).item() return value
[文档] def GMD(X): r""" Calculate the Gini Mean Difference (GMD) of a returns series. Parameters ---------- X : 1d-array Returns series, must have Tx1 size. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float Gini Mean Difference of a returns series. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") T = a.shape[0] w_ = owa.owa_gmd(T) value = (w_.T @ np.sort(a, axis=0)).item() return value
[文档] def L_Moment(X, k=2): r""" Calculate the kth l-moment of a returns series. .. math: \lambda_k = {\tbinom{T}{k}}^{-1} \mathop{\sum \sum \ldots \sum}_{1 \leq i_{1} < i_{2} \cdots < i_{k} \leq n} \frac{1}{k} \sum^{k-1}_{j=0} (-1)^{j} \binom{k-1}{j} y_{[i_{k-j}]} \\ Where $y_{[i]}$ is the ith-ordered statistic. Parameters ---------- X : 1d-array Returns series, must have Tx1 size. k : int Order of the l-moment. Must be an integer higher or equal than 1. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float Kth l-moment of a returns series. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") T = a.shape[0] w_ = owa.owa_l_moment(T, k=k) value = (w_.T @ np.sort(a, axis=0)).item() return value
[文档] def L_Moment_CRM(X, k=4, method="MSD", g=0.5, max_phi=0.5, solver=None): r""" Calculate a custom convex risk measure that is a weighted average of first k-th l-moments. Parameters ---------- X : 1d-array Returns series, must have Tx1 size. k : int Order of the l-moment. Must be an integer higher or equal than 2. method : str, optional Method to calculate the weights used to combine the l-moments with order higher than 2. The default value is 'MSD'. Possible values are: - 'CRRA': Normalized Constant Relative Risk Aversion coefficients. - 'ME': Maximum Entropy. - 'MSS': Minimum Sum Squares. - 'MSD': Minimum Square Distance. g : float, optional Risk aversion coefficient of CRRA utility function. The default is 0.5. max_phi : float, optional Maximum weight constraint of L-moments. The default is 0.5. solver: str, optional Solver available for CVXPY. Used to calculate 'ME', 'MSS' and 'MSD' weights. The default value is None. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float Custom convex risk measure that is a weighted average of first k-th l-moments of a returns series. """ if k < 2 or (not isinstance(k, int)): raise ValueError("k must be an integer higher equal than 2") if method not in ["CRRA", "ME", "MSS", "MSD"]: raise ValueError("Available methods are 'CRRA', 'ME', 'MSS' and 'MSD'") if g >= 1 or g <= 0: raise ValueError("The risk aversion coefficient mus be between 0 and 1") if max_phi >= 1 or max_phi <= 0: raise ValueError( "The constraint on maximum weight of L-moments must be between 0 and 1" ) a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") T = a.shape[0] w_ = owa.owa_l_moment_crm( T, k=k, method=method, g=g, max_phi=max_phi, solver=solver ) value = (w_.T @ np.sort(a, axis=0)).item() return value
[文档] def TG(X, alpha=0.05, a_sim=100): r""" Calculate the Tail Gini of a returns series. Parameters ---------- X : 1d-array Returns series, must have Tx1 size. alpha : float, optional Significance level of Tail Gini. The default is 0.05. a_sim : float, optional Number of CVaRs used to approximate Tail Gini. The default is 100. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float Ulcer Index of a cumpounded cumulative returns. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") T = a.shape[0] w_ = owa.owa_tg(T, alpha, a_sim) value = (w_.T @ np.sort(a, axis=0)).item() return value
[文档] def RG(X): r""" Calculate the range of a returns series. Parameters ---------- X : 1d-array Returns series, must have Tx1 size. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float Ulcer Index of a cumpounded cumulative returns. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") T = a.shape[0] w_ = owa.owa_rg(T) value = (w_.T @ np.sort(a, axis=0)).item() return value
[文档] def CVRG(X, alpha=0.05, beta=None): r""" Calculate the CVaR range of a returns series. Parameters ---------- X : 1d-array Returns series, must have Tx1 size. alpha : float, optional Significance level of CVaR of losses. The default is 0.05. beta : float, optional Significance level of CVaR of gains. If None it duplicates alpha value. The default is None. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float Ulcer Index of a cumpounded cumulative returns. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") T = a.shape[0] w_ = owa.owa_cvrg(T, alpha=alpha, beta=beta) value = (w_.T @ np.sort(a, axis=0)).item() return value
[文档] def TGRG(X, alpha=0.05, a_sim=100, beta=None, b_sim=None): r""" Calculate the Tail Gini range of a returns series. Parameters ---------- X : 1d-array Returns series, must have Tx1 size. alpha : float, optional Significance level of Tail Gini of losses. The default is 0.05. a_sim : float, optional Number of CVaRs used to approximate Tail Gini of losses. The default is 100. beta : float, optional Significance level of Tail Gini of gains. If None it duplicates alpha value. The default is None. b_sim : float, optional Number of CVaRs used to approximate Tail Gini of gains. If None it duplicates a_sim value. The default is None. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float Ulcer Index of a cumpounded cumulative returns. """ a = np.array(X, ndmin=2) if a.shape[0] == 1 and a.shape[1] > 1: a = a.T if a.shape[0] > 1 and a.shape[1] > 1: raise ValueError("returns must have Tx1 size") T = a.shape[0] w_ = owa.owa_tgrg(T, alpha=alpha, a_sim=a_sim, beta=beta, b_sim=b_sim) value = (w_.T @ np.sort(a, axis=0)).item() return value
############################################################################### # Risk Adjusted Return Ratios ###############################################################################
[文档] def Sharpe_Risk( w, cov=None, returns=None, rm="MV", rf=0, alpha=0.05, a_sim=100, beta=None, b_sim=None, kappa=0.01, solver=None, ): r""" Calculate the risk measure available on the Sharpe function. Parameters ---------- w : DataFrame or 1d-array of shape (n_assets, 1) Weights matrix, where n_assets is the number of assets. cov : DataFrame or nd-array of shape (n_features, n_features) Covariance matrix, where n_features is the number of features. returns : DataFrame or nd-array of shape (n_samples, n_features) Features matrix, where n_samples is the number of samples and n_features is the number of features. rm : str, optional Risk measure used in the denominator of the ratio. The default is 'MV'. Possible values are: - 'MV': Standard Deviation. - 'KT': Square Root Kurtosis. - 'MAD': Mean Absolute Deviation. - 'GMD': Gini Mean Difference. - 'MSV': Semi Standard Deviation. - 'SKT': Square Root Semi Kurtosis. - 'FLPM': First Lower Partial Moment (Omega Ratio). - 'SLPM': Second Lower Partial Moment (Sortino Ratio). - 'VaR': Value at Risk. - 'CVaR': Conditional Value at Risk. - 'TG': Tail Gini. - 'EVaR': Entropic Value at Risk. - 'RLVaR': Relativistic Value at Risk. I recommend only use this function with MOSEK solver. - 'WR': Worst Realization (Minimax). - 'RG': Range of returns. - 'CVRG': CVaR range of returns. - 'TGRG': Tail Gini range of returns. - 'MDD': Maximum Drawdown of uncompounded cumulative returns (Calmar Ratio). - 'ADD': Average Drawdown of uncompounded cumulative returns. - 'DaR': Drawdown at Risk of uncompounded cumulative returns. - 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns. - 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns. - 'RLDaR': Relativistic Drawdown at Risk of uncompounded cumulative returns. I recommend only use this risk measure with MOSEK solver. - 'UCI': Ulcer Index of uncompounded cumulative returns. - 'MDD_Rel': Maximum Drawdown of compounded cumulative returns (Calmar Ratio). - 'ADD_Rel': Average Drawdown of compounded cumulative returns. - 'CDaR_Rel': Conditional Drawdown at Risk of compounded cumulative returns. - 'EDaR_Rel': Entropic Drawdown at Risk of compounded cumulative returns. - 'RLDaR_Rel': Relativistic Drawdown at Risk of compounded cumulative returns. I recommend only use this risk measure with MOSEK solver. - 'UCI_Rel': Ulcer Index of compounded cumulative returns. rf : float, optional Risk free rate. The default is 0. alpha : float, optional Significance level of VaR, CVaR, EVaR, RLVaR, DaR, CDaR, EDaR, RLDaR and Tail Gini of losses. The default is 0.05. a_sim : float, optional Number of CVaRs used to approximate Tail Gini of losses. The default is 100. beta : float, optional Significance level of CVaR and Tail Gini of gains. If None it duplicates alpha value. The default is None. b_sim : float, optional Number of CVaRs used to approximate Tail Gini of gains. If None it duplicates a_sim value. The default is None. kappa : float, optional Deformation parameter of RLVaR, must be between 0 and 1. The default is 0.01. solver: str, optional Solver available for CVXPY that supports power cone programming. Used to calculate RLVaR and RLDaR. The default value is None. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float Risk measure of the portfolio. """ w_ = np.array(w, ndmin=2) if w_.shape[0] == 1 and w_.shape[1] > 1: w_ = w_.T if w_.shape[0] > 1 and w_.shape[1] > 1: raise ValueError("weights must have n_assets x 1 size") if cov is not None: cov_ = np.array(cov, ndmin=2) if returns is not None: returns_ = np.array(returns, ndmin=2) a = returns_ @ w_ if rm == "MV": risk = w_.T @ cov_ @ w_ risk = np.sqrt(risk.item()) elif rm == "MAD": risk = MAD(a) elif rm == "GMD": risk = GMD(a) elif rm == "MSV": risk = SemiDeviation(a) elif rm == "FLPM": risk = LPM(a, MAR=rf, p=1) elif rm == "SLPM": risk = LPM(a, MAR=rf, p=2) elif rm == "VaR": risk = VaR_Hist(a, alpha=alpha) elif rm == "CVaR": risk = CVaR_Hist(a, alpha=alpha) elif rm == "TG": risk = TG(a, alpha=alpha, a_sim=a_sim) elif rm == "EVaR": risk = EVaR_Hist(a, alpha=alpha)[0] elif rm == "RLVaR": risk = RLVaR_Hist(a, alpha=alpha, kappa=kappa, solver=solver) elif rm == "WR": risk = WR(a) elif rm == "RG": risk = RG(a) elif rm == "CVRG": risk = CVRG(a, alpha=alpha, beta=beta) elif rm == "TGRG": risk = TGRG(a, alpha=alpha, a_sim=a_sim, beta=beta, b_sim=b_sim) elif rm == "MDD": risk = MDD_Abs(a) elif rm == "ADD": risk = ADD_Abs(a) elif rm == "DaR": risk = DaR_Abs(a, alpha=alpha) elif rm == "CDaR": risk = CDaR_Abs(a, alpha=alpha) elif rm == "EDaR": risk = EDaR_Abs(a, alpha=alpha)[0] elif rm == "RLDaR": risk = RLDaR_Abs(a, alpha=alpha, kappa=kappa, solver=solver) elif rm == "UCI": risk = UCI_Abs(a) elif rm == "MDD_Rel": risk = MDD_Rel(a) elif rm == "ADD_Rel": risk = ADD_Rel(a) elif rm == "DaR_Rel": risk = DaR_Rel(a, alpha=alpha) elif rm == "CDaR_Rel": risk = CDaR_Rel(a, alpha=alpha) elif rm == "EDaR_Rel": risk = EDaR_Rel(a, alpha=alpha)[0] elif rm == "RLDaR_Rel": risk = RLDaR_Rel(a, alpha=alpha, kappa=kappa, solver=solver) elif rm == "UCI_Rel": risk = UCI_Rel(a) elif rm == "KT": risk = Kurtosis(a) elif rm == "SKT": risk = SemiKurtosis(a) value = risk return value
[文档] def Sharpe( w, mu, cov=None, returns=None, rm="MV", rf=0, alpha=0.05, a_sim=100, beta=None, b_sim=None, kappa=0.01, solver=None, ): r""" Calculate the Risk Adjusted Return Ratio from a portfolio returns series. .. math:: \text{Sharpe}(X) = \frac{\mathbb{E}(X) - r_{f}}{\phi(X)} Where: :math:`X` is the vector of portfolio returns. :math:`r_{f}` is the risk free rate, when the risk measure is :math:`\text{LPM}` uses instead of :math:`r_{f}` the :math:`\text{MAR}`. :math:`\phi(X)` is a convex risk measure. The risk measures availabe are: Parameters ---------- w : DataFrame or 1d-array of shape (n_assets, 1) Weights matrix, where n_assets is the number of assets. mu : DataFrame or nd-array of shape (1, n_assets) Vector of expected returns, where n_assets is the number of assets. cov : DataFrame or nd-array of shape (n_features, n_features) Covariance matrix, where n_features is the number of features. returns : DataFrame or nd-array of shape (n_samples, n_features) Features matrix, where n_samples is the number of samples and n_features is the number of features. rm : str, optional Risk measure used in the denominator of the ratio. The default is 'MV'. Possible values are: - 'MV': Standard Deviation. - 'KT': Square Root Kurtosis. - 'MAD': Mean Absolute Deviation. - 'GMD': Gini Mean Difference. - 'MSV': Semi Standard Deviation. - 'SKT': Square Root Semi Kurtosis. - 'FLPM': First Lower Partial Moment (Omega Ratio). - 'SLPM': Second Lower Partial Moment (Sortino Ratio). - 'VaR': Value at Risk. - 'CVaR': Conditional Value at Risk. - 'TG': Tail Gini. - 'EVaR': Entropic Value at Risk. - 'RLVaR': Relativistic Value at Risk. I recommend only use this function with MOSEK solver. - 'WR': Worst Realization (Minimax). - 'RG': Range of returns. - 'CVRG': CVaR range of returns. - 'TGRG': Tail Gini range of returns. - 'MDD': Maximum Drawdown of uncompounded cumulative returns (Calmar Ratio). - 'ADD': Average Drawdown of uncompounded cumulative returns. - 'DaR': Drawdown at Risk of uncompounded cumulative returns. - 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns. - 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns. - 'RLDaR': Relativistic Drawdown at Risk of uncompounded cumulative returns. I recommend only use this function with MOSEK solver. - 'UCI': Ulcer Index of uncompounded cumulative returns. - 'MDD_Rel': Maximum Drawdown of compounded cumulative returns (Calmar Ratio). - 'ADD_Rel': Average Drawdown of compounded cumulative returns. - 'CDaR_Rel': Conditional Drawdown at Risk of compounded cumulative returns. - 'EDaR_Rel': Entropic Drawdown at Risk of compounded cumulative returns. - 'RLDaR_Rel': Relativistic Drawdown at Risk of compounded cumulative returns. I recommend only use this function with MOSEK solver. - 'UCI_Rel': Ulcer Index of compounded cumulative returns. rf : float, optional Risk free rate. The default is 0. alpha : float, optional Significance level of VaR, CVaR, EVaR, RLVaR, DaR, CDaR, EDaR, RLDaR and Tail Gini of losses. The default is 0.05. a_sim : float, optional Number of CVaRs used to approximate Tail Gini of losses. The default is 100. beta : float, optional Significance level of CVaR and Tail Gini of gains. If None it duplicates alpha value. The default is None. b_sim : float, optional Number of CVaRs used to approximate Tail Gini of gains. If None it duplicates a_sim value. The default is None. kappa : float, optional Deformation parameter of RLVaR, must be between 0 and 1. The default is 0.01. solver: str, optional Solver available for CVXPY that supports power cone programming. Used to calculate RLVaR and RLDaR. The default value is None. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float Risk adjusted return ratio of :math:`X`. """ w_ = np.array(w, ndmin=2) if w_.shape[0] == 1 and w_.shape[1] > 1: w_ = w_.T if w_.shape[0] > 1 and w_.shape[1] > 1: raise ValueError("weights must have n_assets x 1 size") if cov is None and rm == "MV": raise ValueError("covariance matrix is necessary to calculate the sharpe ratio") elif returns is None and rm != "MV": raise ValueError( "returns scenarios are necessary to calculate the sharpe ratio" ) mu_ = np.array(mu, ndmin=2) if cov is not None: cov_ = np.array(cov, ndmin=2) if returns is not None: returns_ = np.array(returns, ndmin=2) ret = mu_ @ w_ ret = ret.item() risk = Sharpe_Risk( w, cov=cov_, returns=returns_, rm=rm, rf=rf, alpha=alpha, a_sim=a_sim, beta=beta, b_sim=b_sim, kappa=kappa, solver=solver, ) value = (ret - rf) / risk return value
############################################################################### # Risk Contribution Vectors ###############################################################################
[文档] def Risk_Contribution( w, cov=None, returns=None, rm="MV", rf=0, alpha=0.05, a_sim=100, beta=None, b_sim=None, kappa=0.01, solver=None, ): r""" Calculate the risk contribution for each asset based on the risk measure selected. Parameters ---------- w : DataFrame or 1d-array of shape (n_assets, 1) Weights matrix, where n_assets is the number of assets. cov : DataFrame or nd-array of shape (n_features, n_features) Covariance matrix, where n_features is the number of features. returns : DataFrame or nd-array of shape (n_samples, n_features) Features matrix, where n_samples is the number of samples and n_features is the number of features. rm : str, optional Risk measure used in the denominator of the ratio. The default is 'MV'. Possible values are: - 'MV': Standard Deviation. - 'KT': Square Root Kurtosis. - 'MAD': Mean Absolute Deviation. - 'GMD': Gini Mean Difference. - 'MSV': Semi Standard Deviation. - 'SKT': Square Root Semi Kurtosis. - 'FLPM': First Lower Partial Moment (Omega Ratio). - 'SLPM': Second Lower Partial Moment (Sortino Ratio). - 'VaR': Value at Risk. - 'CVaR': Conditional Value at Risk. - 'TG': Tail Gini. - 'EVaR': Entropic Value at Risk. - 'RLVaR': Relativistic Value at Risk. I recommend only use this function with MOSEK solver. - 'WR': Worst Realization (Minimax). - 'RG': Range of returns. - 'CVRG': CVaR range of returns. - 'TGRG': Tail Gini range of returns. - 'MDD': Maximum Drawdown of uncompounded cumulative returns (Calmar Ratio). - 'ADD': Average Drawdown of uncompounded cumulative returns. - 'DaR': Drawdown at Risk of uncompounded cumulative returns. - 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns. - 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns. - 'RLDaR': Relativistic Drawdown at Risk of uncompounded cumulative returns. I recommend only use this function with MOSEK solver. - 'UCI': Ulcer Index of uncompounded cumulative returns. - 'MDD_Rel': Maximum Drawdown of compounded cumulative returns (Calmar Ratio). - 'ADD_Rel': Average Drawdown of compounded cumulative returns. - 'CDaR_Rel': Conditional Drawdown at Risk of compounded cumulative returns. - 'EDaR_Rel': Entropic Drawdown at Risk of compounded cumulative returns. - 'RLDaR_Rel': Relativistic Drawdown at Risk of compounded cumulative returns. I recommend only use this function with MOSEK solver. - 'UCI_Rel': Ulcer Index of compounded cumulative returns. rf : float, optional Risk free rate. The default is 0. alpha : float, optional Significance level of VaR, CVaR, EVaR, RLVaR, DaR, CDaR, EDaR, RLDaR and Tail Gini of losses. The default is 0.05. a_sim : float, optional Number of CVaRs used to approximate Tail Gini of losses. The default is 100. beta : float, optional Significance level of CVaR and Tail Gini of gains. If None it duplicates alpha value. The default is None. b_sim : float, optional Number of CVaRs used to approximate Tail Gini of gains. If None it duplicates a_sim value. The default is None. kappa : float, optional Deformation parameter of RLVaR, must be between 0 and 1. The default is 0.01. solver: str, optional Solver available for CVXPY that supports power cone programming. Used to calculate RLVaR and RLDaR. The default value is None. Raises ------ ValueError When the value cannot be calculated. Returns ------- value : float Risk measure of the portfolio. """ w_ = np.array(w, ndmin=2) if w_.shape[0] == 1 and w_.shape[1] > 1: w_ = w_.T if w_.shape[0] > 1 and w_.shape[1] > 1: raise ValueError("weights must have n_assets x 1 size") if cov is not None: cov_ = np.array(cov, ndmin=2) if returns is not None: returns_ = np.array(returns, ndmin=2) RC = [] if rm in ["RLVaR", "RLDaR"]: d_i = 0.0001 else: d_i = 0.0000001 for i in range(0, w_.shape[0]): delta = np.zeros((w_.shape[0], 1)) delta[i, 0] = d_i w_1 = w_ + delta w_2 = w_ - delta a_1 = returns_ @ w_1 a_2 = returns_ @ w_2 if rm == "MV": risk_1 = w_1.T @ cov_ @ w_1 risk_1 = np.sqrt(risk_1.item()) risk_2 = w_2.T @ cov_ @ w_2 risk_2 = np.sqrt(risk_2.item()) elif rm == "MAD": risk_1 = MAD(a_1) risk_2 = MAD(a_2) elif rm == "GMD": risk_1 = GMD(a_1) risk_2 = GMD(a_2) elif rm == "MSV": risk_1 = SemiDeviation(a_1) risk_2 = SemiDeviation(a_2) elif rm == "FLPM": risk_1 = LPM(a_1, MAR=rf, p=1) risk_2 = LPM(a_2, MAR=rf, p=1) elif rm == "SLPM": risk_1 = LPM(a_1, MAR=rf, p=2) risk_2 = LPM(a_2, MAR=rf, p=2) elif rm == "VaR": risk_1 = VaR_Hist(a_1, alpha=alpha) risk_2 = VaR_Hist(a_2, alpha=alpha) elif rm == "CVaR": risk_1 = CVaR_Hist(a_1, alpha=alpha) risk_2 = CVaR_Hist(a_2, alpha=alpha) elif rm == "TG": risk_1 = TG(a_1, alpha=alpha, a_sim=a_sim) risk_2 = TG(a_2, alpha=alpha, a_sim=a_sim) elif rm == "EVaR": risk_1 = EVaR_Hist(a_1, alpha=alpha)[0] risk_2 = EVaR_Hist(a_2, alpha=alpha)[0] elif rm == "RLVaR": risk_1 = RLVaR_Hist(a_1, alpha=alpha, kappa=kappa, solver=solver) risk_2 = RLVaR_Hist(a_2, alpha=alpha, kappa=kappa, solver=solver) elif rm == "WR": risk_1 = WR(a_1) risk_2 = WR(a_2) elif rm == "CVRG": risk_1 = CVRG(a_1, alpha=alpha, beta=beta) risk_2 = CVRG(a_2, alpha=alpha, beta=beta) elif rm == "TGRG": risk_1 = TGRG(a_1, alpha=alpha, a_sim=a_sim, beta=beta, b_sim=b_sim) risk_2 = TGRG(a_2, alpha=alpha, a_sim=a_sim, beta=beta, b_sim=b_sim) elif rm == "RG": risk_1 = RG(a_1) risk_2 = RG(a_2) elif rm == "MDD": risk_1 = MDD_Abs(a_1) risk_2 = MDD_Abs(a_2) elif rm == "ADD": risk_1 = ADD_Abs(a_1) risk_2 = ADD_Abs(a_2) elif rm == "DaR": risk_1 = DaR_Abs(a_1, alpha=alpha) risk_2 = DaR_Abs(a_2, alpha=alpha) elif rm == "CDaR": risk_1 = CDaR_Abs(a_1, alpha=alpha) risk_2 = CDaR_Abs(a_2, alpha=alpha) elif rm == "EDaR": risk_1 = EDaR_Abs(a_1, alpha=alpha)[0] risk_2 = EDaR_Abs(a_2, alpha=alpha)[0] elif rm == "RLDaR": risk_1 = RLDaR_Abs(a_1, alpha=alpha, kappa=kappa, solver=solver) risk_2 = RLDaR_Abs(a_2, alpha=alpha, kappa=kappa, solver=solver) elif rm == "UCI": risk_1 = UCI_Abs(a_1) risk_2 = UCI_Abs(a_2) elif rm == "MDD_Rel": risk_1 = MDD_Rel(a_1) risk_2 = MDD_Rel(a_2) elif rm == "ADD_Rel": risk_1 = ADD_Rel(a_1) risk_2 = ADD_Rel(a_2) elif rm == "DaR_Rel": risk_1 = DaR_Rel(a_1, alpha=alpha) risk_2 = DaR_Rel(a_2, alpha=alpha) elif rm == "CDaR_Rel": risk_1 = CDaR_Rel(a_1, alpha=alpha) risk_2 = CDaR_Rel(a_2, alpha=alpha) elif rm == "EDaR_Rel": risk_1 = EDaR_Rel(a_1, alpha=alpha)[0] risk_2 = EDaR_Rel(a_2, alpha=alpha)[0] elif rm == "RLDaR_Rel": risk_1 = RLDaR_Rel(a_1, alpha=alpha, kappa=kappa, solver=solver) risk_2 = RLDaR_Rel(a_2, alpha=alpha, kappa=kappa, solver=solver) elif rm == "UCI_Rel": risk_1 = UCI_Rel(a_1) risk_2 = UCI_Rel(a_2) elif rm == "KT": risk_1 = Kurtosis(a_1) * 0.5 risk_2 = Kurtosis(a_2) * 0.5 elif rm == "SKT": risk_1 = SemiKurtosis(a_1) * 0.5 risk_2 = SemiKurtosis(a_2) * 0.5 RC_i = (risk_1 - risk_2) / (2 * d_i) * w_[i, 0] RC.append(RC_i) RC = np.array(RC, ndmin=1) return RC