Skip to content

Commit

Permalink
fmt
Browse files Browse the repository at this point in the history
  • Loading branch information
tschm committed Jan 1, 2025
1 parent 2aec80c commit 04ff56a
Show file tree
Hide file tree
Showing 28 changed files with 96 additions and 184 deletions.
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ repos:
hooks:
- id: ruff
args: [ --fix, --exit-non-zero-on-fix ]
# Run the formatter
- id: ruff-format

- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.43.0
Expand Down
4 changes: 1 addition & 3 deletions book/marimo/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,7 @@ def __(portfolio):

@app.cell
def __(pd, portfolio, portfolio_resampled):
frame = pd.DataFrame(
{"original": portfolio.nav, "monthly": portfolio_resampled.nav}
)
frame = pd.DataFrame({"original": portfolio.nav, "monthly": portfolio_resampled.nav})
frame
return (frame,)

Expand Down
1 change: 1 addition & 0 deletions cvx/linalg/cholesky.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Cholesky decomposition with numpy"""

from __future__ import annotations

import numpy as np
Expand Down
9 changes: 3 additions & 6 deletions cvx/linalg/pca.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""PCA analysis with numpy"""

from __future__ import annotations

from dataclasses import dataclass
Expand All @@ -28,9 +29,7 @@ class PCA:

def __post_init__(self) -> None:
if self.n_components > self.returns.shape[1]:
raise ValueError(
"The number of components cannot exceed the number of assets"
)
raise ValueError("The number of components cannot exceed the number of assets")

# compute the principal components without sklearn
# 1. compute the correlation
Expand All @@ -51,9 +50,7 @@ def __post_init__(self) -> None:

@property
def explained_variance(self) -> Matrix:
return np.array(
self.eigenvalues[: self.n_components] / np.sum(self.eigenvalues)
)
return np.array(self.eigenvalues[: self.n_components] / np.sum(self.eigenvalues))

@property
def cov(self) -> Matrix:
Expand Down
1 change: 1 addition & 0 deletions cvx/linalg/valid.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Extract valid submatrix of a matrix"""

from __future__ import annotations

import numpy as np
Expand Down
16 changes: 4 additions & 12 deletions cvx/markowitz/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,9 @@ def __post_init__(self) -> None:
self.model[M.RISK] = FactorModel(assets=self.assets, factors=self.factors)

# add variable for factor weights
self.variables[D.FACTOR_WEIGHTS] = cp.Variable(
self.factors, name=D.FACTOR_WEIGHTS
)
self.variables[D.FACTOR_WEIGHTS] = cp.Variable(self.factors, name=D.FACTOR_WEIGHTS)
# add bounds for factor weights
self.model[M.BOUND_FACTORS] = Bounds(
assets=self.factors, name="factors", acting_on=D.FACTOR_WEIGHTS
)
self.model[M.BOUND_FACTORS] = Bounds(assets=self.factors, name="factors", acting_on=D.FACTOR_WEIGHTS)
# add variable for absolute factor weights
self.variables[D._ABS] = cp.Variable(self.factors, name=D._ABS, nonneg=True)

Expand All @@ -141,9 +137,7 @@ def __post_init__(self) -> None:
self.variables[D.WEIGHTS] = cp.Variable(self.assets, name=D.WEIGHTS)

# add bounds on assets
self.model[M.BOUND_ASSETS] = Bounds(
assets=self.assets, name="assets", acting_on=D.WEIGHTS
)
self.model[M.BOUND_ASSETS] = Bounds(assets=self.assets, name="assets", acting_on=D.WEIGHTS)

@property
@abstractmethod
Expand All @@ -157,9 +151,7 @@ def build(self) -> _Problem:
Build the cvxpy problem
"""
for name_model, model in self.model.items():
for name_constraint, constraint in model.constraints(
self.variables
).items():
for name_constraint, constraint in model.constraints(self.variables).items():
self.constraints[f"{name_model}_{name_constraint}"] = constraint

problem = cp.Problem(self.objective, list(self.constraints.values()))
Expand Down
4 changes: 2 additions & 2 deletions cvx/markowitz/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Abstract cp model
"""
"""Abstract cp model"""

from __future__ import annotations

from abc import ABC, abstractmethod
Expand Down
15 changes: 5 additions & 10 deletions cvx/markowitz/models/bounds.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Bounds"""

from __future__ import annotations

from dataclasses import dataclass
Expand Down Expand Up @@ -49,17 +50,11 @@ def __post_init__(self) -> None:
)

def update(self, **kwargs: Matrix) -> None:
self.data[self._f("lower")].value = fill_vector(
num=self.assets, x=kwargs[self._f("lower")]
)
self.data[self._f("upper")].value = fill_vector(
num=self.assets, x=kwargs[self._f("upper")]
)
self.data[self._f("lower")].value = fill_vector(num=self.assets, x=kwargs[self._f("lower")])
self.data[self._f("upper")].value = fill_vector(num=self.assets, x=kwargs[self._f("upper")])

def constraints(self, variables: Variables) -> Expressions:
return {
f"lower bound {self.name}": variables[self.acting_on]
>= self.data[self._f("lower")],
f"upper bound {self.name}": variables[self.acting_on]
<= self.data[self._f("upper")],
f"lower bound {self.name}": variables[self.acting_on] >= self.data[self._f("lower")],
f"upper bound {self.name}": variables[self.acting_on] <= self.data[self._f("upper")],
}
9 changes: 3 additions & 6 deletions cvx/markowitz/models/expected_returns.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Model for expected returns"""

from __future__ import annotations

from dataclasses import dataclass
Expand Down Expand Up @@ -46,9 +47,7 @@ def __post_init__(self) -> None:
)

def estimate(self, variables: Variables) -> cp.Expression:
return self.data[D.MU] @ variables[D.WEIGHTS] - self.parameter[
"mu_uncertainty"
] @ cp.abs(variables[D.WEIGHTS])
return self.data[D.MU] @ variables[D.WEIGHTS] - self.parameter["mu_uncertainty"] @ cp.abs(variables[D.WEIGHTS])

def update(self, **kwargs: Matrix) -> None:
exp_returns = kwargs[D.MU]
Expand All @@ -59,6 +58,4 @@ def update(self, **kwargs: Matrix) -> None:
if not uncertainty.shape[0] == exp_returns.shape[0]:
raise CvxError("Mismatch in length for mu and mu_uncertainty")

self.parameter["mu_uncertainty"].value = fill_vector(
num=self.assets, x=uncertainty
)
self.parameter["mu_uncertainty"].value = fill_vector(num=self.assets, x=uncertainty)
13 changes: 4 additions & 9 deletions cvx/markowitz/models/holding_costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Model for holding costs"""

from __future__ import annotations

from dataclasses import dataclass
Expand All @@ -30,16 +31,10 @@ class HoldingCosts(Model):
"""Model for holding costs"""

def __post_init__(self) -> None:
self.data[D.HOLDING_COSTS] = cp.Parameter(
shape=self.assets, name=D.HOLDING_COSTS, value=np.zeros(self.assets)
)
self.data[D.HOLDING_COSTS] = cp.Parameter(shape=self.assets, name=D.HOLDING_COSTS, value=np.zeros(self.assets))

def estimate(self, variables: Variables) -> cp.Expression:
return cp.sum(
cp.neg(cp.multiply(variables[D.WEIGHTS], self.data[D.HOLDING_COSTS]))
)
return cp.sum(cp.neg(cp.multiply(variables[D.WEIGHTS], self.data[D.HOLDING_COSTS])))

def update(self, **kwargs: Matrix) -> None:
self.data[D.HOLDING_COSTS].value = fill_vector(
num=self.assets, x=kwargs[D.HOLDING_COSTS]
)
self.data[D.HOLDING_COSTS].value = fill_vector(num=self.assets, x=kwargs[D.HOLDING_COSTS])
5 changes: 2 additions & 3 deletions cvx/markowitz/models/trading_costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Model for trading costs"""

from __future__ import annotations

from dataclasses import dataclass
Expand All @@ -33,9 +34,7 @@ def __post_init__(self) -> None:
self.parameter["power"] = cp.Parameter(shape=1, name="power", value=np.ones(1))

# initial weights before rebalancing
self.data["weights"] = cp.Parameter(
shape=self.assets, name="weights", value=np.zeros(self.assets)
)
self.data["weights"] = cp.Parameter(shape=self.assets, name="weights", value=np.zeros(self.assets))

def estimate(self, variables: Variables) -> cp.Expression:
return cp.sum(
Expand Down
9 changes: 2 additions & 7 deletions cvx/markowitz/portfolios/max_sharpe.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@

@dataclass(frozen=True)
class MaxSharpe(Builder):

"""
Minimize the standard deviation of the portfolio returns subject to a set of constraints
min StdDev(r_p)
Expand All @@ -42,12 +41,8 @@ def __post_init__(self) -> None:

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

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

self.constraints[C.LONG_ONLY] = self.weights >= 0
self.constraints[C.BUDGET] = cp.sum(self.weights) == 1.0
self.constraints[C.RISK] = (
self.risk.estimate(self.variables) <= self.parameter[P.SIGMA_MAX]
)
self.constraints[C.RISK] = self.risk.estimate(self.variables) <= self.parameter[P.SIGMA_MAX]
1 change: 0 additions & 1 deletion cvx/markowitz/portfolios/min_var.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

@dataclass(frozen=True)
class MinVar(Builder):

"""
Minimize the standard deviation of the portfolio returns subject to a set of constraints
min StdDev(r_p)
Expand Down
9 changes: 2 additions & 7 deletions cvx/markowitz/portfolios/soft_risk.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@

@dataclass(frozen=True)
class SoftRisk(Builder):

"""
maximize w^T mu - omega * (sigma - sigma_target)_+
subject to w >= 0, w^T 1 = 1, sigma <= sigma_max
Expand All @@ -40,9 +39,7 @@ class SoftRisk(Builder):
@property
def objective(self) -> cp.Maximize:
expected_return = self.model[M.RETURN].estimate(self.variables)
soft_risk = cp.pos(
self.parameter[P.OMEGA] * self._sigma - self._sigma_target_times_omega
)
soft_risk = cp.pos(self.parameter[P.OMEGA] * self._sigma - self._sigma_target_times_omega)
return cp.Maximize(expected_return - soft_risk)

def __post_init__(self) -> None:
Expand All @@ -52,9 +49,7 @@ def __post_init__(self) -> None:

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

self.parameter[P.SIGMA_TARGET] = cp.Parameter(
nonneg=True, name="target volatility"
)
self.parameter[P.SIGMA_TARGET] = cp.Parameter(nonneg=True, name="target volatility")

self.parameter[P.OMEGA] = cp.Parameter(nonneg=True, name="risk priority")
self._sigma_target_times_omega._callback = lambda: (
Expand Down
4 changes: 1 addition & 3 deletions cvx/markowitz/risk/cvar/cvar.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,4 @@ def estimate(self, variables: Variables) -> cp.Expression:
return -cp.sum_smallest(self.data[D.RETURNS] @ variables[D.WEIGHTS], k=k) / k

def update(self, **kwargs: Matrix) -> None:
self.data[D.RETURNS].value = fill_matrix(
rows=self.rows, cols=self.assets, x=kwargs[D.RETURNS]
)
self.data[D.RETURNS].value = fill_matrix(rows=self.rows, cols=self.assets, x=kwargs[D.RETURNS])
35 changes: 10 additions & 25 deletions cvx/markowitz/risk/factor/factor.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Factor risk model
"""
"""Factor risk model"""

from __future__ import annotations

from dataclasses import dataclass
Expand Down Expand Up @@ -104,13 +104,8 @@ def update(self, **kwargs: Matrix) -> None:
if key not in kwargs.keys():
raise CvxError(f"Missing keyword {key}")

if (
not kwargs[D.IDIOSYNCRATIC_VOLA].shape[0]
== kwargs[D.IDIOSYNCRATIC_VOLA_UNCERTAINTY].shape[0]
):
raise CvxError(
"Mismatch in length for idiosyncratic_vola and idiosyncratic_vola_uncertainty"
)
if not kwargs[D.IDIOSYNCRATIC_VOLA].shape[0] == kwargs[D.IDIOSYNCRATIC_VOLA_UNCERTAINTY].shape[0]:
raise CvxError("Mismatch in length for idiosyncratic_vola and idiosyncratic_vola_uncertainty")

exposure = kwargs[D.EXPOSURE]
k, assets = exposure.shape
Expand All @@ -119,22 +114,14 @@ def update(self, **kwargs: Matrix) -> None:
raise CvxError("Mismatch in length for idiosyncratic_vola and exposure")

if not kwargs[D.SYSTEMATIC_VOLA_UNCERTAINTY].shape[0] == k:
raise CvxError(
"Mismatch in length of systematic_vola_uncertainty and exposure"
)
raise CvxError("Mismatch in length of systematic_vola_uncertainty and exposure")

if not kwargs[D.CHOLESKY].shape[0] == k:
raise CvxError("Mismatch in size of chol and exposure")

self.data[D.EXPOSURE].value = fill_matrix(
rows=self.factors, cols=self.assets, x=kwargs["exposure"]
)
self.data[D.IDIOSYNCRATIC_VOLA].value = fill_vector(
num=self.assets, x=kwargs[D.IDIOSYNCRATIC_VOLA]
)
self.data[D.CHOLESKY].value = fill_matrix(
rows=self.factors, cols=self.factors, x=kwargs[D.CHOLESKY]
)
self.data[D.EXPOSURE].value = fill_matrix(rows=self.factors, cols=self.assets, x=kwargs["exposure"])
self.data[D.IDIOSYNCRATIC_VOLA].value = fill_vector(num=self.assets, x=kwargs[D.IDIOSYNCRATIC_VOLA])
self.data[D.CHOLESKY].value = fill_matrix(rows=self.factors, cols=self.factors, x=kwargs[D.CHOLESKY])

# Robust risk
self.data[D.SYSTEMATIC_VOLA_UNCERTAINTY].value = fill_vector(
Expand All @@ -146,8 +133,6 @@ def update(self, **kwargs: Matrix) -> None:

def constraints(self, variables: Variables) -> Expressions:
return {
"factors": variables[D.FACTOR_WEIGHTS]
== self.data[D.EXPOSURE] @ variables[D.WEIGHTS],
"_abs": variables[D._ABS]
>= cp.abs(variables[D.FACTOR_WEIGHTS]), # Robust risk dummy variable
"factors": variables[D.FACTOR_WEIGHTS] == self.data[D.EXPOSURE] @ variables[D.WEIGHTS],
"_abs": variables[D._ABS] >= cp.abs(variables[D.FACTOR_WEIGHTS]), # Robust risk dummy variable
}
15 changes: 5 additions & 10 deletions cvx/markowitz/risk/sample/sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Risk models based on the sample covariance matrix
"""
"""Risk models based on the sample covariance matrix"""

from __future__ import annotations

from dataclasses import dataclass
Expand Down Expand Up @@ -63,15 +63,10 @@ def update(self, **kwargs: Matrix) -> None:
if not kwargs[D.CHOLESKY].shape[0] == kwargs[D.VOLA_UNCERTAINTY].shape[0]:
raise CvxError("Mismatch in length for chol and vola_uncertainty")

self.data[D.CHOLESKY].value = fill_matrix(
rows=self.assets, cols=self.assets, x=kwargs[D.CHOLESKY]
)
self.data[D.VOLA_UNCERTAINTY].value = fill_vector(
num=self.assets, x=kwargs[D.VOLA_UNCERTAINTY]
)
self.data[D.CHOLESKY].value = fill_matrix(rows=self.assets, cols=self.assets, x=kwargs[D.CHOLESKY])
self.data[D.VOLA_UNCERTAINTY].value = fill_vector(num=self.assets, x=kwargs[D.VOLA_UNCERTAINTY])

def constraints(self, variables: Variables) -> Expressions:
return {
"dummy": variables[D._ABS]
>= cp.abs(variables[D.WEIGHTS]), # Robust risk dummy variable
"dummy": variables[D._ABS] >= cp.abs(variables[D.WEIGHTS]), # Robust risk dummy variable
}
Loading

0 comments on commit 04ff56a

Please sign in to comment.