Optimize API maximize Sharpe ratio

Hi,

I normally use following simple method to maximize unconstrained Sharpe as a litmus test.

def max_sharpe(cov, expected_returns):
covis = np.linalg.inv(cov)
w = np.dot(covis, expected_returns)
return w / np.sum(np.abs(w))


Can I do something like this using optimize API or CVXPY but with additional constraints for following?:
1. sector neutrality
2. position concentration
3. dollar neutrality

Will that even be a convex optimization problem?

Best regards,
Pravin

7 responses

You could consider minimizing the variance in the returns, with a constraint that the expected portfolio return upon each update be greater than or equal to a target value (which wouldn't need to be constant in time--you could adjust the target value based on overall market volatility, for example).

I'm no expert, but I think this would be convex. I'm not sure if you can apply the optimize API, but it would be nice not to have to write your own additional constraints.

Thanks Grant. That is convex and will work but is a minimum variance portfolio. I want to find the portfolio that maximizes Sharpe ratio (I guess would be non-linear). Some toolbox allow for it but I am wondering how to do it with CVXPY or optimize API.

Well, it seems you can either maximize the projected return, and constrain the variance. Or minimize the variance, with a constraint on the projected return. For the former, I'm not sure that the optimize API provides a means to constrain the variance directly; the risk is managed indirectly (with dollar neutral, beta neutral, max weightings per stock and per sector, etc.). I think that there is an implicit model of the variance baked into the optimize API, implemented via the constraints on maximizing the alpha. So would it make sense to add an additional direct constraint on the variance? In some sense, if one knew how to perfectly constrain the variance directly, the other constraints would not be needed. They would seem to be a proxy for a model that does not exist.

Another thought is that one could possibly implement the optimize API iteratively, by using calculate_optimal_portfolio in a loop. Maybe with a set of successive optimizations, one could effectively maximize SR.

Portfolio optimization is a tradeoff between robustness and complexity. Naive Sharpe Ratio maximization is generally ill-advised as the estimation of expected returns is very noisy and the estimation of the covariance matrix is also very noisy. The noble goal of maximizing out-of-sample Sharpe Ratio is unlikely to be achieved by maximizing in-sample Sharpe Ratio. That's even before considering transaction costs; portfolio turnover tends to be very high when you optimize like this. Caveats noted, the function er'x / sqrt(x'Cx) is not convex. CVXPY will, rightly so, throw an exception if you try to formulate an objective to maximize that. There is a straightforward way to get around this however. With CVXPY, given your arbitrary set of constraints=[...] above active for all calculations:

1. Find the variance, say v_max, of the maximum return portfolio (i.e., cvx.Maximize(er.T*x, ...).
2. Define a function, say sharpe_ratio_v_capped that maximizes return s.t. a given maximum variance v_cap; it returns the Sharpe ratio at the solution times -(np.log(leverage) + leverage). Why this adjustment term? In a long/short portfolio you cannot constraint sum(abs(x))==1 as you will get a DCPError. The constraint must be sum(abs(x))<=1. This means that at very low variance caps, the leverage will approach zero (what portfolio has zero variance? The empty portfolio.). Thus you need to find the maximum Sharpe ratio of the fully invested portfolio. The adjustment term increasingly penalizes under-invested portfolios.
3. Minimize this function with scipy.optimize.minimize_scalar(sharpe_ratio_v_capped, bounds=(0, v_max), method='bounded').
4. The solution of 3, v_star, is the variance of the maximum Sharpe Ratio portfolio which is also fully invested. To get the stock weights, find the maximum return portfolio (i.e., cvx.Maximize(er.T*x, ...) with the additional constraint cvx.quad_form(x, C) <= v_star and you are done.
Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

Thanks very much Jonathan. Your expertise in optimization is evident from your posts. I will give this approach a try.

The issue I see is that you probably want to have all of the wonderful constraints of the optimize API (for example, see https://www.quantopian.com/posts/quantcon-nyc-2017-advanced-workshop ). And so for all of the steps outlined by Jonathan, one has to sort out how to formulate those constraints. I know the plan was to put the optimize API into github. Is it there yet?