Note
Go to the end to download the full example code. or to run this example in your browser via JupyterLite or Binder
Black & Litterman Factor Model#
This tutorial shows how to use the FactorModel
estimator coupled
with the BlackLitterman
estimator in the
MeanRisk
optimization.
The Black & Litterman Factor Model is a Factor Model in which we incorporate views on factors using the Black & Litterman Model.
In the previous two tutorials, we introduced the Factor Model and the Black & Litterman
separately. In this tutorial we show how we can merge them together by building a
Maximum Sharpe Ratio portfolio using the FactorModel
estimator.
Data#
We load the S&P 500 dataset composed of the daily prices of 20 assets from the SPX Index composition and the Factors dataset composed of the daily prices of 5 ETF representing common factors:
from plotly.io import show
from sklearn.model_selection import train_test_split
from skfolio import Population, RiskMeasure
from skfolio.datasets import load_factors_dataset, load_sp500_dataset
from skfolio.optimization import MeanRisk, ObjectiveFunction
from skfolio.preprocessing import prices_to_returns
from skfolio.prior import BlackLitterman, FactorModel
prices = load_sp500_dataset()
factor_prices = load_factors_dataset()
prices = prices["2014":]
factor_prices = factor_prices["2014":]
X, y = prices_to_returns(prices, factor_prices)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, shuffle=False)
Analyst views#
Let’s assume we are able to accurately estimate views about future realization of the
factors. We estimate that the factor Size will have an expected return of 10% p.a.
(absolute view) and will outperform the factor Value by 3% p.a. (relative view). We
also estimate the factor Momentum will outperform the factor Quality by 2% p.a
(relative view). By converting these annualized estimates into daily estimates to be
homogenous with the input X
, we get:
factor_views = [
"SIZE == 0.00039",
"SIZE - VLUE == 0.00011 ",
"MTUM - QUAL == 0.00007",
]
Black & Litterman Factor Model#
We create a Maximum Sharpe Ratio model using the Black & Litterman Factor Model that we fit on the training set:
model_bl_factor = MeanRisk(
risk_measure=RiskMeasure.VARIANCE,
objective_function=ObjectiveFunction.MAXIMIZE_RATIO,
prior_estimator=FactorModel(
factor_prior_estimator=BlackLitterman(views=factor_views),
),
portfolio_params=dict(name="Black & Litterman Factor Model"),
)
model_bl_factor.fit(X_train, y_train)
model_bl_factor.weights_
array([5.55998971e-02, 2.42678756e-02, 3.63991372e-02, 1.97989097e-02,
4.38883372e-08, 1.69582846e-02, 1.34103109e-01, 3.52967028e-07,
2.78013243e-02, 9.56133325e-02, 6.72490988e-02, 9.04221682e-02,
1.24559355e-01, 8.98176230e-02, 5.85674115e-02, 2.83525179e-02,
6.69595123e-03, 1.02316969e-01, 2.14765919e-02, 4.61288061e-08])
For comparison, we also create a Maximum Sharpe Ratio model using a simple Factor Model:
model_factor = MeanRisk(
risk_measure=RiskMeasure.VARIANCE,
objective_function=ObjectiveFunction.MAXIMIZE_RATIO,
prior_estimator=FactorModel(),
portfolio_params=dict(name="Factor Model"),
)
model_factor.fit(X_train, y_train)
model_factor.weights_
array([1.03294289e-06, 1.27482685e-03, 4.19682805e-07, 3.34130827e-06,
7.36838289e-07, 1.28824408e-06, 5.13031432e-02, 6.35619183e-02,
6.14804836e-07, 1.79106051e-01, 5.03130911e-02, 7.13734379e-02,
4.13002526e-02, 2.27978407e-01, 5.13348034e-02, 1.44130375e-01,
2.99026117e-07, 6.19737850e-02, 5.63413085e-02, 8.67773200e-07])
Prediction#
We predict both models on the test set:
ptf_bl_factor_test = model_bl_factor.predict(X_test)
ptf_factor_test = model_factor.predict(X_test)
population = Population([ptf_bl_factor_test, ptf_factor_test])
population.plot_cumulative_returns()
Because our factor views were accurate, the Black & Litterman Factor Model outperformed the simple Factor Model on the test set.
Let’s plot the portfolios compositions:
fig = population.plot_composition()
show(fig)
Going Further#
The API design makes it possible to created nested models without limits. In the below example, we re-apply a Black & Litterman model incorporating assets views. But instead of using the empirical moments, we use the above Black & Litterman factor model:
assets_views = [
"AAPL == 0.00098",
"AAPL - GE == 0.00086",
"JPM - GE == 0.00059",
]
model = BlackLitterman(
views=assets_views,
prior_estimator=FactorModel(
factor_prior_estimator=BlackLitterman(views=factor_views),
),
)
model.fit(X, y)
print(model.prior_model_.covariance.shape)
(20, 20)
Total running time of the script: (0 minutes 3.402 seconds)