【python量化】如何用Python找到投資時的最佳組合比例

現代投資組合理論(Modern Portfolio Theory,MPT)告訴咱們投資者應該分散投資來實現最小化風險最大化投資回報。大鄧剛開始學習這方面知識,用了將近一天的時候才搞懂MPT理論的推導,順便複習了部分高中數學知識,這樣會讓咱們更加有新信心的去使用本身編寫的代碼。如今咱們從實戰開始接觸理論。

css

項目代碼下載
若是沒時間閱讀,可直接瀏覽至文章末尾獲取本文章的項目代碼html


1、資產組合理論導論

1.1 風險厭惡(Risk aversion)

在投資組合理論中,咱們經常使用方差來刻畫資產的風險。這裏舉個例子,方便你們理解。python

假設你如今將要進行投資,有兩種策略:web

  • 資產A給你帶來的收益是200元,或者0元。每種狀況發生的機率也是各50%算法

  • 資產B給你帶來的收益是400元,或者-200元(虧損)。每種狀況發生的機率也是各50%express

資產的數學指望收益bash

兩種投資策略的波動性-標準差
微信

若是你是風險偏好者,你可能會選擇B,由於B的潛在的最大收益最大。可是MPT理論認爲大多數的投資者是風險厭惡者,不喜歡玩心跳,因此更傾向於選擇A。
app

1.2資產組合池(portfolio)

假設咱們將採用分散投資,每種資產的比例爲 w一、w二、w3...wn,咱們知道全部投資比例之和爲1,即 w1+w2+w3+...+wn=1 。假設R0、R一、R2…Rn分別表明每種資產的收益,則資產組合投資收益爲dom

咱們預期的投資組合收益爲

1.3 相關性

在計算資產組合風險前,咱們須要先回憶一下高中數學中的方差和相關性的計算方法。方差和相關性主要是刻畫任意兩個變量之間的線性關係。

X和Y的方差計算公式

1.4 風險

如今咱們將要計算資產組合的風險,這裏使用 資產組合收益的方差 來刻畫投資風險。

原本大鄧直接看到推導完的結果,可是忽略了中間過程,內心怎麼也不相信。因此花了不少時間用來找推導過程的教程和視頻,終於找到一份比較詳細的,如今貼給你們看。咱們將該公式中的加總用累加zigma符號表示,一步步的進行推導

如今咱們先看看簡單的例子,如何將累加和的平方展開?

咱們再將上面的公式抽象一下

所以,資產組合收益的方差能夠表示爲

最後咱們將上面的符號所有用向量(或矩陣)來表示,這樣後期咱們使用python寫代碼時候能夠直接使用numpy表達。

2、實戰

2.1 加載數據

這裏我參考JoinQuant中的一篇文章,其選取的股票的收益比較符合正太分佈,我就直接拿來用吧。

'000651',       ##格力電器   
'600519',       ##貴州茅臺  
'601318',       ##中國平安               
'000858',       ##五糧液  
'600887',       ##伊利股份  
'000333',       ##美的集團   
'601166',       ##興業銀行  
'601328',       ##交通銀行  
'600104'        ##上汽集團  

咱們使用tushare來獲取股票數據,tushare的get_hist_data(stock_code, start, end)函數獲取stock_code從start到end期間內的全部交易數據,返回的是dataframe類型。這裏咱們都是用close列的數據,即只用收盤價數據。

import tushare as ts

def fetch_stock_data(stock_code, stock_name, start, end):
    df = ts.get_hist_data(stock_code, start=start, end=end) #前復權
    df = df.close
    df.name = stock_name
    return df

geli = fetch_stock_data('000651''格力電器''2016-05-28''2018-03-26')
geli.head()

Run

    date
    2018-03-26    48.13
    2018-03-23    48.88
    2018-03-22    49.90
    2018-03-21    51.70
    2018-03-20    52.20
    Name: 格力電器, dtypefloat64

如今咱們將全部股票都的收盤數據都裝進pandas.DataFrame中,每一列表明一隻股票,每一行表明一天的收盤價

import pandas as pd

data = pd.DataFrame({'格力電器':fetch_stock_data('000651''格力電器''2016-05-28''2018-03-26'), 
                     '貴州茅臺':fetch_stock_data('600519''貴州茅臺''2016-05-28''2018-03-26'), 
                     '中國平安':fetch_stock_data('601318''中國平安''2016-05-28''2018-03-26'),
                     '五糧液':fetch_stock_data('000858''五糧液''2016-05-28''2018-03-26'),
                     '伊利股份':fetch_stock_data('600887''伊利股份''2016-05-28''2018-03-26'),
                     '美的集團':fetch_stock_data('000333''美的集團''2016-05-28''2018-03-26'),
                     '興業銀行':fetch_stock_data('601166''興業銀行''2016-05-28''2018-03-26'),
                     '交通銀行':fetch_stock_data('601328''交通銀行''2016-05-28''2018-03-26'),
                     '上汽集團':fetch_stock_data('600104''上汽集團''2016-05-28''2018-03-26')})
data = data.dropna()
data.head()


data.to_excel('stock_data.xlsx')

我將整理好的數據保存到了 stock_data.xlsx 中,方便你們即便沒法使用tushare也有數據可供分析。

import pandas as pd

data = pd.read_excel('stock_data.xlsx')
data.head()

將每一個股票價格與最初始(2016年10月24日)的價格做比較,並據此獲得以後的股價走勢圖

#將date列從newdata中踢出
date = data.pop('date')
#data.iloc[0, :] 選取第一行的數據
newdata = (data/data.iloc[0, :])*100

昨天咱們分享的可視化使用的pyplotz庫,該庫支持全部基於matplotlib的可視化庫。今天咱們用plotly這個動態可視化庫來做圖,正好複習下前面分享的plotly-express庫,注意plotly-express是基於plotly

from plotly.offline import init_notebook_mode, iplot, plot
import plotly.graph_objs as go

init_notebook_mode()

stocks = ['格力電器''貴州茅臺''中國平安''五糧液''伊利股份''美的集團''興業銀行''交通銀行''上汽集團']

def trace(df, date, stock):
    return go.Scatter(x = date, #橫座標日期
                      y = df[stock],
                      name=stock)#縱座標爲股價與(2016年10月24日)的比值


data = [trace(newdata,date,stock) for stock in stocks]
iplot(data)

2.2 計算不一樣股票的均值、協方差

每一年有252個交易日,用每日收益率乘以252獲得年華收益率。如今須要計算每隻股票的收益率,在金融領域中咱們通常使用對數收益率。這裏體現了pandas的強大,df.pct_change()直接就能獲得股票收益率

import numpy as np

log_returns = np.log(newdata.pct_change()+1)
log_returns = log_returns.dropna()
log_returns.mean()*252

Run

    格力電器    0.582497
    貴州茅臺    0.648666
    中國平安    0.524031
    五糧液     0.561282
    伊利股份    0.352049
    美的集團    0.560136
    興業銀行    0.024539
    交通銀行    0.068539
    上汽集團    0.289760
    dtypefloat64

2.3 進行正態檢驗

馬科維茨的投資組合理論須要知足收益率符合正態分佈,scipy.stats庫爲咱們提供了正態性測試函數

  • scipy.stats.normaltest 測試樣本是否與正態分佈不一樣,返回p值。

import scipy.stats as scs

def normality_test(array):
    print('Norm test p-value %14.3f' % scs.normaltest(array)[1])

for stock in stocks:
    print('\nResults for {}'.format(stock))
    print('-'*32)
    log_data = np.array(log_returns[stock])
    normality_test(log_data)

Run

    Results for 格力電器
    --------------------------------
    Norm test p-value          0.000

    Results for 貴州茅臺
    --------------------------------
    Norm test p-value          0.000

    Results for 中國平安
    --------------------------------
    Norm test p-value          0.000

    Results for 五糧液
    --------------------------------
    Norm test p-value          0.051

    Results for 伊利股份
    --------------------------------
    Norm test p-value          0.000

    Results for 美的集團
    --------------------------------
    Norm test p-value          0.453

    Results for 興業銀行
    --------------------------------
    Norm test p-value          0.000

    Results for 交通銀行
    --------------------------------
    Norm test p-value          0.000

    Results for 上汽集團
    --------------------------------
    Norm test p-value          0.000

從上面的檢驗中,伊利股份和興業銀行股票收益率不符合正態分佈。

2.4 投資組合預期收益率、波動率

咱們先隨機生成一維投資組合權重向量(長度爲9,與股票數量相等),由於中國股市的不容許賣空,因此投資組合權重向量中的數值必須在0到1之間。

weights = np.random.random(9)
weights /= np.sum(weights)
weights
array([0.134031440.117039390.141256590.025306770.10496042,
       0.161061270.051553710.103611310.16117911])

投資組合預期收益率等於每隻股票的權重與其對應股票的年化收益率的乘積。

np.dot(weights, log_returns.mean())*252
0.4035947984701051

投資組合波動率(方差)


np.dot(weights, np.dot(log_returns.cov()*252, weights))
0.035798322938178584

投資組合收益的年化風險(標準差)

np.sqrt(np.dot(weights, np.dot(log_returns.cov()*252, weights)))
0.18920444745877033

2.5 隨機生成大量的投資組合權重

生成1000種隨機的投資組合,即權重weights的尺寸爲(1000*9)。

import matplotlib.pyplot as plt
%matplotlib inline


port_returns = []
port_variance = []
for p in range(1000):
    weights = np.random.random(9)
    weights /=np.sum(weights)
    port_returns.append(np.sum(log_returns.mean()*252*weights))
    port_variance.append(np.sqrt(np.dot(weights.T, np.dot(log_returns.cov()*252, weights))))

port_returns = np.array(port_returns)
port_variance = np.array(port_variance)

#無風險利率設定爲3%
risk_free = 0.03
plt.figure(figsize=(86))
plt.scatter(port_variance, port_returns, c=(port_returns-risk_free)/port_variance, marker = 'o')
plt.grid(True)
plt.xlabel('Expected Volatility')
plt.ylabel('Expected Return')
plt.colorbar(label = 'Sharpe Ratio')

2.5.1 投資組合優化1—夏普率最大

創建statss函數來記錄重要的投資組合統計數據(收益,方差和夏普比)。scipy.optimize能夠提供給咱們最小優化算法,而最大化夏普率能夠轉化爲最小化負的夏普率。

import scipy.optimize as sco

def stats(weights):
    weights = np.array(weights)
    port_returns = np.sum(log_returns.mean()*weights)*252
    port_variance = np.sqrt(np.dot(weights.T, np.dot(log_returns.cov()*252,weights)))
    return np.array([port_returns, port_variance, port_returns/port_variance])

#最小化夏普指數的負值
def min_sharpe(weights):
    return -stats(weights)[2]

#給定初始權重
x0 = 9*[1./9]

#權重(某股票持倉比例)限制在0和1之間。
bnds = tuple((0,1for x in range(9))

#權重(股票持倉比例)的總和爲1。
cons = ({'type':'eq''fun':lambda x: np.sum(x)-1})

#優化函數調用中忽略的惟一輸入是起始參數列表(對權重的初始猜想)。咱們簡單的使用平均分佈。
opts = sco.minimize(min_sharpe,
                    x0, 
                    method = 'SLSQP'
                    bounds = bnds, 
                    constraints = cons)
opts

Run

         fun: -2.7277947674404794
         jac: array([ 1.08440101e-01-4.65214252e-05,  3.44902277e-04,  7.20474541e-01,
            5.15412062e-01-2.46465206e-05,  3.42730999e-01-1.48534775e-04,
           -5.43147326e-04])
     message: 'Optimization terminated successfully.'
        nfev: 111
         nit: 10
        njev: 10
      status: 0
     success: True
           x: array([1.07588996e-164.93897426e-012.37878143e-016.47455750e-17,
           5.74725095e-171.14655596e-018.60056411e-176.88721816e-02,
           8.46966532e-02])

最優投資組合權重向量,小數點保留3位

opts['x'].round(3)
array([0.   , 0.4940.2380.   , 0.   , 0.1150.   , 0.0690.085])

sharpe最大的組合3個統計數據分別爲:

stats(opts['x']).round(3)
array([0.5390.1972.728])

2.5.2 投資組合優化2——方差最小

接下來,咱們經過方差最小來選出最優投資組合。

#可是咱們定義一個函數對 方差進行最小化
def min_variance(weights):
    return stats(weights)[1]

optv = sco.minimize(min_variance, 
                    x0, 
                    method = 'SLSQP'
                    bounds = bnds, 
                    constraints = cons)
optv

Run

         fun: 0.1260429626044057
         jac: array([0.151275110.126276050.1532943 , 0.147435940.12581278,
           0.1258321 , 0.126123210.1258938 , 0.12583541])
     message: 'Optimization terminated successfully.'
        nfev: 88
         nit: 8
        njev: 8
      status: 0
     success: True
           x: array([0.00000000e+002.13881938e-010.00000000e+008.07576429e-18,
           3.06402571e-021.40860179e-023.22191709e-013.65127109e-01,
           5.40729695e-02])

方差最小的最優投資組合權重向量

optv['x'].round(3)
array([0.   , 0.2140.   , 0.   , 0.0310.0140.3220.3650.054])
獲得的投資組合預期收益率、波動率和夏普指數
stats(optv['x']).round(3)
array([0.2060.1261.634])

2.5.3 資產組合的有效邊界

有效邊界是由一系列既定的目標收益率下方差最小的投資組合點組成的。在最優化時採用兩個約束,1.給定目標收益率,2.投資組合權重和爲1。

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

#在不一樣目標收益率水平(target_returns)循環時,最小化的一個約束條件會變化。
target_returns = np.linspace(0.0,0.5,50)
target_variance = []
for tar in target_returns:
    #給定限制條件:給定收益率、投資組合權重之和爲1
    cons = ({'type':'eq','fun':lambda x:stats(x)[0]-tar},{'type':'eq','fun':lambda x:np.sum(x)-1})
    res = sco.minimize(min_variance, x0, method = 'SLSQP', bounds = bnds, constraints = cons)
    target_variance.append(res['fun'])

target_variance = np.array(target_variance)

下面是最優化結果的展現。

叉號:構成的曲線是有效前沿(目標收益率下最優的投資組合)

紅星:sharpe最大的投資組合

黃星:方差最小的投資組合

plt.figure(figsize = (8,4))
#圓點:隨機生成的投資組合散佈的點
plt.scatter(port_variance, port_returns, c = port_returns/port_variance,marker = 'o')
#叉號:投資組合有效邊界
plt.scatter(target_variance,target_returns, c = target_returns/target_variance, marker = 'x')
#紅星:標記夏普率最大的組合點
plt.plot(stats(opts['x'])[1], stats(opts['x'])[0], 'r*', markersize = 15.0)
#黃星:標記方差最小投資組合點
plt.plot(stats(optv['x'])[1], stats(optv['x'])[0], 'y*', markersize = 15.0)
plt.grid(True)
plt.xlabel('expected volatility')
plt.ylabel('expected return')
plt.colorbar(label = 'Sharpe ratio')

從黃色五角星到紅色五角星是投資最有效的組合,這一系列的點所組成的邊界就叫作 投資有效邊界 。這條邊界的特色是一樣的風險的狀況下得到的收益最大,一樣的收益水平風險是最小的。從這條邊界也印證了風險與收益成正比,要想更高的收益率就請承擔更大的風險,但若是落在投資有效邊界上,性價比最高。

參考文章

  1. 陳小米.正態性檢驗和蒙特卡洛完成投資組合優化. http://t.cn/EJSdrgG

  2. 定立在簡書.最優風險資產組合-Python筆記. https://www.jianshu.com/p/0363bc4fdad4

  3. 王小川.《Python與量化投資:從基礎到實戰》

  4. Introduction to Financial Python-Modern Portfolio Theory http://t.cn/EJSeiPo

精選文章

大神kennethreitz寫出requests-html,號稱爲人設計的網頁解析庫

少有人知的python數據科學庫


Python系列課(爬蟲、文本分析、機器學習)視頻教程

100G 文本分析語料資源(免費下載) 

手把手教你學會LDA話題模型可視化pyLDAvis庫

【工具篇】如何用Google Colab高效的學習Python

爬蟲實戰:抓取知乎問題「大學生如何賺到一萬元」

使用Python製做WORD報告 

使用Pandas、Jinja和WeasyPrint製做pdf報告  

有了ta,不再用爲數據分析發愁


公衆號後臺回覆」20190327"得本項目代碼

我就但願你給我一個好看




本文分享自微信公衆號 - 大鄧和他的Python(DaDengAndHisPython)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索