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
2 changes: 1 addition & 1 deletion pypfopt/objective_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ def ex_ante_tracking_error(w, cov_matrix, benchmark_weights):
value of the objective function OR objective function expression
"""
relative_weights = w - benchmark_weights
tracking_error = cp.quad_form(relative_weights, cov_matrix)
tracking_error = cp.quad_form(relative_weights, cov_matrix, assume_PSD=True)
return _objective_value(w, tracking_error)


Expand Down
19 changes: 19 additions & 0 deletions tests/test_objective_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,25 @@ def test_ex_ante_tracking_error():
np.testing.assert_almost_equal(te, 0.028297778946639436)


def test_ex_ante_tracking_error_assumes_psd():
# ex_ante_tracking_error is the only quad_form-based objective in this
# module that didn't pass assume_PSD=True (unlike portfolio_variance,
# sharpe_ratio and quadratic_utility), even though cov_matrix is
# documented/used the same way as those. Passing assume_PSD=True skips
# cvxpy's own PSD-certification check, which is known to fail for
# legitimately PSD-but-ill-conditioned covariance matrices on some
# cvxpy/solver versions (see #631 for a user-reported case with a custom
# quad_form-based objective). This just checks the numeric result is
# unchanged for a well-conditioned matrix, i.e. the fix is behavior
# preserving in the normal case.
bm_w = np.ones(5) / 5
w = np.array([0.4, 0.4, 0, 0, 0])
S = pd.DataFrame(np.eye(5))

te = objective_functions.ex_ante_tracking_error(w, S, bm_w)
np.testing.assert_almost_equal(te, 0.2)


def test_ex_post_tracking_error():
df = get_data()
rets = returns_from_prices(df).dropna()
Expand Down