robertmartin8 / PyPortfolioOpt

Financial portfolio optimisation in python, including classical efficient frontier, Black-Litterman, Hierarchical Risk Parity
https://pyportfolioopt.readthedocs.io/
MIT License
4.38k stars 940 forks source link

How can I add the volatility constraints to max_sharpe optimization ? #366

Closed koalalovepabro closed 3 years ago

koalalovepabro commented 3 years ago

Hi Robert ! Thanks for all the great work ! I'm trying to optimize portfolio with using your library and I have a question as below.

What are you trying to do? I'd like to add the volatility constraints to max_sharpe optimization.

What have you tried? For example, when I optimize the portfolio with max_sharpe as below, the annual volatility of this portfolio is 25.2%. But if I can handle up to 20% risk, can I optimize the portfolio that maximizes the sharpe ratio within a given risk range?

# Acquiring data
tickers = ["BLK", "BAC", "AAPL", "TM", "WMT",
           "JD", "INTU", "MA", "UL", "CVS",
           "DIS", "AMD", "NVDA", "PBI", "TGT"]
ohlc = yf.download(tickers, period="max")
prices = ohlc["Adj Close"]

# Expected returns and risk models
mu = expected_returns.mean_historical_return(prices)
S = risk_models.sample_cov(prices)

# Optimizing portfolio
ef = EfficientFrontier(mu, S)
weights = ef.max_sharpe(risk_free_rate=0.01426)
cleaned_weights = ef.clean_weights()
ef.portfolio_performance(verbose=True)

Please advise if there is any way to apply it in this case. Your advice will be very helpful to me, thank you !

robertmartin8 commented 3 years ago

Hi @koalalovepabro,

Is there a reason why efficient_risk won't work? It maximises return for a risk constraint (which is almost always equivalent to maximising sharpe).

Best, Robert

koalalovepabro commented 3 years ago

Thank you for your prompt reply, @robertmartin8 !

I tried efficient_risk to limit the volatility, but the result was not better than max_sharpe.

Below code is what I tried. All conditions are same except the optimization function.

--- with max_sharpe ---

ef = EfficientFrontier(mu, S)  

weights = ef.max_sharpe()
cleaned_weights = ef.clean_weights()

ef.portfolio_performance(verbose=True)

Result of max_sharpe>>
Expected annual return: 238.9%
Annual volatility: 12.4%
Sharpe Ratio: 19.13
(2.3890972914731083, 0.12385293828723035, 19.1283091401423)

--- with efficient_risk ---

ef = EfficientFrontier(mu, S)  

# Set the target volatility as 50% to compare extremely 
weights = ef.efficient_risk(0.5)
cleaned_weights = ef.clean_weights()

ef.portfolio_performance(verbose=True)

Result of efficient_risk>>
Expected annual return: 417.6%
Annual volatility: 50.0%
Sharpe Ratio: 8.31
(4.175623939219235, 0.49999999901220493, 8.31124789485809)

I expected the result of efficient_risk is same with max_sharpe. Because I set the target volatility as 50% to make the range big enough, so I think it could find out the point of maximizing sharpe ratio. But the resulting portfolio have the target volatility, not less than the target. And the sharpe ratio is lower than max_sharpe.

I want to limit the volatility and maximize the sharpe ratio, not make the volatility to be equal with target. How could I do this?

Your advice will be very helpful to me and thank you for considering my request !

robertmartin8 commented 3 years ago

To clarify, I meant that maximising return subject to a risk constraint is almost always the same as maximising sharpe subject to a risk constraint, since sharpe is just return/risk and return is almost always monotonic with risk.

I want to limit the volatility and maximize the sharpe ratio, not make the volatility to be equal with target. How could I do this?

I would have thought that the following gives you the answer (replace vol_limit with your constraint).

vol_limit = 0.124
ef = EfficientFrontier(mu, S)
weights = ef.efficient_risk(vol_limit)
cleaned_weights = ef.clean_weights()
ef.portfolio_performance(verbose=True)

Let me know if this isn't what you are after

koalalovepabro commented 3 years ago

Thank you for your prompt reply, @robertmartin8 !

I'll keep trying with your advice, thank you !