文章目录

  • 一、读取excel文件,获取收益率数据
  • 二、给定权重,求组合收益率、标准差、夏普比率
  • 三、规划求解
  • (一)求解夏普比率最高的投资组合,并且单个资产权重均不超过20%
  • (二)求解方差最小的投资组合
  • (三)求解方差最小的投资组合,组合收益率为30%
  • (四)求解收益率最高的投资组合,组合标准差为40%。


问题导入

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

Year

BMW

ORACLE

YOIGO

SACYR

BALAY

APPLE

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

首先我们导入相关库。

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%