用Python分析多股票的投資組合

俗話說不要將所有的雞蛋放在同一個籃子里,在投資股票的時候我們也會多買幾只以抵抗風(fēng)險。本文將帶領(lǐng)著你使用Python,來分析多只股票投資時的收益和風(fēng)險,并找到最優(yōu)的投資組合方案。這是上一篇文章《用Python分析股票的收益和風(fēng)險》 的多股票升級版本。

本文目錄如下:

一、股票數(shù)據(jù)的在線獲取

二、投資組合的收益計算

三、相關(guān)性和協(xié)方差

四、尋找最優(yōu)的投資組合

一、股票數(shù)據(jù)的在線獲取

我們打算用以下9家大公司的股票構(gòu)建投資組合,并用2017年的歷史數(shù)據(jù)進(jìn)行回溯測試。

公司名 股票代碼
Apple AAPL
Microsoft MSFT
Exxon Mobil XOM
Johnson & Johnson JNJ
JP Morgan JPM
Amazon AMZN
General Electric GE
Facebook FB
AT&T T

在具體分析前,還是先導(dǎo)入將要用到的Python包。

import pandas as pd  
import numpy as np
import quandl   # 獲取股票數(shù)據(jù)

from datetime import date
import matplotlib.pyplot as plt
%config InlineBackend.figure_format = 'retina'

我們使用上面導(dǎo)入的 quandl 包從網(wǎng)絡(luò)獲取相應(yīng)的股票數(shù)據(jù),并將每日調(diào)整后的收盤價存入數(shù)據(jù)框 StockPrices 變量中,具體獲取方法可參見《如何用Python下載金融數(shù)據(jù)》一文。

# 創(chuàng)建空的DataFrame變量,用于存儲股票數(shù)據(jù)
StockPrices = pd.DataFrame()

# 設(shè)置股票數(shù)據(jù)的開始和結(jié)束的時間
start = date(2016,12,30)
end = date(2017,12,31)

# 創(chuàng)建股票代碼的列表
ticker_list = ['AAPL', 'MSFT', 'XOM', 'JNJ', 'JPM', 'AMZN', 'GE', 'FB', 'T']

# 使用循環(huán),挨個獲取每只股票的數(shù)據(jù),并存儲調(diào)整后的收盤價
for ticker in ticker_list:
    data = quandl.get('WIKI/'+ticker, start_date=start, end_date=end)
    StockPrices[ticker] = data['Adj. Close']  # 注意 Adj. 和 Close 之間有一空格
    
# 輸出數(shù)據(jù)的前5行
print(StockPrices.head())
                  AAPL       MSFT        XOM         JNJ        JPM    AMZN  \
Date                                                                          
2016-12-30  114.389454  60.788710  86.960273  112.310940  84.383167  749.87   
2017-01-03  114.715378  61.219142  87.567241  112.925087  85.302395  753.67   
2017-01-04  114.586983  60.945231  86.603799  112.739868  85.458859  757.18   
2017-01-05  115.169696  60.945231  85.312787  113.919421  84.672217  780.45   
2017-01-06  116.453639  61.473488  85.264615  113.373512  84.682050  795.99   

                   GE      FB          T  
Date                                      
2016-12-30  30.782801  115.05  40.476170  
2017-01-03  30.870473  116.86  40.942507  
2017-01-04  30.880215  118.69  40.704580  
2017-01-05  30.704870  120.67  40.590375  
2017-01-06  30.792542  123.41  39.790940  

這里用行來記錄每一天的數(shù)據(jù),用列記錄每只股票的收盤價。然后計算每天的收益率,將數(shù)據(jù)存儲在數(shù)據(jù)框 StockReturns 變量中。收益率的具體計算方法可參見文章《用Python分析股票的收益和風(fēng)險》

# 計算每日收益率,并丟棄缺失值
StockReturns = StockPrices.pct_change().dropna()
# 打印前5行數(shù)據(jù)
print(StockReturns.head())
                AAPL      MSFT       XOM       JNJ       JPM      AMZN  \
Date                                                                     
2017-01-03  0.002849  0.007081  0.006980  0.005468  0.010893  0.005068   
2017-01-04 -0.001119 -0.004474 -0.011002 -0.001640  0.001834  0.004657   
2017-01-05  0.005085  0.000000 -0.014907  0.010463 -0.009205  0.030732   
2017-01-06  0.011148  0.008668 -0.000565 -0.004792  0.000116  0.019912   
2017-01-09  0.009160 -0.003183 -0.016497 -0.000172  0.000697  0.001168   

                  GE        FB         T  
Date                                      
2017-01-03  0.002848  0.015732  0.011521  
2017-01-04  0.000316  0.015660 -0.005811  
2017-01-05 -0.005678  0.016682 -0.002806  
2017-01-06  0.002855  0.022707 -0.019695  
2017-01-09 -0.004745  0.012074 -0.012585  

至此,我們已經(jīng)準(zhǔn)備好了用于分析的數(shù)據(jù) StockReturns, 它記錄了9只股票2017年每天的收益率。如果你想跳過以上步驟直接開始分析的話,也可以下載我為你準(zhǔn)備好的數(shù)據(jù)(點我哦!),再用以下代碼讀取。

# 從CSV文件讀取數(shù)據(jù)
StockReturns = pd.read_csv('StockReturns2017.csv', parse_dates=['Date'], index_col='Date')

二、投資組合的收益計算

我們選了9只股票,可資金怎么分配呢?哪只買多些,哪只買少些?這就需要對它們設(shè)置相應(yīng)的權(quán)重,下面我們采用三種權(quán)重分配的方案,來計算不同組合下的投資收益。

2.1 給定權(quán)重的投資組合

第一種方案是預(yù)先設(shè)置一組權(quán)重,如下所示,注意所有股票權(quán)重的和為1。

公司名 股票代碼 權(quán)重
Apple AAPL 12%
Microsoft MSFT 15%
Exxon Mobil XOM 8%
Johnson & Johnson JNJ 5%
JP Morgan JPM 9%
Amazon AMZN 10%
General Electric GE 11%
Facebook FB 14%
AT&T T 16%

我們將每只股票的收益,乘上其對應(yīng)的權(quán)重,得到加權(quán)后的股票收益;再對所有股票加權(quán)后的收益求和,得到該組合投資的收益。

# 設(shè)置組合權(quán)重,存儲為numpy數(shù)組類型
portfolio_weights = np.array([0.12, 0.15, 0.08, 0.05, 0.09, 0.10, 0.11, 0.14, 0.16])

# 將收益率數(shù)據(jù)拷貝到新的變量 stock_return 中,這是為了后續(xù)調(diào)用的方便
stock_return = StockReturns.copy()

# 計算加權(quán)的股票收益
WeightedReturns = stock_return.mul(portfolio_weights, axis=1)

# 計算投資組合的收益
StockReturns['Portfolio'] = WeightedReturns.sum(axis=1)

# 繪制組合收益隨時間變化的圖
StockReturns.Portfolio.plot()
plt.show()

以上繪制了該組合投資收益隨時間變化的圖,顯得有些凌亂,因為畫的是每天的收益。如果把每天的收益進(jìn)行累積,可以繪制如下常見的收益曲線。

# 計算累積的組合收益,并繪圖
CumulativeReturns = ((1+StockReturns["Portfolio"]).cumprod()-1)
CumulativeReturns.plot()
plt.show()

因為后面我們會不斷繪制這樣的累積收益曲線,所以將繪制的代碼寫成函數(shù) cumulative_returns_plot(),方便后續(xù)調(diào)用。

# 累積收益曲線繪制函數(shù)
def cumulative_returns_plot(name_list):
    for name in name_list:
        CumulativeReturns = ((1+StockReturns[name]).cumprod()-1)
        CumulativeReturns.plot(label=name)
    plt.legend()
    plt.show()

2.2 等權(quán)重的投資組合

第二種方案是平均分配每只股票的權(quán)重,使它們都相等。這是最簡單的投資方法,可作為其他投資組合的參考基準(zhǔn)。計算方法和上面一致,只需更改存儲權(quán)重的數(shù)組。

# 設(shè)置投資組合中股票的數(shù)目
numstocks = 9

# 平均分配每一項的權(quán)重
portfolio_weights_ew = np.repeat(1/numstocks, numstocks)

# 計算等權(quán)重組合的收益
StockReturns['Portfolio_EW'] = stock_return.mul(portfolio_weights_ew, axis=1)  \
                                           .sum(axis=1)

# 繪制累積收益曲線
cumulative_returns_plot(['Portfolio', 'Portfolio_EW'])

上圖中藍(lán)色曲線代表第一種方案的累積收益,橙色曲線代表等權(quán)重組合的累積收益,它們倆比較接近,說明第一種方案設(shè)置的權(quán)重并沒有多少優(yōu)勢,我們還需要尋找更好的投資組合。

1.3 市值加權(quán)的投資組合

第三種法案是考慮了公司的市值,按市值的占比來分配權(quán)重。因此市值高的公司對應(yīng)的權(quán)重就更大,當(dāng)這些大公司的股票表現(xiàn)良好時,該投資組合的表現(xiàn)也更好。眾所周知的標(biāo)普500指數(shù)就是按照市值進(jìn)行加權(quán)計算的。

以下列出了這9家公司2017年1月前的市值(單位:10億美元)。

公司名 股票代碼 市值
Apple AAPL 601.51
Microsoft MSFT 469.25
Exxon Mobil XOM 349.5
Johnson & Johnson JNJ 310.48
JP Morgan JPM 299.77
Amazon AMZN 356.94
General Electric GE 268.88
Facebook FB 331.57
AT&T T 246.09
# 創(chuàng)建市值的數(shù)組
market_capitalizations = np.array([601.51, 469.25, 349.5, 310.48, 299.77, 
                                   356.94, 268.88, 331.57, 246.09])

# 計算市值權(quán)重
mcap_weights = market_capitalizations / np.sum(market_capitalizations)

# 計算市值加權(quán)的組合收益
StockReturns['Portfolio_MCap'] = stock_return.mul(mcap_weights, axis=1).sum(axis=1)
cumulative_returns_plot(['Portfolio', 'Portfolio_EW', 'Portfolio_MCap'])

上圖中綠色曲線代表市值加權(quán)的組合投資,這里它的表現(xiàn)明顯優(yōu)于前兩種方案。

三、相關(guān)性和協(xié)方差

3.1 相關(guān)矩陣

相關(guān)矩陣用于估算多只股票收益之間的線性關(guān)系,可使用pandas數(shù)據(jù)框內(nèi)建的 .corr() 方法來計算。

# 計算相關(guān)矩陣
correlation_matrix = stock_return.corr()

# 輸出相關(guān)矩陣
print(correlation_matrix)
          AAPL      MSFT       XOM       JNJ       JPM      AMZN        GE  \
AAPL  1.000000  0.436104  0.063337  0.030635  0.197596  0.506639  0.002801   
MSFT  0.436104  1.000000  0.085906  0.227071  0.195753  0.621833 -0.044486   
XOM   0.063337  0.085906  1.000000  0.132549  0.300892  0.021084  0.180453   
JNJ   0.030635  0.227071  0.132549  1.000000  0.074452  0.048620  0.068477   
JPM   0.197596  0.195753  0.300892  0.074452  1.000000  0.014245  0.260649   
AMZN  0.506639  0.621833  0.021084  0.048620  0.014245  1.000000 -0.094063   
GE    0.002801 -0.044486  0.180453  0.068477  0.260649 -0.094063  1.000000   
FB    0.542663  0.549501 -0.048242  0.078149  0.093780  0.653162 -0.021957   
T     0.004983 -0.019970  0.194238  0.095602  0.242841 -0.014819  0.284551   

            FB         T  
AAPL  0.542663  0.004983  
MSFT  0.549501 -0.019970  
XOM  -0.048242  0.194238  
JNJ   0.078149  0.095602  
JPM   0.093780  0.242841  
AMZN  0.653162 -0.014819  
GE   -0.021957  0.284551  
FB    1.000000 -0.029623  
T    -0.029623  1.000000  

矩陣中每個一元素都是其對應(yīng)股票的相關(guān)系數(shù),取值從-1到1,正數(shù)代表正相關(guān),反之負(fù)數(shù)代表負(fù)相關(guān)。我們觀察到矩陣的對角線永遠(yuǎn)是1,因為自己和自己當(dāng)然是完全相關(guān)的。另外相關(guān)矩陣也是對稱的,即上三角和下三角呈鏡像對稱。

為了便于觀察,可以將數(shù)值的相關(guān)矩陣用熱圖的形式展現(xiàn)出來。以下采用了 seaborn 包來繪制熱圖。

# 導(dǎo)入seaborn
import seaborn as sns

# 創(chuàng)建熱圖
sns.heatmap(correlation_matrix,
            annot=True,
            cmap="YlGnBu", 
            linewidths=0.3,
            annot_kws={"size": 8})

plt.xticks(rotation=90)
plt.yticks(rotation=0) 
plt.show()

3.2 協(xié)方差矩陣

相關(guān)系數(shù)只反了股票之間的線性關(guān)系,但并不能告訴我們股票的波動情況,而協(xié)方差矩陣則包含這一信息。可使用pandas數(shù)據(jù)框內(nèi)建的 .cov() 方法來計算協(xié)方差矩陣。

# 計算協(xié)方差矩陣
cov_mat = stock_return.cov()

# 年化協(xié)方差矩陣
cov_mat_annual = cov_mat * 252

# 輸出協(xié)方差矩陣
print(cov_mat_annual)
          AAPL      MSFT       XOM       JNJ       JPM      AMZN        GE  \
AAPL  0.031577  0.011494  0.001268  0.000622  0.005721  0.018938  0.000099   
MSFT  0.011494  0.022000  0.001435  0.003850  0.004731  0.019401 -0.001313   
XOM   0.001268  0.001435  0.012688  0.001707  0.005522  0.000500  0.004045   
JNJ   0.000622  0.003850  0.001707  0.013067  0.001387  0.001169  0.001558   
JPM   0.005721  0.004731  0.005522  0.001387  0.026546  0.000488  0.008451   
AMZN  0.018938  0.019401  0.000500  0.001169  0.000488  0.044248 -0.003937   
GE    0.000099 -0.001313  0.004045  0.001558  0.008451 -0.003937  0.039601   
FB    0.016428  0.013885 -0.000926  0.001522  0.002603  0.023406 -0.000744   
T     0.000152 -0.000508  0.003755  0.001876  0.006791 -0.000535  0.009719   

            FB         T  
AAPL  0.016428  0.000152  
MSFT  0.013885 -0.000508  
XOM  -0.000926  0.003755  
JNJ   0.001522  0.001876  
JPM   0.002603  0.006791  
AMZN  0.023406 -0.000535  
GE   -0.000744  0.009719  
FB    0.029021 -0.000866  
T    -0.000866  0.029461  

3.3 投資組合的標(biāo)準(zhǔn)差

投資組合的風(fēng)險可以用標(biāo)準(zhǔn)差來衡量,只要知道組合權(quán)重和協(xié)方差矩陣,就可以通過以下公式進(jìn)行計算。

\sigma = \sqrt{w_T \cdot \Sigma \cdot w}

  • \sigma:投資組合的標(biāo)準(zhǔn)差
  • \Sigma:收益的協(xié)方差矩陣
  • w:投資組合的權(quán)重(w_T是權(quán)重的轉(zhuǎn)置)
  • \cdot 是點積運(yùn)算

在NumPy中,使用.T屬性對數(shù)組進(jìn)行轉(zhuǎn)置np.dot()函數(shù)用于計算兩個數(shù)組的點積

# 計算投資組合的標(biāo)準(zhǔn)差
portfolio_volatility = np.sqrt(np.dot(portfolio_weights.T, 
                                      np.dot(cov_mat_annual, portfolio_weights)))
print(portfolio_volatility)
0.0896350886377703

四、尋找最優(yōu)的投資組合

掌握了收益和風(fēng)險(標(biāo)準(zhǔn)差)的計算方法后,接下來要考慮的是:應(yīng)該選擇怎樣的組合權(quán)重才是最好的呢?是讓收益最大嗎?還是風(fēng)險最小?我們需要綜合權(quán)衡風(fēng)險和收益這兩個因素。

諾貝爾經(jīng)濟(jì)學(xué)獎得主馬科維茨(Markowitz)提出的投資組合理論被廣泛用于組合選擇和資產(chǎn)配置中。該理論中的均值-方差分析法和有效邊界模型可用于尋找最優(yōu)的投資組合。

4.1 蒙特卡洛模擬Markowitz模型

我們采用蒙特卡洛模擬來進(jìn)行分析,也就是隨機(jī)生成一組權(quán)重,計算該組合下的收益和標(biāo)準(zhǔn)差,重復(fù)這一過程許多次(比如1萬次),將每一種組合的收益和標(biāo)準(zhǔn)差繪制成散點圖。

# 設(shè)置模擬的次數(shù)
number = 10000
# 設(shè)置空的numpy數(shù)組,用于存儲每次模擬得到的權(quán)重、收益率和標(biāo)準(zhǔn)差
random_p = np.empty((number, 11))
# 設(shè)置隨機(jī)數(shù)種子,這里是為了結(jié)果可重復(fù)
np.random.seed(123)

# 循環(huán)模擬10000次隨機(jī)的投資組合
for i in range(number):
    # 生成9個隨機(jī)數(shù),并歸一化,得到一組隨機(jī)的權(quán)重數(shù)據(jù)
    random9 = np.random.random(9)
    random_weight = random9 / np.sum(random9)
    
    # 計算年化平均收益率
    mean_return = stock_return.mul(random_weight, axis=1).sum(axis=1).mean()
    annual_return = (1 + mean_return)**252 - 1

    # 計算年化的標(biāo)準(zhǔn)差,也稱為波動率
    random_volatility = np.sqrt(np.dot(random_weight.T, 
                                       np.dot(cov_mat_annual, random_weight)))

    # 將上面生成的權(quán)重,和計算得到的收益率、標(biāo)準(zhǔn)差存入數(shù)組random_p中
    random_p[i][:9] = random_weight
    random_p[i][9] = annual_return
    random_p[i][10] = random_volatility
    
# 將numpy數(shù)組轉(zhuǎn)化成DataFrame數(shù)據(jù)框
RandomPortfolios = pd.DataFrame(random_p)
# 設(shè)置數(shù)據(jù)框RandomPortfolios每一列的名稱
RandomPortfolios.columns = [ticker + "_weight" for ticker in ticker_list]  \
                         + ['Returns', 'Volatility']
# 繪制散點圖
RandomPortfolios.plot('Volatility', 'Returns', kind='scatter', alpha=0.3)
plt.show()

投資的本質(zhì)是在風(fēng)險和收益之間做出選擇,上圖正是刻畫了這兩個要素。其中每一個點都代表著一種投資組合的情況,橫坐標(biāo)是代表風(fēng)險的標(biāo)準(zhǔn)差,縱坐標(biāo)是收益率。

Markowitz投資組合理論認(rèn)為,理性的投資者總是在給定風(fēng)險水平下對期望收益進(jìn)行最大化,或者是在給定收益水平下對期望風(fēng)險做最小化。反映在圖中也就是紅色曲線所示的有效邊界,只有在有效邊界上的點才是最有效的投資組合。

現(xiàn)在我們知道,理性的投資者都會選擇有效邊界上的投資組合。可具體選擇哪個點呢?我們接著往下看。

4.2 風(fēng)險最小組合

一種策略是選擇最低的風(fēng)險,且在該風(fēng)險水平下收益最高的組合,稱為最小風(fēng)險組合(GMV portfolio)。

讓我們找到風(fēng)險最小的組合,并繪制在代表收益-風(fēng)險的散點圖中。

# 找到標(biāo)準(zhǔn)差最小數(shù)據(jù)的索引值
min_index = RandomPortfolios.Volatility.idxmin()

# 在收益-風(fēng)險散點圖中突出風(fēng)險最小的點
RandomPortfolios.plot('Volatility', 'Returns', kind='scatter', alpha=0.3)
x = RandomPortfolios.loc[min_index,'Volatility']
y = RandomPortfolios.loc[min_index,'Returns']
plt.scatter(x, y, color='red')   
plt.show()

繪制風(fēng)險最小組合(GMV)的累積收益率曲線,并和等權(quán)重組合(EV)、市值加權(quán)的組合(MCap)進(jìn)行比較。圖中綠色曲線代表風(fēng)險最小組合,它的收益率低于另外兩種組合,這也符合人們對于風(fēng)險小的投資收益相對較低的認(rèn)知。

# 提取最小波動組合對應(yīng)的權(quán)重, 并轉(zhuǎn)換成Numpy數(shù)組
GMV_weights = np.array(RandomPortfolios.iloc[min_index, 0:numstocks])

# 計算GMV投資組合收益
StockReturns['Portfolio_GMV'] = stock_return.mul(GMV_weights, axis=1).sum(axis=1)

# 繪制累積收益曲線
cumulative_returns_plot(['Portfolio_EW', 'Portfolio_MCap', 'Portfolio_GMV'])

4.3 夏普最優(yōu)組合

其實我們更想在收益和風(fēng)險之間找到平衡點,夏普比率這個變量能幫我做出更好的決策,它計算的是每承受一單位的風(fēng)險所產(chǎn)生的超額回報。更多關(guān)于夏普比率的計算可參考《用夏普比率分析股票的風(fēng)險和回報》一文。

我們首先來計算上述蒙特卡洛模擬的組合所對應(yīng)的夏普比率,并將之作為第三個變量繪制在收益-風(fēng)險的散點圖中,這里采用顏色這一視覺線索來表征夏普比率。

# 設(shè)置無風(fēng)險回報率為0
risk_free = 0

# 計算每項資產(chǎn)的夏普比率
RandomPortfolios['Sharpe'] = (RandomPortfolios.Returns - risk_free)   \
                            / RandomPortfolios.Volatility

# 繪制收益-標(biāo)準(zhǔn)差的散點圖,并用顏色描繪夏普比率
plt.scatter(RandomPortfolios.Volatility, RandomPortfolios.Returns, 
            c=RandomPortfolios.Sharpe)
plt.colorbar(label='Sharpe Ratio')
plt.show()

我們發(fā)現(xiàn)散點圖上沿的組合具有較高的夏普比率。接著再找到夏普比率最大的組合,將其繪制在收益-風(fēng)險的散點圖中。

# 找到夏普比率最大數(shù)據(jù)對應(yīng)的索引值
max_index = RandomPortfolios.Sharpe.idxmax()

# 在收益-風(fēng)險散點圖中突出夏普比率最大的點
RandomPortfolios.plot('Volatility', 'Returns', kind='scatter', alpha=0.3)
x = RandomPortfolios.loc[max_index,'Volatility']
y = RandomPortfolios.loc[max_index,'Returns']
plt.scatter(x, y, color='red')   
plt.show()

最后繪制夏普最優(yōu)組合(MSR)的累積收益曲線(下圖中的紅色曲線),發(fā)現(xiàn)它的收益遠(yuǎn)遠(yuǎn)高于其他組合。當(dāng)然,我們用的是歷史數(shù)據(jù),至于能否在未來獲得同樣好的表現(xiàn),還有待考量。

# 提取最大夏普比率組合對應(yīng)的權(quán)重,并轉(zhuǎn)化為numpy數(shù)組
MSR_weights = np.array(RandomPortfolios.iloc[max_index, 0:numstocks])

# 計算MSR組合的收益
StockReturns['Portfolio_MSR'] = stock_return.mul(MSR_weights, axis=1).sum(axis=1)

# 繪制累積收益曲線
cumulative_returns_plot(['Portfolio_EW', 'Portfolio_MCap',   \
                         'Portfolio_GMV', 'Portfolio_MSR'])

注:本文是 DataCamp 課程 Intro to Portfolio Risk Management in Python 的學(xué)習(xí)筆記。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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