# Black-Litterman example code for python (hl.py)
# Copyright (c) Jay Walters, blacklitterman.org, 2012.
#
# Redistribution and use in source and binary forms,
# with or without modification, are permitted provided
# that the following conditions are met:
#
# Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# Neither the name of blacklitterman.org nor the names of its
# contributors may be used to endorse or promote products
# derived from this software without specific prior written
# permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.
#
# This program uses the examples from the paper "The Intuition
# Behind Black-Litterman Model Portfolios", by He and Litterman,
# 1999. You can find a copy of this paper at the following url.
# http://papers.ssrn.com/sol3/papers.cfm?abstract_id=334304
#
# For more details on the Black-Litterman model you can also view
# "The BlackLitterman Model: A Detailed Exploration", by this author
# at the following url.
# http://papers.ssrn.com/sol3/papers.cfm?abstract_id=1314585
#
import numpy as np
from scipy import linalg
# blacklitterman
# This function performs the Black-Litterman blending of the prior
# and the views into a new posterior estimate of the returns as
# described in the paper by He and Litterman.
# Inputs
# delta - Risk tolerance from the equilibrium portfolio
# weq - Weights of the assets in the equilibrium portfolio
# sigma - Prior covariance matrix
# tau - Coefficiet of uncertainty in the prior estimate of the mean (pi)
# P - Pick matrix for the view(s)
# Q - Vector of view returns
# Omega - Matrix of variance of the views (diagonal)
# Outputs
# Er - Posterior estimate of the mean returns
# w - Unconstrained weights computed given the Posterior estimates
# of the mean and covariance of returns.
# lambda - A measure of the impact of each view on the posterior estimates.
#
def blacklitterman(delta, weq, sigma, tau, P, Q, Omega):
# Reverse optimize and back out the equilibrium returns
# This is formula (12) page 6.
pi = weq.dot(sigma * delta)
print(pi)
# We use tau * sigma many places so just compute it once
ts = tau * sigma
# Compute posterior estimate of the mean
# This is a simplified version of formula (8) on page 4.
middle = linalg.inv(np.dot(np.dot(P,ts),P.T) + Omega)
print(middle)
print(Q-np.expand_dims(np.dot(P,pi.T),axis=1))
er = np.expand_dims(pi,axis=0).T + np.dot(np.dot(np.dot(ts,P.T),middle),(Q - np.expand_dims(np.dot(P,pi.T),axis=1)))
# Compute posterior estimate of the uncertainty in the mean
# This is a simplified and combined version of formulas (9) and (15)
posteriorSigma = sigma + ts - ts.dot(P.T).dot(middle).dot(P).dot(ts)
print(posteriorSigma)
# Compute posterior weights based on uncertainty in mean
w = er.T.dot(linalg.inv(delta * posteriorSigma)).T
# Compute lambda value
# We solve for lambda from formula (17) page 7, rather than formula (18)
# just because it is less to type, and we've already computed w*.
lmbda = np.dot(linalg.pinv(P).T,(w.T * (1 + tau) - weq).T)
return [er, w, lmbda]
# Function to display the results of a black-litterman shrinkage
# Inputs
# title - Displayed at top of output
# assets - List of assets
# res - List of results structures from the bl function
#
def display(title,assets,res):
er = res[0]
w = res[1]
lmbda = res[2]
print('\n' + title)
line = 'Country\t\t'
for p in range(len(P)):
line = line + 'P' + str(p) + '\t'
line = line + 'mu\tw*'
print(line)
i = 0;
for x in assets:
line = '{0}\t'.format(x)
for j in range(len(P.T[i])):
line = line + '{0:.1f}\t'.format(100*P.T[i][j])
line = line + '{0:.3f}\t{1:.3f}'.format(100*er[i][0],100*w[i][0])
print(line)
i = i + 1
line = 'q\t\t'
i = 0
for q in Q:
line = line + '{0:.2f}\t'.format(100*q[0])
i = i + 1
print(line)
line = 'omega/tau\t'
i = 0
for o in Omega:
line = line + '{0:.5f}\t'.format(o[i]/tau)
i = i + 1
print(line)
line = 'lambda\t\t'
i = 0
for l in lmbda:
line = line + '{0:.5f}\t'.format(l[0])
i = i + 1
print(line)
# Take the values from He & Litterman, 1999.
weq = np.array([0.016,0.022,0.052,0.055,0.116,0.124,0.615])
C = np.array([[ 1.000, 0.488, 0.478, 0.515, 0.439, 0.512, 0.491],
[0.488, 1.000, 0.664, 0.655, 0.310, 0.608, 0.779],
[0.478, 0.664, 1.000, 0.861, 0.355, 0.783, 0.668],
[0.515, 0.655, 0.861, 1.000, 0.354, 0.777, 0.653],
[0.439, 0.310, 0.355, 0.354, 1.000, 0.405, 0.306],
[0.512, 0.608, 0.783, 0.777, 0.405, 1.000, 0.652],
[0.491, 0.779, 0.668, 0.653, 0.306, 0.652, 1.000]])
Sigma = np.array([0.160, 0.203, 0.248, 0.271, 0.210, 0.200, 0.187])
refPi = np.array([0.039, 0.069, 0.084, 0.090, 0.043, 0.068, 0.076])
assets= {'Australia','Canada ','France ','Germany ','Japan ','UK ','USA '}
# Equilibrium covariance matrix
V = np.multiply(np.outer(Sigma,Sigma), C)
#print(V)
# Risk aversion of the market
delta = 2.5
# Coefficient of uncertainty in the prior estimate of the mean
# from footnote (8) on page 11
tau = 0.05
tauV = tau * V
# Define view 1
# Germany will outperform the other European markets by 5%
# Market cap weight the P matrix
# Results should match Table 4, Page 21
P1 = np.array([0, 0, -.295, 1.00, 0, -.705, 0 ])
Q1 = np.array([0.05])
P=np.array([P1])
Q=np.array([Q1]);
Omega = np.dot(np.dot(P,tauV),P.T) * np.eye(Q.shape[0])
res = blacklitterman(delta, weq, V, tau, P, Q, Omega)
display('View 1',assets,res)
# Define view 2
# Canadian Equities will outperform US equities by 3%
# Market cap weight the P matrix
# Results should match Table 5, Page 22
P2 = np.array([0, 1.0, 0, 0, 0, 0, -1.0 ])
Q2 = np.array([0.03])
P=np.array([P1,P2])
Q=np.array([Q1,Q2]);
Omega = np.dot(np.dot(P,tauV),P.T) * np.eye(Q.shape[0])
res = blacklitterman(delta, weq, V, tau, P, Q, Omega)
display('View 1 + 2', assets, res)