Home > Enterprise >  How to generate weights with constraint \sum{x_i} = 1 for more than two assets in a meshgrid fashio
How to generate weights with constraint \sum{x_i} = 1 for more than two assets in a meshgrid fashio

Time:12-09

I would like to generate asset weights for more than two assets in a meshgrid fashion so that the sum is one. For example, I can use numpy's linespace with two assets, but not sure how to go about it with more than 2 assets:

import numpy as np

# get 3 weight configurations for 2 assets, that sum up to one
[np.array([w, 1-w]) for w in np.linspace(0, 1, 3)]
>[array([0., 1.]), 
  array([0.5, 0.5]), 
  array([1., 0.])]

# get 4 weight configurations for 2 assets, that sum up to one
[np.array([w, 1-w]) for w in np.linspace(0, 1, 4)]
>[array([0., 1.]),
  array([0.33333333, 0.66666667]),
  array([0.66666667, 0.33333333]),
  array([1., 0.])]

# get 5 weight configurations for 2 assets, that sum up to one
[np.array([w, 1-w]) for w in np.linspace(0, 1, 5)]
> [array([0., 1.]),
   array([0.25, 0.75]),
   array([0.5, 0.5]),
   array([0.75, 0.25]),
   array([1., 0.])]

UPDATE: by meshgrid fashion I mean that the weights are not random but follow a mesh grid pattern, for example, for 3 assets these would be possible configurations. Of course, the granularity depends on the number of weight configurations requested in the num argument to such function (equivalent to numpy#linspace):

[1, 0, 0]
[0, 1, 0]
[0, 0, 1]
[0.333333, 0.333333, 0.333333]
[0.333333, 0.666666, 0]
[0.666666, 0.333333, 0]
[0, 0.333333, 0.666666]
[0, 0.666666, 0.333333]
[0.333333, 0, 0.666666]
[0.666666, 0, 0.333333]

CodePudding user response:

Here is a solution that fits your examples.

Using integer partitioning!

from sympy.utilities.iterables import partitions
from more_itertools import distinct_permutations
# if you don't want to install more_itertools:
#     set(itertools.permutations( ... ))
#     is equivalent but less efficient than
#     more_itertools.distinct_permutations( ... )
from collections import Counter  # not really needed; just for .elements()

def weights(dim, grid):
    for p in partitions(grid, m=dim):
        p[0] = dim - sum(p.values())
        yield from (distinct_permutations(x / grid for x in Counter(p).elements()))

print( list(weights(2, 2)) )
# [(0.0, 1.0), (1.0, 0.0), (0.5, 0.5)]

print( list(weights(2, 4)) )
# [(0.0, 1.0), (1.0, 0.0), (0.25, 0.75), (0.75, 0.25), (0.5, 0.5)]

print( list(weights(2, 3)) )
# [(0.0, 1.0), (1.0, 0.0), (0.333, 0.667), (0.667, 0.333)]

print( list(weights(3, 2)) )
# [(0.0, 0.0, 1.0), (0.0, 1.0, 0.0), (1.0, 0.0, 0.0), (0.0, 0.5, 0.5), (0.5, 0.0, 0.5), (0.5, 0.5, 0.0)]

for x,y,z in weights(3, 3):
    print('{:0.2f},{:0.2f},{:0.2f}'.format(x,y,z))
# 0.00,0.00,1.00
# 0.00,1.00,0.00
# 1.00,0.00,0.00
# 0.00,0.33,0.67
# 0.00,0.67,0.33
# 0.33,0.00,0.67
# 0.33,0.67,0.00
# 0.67,0.00,0.33
# 0.67,0.33,0.00
# 0.33,0.33,0.33

Relevant documentation:

  • Related