SciPy "Successfully" 找到不正确的最佳解决方案和 "Unsuccessfully" 找到最佳解决方案(投资组合构建)
SciPy "Successfully" finding incorrect optimal solution and "Unsuccessfully" finding optimal solution (Portfolio Construction)
我正在构建一个交易机器人,我正在尝试实施一个优化器以在遵守某些约束条件的同时最大化 alpha。
我的变量是一个包含投资组合中证券权重的向量。
我还有一个向量,其中包含每种证券各自的 alpha 分数。
我的 objective 函数是 -sum(weights*alphas) (以最小化负 alpha)。
我的限制是:
- 投资组合中股票的最小和最大权重
- 最低交易额(占投资组合总价值的百分比 - 交易成本是固定的,所以如果只是几个基点的变化我不想交易)
- 最大营业额(最大总交易价值占投资组合总价值的百分比)
- 绝对权重之和必须为1
- 权重总和必须等于 0(对于 long/short)或 1(仅对于多头)
我在下面创建了一个 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 的帮助和指导。
我正在构建一个交易机器人,我正在尝试实施一个优化器以在遵守某些约束条件的同时最大化 alpha。
我的变量是一个包含投资组合中证券权重的向量。 我还有一个向量,其中包含每种证券各自的 alpha 分数。 我的 objective 函数是 -sum(weights*alphas) (以最小化负 alpha)。 我的限制是:
- 投资组合中股票的最小和最大权重
- 最低交易额(占投资组合总价值的百分比 - 交易成本是固定的,所以如果只是几个基点的变化我不想交易)
- 最大营业额(最大总交易价值占投资组合总价值的百分比)
- 绝对权重之和必须为1
- 权重总和必须等于 0(对于 long/short)或 1(仅对于多头)
我在下面创建了一个 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 的帮助和指导。