Scipy极小化函数的约束问题

hfsqlsce  于 2023-08-05  发布在  其他
关注(0)|答案(1)|浏览(122)

我的目标是优化40只股票的投资组合中的权重,回报和风险都在一个40行2列的df数据框架中。
对于这个优化,我使用了scipy的minimize函数。
资产的权重不能大于0.1(10%)(界限)。还有两个限制(缺点):

  • 第1个约束条件:权重和等于1
  • 第二个约束:权重大于0.05的资产的权重总和不得超过0.4(即投资组合总权重的40%)

我的问题是在第二个约束上,我试图找到一种方法来将其表示为不等式,但没有成功:
第一个月
这个不等式对我来说很好,但当我优化时它不起作用。大于或等于0.05(5%)的权重之和始终大于0.4(40%)

import pandas as pd
import numpy as np
from scipy.optimize import minimize, Bounds, LinearConstraint

# Set the number of rows in the DataFrame (here we put 40)
n = 40

# Generate 40 random codes as a list
codes = [''.join(np.random.choice(list('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 2)) + str(np.random.randint(10, 100)) for _ in range(n)]

# Generate 40 random returns between 0.05 and 0.2
returns = np.random.uniform(0.05, 0.2, n)

# Generate 40 random risks between 0.15 and 0.39
risks = np.random.uniform(0.15, 0.39, n)

# Create the DataFrame using a dictionary
data = {"Code_title": codes, "Return": returns, "Risk": risks}
df = pd.DataFrame(data)

# Set parameters
guess = np.full((df.shape[0],1), 1.0/df.shape[0]) # initial weights for iterations
bounds = tuple((0,0.1) for x in range(df.shape[0]))  # set assets weight between 0 and 0.1 
cons = (# sum of weights = 1 
    {"type":"eq", "fun": lambda x: np.sum(x)-1},
    **#5 10 constraints
    {"type": "ineq", "fun": lambda x: -np.sum(x[x>=0.05]) + 0.4})**

def function_obj(W) :

        #objective function extended maximum sharpe ration model
        W = np.array(W)
        fund_return = np.array(df['Return']) @ W # portfolio return
        fund_risk = np.array(df['Risk']) @ W # portfolio risk 

        return fund_return/fund_risk # objective function

result = minimize(function_obj, guess,  method = "SLSQP", bounds=bounds, constraints=cons)
np.set_printoptions(suppress=True)
result['x']

np.sum(result['x'][result['x']>=0.05]) # calculate the sum of weights greater than 0.05

字符串

m2xkgtsf

m2xkgtsf1#

正如我在评论中提到的,第一种方法是用线性目标代替你的目标。有几种可能的形式,每一种看起来都像是某个常数向量与决策权重的点积。这个常数向量可以是按元素(而不是按总和)的风险收益比,也可以是风险和收益的(潜在加权)差。
构造一个混合整数线性规划:

import string
import pandas as pd
import numpy as np
from numpy.random import default_rng
from scipy.optimize import Bounds, LinearConstraint, milp

n = 40
rand = default_rng(seed=0)

df = pd.DataFrame({
    'Code_title': [  # FM85, etc.
        ''.join((*rand.choice(tuple(string.ascii_uppercase), 2), str(x)))
        for x in rand.integers(10, 100, size=n)
    ],
    'Return': rand.uniform(0.05, 0.2, n),
    'Risk': rand.uniform(0.15, 0.39, n),
})

# or df.Risk / df.Return (though this is element-wise and not a sum)
# or df.Risk * risk_coefficient + df.Return * return_coefficient
risk_return = df.Risk - df.Return

# Decision variables: n low-weights, n high-weights, n high-weight assignments
c = np.hstack((
    # Minimize risk, maximize return
    risk_return, risk_return,
    # High-weight assignments are not optimized
    np.zeros(n),
))

# Low-weights are continuous, high-weights are semicontinuous, assignments are integral
integrality = np.hstack((
    np.zeros(n), np.full(n, 2), np.ones(n),
))

bounds = Bounds(
    lb=np.hstack((
        np.zeros(n),
        np.full(n, 0.05 + 1e-6),
        np.zeros(n),
    )),
    ub=np.hstack((
        np.full(n, 0.05),
        np.full(n, 0.10),
        np.ones(n),
    )),
)

# sum of weights is equal to 1
weight_sum = LinearConstraint(
    A=np.hstack((np.ones(2*n), np.zeros(n))), lb=1, ub=1,
)
# if a weight is low and nonzero, it cannot be assigned high
low_asn = LinearConstraint(  # loweight + highasn <= 1
    A=np.hstack((np.eye(n), np.zeros((n, n)), np.eye(n))), lb=0, ub=1,
)
# if a weight is high, it must be assigned high
high_asn_on = LinearConstraint(  # highweight - highasn * 0.05 >= 0
    A=np.hstack((np.zeros((n, n)), np.eye(n), -0.05*np.eye(n))), lb=0, ub=np.inf,
)
high_asn_off = LinearConstraint(  # -highweight + highasn >= 0
    A=np.hstack((np.zeros((n, n)), -np.eye(n), np.eye(n))), lb=0, ub=np.inf,
)
# sum of weights of assets with a weight greater than 0.05 must not exceed 0.4
high_sum = LinearConstraint(
    A=np.hstack((np.zeros(n), np.ones(n), np.zeros(n))), lb=0, ub=0.4,
)

result = milp(
    c=c, integrality=integrality, bounds=bounds,
    constraints=(weight_sum, low_asn, high_asn_on, high_asn_off, high_sum),
)
assert result.success, result.message

df['low_weight'], df['high_weight'], df['high_assigned'] = np.split(result.x, (n, 2*n))
nonzero_weights = df[df.iloc[:, -3:].any(axis=1)]
print(nonzero_weights)

个字符
这将 * 不 * 等同于你最初写的目标,但如果你没有一些严格的理论需要,那么也许这是可以的。
解决原始非线性目标的其他选项(软S形约束、差分进化MINLP)将在计算上更加复杂。

相关问题