0
\$\begingroup\$

What do you suggest to make this code faster? This code gets all possible combinations of actions for a certain number of stocks in pairs in the format [action, volume]. One action element would be something like [[action1,volume1],[action2,volume2],[action3,volume3]] where the actions are either 0 (i.e. Sell) or 1 (i.e. Buy) and elements with volume = 0 correspond to Hold. The volumes values will go from 0 to max_transactions, which does the function of a ceiling for the max transactions enabled per step. The use cases will be something like num_stocks=5 and max_transactions = 100 which takes more than one hour.

import more_itertools
import numpy as np
from copy import deepcopy
from enum import Enum

class Actions(Enum): 
    Sell = 0
    Buy = 1
    Hold = 2 

def get_possible_actions(num_stocks, max_transactions):
    actions = [i for i in range(len(Actions)-1)]*num_stocks 
    # in this env we have only Sell and Buy in the action combos list, because Hold will be represented by the elements with vol=0
    volumes = [i for i in range(max_transactions+1)]*num_stocks
    # we have a plus 1 here to include the elements with vol=0, the elements that represent hold of each stock
    act_combos = list(more_itertools.distinct_permutations(actions,num_stocks))
    vol_combos = list(more_itertools.distinct_permutations(volumes,num_stocks))
    possible_actions = []
    for i in range(len(act_combos)):
        act_combo = deepcopy(act_combos[i])
        for j in range(len(vol_combos)):
            vol_combo = deepcopy(vol_combos[j])
            action = []
            for z in range(num_stocks):
                action.append([act_combo[z],vol_combo[z]]) 
                # each combination has the len of num_stocks, because it represents a chosen action for each stock, and to each stock will have 
                # associated a pair [action,volume]
            possible_actions.append(action)

    return possible_actions
\$\endgroup\$
3
  • \$\begingroup\$ Can you add calling code? What are typical values for num_stocks and max_transactions? \$\endgroup\$
    – Reinderien
    Sep 12 at 18:42
  • \$\begingroup\$ Its in a class and I call it like self.possible_actions = get_possible_actions(self.num_stocks,self.max_transactions), and then I have a reinforcement learning model that will choose and index of the list that will give the give to the class the action and volume for each stock to be executed. The values I wanted to analyse were 2<num_stocks<5 and 20<max_transactions<100 \$\endgroup\$ Sep 12 at 23:08
  • \$\begingroup\$ It looks like len(act_combos) is 2**num_stocks, and len(vol_combos) is (max_transactions+1)**num_stocks. So for num_stocks = 5 and max_transactions = 100, vol_combos has over 10 billion elements and the code in the inner loop runs 2**5 * 101**5 * 5 = 1.68 trillion times. It's going to take some time. \$\endgroup\$
    – RootTwo
    Sep 13 at 6:22

1 Answer 1

3
\$\begingroup\$

I mean it won't solve your problem entirely because your problem as specified is so large, it will take a long time (see RootTwo's comments). (Also, I'm not sure why you would say more_itertools is C, the source is available)

However, in general, you should be using generators for these large tasks, which is what itertools returns by default. "Generators" are iterables which only create the result on demand, so they (usually) don't take up much storage (allocation takes time) and they can be drawn from only as much as you need (rather than having to all exist first), though only once per generator (they do not store their history).

Your function seems to just be doing:

def get_possible_actions(num_stocks: int, max_transactions: int):
    actions = (i for i in range(len(Actions)-1) for _ in range(num_stocks))
    volumes = (i for i in range(max_transactions+1) for _ in range(num_stocks))
    # N.B. will not be sorted, will create a tuple the size of the Cartesian product (i.e. n_actions*max_transactions*num_stocks**2) 
    return more_itertools.distinct_combinations(itertools.product(actions, volumes), num_stocks)

With the generator we can loop over them:

for elem in get_possible_actions(2, 3):
    print(elem)
    ...

Transform them (filter also returns a generator):

q = filter(lambda x: x[2] == 3, get_possible_actions(2, 3))

Or even transform them into a list when we've set up the final structure (requires generating all the values at once):

acts = list(get_possible_actions(2, 3))
\$\endgroup\$
1
  • \$\begingroup\$ Hey, sorry for the mistake with more-itertools, your answer is awesome and Its what I needed, thanks \$\endgroup\$ Sep 13 at 10:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.