Coverage for cvxrisk/cvar/cvar.py: 100%

27 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-06-18 11:11 +0000

1"""Conditional Value at Risk (CVaR) risk model implementation.""" 

2 

3# Copyright 2023 Stanford University Convex Optimization Group 

4# 

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

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

7# You may obtain a copy of the License at 

8# 

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

10# 

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

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

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

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

15# limitations under the License. 

16from __future__ import annotations 

17 

18from dataclasses import dataclass 

19 

20import cvxpy as cvx 

21import numpy as np 

22 

23from ..bounds import Bounds 

24from ..model import Model 

25 

26 

27@dataclass 

28class CVar(Model): 

29 """Conditional value at risk model.""" 

30 

31 alpha: float = 0.95 

32 """alpha parameter to determine the size of the tail""" 

33 

34 n: int = 0 

35 """number of samples""" 

36 

37 m: int = 0 

38 """number of assets""" 

39 

40 def __post_init__(self): 

41 """Initialize the parameters after the class is instantiated. 

42 

43 Calculates the number of samples in the tail (k) based on alpha, 

44 creates the returns parameter matrix, and initializes the bounds. 

45 """ 

46 self.k = int(self.n * (1 - self.alpha)) 

47 self.parameter["R"] = cvx.Parameter(shape=(self.n, self.m), name="returns", value=np.zeros((self.n, self.m))) 

48 self.bounds = Bounds(m=self.m, name="assets") 

49 

50 def estimate(self, weights: cvx.Variable, **kwargs) -> cvx.Expression: 

51 """Estimate the Conditional Value at Risk (CVaR) for the given weights. 

52 

53 Computes the negative average of the k smallest returns in the portfolio, 

54 where k is determined by the alpha parameter. 

55 

56 Args: 

57 weights: CVXPY variable representing portfolio weights 

58 

59 **kwargs: Additional keyword arguments (not used) 

60 

61 Returns: 

62 CVXPY expression: The negative average of the k smallest returns 

63 

64 """ 

65 # R is a matrix of returns, n is the number of rows in R 

66 # k is the number of returns in the left tail 

67 # average value of the k elements in the left tail 

68 return -cvx.sum_smallest(self.parameter["R"] @ weights, k=self.k) / self.k 

69 

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

71 """Update the returns data and bounds parameters. 

72 

73 Args: 

74 **kwargs: Keyword arguments containing: 

75 

76 - returns: Matrix of returns 

77 

78 - Other parameters passed to bounds.update() 

79 

80 """ 

81 ret = kwargs["returns"] 

82 m = ret.shape[1] 

83 

84 self.parameter["R"].value[:, :m] = kwargs["returns"] 

85 self.bounds.update(**kwargs) 

86 

87 def constraints(self, weights: cvx.Variable, **kwargs) -> list[cvx.Constraint]: 

88 """Return constraints for the CVaR model. 

89 

90 Args: 

91 weights: CVXPY variable representing portfolio weights 

92 

93 **kwargs: Additional keyword arguments passed to bounds.constraints() 

94 

95 Returns: 

96 List of CVXPY constraints from the bounds object 

97 

98 """ 

99 return self.bounds.constraints(weights)