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

1. 問題描述

現(xiàn)假設(shè)有A, B, C, D, E五只股票的收益率數(shù)據(jù)((第二日收盤價(jià)-第一日收盤價(jià))/第一日收盤價(jià))), 如果投資人的目標(biāo)是達(dá)到20%的年收益率,那么該如何進(jìn)行資產(chǎn)配置,才能使得投資的風(fēng)險(xiǎ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)稱MPT),為現(xiàn)代西方證券投資理論奠定了基礎(chǔ)俭驮。其基本思想是监嗜,證券投資的風(fēng)險(xiǎn)在于證券投資收益的不確定性。如果將收益率視為一個(gè)數(shù)學(xué)上的隨機(jī)變量的話,證券的期望收益是該隨機(jī)變量的數(shù)學(xué)期望(均值)温数,而風(fēng)險(xiǎn)可以用該隨機(jī)變量的方差來表示撞牢。

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

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

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


公式(1)

其中 \overline{R}p 為投資人的投資目標(biāo),即投資人期待的投資組合的期望值. 目標(biāo)函數(shù)說明投資人資產(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)問題,在允許賣空的情況下(即wi可以為負(fù),只有等式約束)時(shí),可以用拉格朗日(Lagrange)方法求解诵竭。

有效前緣曲線如下圖:

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

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

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

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


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

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

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


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

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

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

求解時(shí),將對(duì)應(yīng)的P,q,G,h,A,b寫出,帶入求解函數(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ì)收益率曲線

看來卵慰,20%的預(yù)期收益是達(dá)不到了超棺。

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

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

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

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

繪制的有效前緣曲線為:

有效前緣曲線

將數(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ù)不夠或者沒有動(dòng)態(tài)調(diào)倉造成的区赵,在后面寫策略的時(shí)候惭缰,我會(huì)加入動(dòng)態(tài)調(diào)倉的部分。

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ù)笼才,參見“歷史行情指標(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類,該類可以根據(jù)輸入的收益率序列,求解二次規(guī)劃問題,計(jì)算出最優(yōu)資產(chǎn)比例,并繪制最小方差前緣曲線
#定義 MeanVariance類
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類型與矩陣直接相乘,得到的結(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ù),可以寫成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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市骡送,隨后出現(xiàn)的幾起案子昂羡,更是在濱河造成了極大的恐慌絮记,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虐先,死亡現(xiàn)場(chǎng)離奇詭異怨愤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蛹批,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門憔四,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人般眉,你說我怎么就攤上這事了赵。” “怎么了甸赃?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵柿汛,是天一觀的道長。 經(jīng)常有香客問我埠对,道長络断,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任项玛,我火速辦了婚禮貌笨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘襟沮。我一直安慰自己锥惋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布开伏。 她就那樣靜靜地躺著膀跌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪固灵。 梳的紋絲不亂的頭發(fā)上捅伤,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音巫玻,去河邊找鬼丛忆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛仍秤,可吹牛的內(nèi)容都是我干的熄诡。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼徒扶,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼粮彤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起姜骡,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤导坟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后圈澈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惫周,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年康栈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了递递。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡啥么,死狀恐怖登舞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情悬荣,我是刑警寧澤菠秒,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站氯迂,受9級(jí)特大地震影響践叠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嚼蚀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一禁灼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧轿曙,春花似錦弄捕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至舟扎,卻和暖如春分飞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背睹限。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國打工譬猫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人羡疗。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓染服,卻偏偏與公主長得像,于是被迫代替她去往敵國和親叨恨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子柳刮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容