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)化后的公式為:
其中 p 為投資人的投資目標(biāo),即投資人期待的投資組合的期望值. 目標(biāo)函數(shù)說明投資人資產(chǎn)分配的原則是在達(dá)成投資目標(biāo)
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)方法求解诵竭。
有效前緣曲線如下圖:
3. 二次規(guī)劃求解方法介紹
3.1 等式約束凸二次規(guī)劃的解法
我們考慮如下的二次規(guī)劃問題
運(yùn)用拉格朗日方法求解,可以得到
再看公式(1),則將目標(biāo)函數(shù)由 min WTW 調(diào)整為 min 1/2(WT
W), 兩問題等價(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)
求解時(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), 華夏銀行, 浙能電力, 福建高速, 生益科技
先大體了解一下五支股票的收益率情況:
看來卵慰,20%的預(yù)期收益是達(dá)不到了超棺。
接下來,我們來看五支股票的相關(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ì)比,得到:
可以看出罢荡,在前半段大部分時(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è)出版社