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
12 changes: 11 additions & 1 deletion pypfopt/black_litterman.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ def bl_cov(self):
posterior_cov = self.cov_matrix + M
return pd.DataFrame(posterior_cov, index=self.tickers, columns=self.tickers)

def bl_weights(self, risk_aversion=None):
def bl_weights(self, risk_aversion=None, return_raw=False):
r"""
Compute the weights implied by the posterior returns, given the
market price of risk. Technically this can be applied to any
Expand All @@ -486,6 +486,14 @@ def bl_weights(self, risk_aversion=None):
----------
risk_aversion : positive float, optional
risk aversion parameter, defaults to 1
return_raw : bool, optional
if True, return the unnormalized weights (which do not sum to 1)
instead of normalizing them to sum to 1. The raw weights carry
information about the total amount of borrowing/lending implied
by the posterior, which normalizing discards; ``self.weights``
is always set to the normalized weights regardless of this flag,
so subsequent calls (e.g. ``portfolio_performance``) are
unaffected. Defaults to False.

Returns
-------
Expand All @@ -507,6 +515,8 @@ def bl_weights(self, risk_aversion=None):
raise e
raw_weights = weight_solution
self.weights = raw_weights / raw_weights.sum()
if return_raw:
return self._make_output_weights(raw_weights)
return self._make_output_weights()

def optimize(self, risk_aversion=None):
Expand Down
30 changes: 30 additions & 0 deletions tests/test_black_litterman.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,36 @@ def test_bl_weights():
assert w2 == w


def test_bl_weights_return_raw():
df = get_data()
S = risk_models.sample_cov(df)

viewdict = {"AAPL": 0.20, "BBY": -0.30, "BAC": 0, "SBUX": -0.2, "T": 0.131321}
bl = BlackLittermanModel(S, absolute_views=viewdict)

prices = pd.read_csv(
resource("spy_prices.csv"), parse_dates=True, index_col=0
).squeeze("columns")
delta = market_implied_risk_aversion(prices)

raw = bl.bl_weights(delta, return_raw=True)
# self.weights is always the normalized version, regardless of return_raw,
# so downstream calls like portfolio_performance() are unaffected.
normalized = bl._make_output_weights()

# The raw weights are proportional to the normalized ones by exactly the
# sum that normalization divided out - not equal to them.
raw_sum = sum(raw.values())
assert (
abs(raw_sum - 1) > 1e-3
), "fixture should have real leverage to make this a meaningful check"
for ticker in raw:
assert raw[ticker] == pytest.approx(normalized[ticker] * raw_sum, rel=1e-8)

# Calling with return_raw=False (the default) is unchanged from before.
assert bl.bl_weights(delta) == normalized


def test_market_implied_prior():
df = get_data()
S = risk_models.sample_cov(df)
Expand Down