Search code examples
pythonfinance

Limit numbers within a list & Redistribute excess numbers in a list


I am attempting to write a function in Python to find a list when entered, with a max value, will limit the values in the list and proportionally redistribute the excess subtracted from the number to the other numbers within the list.

For example, I want [0.05, 0.02, 0.05, 0.08, 0.80] passed through a function with a max of 0.3 to be [0.175, 0.07, 0.175, 0.28, 0.30].

Currently, I have something like this in Python:

'''

import numpy as np

x = np.array([0.05, 0.05, 0.02, 0.08, 0.80])
limit = 0.3


excess = 0
y = np.zeros(len(x))
z = np.zeros(len(x))

for i in range(len(x)):
    if x[i] > limit:
        excess = x[i] - limit
        x[i] = 0
        y[i] = limit
        z = x*(1/sum(x)*excess)
        z = z+x+y

'''

Where z is the result of this particular array.

'''

z = array([0.175, 0.175, 0.07 , 0.28 , 0.3  ])

'''

However, this breaks down when I have multiple numbers above the limit, or if the array is in a different order.


Solution

  • You can achieve your objective using the following script. The idea is to take the "excess weight" and redistribute it over the underweight elements repeatedly until all elements have been capped. The process needs to be repeated because redistribution may bring some originally underweight elements over the cap. Also, you haven't mentioned it explicitly but based on the finance tag and your example, I'm assuming that the sum of the list needs to stay constant.

    First, I have created a pandas data frame that contains 20 values summing to 1 and ordered it in descending order.

        Elements    Uncon
    3   D   0.081778
    1   B   0.079887
    19  T   0.079451
    17  R   0.070283
    11  L   0.068052
    4   E   0.057335
    12  M   0.054695
    5   F   0.051099
    6   G   0.049873
    18  S   0.049469
    14  O   0.045059
    16  Q   0.043583
    8   I   0.041186
    2   C   0.036802
    7   H   0.036315
    13  N   0.035440
    0   A   0.034311
    15  P   0.031519
    10  K   0.027173
    9   J   0.026689
    

    Second, let's set the cap to 0.06. This will make this a good test case for the multiple overweight elements problem you are facing.

    cap = 0.06
    weights = df.Uncon
    

    Below is the iterative script.

    # Obtain constrained weights
    constrained_wts = np.minimum(cap, weights)
    # Locate all stocks with less than max weight
    nonmax = constrained_wts.ne(cap)
    # Calculate adjustment factor - this is proportional to original weights
    adj = ((1 - constrained_wts.sum()) *
            weights.loc[nonmax] / weights.loc[nonmax].sum())
    # Apply adjustment to obtain final weights
    constrained_wts = constrained_wts.mask(nonmax, weights + adj)
    # Repeat process in loop till conditions are satisfied
    while ((constrained_wts.sum() < 1) or
           (len(constrained_wts[constrained_wts > cap]) >=1 )):
        # Obtain constrained weights
        constrained_wts = np.minimum(cap, constrained_wts)
        # Locate all stocks with less than max weight
        nonmax = constrained_wts.ne(cap)
        # Calculate adjustment factor - this is proportional to original weights
        adj = ((1 - constrained_wts.sum()) *
            constrained_wts.loc[nonmax] / weights.loc[nonmax].sum())
        # Apply adjustment to obtain final weights
        constrained_wts = constrained_wts.mask(nonmax, constrained_wts + adj)
    

    The results can then be reassigned to the data frame and compared.

    df['Cons'] = constrained_wts
     Elements Uncon       Cons
    0   A   0.034311    0.039189
    1   B   0.079887    0.060000
    2   C   0.036802    0.042034
    3   D   0.081778    0.060000
    4   E   0.057335    0.060000
    5   F   0.051099    0.058364
    6   G   0.049873    0.056964
    7   H   0.036315    0.041478
    8   I   0.041186    0.047041
    9   J   0.026689    0.030483
    10  K   0.027173    0.031037
    11  L   0.068052    0.060000
    12  M   0.054695    0.060000
    13  N   0.035440    0.040479
    14  O   0.045059    0.051465
    15  P   0.031519    0.036001
    16  Q   0.043583    0.049780
    17  R   0.070283    0.060000
    18  S   0.049469    0.056502
    19  T   0.079451    0.060000
    

    Element M is a good example of why a repetitive process is required. It was originally underweight but so close to the cap that it would have become overweight after the first redistribution.