4
$\begingroup$

I am looking to compute the tangency portfolio of the efficient frontier, but taking into account min_allocations and max_allocations for asset weights in the portfolio. These constraints make me think I need to use an optimization tool such as cvxopt. The tangency portfolio is the portfolio that maximizes the Sharpe ratio and I believe computing the tangency portfolio requires the inputs compute_tanp(exp_ret_vec, cov_mat, min_allocations, max_allocations, rf).

These lecture notes are able to transform the optimization problem above to the standard quadratic format below, but I am not exactly sure how to properly form the matrices for this approach.

How do I form the matrices to properly use cvoxpt to find the portfolio with the max Sharpe ratio? I am also open to other techniques to calculate the tangency portfolio with constraints.

Below I have a working function that will find the efficient portfolio weights $W$ when passed a desired target return. It uses cvxopt to handle optimization of the form:

import pandas as pd
import numpy as np
import cvxopt as opt

def compute_ep(target_ret, exp_ret_vec, cov_mat, min_allocations, max_allocations):
    """
    computes efficient portfolio with min variance for given target return 

    """
    # number of assets
    n = len(exp_ret_vec)

    one_vec = np.ones(n)

    # objective
    # minimize (0.5)x^TPx _ q^Tx
    P = opt.matrix(cov_mat.values) # covariance matrix
    q = opt.matrix(np.zeros(n)) # zero

    # constraints Gx <= h
    # >= target return, >= min allocations, <= max allocations
    G = opt.matrix(np.vstack((-exp_ret_vec,-np.identity(n), np.identity(n))))  

    h = opt.matrix(np.hstack((-target_ret,-min_allocations, max_allocations)))

    # constraints Ax = b
    A = opt.matrix(np.ones(n)).T
    b = opt.matrix(1.0) # sum(w) = 1; not market-netural

    # convex optimization
    opt.solvers.options['show_progress'] = False
    sol = opt.solvers.qp(P, q, G, h, A, b)

    weights = pd.Series(sol['x'], index = cov_mat.index)

    w = pd.DataFrame(weights, columns=['weight'])

    return(w)
$\endgroup$

3 Answers 3

3
$\begingroup$

There are two transformations of the input data to be made to go from the first problem to the second:

  • the $\hat{\mu}$ are found by subtracting the scalar $r_f$ from all the $\mu$ vector components: $$\hat{\mu}=\mu-r_f=(\mu_1-r_f,\mu_2-r_f,\cdots,\mu_N-r_f)^T$$

in other words the $\mu$ are returns and the $\hat\mu$ are "excess returns".

  • the $\hat{A}$ matrix is found by subtracting the $b$ column vector from each column of the $A$ matrix, i.e. $\hat{a}_{ij}=a_{ij}-b_i$

  • the $Q$ matrix (covariance matrix) is unchanged in problem 2 compared to problem 1

Once you solve problem 2, you have the optimal $y$. You can find the optimal $x$ for Problem 1 by doing $x=\frac{y}{1^T y}$. This makes the x components add up to 1 (as desired) even though the y components do not.

HTH

(I don't know R and cvxopt well enough to write the code, but it should be straightfoward).

$\endgroup$
3
  • $\begingroup$ What does the $b$ column vector refer to in this case? $\endgroup$
    – cpage
    Nov 25, 2018 at 2:18
  • 3
    $\begingroup$ The b column is on the right side of the inequality constraint $A x >= b$ in Problem 1. These are for example limits on exposure to certain stocks or stock sectors, etc. $\endgroup$
    – Alex C
    Nov 25, 2018 at 2:33
  • 3
    $\begingroup$ For example if the weight of the first stock must be between 0.05 and 0.2, that would be two constraints $x_1>= 0.05$ and $-x_1 >= -0.2$ which would be expressed in the A matrix and the b vector. $\endgroup$
    – Alex C
    Nov 25, 2018 at 3:01
4
$\begingroup$

If you are willing to switch to CVXPY, it comes with a pretty example of exactly this exercise:

http://nbviewer.jupyter.org/github/cvxgrp/cvx_short_course/blob/master/applications/portfolio_optimization.ipynb

$\endgroup$
1
$\begingroup$

The simplest is to get the admissible return range using the cvxopt optimizer with $q = \alpha \mu$ and $q = -\alpha \mu$ for a large $\alpha$ instead of $q=0$ and then run the function compute_ep iteratively to find the portfolio with the highest Sharpe ratio in this range.

Using code similar to the one posted above, one can get the bounds and optimal Sharpe ratio as follow:

def compute_ep_bound(alpha, exp_ret_vec, cov_mat, min_allocations, max_allocations):
    n = len(exp_ret_vec)
    one_vec = np.ones(n)
    P = opt.matrix(cov_mat.values) # covariance matrix
    q = opt.matrix(exp_ret_vec * alpha) # zero
    G = opt.matrix(np.vstack((-np.identity(n), np.identity(n))))  
    h = opt.matrix(np.hstack((-min_allocations, max_allocations)))
    A = opt.matrix(np.ones(n)).T
    b = opt.matrix(1.0) # sum(w) = 1; not market-netural
    opt.solvers.options['show_progress'] = False
    sol = opt.solvers.qp(P, q, G, h, A, b)
    weights = pd.Series(sol['x'], index = cov_mat.index)
    w = pd.DataFrame(weights, columns=['weight'])
    return np.dot(mu,sol['x'])

def compute_tanp(exp_ret_vec, cov_mat, min_allocations, max_allocations, rf):
    alpha = 10000
    e1 = compute_ep_bound(alpha, exp_ret_vec, cov_mat, min_allocations, max_allocations)
    e2 = compute_ep_bound(-alpha, exp_ret_vec, cov_mat, min_allocations, max_allocations)
    sharpemax = -1e300
    wmax = 1
    for target_ret in np.linspace(e2,e1):
        w = compute_ep(target_ret, exp_ret_vec, cov_mat, min_allocations, max_allocations)
        sharpe = (np.dot(mu,w)-rf)/np.dot(w.T,np.dot(cov_mat,w))
        if sharpe > sharpemax:
            print("sharpe=%f" % sharpe)
            wmax = w
            sharpemax = sharpe
    return wmax

The alternative presented in the paper is to compute the maximum Sharpe ratio portfolio directly using the equivalent problem $\hat{\mu}^t y = 1$ and $\hat{A} y > 0$:

def compute_tanp_direct(exp_ret_vec, cov_mat, min_allocations, max_allocations, rf):
    # number of assets
    n = len(exp_ret_vec)
    mu_hat = exp_ret_vec - rf

    # objective minimize (0.5)x^TPx _ q^Tx
    P = opt.matrix(cov_mat.values) # covariance matrix
    q = opt.matrix(np.zeros(n)) # zero

    # constraints Gx <= h: -hatA y > 0 matrix 
    G = opt.matrix(np.vstack((-np.identity(n)+min_allocations, np.identity(n)-max_allocations)))  
    h = opt.matrix(np.zeros(2*n))

    # constraints Ax = b: hatmu . y = 1
    A = opt.matrix(mu_hat).T
    b = opt.matrix(1.0)

    # convex optimization
    opt.solvers.options['show_progress'] = False
    sol = opt.solvers.qp(P, q, G, h, A, b)

    weights = pd.Series(sol['x'], index = cov_mat.index)
    w = pd.DataFrame(weights, columns=['weight'])
    w = w/np.dot(np.ones(n), w)
    sharpe = (np.dot(exp_ret_vec,w)-rf)/np.sqrt(np.dot(w.T,np.dot(cov_mat,w)))
    print("sharpe=%f" % sharpe)
    return(w)

I verified on a practical example with 6 assets that the iterative methods converges towards the optimal Sharpe ratio computed directly.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.