分享

Python量化 | 构建马科维茨有效边界

 伊伊爸 2023-05-24 发布于湖北

本文基于马科维茨有效集理论,利用Python构建一个有效边界。假设有一投资组合,含有5支股票:中国平安(000001)、格力电器(000651)、爱尔眼科(300015)、002156(通富微电)、长安汽车(000625),如何构建投资组合?

Part1获取股票日收盘价数据

本文利用AKShare包获取股价数据。AKShare包是一个免费、开源的 Python 财经数据接口包。AKShare返回的绝大部分的数据格式都是 pandas DataFrame 类型,非常便于用 pandas/ NumPy/ Matplotlib 进行数据分析和可视化。

import pandas as pd
import numpy as np
import akshare as ak

# 读入5支股票 2021-01-01 到 2021-12-31日收盘价数据
def get_close(code):
    data = ak.stock_zh_a_hist(symbol=code, period='daily', start_date='20180101', end_date='20211231', adjust='')
    data.index = pd.to_datetime(data['日期'],format='%Y-%m-%d'#设置日期索引
    close = data['收盘'#日收盘价
    close.name = code
    return close

codes=['000001','000651','300015','002156','000625']
df = pd.DataFrame()
for code in codes:
    df_ = get_close(code)
    df = pd.concat([df,df_],axis=1)
df = df.dropna()

为了更加直观展示这五只股票,本文绘制了股价历史走势折线图,代码如下:

import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('whitegrid')
sns.set(font='SimHei')     # 设置字体
plt.rcParams['axes.unicode_minus'] = False  #正常显示负号
ax = df.plot(figsize=(10,6))
ax.set_xlabel('日期',fontsize=12)  
ax.set_ylabel('收盘价',fontsize=12)  
ax.set_title('股价历史走势',fontsize=18)
图片
image-20221129213726885

Part2计算股票收益率、协方差矩阵

为了构建投资组合,我们首先需要将股价转化为股票收益率。本文计算对数收益率,代码如下:

import numpy as np
returns_day=np.log(df/df.shift(1))          #计算日度收益率
returns_day.dropna(inplace=True)            #删除空值

#绘制日收益率波动图
ax_v = returns_day.plot(figsize=(10,6))
ax_v.set_xlabel('日期',fontsize=12)  # x轴标注
ax_v.set_ylabel('日收益波动率',fontsize=12)  # y轴标注
ax_v.set_title('股票日收益率波动',fontsize=18)
图片
image-20221129213758397

考虑到股票停盘和非交易日等因素,选取252天作为年化天数,这几只股票的年化收益率如下:

returns_annual = returns_day.mean()*252 #计算年化收益率
returns_annual
000001 0.048497
000651 -0.052044
300015 0.086419
002156 0.097144
000625 0.046377
dtype: float64
这几只股票的协方差矩阵如下:
cov_annual = returns_day.cov()*252 #计算协方差
cov_annual

000001000651300015002156000625
0000010.1235370.0540460.0449190.0276720.044307
0006510.0540460.1089220.0537760.0288330.044216
3000150.0449190.0537760.2982100.0406770.053856
0021560.0276720.0288330.0406770.2837990.070811
0006250.0443070.0442160.0538560.0708110.286122

Part3计算组合收益率、标准差、夏普比率

rf = 0.02
# 给定权重,求组合收益率、标准差、夏普比率
def statistics(weights):        
    weights = np.array(weights)
    port_returns = np.dot(weights, returns_annual)                            #获取组合收益率
    port_stdev = np.sqrt(np.dot(weights.T, np.dot(cov_annual, weights)))      #获取组合标准差
    port_sharpe = (port_returns - rf) / port_stdev                            #获取组合夏普比率
    return np.array([port_returns, port_stdev, port_sharpe])

Part4求解最小方差组合

import scipy.optimize as sco

# 定义规划求解目标函数:标准差进行最小化
def port_vol(weights): 
    return statistics(weights)[1]

num_assets = len(cov_annual)
# 约束条件:权重之和为1
constraints = ({'type''eq''fun'lambda x: np.sum(x) - 1})
# 目标函数输入参数(即权重)取值范围为0-1
bounds = tuple((01for x in range(num_assets))
min_vol_port = sco.minimize(port_vol, num_assets*[1./num_assets,],
                     method='SLSQP', bounds=bounds, constraints=constraints)
min_vol_port_weights = min_vol_port.x                                      # 最小方差组合各资产权重
min_vol_port_statisitcs = statistics(min_vol_port_weights)                 # 最小方差组合的收益、标准差、夏普比率
min_vol_port_allocation = pd.DataFrame(min_vol_port_weights,index=cov_annual.columns,columns=['最小方差组合配置比例'])   # 最小方差组合配置表格
min_vol_port_allocation = min_vol_port_allocation.applymap(lambda x:format(x,'.2%'))                 #设置数据格式为百分比两位数
print(min_vol_port_allocation)
最小方差组合配置比例
000001 30.82%
000651 37.38%
300015 8.56%
002156 14.48%
000625 8.76%
tag = ['收益率','标准差','夏普比率']
for i in range(len(tag)):
    print(f'最小方差组合{tag[i]}:{min_vol_port_statisitcs[i]:.1%}')
最小方差组合收益率:2.1%
最小方差组合标准差:26.5%
最小方差组合夏普比率:0.4%

Part5求解最大夏普比率组合

在Scipy的优化函数中,没有“最大化”的功能,因此作为一个目标函数,需要找到被最小化的变量值。因此我们定义neg_sharpe_ratio函数来计算负的夏普比率。

# 定义规划求解目标函数:负的夏普比率进行最小化
def neg_sharpe_ratio(weights): 
    return -statistics(weights)[2]

num_assets = len(cov_annual)
# 约束条件:权重之和为1
constraints = ({'type''eq''fun'lambda x: np.sum(x) - 1})
# 目标函数输入参数(即权重)取值范围为0-1
bounds = tuple((01for x in range(num_assets))
max_sharpe_port = sco.minimize(neg_sharpe_ratio, num_assets*[1./num_assets,], 
                    method='SLSQP', bounds=bounds, constraints=constraints)
max_sharpe_port_weights = max_sharpe_port.x                                        # 最大夏普比率组合各资产权重
max_sharpe_port_statisitcs = statistics(max_sharpe_port_weights)                   # 最大夏普比率组合的收益、标准差、夏普比率
max_sharpe_port_allocation = pd.DataFrame(max_sharpe_port_weights,index=cov_annual.columns,columns=['最大夏普比率组合配置比例'])   # 最大夏普比率组合配置表格
max_sharpe_port_allocation = max_sharpe_port_allocation.applymap(lambda x:format(x,'.2%'))                 #设置数据格式为百分比两位数
print(max_sharpe_port_allocation)
       最大夏普比率组合配置比例
000001 22.15%
000651 0.00%
300015 32.93%
002156 44.92%
000625 0.00%
tag = ['收益率','标准差','夏普比率']
for i in range(len(tag)):
    print(f'最大夏普比率组合{tag[i]}:{max_sharpe_port_statisitcs[i]:.1%}')
最大夏普比率组合收益率:8.3%
最大夏普比率组合标准差:34.6%
最大夏普比率组合夏普比率:18.2%

Part6随机生成投资组合

目前我们有五只股票,那么在投资组合里我们应该如何对这五只股票进行资产配置呢?我们要对每只股票投资一定的权重,权重之和等于1 。接下来我们对投资组合里的每只股票生成随机权重,计算出投资组合每年的收益和波动性。

#随机生成1000个投资组合
port_returns_list = []                  # 定义列表,存放组合年化收益率       
port_stdev_list = []                    # 定义列表,存放组合年化标准差
num_assets =len(cov_annual)        # 组合中的资产数
for x in range(1000):  
    #设置不同的随机种子,生成和为1的权重
    np.random.seed(x)
    weights = np.random.random(num_assets)     
    weights /= np.sum(weights)      
    port_returns = np.sum(weights*returns_annual)                              # 组合年化期望收益率
    port_stdev = np.sqrt(np.dot(weights.T ,np.dot(cov_annual ,weights)))       # 组合年化标准差
    port_returns_list.append(port_returns)    
    port_stdev_list.append(port_stdev)

port_returns = np.array(port_returns_list)
port_stdev = np.array(port_stdev_list)

port_returns_stdev = pd.DataFrame({'组合收益率':port_returns_list,'组合标准差':port_stdev_list})
port_returns_stdev = port_returns_stdev.applymap(lambda x:format(x,'.2%'))                 #设置数据格式为百分比两位数
port_returns_stdev.head()

组合收益率组合标准差
04.02%27.97%
11.19%26.98%
26.91%31.27%
33.59%29.10%
45.24%28.88%

Part7绘制有效边界

已知投资组合的收益与风险,我们可以画出所有可行集。但为了得到有效边界,我们还需要求解给定收益率水平下的最小标准差。利用Scipy包中的scipy.optimize.minimize我们可以在一定约束条件下求解目标函数的最小值。

import scipy.optimize as sco

# 定义规划求解目标函数:标准差进行最小化
def port_vol(weights): 
    return statistics(weights)[1]

# 规划求解:给定收益,求最小标准差
target_returns = np.linspace(min_vol_port_statisitcs[0],0.09,500)      # 定义不同目标收益率水平,最小为最小方差组合收益率
target_min_stdev = []

#初始值x0:
num_assets = len(cov_annual)
x0 = num_assets*[1./num_assets,]
for tar in target_returns:
    #两个约束条件:(1)权重之和为1 (2)给定目标收益率
    constraints = [{'type':'eq','fun':lambda x: sum(x)-1},{'type':'eq','fun'lambda x:statistics(x)[0]-tar}] 
    #目标函数输入参数(即权重)取值范围为0-1
    bounds = tuple((01for x in range(num_assets))
    outcome = sco.minimize(port_vol,x0 = x0,method = 'SLSQP',constraints = constraints,bounds = bounds) #求解
    target_min_stdev.append(outcome.fun)         #将最小标准差合并到列表中

target_min_stdev = np.array(target_min_stdev)

port_returns_min_stdev = pd.DataFrame({'组合收益率':target_returns,'组合最小标准差':target_min_stdev})
port_returns_min_stdev = port_returns_min_stdev.applymap(lambda x:format(x,'.2%'))                 #设置数据格式为百分比两位数
port_returns_min_stdev.head()

组合收益率组合最小标准差
02.10%26.47%
12.12%26.47%
22.13%26.47%
32.14%26.47%
42.16%26.47%

绘制有效边界的代码如下:

plt.scatter(port_stdev,port_returns,c=(port_returns-rf)/port_stdev,marker='o',cmap='YlGnBu')              # 1000个随机组合
plt.plot(target_min_stdev,target_returns,label='有效前沿',linewidth=4)      # 给定收益,方差最小的组合

plt.xlabel('组合标准差')
plt.ylabel('组合收益率')
plt.title('马科维茨有效边界')

plt.colorbar(label='夏普比率')
plt.legend()
plt.show()


图片

Part8将最小方差组合和最大化夏普比率组合绘制在有效边界

plt.scatter(port_stdev,port_returns,c=(port_returns-rf)/port_stdev,marker='o',cmap='YlGnBu')              # 1000个随机组合
plt.plot(target_min_stdev,target_returns,label='有效前沿',linewidth=4)      # 给定收益,方差最小的组合

plt.xlabel('组合标准差')
plt.ylabel('组合收益率')
plt.title('马科维茨有效边界')
plt.plot()
plt.colorbar(label='夏普比率')

# 黄星:标记最小方差组合
plt.scatter(min_vol_port_statisitcs[1],min_vol_port_statisitcs[0],color='black',marker='*',s=150,label='最小方差组合')
# 红星: 标记最大夏普比率组合
plt.scatter(max_sharpe_port_statisitcs[1],max_sharpe_port_statisitcs[0],color='r',marker='*',s= 150,label='最大夏普比率组合')

plt.legend()
plt.show()


图片

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多