Note
Go to the end to download the full example code. or to run this example in your browser via 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.56000809e-02, 2.42685508e-02, 3.64005580e-02, 1.97994501e-02,
8.93188830e-11, 1.69581067e-02, 1.34097308e-01, 1.19789402e-09,
2.78013891e-02, 9.56214479e-02, 6.72469426e-02, 9.04183614e-02,
1.24556425e-01, 8.98242729e-02, 5.85688593e-02, 2.83522508e-02,
6.69632764e-03, 1.02313728e-01, 2.14759387e-02, 9.32493866e-11])
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.22188511e-09, 1.31737525e-03, 4.74253021e-10, 7.70440099e-10,
9.46436338e-10, 1.24959219e-09, 5.13001762e-02, 6.35652050e-02,
7.36336705e-10, 1.79104637e-01, 5.03133290e-02, 7.13696569e-02,
4.12895735e-02, 2.27967880e-01, 5.13346758e-02, 1.44128512e-01,
2.88142338e-10, 6.19657594e-02, 5.63432134e-02, 1.09849875e-09])
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.416 seconds)