【Python量化】 Scipy库求解最优资产投资组合


此文章首发于公众号:Python for Finance

链接:https://mp.weixin.qq.com/s/6wp8-AOxF-K_xni9eEsAsQ

问题导入

已知无风险利率为3%,下表包括六家公司的年度回报(文件),使用Python编制程序,求解最优投资组合。

YearBMWORACLEYOIGOSACYRBALAYAPPLE
20111.48660.05620.9641-1.23670.05120.5075
20120.29020.43560.21114.03320.24910.4014
20130.19110.88310.37460.38130.1815-1.6639
20140.12880.3643-0.25250.06660.25660.1564
2015-0.08500.14600.42360.41140.47180.2199
20160.10680.08360.1333-0.92410.3121-0.0216
2017-0.0040-0.6284-0.30533.4962-0.0368-0.0636
20180.07260.4273-0.24700.48700.1204-0.2773
2019-0.2030-0.21960.14040.0862-0.3052-0.2985
2020-0.04370.0681-0.0756-1.1594-0.15013.0549

首先我们导入相关库。

import pandas as pd
import numpy as np
import scipy.optimize as sco

一、读取excel文件,获取收益率数据

data = pd.read_excel(r'C:\Users\mi\Desktop\收益率.xlsx',index_col=0)
>>>data
          BMW  ORACLE   YOIGO   SACYR   BALAY   APPLE
Year                                                
2011   1.4866  0.0562  0.9641 -1.2367  0.0512  0.5075
2012   0.2902  0.4356  0.2111  4.0332  0.2491  0.4014
2013   0.1911  0.8831  0.3746  0.3813  0.1815 -1.6639
2014   0.1288  0.3643 -0.2525  0.0666  0.2566  0.1564
2015  -0.0850  0.1460  0.4236  0.4114  0.4718  0.2199
2016   0.1068  0.0836  0.1333 -0.9241  0.3121 -0.0216
2017  -0.0040 -0.6284 -0.3053  3.4962 -0.0368 -0.0636
2018   0.0726  0.4273 -0.2470  0.4870  0.1204 -0.2773
2019  -0.2030 -0.2196  0.1404  0.0862 -0.3052 -0.2985
2020  -0.0437  0.0681 -0.0756 -1.1594 -0.1501  3.0549

二、给定权重,求组合收益率、标准差、夏普比率

# 定义无风险收益率
rf = 0.03
# 获取股票平均收益率
returns = data.mean() 
# 获取股票收益率的方差协方差矩阵
cov = data.cov(bias=True) 
# 定义资产数量
number_assets = 6

# 给定权重,求组合收益率、标准差、夏普比率
def statistics(weights):        
    weights = np.array(weights)
    pret = np.dot(weights, returns) #获取组合收益率
    pvol = np.sqrt(np.dot(weights.T, np.dot(cov, weights))) #获取组合标准差
    psharpe = (pret - rf) / pvol #获取组合夏普比率
    return np.array([pret, pvol, psharpe])

三、规划求解

规划求解需要用到scipy.optimize的minimize函数。函数用法如下所示。

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

主要参数如下表所示:

参数说明
fun目标函数
x0初始迭代值
args要输入到目标函数中的参数
method求解的算法,目前可选的有Nelder-Mead,Powell,CG,BFGS,Newton-CG,L-BFGS-B,TNC,COBYLA,SLSQP,dogleg,trust-ncg等。一般求极值多用 'SLSQP’算法
bounds可选项,变量的边界(仅适用于L-BFGS-B,TNC和SLSQP)。以(min,max)对的形式定义 x 中每个元素的边界。如果某个参数在 min 或者 max 的一个方向上没有边界,则用 None 标识。如(None, max)
constraints约束条件(只对 COBYLA 和 SLSQP)。dict 类型。type : str, ‘eq’ 表示等于0,‘ineq’ 表示不小于0 ;fun : 定义约束的目标函数。

(一)求解夏普比率最高的投资组合,并且单个资产权重均不超过20%

目标函数fun:夏普比率最大化

约束条件constraints:权重之和=1

变量边界bounds:单个权重不超过0.2

代码如下:

# 设置目标:夏普比率最大
def minus_sharpe(x):
    return -statistics(x)[2]

# 约束条件分为eq和ineq
# eq表示函数结果等于0 ;ineq 表示 表达式大于等于0  
# 约束条件:权重之和=1
def constraint1(x):
    return np.sum(x) -1

con1 = {'type': 'eq', 'fun': constraint1} 

# 定义边界约束(优化变量的上下限)
bnds = tuple((None, 0.2) for x in range(number_assets)) #每个资产权重取值范围为0-0.2

# 设置初始权重
x0 = number_assets * [1 / number_assets]

# 规划求解
solution = sco.minimize(minus_sharpe,x0,method='SLSQP', bounds=bnds, constraints=con1)

# 求解出权重、组合收益率、标准差、夏普比率
final_weights = solution.x #权重
finanl_ratio = statistics(final_weights) #组合收益率、标准差、夏普比率

print(f'{final_weights[0]:.1%}投资于{data.columns[0]}{final_weights[1]:.1%}投资于{data.columns[1]},'
     f'{final_weights[2]:.1%}投资于{data.columns[2]}{final_weights[3]:.1%}投资于{data.columns[3]},'
     f'{final_weights[4]:.1%}投资于{data.columns[4]}{final_weights[5]:.1%}投资于{data.columns[5]},'
     f'组合收益率:{finanl_ratio[0]:.1%},组合标准差:{finanl_ratio[1]:.1%},组合夏普比率:{finanl_ratio[2]:.1%}')

结果如下:

18.5%投资于BMW,20.0%投资于ORACLE,20.0%投资于YOIGO,9.7%投资于SACYR,20.0%投资于BALAY,11.7%投资于APPLE,组合收益率:19.7%,组合标准差:22.3%,组合夏普比率:75.0%

(二)求解方差最小的投资组合

目标函数fun:方差最小化,即标准差最小化

约束条件constraints:权重之和=1

变量边界bounds:无

代码如下:

# 设置目标:方差最小
def min_std(x):
    return statistics(x)[1]

# 约束条件:权重之和=1
def constraint1(x):
    return np.sum(x) -1

con1 = {'type': 'eq', 'fun': constraint1} 

# 设置初始权重
x0 = number_assets * [1 / number_assets]

# 规划求解
solution = sco.minimize(min_std,x0,method='SLSQP',constraints=con1)

# 求解出权重、组合收益率、标准差、夏普比率
final_weights = solution.x #权重
finanl_ratio = statistics(final_weights) #组合收益率、标准差、夏普比率

print(f'{final_weights[0]:.1%}投资于{data.columns[0]}{final_weights[1]:.1%}投资于{data.columns[1]},'
     f'{final_weights[2]:.1%}投资于{data.columns[2]}{final_weights[3]:.1%}投资于{data.columns[3]},'
     f'{final_weights[4]:.1%}投资于{data.columns[4]}{final_weights[5]:.1%}投资于{data.columns[5]},'
     f'组合收益率:{finanl_ratio[0]:.1%},组合标准差:{finanl_ratio[1]:.1%},组合夏普比率:{finanl_ratio[2]:.1%}')

结果如下:

3.6%投资于BMW,10.1%投资于ORACLE,18.5%投资于YOIGO,3.4%投资于SACYR,56.0%投资于BALAY,8.4%投资于APPLE,组合收益率:14.9%,组合标准差:18.1%,组合夏普比率:65.9%

(三)求解方差最小的投资组合,组合收益率为30%

目标函数fun:方差最小化,即标准差最小化

约束条件constraints:1、权重之和=1;2、组合收益率=0.3

代码如下:

#设置目标:方差最小
def min_std(x):
    return statistics(x)[1]

# 约束条件1:权重之和=1
def constraint1(x):
    return np.sum(x) -1
# 约束条件2:组合收益=0.3
def constraint2(x):
    return statistics(x)[0]-0.3

con1 = {'type': 'eq', 'fun': constraint1} 
con2 = {'type': 'eq', 'fun': constraint2} 
cons = (con1,con2)

# 设置初始权重
x0 = number_assets * [1 / number_assets]

# 规划求解
solution = sco.minimize(min_std,x0,method='SLSQP',constraints=cons)

# 求解出权重、组合收益率、标准差、夏普比率
final_weights = solution.x #权重
finanl_ratio = statistics(final_weights) #组合收益率、标准差、夏普比率

print(f'{final_weights[0]:.1%}投资于{data.columns[0]}{final_weights[1]:.1%}投资于{data.columns[1]},'
     f'{final_weights[2]:.1%}投资于{data.columns[2]}{final_weights[3]:.1%}投资于{data.columns[3]},'
     f'{final_weights[4]:.1%}投资于{data.columns[4]}{final_weights[5]:.1%}投资于{data.columns[5]},'
     f'组合收益率:{finanl_ratio[0]:.1%},组合标准差:{finanl_ratio[1]:.1%},组合夏普比率:{finanl_ratio[2]:.1%}')

结果如下:

47.1%投资于BMW,66.5%投资于ORACLE,2.8%投资于YOIGO,22.3%投资于SACYR,-57.0%投资于BALAY,18.3%投资于APPLE,组合收益率:30.0%,组合标准差:42.9%,组合夏普比率:63.0%

(四)求解收益率最高的投资组合,组合标准差为40%。

目标函数fun:组合收益率最大化

约束条件constraints:1、权重之和=1;2、组合标准差=0.4

代码如下:

# 设置目标:收益率最大化
def minus_return(x):
    return -statistics(x)[0]

# 约束条件1:权重之和=1
def constraint1(x):
    return np.sum(x) -1
# 约束条件2:组合标准差=0.4
def constraint2(x):
    return statistics(x)[1]-0.4

con1 = {'type': 'eq', 'fun': constraint1} 
con2 = {'type': 'eq', 'fun': constraint2} 
cons = (con1,con2)

# 设置初始权重
x0 = number_assets * [1 / number_assets]

# 规划求解
solution = sco.minimize(minus_return,x0,method='SLSQP',constraints=cons)

# 求解出权重、组合收益率、标准差、夏普比率
final_weights = solution.x #权重
finanl_ratio = statistics(final_weights) #组合收益率、标准差、夏普比率

print(f'{final_weights[0]:.1%}投资于{data.columns[0]}{final_weights[1]:.1%}投资于{data.columns[1]},'
     f'{final_weights[2]:.1%}投资于{data.columns[2]}{final_weights[3]:.1%}投资于{data.columns[3]},'
     f'{final_weights[4]:.1%}投资于{data.columns[4]}{final_weights[5]:.1%}投资于{data.columns[5]},'
     f'组合收益率:{finanl_ratio[0]:.1%},组合标准差:{finanl_ratio[1]:.1%},组合夏普比率:{finanl_ratio[2]:.1%}')

结果如下:

43.5%投资于BMW,61.8%投资于ORACLE,4.1%投资于YOIGO,20.8%投资于SACYR,-47.7%投资于BALAY,17.5%投资于APPLE,组合收益率:28.8%,组合标准差:40.0%,组合夏普比率:64.4%
  • 9
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值