robertmartin8 / PyPortfolioOpt

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

Solver status: infeasible when using add_sector_constraints #468

Closed kevinl1210 closed 1 year ago

kevinl1210 commented 1 year ago

Hi, I am trying to use the max_sharpe() to optimize my portfolio. p.s. I am not optimizing portfolio of stock but wine assets.

I have 90 assets in the price df.

return: image

risk matrix: image

sector_mapper = {...}
sector_lower = {"Bordeaux": 0.2, "Burgundy White": 0.1, "Burgundy Red": 0.1, 
                "Italy": 0.1, "Champagne": 0.1, "Rest of France": 0.05, "Spain": 0.0,
               "USA": 0.0, "Rest of World": 0.0}

sector_upper = {"Bordeaux": 0.35, "Burgundy White": 0.2, "Burgundy Red": 0.2, 
                "Italy": 0.15, "Champagne": 0.2, "Rest of France": 0.15, "Spain": 0.1, 
               "USA": 0.15, "Rest of World": 0.15}

mu = expected_returns.mean_historical_return(df)
S = risk_models.risk_matrix(df, method="ledoit_wolf")

ef = EfficientFrontier(mu, S)
ef.add_sector_constraints(sector_mapper, sector_lower, sector_upper)
ef.add_objective(objective_functions.L2_reg, gamma=0.1)
weights = ef.max_sharpe()      <----- error here

cleaned_weights = ef.clean_weights()
ef.portfolio_performance(verbose=True)

The error occurs in ef.max_sharpe()

---------------------------------------------------------------------------
OptimizationError                         Traceback (most recent call last)
Input In [233], in <cell line: 2>()
     59 ef.add_objective(objective_functions.L2_reg, gamma=0.1)  # gamme is the tuning parameter
     60 #ef.solver = "ECOS"
---> 61 weights = ef.max_sharpe()
     62 #weights = ef.min_volatility()
     64 cleaned_weights = ef.clean_weights()

File ~\AppData\Roaming\Python\Python39\site-packages\pypfopt\efficient_frontier\efficient_frontier.py:288, in EfficientFrontier.max_sharpe(self, risk_free_rate)
    281 # Transformed max_sharpe convex problem:
    282 self._constraints = [
    283     (self.expected_returns - risk_free_rate).T @ self._w == 1,
    284     cp.sum(self._w) == k,
    285     k >= 0,
    286 ] + new_constraints
--> 288 self._solve_cvxpy_opt_problem()
    289 # Inverse-transform
    290 self.weights = (self._w.value / k.value).round(16) + 0.0

File ~\AppData\Roaming\Python\Python39\site-packages\pypfopt\base_optimizer.py:300, in BaseConvexOptimizer._solve_cvxpy_opt_problem(self)
    297     raise exceptions.OptimizationError from e
    299 if self._opt.status not in {"optimal", "optimal_inaccurate"}:
--> 300     raise exceptions.OptimizationError(
    301         "Solver status: {}".format(self._opt.status)
    302     )
    303 self.weights = self._w.value.round(16) + 0.0  # +0.0 removes signed zero
    304 return self._make_output_weights()

OptimizationError: ('Please check your objectives/constraints or use a different solver.', 'Solver status: infeasible')

I have also tried to use min_volatility/efficient_risk/efficient_return instead of max_sharpe, but same error occurs. When i comment add_sector_constraints(), it is working fine.

robertmartin8 commented 1 year ago

Interesting!

It looks like there shouldn't be any problem with your set of constraints. Something that might be worth trying is using very loose sector constraints i.e set all lower to 0 and all upper to 1. If this works, then you can start bringing the bounds towards your desired bounds to try and see where the bug is coming from.

If it doesn't work, then I'll need to think more.

Another thing: could you try running without ef.add_objective(objective_functions.L2_reg, gamma=0.1)? max_sharpe doesn't work well with additional objectives

kevinl1210 commented 1 year ago

Thanks. It is working fine after tuning the sector_lower and sector_upper to different values, and fillna to my price dataset.