如何用Python透過現代投資理論組成資產配置以及為何不應該依賴他

現代投資理論(簡稱MPT),是馬可維茲的巨作,也是許多經濟以及金融學家在現代接觸到投資學的第一課,而許多對於投資有興趣的或許也都聽過。但是,很多實務上的運用都是用Excel或者是Matlab來完成的,前者其實功能很受侷限,後者則是很貴。這篇文章就試圖透過Python這個免費的開源軟體來手把手的教你現代投資理論,同樣的使用市場的資料跟你說現代投資理論真正要跟你說的,以及為何你不應該過度依賴他。

現代投資理論簡介

要理解MPT,我覺得最好理解的方式就是從其開發者馬可維茲在碩士期間對於論文的想法開始最值觀。當時,馬可維茲最有興趣的就是最優解這種現代我想只要有基本Excel概念的人都會的東西,例如:機票要怎麼賣?座位要怎麼排?預算怎麼分配?這些問題,因為往往都會有所取捨,而數學上來講其實是可以透過計算來得到這樣的最優解,高中生一定都不陌生畫兩條直線然後把交叉以下的面積塗黑的問題,而現實生活的數學並非是線性的,要如何在非線性的情況下取得最優解就是馬可維茲想要知道的。

而馬可維茲想要知道的就是:”我是投資人想追求獲利,但是我又不想追求過高的風險,那我要怎麼辦?”於是,現代投資理論就出現了,這個理論他想強調的其實不是你現在應該如何投資,而是要展現為何資產的有效權重分配可以做到馬可維茲想問的問題:我要如何在獲利以及風險中做出取捨。

與其證明一堆數學,倒不如讓我們把重心放到程式碼上面去,手把手帶你理解現代投資理論以及他為何不應該作為投資唯一的準則。

Python環境設定

Python是個對於經濟以及金融有興趣的人非常強大的免費開源軟體,我自己做研究其實主要是仰賴Matlab跟Stata,但是我如果教人的話則是喜歡用Python跟R作為對應。原因無他,只是因為後兩者是免費的,而前兩者我相對而言做一些複雜的運算比較熟悉,並非是Python或這R無法模擬Matlab或者Stata的結果,只是單純要多一點時間,也不是說因為Python跟R比較簡單所以我寫給初學者,而只是因為Matlab跟Stata如果出了學校有夠貴。

對於Python,我自己最喜歡用的是Anaconda,但如果有自己喜歡的環境就用自己的,而可以在這邊下載

首先可以把終端機打開,透過下面的程式碼來更新

conda update anaconda

然後把Anaconda的資料夾打開應該會看到一個叫做Jupyter的東西,點開來應該會看到類似圖一的畫面,當然你的資料夾不會跟我一樣有一堆亂七八糟的東西,然後把右上方的New點下去,選擇Python 3,應該就會看到圖二一樣的開始畫面,這樣就能開始了。

圖一:首頁
圖二:開始頁面

透過Python學MPT

讓我們先弄個環境設定一下,先把幾個常用的api放進去,下面這幾行程式碼的作用就是這樣,基本上就指示輸入一些重要的

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pandas_datareader import data as pdr
%matplotlib inline

上面幾行基本上是我自己在模擬一些模型很常用到的外掛工具,有興趣的可以自己google那些強大的功能,我這邊就不多做介紹。

接下來,我們先下面這一段透過yahoo finance來擷取資料,就使用尖牙股搭配VOO以及美債好了,資料就抓2015-2021,圖三應該是你會看到的結果:

df = pdr.DataReader(['AAPL', 'TSLA', 'GOOGL', 'AMZN','VOO','BND'],data_source='yahoo', start='2015-01-01', end='2021-01-20')
df = df['Adj Close']
df.head()
圖三

接下來,我們需要的是計算報酬,也就是(今天股價-昨天股價)/昨天股價這個簡單的東西。但是寫這個很麻煩,而事實上你可以透過一些簡單的數學來證明在大部分情況下log(x/y)=(x-y)/y,所以我們就乾脆取log,不信你可以自己按計算機看看,其實差不多,而如果真的想知道為什麼地請去把大一微積分的泰勒展開式有關於log(x)的部分讀完你就知道了。

而接下來,我們需要的是計算不同的資產報酬之間的共變異術以及相關係數。

何謂共變異數(covariance)?很簡單,就是當某個資產A波動的時候,某個資產B會不會跟著一起波動。你不會希望你的資產裡面都是一起波動的,這樣風險太高。

何謂相關係數(correlation)?更簡單了,就是當某個資產A獲利的時候,某個資產B會不會跟著一起獲利,通常你會希望相關係數較低,但這並不一定,共變異數其實比相關係數好用。

而如果有學過Excel的基本投資理財的,因為往往都是股債平衡,兩種資產很好計算,但是如果你有很多種資產的話,你會需要矩陣比較好讀,因此我們需要共變異矩陣以及相關係數矩陣

因此我們需要這樣的code:

cov_matrix = df.pct_change().apply(lambda x: np.log(1+x)).cov()
corr_matrix = df.pct_change().apply(lambda x: np.log(1+x)).corr()

cov_matrix

上面那一行則是讓我們把共變異矩陣叫出來,應該可以看到圖六的結果,同樣你也可以透過類似的方法叫出塗漆的相關係數矩陣,基本上讀的方法就是格子裡面代表的是某資產a跟另外一個資產b的共變異數(相關係數),而如果是左上斜線下來的就是自己本身的變異數(而如果是相關係數的話一定是1):

圖六:共變異矩陣
圖七:相關係數矩陣

而接下來,我們要計算的是整體資產的波動,基本上是透過下面的公式:

w是比重,cov是共變異數,如果只有兩個資產這很好處理,但是如果有很多資產的話會變得很長的計算,不難,但是很煩,因此我們需要Python之類的來幫忙,於是就寫下面的code

w = {'AAPL': 0.1, 'TSLA': 0.2, 'GOOGL': 0.2, 'AMZN':0.1,'VOO':0.3,'BND':0.1}
port_var = cov_matrix.mul(w, axis=0).mul(w, axis=1).sum().sum()
port_var

上面那行只是比重而已,可以隨便給,但是要記得全部加起來等於1,下面那行則是上面那條公式寫成code的結果而已,沒啥特別的,而最後一行只是把結果叫出來而已。

接著,讓我們來計算個別資產的年化報酬,下面的code可以幫我們做到這一點,而同樣最後一行可以把結果叫出來,如果跟我用同樣資料的話應該會發現特斯拉的年畫報酬真的是一倍…..

ind_er = df.resample('Y').last().pct_change().mean()
ind_er

接著,我們可以透過下面的code來計算總資產報酬,要記住的是這裡面資產的比重必須要跟上面的一致。

w = [0.1, 0.2, 0.2, 0.1,0.3,0.1]
port_er = (w*ind_er).sum()
port_er

接著,我們來計算年化標準差,最後面之所以有一個sqrt(250)的原因是一年的交易日有250日(以美股來說):

ann_sd = df.pct_change().apply(lambda x: np.log(1+x)).std().apply(lambda x: x*np.sqrt(250))
ann_sd

然後我們先來把表格叫出來檢視一下結果,來大概看一下計算出來的報酬跟波動,而結果如同圖八一樣,你可以看到特斯拉(TSLA)有最高的年化報酬以及最高的波動性,而美債(BND)則是有最低的波動性以及最高的報酬,那我們不禁就要問,是否可以找到一個把這些組合起來,達到我想要最佳解呢?接下來,讓我們來模擬吧。

assets = pd.concat([ind_er, ann_sd], axis=1)
assets.columns = ['Returns', 'Volatility']
assets
圖八:報酬跟波動

不同的可能性

上面那些分開的步驟只是解釋而已,而我也只是隨便測是一個比重而已,但實際上可以有上萬種不同的比重,讓我們就來測試一萬種可能性好了,下面這個code看起來很複雜,但是這其實只是重複我們之前做過的事情而已,其實這才是真正有用的。

p_ret = [] 
p_vol = [] 
p_weights = [] 

num_assets = len(df.columns)
num_portfolios = 10000
for portfolio in range(num_portfolios):
    weights = np.random.random(num_assets)
    weights = weights/np.sum(weights)
    p_weights.append(weights)
    returns = np.dot(weights, ind_er)  
    p_ret.append(returns)
    var = cov_matrix.mul(weights, axis=0).mul(weights, axis=1).sum().sum()
    sd = np.sqrt(var) 
    ann_sd = sd*np.sqrt(250) 
    p_vol.append(ann_sd)

上面那三行只是先定義一個空集合方便之後來運用,第一行只是告訴電腦說我要那些資產,第二行則是說我要模擬10000次。而後面連續除了起先的三行告訴電腦說比重要加起來等於1以外,其餘做的事情其實跟我們上面在做的事情一模一樣,沒什麼特別的,一樣是先計算報酬然後是波動。

接下來,我們就能告把所有的組合聚再一起,使用的是下面的code,這一樣沒啥特別的,只是告訴電腦我這裡面有報酬跟波動,然後給不同的資產給予隨機亂數產生的比重,最後一行只是把結果叫出來,有沒有也同樣沒差:

data = {'Returns':p_ret, 'Volatility':p_vol}

for counter, symbol in enumerate(df.columns.tolist()):
    #print(counter, symbol)
    data[symbol+' weight'] = [w[counter] for w in p_weights]
portfolios  = pd.DataFrame(data)
portfolios.head()

接著,讓我們來到最興奮的階段:畫圖。

下面的程式碼可以把我們的結果給精美的呈現出來,如同圖九,其實這真的很有趣,因為我們把2020一整年古怪的資料放了進去,而且放了科技股進去,就變成一個幾乎風險以及報酬是線性的組合,雖然還沒跑結果,但我大概可以猜出在這種情況下美債的比例並不會太高:

portfolios.plot.scatter(x='Volatility', y='Returns', marker='o', s=10, alpha=0.3, grid=True, figsize=[10,10])

圖九:模擬結果

那我們可以來測試兩個策略:

如果我其實不想賺大錢,只想要風險最低的話,我要怎麼配置。

我只要賺大錢,不管風險,我又要怎麼配置。

第一個策略是這樣寫的,從圖十模擬的結果來看,根據模擬出來的10000次結果中,其美債比例是60%,其餘放進去科技股以及VOO,而可以享受年報酬17%以及波動性9%的組合,這是什麼意思?這代表除非產生2.5%的機率的風險(兩個標準差以外),你的虧損其實也只有1%,而最後兩行則是視覺呈現結果的圖十一。

min_vol_port = portfolios.iloc[portfolios['Volatility'].idxmin()]
min_vol_port
plt.subplots(figsize=[10,10])
plt.scatter(portfolios['Volatility'], portfolios['Returns'],marker='o', s=10, alpha=0.3)
plt.scatter(min_vol_port[1], min_vol_port[0], color='r', marker='*', s=50)
圖十:風險最低配置
圖十一:風險最低配置視覺化

納第二個我只想要賺大錢呢?那麼從10000次模擬的結果當中,我可以寫一個最大報酬的策略,而code是最大報酬的策略,圖十二可以看到結果是64.3%進去特斯拉就好,其他也是平均分配,得到的報酬是87%以及波動性40%,也就是除非發生兩個標準差以外的暴跌風險,你也至少還是賺4%。

max_rt_port = portfolios.iloc[portfolios['Returns'].idxmax()]
max_rt_port
plt.subplots(figsize=[10,10])
plt.scatter(portfolios['Volatility'], portfolios['Returns'],marker='o', s=10, alpha=0.3)
plt.scatter(max_rt_port[1], max_rt_port[0], color='b', marker='*', s=50)
圖十二:極大報酬策略配置
圖十三:極大報酬配置視覺化

夏普比例

但是讓我們回到馬可維茲提出的問題,我們大部分人其實既不想太過安逸想追求風險,但是又不想要太過冒險而追求高風險,那怎麼辦呢?

同樣是諾貝爾獎得主的威廉夏普,就用一個很簡單的方法來給出答案,這個就是人們常聽到的夏普比例,夏普比例基本上是這樣計算的:(投資組合報酬-無風險報酬)/波動性,而投資組合報酬-無風險報酬這又稱為風險溢酬,因此可以寫成風險溢酬/波動性。基本上,夏普比例可以方便我們有效的去比較在固定報酬(風險)的情況下,何者的風險(報酬)較高,而我們希望夏普比例越高越搞。

讓我們把市場無風險利率設定為1%,那下面的code可以制定追求最高夏普比例的組合,而從圖十四可以看到這樣的配置是可以達到報酬56%以及波動性24.2%很棒的配置,一方面你有特斯拉的漲幅(配置了34%),另一方面則是配置了美債(32%),而圖十五則是視覺化的結果。

rf = 0.01
optimal_risky_port = portfolios.iloc[((portfolios['Returns']-rf)/portfolios['Volatility']).idxmax()]
optimal_risky_port
圖十四:夏普比例最優配置
圖十五:比較策略配置

好了,我想基本的教學已經結束了,而你也自信滿滿地想靠著這些程式碼來配置去賺那些可口的報酬了,但這真的是現代投資組合理論要跟你說的嗎?事實上並沒有,現代投資組合理論只是要跟你強調資產配置的重要,沒有跟你保證你現在做的一定會跟過去一樣獲利,讓我們來看看不同的平行時空。

假設我在一年前看到就好了?

假設你在一年前看到這篇文章,而真的去做了,讓我們來看看會發生什麼事情,基本上code都是重複的,除了日期改變,而我同樣做風險最小,報酬最高以及夏普比例最優的三種配置,你會發現其實結果會差很多,像是相較於2020的資料,風險最小策略的美債的比例會過度增加,而報酬最大的策略亞馬遜的比重會過大但是特斯拉會過小,同樣夏普比例也會是一樣問題:

import numpy as np
import pandas as pd
from pandas_datareader import data
import matplotlib.pyplot as plt
%matplotlib inline
df = data.DataReader(['AAPL', 'TSLA', 'GOOGL', 'AMZN','VOO','BND'], 'yahoo', start='2015/01/01', end='2020/1/1')
df = df['Adj Close']
cov_matrix = df.pct_change().apply(lambda x: np.log(1+x)).cov()
corr_matrix = df.pct_change().apply(lambda x: np.log(1+x)).corr()
num_assets = len(df.columns)
num_portfolios = 10000
p_ret = [] 
p_vol = [] 
p_weights = [] 
ind_er = df.resample('Y').last().pct_change().mean()
for portfolio in range(num_portfolios):
    weights = np.random.random(num_assets)
    weights = weights/np.sum(weights)
    p_weights.append(weights)
    returns = np.dot(weights, ind_er) # Returns are the product of individual expected returns of asset and its 
                                      # weights 
    p_ret.append(returns)
    var = cov_matrix.mul(weights, axis=0).mul(weights, axis=1).sum().sum()# Portfolio Variance
    sd = np.sqrt(var) # Daily standard deviation
    ann_sd = sd*np.sqrt(250) # Annual standard deviation = volatility
    p_vol.append(ann_sd)
2015-2019的資料:風險最小
2015-2019的資料:報酬最大
2015-2019的資料:夏普最優
2015-2019的資料:策略比較配置

而同時也可以看到,當把2020的資料達掉,純粹拿2015-2019的資料去做回測,預期的報酬以及波動性都不會那麼的大。原因在於這個現代投資組合理論並沒有考慮到利率(事實上與這理論配合的ICAPM模型也沒考慮到利率),沒考慮到市場的不確定性,沒有考慮到市場本身的演化。

因此,任何人如果跟你說只要照著現代投資組合理論就可以組成一個可以安心睡覺的配置那根本是在唬爛,事實上總體經濟以及政治不確定等事件都會影響到現代投資組合的有效性。其實這才是我真正想表達的:現代投資理論他是可以當作配置的參考,但不要過度依賴。

無論是夏普或者是馬可維茲,他們都從來沒說過現代投資組合是個可以讓你安心睡覺的投資組合理論,他們真正想表達的是解釋投資必須分散的重要性,而你也可以看到美債的比例,以及應該投資到市場個股的比例,都會隨著時間的演變而有所不同。

或許,有些人看到前面的模擬就覺得可以依照2020的資料來做出2021的配置,但或許應該仔細想想2020這種情況是否常見,而應該參考經濟理論又是什麼,這些理論背後的邏輯為何。如果只是跟著電腦程式模擬,卻不加以思索,那無論何等高明的諾貝爾獎理論都救不你。

但這並不代表現代投資組合沒用,事實上,你可以從這裡面看到幾個重要的點:

1.應該要分散投資,即便是要極大化報酬或者最小化風險都要分散

2.應該要適時的檢視資產配置,過去好用的策略現在並不一定好用

3.不存在安心睡覺的組合,好的組合也是有機率變成垃圾

4.其實寫程式不難,這東西大概有國中智力程度就可以了

4 thoughts on “如何用Python透過現代投資理論組成資產配置以及為何不應該依賴他

  1. 請問為什麼我在執行這行時
    ind_er = df.resample(‘Y’).last().pct_change().mean()
    ind_er
    一直都是出現
    TypeError: Only valid with DatetimeIndex, TimedeltaIndex or PeriodIndex, but got an instance of ‘RangeIndex’
    的錯誤
    怎麼改還是錯誤

    1. 請問您Python版本為何有更新到最新的嗎?

      而最近Yahoo的html有改,因此原本單純有datareader去擷取網頁資料的方法會有問題,我有重寫一開始的幾行code,重新執行沒有問題,如果還有問題的話再聯絡謝謝。

  2. 您好python版本有更新到最新,但是在一開始擷取資料就有錯誤(pdr_override() got an unexpected keyword argument ‘start’),所以我改用DataReader = [‘AAPL’, ‘TSLA’, ‘GOOGL’, ‘AMZN’,’VOO’,’BND’]
    start_date = ‘2015-01-01′
    end_date=’2021-01-20’
    df = yfin.download(DataReader, start=start_date, end=end_date)
    df = df.reset_index()
    df = df[‘Adj Close’]
    df
    但是到了ind_er = df.resample(‘Y’).last().pct_change().mean()
    ind_er
    還是出現一樣的錯誤TT

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *