Coverage for cvxrisk/bounds.py: 100%

27 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-06-18 11:11 +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"""Bounds.""" 

15 

16from __future__ import annotations 

17 

18from dataclasses import dataclass 

19 

20import cvxpy as cp 

21import numpy as np 

22 

23from .model import Model 

24 

25 

26@dataclass 

27class Bounds(Model): 

28 """Representation of bounds for a model, defining constraints and parameters. 

29 

30 This dataclass provides functionality to establish and manage bounds for a model. It 

31 includes methods to handle bound parameters, update them dynamically, and generate 

32 constraints that can be used in optimization models. 

33 

34 Attributes: 

35 m: Maximal number of bounds. 

36 name: Name for the bounds, e.g., assets or factors. 

37 

38 """ 

39 

40 m: int = 0 

41 """Maximal number of bounds""" 

42 

43 name: str = "" 

44 """Name for the bounds, e.g. assets or factors""" 

45 

46 def estimate(self, weights: cp.Variable, **kwargs) -> cp.Expression: 

47 """No estimation for bounds. 

48 

49 Args: 

50 weights: CVXPY variable representing portfolio weights 

51 **kwargs: Additional keyword arguments 

52 

53 Raises: 

54 NotImplementedError: This method is not implemented for Bounds 

55 

56 """ 

57 raise NotImplementedError("No estimation for bounds") 

58 

59 def _f(self, str_prefix: str) -> str: 

60 """Create a parameter name by appending the name attribute. 

61 

62 Args: 

63 str_prefix: Base string for the parameter name 

64 

65 Returns: 

66 Combined parameter name in the format "{str_prefix}_{self.name}" 

67 

68 """ 

69 return f"{str_prefix}_{self.name}" 

70 

71 def __post_init__(self): 

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

73 

74 Creates lower and upper bound parameters with appropriate shapes and default values. 

75 """ 

76 self.parameter[self._f("lower")] = cp.Parameter( 

77 shape=self.m, 

78 name="lower bound", 

79 value=np.zeros(self.m), 

80 ) 

81 self.parameter[self._f("upper")] = cp.Parameter( 

82 shape=self.m, 

83 name="upper bound", 

84 value=np.ones(self.m), 

85 ) 

86 

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

88 """Update the lower and upper bound parameters. 

89 

90 Args: 

91 **kwargs: Keyword arguments containing lower and upper bounds 

92 

93 with keys formatted as "{lower/upper}_{self.name}" 

94 

95 """ 

96 lower = kwargs[self._f("lower")] 

97 self.parameter[self._f("lower")].value = np.zeros(self.m) 

98 self.parameter[self._f("lower")].value[: len(lower)] = lower 

99 

100 upper = kwargs[self._f("upper")] 

101 self.parameter[self._f("upper")].value = np.zeros(self.m) 

102 self.parameter[self._f("upper")].value[: len(upper)] = upper 

103 

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

105 """Return constraints that enforce the bounds on weights. 

106 

107 Args: 

108 weights: CVXPY variable representing portfolio weights 

109 

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

111 

112 Returns: 

113 List of CVXPY constraints enforcing lower and upper bounds 

114 

115 """ 

116 return [ 

117 weights >= self.parameter[self._f("lower")], 

118 weights <= self.parameter[self._f("upper")], 

119 ]