Question

I am looking for tips and tricks to better code the following in Python (e.g. remove superfluous loops and copying, use more splicing)

I have coded this to create all possible combinations of weights for a portfolio of N securities subject to the constraints:

Weights come from a list of possibilities (in this case 0,.1,.2,.3,.4,.5) The sum of weights for a valid portfolio must = 1 (Fully Invested)

This is not a valid approach as the number of combinations quickly becomes unmanageable. It's just me trying to get to grips with this language.

Thank you all in advance for you help!

import copy as cp

def generateWeights (weights,possibleWeights,N):
    "Generate all possible combinations of weights"
    # cycle over number of desired coloumns
    for i in range(0,N):
        # copy weights to iterate over every element while i pop stuff out of
        # the original list
        weightsCopy = cp.deepcopy(weights)
        for w in weightsCopy:
            # make a copy to edit
            wtemp = cp.deepcopy(w)
            for p in possibleWeights:
                # append every possibility 
                wtemp.append(p)
                # I only want combinations with sum == 1 so I can start
                # avoiding those that are > 1
                if sum(wtemp) <= 1:
                    weights.append(cp.deepcopy(wtemp))
                # get the original wtemp back so I can work on it with my next p
                wtemp.pop()
            # finished developing the first line of the table. Pop it out and
            # move on.
            weights.pop(0)
    # once again copy weights to iterate over every element while I edit the 
    # original list
    weightsCopy = cp.deepcopy(weights)        
    for w in weightsCopy:
        # remove all possibilities whose sum < 1
        # all those > 1 were never added
        if sum(w) < 1:
            weights.remove(w)
    return weights

N=6 # Number of securities
possibleWeights = [0.0,0.1,0.2,0.3,0.4,0.5]
# weights is a coloumn because I want to access its elements and still get
# lists instead of floats.
weights = [[0.0],[0.1],[0.2],[0.3],[0.4],[0.5]]
weights = generateWeights(weights,possibleWeights,N)
Was it helpful?

Solution

You should use the itertools module that already has the algorithms to do most of what you want.

from itertools import combinations

def valid_combinations(weights):
    '''generator of possible combinations of weights elements that add up to 1'''
    list_length = len(weights) # we will need this
    for lengths in range(list_length):
        for possible in combinations(weights, lengths): # all possible orderings of weights
            if sum(possible[:lengths]) == 1: # only generate valid ones
                yield possible[:lengths]


>>> original = [0, .1, .2, .3, .4, .5]
>>> print list(valid_combinations(original))
[(0.1, 0.4, 0.5), (0.2, 0.3, 0.5), (0, 0.1, 0.4, 0.5), (0, 0.2, 0.3, 0.5), (0.1, 0.2, 0.3, 0.4), (0, 0.1, 0.2, 0.3, 0.4)]

If you are only interested in unique combinations of the weights (the order does not matter), you need to use combinations, if it does matter you should use permutations the following way:

from itertools import permutations

def valid_combinations(weights):
    '''generator of possible combinations of weights elements that add up to 1'''
    list_length = len(weights) # we will need this
    for possible in permutations(weights): # all possible orderings of weights
        for lengths in range(list_length): # get all prefix sublists
            if sum(possible[:lengths]) == 1: # only generate valid ones
                yield possible[:lengths]


>>> original = [0, .1, .2, .3, .4, .5]
>>> print list(valid_combinations(original))
>>> [(0, 0.1, 0.2, 0.3, 0.4), (0, 0.1, 0.2, 0.4, 0.3), (0, 0.1, 0.3, 0.2, 0.4), (0, 0.1, 0.3, 0.4, 0.2), (0, 0.1, 0.4, 0.2, 0.3), (0, 0.1, 0.4, 0.3, 0.2), (0, 0.1, 0.4, 0.5), (0, 0.1, 0.4, 0.5), (0, 0.1, 0.5, 0.4), (0, 0.1, 0.5, 0.4), (0, 0.2, 0.1, 0.3, 0.4), (0, 0.2 ...

OTHER TIPS

You can use itertools.combinations(), however you will have to increase the size of combinations until you reach the length of the dataset.

>>> input_list = [0,.1,.2,.3,.4,.5]
>>> from itertools import combinations
>>> valid_combinations = []
>>> for comb_length in range(1,len(input_list)+1):
    possible_combinations = combinations(input_list,comb_length)
    for comb in possible_combinations:
        if sum(comb) ==1:
            valid_combinations.append(comb)


>>>valid_combinations
[(0.1, 0.4, 0.5), (0.2, 0.3, 0.5), (0, 0.1, 0.4, 0.5), (0, 0.2, 0.3, 0.5), (0.1, 0.2, 0.3, 0.4), (0, 0.1, 0.2, 0.3, 0.4)]

Read your requirements and updated to have combinations ==1, not <= 1.

Note -- if your dataset for input is very large, you will need a better algorithm as this is bruteforce.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top