Home > Software engineering >  Python - Generate a list of 40 random numbers summing to 1, with min and max weight
Python - Generate a list of 40 random numbers summing to 1, with min and max weight

Time:12-30

I have a portfolio of 40 stocks and I'm trying to calculate the standard deviation for the total portfolio while changing the weight per stock every time I calculate this standard deviation. I know how to create a list of random numbers summing to 1, but how can I add max and min weights per stock..

The maximum I'm using is 4.5% and the minimum 0.5%.

The code I use for creating a list of random numbers summing to 1 is:

import numpy as np, numpy.random

random = np.random.dirichlet(np.ones(10), size=1)

But how can I make this list with only values between 0.005 and 0.045?

CodePudding user response:

Consider using the following:

import numpy as np
first_half = np.random.random(size=20)*0.04 0.005
second_half = 0.05-first_half

This is the right range, uniformly distributed, all random, however, the 2nd half is (anti)correlated with the 1st half. You can merge the two halves:

tot40elems = np.concatenate((first_half,second_half))

CodePudding user response:

Perhaps using np.random.normal would give better results. You could scale down the distribution to the 0.005-0.045 range using the proportion of 80% that is variable (above 0.005). Because normal distributions can still have outliers, it will be necessary to "retry" the calculation if the values go out of bounds (but that shouldn't happen too frequently unless you give a large standard deviation):

import numpy as np

def randStock(count=40,minR=0.005,maxR=0.045,sd=3):
    iterations = 0
    while True:
        iterations  = 1
        r = np.random.normal(1,sd,count) #normal distribution
        r -= min(r) # offset to zero
        r /= max(r) # scale to 0..1
        r = minR   r/sum(r)*(maxR-minR)/(maxR minR) # scale to range
        if min(r)>=minR and max(r)<=maxR: return r, iterations

Output:

for _ in range(10):
    s,i = randStock()
    print(*map("{:6.4f}".format,(sum(s),min(s),max(s))),i,"iterations")

[sum]  [min]  [max]  [mean] 
1.0000 0.0050 0.0404 0.0250 1 iterations
1.0000 0.0050 0.0409 0.0250 2 iterations
1.0000 0.0050 0.0395 0.0250 1 iterations
1.0000 0.0050 0.0411 0.0250 4 iterations
1.0000 0.0050 0.0410 0.0250 2 iterations
1.0000 0.0050 0.0428 0.0250 1 iterations
1.0000 0.0050 0.0433 0.0250 1 iterations
1.0000 0.0050 0.0424 0.0250 1 iterations
1.0000 0.0050 0.0371 0.0250 1 iterations
1.0000 0.0050 0.0446 0.0250 1 iterations

Note that this could be improved to randomize the lower bound a bit more and you can chose a different standard deviations

  • Related