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

19 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"""Conditional Value-at-Risk (CVaR) risk model implementation.""" 

15 

16from __future__ import annotations 

17 

18from dataclasses import dataclass 

19 

20import cvxpy as cp 

21import numpy as np 

22 

23from cvx.markowitz.model import Model 

24from cvx.markowitz.names import DataNames as D 

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

26from cvx.markowitz.utils.fill import fill_matrix 

27 

28 

29@dataclass(frozen=True) 

30class CVar(Model): 

31 """Conditional value at risk model.""" 

32 

33 alpha: float = 0.95 

34 rows: int = 0 

35 

36 def __post_init__(self) -> None: 

37 """Initialize CVaR model parameters. 

38 

39 Creates the returns matrix parameter with shape `(rows, assets)` and 

40 zeros as default value. The `alpha` quantile controls tail size during 

41 estimation in `estimate`. 

42 """ 

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

44 self.data[D.RETURNS] = cp.Parameter( 

45 shape=(self.rows, self.assets), 

46 name=D.RETURNS, 

47 value=np.zeros((self.rows, self.assets)), 

48 ) 

49 

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

51 """Estimate the risk by computing the Cholesky decomposition of self.cov.""" 

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

53 # n = self.R.shape[0] 

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

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

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

57 k = int(self.rows * (1 - self.alpha)) 

58 return -cp.sum_smallest(self.data[D.RETURNS] @ variables[D.WEIGHTS], k=k) / k 

59 

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

61 """Update the returns matrix used by the CVaR model. 

62 

63 Expected keyword arguments: 

64 D.RETURNS: Matrix of historical/scenario returns with shape (rows, assets). 

65 """ 

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