python實(shí)現(xiàn)資產(chǎn)配置(1)----Markowitz 投資組合模型

1. 問(wèn)題描述

現(xiàn)假設(shè)有A, B, C, D, E五只股票的收益率數(shù)據(jù)((第二日收盤(pán)價(jià)-第一日收盤(pán)價(jià))/第一日收盤(pán)價(jià))), 如果投資人的目標(biāo)是達(dá)到20%的年收益率,那么該如何進(jìn)行資產(chǎn)配置,才能使得投資的風(fēng)險(xiǎn)最低?

更一般的問(wèn)題,假設(shè)現(xiàn)有x1,x2,...,xn, n支風(fēng)險(xiǎn)資產(chǎn),且收益率已知,如果投資人的預(yù)期收益為goalRet,那么該如何進(jìn)行資產(chǎn)配置,才能使得投資的風(fēng)險(xiǎn)最低?

2. Markowitz 投資組合理論簡(jiǎn)介

1952年,芝加哥大學(xué)的Markowitz提出現(xiàn)代資產(chǎn)組合理論(Modern Portfolio Theory,簡(jiǎn)稱(chēng)MPT),為現(xiàn)代西方證券投資理論奠定了基礎(chǔ)。其基本思想是,證券投資的風(fēng)險(xiǎn)在于證券投資收益的不確定性。如果將收益率視為一個(gè)數(shù)學(xué)上的隨機(jī)變量的話,證券的期望收益是該隨機(jī)變量的數(shù)學(xué)期望(均值),而風(fēng)險(xiǎn)可以用該隨機(jī)變量的方差來(lái)表示。

對(duì)于投資組合而言,如何分配各種證券上的投資比例,從而使風(fēng)險(xiǎn)最小而收益最大?

答案是將投資比例設(shè)定為變量,通過(guò)數(shù)學(xué)規(guī)劃,對(duì)每一固定收益率求最小方差,對(duì)每一個(gè)固定的方差求最大收益率,這個(gè)多元方程的解可以決定一條曲線,這條曲線上的每一個(gè)點(diǎn)都對(duì)應(yīng)著最優(yōu)投資組合,即在給定風(fēng)險(xiǎn)水平下,收益率最大,這條曲線稱(chēng)作“有效前沿” (Efficient Frontier)。

對(duì)投資者而言,不存在比有效前沿更優(yōu)的投資組合,只需要根據(jù)自己的風(fēng)險(xiǎn)偏好在有效前沿上尋找最優(yōu)策略。
簡(jiǎn)化后的公式為:


公式(1)

其中 \overline{R}p 為投資人的投資目標(biāo),即投資人期待的投資組合的期望值. 目標(biāo)函數(shù)說(shuō)明投資人資產(chǎn)分配的原則是在達(dá)成投資目標(biāo)\overline{R}p的前提下,要將資產(chǎn)組合的風(fēng)險(xiǎn)最小化,這個(gè)公式就是Markowitz在1952年發(fā)表的'Portfolio Selection'一文的精髓,該文奠定了現(xiàn)代投資組合理論的基礎(chǔ),也為Markowitz贏得了1990年的諾貝爾經(jīng)濟(jì)學(xué)獎(jiǎng). 公式(1)中的決策變量為wi, i = 1,...,N, 整個(gè)數(shù)學(xué)形式是二次規(guī)劃(Quadratic Programming)問(wèn)題,在允許賣(mài)空的情況下(即wi可以為負(fù),只有等式約束)時(shí),可以用拉格朗日(Lagrange)方法求解。

有效前緣曲線如下圖:

只考慮風(fēng)險(xiǎn)資產(chǎn)的效率前緣

3. 二次規(guī)劃求解方法介紹

3.1 等式約束凸二次規(guī)劃的解法

我們考慮如下的二次規(guī)劃問(wèn)題


運(yùn)用拉格朗日方法求解,可以得到

再看公式(1),則將目標(biāo)函數(shù)由 min WT\SigmaW 調(diào)整為 min 1/2(WT\SigmaW), 兩問(wèn)題等價(jià),寫(xiě)出的求解矩陣為:

3.2 含有不等式約束的凸二次規(guī)劃的解法


關(guān)于該問(wèn)題的解法,可利用KKT條件來(lái)進(jìn)行求解,具體的方法可以參考:
拉格朗日乘子法和KKT條件,這里給大家簡(jiǎn)單介紹一下python中求解二次規(guī)劃問(wèn)題的包 CVXOPT。

工具包: CVXOPT python凸優(yōu)化包
函數(shù)原型: CVXOPT.solvers.qp(P,q,G,h,A,b)

二次規(guī)劃問(wèn)題的標(biāo)準(zhǔn)形式

求解時(shí),將對(duì)應(yīng)的P,q,G,h,A,b寫(xiě)出,帶入求解函數(shù)即可.值得注意的是輸入的矩陣必須使用CVXOPT 中的matrix函數(shù)轉(zhuǎn)化,輸出的結(jié)果要使用 print(CVXOPT.solvers.qp(P,q,G,h,A,b)['x']) 函數(shù)才能輸出。

4. 實(shí)例

這里選取五支股票2014-01-01到2015-01-01的收益率數(shù)據(jù)進(jìn)行分析.
選取的五支股票分別為: 白云機(jī)場(chǎng), 華夏銀行, 浙能電力, 福建高速, 生益科技

先大體了解一下五支股票的收益率情況:


日收益率曲線
累計(jì)收益率曲線

看來(lái),20%的預(yù)期收益是達(dá)不到了。

接下來(lái),我們來(lái)看五支股票的相關(guān)系數(shù)矩陣:

五支股票的收益率相關(guān)系數(shù)矩陣

可以看出,白云機(jī)場(chǎng)和福建高速的相關(guān)性較高,因?yàn)槎咄瑢儆诮煌ò鎵K。在資產(chǎn)配置時(shí),不利于降低非系統(tǒng)性風(fēng)險(xiǎn)。

接下來(lái)編寫(xiě)一個(gè)MeanVariance類(lèi),對(duì)于傳入的收益率數(shù)據(jù),可以進(jìn)行給定預(yù)期收益的最佳持倉(cāng)配比求解以及有效前緣曲線的繪制。

繪制的有效前緣曲線為:

有效前緣曲線

將數(shù)據(jù)分為訓(xùn)練集和測(cè)試集,并將隨機(jī)模擬的資產(chǎn)配比求得的累計(jì)收益與測(cè)試集的數(shù)據(jù)進(jìn)行對(duì)比,得到:

收益率對(duì)比

可以看出,在前半段大部分時(shí)間用Markowitz模型計(jì)算出的收益率要高于隨機(jī)模擬的組合,然而在后半段卻不如隨機(jī)模擬的數(shù)據(jù),可能是訓(xùn)練的數(shù)據(jù)不夠或者沒(méi)有動(dòng)態(tài)調(diào)倉(cāng)造成的,在后面寫(xiě)策略的時(shí)候,我會(huì)加入動(dòng)態(tài)調(diào)倉(cāng)的部分。

5. 代碼

股票分析部分:

# -*- coding: utf-8 -*-
# @Time    : 2019/2/9 0:05
# @Author  : Arron Zhang
# @Email   : 549144697@qq.com
# @File    : plot return rate.py
# @Software: PyCharm

import pandas as pd
import baostock as bs
import matplotlib
from matplotlib import pyplot as plt
import numpy as np

#獲取給定時(shí)間段的股票交易信息
def get_stock_data(t1,t2,stock_name):
    lg = bs.login()
    print('login respond error_code:' + lg.error_code)
    print('login respond  error_msg:' + lg.error_msg)

    #### 獲取滬深A(yù)股歷史K線數(shù)據(jù) ####
    # 詳細(xì)指標(biāo)參數(shù),參見(jiàn)“歷史行情指標(biāo)參數(shù)”章節(jié)
    rs = bs.query_history_k_data(stock_name,
                                 "date,code,open,high,low,close,preclose,volume,amount,adjustflag,turn,tradestatus,pctChg,isST",
                                 start_date=t1, end_date=t2,
                                 frequency="d", adjustflag="3")
    print('query_history_k_data respond error_code:' + rs.error_code)
    print('query_history_k_data respond  error_msg:' + rs.error_msg)

    #### 打印結(jié)果集 ####
    data_list = []
    while (rs.error_code == '0') & rs.next():
        # 獲取一條記錄,將記錄合并在一起
        data_list.append(rs.get_row_data())
    result = pd.DataFrame(data_list, columns=rs.fields)
    print(result)

    #### 結(jié)果集輸出到csv文件 ####
    result.to_csv("D:\stockdata\history_A_stock_k_data.csv", index=False)
    print(result)

    #### 登出系統(tǒng) ####
    bs.logout()
    result['date'] = pd.to_datetime(result['date'])
    result.set_index("date", inplace=True)
    return result

byjc = get_stock_data('2014-1-1','2015-1-1','sh.600004')
hxyh = get_stock_data('2014-1-1','2015-1-1','sh.600015')
zndl = get_stock_data('2014-1-1','2015-1-1','sh.600023')
fjgs = get_stock_data('2014-1-1','2015-1-1','sh.600033')
sykj = get_stock_data('2014-1-1','2015-1-1','sh.600183')


by = byjc['pctChg']
by.name = 'byjc'
by = pd.DataFrame(by,dtype=np.float)/100


hx = hxyh['pctChg']
hx.name = 'hxyh'
hx = pd.DataFrame(hx,dtype=np.float)/100

zn = zndl['pctChg']
zn.name = 'zndl'
zn = pd.DataFrame(zn,dtype=np.float)/100

fj = fjgs['pctChg']
fj.name = 'fjgs'
fj = pd.DataFrame(fj,dtype=np.float)/100

sy = sykj['pctChg']
sy.name = 'sykj'
sy = pd.DataFrame(sy,dtype=np.float)/100

sh_return = pd.concat([by,fj,hx,sy,zn],axis=1)

#了解各股票的收益率狀況,并作圖
sh_return = sh_return.dropna()
cumreturn = (1+ sh_return).cumprod()
sh_return.plot()
plt.title('Daily Return of 5 Stocks(2014-2015)')
plt.legend(bbox_to_anchor = (0.5,-0.3),ncol = 5,fancybox = True,shadow = True)
cumreturn.plot()
plt.title('Cumulative Return of 5 stocks(2014-2015)')

Markowitz 投資組合模型求解

# -*- coding: utf-8 -*-
# @Time    : 2019/2/11 11:10
# @Author  : Arron Zhang
# @Email   : 549144697@qq.com
# @File    : Markowitz portfolio selection.py
# @Software: PyCharm

#構(gòu)建一個(gè)MeanVariance類(lèi),該類(lèi)可以根據(jù)輸入的收益率序列,求解二次規(guī)劃問(wèn)題,計(jì)算出最優(yōu)資產(chǎn)比例,并繪制最小方差前緣曲線
#定義 MeanVariance類(lèi)
from matplotlib import pyplot as plt
from scipy import linalg
import numpy as np
import ffn
import cvxopt
from cvxopt import matrix
class MeanVariance:
    #定義構(gòu)造器,傳入收益率數(shù)據(jù)(dataframe格式的每日收益率)
    def __init__(self,returns):
        self.returns = returns

    #定義最小化方差的函數(shù),即求解二次規(guī)劃
    def minVar(self,goalRet):
        covs = np.array(self.returns.cov())
        means = np.array(self.returns.mean())
        L1 = np.append(np.append(covs.swapaxes(0,1),[means],axis=0),
                       [np.ones(len(means))],axis=0).swapaxes(0,1)

        L2 = list(np.ones(len(means)))
        L2.extend([0,0])
        L3 = list(means)
        L3.extend([0,0])
        L4 = np.array([L2,L3])
        L = np.append(L1,L4,axis=0)
        results = linalg.solve(L,np.append(np.zeros(len(means)),[1,goalRet]))

        return np.array([list(self.returns.columns), results[:-2]])

    #定義繪制最小方差前緣曲線函數(shù)
    def frontierCurve(self):
        goals = [x/500000 for x in range(-100,4000)]
        variances = list(map(lambda x: self.calVar(self.minVar(x)[1,:].astype(np.float)),goals))
        plt.plot(variances,goals)

    #給定各資產(chǎn)的比例,計(jì)算收益率的均值
    def meanRet(self,fracs):
        meanRisky = ffn.to_returns(self.returns).mean()
        #不符合條件時(shí),彈出錯(cuò)誤
        assert len(meanRisky) == len(fracs), 'length of fractions must be equal to number of assets'
        return np.sum(np.multiply(meanRisky,np.array(fracs)))

    #給定各資產(chǎn)的比例,計(jì)算收益率方差
    def calVar(self,fracs):
        #np.dot 可以將dataframe類(lèi)型與矩陣直接相乘,得到的結(jié)果是array
        return (np.dot(np.dot(fracs,self.returns.cov()),fracs))

    #sim_weight = np.apply_along_axis(lambda x: x/sum(x),1,xim_weight)
    #apply_along_axis可以將函數(shù)作用于矩陣的行或者列
    #lambda函數(shù)的作用是對(duì)傳入的參數(shù)直接給出結(jié)果,如果需要傳入多個(gè)參數(shù),可以寫(xiě)成array的形式傳入,此案列中應(yīng)用到了對(duì)數(shù)據(jù)的標(biāo)準(zhǔn)化中

    def solve_quadratic_problem(self,goal):
        covs = np.array(self.returns.cov())
        means = np.array(self.returns.mean())
        P = matrix(np.dot(2,covs))
        Q = matrix(np.zeros((len(means),1)))
        G = -matrix(np.zeros((len(means),len(means))))
        A = matrix(np.append([np.ones(len(means))],[means],axis=0))
        h = matrix(np.zeros((len(means),1)))
        b = matrix(np.array([[1,goal]]).swapaxes(0,1))
        sol = cvxopt.solvers.qp(P, Q, G , h, A, b)
        return sol

6. 參考資料

蔡立耑:量化投資——以python為工具. 電子工業(yè)出版社

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容