SciPy "Successfully" 找到不正确的最佳解决方案和 "Unsuccessfully" 找到最佳解决方案(投资组合构建)

SciPy "Successfully" finding incorrect optimal solution and "Unsuccessfully" finding optimal solution (Portfolio Construction)

我正在构建一个交易机器人,我正在尝试实施一个优化器以在遵守某些约束条件的同时最大化 alpha。

我的变量是一个包含投资组合中证券权重的向量。 我还有一个向量,其中包含每种证券各自的 alpha 分数。 我的 objective 函数是 -sum(weights*alphas) (以最小化负 alpha)。 我的限制是:

我在下面创建了一个 class,它使用 scipy.optimize.minimize:

实现了这个
class Optimiser:

    def __init__(self, initial_portfolio, turnover, min_trade, max_wt, longshort=True):
        self.symbols = initial_portfolio.index.to_numpy()
        self.init_wt = initial_portfolio['weight'].to_numpy()
        self.alpha = initial_portfolio['alpha'].to_numpy()
        self.longshort = longshort
        self.turnover = turnover
        self.min_trade = self.init_wt.copy()
        self.set_min_trade(min_trade)
        self.max_wt = max_wt
        if self.longshort:
            self.wt_sum = 0
            self.abs_wt_sum = 1
        else:
            self.wt_sum = 1
            self.abs_wt_sum = 1

    def set_min_trade(self, min_trade):
        for i in range(len(self.init_wt)):
            if abs(self.init_wt[i]) > min_trade:
                self.min_trade[i] = 0.1

    def optimise(self):
        wt_bounds = self.get_stock_wt_bounds()
        constraints = self.get_constraints()
        result = minimize(
            fun=self.minimise_negative_alpha,
            x0=self.init_wt,
            bounds=wt_bounds,
            constraints=constraints,
            options={
                'disp': True,
            }
        )
        return result

    def minimise_negative_alpha(self, opt_wt):
        return -sum(opt_wt * self.alpha)

    def get_stock_wt_bounds(self):
        if self.longshort:
            return tuple((-self.max_wt, self.max_wt) for s in self.init_wt)
        else:
            return tuple((0, self.max_wt) for i in range(len(self.init_wt)))

    def get_constraints(self):
        min_trade = {'type': 'ineq', 'fun': self.min_trade_fn}
        turnover = {'type': 'ineq', 'fun': self.turnover_fn}
        wt_sum = {'type': 'eq', 'fun': self.wt_sum_fn}
        abs_wt_sum = {'type': 'eq', 'fun': self.abs_wt_sum_fn}
        return turnover, wt_sum, abs_wt_sum

    def min_trade_fn(self, opt_wt):
        return self.min_trade - abs(opt_wt - self.init_wt)

    def turnover_fn(self, opt_wt):
        return sum(abs(opt_wt - self.init_wt)) - self.turnover*2

    def wt_sum_fn(self, opt_wt):
        return sum(opt_wt)

    def abs_wt_sum_fn(self, opt_wt):
        return sum(abs(opt_wt)) - self.abs_wt_sum

如您所见,我没有使用 min_trade 约束,我将在稍后的问题中谈到这一点。

这是我传递给它的两个例子(这些例子只包含 4 只股票,在正确的实施中我希望传递 50-100 支证券的数组):

一)

def run_optimisation():
    initial_portfolio = pd.DataFrame({
        'symbol': ['AAPL', 'MSFT', 'GOOGL', 'TSLA'],
        'weight': [-0.3, -0.2, 0.45, 0.05],
        'alpha': [-0.2, -0.3, 0.25, 0],
    }).set_index('symbol')

    opt = Optimiser(initial_portfolio, turnover=0.3, min_trade=0.1, max_wt=0.4)
    result = opt.optimise()

b)

def run_optimisation():
    initial_portfolio = pd.DataFrame({
        'symbol': ['AAPL', 'MSFT', 'GOOGL', 'TSLA'],
        'weight': [-0.25, -0.25, 0.25, 0.25],
        'alpha': [-0.2, -0.3, 0.25, 0],
    }).set_index('symbol')

    opt = Optimiser(initial_portfolio, turnover=0.3, min_trade=0.1, max_wt=0.4)
    result = opt.optimise()

对于这个多空例子,我从 a) 得到的结果是:[-0.1, -0.4, 0.25, 0.25] 这显然不是最优的 [-0.1, -0.4, 0.4, 0.1]。

我收到这条消息:

Optimization terminated successfully.    (Exit mode 0)
            Current function value: -0.20249999999999585
            Iterations: 7
            Function evaluations: 42
            Gradient evaluations: 7

它说它成功地找到了最小值......就像它试图最大化周转限制一样。这是因为初始权重不符合约束条件吗?如果是这样,我该如何修改它,理想情况下我想将投资组合的当前权重作为 x0 传递给它。

在 b) 中,我得到了最优解 [-0.1, -0.4, 0.4, 0.1] 但我得到的是 result.success.

的错误

我也收到这条消息:

Positive directional derivative for linesearch    (Exit mode 8)
            Current function value: -0.23999999999776675
            Iterations: 8
            Function evaluations: 34
            Gradient evaluations: 4

我认为这条消息可能意味着它无法 increase/decrease 和 objective 功能有很大的变化,因此它不知道它是否处于最低限度,如果我是,请纠正我错误的。尽管我不太确定如何最佳地设置它,但我试过乱用 ftol 设置但无济于事。

有没有办法修改这个优化器,以便 a) 它实现最优解并相应地产生正确的状态 b) 可以采用不符合约束的初始权重?未来希望也包括部门和行业的约束,让我不能在某些领域过度投资。

另外,作为一个附带问题(虽然没有我想让它开始工作那么重要):我如何实施最小交易限制?我希望它要么根本不交易股票,要么它的交易价值超过这个数量,或者交易掉它的全部价值(如果它小于投资组合中的 min_trade 权重,则权重为零)。

如您所见,这是一个很长的问题,但我非常感谢您为这个问题提供的任何帮助、指导或答案!请要求任何澄清或额外信息,因为这花了我很长时间才拼凑起来,我可能没有很好地解释或遗漏某些东西。谢谢!

根据 Sascha 上面的评论,我想 post 在 cvxpy 中正确实施此问题:

import cvxpy as cv


class Optimiser:

    def __init__(self, initial_portfolio, turnover, max_wt, longshort=True):
        self.symbols = initial_portfolio.index.to_numpy()
        self.init_wt = initial_portfolio['weight'].to_numpy()
        self.opt_wt = cv.Variable(self.init_wt.shape)
        self.alpha = initial_portfolio['alpha'].to_numpy()
        self.longshort = longshort
        self.turnover = turnover
        self.max_wt = max_wt
        if self.longshort:
            self.min_wt = -self.max_wt
            self.net_exposure = 0
            self.gross_exposure = 1
        else:
            self.min_wt = 0
            self.net_exposure = 1
            self.gross_exposure = 1

    def optimise(self):
        constraints = self.get_constraints()
        optimisation = cv.Problem(cv.Maximize(cv.sum(self.opt_wt*self.alpha)), constraints)
        optimisation.solve()
        if optimisation.status == 'optimal':
            print('Optimal solution found')
        else:
            print('Optimal solution not found')
        return optimisation.solution.primal_vars

    def get_constraints(self):
        min_wt = self.opt_wt >= self.min_wt
        max_wt = self.opt_wt <= self.max_wt
        turnover = cv.sum(cv.abs(self.opt_wt-self.init_wt)) <= self.turnover*2
        net_exposure = cv.sum(self.opt_wt) == self.net_exposure
        gross_exposure = cv.sum(cv.abs(self.opt_wt)) <= self.gross_exposure
        return [min_wt, max_wt, turnover, net_exposure, gross_exposure]

非常感谢 Sascha 的帮助和指导。