Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions pypfopt/efficient_frontier/efficient_frontier.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,14 +386,14 @@ def efficient_risk(self, target_volatility, market_neutral=False):
if not isinstance(target_volatility, (float, int)) or target_volatility < 0:
raise ValueError("target_volatility should be a positive float")

global_min_volatility = np.sqrt(1 / np.sum(np.linalg.pinv(self.cov_matrix)))

if target_volatility < global_min_volatility:
raise ValueError(
"The minimum volatility is {:.3f}. Please use a higher target_volatility".format(
global_min_volatility
if not market_neutral:
global_min_volatility = np.sqrt(1 / np.sum(np.linalg.pinv(self.cov_matrix)))
if target_volatility < global_min_volatility:
raise ValueError(
"The minimum volatility is {:.3f}. Please use a higher target_volatility".format(
global_min_volatility
)
)
)

update_existing_parameter = self.is_parameter_defined("target_variance")
if update_existing_parameter:
Expand Down
37 changes: 37 additions & 0 deletions tests/test_market_neutral_risk_regression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import numpy as np
import pytest

from pypfopt import EfficientFrontier, exceptions


@pytest.mark.parametrize("target", [0.01, 0.05, 0.1])
def test_market_neutral_risk_below_fully_invested_minimum(target):
# With diagonal 0.04 covariance and weights (a, -a), risk=sqrt(0.08)*a.
ef = EfficientFrontier([0.1, 0.0], np.eye(2) * 0.04, weight_bounds=(-1, 1))
weights = np.array(list(ef.efficient_risk(target, market_neutral=True).values()))
a = target / np.sqrt(0.08)
np.testing.assert_allclose(weights, [a, -a], atol=2e-5)


def test_market_neutral_risk_updates_parameter():
ef = EfficientFrontier([0.1, 0.0], np.eye(2) * 0.04, weight_bounds=(-1, 1))
for target in (0.05, 0.02):
weights = np.array(
list(ef.efficient_risk(target, market_neutral=True).values())
)
np.testing.assert_allclose(
weights, [target / np.sqrt(0.08), -target / np.sqrt(0.08)], atol=2e-5
)


def test_fully_invested_low_risk_is_still_rejected():
ef = EfficientFrontier([0.1, 0.0], np.eye(2) * 0.04)
with pytest.raises(ValueError, match="minimum volatility"):
ef.efficient_risk(0.05)


def test_market_neutral_infeasible_extra_constraint_is_not_accepted():
ef = EfficientFrontier([0.1, 0.0], np.eye(2) * 0.04, weight_bounds=(-1, 1))
ef.add_constraint(lambda w: w[0] >= 0.5)
with pytest.raises(exceptions.OptimizationError):
ef.efficient_risk(0.05, market_neutral=True)