SciPy优化器忽略其中一个约束

时间:2017-08-15 16:17:27

标签: python scipy minimization

我正在尝试解决一个优化问题,我需要创建一个投资组合,其基准投资组合的跟踪误差最小,并受到一些限制:

import scipy.optimize as opt
import numpy as np

def random_portfolio(n):
    a = np.random.random(n)
    a /= a.sum()
    return a

portfolio_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_diff

def total_max_weight_constraint(weights):
    max_weights_share = sum([i for i in weights if i > 0.045])
    max_ineq = 0.36 - max_weights_share
    return max_ineq

def gen_initial_weights(n):
    max_weights = [0.089 for i in range(4)]
    rest_of_n = n - 4
    rest_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_weights
    return all_weights

initial_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 })

因此,我的初步猜测遵循约束条件,基准测试均衡。当我运行它时,结果是完全相同加权的投资组合,尽管它显然违反了第二个约束。而且,地位是成功的。任何想法我做错了什么?

更新 这个解决方案似乎适合我的情况

import scipy.optimize as opt
import numpy as np
import random
import matplotlib.pyplot as plt


def random_portfolio(n):
    #random.seed(123)
    a = np.random.random(n)
    a /= a.sum()
    return a

def 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 ans

def gen_initial_weights(n):
    max_weights = [0.0899999 for i in range(4)]
    rest_of_n = n - 4
    rest_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_weights
    return all_weights

def tracking_error_function(W, port_weights):
    weight_diff = port_weights - W
    weight_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_weight

def 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_share
    return max_ineq

def 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.x

    if 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()

1 个答案:

答案 0 :(得分:1)

在这种特殊情况下,最小化似乎只是忽略了不等式约束。我不知道为什么会发生这种情况 - 当测试一个更简单的例子时,相等和不等式约束一起正确地工作。

等式约束通常会导致数值优化问题,因为浮点数可能无法完全匹配它们。摆脱平等约束似乎可以解决手头的问题。

约束{'type': 'eq', 'fun': lambda W: sum(W) - 1}强制所有N权重精确地加到1.还有另一种方法可以强制执行此操作:我们只能优化N-1权重并将其总和约束为<然后由1 - sum(other_weights)隐含地给出剩余的权重。这需要对代码进行一些更改:

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_diff

def 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_share
    return max_ineq

我们只需取原始初始权重并删除最后一个:

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

最后,约束变为:

c_ = ({'type': 'ineq', 'fun': lambda W: 1 - sum(W)},
      {'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, '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