Coverage for src / cvx / markowitz / portfolios / max_sharpe.py: 100%

22 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"""Portfolio builder maximizing expected return subject to risk and basic constraints.""" 

15 

16from __future__ import annotations 

17 

18from dataclasses import dataclass 

19 

20import cvxpy as cp 

21 

22from cvx.markowitz.builder import Builder 

23from cvx.markowitz.model import Model # noqa: F401 

24from cvx.markowitz.models.expected_returns import ExpectedReturns 

25from cvx.markowitz.names import ConstraintName as C 

26from cvx.markowitz.names import ModelName as M 

27from cvx.markowitz.names import ParameterName as P 

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

29 

30 

31@dataclass(frozen=True) 

32class MaxSharpe(Builder): 

33 """Maximize expected return under long-only, budget, and risk constraints.""" 

34 

35 @property 

36 def objective(self) -> cp.Objective: 

37 """Return the CVXPY objective for maximizing expected return.""" 

38 return cp.Maximize(self.model[M.RETURN].estimate(self.variables)) 

39 

40 def __post_init__(self) -> None: 

41 """Initialize models, parameters, and constraints for the builder.""" 

42 super().__post_init__() 

43 

44 self.model[M.RETURN] = ExpectedReturns(assets=self.assets) 

45 

46 self.parameter[P.SIGMA_MAX] = cp.Parameter(nonneg=True, name="maximal volatility") 

47 

48 self.constraints[C.LONG_ONLY] = self.weights >= 0 

49 self.constraints[C.BUDGET] = cp.sum(self.weights) == 1.0 

50 self.constraints[C.RISK] = self.risk.estimate(self.variables) <= self.parameter[P.SIGMA_MAX]