quantlab.portfolio.predefined.black_litterman.BlackLittermanOptimizer#

class quantlab.portfolio.predefined.black_litterman.BlackLittermanOptimizer(config)[source]#

Bases: MeanVarianceOptimizer

Mean-variance weights on a Black-Litterman posterior.

See the module docstring for the model. On each rebalance bar the candidates are every selectable symbol (tradable, not locked, covered by the forecast, with every bounded exposure): a symbol without a view gets its equilibrium return plus what the views on correlated symbols move it by. The symbol views are the expected_return model’s mu on the predicted candidates over the span, each a total return or, with views_relative_to_prior, an alpha added to the symbol’s equilibrium return; omega sets their uncertainty. The user’s views are factor_views (FactorReturnView, RelativeFactorReturnView) and market_views (MarketReturnView). Without expected_return the rule reads no prediction and its span is span. The market’s weights are market’s output market_weight at the bar on the symbols the forecast covers (NaN counts as 0), renormalised to sum to one. candidate_top_k ranks the posterior expected return. Every other field works as in MeanVarianceOptimizer; risk_aversion is both the optimiser’s and the equilibrium’s delta.

Parameters:
configBlackLittermanConfig

MeanVarianceConfig’s fields, the market factor, its market_weight output, tau, omega (with confidence), views_relative_to_prior, span and the user’s views.

Raises:
ValueError

If there are no views (no expected_return and no user views), expected_return and expected_return_label are not set together, span is missing without expected_return or not a positive integer, a factor_views entry is not a FactorReturnView or RelativeFactorReturnView or a market_views one not a MarketReturnView, a factor view names a factor the risk model lacks, bind finds a span other than the label’s, covariance is not a FactorRiskStoreEstimator, market is missing or lacks market_weight, tau is not positive, omega is unknown, confidence is missing or outside (0, 1] with "idzorek" or set with another omega, or the rule measures risk against a reference book (reference_weights); and as MeanVarianceOptimizer.

Warns:
UserWarning

If a z-score expected return model (GrinoldExpectedReturn, ZScoreExpectedReturn, UnscaledZScoreExpectedReturn) gives total-return views: its mu is a cross-sectionally centred alpha, so as a total return it pulls every viewed name towards zero; set views_relative_to_prior=True.

Examples

forecast is a FactorRiskForecast over AAA..``FFF``, risk a FactorRiskStoreEstimator returning it, cap_weight a factor whose output benchmark_weight is 0.3, 0.25, 0.2, 0.15, 0.07 and 0.03, and context a bar whose predictions are all NaN. With no views the book is the market:

>>> from quantlab.portfolio.config import BlackLittermanConfig, RawExpectedReturnConfig
>>> from quantlab.portfolio.predefined.expected_return import RawExpectedReturn
>>> rule = BlackLittermanOptimizer(BlackLittermanConfig(
...     expected_return_label="ret_5", covariance=risk,
...     expected_return=RawExpectedReturn(RawExpectedReturnConfig()),
...     risk_aversion=3.0, market=cap_weight, tau=0.05,
... ))
>>> rule.bind(specs)
>>> rule.construct(context).values.round(4)
array([0.3 , 0.25, 0.2 , 0.15, 0.07, 0.03])
axis_factors()[source]#

The market’s weights: its symbols without a prediction are in the equilibrium portfolio.

Examples

>>> rule.axis_factors() == (rule.config.market_weight,)
True
bind(labels)[source]#

Read the span: the expected-return label’s, or span without symbol views.

Parameters:
labelsSequence[LabelSpec]

The specs of the predicted labels; not read without expected_return.

Raises:
ValueError

If span is set and differs from the label’s span, and as MeanVarianceOptimizer.bind.

Examples

>>> rule.bind([LabelSpec(name="ret_5", scale="raw", delay=1, span=5)])
>>> rule.span
5
config_cls#

alias of BlackLittermanConfig

declared_inputs()[source]#

The optimiser’s declaration, and the market among the factors.

Examples

>>> rule.declared_inputs().factors[-1] is rule.config.market
True
market_exposures(context, forecast, weights)[source]#

Return the market’s factor exposures x: B' w on the forecast’s symbols.

A market wider than the roster (the whole market, say) overrides it together with market_weights, the market’s weights on the roster, so that x and the weights describe one portfolio.

Parameters:
contextPortfolioContext

The bar’s context.

forecastFactorRiskForecast

The bar’s forecast.

weightsnp.ndarray

The market’s weights on forecast.symbols (market_weights).

Returns:
np.ndarray

One exposure per factor of the forecast.

Examples

>>> rule.market_exposures(context, forecast, weights).shape
(2,)
market_weights(context, forecast)[source]#

Return the market’s weights on the forecast’s symbols, summing to one.

Parameters:
contextPortfolioContext

The bar’s context; context.factors holds market_weight.

forecastFactorRiskForecast

The bar’s forecast.

Returns:
np.ndarray

One weight per forecast.symbols.

Raises:
PortfolioConstructionError

If the market has no weight on the covered symbols.

Examples

>>> rule.market_weights(context, forecast).sum()
1.0
moments(timestamp, factor_names, factor_form, market, rows, variance, views)[source]#

Return the posterior on one bar’s arrays: the mean, the factor covariance and the diagonal parts of M.

The model’s arithmetic (see the module docstring) on the bar’s forecast in factor form, shared by posterior and the approximate backtest’s implementation of the rule, which hands it each bar’s arrays; the user’s views known at timestamp are read here.

Parameters:
timestamppd.Timestamp or np.datetime64

The bar, at which the user’s views are read.

factor_namesSequence[str]

The forecast’s factors.

factor_formtuple of np.ndarray

(B, F, D) over the forecast’s n symbols on the span: [n, k] exposures, [k, k] factor covariance, [n] specific variances.

markettuple of np.ndarray

(w, x): the market’s weights on the n symbols, summing to one, and its factor exposures.

rowsnp.ndarray

The viewed symbols’ positions among the n.

variancenp.ndarray or None

Sigma_jj of each viewed symbol; not read (None) with omega="specific".

viewsnp.ndarray

The expected return model’s mu of each viewed symbol.

Returns:
meannp.ndarray

[n]: the posterior expected return.

factor_posteriornp.ndarray

[k, k]: the posterior covariance of f, F_post.

keepnp.ndarray

[n]: how much of each symbol’s specific prior variance a view keeps, so M = U F_post U' + diag(specific), U = diag(keep) B.

specificnp.ndarray

[n]: the diagonal of M.

Raises:
PortfolioConstructionError

If the user’s views cannot be blended (two fully confident views on one return, or a confident view on a factor without variance).

Examples

With no views the mean is the equilibrium delta (1 + tau) Sigma w:

>>> mean, _, keep, _ = rule.moments(
...     day, forecast.factor_names, forecast.factor_form(), (w, forecast.exposures.T @ w),
...     np.array([], dtype=int), np.array([]), np.array([]),
... )
>>> np.allclose(mean, 3.0 * 1.05 * forecast.covariance @ w), bool((keep == 1).all())
(True, True)
posterior(context, symbols, expected_return, estimate, candidates)[source]#

Return the posterior expected return, the posterior covariance and every selectable symbol.

See the module docstring for the model; the views are the finite entries of expected_return, total returns or alphas (views_relative_to_prior), with omega’s uncertainty.

Parameters:
contextPortfolioContext

The bar’s context; context.factors holds market_weight.

symbolsnp.ndarray

The selectable symbols.

expected_returnnp.ndarray

The expected_return model’s mu per selectable symbol on the span, NaN without a prediction: the symbol views.

estimateFactorRiskForecast

The bar’s forecast on the span.

candidatesnp.ndarray

Ignored: every selectable symbol is a candidate.

Returns:
expected_returnnp.ndarray

The posterior mean per selectable symbol.

estimateFactorRiskForecast

The posterior covariance Sigma + M over estimate’s symbols, its factors the forecast’s and their posterior copies.

candidatesnp.ndarray

All True.

Examples

With no views, the posterior mean is the equilibrium and the covariance (1 + tau) Sigma:

>>> mu, posterior, chosen = rule.posterior(
...     context, symbols, np.full(len(symbols), np.nan), estimate, candidates,
... )
>>> np.allclose(posterior.covariance, (1 + rule.config.tau) * estimate.covariance)
True
>>> bool(chosen.all())
True
ranking_label(names)[source]#

Return expected_return_label, or without symbol views the first label.

Examples

>>> rule.ranking_label(["ret_1", "ret_5"])
'ret_5'