【问题标题】:Python: get every possible combination of weights for a portfolioPython:获取投资组合的所有可能的权重组合
【发布时间】:2016-12-01 09:01:30
【问题描述】:

我认为这个问题可以使用 itertools 或笛卡尔来解决,但我对 Python 还很陌生,并且正在努力使用这些:

我有 5 只股票的投资组合,其中每只股票的权重为 -0.4、-0.2、0、0.2 或 0.4,权重加起来为 0。如何创建一个函数来生成每个股票的列表可能的权重组合。例如[-0.4, 0.2, 0, 0.2, 0]...等

理想情况下,该函数适用于 n 只股票,因为我最终希望对 50 只股票执行相同的过程。

编辑:澄清一下,我正在寻找长度为 n(在本例中为 5)的所有组合,总和为 0。值可以重复:例如:[0.2, 0.2, -0.4 , 0, 0], [ 0.4, 0, -0.2, -0.2, 0.4], [0,0,0,0.2,-0.2], [0, 0.4, -0.4, 0.2, -0.2] 等等。所以 [ 0,0,0,0,0] 将是一个可能的组合。有 5 种可能的权重和 5 只股票的事实是巧合(我应该避免的!),同样的问题可能是 5 种可能的权重和 3 只股票或 7 只股票。谢谢。

【问题讨论】:

  • 您希望生成的每个列表的长度为 n 且总和为零吗?
  • 从人们的回答来看,这个问题有一定的解释空间。您能否列出更多组合,以确保我们在开始编写代码之前正确理解这一点?
  • 我怀疑您的计算机是否可以处理 50 只股票的相同过程,无论您如何生成它们。很明显,前 25 个值可以是任何值(只需将剩余的 25 设置为 -1 次每个值的总和为 0),这已经是 5**25 要迭代的东西,大约是 10**18
  • @Steve Jessop 但是用生成器而不是列表构造不可能吗?
  • @PM2Ring:哎呀,好点,我数不清。 5**25 是订单 10**17,而不是 18。但 10**18 仍然是一个非常宽松的下限,因为考虑到前 25 个项目,平均有不止三种方法可以选择最后 25 个项目。

标签: python list itertools cartesian-product


【解决方案1】:

类似的东西,虽然它不是很有效。

from decimal import Decimal
import itertools

# possible optimization: use integers rather than Decimal
weights = [Decimal("-0.4"), Decimal("-0.2"), Decimal(0), Decimal("0.2"), Decimal("0.4")]

def possible_weightings(n = 5, target = 0):
    for all_bar_one in itertools.product(weights, repeat = n - 1):
        final = target - sum(all_bar_one)
        if final in weights:
            yield all_bar_one + (final,)

我从 cmets 重复一遍,你不能n = 50 这样做。代码产生了正确的值,但宇宙中没有时间迭代所有可能的权重。

这段代码并不出色。它会做一些不必要的工作来检查情况,例如,除了前两个之外的所有情况的总和已经大于 0.8,因此单独检查这两个中的第一个的所有可能性是没有意义的。

因此,n = 5 几乎很快就会执行此操作,但 n 的某些值会使此代码变得异常缓慢,您可以通过更好的代码走得更远。你仍然不会达到 50。我懒得写更好的代码,但基本上你可以递归调用 possible_weightings,而不是 n 和 @987654328 的值。 @ 等于你给定的目标,减去你到目前为止的总和。然后修剪所有不需要的分支,在 target 太大(正或负)而无法仅使用 n 值达到的情况下尽早退出。

【讨论】:

  • 感谢史蒂夫,这正是我想要的。我将不得不考虑去 n=50 的问题。也许我应该只指定 2 个可能的权重(正或负)并分步进行。本质上,我在做蒙特卡罗模拟,并试图找出哪组权重能给出最好的结果(平均而言)。在理想情况下,这将一步完成,但我想我受到了我所拥有的计算机能力的限制。
【解决方案2】:

我知道这些值可以重复,但所有值的总和必须为零,因此解决方案可能是:

>>> from itertools import permutations
>>> weights = [-0.4, -0.2, 0, 0.2, 0.4]
>>> result = (com for com in permutations(weights) if sum(com)==0)
>>> for i in result: print(i)

编辑: 您可以按照@Steve Jassop 的建议使用product

combi = (i for i in itertools.product(weights, repeat= len(weights)) if not sum(i))
for c in combi:
    print(c)

【讨论】:

  • 感谢 Juraj,这似乎很接近,但并不完全是我所追求的,因为它似乎没有列出所有可能的组合。例如,它没有列出 [0,0,0,0,0]。另外,我该如何调整它,以便它对 6 只股票(或 n 只股票?)做同样的事情。即权重仍然只能是 -0.4、-0.2、0、0.2 或 0.4,但这次有 n 只股票。谢谢
  • 您可以尝试使用combinations_with_replacement(weights, len(weights)) 而不是permutations。但是,您只会得到组合而不是变化,即顺序不相关。
【解决方案3】:

我喜欢使用filter 函数:

from itertools import permutations
w = [-0.4, -0.2, 0, 0.2, 0.4] 

def foo(w):
    perms = list(permutations(w))
    sum0 = filter(lambda x: sum(x)==0, perms)
    return sum0

print foo(w)

【讨论】:

  • 除非我理解问题不是permutations(w),而是product(w, repeat=5)。观察到一个预期的输出是[-0.4, 0.2, 0, 0.2, 0]。这不是w 的排列,而是“-0.4、-0.2、0、0.2 或 0.4 的 5 个权重,权重加起来为 0”。 w 的所有排列都是解(因为它们的和都相同),但您还需要生成多次使用相同数字的解。
  • 虽然目前尚不清楚 OP 想要什么,但由于史蒂夫提到的原因,它不可能是您的代码生成的内容。此外,您的代码没有多大意义。为什么要对每个排列求和?它们都将具有相同的总和(零)。为什么在过滤它们之前浪费 RAM 将排列收集到一个列表中? FWIW,在 Python 3 中 filter 返回一个可迭代而不是列表,但希望这不是问题。
  • 同意,排列不是要走的路。从讨论来看,空间太大,无法像这样扫描并使用过滤器。
【解决方案4】:

不同的方法。

1 按顺序找出所有权重之和为零的序列。

例如,这些是一些可能性(使用整数键入 less):
[0, 0, 0, 0, 0]
[-4, 0, 0, +2, +2]
[-4, 0, 0, 0, +4]

[-4, +4, 0, 0, 0] 不正确,因为没有按顺序选取权重。

2 排列你上面得到的东西,因为排列加起来也会为零。

这是您获得 [-4, 0, 0, 0, +4] [-4, +4, 0, 0, 0]的地方

好吧,懒惰。我将对我的解决方案进行大量伪代码/注释代码。递归不是那么强,这些东西太棘手而无法快速编码,我怀疑这种类型的解决方案是否可以扩展到 50。

即我不认为我是对的,但它可能会给其他人一个想法。

def find_solution(weights, length, last_pick, target_sum):

    # returns a list of solutions, in growing order, of weights adding up to the target_sum

    # weights are the sequence of possible weights - IN ORDER, NO REPEATS
    # length is how many weights we are adding up
    # last_pick - the weight picked by the caller 
    # target_sum is what we are aiming for, which will always be >=0

    solutions = []

    if length > 1:

        #since we are picking in order, having picked 0 "disqualifies" -4 and -2.
        if last_pick > weights[0]:
            weights = [w for w in weights if w >= last_pick]

        #all remaining weights are possible
        for weight in weights:
            child_target_sum = target_sum + weight

            #basic idea, we are picking in growing order
            #if we start out picking +2 in a [-4,-2,0,+2,+4] list in order, then we are constrained to finding -2
            #with just 2 and 4 as choices.  won't work.
            if child_target_sum <= 0:
                break

            child_solutions = find_solution(weights, length=length-1, last_pick=weight, target_sum=child_target_sum)

            [solutions.append([weight] + child ) for child in child_solutions if child_solution]

    else:
        #only 1 item to pick left, so it has be the target_sum
        if target_sum in weights:
            return [[target_sum]]

    return solutions

weights = list(set(weights))
weights.sort()

#those are not permutated yet
solutions = find_solutions(weights, len(solution), -999999999, 0)

permutated = []
for solution in solutions:
   permutated.extend(itertools.permutations(solution))

【讨论】:

    【解决方案5】:

    如果您只想要所有组合的列表,请使用itertools.combinations

    w = [-0.4, -0.2, 0, 0.2, 0.4]
    l = len(w)
    
    if __name__ == '__main__':
        for i in xrange(1, l+1):
            for p in itertools.combinations(w, i):
                print p
    

    如果你想计算这些组合可以创建的不同权重,那就有点复杂了。

    首先,您生成具有 1、2、3、... 元素的排列。然后你把它们加起来。然后将总和添加到集合中(如果数字已经存在,则不会做任何事情,非常快的操作)。最后你转换成一个列表并对其进行排序。

    from itertools import combinations
    
    def round_it(n, p):
        """rounds n, to have maximum p figures to the right of the comma"""
        return int((10**p)*n)/float(10**p)
    
    w = [-0.4, -0.2, 0, 0.2, 0.4]
    l = len(w)
    res = set()
    
    if __name__ == '__main__':
        for i in xrange(1, l+1):
            for p in combinations(w, i):
                res.add(round_it(sum(p), 10))  # rounding necessary to avoid artifacts
    
        print sorted(list(res))
    

    【讨论】:

      【解决方案6】:

      这是您正在寻找的: 如果L = [-0.4, 0.2, 0, 0.2, 0]

      AllCombi = itertools.permutations(L)
      
      for each in AllCombi:
          print each
      

      【讨论】:

        猜你喜欢
        • 2022-01-18
        • 2020-01-22
        • 2013-10-06
        • 2011-11-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-23
        • 1970-01-01
        相关资源
        最近更新 更多