Closed agj60 closed 2 years ago
Not mentioned: you have to impose a restriction of x[0] == 0
to get the correct tangent portfolio if you include a risk-free asset in the set:
ef = EfficientFrontier(ret, cov)
ef.add_constraint(lambda x: x[0]==0)
ef.max_sharpe(ret[0],[0])
# you should get weights of [0., .68, .32]
Otherwise you are going to get some random allocation to the risk-free asset, probably due to precision issues with the infinitely many solution over the security market line with a a constant maximum sharpe ratio.
@agj60 Can you try upgrading to version 1.5.3 ? Your issue should be fixed by https://github.com/robertmartin8/PyPortfolioOpt/pull/453/commits/e88bb1564571c2a27cc29168c6fc2602442d64aa , which was included in the 1.5.3 release.
Using a pseudoinverse will definitely do the trick! 1.5.3 is available for python >=3.8 only, but thanks for pointing it out!
Description If you include a risk-free asset with zero volatility, the set of maximum sharpe ratio portfolios lie in a straight line that represents a linear combination between the risk-free asset and the tangent portfolio of the efficient frontier that does not include the risk-free asset, the so called "securities market line". That works perfectly well if you calculate efficient_return portfolios: if you aim at half the volatility of the tangent portfolio, you get a portfolio with half the risk-free asset, half the components of the tangent portfolio. But if you try to calculate efficient_risk portfolios, you get a "LinAlgError: Singular matrix"
Workaround A workaround is to test the diagonal of the covariance matrix and use a very small variance instead of zero, say 1e-32 (or change the zero volatility to 1e-16), in order to avoid the singularity.
Code sample
import numpy as np
from pypfopt.efficient_frontier import EfficientFrontier
ret = np.array([[.0, .06, .10]]).T # Returns
vol = np.array([[.0,.08,.15]]).T # Volatilities -- workaround change .0 to 1e-16
rho = np.array([[1.,.0,.0],[.0,1.,.0],[.0,.0,1.]]) # Correlations
cov = np.diagflat(vol)@rho@np.diagflat(vol)
ef = EfficientFrontier(ret, cov)
ef.efficient_risk(0.0726/2) # 0.0726 is the return of the tangent portfolio
# you should get weights of [0.50, 0.34, 0.16]
Versions UBUNTU 18.04.5 LTS (Bionic Beaver) Python 3.7.13 PyPortfolioOpt 1.5.2 on Google Colab