0

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

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

  • 投资组合中股票的最小和最大权重
  • 最低交易量(占投资组合总价值的百分比 - 交易成本是固定的,所以如果只是几个基点变化,我不想交易)
  • 最大营业额(最大总交易价值占投资组合总价值的百分比)
  • 绝对权重之和必须为 1
  • 权重之和必须等于 0(长/短)或 1(仅长)

我在下面创建了一个类,它使用 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 为 False。

我也收到这条消息:

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

我认为这条消息可能意味着它无法随着变化而增加/减少目标函数,因此它不知道它是否至少,如果我错了,请纠正我。尽管我不太确定如何以最佳方式设置它,但我尝试过使用 ftol 设置无济于事。

有没有办法修改这个优化器,以便 a) 它达到最佳解决方案并相应地产生正确的状态,并且 b) 可以采用不符合约束的初始权重?未来的希望是还包括部门和行业限制,这样我就不会在某些领域过度投资。

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

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

4

1 回答 1

1

继上面 Sascha 的评论之后,我想在以下位置发布此问题的正确实现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的帮助和指导。

于 2020-04-12T16:40:36.037 回答