如何在资产数量受限的情况下进行投资组合优化?

How to do Portfolio Optimization with constraints on the number of assets?

我正在尝试根据四个约束将某个 return 的波动性降至最低。

  1. 每个资产的权重都在 0 到 1 之间
  2. 所有权重之和等于1
  3. 由资产组成的投资组合的return等于给定的return
  4. 5 项资产中最多只使用了 3 项

我试过 Scipy 以这种方式最小化:

cons_3 = ({'type': 'ineq', 'fun': lambda x: -np.count_nonzero(x) + 3}, 
          {'type': 'eq', 'fun': lambda x: statistics(x)[0] - tret_3}, 
          {'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
x0 = [1/3, 1/3, 1/3, 0, 0]
res_3 = sco.minimize(min_func_port, x0, method='SLSQP', bounds=bnds_3, constraints=cons_3)

但显然这是不可能的。

我还了解到可以使用 Z3Py,但我似乎找不到正确的编码方式。这是我到目前为止发现的内容:

import pandas as pd
from z3 import *

DesiredReturn = 0.05
df = pd.DataFrame(columns=['Name', 'Return', 'Volatility'],
                  data=[['Asset_1', 0.01744, 0.694149],
                        ['Asset_2', 0.03818, 0.475544],
                        ['Asset_3', 0.08218, 0.500724],
                        ['Asset_4', 0.09818, 0.489052],
                        ['Asset_5', 0.04272, 0.706223]])

W = [Real(row.Name) for row in df.itertuples()]
Vol = Real('Vol')
Ret = Real('Ret')
s = Optimize()
s.add(And([And(w >= 0, w <= 1) for w in W]))
s.add(Sum([w for w in W]) == 1)
#Missing constraint
s.add(Ret == Sum([w * row.Return for w, row in zip(W, df.itertuples())]))
s.add(Vol == Sum([w * row.Volatility for w, row in zip(W, df.itertuples())]))
s.add(Ret == DesiredReturn)
h1 = s.minimize(Vol)
print(s.check())
print(s.model())

让我们假设资产之间的相关性在示例中等于零。

非常感谢:)

您的编码非常接近。以下是我的编码方式。 (我正在避免 pandas 因为我没有安装它..)

from z3 import *

Data = [  ['Asset_1', 0.01744, 0.694149]
       ,  ['Asset_2', 0.03818, 0.475544]
       ,  ['Asset_3', 0.08218, 0.500724]
       ,  ['Asset_4', 0.09818, 0.489052]
       ,  ['Asset_5', 0.04272, 0.706223]
       ]

# Weights
W = [Real(row[0]) for row in Data]

# Return and Volatility
Ret = Sum([w * row[1] for (w, row) in zip(W, Data)])
Vol = Sum([w * row[2] for (w, row) in zip(W, Data)])

o = Optimize()

# 1. Every weight is between 0 and 1
o.add([And(w >= 0, w <= 1) for w in W])

# 2. They sum up to 1
o.add(Sum([w for w in W]) == 1)

# 3. Return is what was requested
DesiredReturn = 0.05
o.add(Ret == DesiredReturn)

# 4. Only a maximum of 3 out of the 5 assets are used
o.add(Sum([If(w == 0, 0, 1) for w in W]) <= 3)

# Want to minimize volatility
o.minimize(Vol)

r = o.check()
if r == sat:
    m = o.model()
    prec = 10
    for w in W:
         print("%s    = %s" % (w, m[w].as_decimal(prec)))
    print("Return     = %s" % m.evaluate(Ret).as_decimal(prec))
    print("Volatility = %s" % m.evaluate(Vol).as_decimal(prec))
else:
    print("solver said: %s" % r)

当 运行 时,打印:

Asset_1    = 0
Asset_2    = 0.803
Asset_3    = 0
Asset_4    = 0.197
Asset_5    = 0
Return     = 0.05
Volatility = 0.478205076