SciPy optimizer ignores one of the constraints

2024/10/10 6:21:30

I am trying to solve an optimization problem where I need to create a portfolio that with a minimum tracking error from benchmark portfolio and it's subject to some constraints:

import scipy.optimize as opt
import numpy as npdef random_portfolio(n):a = np.random.random(n)a /= a.sum()return aportfolio_weights = [1 for i in range(20)]
portfolio_weights = [i/len(portfolio_weights) for i in portfolio_weights]def tracking_error_function(W, port_weights):weight_diff = list(np.array(port_weights)-np.array(W))weight_diff = sum([i**2 for i in weight_diff])return weight_diffdef total_max_weight_constraint(weights):max_weights_share = sum([i for i in weights if i > 0.045])max_ineq = 0.36 - max_weights_sharereturn max_ineqdef gen_initial_weights(n):max_weights = [0.089 for i in range(4)]rest_of_n = n - 4rest_of_weight = 1 - sum(max_weights)other_weights = [rest_of_weight/rest_of_n for i in range(rest_of_n)]all_weights = max_weights + other_weightsreturn all_weightsinitial_weights = np.asarray(gen_initial_weights(len(portfolio_weights)))tr_err = tracking_error_function(initial_weights, portfolio_weights)  
b_ = [(0.0, 0.09) for i in range(len(initial_weights))]
c_ = ({'type': 'eq', 'fun': lambda W: sum(W) - 1},{'type': 'ineq', 'fun': total_max_weight_constraint})optimized = opt.minimize(tracking_error_function, initial_weights, args=(portfolio_weights), method='SLSQP', constraints=c_, bounds=b_, options={'maxiter': 100000 })

So my initial guess abides the constraints and the benchmark is equally-weighted. When I run it, the result is exactly equally-weighted portfolio although it is clearly violating the second constraint. Moreover, the status is success. Any ideas what i do wrong?

Update: This is a solution that seems to work in my case

import scipy.optimize as opt
import numpy as np
import random
import matplotlib.pyplot as pltdef random_portfolio(n):#random.seed(123)a = np.random.random(n)a /= a.sum()return adef continous_step_function(x, cutoff):return x / (1 + safe_exp(-(x - cutoff) * 200000))def safe_exp(x):try:ans = np.math.exp(x)except OverflowError:ans = float('inf')return ansdef gen_initial_weights(n):max_weights = [0.0899999 for i in range(4)]rest_of_n = n - 4rest_of_weight = 1 - sum(max_weights)other_weights = [rest_of_weight/rest_of_n for i in range(rest_of_n)]all_weights = max_weights + other_weightsreturn all_weightsdef tracking_error_function(W, port_weights):weight_diff = port_weights - Wweight_diff = np.sum(weight_diff ** 2)excessive_weight = max(0,(sum([continous_step_function(i,0.045) for i in W]) - 0.36))return weight_diff + excessive_weightdef total_max_weight_constraint(weights):max_weights_share = sum([continous_step_function(i,0.045) for i in weights])max_ineq = 0.36 - max_weights_sharereturn max_ineqdef run():portfolio_weights = sorted(random_portfolio(20))initial_weights = np.asarray(gen_initial_weights(len(portfolio_weights)))initial_weights = sorted(initial_weights)b_ = [(0.0, 0.09) for i in range(len(initial_weights))]c_ = ({'type': 'eq', 'fun': lambda W: sum(W) - 1},{'type': 'ineq', 'fun': total_max_weight_constraint})optimized = opt.minimize(tracking_error_function, initial_weights, args=(portfolio_weights), constraints=c_,bounds=b_, options={'eps': 0.00000001, 'ftol' : 0.00000001, 'iprint': 0, 'disp': 0, 'maxiter': 10000})result = optimized.xif tracking_error_function(result, portfolio_weights) > 0.05:print('Excessive tracking error: ')print('Residual error: {}'.format(tracking_error_function(result, portfolio_weights)))print('Target: {} {}'.format(sum(portfolio_weights), portfolio_weights))print('Result: {} {}'.format(sum(result), result))if sum([i for i in result if i > 0.045]) > 0.36:print('Excessive weight > .045: ')print('Percentage > .045: {}'.format(sum([x for x in result if x > 0.045])))print('Target: {} {}'.format(sum(portfolio_weights), portfolio_weights))print('Result: {} {}'.format(sum(result), result))if not all(b >= (a - 0.001) for a, b in zip(result, result[1:])):print('Result not continously rising: ')print('Target: {} {}'.format(sum(portfolio_weights), portfolio_weights))print('Result: {} {}'.format(sum(result), result))def plot_output(result, target):plt.bar(range(len(result)), result,  color='b', width = 0.3)plt.plot(range(len(target)), target, color='r')plt.show()
Answer

It appears that the minimization simply ignores the inequality constraint in this particular case. I do not know why this happens - when testing a simpler example both equality and inequality constraints worked correctly together.

Equality constraints often cause problems in numeric optimization because it may be impossible for floating point numbers to match them exactly. Getting rid of the equality constraint seems to work as a workaround for the problem at hand.

The constraint {'type': 'eq', 'fun': lambda W: sum(W) - 1} forces all N weights to sum exactly to 1. There is another way to enforce this: We can optimize only N-1 weights and constrain their sum to be < 1. Then the remaining weight is implicitly given by 1 - sum(other_weights). This requires some changes to the code:

def expand_weights(weights):"""This function takes N-1 weights and adds the implicit Nth weight so that together their sum is 1."""return np.append(weights, 1 - np.sum(weights))def tracking_error_function(W, port_weights):weight_diff = port_weights - expand_weights(W)weight_diff = np.sum(weight_diff ** 2)return weight_diffdef total_max_weight_constraint(weights):weights = expand_weights(weights)max_weights_share = sum([i for i in weights if i > 0.045])max_ineq = 0.36 - max_weights_sharereturn max_ineq

We simply take the original initial weights and remove the last one:

initial_weights = np.asarray(gen_initial_weights(len(portfolio_weights)))
initial_weights = initial_weights[:-1]

Finally, the constraints become:

c_ = ({'type': 'ineq', 'fun': lambda W: 1 - sum(W)},{'type': 'ineq', 'fun': total_max_weight_constraint})

Run the optimization and see if the constraints are satisfied:

optimized = opt.minimize(tracking_error_function, initial_weights, args=(portfolio_weights), method='SLSQP', constraints=c_, bounds=b_, options={'maxiter': 100000, 'disp': 5})assert np.allclose(1, np.sum(expand_weights(optimized.x)))  # check equality constraint
assert total_max_weight_constraint(optimized.x) > 0  # check second constraint
https://en.xdnf.cn/q/69929.html

Related Q&A

How can one use HashiCorp Vault in Airflow?

I am starting to use Apache Airflow and I am wondering how to effectively make it use secrets and passwords stored in Vault. Unfortunately, search does not return meaningful answers beyond a yet-to-be-…

List all words in a dictionary that start with user input

How would a go about making a program where the user enters a string, and the program generates a list of words beginning with that string?Ex: User: "abd" Program:abdicate, abdomen, abduct..…

Python version of C#s conditional operator (?)

I saw this question but it uses the ?? operator as a null check, I want to use it as a bool true/false test.I have this code in Python:if self.trait == self.spouse.trait:trait = self.trait else:trait…

Python String Replace Error

I have a python script that keeps returning the following error:TypeError: replace() takes at least 2 arguments (1 given)I cannot for the life of me figure out what is causing this.Here is part of my c…

How to run two modules at the same time in IDLE

I am working on a super simple socket program and I have code for the client and code for the server. How do I run both these .py files at the same time to see if they work ?

Passing 2 dimensional C array to python numpy

I need some help regarding passing C array to python(numpy). I have 2d array of doubles NumRows x NumInputs, it seems that PyArray_SimpleNewFromData does not convert it right way - it is hard to see be…

Best way to implement numpy.sin(x) / x where x might contain 0

What I am doing now is:import numpy as npeps = np.finfo(float).epsdef sindiv(x):x = np.abs(x)return np.maximum(eps, np.sin(x)) / np.maximum(eps, x)But there is quite a lot of additional array operation…

Scrapy process.crawl() to export data to json

This might be a subquestion of Passing arguments to process.crawl in Scrapy python but the author marked the answer (that doesnt answer the subquestion im asking myself) as a satisfying one.Heres my pr…

Embedding Python in C: Error in linking - undefined reference to PyString_AsString

I am trying to embed a python program inside a C program. My OS is Ubuntu 14.04I try to embed python 2.7 and python 3.4 interpreter in the same C code base (as separate applications). The compilation a…

How can I add properties to a class using a decorator that takes a list of names as argument?

I would like to add many dummy-properties to a class via a decorator, like this:def addAttrs(attr_names):def deco(cls):for attr_name in attr_names:def getAttr(self):return getattr(self, "_" +…