如何在资产数量受限的情况下进行投资组合优化?
How to do Portfolio Optimization with constraints on the number of assets?
我正在尝试根据四个约束将某个 return 的波动性降至最低。
- 每个资产的权重都在 0 到 1 之间
- 所有权重之和等于1
- 由资产组成的投资组合的return等于给定的return
- 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
我正在尝试根据四个约束将某个 return 的波动性降至最低。
- 每个资产的权重都在 0 到 1 之间
- 所有权重之和等于1
- 由资产组成的投资组合的return等于给定的return
- 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