马科维茨有效前沿实现(Python 版本)

马科维茨有效前沿实现(Python 版本)

这里使用的数据是 QA 框架建立的数据库里面的数据。

准备工作

调用所需要的库

1
2
3
4
5
6
7
import pandas as pd
import numpy as np
import statsmodels.api as sm
import scipy.stats as scs
import matplotlib.pyplot as plt
import QUANTAXIS as QA
import pylab

获取股票,选取 close 进行初步分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 获取上证50中的4根票
code = QA.QA_fetch_stock_block_adv().get_block('上证50').code[0:4]
data = QA.QA_fetch_stock_day_adv(code, '2018-01-01', '2018-09-27').to_qfq()
# 选取close序列,进行pivot成表
close = data.pivot('close')
close.head()

Out[14]:
code 600000 600016 600019 600028
date
2018-01-02 12.587085 7.006029 8.521262 5.866786
2018-01-03 12.527712 7.055368 8.492731 5.958886
2018-01-04 12.527712 7.022475 8.502242 6.354917
2018-01-05 12.557398 7.071814 8.435669 6.502278
2018-01-08 12.547503 7.170490 8.835103 6.493068

使用基准价格比较股票

注意,这里的绘图代码在 PyCharm 中是无法直接显示的,调用 pylab 包的 avefig()函数就可以保存了,不建议使用 show(),会很久都显示不出来,最后 PyCharm 崩溃。

1
2
(close/close.iloc[0] * 100).plot(figsize = (18, 16))
pylab.savefig("走势.png")

计算收益率

1
2
3
4
5
6
7
8
9
10
11
log_returns = np.log(close/close.shift(1))
log_returns.head()

Out[17]:
code 600000 600016 600019 600028
date
2018-01-02 NaN NaN NaN NaN
2018-01-03 -0.004728 0.007018 -0.003354 0.015577
2018-01-04 0.000000 -0.004673 0.001119 0.064345
2018-01-05 0.002367 0.007001 -0.007861 0.022924
2018-01-08 -0.000788 0.013857 0.046264 -0.001417

绘制收益率的直方图

1
log_returns.hist(bins = 50, figsize = (10, 8))

计算协方差矩阵再年化

1
2
log_returns.cov() * 252
noa = len(code)

给不同股票随意分配权重

1
2
3
4
5
6
7
8
9
weights = np.random.random(noa)
weights

Out[27]: array([0.81777477, 0.37975299, 0.26486785, 0.25297689])

weights /= np.sum(weights)
weights

Out[29]: array([0.47673306, 0.22138223, 0.15440836, 0.14747636])

计算组合年化收益、组合方差和组合标准差

1
2
3
4
5
6
7
8
np.sum(log_returns.mean()*weights)*252
Out[30]: -0.1385869559014955

np.dot(weights.T, np.dot(log_returns.cov()*252, weights))
Out[31]: 0.039876150683717235

np.sqrt(np.dot(weights.T, np.dot(log_returns.cov()*252, weights)))
Out[32]: 0.19969013667108657

用蒙特卡洛模拟产生大量的随机组合

1
2
3
4
5
6
7
8
9
port_returns = []
port_variance = []
for p in range(4000):
weights = np.random.random(noa)
weights /= np.sum(weights)
port_returns.append(np.sum(log_returns.mean()*weights)*252)
port_variance.append(np.sqrt(np.dot(weights.T, np.dot(log_returns.cov()*252, weights))))
port_returns = np.array(port_returns)
port_variance = np.array(port_variance)

无风险利率设定为 4%

1
2
3
4
5
6
7
8
risk_free = 0.04
plt.figure(figsize = (8, 4))
plt.scatter(port_variance, port_returns, c = (port_returns-risk_free)/port_variance)
plt.grid(True)
plt.xlabel("波动率")
plt.ylabel("收益率")
plt.colorbar(label = '夏普比率')
plt.savefig("投资组合.png")

投资组合优化 1——夏普比率最大

建立 statistics 函数来记录重要的投资组合统计数据(收益、方差和夏普比)。通过对约束最优化问题的求解,得到最优解,其中约束权重为 1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def statistics(weights):
weights = np.array(weights)
port_returns = np.sum(log_returns.mean()*weights)*252
port_variance = np.sqrt(np.dot(weights.T, np.dot(log_returns.cov()*252, weights)))
return np.array([port_returns, port_variance, port_returns/port_variance])

# 最优化投资组合的推导是一个约束最优化的问题
import scipy.optimize as sco

# 最小化夏普比率的负值
def min_sharpe(weights):
return -statistics(weights)[2]

# 约束是所有参数(权重)的总和为1
cons = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})

# 我们还将参数限制在0~1之间(不允许做多做空)
bnds = tuple((0, 1) for x in range(noa))

# 优化函数调用中忽略的唯一输入是起始参数列表(对权重的初始猜测)。我们简单的使用平均分布
opts = sco.minimize(min_sharpe, noa*[1./noa,], method = 'SLSQP', bounds = bnds, constraints = cons)
opts

Out[33]:
fun: -0.8319659036560182
jac: array([1.17844296e+00, 8.59945662e-01, 7.92490840e-01, 1.49011612e-08])
message: 'Optimization terminated successfully.'
nfev: 12
nit: 2
njev: 2
status: 0
success: True
x: array([0.00000000e+00, 0.00000000e+00, 5.55111512e-17, 1.00000000e+00])

# 得到的最优组合权重向量为:
opts['x'].round(3)

Out[34]: array([0., 0., 0., 1.])

# sharpe最大的组合3个统计数据分别为:
statistics(opts['x']).round(3)

Out[35]: array([0.24 , 0.288, 0.832])

投资组合优化 2——方差最小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 接下来,我们通过方差最小来选出最有投资组合
# 首先定义一个函数对方差最小化
def min_variance(weights):
return statistics(weights)[1]
optv = sco.minimize(min_variance, noa*[1./noa,], method = 'SLSQP', bounds = bnds, constraints = cons)
optv

Out[36]:
fun: 0.18738209220809027
jac: array([0.18737382, 0.1873877 , 0.20613519, 0.18737812])
message: 'Optimization terminated successfully.'
nfev: 48
nit: 8
njev: 8
status: 0
success: True
x: array([3.30105534e-01, 5.62277254e-01, 6.93889390e-18, 1.07617212e-01])

# 方差最小的最优组合权重向量及组合的统计数据分别为
optv['x'].round(3)

Out[37]: array([0.33 , 0.562, 0. , 0.108])

# 得到预期收益率、波动率和夏普比率
statistics(optv['x']).round(3)

Out[38]: array([-0.15 , 0.187, -0.802])

组合的有效前沿

有效前沿由既定的目标收益率方差最小的投资组合构成。在最优化时采用两个约束:1. 给定目标收益率; 2. 投资组合权重和为 1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def min_variancemin_varia(weights):
return statistics(weights)[1]

# 在不同目标收益率水平(target_returns)循环时,最小化的一个约束条件会变化
target_returns = np.linspace(0.0, 0.5, 50)
target_variance = []
for tar in target_returns:
cons = ({'type':'eq','fun':lambda x:statistics(x)[0]-tar},{'type':'eq','fun':lambda x:np.sum(x)-1})
res = sco.minimize(min_variance, noa*[1./noa,],method = 'SLSQP', bounds = bnds, constraints = cons)
target_variance.append(res['fun'])

target_variance = np.array(target_variance)
target_variance

Out[39]:
array([0.20391184, 0.206127 , 0.20846174, 0.21091404, 0.21351393,
0.21626772, 0.21916962, 0.22221382, 0.22539456, 0.22870613,
0.23214295, 0.23569953, 0.23937054, 0.24315079, 0.24703526,
0.25101912, 0.25509771, 0.25926657, 0.2635214 , 0.26785811,
0.27227279, 0.27676171, 0.2813213 , 0.2859482 , 0.2883217 ,
0.2883217 , 0.2883217 , 0.2883217 , 0.2883217 , 0.2883217 ,
0.2883217 , 0.2883217 , 0.2883217 , 0.2883217 , 0.2883217 ,
0.2883217 , 0.2883217 , 0.2883217 , 0.2883217 , 0.2883217 ,
0.2883217 , nan, 0.2883217 , 0.2883217 , 0.2883217 ,
0.2883217 , 0.2883217 , 0.2883217 , 0.2883217 , 0.2883217 ])

下面展示最优化的结果

  • 叉号:构成的曲线是有效前沿
  • 红星:sharpe ratio 最大的投资组合
  • 黄星:方差最小的投资组合
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    plt.figure(figsize = (8, 4))
    plt.scatter(port_variance, port_returns, c = port_returns/port_variance, marker = 'o')
    plt.scatter(target_variance, target_returns, c = target_returns/target_variance, marker = 'x')
    plt.plot(statistics(opts['x'])[1], statistics(opts['x'])[0], 'r*', markersize = 15.0)
    plt.plot(statistics(optv['x'])[1], statistics(optv['x'])[0], 'y*', markersize = 15.0)
    plt.grid(True)
    plt.xlabel('期望波动率')
    plt.ylabel('期望收益率')
    plt.colorbar(label = '夏普比率')
    plt.savefig("有效前沿.png")

# Python

评论

程振兴

程振兴 @czxa.top
截止今天,我已经在本博客上写了165.1k个字了!

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×