第八篇:Python实现马克维兹投资组合理论
导语:针对著名的马克维兹投资组合理论,本篇内容深入浅出,完整的将整个理论过程通过python语言实现。
马克维兹投资组合理论
1952年,马克维兹发表了题为《投资组合的选择》的论文, 首次用数学模型分析投资组合, 从而使这项的革命性的科学方法对投资理论产生了重大的影响。资产选择分析的目标是要求出最有效的投资组合集, 即投资的有效边界和资产市场线。
在有效证券组合可行域的上边缘部分称为有效边界,也称“马科维兹边界”。
有效边界一定是向外凸的, 在它左方的投资组合是不可能的,而位于它右方的投资组合是没有效率的。
因为在有效边界上的投资组合较其右方与之风险相同的投资组合有较高的收益率,较其右方与之收益相同的投资组合有较低的风险。
资本市场线是指表明有效组合的期望收益率和标准差之间的一种简单的线性关系的一条射线。
它是沿着投资组合的有效边界,由风险资产和无风险资产构成的投资组合。
这里将介绍使用均值 - 方差矩阵计算夏普最大组合、方差最小组合以及有效边界和资本市场线
import pandas as pd
import numpy as np
import scipy.optimize as sco
import matplotlib.pyplot as plt
1.以下面5只股票为例(回测周期:20150101至20151231)
stock_list = ['600050.SH','000528.SZ','000001.SZ','002007.SZ','300033.SZ']
num = len(stock_list)
df = pd.DataFrame()
for stock in stock_list:
df_stock = get_price(stock, '20150101', '20151231', '1d', ['close'], skip_paused = False, fq = 'pre', is_panel = 0)
df[stock] = df_stock['close']
df.head(1)
600050.SH | 000528.SZ | 000001.SZ | 002007.SZ | 300033.SZ | |
---|---|---|---|---|---|
2015-01-05 | 5.19 | 12.65 | 13.21 | 32.39 | 22.76 |
2.计算股票每日收益率和协方差矩阵
df_returns = df.pct_change().dropna()
df_returns.head(2)
600050.SH | 000528.SZ | 000001.SZ | 002007.SZ | 300033.SZ | |
---|---|---|---|---|---|
2015-01-06 | -0.017341 | -0.048221 | -0.015140 | 0.052794 | 0.100176 |
2015-01-07 | 0.037255 | 0.006645 | -0.019216 | -0.018475 | 0.042732 |
df_returns.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7fd95fda2a58>
df_returns.mean() *250
600050.SH 0.382423
000528.SZ -0.224440
000001.SZ 0.002095
002007.SZ 0.477311
300033.SZ 1.640438
dtype: float64
df_returns.cov() * 250 # 取一年250个交易日
600050.SH | 000528.SZ | 000001.SZ | 002007.SZ | 300033.SZ | |
---|---|---|---|---|---|
600050.SH | 0.408220 | 0.253687 | 0.168676 | 0.136914 | 0.249341 |
000528.SZ | 0.253687 | 0.416757 | 0.153297 | 0.208305 | 0.278565 |
000001.SZ | 0.168676 | 0.153297 | 0.203456 | 0.092488 | 0.129261 |
002007.SZ | 0.136914 | 0.208305 | 0.092488 | 0.322624 | 0.295975 |
300033.SZ | 0.249341 | 0.278565 | 0.129261 | 0.295975 | 0.941120 |
3.计算组合的年化收益率、标准差和夏普比
先给每只股票分配随机权重
weights = np.random.random(num)
weights /= np.sum(weights)
weights
array([ 0.24468904, 0.17346735, 0.07887898, 0.22881537, 0.27414926])
value1 = np.dot(df_returns.mean(), weights) * 250
print('组合年化收益率: ' + str(value1.round(4)))
组合年化收益率: 0.6137
value2 = np.sqrt(np.dot(weights, np.dot(df_returns.cov(), weights)) * 250)
print('组合年化标准差: ' + str(value2.round(4)))
组合年化标准差: 0.544
value3 = (value1 - 0.04) / value2
print('组合夏普比: ' + str(value3.round(2)))
组合夏普比: 1.05
4.求解夏普比最大的投资组合
def portfolio_stat(weights):
risk_free_rate = 0.04
weights = np.array(weights)
port_return = np.dot(df_returns.mean(), weights) * 250
port_std = np.sqrt(np.dot(weights, np.dot(df_returns.cov(), weights.T)) * 250)
return np.array([port_return, port_std, (port_return - risk_free_rate)/port_std])
# 因为scipy.optimization只有最小化函数,所以夏普比需要变成负值
def min_sharpe(weights):
return -(portfolio_stat(weights)[2])
# 约束条件1:所有权重的总和为1
cons = ({'type':'eq', 'fun':lambda x: np.sum(x)-1})
# 约束条件2: 权重应限制在0和1之间。这些值以多个元组组成的一个元组形式提供给
bnds = tuple((0,1) for x in range(num))
# 优化函数调用中忽略的唯一输入是起始参数列表(对权重的初始猜测)。我们使用等权分布。
opts = sco.minimize(min_sharpe, num*[1./num,], method = 'SLSQP', bounds = bnds, constraints = cons)
opts
fun: -1.6497425960291425
jac: array([ 0.04287983, 0.71966536, 0.22442886, 0.02681652, -0.04123227])
message: 'Optimization terminated successfully.'
nfev: 14
nit: 2
njev: 2
status: 0
success: True
x: array([ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
5.55111512e-17, 1.00000000e+00])
得到的最优组合结果
print('权重: ')
print(opts['x'].round(4))
print()
print('组合的收益率、标准差和夏普比: ')
print(portfolio_stat(opts['x']))
权重:
[ 0. 0. 0. 0. 1.]
组合的收益率、标准差和夏普比:
[ 1.64043761 0.97011353 1.6497426 ]
5.求解标准差最小的投资组合
#但是我们定义一个函数对 方差进行最小化
def min_std(weights):
return portfolio_stat(weights)[1]
optv = sco.minimize(min_std, num*[1./num,],method = 'SLSQP', bounds = bnds, constraints = cons)
optv
fun: 0.40870394126925574
jac: array([ 0.40821013, 0.4263164 , 0.40859295, 0.40898372, 0.45602276])
message: 'Optimization terminated successfully.'
nfev: 49
nit: 7
njev: 7
status: 0
success: True
x: array([ 3.44582749e-02, 0.00000000e+00, 6.47754037e-01,
3.17787688e-01, 2.76471554e-18])
得到的标准差最小的组合结果
print('权重: ')
print(optv['x'].round(4))
print()
print('组合的收益率、标准差和夏普比: ')
print(portfolio_stat(optv['x']))
权重:
[ 0.0345 0. 0.6478 0.3178 0. ]
组合的收益率、标准差和夏普比:
[ 0.16621806 0.40870394 0.30882516]
6.投资组合的有效边界和资本市场线
首先观察蒙特卡洛的模拟结果
有效边界为所有可能组合的上半边界,另外可以看出最左点应为(0.43, 0.32)附近
port_returns = []
port_std = []
for p in range(10000):
weights = np.random.random(num)
weights /=np.sum(weights)
port_returns.append(np.dot(df_returns.mean(), weights) * 250)
port_std.append(np.sqrt(np.dot(weights, np.dot(df_returns.cov(), weights.T)) * 250))
port_returns = np.array(port_returns)
port_std = np.array(port_std)
plt.figure(figsize = (8,4))
plt.scatter(port_std, port_returns, c=(port_returns - 0.04)/port_std, marker = 'o')
plt.grid(True)
plt.xlabel('excepted volatility')
plt.ylabel('expected return')
plt.colorbar(label = 'Sharpe ratio')
<matplotlib.colorbar.Colorbar at 0x7fd964821c50>
计算有效边界
# 在不同目标收益率水平(target_returns)循环时,最小化的一个约束条件会变化。
frontier_returns = np.linspace(0.32, 1.4, 50)
frontier_std = []
for tar in frontier_returns:
cons = ({'type':'eq','fun':lambda x:portfolio_stat(x)[0]-tar}, {'type':'eq','fun':lambda x:np.sum(x)-1})
res = sco.minimize(min_std, num*[1./num,],method = 'SLSQP', bounds = bnds, constraints = cons)
frontier_std.append(res['fun'])
frontier_std = np.array(frontier_std)
资本市场线为无风险资产与夏普最优组合的连线
plt.figure(figsize = (12,6))
# 圆圈:蒙特卡洛随机产生的组合分布
plt.scatter(port_std, port_returns, c = (port_returns-0.04)/port_std,marker = 'o')
# 红星:标记最高sharpe组合
plt.plot(portfolio_stat(opts['x'])[1], portfolio_stat(opts['x'])[0], 'r*', markersize = 15.0)
# 蓝星:标记最小方差组合
plt.plot(portfolio_stat(optv['x'])[1], portfolio_stat(optv['x'])[0], 'b*', markersize = 15.0)
# 蓝线:有效边界
plt.plot(frontier_std, frontier_returns, 'b')
# 红线:资本市场线
plt.plot([0, portfolio_stat(opts['x'])[1]], [0.04, portfolio_stat(opts['x'])[0]], 'r')
plt.legend(['Max Sharpe Portfolio', 'Min Variance Portfolio','Efficient Frontier', 'CML'])
plt.grid(True)
plt.xlabel('expected volatility')
plt.ylabel('expected return')
plt.colorbar(label = 'Sharpe ratio')
<matplotlib.colorbar.Colorbar at 0x7fd9486f3128>
根据有效边界理论,资本市场线上的组合是最优组合。如果可以以无风险利率贷款,那么资本市场线的延长线也可以供投资者选择。