Lucca Portes
Lucca Portes

Reputation: 86

Scipy SLSQP constrained optimization doesn't always work

I am trying to minimize a function with scipy.optimize.minimize with SLSQP method. But sometimes it fails with the error message 'Iteration limit exceeded' or 'Positive directional derivative for linesearch', for the exact same input.

Example that just happened: I have it running on a Flask server, so I initalized it and send a request, it failed (iteration limit). I sent a lot of the same request without shutting the Flask down, it failed with every single one of them. Then, I restarted the Flask. Suddenly ALL the requests were optimized succesfully, I kept sending them and they never failed. But if I restart the Flask again, it might stop working, or not, it seems random, but if it worked one time, it will work forever until the server is restarted.

But even when it is one of the server instance that the optimizer is failing, it still works for less complex inputs.

Problem Context: What I am trying to optimize is a stock market portfolio, respecting constraints such as volatility. It usually fails when it is more than $500,000.

con = {"type": "eq", "fun": self.sum}
con2 = {"type": "ineq", "fun": self.volatility_ceiling}
con3 = {"type": "ineq", "fun": self.volatility_floor}
cons = (con, con2, con3)

#allocation_list is a list of percentages for each stock in the portfolio

optimized_result = minimize(self.gain, allocation_list, constraints=cons, bounds=self.bounds, method="SLSQP", options={"maxiter": 400})

def gain(self, allocation_list):
    gain_list = [get_gain(id) for id in self.id_list]
    gain_avg = np.average(gain_list, weights=allocation_list)
    return (gain_avg) * (-1)

def sum(self, allocation_list):
    return np.sum(allocation_list) - 1

def volatility_ceiling(self, allocation_list):
    standard_dev = self.vol_portfolio(lista_aloc)
    if self.current_risk_profile == "PROFILE_1":
        return (standard_dev * (-1)) + 0.009
    elif self.current_risk_profile == "PROFILE_2":
        return (standard_dev * (-1)) + 0.011
    elif self.current_risk_profile == "PROFILE_3":
        return (standard_dev * (-1)) + 0.015
    elif self.current_risk_profile == "PROFILE_4":
        return (standard_dev * (-1)) + 0.024
    #The function continues until PROFILE_10

def volatility_ceiling(self, allocation_list):
    standard_dev = self.vol_portfolio(allocation_list)
    if self.current_risk_profile == "PROFILE_1":
        return standard_dev - 0.0
    elif self.current_risk_profile == "PROFILE_2":
        return standard_dev - 0.009
    elif self.current_risk_profile == "PROFILE_3":
        return standard_dev - 0.011
    elif self.current_risk_profile == "PROFILE_4":
        return standard_dev - 0.015
    #The function continues until PROFILE_10

Upvotes: 0

Views: 1507

Answers (1)

mka
mka

Reputation: 53

I would recommend modifying the 'units' of dollars in your model. For example, instead of 'dollars' switch to 'thousands of dollars' when representing the budget. To that end, use '500' instead of '500,000', and interpret the results accordingly. For example, if an allocation of '3.5' units is made to an asset from the available budget, this would correspond to $3,500 and so on...

As a general model principle, if the numerical values of the numbers span a wide range, the model would be poorly scaled and it is an invitation to numerical problems. Most commercial optimizers would throw warnings alerting you to take actions to improve scaling. If the model had been linear (with or without integer variables), the optimizer would normally attempt to improve scaling itself. For example,

https://www.lindo.com/doc/online_help/lingo15_0/205_themodelispoorlyscaled_.htm

For black-box type nonlinear models, such features may not be available.

Upvotes: 1

Related Questions