In [2]:
import numpy as np
from scipy.optimize import minimize

In [3]:
C = np.array([[8, -2, 4], [-2, 2, -2],[4,-2,8]])/1000    # C is the covariance array (analogous to V)
R = np.array([.06, .02, .04])                            # R is a return array (analogous to muvec)
W = np.ones([len(R)])/len(R)                             # W is the weight array (analogous to X): starts with equal weighting
rf = .01

In [4]:
def mu_port(W, R):                      # Calculates the portfolio return
    return np.dot(R, W)

def var_port(W, C):                     # Calculates the portfolio variance (not the SD)
    return np.dot(W, np.dot(C, W))

In [5]:
Mu = mu_port(W, R)
Var = var_port(W, C)
print("Equal weights are: {}".format(W))
print("Return with Equal weights is: {:.2%} with volatility of: {:.2%}".format(Mu, np.sqrt(Var)))


Equal weights are: [ 0.33333333  0.33333333  0.33333333]
Return with Equal weights is: 4.00% with volatility of: 4.47%

In [6]:
def min_var_optimize(W, C, rf):                                                # takes W and C (doesn't use rf) and returns a new
    def mvp(W, C, rf):                                                         # vector of optimum weights
        return var_port(W, C)
    W = np.ones([len(R)])/len(R)
    b_ = [(0., 1.) for i in range(len(R))]                                     # weights between 0 and 100%, no shorts
    c_ = ({'type':'eq', 'fun': lambda W: sum(W)-1. })                          # weights must add to 100%
    res = minimize(mvp, W, (C, rf), method='SLSQP', constraints=c_, bounds=b_) # the magic scipy function! (look it up for more info)
    if not res.success:
        raise BaseException(res.message)
    return res.x

In [7]:
minvar_W = min_var_optimize(W, C, rf)
print("Min Var weights are: {} (sum: {:.2f})".format(minvar_W, minvar_W.sum()))
print("Min Var Portfolio return is: {:.2%} with a volatility of: {:.2%}".format(mu_port(minvar_W, R), np.sqrt(var_port(minvar_W, C))))


Min Var weights are: [ 0.16666666  0.66666667  0.16666666] (sum: 1.00)
Min Var Portfolio return is: 3.00% with a volatility of: 2.58%

In [8]:
def tangent_optimize(W, C, rf):
    def Sharpe(W, C, rf):
        mean, var = mu_port(W, R), var_port(W, C) 
        util = (mean - rf) / np.sqrt(var)               # calculates the Sharpe Ratio
        return 1/util                                   # maximize utility, minimize the function so we can use the minimize function
    W = np.ones([len(R)])/len(R)                        # start with equal weights if not already set (not necessary)
    b_ = [(0., 1.) for i in range(len(R))]              # weights between 0 and 100%, no shorts
    c_ = ({'type':'eq', 'fun': lambda W: sum(W)-1. })   # weights must add to 100%
    res = minimize(Sharpe, W, (C, rf), method='SLSQP', constraints=c_, bounds=b_) # the magic scipy function! (look it up for more info)
    if not res.success:
        raise BaseException(res.message)
    return res.x

In [9]:
Sharpe_W = tangent_optimize(W, C, rf)
print("Sharpe Optimal weights are: {} (sum: {:.2f})".format(Sharpe_W, Sharpe_W.sum())) # close to empirical solution but not exact
print("Sharpe Optimal Portfolio return is: {:.2%} with a volatility of: {:.2%}".format(mu_port(Sharpe_W, R), np.sqrt(var_port(Sharpe_W, C))))


Sharpe Optimal weights are: [ 0.29167043  0.58333032  0.12499925] (sum: 1.00)
Sharpe Optimal Portfolio return is: 3.42% with a volatility of: 2.84%

In [10]:
Sharpe_ratio = (mu_port(Sharpe_W, R) - rf) / np.sqrt(var_port(Sharpe_W, C))
print("Sharpe ratio is: {:.2}".format(Sharpe_ratio))


Sharpe ratio is: 0.85

In [11]:
Q7_return = rf + Sharpe_ratio*.05
print("Expected return for a volatility of 5% is: {:.2%}".format(Q7_return))


Expected return for a volatility of 5% is: 5.26%

In [ ]: