【金融、量化系列】优化问题在构建投资组合时的应用(利用scipy.optimize.minimize构建满足条件的最优投资组合)

发布时间:2024-11-08 18:01

        在进行资产配置时,假如我们已经通过一系列的操作选定了5只股票进入我们的股票池,下一步应该就是要确定每只股票各要购买多少,即确定最终购买股票的权重,这就出现了一个最优化问题。通常在这个问题背景下,我们会提出一些诸如最大化收益、最小化标准差或方差的优化目标,并且常常伴随着一些优化约束,例如股票不允许卖空,某只股票的最高持股比例不超过50%,组合的波动率不超过组合内股票的波动率平均值等。

        例如诺贝尔经济学奖得主马科维茨(Markowitz)提出的投资组合理论被广泛用于组合选择和资产配置中,该理论中的均值-方差分析法和有效边界模型可用于寻找最优的投资组合,这是一个具体的最优化应用,并且同时考虑了风险(方差)和收益(收益率)。通常,我们会使用蒙特卡洛方法来找出有效边界,进而进行下一步的最优权重选择。(关于使用蒙特卡洛方法研究最优投资组合的内容将在后续博客中更新。)

        本文就更一般化的最优化方法寻找最佳股票配置权重进行介绍,主要使用到scipy.optimize.minimize工具。

         

目录

1 背景介绍

2  scipy.optimize.minimize介绍

2.1 官方文档链接

2.2 参数

2.3 无约束求极值、有约束求极值

 3 用挑选出来的5只股票组成投资组合P,计算每只股票的权重W,使得投资组合收益率实现最大化,同时要求整个投资组合的波动率不超过这5只股票波动率的平均值。注意,这里每只股票均不允许卖空。


1 背景介绍

        本篇博客为【金融、量化系列】计算股票历史期望收益率(年化)、收益率标准差(年化)、夏普比率、以及股票之间月收益率的相关系数,并以夏普比率、相关系数为条件筛选股票_学金融的程序员懒羊羊的博客-CSDN博客

的后续,接续上篇博客的内容 ,我们已经通过 A)过去3年的夏普比率大于0.2; B)过去1年的夏普比率大于0.1; C)这5只股票彼此之间的相关系数之和最小,这三个条件的筛选得到了一个含有5只股票的股票池,接下来就是根据一些约束条件以及最大化历史收益率的目标进行权重的选择。

具体做法是:

        用挑选出来的5只股票组成投资组合P,计算每只股票的权重W,使得投资组合收益率实现最大化,同时要求整个投资组合的波动率不超过这5只股票波动率的平均值。注意,这里每只股票均不允许卖空

        在上一篇博客中我们得到的最终股票组合是:

最终组合为: [\'600436\', \'600745\', \'601088\', \'601633\', \'601899\'] 相关系数之和为: 1.3162840831751845

2  scipy.optimize.minimize介绍

2.1 官方文档链接

scipy.optimize.minimize — SciPy v1.8.1 Manual

scipy.optimize.minimize(fun, x0, args=(), method=None, jac=None, hess=None, hessp=None, bounds=None, constraints=(), tol=None, callback=None, options=None)

2.2 参数

fun:自定义的损失函数(优化目标函数),目标是函数是最小化。该参数就是costFunction你要去最小化的损失函数,将costFunction的名字传给fun。

fun(x, *args) -> float

x0:初始化的theta,其shape必须为shape(n,)即一维数组. 

method:str or callable, optional

选择要使用的最优化方法。(一般求极值多用 \'SLSQP\'算法)

  • ‘Nelder-Mead’ (see here)

  • ‘Powell’ (see here)

  • ‘CG’ (see here)

  • ‘BFGS’ (see here)

  • ‘Newton-CG’ (see here)

  • ‘L-BFGS-B’ (see here)

  • ‘TNC’ (see here)

  • ‘COBYLA’ (see here)

  • ‘SLSQP’ (see here)

  • ‘trust-constr’(see here)

  • ‘dogleg’ (see here)

  • ‘trust-ncg’ (see here)

  • ‘trust-exact’ (see here)

  • ‘trust-krylov’ (see here)

  • custom - a callable object

如果未给出,则选择BFGS、L-BFGS-B、SLSQP中的一个,这取决于问题是否具有约束或边界。

jac:{callable, ‘2-point’, ‘3-point’, ‘cs’, bool}, optional

该参数就是计算梯度的函数。仅适用于CG、BFGS、Newton CG、L-BFGS-B、TNC、SLSQP、dogleg、trust ncg、trust krylov、trust exact和trust constr。如果它是可调用的,则应该是返回梯度向量的函数:

jac(x, *args) -> array_like, shape (n,)

hess:{callable, ‘2-point’, ‘3-point’, ‘cs’, HessianUpdateStrategy}, optional

计算Hessian矩阵的方法。仅适用于Newton CG、dogleg、trust ncg、trust krylov、trust exact和trust Const。如果是callable,则应返回Hessian矩阵:

hess(x, *args) -> {LinearOperator, spmatrix, array}, (n, n)

hessp:callable, optional

Hessian of objective function times an arbitrary vector p. Only for Newton-CG, trust-ncg, trust-krylov, trust-constr. Only one of hessp or hess needs to be given. If hess is provided, then hessp will be ignored. hessp must compute the Hessian times an arbitrary vector:

hessp(x, p, *args) ->  ndarray shape (n,)

where x is a (n,) ndarray, p is an arbitrary vector with dimension (n,) and args is a tuple with the fixed parameters.

bounds:sequence or Bounds, optional

Bounds on variables for Nelder-Mead, L-BFGS-B, TNC, SLSQP, Powell, and trust-constr methods. There are two ways to specify the bounds:

  1. Instance of Bounds class.

  2. Sequence of (min, max) pairs for each element in x. None is used to specify no bound.

constraints:{Constraint, dict} or List of {Constraint, dict}, optional

Constraints definition. Only for COBYLA, SLSQP and trust-constr.

Constraints for ‘trust-constr’ are defined as a single object or a list of objects specifying constraints to the optimization problem. Available constraints are:

  • LinearConstraint

  • NonlinearConstraint

tol:float, optional

差异容忍度。

options:dict, optional.用来控制最大的迭代次数,以字典的形式来进行设置,例如:options={‘maxiter’:400}

callback:callable, optional

Called after each iteration.

2.3 无约束求极值、有约束求极值

无约束求极值:例子1:计算 1/x+x 的最小值

from scipy.optimize import minimize
import numpy as np
def fun(args):
    a=args
    v=lambda x:a/x[0] +x[0]
    return v
args = (1) #a
x0 = np.asarray((2)) # 初始猜测值
res = minimize(fun(args), x0, method=\'SLSQP\')
print(res.fun, res.success, res.x)

输出:

2.0000000815356342 True [1.00028559]

有约束求极值:例子2:计算 (2+x1)/(1+x2) - 3x1+4x3 的最小值 ,约束条件是x1,x2,x3 都处于[0.1, 0.9] 区间内

def fun(args):
    a,b,c,d = args
    v = lambda x: (a+x[0])/(b+x[1]) -c*x[0]+d*x[2]
    return v

def con(args):
    # 约束条件 分为eq 和ineq
    # eq表示 函数结果等于0 ; ineq 表示 表达式大于等于0
    x1min, x1max, x2min, x2max, x3min, x3max = args
    cons = ({\'type\': \'ineq\', \'fun\': lambda x: x[0] - x1min},\\
    {\'type\': \'ineq\', \'fun\': lambda x: -x[0] + x1max},\\
    {\'type\': \'ineq\', \'fun\': lambda x: x[1] - x2min},\\
    {\'type\': \'ineq\', \'fun\': lambda x: -x[1] + x2max},\\
    {\'type\': \'ineq\', \'fun\': lambda x: x[2] - x3min},\\
    {\'type\': \'ineq\', \'fun\': lambda x: -x[2] + x3max})
    return cons

# 定义常量值
args = (2,1,3,4) # a,b,c,d
# 设置参数范围/约束条件
args1 = (0.1,0.9,0.1, 0.9,0.1,0.9) # x1min, x1max, x2min, x2max
cons = con(args1)
# 设置初始猜测值
x0 = np.asarray((0.5,0.5,0.5))
res = minimize(fun(args), x0, method=\'SLSQP\',constraints=cons)
print(res.fun,res.success,res.x)

输出:

-0.773684210526435 True [0.9 0.9 0.1]

 3 用挑选出来的5只股票组成投资组合P,计算每只股票的权重W,使得投资组合收益率实现最大化,同时要求整个投资组合的波动率不超过这5只股票波动率的平均值。注意,这里每只股票均不允许卖空。

数据获取:

import pandas as pd
import numpy as np
import tushare as ts
import matplotlib.pyplot as plt
from scipy.optimize import minimize
from itertools  import *
from scipy import interpolate
#下面是显示中文必备代码
from pylab import mpl
mpl.rcParams[\'font.sans-serif\']=[\'SimHei\']
mpl.rcParams[\'axes.unicode_minus\']=False

mingroup = [\'600436\', \'600745\', \'601088\', \'601633\', \'601899\']

#获取五只股票(mingroup)三年的收益率
mingroup_quotations_dic = {}
for i in mingroup:
    data_temp = ak.stock_zh_a_hist(symbol=i, period=\'monthly\', start_date=\"20190101\", end_date=\'20211231\', adjust=\"hfq\")
    mingroup_quotations_dic[i] = (data_temp[\'收盘\'][35]-data_temp[\'开盘\'][0])/data_temp[\'开盘\'][0]
print(\"五只股票三年的收益率为\",mingroup_quotations_dic)
#获取五只股票(mingroup)三年的波动率均值
volatility = []
for code in mingroup:
    returns = ak.stock_zh_a_hist(symbol=code, period=\'monthly\', start_date=\"20190101\", end_date=\'20211231\', adjust=\"hfq\")[\'涨跌幅\']
    volatility.append(empyrical.annual_volatility(returns, period=\'monthly\', alpha=2.0, annualization=True))
volatility_mean = sum(volatility)/len(volatility)
print(\"五只股票的平均年化波动率为:\",volatility_mean)

 输出:

五只股票三年的收益率为 {\'600436\': 4.012111475004634, \'600745\': 5.726076129491283, \'601088\': 0.3098693759071119, \'601633\': 6.080073630924988, \'601899\': 1.633495145631068}
五只股票的平均年化波动率为: 12.417705698134807

 定义目标函数:

# 定义目标函数
def func(x, sign=-1.0):
    # scipy.minimize默认求最小,求max时只需要sign*(-1),跟下面的args对应
    return sign * (x[0] * mingroup_quotations_dic[mingroup[0]] + x[1] * mingroup_quotations_dic[mingroup[1]] + x[2] * mingroup_quotations_dic[mingroup[2]] + x[3] *mingroup_quotations_dic[mingroup[3]] + x[4] *mingroup_quotations_dic[mingroup[4]])

定义目标函数的梯度:

#定义目标函数的梯度
def func_deriv(x, sign=-1):
    jac_x0 = sign * mingroup_quotations_dic[mingroup[0]]
    jac_x1 = sign * mingroup_quotations_dic[mingroup[1]]
    jac_x2 = sign * mingroup_quotations_dic[mingroup[2]]
    jac_x3 = sign * mingroup_quotations_dic[mingroup[3]]
    jac_x4 = sign * mingroup_quotations_dic[mingroup[4]]
    return np.array([jac_x0, jac_x1, jac_x2 , jac_x3 , jac_x4])
 

定义波动率约束函数:

#定义波动率约束函数
def volatilitycon(x):
    returns = (ak.stock_zh_a_hist(symbol=mingroup[0], period=\'monthly\', start_date=\"20190101\", end_date=\'20211231\', adjust=\"hfq\")[\'涨跌幅\']*x[0] +
    ak.stock_zh_a_hist(symbol=mingroup[1], period=\'monthly\', start_date=\"20190101\", end_date=\'20211231\', adjust=\"hfq\")[\'涨跌幅\']*x[1] +
    ak.stock_zh_a_hist(symbol=mingroup[2], period=\'monthly\', start_date=\"20190101\", end_date=\'20211231\', adjust=\"hfq\")[\'涨跌幅\']*x[2] +
    ak.stock_zh_a_hist(symbol=mingroup[3], period=\'monthly\', start_date=\"20190101\", end_date=\'20211231\', adjust=\"hfq\")[\'涨跌幅\']*x[3] +
    ak.stock_zh_a_hist(symbol=mingroup[4], period=\'monthly\', start_date=\"20190101\", end_date=\'20211231\', adjust=\"hfq\")[\'涨跌幅\']*x[4] )
    return (-empyrical.annual_volatility(returns, period=\'monthly\', alpha=2.0, annualization=True)+volatility_mean)

定义约束条件:

# 定义约束条件
cons = (
    {\'type\': \'eq\',
     \'fun\': lambda x: np.array([x[0] + x[1] + x[2] + x[3] + x[4] -1.0 ]),},
 
    {\'type\': \'ineq\',
     \'fun\': volatilitycon,}
    )

设置不可卖空bound:

#设置不可卖空(均大于零)
bnds = ((0, None), (0, None), (0, None), (0, None), (0, None))

定义初始解集:

# 定义初始解x0
x0 = np.array([0.2,0.2,0.2,0.2,0.2])

使用SLSQP求解:

# 使用SLSQP算法求解
res = minimize(func, x0 , args=(-1,), jac=func_deriv, method=\'SLSQP\', options={\'disp\': True},bounds=bnds,constraints=cons)
# args是传递给目标函数和偏导的参数,arg为1,求min问题,args=-1时是求解max问题
print(res.x)

输出:

Optimization terminated successfully    (Exit mode 0)
            Current function value: -5.884999219286995
            Iterations: 7
            Function evaluations: 8
            Gradient evaluations: 7
[0.02722071 0.39204518 0.         0.58073411 0.    ]

[0.02722071 0.39204518 0. 0.58073411 0. ]为五只股票的权重。

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号