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
« 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."""
16from __future__ import annotations
18from dataclasses import dataclass
20import cvxpy as cp
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
31@dataclass(frozen=True)
32class MaxSharpe(Builder):
33 """Maximize expected return under long-only, budget, and risk constraints."""
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))
40 def __post_init__(self) -> None:
41 """Initialize models, parameters, and constraints for the builder."""
42 super().__post_init__()
44 self.model[M.RETURN] = ExpectedReturns(assets=self.assets)
46 self.parameter[P.SIGMA_MAX] = cp.Parameter(nonneg=True, name="maximal volatility")
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]