Coverage for cvxrisk/bounds.py: 100%
27 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-18 11:11 +0000
« 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."""
16from __future__ import annotations
18from dataclasses import dataclass
20import cvxpy as cp
21import numpy as np
23from .model import Model
26@dataclass
27class Bounds(Model):
28 """Representation of bounds for a model, defining constraints and parameters.
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.
34 Attributes:
35 m: Maximal number of bounds.
36 name: Name for the bounds, e.g., assets or factors.
38 """
40 m: int = 0
41 """Maximal number of bounds"""
43 name: str = ""
44 """Name for the bounds, e.g. assets or factors"""
46 def estimate(self, weights: cp.Variable, **kwargs) -> cp.Expression:
47 """No estimation for bounds.
49 Args:
50 weights: CVXPY variable representing portfolio weights
51 **kwargs: Additional keyword arguments
53 Raises:
54 NotImplementedError: This method is not implemented for Bounds
56 """
57 raise NotImplementedError("No estimation for bounds")
59 def _f(self, str_prefix: str) -> str:
60 """Create a parameter name by appending the name attribute.
62 Args:
63 str_prefix: Base string for the parameter name
65 Returns:
66 Combined parameter name in the format "{str_prefix}_{self.name}"
68 """
69 return f"{str_prefix}_{self.name}"
71 def __post_init__(self):
72 """Initialize the parameters after the class is instantiated.
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 )
87 def update(self, **kwargs) -> None:
88 """Update the lower and upper bound parameters.
90 Args:
91 **kwargs: Keyword arguments containing lower and upper bounds
93 with keys formatted as "{lower/upper}_{self.name}"
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
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
104 def constraints(self, weights: cp.Variable, **kwargs) -> list[cp.Constraint]:
105 """Return constraints that enforce the bounds on weights.
107 Args:
108 weights: CVXPY variable representing portfolio weights
110 **kwargs: Additional keyword arguments (not used)
112 Returns:
113 List of CVXPY constraints enforcing lower and upper bounds
115 """
116 return [
117 weights >= self.parameter[self._f("lower")],
118 weights <= self.parameter[self._f("upper")],
119 ]