def fit_cor(copula: Copula, data: np.ndarray, typ: str) -> np.ndarray: """ Constructs parameter matrix from matrix of Kendall's Taus or Spearman's Rho Parameters ---------- copula: AbstractCopula Copula instance data: ndarray Data to fit copula with typ: {'irho', 'itau'} The type of rank correlation measure to use. 'itau' uses Kendall's tau while 'irho' uses Spearman's rho Returns ------- ndarray Parameter matrix is copula is elliptical. Otherwise, a vector """ indices = tri_indices(copula.dim, 1, 'lower') if typ == 'itau': tau = kendall_tau(data)[indices] theta = copula.itau(tau) elif typ == 'irho': rho = spearman_rho(data)[indices] theta = copula.irho(rho) else: raise ValueError("Correlation Inversion must be either 'itau' or 'irho'") if is_elliptical(copula): theta = near_psd(create_cov_matrix(theta))[indices] return theta
def _force_psd(self): """ Forces covariance matrix to be positive semi-definite. This is useful when user is overwriting covariance parameters """ cov = near_psd(self.sigma) self._rhos = cov[tri_indices(self.dim, 1, 'lower')]
def fit_cor(copula, data: np.ndarray, typ: str) -> np.ndarray: """ Constructs parameter matrix from matrix of Kendall's Taus or Spearman's Rho Parameters ---------- copula: BaseCopula Copula instance data: ndarray Data to fit copula with typ: {'irho', 'itau'} The type of rank correlation measure to use. 'itau' uses Kendall's tau while 'irho' uses Spearman's rho Returns ------- ndarray Parameter matrix is copula is elliptical. Otherwise, a vector """ indices = tri_indices(copula.dim, 1, 'lower') if typ == 'itau': tau = kendall_tau(data)[indices] theta = copula.itau(tau) elif typ == 'irho': rho = spearman_rho(data)[indices] theta = copula.irho(rho) else: raise ValueError( "Correlation Inversion must be either 'itau' or 'irho'") if is_elliptical(copula): theta = near_psd(create_cov_matrix(theta))[indices] return theta
def __setitem__(self, index: Union[int, Tuple[Union[slice, int], Union[slice, int]], slice], value: Union[float, Collection[float], np.ndarray]): d = self.dim if np.isscalar(value): if value < -1 or value > 1: raise ValueError("correlation value must be between -1 and 1") else: value = np.asarray(value) if not np.all((value >= -1 - EPS) & (value <= 1 + EPS)): raise ValueError("correlation value must be between -1 and 1") if isinstance(index, slice): value = near_psd(value) if value.shape != (d, d): raise ValueError( f"The value being set should be a matrix of dimension ({d}, {d})" ) self._rhos = value[tri_indices(d, 1, 'lower')] return if isinstance(index, int): self._rhos[index] = value else: index = tuple(index) if len(index) != 2: raise IndexError('index can only be 1 or 2-dimensional') x, y = index # having 2 slices for indices is equivalent to self[:] if isinstance(x, slice) and isinstance(y, slice): self[:] = value return elif isinstance(x, slice) or isinstance(y, slice): value = np.repeat( value, d) if np.isscalar(value) else np.asarray(value) if len(value) != d: raise ValueError( f"value must be a scalar or be a vector with length {d}" ) # one of the item is for i, v in enumerate(value): idx = (i, y) if isinstance(x, slice) else (x, i) if idx[0] == idx[1]: # skip diagonals continue idx = _get_rho_index(d, idx) self._rhos[idx] = v else: # both are integers idx = _get_rho_index(d, index) self._rhos[idx] = float(value) self._force_psd()
def covs(self, value: Union[C[float], np.ndarray]): value = np.asarray(value) shape = self.n_clusters, self.n_dim, self.n_dim if value.shape != shape: raise GMCParamError(f"covariance array should have shape {shape}") self._covs = np.array([near_psd(cov) for cov in value ]) # forces covariance matrix to be psd
def __setitem__(self, i, value): d = self.dim if isinstance(i, slice): value = near_psd(value) if value.shape != (d, d): return IndexError(f"The value being set should be a matrix of dimension ({d}, {d})") self._rhos = value[tri_indices(d, 1, 'lower')] return if isinstance(i, int): self._rhos[i] = value else: i = _get_rho_index(d, i) self._rhos[i] = value self._force_psd()
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
def __setitem__(self, i, value): d = self.dim if isinstance(i, slice): value = near_psd(value) if value.shape != (d, d): return IndexError(f"The value being set should be a matrix of dimension ({d}, {d})") self._rhos = value[tri_indices(d, 1, 'lower')] return assert -1.0 <= value <= 1.0, "correlation value must be between -1 and 1" if isinstance(i, int): self._rhos[i] = value else: i = _get_rho_index(d, i) self._rhos[i] = value self._force_psd()
def coalesce_covariance_matrix( cov, w: Iterable[float], indices: Optional[Iterable[int]] = None) -> Union[np.ndarray, float]: """ Aggregates the covariance with the weights given at the indices specified The aggregated column will be the first column. Parameters ---------- cov: ndarray Covariance matrix of the portfolio w: ndarray The weights to aggregate the columns by. Weights do not have to sum to 1, if it needs to, you should check it prior indices: iterable int, optional The column index of the aggregated data. If not specified, method will aggregate the first 'n' columns where 'n' is the length of :code:`w` Returns ------- ndarray Aggregated covariance matrix Examples -------- If we have a (60 x 1000 x 10) data and we want to aggregate the assets the first 3 indexes, >>> from allopy.opt_data import coalesce_covariance_matrix >>> import numpy as np form covariance matrix >>> np.random.seed(8888) >>> cov = np.random.standard_normal((5, 5)) >>> cov = cov @ cov.T coalesce first and second column where contribution is (30%, 70%) respectively. Does not have to sum to 1 >>> coalesce_covariance_matrix(cov, (0.3, 0.7)) coalesce fourth and fifth column >>> coalesce_covariance_matrix(cov, (0.2, 0.4), (3, 4)) """ w = np.asarray(w) cov = np.asarray(cov) n = len(w) assert cov.ndim == 2 and cov.shape[0] == cov.shape[ 1], 'cov must be a square matrix' assert n <= len( cov), 'adjustment weights cannot be larger than the covariance matrix' if indices is None: indices = np.arange(n) _, a = cov.shape # get number of assets originally # form transform matrix T = np.zeros((a - n + 1, a)) T[0, :n] = w T[1:, n:] = np.eye(a - n) # re-order covariance matrix rev_indices = sorted( set(range(a)) - set(indices)) # these are the indices that are not aggregated indices = [*indices, *rev_indices] cov = cov[ indices][:, indices] # reorder the covariance matrix, first by rows then by columns cov = T @ cov @ T.T return float(cov) if cov.size == 1 else near_psd(cov)