V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
thinkingmind
V2EX  ›  推广

[MPT] 经典投资组合理论的优化 RiceQuant

  •  
  •   thinkingmind · 2016-11-18 17:43:31 +08:00 · 845 次点击
    这是一个创建于 2957 天前的主题,其中的信息可能已经有所发展或是发生改变。

    新鲜出品:https://www.ricequant.com/community/topic/1568/?utm_source=v2ex?

    米筐这么厉害,你都听说过! 那你一定也很厉害!

    均值-方差投资组合理论( MPT )是金融理论的重要基础, Harry Markowitz 也凭其获得了 1990 年的诺贝尔经济学奖,可见其意义之大。这里主要介绍了如何用程序实现它,希望对大家有用。

    这里简单介绍一下均值方差理论的内容,这些基本知识也能很方便地从 wiki 上找到 。

    均值-方差理论的核心思想是:如何实现在一定风险水平下最大化资产组合的收益,或者如何在给定收益水平的条件下最小化资产组合的风险。根据其核心思想,不难倒推出其内含的假设条件,那就是投资者是理性的,并且是风险厌恶的。如果两个资产组合拥有相同的预期回报,投资者便会选择风险较小的哪一个,而也只有在获得更高预期回报的前提下,投资者才会才会承担更大的风险。

    均值—方差模型最重要的概念莫过于马科维茨有效边界( Markowitz Efficient Frontier )了,其代表所有最佳投资组合的集合。什么意思呢?简言之,就是其核心思想在图表中的具体表现形式,它代表那些在给定任意一个相同预期回报条件下的风险最低的投资组合。在这一系列的最佳投资组合中,我们再选出一个更好的,那如何衡量这个更好的投资组合呢?我们使用夏普比率( Sharpe Ratio ),夏普值越大,说明单位风险内获得的收益越高。在最佳投资组合中夏普值最高的这一点所构成的资产组合便被称为市场投资组合( Market Portfolio )

    资本市场线( Capital Market Line, CML ) 马科维茨有效前沿曲线上的投资组合里并不包含无风险资产,如果将市场投资组合和无风险资产组合在一起,其便是著名的资本市场线。资本市场线上的每一点代表的投资组合比马科维茨有效边界上的投资组合更优,其能够通过改变市场投资组合和无风险资产之间任意配比而达到资本市场线上的任意一点(前提是允许卖空)。其方程可以表示为:

    我是信息的传递者

    MPT :通过分散投资实现投资组合风险最小化或者在指定风险水平下的组合收益最大化

    In [1]:

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

    In [2]:

    # 选股自选股里面的股票作为说明对象
    symbols = ['600519.XSHG', '600036.XSHG', '000651.XSHE', '002007.XSHE', '600600.XSHG']
    noa = len(symbols)
    

    In [3]:

    startDate = '2013-01-01'
    endDate = '2015-06-01'
    
    data = get_price(symbols, start_date=startDate, end_date=endDate, fields='ClosingPx').dropna(axis=1, how='any')
    data.head()
    

    In [4]:

    #规范化后的不同股票的时间序列图
    (data / data.ix[0]).plot(figsize=(12, 7), linewidth=2)
    

    Out[4]:<matplotlib.axes._subplots.AxesSubplot at 0x7f912261aac8>

    In [5]:

    
    # 均值-方差指的是不同证券(对数)收益的均值和方差,所以这里我们计算出对数收益率
    rets = np.log(data).diff().dropna()
    
    # 这里默认使用 252 个交易日,从每日收益得出年化收益率
    rets.mean() * 252
    
    

    Out [5]: 600519.XSHG 0.159058 600036.XSHG 0.143368 000651.XSHE 0.409322 002007.XSHE 0.492317 600600.XSHG 0.194959 dtype: float64

    In [6]:

    
    # 继续计算收益率的协方差矩阵
    rets.cov() * 252
    
    

    In [7]:

    
    # 由于 A 股目前做空比较麻烦,这里我们只考虑进行多头操作,并且资产权重之和为 1
    
    weights = np.random.random(noa)
    weights /= np.sum(weights)
    
    weights
    
    

    Out[7]:array([ 0.24983943, 0.02577738, 0.42605211, 0.15194329, 0.14638779])

    In [8]:

    
    # 计算预期资产组合收益
    print (np.sum(rets.mean() * weights) * 252)
    
    # 计算投资组合方差
    print (np.dot(weights.T, np.dot(rets.cov() * 252, weights)))
    
    

    0.321171021971 0.0576476704425

    In [9]:

    
    # 这里我们应用蒙特卡洛模拟,生成较大规模的随机投资组合权重向量。对于每一种模拟的分配,我们记录预期投资组合收益和方差
    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)
    
    

    In [10]:

    
    # 根据上面的 MC 结果,我们这里画出收益和方差的散点图
    plt.figure(figsize=(12, 7))
    plt.scatter(pvols, prets, c=prets/pvols, marker='o')
    plt.grid()
    plt.xlabel('Expected Volatility')
    plt.ylabel('Expected Returns')
    plt.colorbar(label='Sharpe Ratio')
    

    Out[10]:<matplotlib.colorbar.Colorbar at 0x7f91176b5e48>

    In [11]:

    
    # 投资组合优化
    def statistics(weights):
        '''
        Returns portfolio statistics.
        
        Parameters
        ===========
        weights: array-like
            weights for different securities in portfolio
            
        Returns
        =======
        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])
    
    

    最大化夏普比率

    In [12]:

    
    # 最优化投资组合的推导是一个约束最优化问题,我们使用 scipy.optimize 子库的 minimize 函数求解
    # 最大化夏普值,或者说最小化夏普指数的负值,约束是所有股票的权重之和为 1
    
    import scipy.optimize as sco
    
    def min_func_sharpe(weights):
        return -statistics(weights)[-1]
    
    cons = ({'type':'eq', 'fun':lambda x: np.sum(x) - 1})
    bnds = tuple((0, 1) for x in range(noa))
    initGuess = noa * [1. / noa, ]
    opts = sco.minimize(min_func_sharpe, initGuess, method='SLSQP', bounds=bnds, constraints=cons)
    opts
    
    

    Out [12]: nfev: 49 fun: -1.5561085657595923 jac: array([ -7.70390034e-05, 1.89436048e-01, -2.55107880e-05, -6.16908073e-06, 1.00284815e-04, 0.00000000e+00]) status: 0 success: True x: array([ 0.03241208, 0. , 0.33520011, 0.49175377, 0.14063404]) message: 'Optimization terminated successfully.' njev: 7 nit: 7

    In [13]:

    
    # 找出最优投资组合的权重
    print (opts['x'].round(3))
    
    # 计算最优投资组合的收益,方差以及夏普值
    print (statistics(opts['x']).round(3))
    
    

    [ 0.032 0. 0.335 0.492 0.141] [ 0.412 0.265 1.556]

    最小化投资组合的方差

    In [14]:

    
    def min_func_variance(weights):
        return statistics(weights)[1] ** 2
    
    optv = sco.minimize(min_func_variance, initGuess, method='SLSQP', bounds=bnds, constraints=cons)
    optv
    
    

    out [14]: nfev: 42 fun: 0.041410632902538005 jac: array([ 0.08288015, 0.08279849, 0.08224446, 0.08318804, 0.08272194, 0. ]) status: 0 success: True x: array([ 0.21769845, 0.18647432, 0.04666651, 0.15638665, 0.39277407]) message: 'Optimization terminated successfully.' njev: 6 nit: 6

    In [15]:

    
    # 找出最优投资组合的权重
    print (optv['x'].round(3))
    
    # 计算最优投资组合的收益,方差以及夏普值
    print (statistics(optv['x']).round(3))
    
    

    [ 0.218 0.186 0.047 0.156 0.393] [ 0.234 0.203 1.15 ]

    有效边界( Efficient Frontier )

    In [16]:

    
    # 有效边界
    cons = ({'type':'eq', 'fun':lambda x : statistics(x)[0] - tret}, {'type' : 'eq', 'fun':lambda x : np.sum(x) - 1})
    bnds = tuple((0, 1) for x in weights)
    
    # 从 statistics 函数返回波动率的值
    def min_func_port(weights):
        return statistics(weights)[1]
    
    trets = np.linspace(0.2, 0.5, 30)
    tvols = []
    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, initGuess, method='SLSQP', bounds=bnds, constraints=cons)
        tvols.append(res['fun'])
    tvols = np.array(tvols)
    tvols
    
    

    Out[16]: array([ 0.20602219, 0.20471964, 0.20388503, 0.20351338, 0.20360723, 0.20416592, 0.20518566, 0.20665984, 0.20857818, 0.21092921, 0.21369842, 0.21686981, 0.22042602, 0.22434874, 0.22861911, 0.23321803, 0.23814288, 0.24345364, 0.24913609, 0.25516538, 0.26151745, 0.26816956, 0.27509983, 0.28230594, 0.28986843, 0.2977758 , 0.31012345, 0.33066182, 0.35798614, 0.36593712])

    In [17]:

    
    plt.figure(figsize=(12, 7))
    
    # 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], 'k*', markersize=15.0)
    
    plt.grid()
    plt.xlabel('Expected Volatitlity')
    plt.ylabel('Expected Return')
    plt.colorbar(label='Sharpe Ratio');
    
    

    资本市场线( CML )

    马科维茨有效组合本身是不包含无风险资产的,如果我们引入无风险资产构建一个新的投资组合,那么便构成了资本市场线,其在给定条件下优于有效边界上的组合(夏普值更大)。通过调整投资于风险资产组合和无风险资产的权重比例,我们可以实现任何风险---收益均衡性,这些收益位于无风险资产和有效投资组合之间的直线上

    In [18]:

    
    import scipy.interpolate as sci
    # 找出最小波动率对应的位置
    ind = np.argmin(tvols)
    
    # 选取有效边界上的点进行插值
    evols = tvols[ind:]
    erets = trets[ind:]
    
    tck = sci.splrep(evols, erets)
    
    

    In [19]:

    
    # 为有效边界定义一个连续可微的函数 f(x)和对应的一阶导数函数 df(x)
    def  f(x):
        '''Efficient frontier function(spline approximation)'''
        return sci.splev(x, tck, der=0)
    
    def df(x):
        '''First derivative of efficient frontier function'''
        return sci.splev(x, tck, der=1)
    
    # 如果这里有看不明白的  欢迎在评论区提问 
    def equations(p, rf=0.05):
        '''p=[a, b, x]  分别表示 CML 的截距,斜率以及波动率'''
        eq1 = rf - p[0]
        eq2 = rf + p[1] * p[2] - f(p[2])
        eq3 = p[1] - df(p[2])
        return eq1, eq2, eq3
    
    
    

    In [20]:

    
    # 解出的无风险利率也恰好是 0.05
    opt = sco.fsolve(equations, [.05, 1, .25])
    opt
    
    

    Out[20]:array([ 0.05 , 1.37419833, 0.28383029])

    In [21]:

    
    # 检验解出来的结果是否正确
    np.round(equations(opt), 6)
    

    Out[21]:array([ 0., 0., 0.])

    In [22]:

    
    plt.figure(figsize=(12, 7))
    plt.scatter(pvols, prets, c = (prets - .05) / pvols, marker='o')
    plt.plot(evols, erets, 'r', lw=4)
    
    # Capital Market Line
    cx = np.linspace(.0, .4)
    plt.plot(cx, opt[0] + opt[1] * cx, lw=1.5)
    
    plt.plot(opt[2], f(opt[2]), 'r*', markersize=15.0)
    plt.grid()
    plt.xlim(0, .5)
    plt.xlabel('Expected Volatility')
    plt.ylabel('Expected Return')
    plt.colorbar(label='Sharpe Ratio');
    
    

    In [ ]:

    新鲜出品:https://www.ricequant.com/community/topic/1568/?utm_source=v2ex? 你一定知道的

    1 条回复    2016-11-18 21:36:36 +08:00
    Satan4869
        1
    Satan4869  
       2016-11-18 21:36:36 +08:00 via iPhone
    还有毛主席说过,让人民拥有土地!
    也是靠不住啊……
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3077 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 13:46 · PVG 21:46 · LAX 05:46 · JFK 08:46
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.