# 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)