Python:获取投资组合权重的所有可能组合

5

我认为这个问题可以使用itertools或cartesian来解决,但我对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]等等都是可能的组合。实际上,有5种可能的权重和5个股票是巧合(我应该尽量避免这种情况!),这个问题可以是5种可能的权重和3个股票或7个股票。谢谢。


你想让每个生成的列表长度为_n_并且总和为零吗? - PM 2Ring
1
从人们的回答来看,这个问题存在一些解释的余地。在我们开始编写代码之前,您能否列出更多的组合以确保我们理解正确? - Aran-Fey
2
我怀疑你的电脑无法处理50只股票的相同过程,无论你如何生成它们。显然,前25个值可以是任何值(只需将其余25个值设置为-1倍,使它们总和为0),这已经有了5 ** 25个要迭代的事情,大约是10 ** 18 - Steve Jessop
即使生成器每纳秒产生1个列表,也需要超过30年才能生成10 ** 18个项目。 5 ** 25纳秒略短:约为9.44年。 另一方面,除非您拥有非常快的计算机,否则一个Python指令将需要超过一纳秒的执行时间。 :) - PM 2Ring
1
@PM2Ring:哎呀,说得好,我数不过来了。5**25的数量级是10**17而不是10**18。但是10**18仍然是一个非常宽松的下限,因为平均而言,在给定前25个项目的情况下,选择最后25个项目的方法远远超过三种。 - Steve Jessop
显示剩余5条评论
6个回答

4

虽然不是特别高效,但类似于这样。

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,)

我在评论中重复,当n = 50时你不能这样做。虽然代码产生了正确的值,但在宇宙中没有时间来迭代所有可能的权值。

此代码不是很好。它会检查一些不必要的情况,例如除前两项之外的所有项的总和已经大于0.8,因此单独检查那两项的所有可能性就没有意义。

因此,此代码几乎可以在n = 5时完成,但有一些n值使得此代码变得难以实现,并且您可以通过更好的代码进一步获取结果。但依然无法满足50。我懒得写出更好的代码,但基本上您可以使用递归调用possible_weightings,使用逐渐变小的n值和一个等于给定目标减去您当前所得到的总和的target值。然后,在无需进行的分支中修剪,通过在target太大(正数或负数)无法仅使用n个值达到时提前退出。


谢谢Steve,这正是我所需的。我得考虑一下n=50的问题。也许我应该只指定两个可能的权重(正或负),并逐步实现它。基本上,我正在进行蒙特卡罗模拟,试图找出哪组权重平均而言会产生最好的结果。在理想情况下,这应该一步完成,但我想我的计算机性能有限。 - Chris Waller

1

我理解数值可以重复,但总和必须为零,因此可能的解决方案是:

>>> 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只股票。谢谢 - Chris Waller
你可以尝试使用combinations_with_replacement(weights, len(weights))代替permutations。但是你只会得到组合而不是排列,即顺序无关紧要。 - Juraj Bezručka

0

我喜欢使用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)

4
据我理解,问题不是“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”的“5个加权”。所有“w”的排列都是解决方案(因为它们的总和相同),但您还需要生成使用相同数字超过一次的解决方案。 - Steve Jessop
尽管目前不清楚 OP 想要什么,但它肯定不是你的代码生成的结果,这是 Steve 提到的原因。此外,你的代码没有太多意义。为什么要对每个排列求和呢?它们都会有相同的总和(零)。而且为什么要浪费 RAM 将排列收集到列表中再进行过滤呢?顺便说一下,在 Python 3 中,filter 返回一个可迭代对象而不是列表,但希望这不是问题。 - PM 2Ring
同意,排列组合不是可行的方法。从讨论中可以得知,这是一个太大的空间需要扫描,然后使用过滤器。 - b3rt0

0

不同的方法。

1 找出所有加起来为零的权重序列,按顺序排列。

例如,这是一些可能性(使用整数以减少输入):
[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))

-1
如果您只想要一个所有组合的列表,请使用 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))

-3

这是您要找的吗: 如果 L = [-0.4, 0.2, 0, 0.2, 0]

AllCombi = itertools.permutations(L)

for each in AllCombi:
    print each

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接