Skip to content

Fix excess-return inversion and singular fallback in BL weights - #769

Open
kadyrbekovhamit-cyber wants to merge 1 commit into
PyPortfolio:mainfrom
kadyrbekovhamit-cyber:fix/bl-excess-returns-and-singular-system
Open

kadyrbekovhamit-cyber wants to merge 1 commit into
PyPortfolio:mainfrom
kadyrbekovhamit-cyber:fix/bl-excess-returns-and-singular-system

Conversation

@kadyrbekovhamit-cyber

Copy link
Copy Markdown

With a market prior and views equal to that prior, bl_weights() should recover the market weights. For cov=diag(.04,.09,.16), caps [500,300,200], delta=2 and rf=.04, it returns [.541353,.282707,.175940] instead of [.5,.3,.2]: the inversion uses total rather than excess returns.

This retains the constructor's existing risk_free_rate kwarg and subtracts it only when computing implied weights. Explicit total-return priors can pass the same rate; zero remains the default for excess-return priors. Posterior returns, posterior-covariance convention and normalization are unchanged.

The same weight solver's singular fallback incorrectly uses the cached view-space matrix self._A. The patch uses its local asset-space matrix delta*cov instead. With duplicate A/C assets and one view it previously raises incompatible dimensions; with three views it silently returns equal thirds instead of [.3,.4,.3].

Validation: the original six regressions gave 4 failures / 2 passes. The complete Black–Litterman module plus seven new cases passes: 28 passed. Tests include zero/nonzero rf, explicit/market priors, both singular shapes and unchanged handling of duplicate exact views. Related #541 introduced the fallback; #762 concerns a different posterior-covariance convention; #763/#764 concern raw-weight normalization, which this PR does not alter.

Tested against main a6638d2, Python 3.12, numpy 2.2.6, pandas 3.0.6; sequential single-thread numerical execution. AI-assisted investigation and patch; reproducible tests included. No production-loss claim.

Signed-off-by: kadyrbekovhamit-cyber <288885044+kadyrbekovhamit-cyber@users.noreply.github.com>
@arhancanli

Copy link
Copy Markdown

Re-derived the headline number outside the repo: with cov=diag(.04,.09,.16), caps [500,300,200], delta=2, rf=.04, the prior is rf + deltaSigmaw_m, and solving (delta*Sigma) w = prior then normalising gives [0.5414, 0.2827, 0.1759] (matches the PR), while subtracting rf first gives [0.5, 0.3, 0.2]. So the bug and the fix check out on the arithmetic (I did not run the repo's test suite).

Two things I noticed in the diff:

  1. The new paragraph in docs/BlackLitterman.rst is appended after the numbered reference list [4], so it will render under References instead of next to the risk-free-rate discussion. It probably wants to sit in the section on priors.
  2. test_neutral_views_recover_market_weights covers the constructor's risk_free_rate, but bl_weights(risk_aversion=...) can override delta. A case calling bl_weights with a different risk_aversion and rf != 0 would show whether the weights still sum to 1 and what the docs promise there, since prior and weights then use different deltas.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants