Beispiel #1
0
    def _process_parameters(dim: OptNumeric, mean: OptNumeric, cov: OptNumeric, df: Optional[float]):
        if dim is None:
            if cov is None:
                dim = 1
                cov = np.array([[1.]], dtype=float)
            else:
                if isinstance(cov, (float, int)):
                    cov = np.array([[cov]], dtype=float)

                dim = len(cov)
                cov = np.asarray(cov, dtype=float).reshape(dim, dim)
        else:
            if not np.isscalar(dim):
                raise ValueError("Dimension of random variable must be a scalar.")

        if mean is None:
            mean = np.zeros(dim)
        elif isinstance(mean, (float, int)):
            mean = np.repeat(mean, dim)
        else:
            mean = np.asarray(mean, dtype=float)

        # checks done here
        if mean.shape[0] != dim or mean.ndim != 1:
            raise ValueError("Array 'mean' must be a vector of length %d." % dim)

        if not is_psd(cov):
            raise ValueError("Matrix 'cov' must be positive semi-definite")

        if df is None:
            df = 4.6692  # Random Feigenbaum Number
        elif df <= 0:
            raise ValueError("Degrees of freedom 'df' must be greater than 0")

        return dim, mean, cov, df
Beispiel #2
0
def _get_covariance_matrix(cov_or_data: np.ndarray, freq: Frequency):
    """
    Helper to derive a covariance matrix.

    If simulated cube is put in, derive empirical covariance matrix. Otherwise, check that matrix
    is a covariance matrix and return it.
    """

    assert cov_or_data.ndim in (
        2, 3
    ), "only 2D covariance matrix or a 3D simulation returns tensor is allowed"
    cov_or_data = np.asarray(cov_or_data)
    n = cov_or_data.shape[
        -1]  # number of assets must be last dimension of cube or covariance matrix

    if cov_or_data.ndim == 3:
        # if 3D, get empirical covariance matrix
        assert n == cov_or_data.shape[
            2], "number of weights does not match simulation cube asset quantity"
        assert freq is not None, "frequency must be specified if deriving covariance structure from simulated data"

        cov_or_data = portfolio_cov(cov_or_data, freq)

    assert is_psd(
        cov_or_data), "covariance matrix is not positive semi-definite"
    return cov_or_data  # this will be a covariance matrix
Beispiel #3
0
    def log_lik(self, data: np.ndarray):
        if not is_psd(self.sigma):
            return -np.inf

        if hasattr(self, '_df') and self._df <= 0:  # t copula
            return -np.inf

        return super().log_lik(data)
Beispiel #4
0
    def log_lik(self, data: np.ndarray):
        if not is_psd(self.sigma):
            return -np.inf

        if hasattr(self, '_df') and self._df <= 0:  # t copula
            return -np.inf

        return super().log_lik(data)
Beispiel #5
0
    def log_lik(self, data: np.ndarray, *, to_pobs=True, ties="average"):
        if not is_psd(self.sigma):
            return -np.inf

        if hasattr(self, '_df') and getattr(self, "_df", 0) <= 0:  # t copula
            return -np.inf

        return super().log_lik(data, to_pobs=to_pobs, ties=ties)
Beispiel #6
0
    def __init__(self, data: np.ndarray, time_unit='monthly'):
        """
        Returns an OptData class which is an enhancement of ndarray with helper methods

        Parameters
        ----------
        data: ndarray
            3D tensor where the first axis represents the time period, the second axis represents the trials
            and the third axis represents the assets

        time_unit: {int, 'monthly', 'quarterly', 'semi-annually', 'yearly'}, optional
            Specifies how many units (first axis) is required to represent a year. For example, if each time period
            represents a month, set this to 12. If quarterly, set to 4. Defaults to 12 which means 1 period represents
            a month. Alternatively, specify one of 'monthly', 'quarterly', 'semi-annually' or 'yearly'
        """

        assert data.ndim == 3, "Data must be 3 dimensional with shape like (t, n, a) where `t` represents the time " \
                               "periods, `n` represents the trials and `a` represents the assets"

        periods, trials, n_assets = data.shape
        self.time_unit = translate_frequency(time_unit)

        # empirical covariance taken along the time-asset axis then averaged by trials
        # annualized data
        a = (data + 1).reshape(periods // self.time_unit, self.time_unit,
                               trials, n_assets).prod(1) - 1
        cov_mat = np.mean(
            [np.cov(a[i].T) for i in range(periods // self.time_unit)], 0)
        cov_mat = near_psd(cov_mat)
        assert is_psd(
            cov_mat), "covariance matrix must be positive semi-definite"

        if np.allclose(np.diag(cov_mat),
                       1) and np.alltrue(np.abs(cov_mat) <= 1):
            warnings.warn(
                "The covariance matrix feels like a correlation matrix. Are you sure it's correct?"
            )

        self.n_years = periods / self.time_unit
        self.n_assets = n_assets

        # minor optimization when using rebalanced optimization. This is essentially a cache
        self._unrebalanced_returns_data: Optional[np.ndarray] = None
        self._cov_mat = cov_mat
Beispiel #7
0
    def _process_parameters(dim: OptNumeric, mean: OptNumeric, cov: OptNumeric,
                            df: Optional[float]):
        if dim is None:
            if cov is None:
                dim = 1
                cov = np.array([[1.]], dtype=float)
            else:
                if isinstance(cov, (float, int)):
                    cov = np.array([[cov]], dtype=float)

                dim = len(cov)
                cov = np.asarray(cov, dtype=float).reshape(dim, dim)
        else:
            if not np.isscalar(dim):
                raise ValueError(
                    "Dimension of random variable must be a scalar.")

        if mean is None:
            mean = np.zeros(dim)
        elif isinstance(mean, (float, int)):
            mean = np.repeat(mean, dim)
        else:
            mean = np.asarray(mean, dtype=float)

        # checks done here
        if mean.shape[0] != dim or mean.ndim != 1:
            raise ValueError("Array 'mean' must be a vector of length %d." %
                             dim)

        if not is_psd(cov):
            raise ValueError("Matrix 'cov' must be positive semi-definite")

        if df is None:
            df = 4.6692  # Random Feigenbaum Number
        elif df <= 0:
            raise ValueError("Degrees of freedom 'df' must be greater than 0")

        return dim, mean, cov, df
Beispiel #8
0
def sharpe_ratio_bmk_m(data: np.ndarray,
                       weights: Vector,
                       bmk_weights: Vector,
                       freq: Frequency,
                       cov: np.ndarray = None,
                       geometric: bool = True,
                       rebalance: bool = True):
    """
    Calculates the Sharpe Ratio of the portfolio over the benchmark.

    The benchmark components must be placed after the portfolio components in the simulated returns cube.

    Parameters
    ----------
    data
        Monte carlo simulation data. This must be 3 dimensional with the axis representing time, trial
        and asset respectively.

    weights
        Weights of the portfolio. This must be 1 dimensional and must match the dimension of the data's
        last axis.

    bmk_weights
        Weights of the benchmark portfolio.

    freq
        Frequency of the data. Can either be a string ('week', 'month', 'quarter', 'semi-annual', 'year') or
        an integer specifying the number of units per year. Week: 52, Month: 12, Quarter: 4, Semi-annual: 6,
        Year: 1.

    cov
        Covariance matrix. If not specified, the empirical covariance matrix from the joined data and
        benchmark data will be derived.

    geometric
        If True, calculates the geometric mean, otherwise, calculates the arithmetic mean.

    rebalance
        If True, portfolio is assumed to be rebalanced at every step.

    Returns
    -------
    float
        Sharpe ratio of the portfolio over the benchmark

    Examples
    --------
    >>> from perfana.datasets import load_cube
    >>> from perfana.monte_carlo import sharpe_ratio_bmk_m
    >>> cube = load_cube()
    >>> weights = [0.25, 0.18, 0.13, 0.11, 0.24, 0.05, 0.04]
    >>> bmk_weights = [0.65, 0.35]
    >>> freq = 'quarterly'
    >>> sharpe_ratio_bmk_m(cube, weights, bmk_weights, freq)
    -0.2186945589389277
    """
    gr = annualized_bmk_returns_m(data, weights, bmk_weights, freq, geometric,
                                  rebalance)

    if cov is not None:
        n = len(weights) + len(bmk_weights)
        assert cov.shape == (n, n), "covariance matrix shape incorrect"
        assert is_psd(cov), "covariance matrix is not positive semi-definite"
        data = cov

    te = tracking_error_m(data, weights, bmk_weights, freq)
    return gr / te