Coverage for src / cvx / markowitz / risk / sample / sample.py: 100%

23 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-12-08 13:49 +0000

1# Copyright 2023 Stanford University Convex Optimization Group 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14"""Risk models based on the sample covariance matrix.""" 

15 

16from __future__ import annotations 

17 

18from dataclasses import dataclass 

19 

20import cvxpy as cp 

21import numpy as np 

22 

23from cvx.markowitz.cvxerror import CvxError 

24from cvx.markowitz.model import Model 

25from cvx.markowitz.names import DataNames as D 

26from cvx.markowitz.types import Expressions, Matrix, Parameter, Variables # noqa: F401 

27from cvx.markowitz.utils.fill import fill_matrix, fill_vector 

28 

29 

30@dataclass(frozen=True) 

31class SampleCovariance(Model): 

32 """Risk model based on the Cholesky decomposition of the sample cov matrix.""" 

33 

34 def __post_init__(self) -> None: 

35 """Initialize parameters for the sample-covariance risk model.""" 

36 self.data[D.CHOLESKY] = cp.Parameter( 

37 shape=(self.assets, self.assets), 

38 name=D.CHOLESKY, 

39 value=np.zeros((self.assets, self.assets)), 

40 ) 

41 

42 self.data[D.VOLA_UNCERTAINTY] = cp.Parameter( 

43 shape=self.assets, 

44 name=D.VOLA_UNCERTAINTY, 

45 value=np.zeros(self.assets), 

46 nonneg=True, 

47 ) 

48 

49 # x: array([ 5.19054e-01, 4.80946e-01, -1.59557e-12, -1.59557e-12]) 

50 def estimate(self, variables: Variables) -> cp.Expression: 

51 """Estimate risk via Cholesky-based norm of exposures and uncertainties.""" 

52 return cp.norm2( 

53 cp.hstack( 

54 [ 

55 self.data[D.CHOLESKY] @ variables[D.WEIGHTS], 

56 self.data[D.VOLA_UNCERTAINTY] @ variables[D._ABS], 

57 ] 

58 ) 

59 ) 

60 

61 def update(self, **kwargs: Matrix) -> None: 

62 """Assign Cholesky factor and volatility-uncertainty vector. 

63 

64 Expected keyword arguments: 

65 D.CHOLESKY: Cholesky factor of the covariance matrix (assets x assets). 

66 D.VOLA_UNCERTAINTY: Nonnegative vector of per-asset uncertainty. 

67 """ 

68 if not kwargs[D.CHOLESKY].shape[0] == kwargs[D.VOLA_UNCERTAINTY].shape[0]: 

69 raise CvxError("Mismatch in length for chol and vola_uncertainty") 

70 

71 self.data[D.CHOLESKY].value = fill_matrix(rows=self.assets, cols=self.assets, x=kwargs[D.CHOLESKY]) 

72 self.data[D.VOLA_UNCERTAINTY].value = fill_vector(num=self.assets, x=kwargs[D.VOLA_UNCERTAINTY]) 

73 

74 def constraints(self, variables: Variables) -> Expressions: 

75 """Return auxiliary constraints used for robust risk modeling.""" 

76 return { 

77 "dummy": variables[D._ABS] >= cp.abs(variables[D.WEIGHTS]), # Robust risk dummy variable 

78 }