by Harold
思考怎样有效的配置资产组合 很多时候根据条件选好股票池之后,通常简单粗暴的等分仓位给每只股票。 其实,过程中有很多可以优化的空间。
下面,来看如何运用有效前沿进行资产组合优化。
import pandas as pd
import numpy as np
import statsmodels.api as sm
import scipy.stats as scs
import matplotlib.pyplot as plt
000723 美锦能源, 600176 中国巨石, 002202 金风科技, 600519 贵州茅台
stock = ['000723.XSHE','600176.XSHG','002202.XSHE','600519.XSHG']
start_date = '2018-05-10'
end_date = '2019-05-10'
df = get_price(stock, start_date, end_date, 'daily',['close'])
data = df['close']
data.head()
000723.XSHE | 600176.XSHG | 002202.XSHE | 600519.XSHG | |
---|---|---|---|---|
2018-05-10 | 5.15 | 12.32 | 15.90 | 704.23 |
2018-05-11 | 5.15 | 12.28 | 15.87 | 708.00 |
2018-05-14 | 5.15 | 12.70 | 16.03 | 732.05 |
2018-05-15 | 5.15 | 12.68 | 16.33 | 732.69 |
2018-05-16 | 5.15 | 12.42 | 16.39 | 724.27 |
(data/data.ix[0]*100).plot(figsize = (8,6));
log_returns = np.log(data/data.shift(1))
log_returns.head()
000723.XSHE | 600176.XSHG | 002202.XSHE | 600519.XSHG | |
---|---|---|---|---|
2018-05-10 | NaN | NaN | NaN | NaN |
2018-05-11 | 0 | -0.003252 | -0.001889 | 0.005339 |
2018-05-14 | 0 | 0.033630 | 0.010031 | 0.033405 |
2018-05-15 | 0 | -0.001576 | 0.018542 | 0.000874 |
2018-05-16 | 0 | -0.020718 | 0.003667 | -0.011558 |
log_returns.hist(bins = 50, figsize = (9,6));
从以上结果看到4个数据集都和正态分布的要求相差太多。
#定义print_statistics函数,为了更加易于理解的方式
#输出给定(历史或者模拟)数据集均值、偏斜度或者峰度等统计数字
def print_statistics(array):
sta = scs.describe(array)
print '%14s %15s' %('statistic','value')
print 30*'-'
print '%14s %15d' %('size', sta[0])
print '%14s %15.5f' %('min', sta[1][0])
print '%14s %15.5f' %('max', sta[1][1])
print '%14s %15.5f' %('mean', sta[2])
print '%14s %15.5f' %('std', np.sqrt(sta[3]))
print '%14s %15.5f' %('skew', sta[4])
print '%14s %15.5f' %('kurtosis', sta[5])
for st in stock:
print '\nResults for stock %s' %st
print 30*'-'
log_data = np.array(log_returns[st].dropna())
print_statistics(log_data)
Results for stock 000723.XSHE ------------------------------ statistic value ------------------------------ size 243 min -0.10542 max 0.09603 mean 0.00453 std 0.03786 skew 0.32416 kurtosis 1.65314 Results for stock 600176.XSHG ------------------------------ statistic value ------------------------------ size 243 min -0.09620 max 0.09571 mean -0.00088 std 0.02562 skew 0.18921 kurtosis 1.91781 Results for stock 002202.XSHE ------------------------------ statistic value ------------------------------ size 243 min -0.10560 max 0.09583 mean -0.00150 std 0.03308 skew 0.28700 kurtosis 1.28129 Results for stock 600519.XSHG ------------------------------ statistic value ------------------------------ size 243 min -0.10536 max 0.06348 mean 0.00104 std 0.02358 skew -0.26321 kurtosis 1.86828
下面是000723.XSHE 对数收益率 分位数-分位数图
sm.qqplot(log_returns['000723.XSHE'].dropna(),line = 's')
plt.grid(True);
plt.xlabel('theoretical quantiles');
plt.ylabel('sample quantiles');
很显然,样本的分位数值不在一条直线上,表明“非正态性”。左侧和右侧分别有许多值远低于和远高于直线。这是典型的Fat tails。 Fat tails是频数分布中观察到的两端的异常值。
def normality_test(array):
'''
对给定的数据集进行正态性检验
组合了3中统计学测试
偏度测试(Skewtest)——足够接近0
峰度测试(Kurtosistest)——足够接近0
正态性测试
'''
print 'Skew of data set %15.3f' % scs.skew(array)
print 'Skew test p-value %14.3f' % scs.skewtest(array)[1]
print 'Kurt of data set %15.3f' % scs.kurtosis(array)
print 'Kurt test p-value %14.3f' % scs.kurtosistest(array)[1]
print 'Norm test p-value %14.3f' % scs.normaltest(array)[1]
for st in stock:
print '\nResults for st %s' %st
print 32*'-'
log_data = np.array(log_returns[st].dropna())
normality_test(log_data)
Results for st 000723.XSHE -------------------------------- Skew of data set 0.324 Skew test p-value 0.038 Kurt of data set 1.653 Kurt test p-value 0.001 Norm test p-value 0.000 Results for st 600176.XSHG -------------------------------- Skew of data set 0.189 Skew test p-value 0.219 Kurt of data set 1.918 Kurt test p-value 0.000 Norm test p-value 0.000 Results for st 002202.XSHE -------------------------------- Skew of data set 0.287 Skew test p-value 0.065 Kurt of data set 1.281 Kurt test p-value 0.003 Norm test p-value 0.002 Results for st 600519.XSHG -------------------------------- Skew of data set -0.263 Skew test p-value 0.090 Kurt of data set 1.868 Kurt test p-value 0.000 Norm test p-value 0.000
从上述测试的p值来看,否定了数据集呈正态分布的测试假设。 这说明,股票市场收益率的正态假设不成立。
该理论基于用均值和方差来表述组合的优劣的前提。将选取几只股票,用蒙特卡洛模拟初步探究组合的有效前沿。
通过最大Sharpe和最小方差两种优化来找到最优的资产组合配置权重参数。
最后,刻画出可能的分布,两种最优以及组合的有效前沿。
000723 美锦能源, 600176 中国巨石, 002202 金风科技, 600519 贵州茅台
并比较一下数据(2018-05-10至2019-05-10)
stock_set = ['000723.XSHE','600176.XSHG','002202.XSHE','600519.XSHG']
noa = len(stock_set)
df = get_price(stock_set, start_date, end_date, 'daily', ['close'])
data = df['close']
#规范化后时序数据
(data/data.ix[0]*100).plot(figsize = (8,5));
每年252个交易日,用每日收益得到年化收益。
计算投资资产的协方差是构建资产组合过程的核心部分。运用pandas内置方法生产协方差矩阵。
returns = np.log(data / data.shift(1))
returns.mean()*252
000723.XSHE 1.141983 600176.XSHG -0.221564 002202.XSHE -0.379245 600519.XSHG 0.262546 dtype: float64
returns.cov()*252
000723.XSHE | 600176.XSHG | 002202.XSHE | 600519.XSHG | |
---|---|---|---|---|
000723.XSHE | 0.361300 | 0.061876 | 0.045880 | 0.054114 |
600176.XSHG | 0.061876 | 0.165376 | 0.117669 | 0.085467 |
002202.XSHE | 0.045880 | 0.117669 | 0.275695 | 0.083692 |
600519.XSHG | 0.054114 | 0.085467 | 0.083692 | 0.140148 |
由于A股不允许建立空头头寸,所有的权重系数均在0-1之间
weights = np.random.random(noa)
weights /= np.sum(weights)
weights
array([0.350076915668, 0.0388221914076, 0.275820833701, 0.335280059223])
np.sum(returns.mean()*weights)*252
0.37460326019452855
np.dot(weights.T, np.dot(returns.cov()*252,weights))
0.12472573811652655
np.sqrt(np.dot(weights.T, np.dot(returns.cov()* 252,weights)))
0.35316531273120039
进行到此,我们最想知道的是给定的一个股票池(证券组合)如何找到风险和收益平衡的位置。
下面通过一次蒙特卡洛模拟,产生大量随机的权重向量,并记录随机组合的预期收益和方差。
port_returns = []
port_variance = []
for p in range(4000):
weights = np.random.random(noa)
weights /=np.sum(weights)
port_returns.append(np.sum(returns.mean()*252*weights))
port_variance.append(np.sqrt(np.dot(weights.T, np.dot(returns.cov()*252, weights))))
port_returns = np.array(port_returns)
port_variance = np.array(port_variance)
#无风险利率设定为4%
risk_free = 0.04
plt.figure(figsize = (8,4))
plt.scatter(port_variance, port_returns, c=(port_returns-risk_free)/port_variance, marker = 'o')
plt.grid(True)
plt.xlabel('excepted volatility')
plt.ylabel('expected return')
plt.colorbar(label = 'Sharpe ratio')
<matplotlib.colorbar.Colorbar at 0x7f7ca746df90>
建立statistics函数来记录重要的投资组合统计数据(收益,方差和夏普比)
通过对约束最优问题的求解,得到最优解。其中约束是权重总和为1。
def statistics(weights):
weights = np.array(weights)
port_returns = np.sum(returns.mean()*weights)*252
port_variance = np.sqrt(np.dot(weights.T, np.dot(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。这可以用minimize函数的约定表达如下
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
status: 0 success: True njev: 7 nfev: 42 fun: -1.9164938258862707 x: array([0.815054276427, 1.95915332568e-16, 0.0, 0.184945723573]) message: 'Optimization terminated successfully.' jac: array([-3.61651182175e-05, 0.919741675258, 1.13021221757, 0.00015951693058, 0.0]) nit: 7
得到的最优组合权重向量为:
opts['x'].round(3)
array([0.815, 0.0, 0.0, 0.185])
sharpe最大的组合3个统计数据分别为:
#预期收益率、预期波动率、最优夏普指数
statistics(opts['x']).round(3)
array([0.979, 0.511, 1.916])
接下来,我们通过方差最小来选出最优投资组合。
#但是我们定义一个函数对 方差进行最小化
def min_variance(weights):
return statistics(weights)[1]
optv = sco.minimize(min_variance, noa*[1./noa,],method = 'SLSQP', bounds = bnds, constraints = cons)
optv
status: 0 success: True njev: 7 nfev: 43 fun: 0.32538668759319472 x: array([0.164706707131, 0.262920333446, 0.101369109366, 0.471003850056]) message: 'Optimization terminated successfully.' jac: array([0.326306864619, 0.325219530612, 0.324905332178, 0.325260814279, 0.0]) nit: 7
方差最小的最优组合权重向量及组合的统计数据分别为:
optv['x'].round(3)
array([0.165, 0.263, 0.101, 0.471])
#得到的预期收益率、波动率和夏普指数
statistics(optv['x']).round(3)
array([0.215, 0.325, 0.661])
有效前沿有既定的目标收益率下方差最小的投资组合构成。
在最优化时采用两个约束,1.给定目标收益率,2.投资组合权重和为1。
def min_variance(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)
下面是最优化结果的展示。
叉号:构成的曲线是有效前沿(目标收益率下最优的投资组合)
红星:sharpe最大的投资组合
黄星:方差最小的投资组合
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')
#红星:标记最高sharpe组合
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('expected volatility')
plt.ylabel('expected return')
plt.colorbar(label = 'Sharpe ratio')
<matplotlib.colorbar.Colorbar at 0x7f7ca79e8910>
^_^ Thanks
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程