Finding a Constrained Tangency Portfolio by Sharpe Optimization
Summary
The document explains ways to find a portfolio with the highest Sharpe ratio when asset weights have lower and upper bounds. One direct formulation uses covariance as the quadratic objective, constrains the portfolio’s excess return to a fixed value, and transforms the weight bounds to match the rescaled optimization variables. After solving, the resulting weights are normalized to sum to one. The expected returns must first be converted to excess returns by subtracting the risk free rate.
It also describes an iterative alternative: determine the feasible return range, solve minimum variance portfolios over target returns in that range, then select the portfolio with the best Sharpe ratio. The author reports that, in an example with six assets, iterative results approach those from the direct method. The example code contains implementation assumptions and apparent variable inconsistencies, and the iterative method samples a range rather than proving an exact optimum. A separate answer points to a CVXPY portfolio optimization example.
Key ideas
- The tangency portfolio maximizes excess return relative to portfolio risk subject to allocation constraints.
- Subtract the risk free rate from each expected return to form the excess return vector.
- The direct quadratic formulation fixes excess return and minimizes variance, then normalizes the solution to sum to one.
- An iterative alternative compares Sharpe ratios across minimum variance portfolios at feasible target returns.
- The document reports a six asset comparison but its example code has limitations and inconsistencies.
Tags
Full text
# Compute tangency portfolio with asset allocation constraints
# Compute tangency portfolio with asset allocation constraints
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)
```
## Answer by Alex C (score 3, accepted)
https://quant.stackexchange.com/a/42779
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).
## Answer by mperlow (score 4)
https://quant.stackexchange.com/a/42778
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
## Answer by Sebapi (score 1)
https://quant.stackexchange.com/a/42781
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.Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.