Python金融大数据分析——第11章 统计学(2)投资组合优化 笔记

第11章 统计学

11.2 投资组合优化

现代 / 均值-方差投资组合理论(MPT)是金融理论的重要基础。正态分布收益率假设是该理论的基础:

只观察均值和方差, 我们必然假定没有必要用其他统计数字描述期末财富的分布。除非投资者有特殊的效用函数(二次效用函数), 否则有必要假设收益率呈正态分布, 用均值和方差就可以完整地描述。

11.2.1 数据

我们选择 5 种不同资产以提供分析:军工类股票 中国卫星 (600118)、中兵红箭 (000519) 、航天动力 (600343) ,以及浦发银行(600000 )和 中证500指数基金 500ETF (510500 )。

import numpy as np
import pandas as pd
import tushare as ts
import matplotlib.pyplot as plt

# 军工类股票 中国卫星 (600118)、中兵红箭 (000519) 、航天动力 (600343) ,
# 以及浦发银行(600000 )和 中证500指数基金 500ETF (510500 )
symbols = ['600118', '000519', '600343', '600000', '510500']

indexes = pd.date_range('2014-01-01', '2018-07-06')
# 为了保持和从toshare获取的时间序列类型一致,这里把时间类型转为字符串
indexes = indexes.map(lambda x: datetime.datetime.strftime(x, '%Y-%m-%d'))

data = pd.DataFrame(index=indexes)
for sym in symbols:
    k_d = ts.get_k_data(sym, '2014-01-01', ktype='D')
    # 如果上面的时间序列不转成字符串,这里就要转成时间序列,以保持index类型一致
    # k_d['date'] = k_d['date'].astype('datetime64[ns]')
    k_d.set_index('date', inplace=True)
    data[sym] = k_d['close']
data = data.dropna()

(data / data.ix[0] * 100).plot(figsize=(8, 5))

一段时间的股票价格走势
一段时间的股票价格走势

# 均值-方差指的是不同证券(对数)收益的均值和方差, 可以这样计算:
rets = np.log(data / data.shift(1))
# 在这些时间序列数据中, 可以看到年化收益表现的显著差异。
# 我们使用 252 个交易日,从每日收益得出年化收益:
rets.mean() * 252
# 600118    0.006067
# 000519   -0.038631
# 600343    0.068651
# 600000    0.140085
# 510500    0.428569
# dtype: float64

# 投资资产的协方差矩阵是整个投资组合选择过程的核心部分
rets.cov()*252
#           600118    000519    600343    600000    510500
# 600118  0.237557  0.107797  0.177612  0.017609  0.102007
# 000519  0.107797  0.209140  0.123730  0.012978  0.062838
# 600343  0.177612  0.123730  0.238805  0.013859  0.095337
# 600000  0.017609  0.012978  0.013859  0.064694  0.022098
# 510500  0.102007  0.062838  0.095337  0.022098  0.510833

11.2.2 基本理论

下面,我们假定投资者不允许在某种证券上建立空头头寸。 只允许多头头寸意味着投资者的财富将在可用资产中分配。所有头寸均为多头(正)头寸,且头寸的总和为 100%。例如, 可以在 5 种证券中投入相同的资金量(每种 20%)。

# 生成 5 个 0 到 1之间的随机数, 
# 然后对这些数值进行规范化,使所有值的总和为1
noa = 5
weights = np.random.random(noa)
weights /= np.sum(weights)
weights
# array([ 0.21076189,  0.23917961,  0.1825734 ,  0.03619006,  0.33129504])

现在可以检查资产权重综合确实为1 ;也就是说 I w i = 1 , 其中 I 是资产的数量, w i 0 是资产的权重。 下面公式提供在给定单一证券权重情况下的预期投资组合收益。 这是假定历史平均表现是未来(预期)表现的最佳预测因素时的预期投资收益公式。公式中, r i 是状态相关未来收益(由假定为正态分布的收益值组成的向量), 而 μ i 是证券i的预期收益。 最后, W T 是权重向量的转置, μ 是预期证券收益的向量。
公式 预期收益一般公式

μ p = E ( I w i r i ) = I w i E ( r i ) = I w i μ i = w T μ

MPT中选择的第二个对象是预期投资组合方差。 两种证券之间的协方经定义为 σ i j = σ j i = E ( r i μ i ) ( r j μ j ) 。证券的方差是与自身的协方差的特例: σ i 2 = E ( ( r i μ i ) 2 ) 。下面公式提供了一个证券投资组合的协方差矩阵(假定每种证券的权重均为1 )。
公式 投资组合协方差矩阵

= [ σ 1 2 σ 12 σ 1 I σ 21 σ 2 2 σ 2 I σ I 1 σ I 2 σ I 2 ]

公式 预期投资组合方差一般公式

σ p 2 = E ( ( r μ ) 2 ) = i I j I w i w j σ i j = w T w

# 预期投资组合方差
np.dot(weights.T, np.dot(rets.cov() * 252, weights))
# 0.14184053722017648

# 预期投资组合标准差
#(预期)投资组合标准差(波动率)只需要计算一次平方根即可得到
np.sqrt(np.dot(weights.T, np.dot(rets.cov() * 252, weights)))
# 0.37661722905381861

投资者最感兴趣的是给定证券组合的风险-收益均衡性及其统计学特性。为此.我们实施一次蒙特卡洛模拟,生成较大规模的随机投资组合权重向量。对于每一种模拟的分配,我们记录得出的预期投资组合收益和方差:

prets = []
pvols = []
for p in range(2500):
    weights = np.random.random(noa)
    weights /= np.sum(weights)
    prets.append(np.sum(rets.mean() * weights) * 252)
    pvols.append(np.sqrt(np.dot(weights.T, np.dot(rets.cov() * 252, weights))))

prets = np.array(prets)
pvols = np.array(pvols)

plt.figure(figsize=(8, 4))
plt.scatter(pvols, prets, c=prets / pvols, marker='o')
plt.grid(True)
plt.xlabel('expected volatility')
plt.ylabel('expected return')
plt.colorbar(label='Sharpe ratio')

不同/随机投资组合极重的预期收益和波动率
不同/随机投资组合极重的预期收益和波动率

上图展示了蒙特卡洛模拟的结果。还提供了定义为 S R μ p r f σ p 的夏普指数(即预期投资组合超额收益)。也就是投资收益率超过无风险利率 r f 的部分除以预期投资组合标准差。为了简单起见,我们假定 r f = 0
从均值和方差考量, 并不是所有权重分布都表现良好。例如, 对于固定风险水平(如30%), 多种组合都表现出不同的收益。作为投资者, 人们通常对周定风险水平的最大收益率或者固定收益率预期下的最小风险感兴趣。 这两种投资组合组成所谓的有效边界, 这是本节后面所要得到的结果。

11.2.3 投资组合优化

# 生成 5 个 0 到 1之间的随机数,
# 然后对这些数值进行规范化,使所有值的总和为1
noa = 5
weights = np.random.random(noa)
weights /= np.sum(weights)
weights
# array([ 0.21076189,  0.23917961,  0.1825734 ,  0.03619006,  0.33129504])

# 预期投资组合方差
np.dot(weights.T, np.dot(rets.cov() * 252, weights))
# 0.14184053722017648

# 预期投资组合标准差
# (预期)投资组合标准差(波动率)只需要计算一次平方根即可得到
np.sqrt(np.dot(weights.T, np.dot(rets.cov() * 252, weights)))
# 0.37661722905381861

prets = []
pvols = []
for p in range(2500):
    weights = np.random.random(noa)
    weights /= np.sum(weights)
    prets.append(np.sum(rets.mean() * weights) * 252)
    pvols.append(np.sqrt(np.dot(weights.T, np.dot(rets.cov() * 252, weights))))

prets = np.array(prets)
pvols = np.array(pvols)

plt.figure(figsize=(8, 4))
plt.scatter(pvols, prets, c=prets / pvols, marker='o')
plt.grid(True)
plt.xlabel('expected volatility')
plt.ylabel('expected return')
plt.colorbar(label='Sharpe ratio')


# 11.2.3 投资组合优化
# 首先建立一个方便的函数,为输入的权重向量 / 数组给出重要的投资组合统计数字:
def statistics(weights):
    """
    Return portfolio statistics
    :param weights: weights for different securities in portfolio
    :return:
    pret:float
    expected portfolio return
    pvol:float
    expected portfolio volatility
    pret/pvol:float
    Sharpe ratio for rf=0
    """
    weights = np.array(weights)
    pret = np.sum(rets.mean() * weights) * 252
    pvol = np.sqrt(np.dot(weights.T, np.dot(rets.cov() * 252, weights)))
    return np.array([pret, pvol, pret / pvol])


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


# 最小化函数minimize很通用,考虑了参数的(不)等式约束和参数的范围。
# 我们从夏普指数的最大化开始。 正式地说,最小化夏普指数的负值:
def min_func_sharpe(weights):
    return -statistics(weights)[2]


# 约束是所有参数(权重)的总和为1。 这可以用minimize函数的约定表达如下
cons = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})

# 我们还将参数值(权重)限制在0和l之间。 这些值以多个元组组成的一个元组形式提供给最小化函数:
bnds = tuple((0, 1) for x in range(noa))

# 优化函数调用中忽略的唯一输入是起始参数列表(对权重的初始猜测)。我们简单地使用平均分布:
noa * [1. / noa, ]
# [0.2, 0.2, 0.2, 0.2, 0.2]

%%time
opts = sco.minimize(min_func_sharpe, noa * [1. / noa, ], method='SLSQP', bounds=bnds, constraints=cons)
# Wall time: 1.2 s

opts
# fun: -0.7689821435140733
# jac: array([3.62539694e-01, 3.84121098e-01, 1.03567891e-01,
#             -1.06185675e-04, 2.67580152e-04])
# message: 'Optimization terminated successfully.'
# nfev: 59
# nit: 8
# njev: 8
# status: 0
# success: True
# x: array([2.69140628e-17, 5.93820112e-17, 0.00000000e+00,
#           7.15876612e-01, 2.84123388e-01])

opts['x'].round(3)
# array([ 0.   ,  0.   ,  0.   ,  0.716,  0.284])

# 最优化工作得出 一个投资组合,仅由5种资产中的2种组成

# 使用优化中得到的投资组合权重, 得出如下统计数字
statistics(opts['x'].round(3))
# array([ 0.22201418,  0.28871174,  0.76898216])
# 预期收益率约为22.2%. 预期被动率约为28.9%, 得到的最优夏普指数为0.77

# 接下来, 我们最小化投资组合的方差。
# 这与被动率的最小化相同,我们定义一个函数对方差进行最小化:
def min_func_variance(weights):
    return statistics(weights)[1]**2

optv = sco.minimize(min_func_variance, noa * [1. / noa, ], method='SLSQP', bounds=bnds, constraints=cons)
optv
# fun: 0.05137907199877911
# jac: array([0.10326265, 0.10273764, 0.10269385, 0.10276436, 0.102121])
# message: 'Optimization terminated successfully.'
# nfev: 71
# nit: 10
# njev: 10
# status: 0
# success: True
# x: array([0.04526382, 0.1335909, 0.05702634, 0.73177776, 0.03234118])

optv['x'].round(3)
# array([ 0.045,  0.134,  0.057,  0.732,  0.032])
# 投资组合中加入了全部资产。 这种组合可以得到绝对值最小方差投资组合
# 得到的预期收益率、波动率和夏普指数如下:
statistics(optv['x']).round(3)
# array([ 0.115,  0.227,  0.509])

11.2.4 有效边界

所有最优化投资组合——即目标收益率水平下波动率最小的所有投资者(或者给定风险水平下收益率最大的所有投资组合)——的求取都和上述最优化过程类似。 唯一的区别是我们必须循环使用多种起始条件。 最优化采用两种条件: 一是目标收益率水平tret , 另一个和以前一样是投资组合权重的总和。每个参数的边界值保持不变:

def min_func_port(weights):
    return statistics(weights)[1]


# 在不同目标收益率水平( trets )中循环时。 最小化的一个条件会变化。
# 这就是每次循环中更新条件字典对象的原因:

trets = np.linspace(0.0, 0.25, 50)
tvols = []
bnds = tuple((0, 1) for x in weights)
for tret in trets:
    cons = ({'type': 'eq', 'fun': lambda x: statistics(x)[0] - tret},
            {'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
    res = sco.minimize(min_func_port, noa * [1. / noa, ], method='SLSQP', bounds=bnds, constraints=cons)
    tvols.append(res['fun'])
tvols = np.array(tvols)

plt.figure(figsize=(8,4))
# random portfolio composition
plt.scatter(pvols,prets,c=prets/pvols,marker='o')
# efficient frontier
plt.scatter(tvols,trets,c=trets/tvols,marker='x')
# portfolio with highest Sharpe ratio
plt.plot(statistics(opts['x'])[1],statistics(opts['x'])[0],'r*',markersize=15.0)
# minimum variance portfolio
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')

给定收益率水平的最小凤险投资组合(变叉)
给定收益率水平的最小凤险投资组合(变叉)

上图展示了最优化的结果。交叉表示给定某个目标收益率的最优投资组合:小点和以前一样表示随机组合。此外, 该图还展示了两个较大的星号: 一个表示最小波动率 / 方差投资组合(最左侧的组合), 另一个表示具有最大夏普指数的投资组合。

有效边界由所有收益率高于绝对最小方差投资组合的最优投资组合构成。 这些投资组合在给定某一风险水平的预期收益率上优于其他所有投资组合。

11.2.5 资本市场线

除了股票等高风险证券或者商品(如黄金)之外, 通常有一种普遍的无风险投资机会:现金或者现金账户。在理想化的世界中, 保存在大银行现金账户中的资金可以认为是无风险的(例如, 通过公共存款保险计划)。这种无风险投资的缺点是通常只能得到很低的收益。有时接近于0。

然而, 考虑这些无风险资产, 可以显著加强投资者的有效投资机会。 基本思路是, 投资者首先确定高风险资产的一个有效组合, 然后在组合中加人无风险资产。 通过调整投资于无风险资产中的财富比例,有可能实现任何风险-收益均衡性,这些配置位于(风
险-收益空间中)无风险资产和有效投资组合之间的直线上。

(在许多选项中)使用哪一个有效投资组合进行最优化风格的投资?这是有效边界切线恰好通过无风险投资组合风险-收益点的投资组合。 例如, 考虑无风险利率 r f = 0.01 的情况。我们寻找有效边界上切线穿过风险-收益空间上的点 ( σ f , r f ) = ( 0 , 1 ) 的投资组合。

import scipy.interpolate as sci

ind = np.argmin(tvols)
evols = tvols[ind:]
erets = trets[ind:]

tck = sci.splrep(evols, erets)

# 通过这条数值化路径,最终可以为有效边界定义一个连续可微函数
# 和对应的一阶导数函数df(x):

def f(x):
    """
    Efficient frontier function (splines approximation)
    :param x:
    :return:
    """
    return sci.splev(x, tck, der=0)


def df(x):
    """
    First derivative of efficient frontier function.
    :param x:
    :return:
    """
    return sci.splev(x, tck, der=1)

我们所寻求的是函数 t(x) = a+b*x , 描述穿过风险-收益空间中无风险资产、与有效边界相切的一条直线。

公式 资本市场线的数学条件

t ( x ) = a + b x t ( 0 ) = r f a = r f t ( x ) = f ( x ) a + b x = f ( x ) t ( x ) = f ( x ) b = f ( x )

# 定义一个函数,返回给定参数集p=(a,b,x)
def equations(p, rf=0.01):
    eq1 = rf - p[0]
    eq2 = rf + p[1] * p[2] - f(p[2])
    eq3 = p[1]-df(p[2])
    return eq1,eq2,eq3

# 数值优化得到如下的值
opt=sco.fsolve(equations,[0.01,0.5,0.15])
opt
# array([ 0.01      ,  0.73464122,  0.29383737])

正如预期,有 a = r f = 0.01

优化的成败可能取决于初始参数化, 因此必须小心选择这些参数——常需要组合合理的猜测, 并反复尝试。

plt.figure(figsize=(8, 4))
# random portfolio composition
plt.scatter(pvols, prets, c=(prets - 0.01) / pvols, marker="o")
# efficient frontier
plt.plot(evols, erets, 'g', lw=4.0)
cx = np.linspace(0.0, 0.3)
plt.plot(cx, opt[0] + opt[1] * cx, lw=1.5)
# capital market line
plt.plot(opt[2], f(opt[2]), 'r*', markersize=15.0)
plt.grid(True)
plt.axhline(0, color='k', ls='-', lw=2.0)
plt.axvline(0, color='k', ls='-', lw=2.0)
plt.xlabel('expected volatility')
plt.ylabel('expected return')
plt.colorbar(label='Sharp ratio')

无风险利率为 1%时的资本市场线和相切的投资组合(星号)
无风险利率为 1%时的资本市场线和相切的投资组合(星号)
星号代表有效边界中切线穿过无风险资产点(0, 0.01)的最优化投资组合。这个最优化组合的预期波动率为29.4% (opt[2]), 预期收益率为22.6% (f(opt[2]))

最优投资组合的权重如下,只包含5种资产中的 3 种:

cons = ({'type': 'eq', 'fun': lambda x: statistics(x)[0] - f(opt[2])},
        {'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
res = sco.minimize(min_func_port, noa * [1. / noa, ],
                   method='SLSQP', bounds=bnds, constraints=cons)
res['x'].round(3)
# array([ 0.   ,  0.   ,  0.   ,  0.703,  0.297])

未完待续 ……

猜你喜欢

转载自blog.csdn.net/weixin_42018258/article/details/80953809