実際のPythonコードを示しての効率的フロンティア と ポートフォリオの最適化を行う記事になります。複数の銘柄に投資する場合、どの銘柄にどの位のウェイトで投資するのか、最適なのか?投資理論の中ではリスクとリターンの観点から、その最適解をプログラミングで導くことを目指します。
元記事は 2020/08/13 noteでの記事になります。
yfinance ライブラリなどに若干の変更があったりしたようで、以前のコードでは動かくなっているようですので、こちらのサイトで修正したものをリライトして、載せていきます。
2021/07/25時点で以下のコードはエラーなく動いていることを確認しています。
元ネタは以下の英文記事になります。
目次
- この記事の対象とするユーザー
- この記事で使用するもの
- 効率的フロンティア (Efficient Frontier)とは
- Google Colaboratoryのpythonでの利用方法
- 投資に関する免責事項プログラムや考え方の情報の提供・作業代行を目的としており、投資勧誘を目的とするものではありません。
この記事の対象とするユーザー
・効率的フロンティア (Efficient Frontier)について知識がある、もしくは興味を持っている方
・米国株のポートフォリオ運用に効率的フロンティア (Efficient Frontier)の考え方を用いて、最適化、リターンの向上並びにリスクの低減を考えている方
・解析を行うPythonプログラムコードに興味のある人
基本的にはコードをコピペすれば動くことを確認していますが、理論の内容の理解や、プログラムの理解などあった方がより深く理解できると思います。
この記事で使用するもの
・Google ColaboratoryのPython
となります。
上記の記事ではQUANDLというデータ配信サービスを使用してのデータ取得となっているので、それを
・Yahoo USからのデータ取得
・Google ColaboratoryのPythonを使う
ことにより、無料で、簡単に行おうということになります。
Quandlとは
QuandlはNasdaq傘下のでーは配信サービスプロバイダーになります。
私もアカウントを持っていて、利用させていただいてます。
APIを利用して高品質なデータを配信しており、株価のヒストリカルデータだけでなくPBR,PERといったファンダメンタルデータなども提供しています。
API接続について
IB証券(インタラクティブ・ブローカーズ証券 )でもAPIによるデータアクセスは可能ですが、アクセススピードや簡便な操作方法など、頻繁にデータアクセスを必要する方は、Quandlはサービス契約を検討すべき・検討に値するサービスを提供しています。
IB証券(インタラクティブ・ブローカーズ証券 )のヒストリカルデータへのAPI接続については以前書いた以下の記事を参考にしてください。
効率的フロンティア (Efficient Frontier)とは
効率的フロンティアについてだけで、非常に多くの記事があるくらい奥深いものですので、ここでは概略だけ取り扱います。
みずほ証券が紹介している「ファイナンス用語集」のリンクを紹介します。
効率的フロンティア (Efficient Frontier)とは、分散投資を実施したときに実現するポートフォリオの中で、あるリスクの水準で最大のリターンを獲得できるポートフォリオの集合のこと
また、オリジナルの英文記事にも説明があります。
数学的なバックグラウンドに興味のある方は以下のサイトなども使って勉強すると良いと思います。
Google Colaboratoryのpythonでの利用方法
実際のコード前半は以下のようになります。
!pip install yfinance --upgrade --no-cache-dir
# import needed modules
import datetime
import yfinance as yf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
start = datetime.date(2018,1,1)
end = datetime.date.today()
# get adjusted closing prices of 5 selected companies with Quandl
selected = ['CNP', 'F', 'WMT', 'GE', 'TSLA']
data = yf.download(selected, start=start, end=end)["Adj Close"]
# calculate daily and annual returns of the stocks
#returns_daily = table.pct_change()
returns_daily = data.pct_change()
returns_annual = returns_daily.mean() * 250
# get daily and covariance of returns of the stock
cov_daily = returns_daily.cov()
cov_annual = cov_daily * 250
# empty lists to store returns, volatility and weights of imiginary portfolios
port_returns = []
port_volatility = []
sharpe_ratio = []
stock_weights = []
# set the number of combinations for imaginary portfolios
num_assets = len(selected)
num_portfolios = 50000
#set random seed for reproduction's sake
np.random.seed(101)
# populate the empty lists with each portfolios returns,risk and weights
for single_portfolio in range(num_portfolios):
weights = np.random.random(num_assets)
weights /= np.sum(weights)
returns = np.dot(weights, returns_annual)
volatility = np.sqrt(np.dot(weights.T, np.dot(cov_annual, weights)))
sharpe = returns / volatility
sharpe_ratio.append(sharpe)
port_returns.append(returns)
port_volatility.append(volatility)
stock_weights.append(weights)
# a dictionary for Returns and Risk values of each portfolio
portfolio = {'Returns': port_returns,
'Volatility': port_volatility,
'Sharpe Ratio': sharpe_ratio}
# extend original dictionary to accomodate each ticker and weight in the portfolio
for counter,symbol in enumerate(selected):
portfolio[symbol+' Weight'] = [Weight[counter] for Weight in stock_weights]
# make a nice dataframe of the extended dictionary
df = pd.DataFrame(portfolio)
# get better labels for desired arrangement of columns
column_order = ['Returns', 'Volatility', 'Sharpe Ratio'] + [stock+' Weight' for stock in selected]
# reorder dataframe columns
df = df[column_order]
# plot frontier, max sharpe & min Volatility values with a scatterplot
plt.style.use('seaborn-dark')
df.plot.scatter(x='Volatility', y='Returns', c='Sharpe Ratio',
cmap='RdYlGn', edgecolors='black', figsize=(10, 8), grid=True)
plt.xlabel('Volatility (Std. Deviation)')
plt.ylabel('Expected Returns')
plt.title('Efficient Frontier')
plt.show()
Quandlのデータ取得の代わりにYahooUSからデータを取得します。
また、今回はデータの取得期間を2018年1月1日から現在の日付までとしています。
また、取得する銘柄はオリジナルのコードと同じ
[‘CNP’, ‘F’, ‘WMT’, ‘GE’, ‘TSLA’]
といった銘柄群としました。
この辺りは使用する人によって銘柄を増やしたり、減らしたり、ETFで行ったり、工夫ができる部分だと思います。
!pip install yfinance --upgrade --no-cache-dir
# import needed modules
import datetime
import yfinance as yf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
start = datetime.date(2018,1,1)
end = datetime.date.today()
# get adjusted closing prices of 5 selected companies with Quandl
selected = ['CNP', 'F', 'WMT', 'GE', 'TSLA']
data = yf.download(selected, start=start, end=end)["Adj Close"]
そのような部分を行っているのが上記の部分になります。
次のパートでは価格の変化をパーセントでの表記に換え、それを年率換算に換えています。
# calculate daily and annual returns of the stocks
#returns_daily = table.pct_change()
returns_daily = data.pct_change()
returns_annual = returns_daily.mean() * 250
株価のパーセント騰落率の共分散「Covariance」を求め、それも年率換算値を求めます。
# get daily and covariance of returns of the stock
cov_daily = returns_daily.cov()
cov_annual = cov_daily * 250
その後の部分はそれぞれ異なったウェイトでの50,000個のポートフォリオを作ります。ウェイトが異なるのでリターンとボラティリティもポートフォリオごとに異なります。
投資家のリターンとリスクの比を表す指標のうちの一つ、シャープレシオで色付けすると以下のようになりなります。
ノーベル賞を取ったマーコウィッツの資本資産価格モデルCapital Asset Pricing Model (CAPM)で見慣れたグラフが出てきます。
以前のスクリーンキャプチャ
今回のスクリーンキャプチャ(リターンが上がっているのが”それっぽい”)です
# find min Volatility & max sharpe values in the dataframe (df)
min_volatility = df['Volatility'].min()
max_sharpe = df['Sharpe Ratio'].max()
# use the min, max values to locate and create the two special portfolios
sharpe_portfolio = df.loc[df['Sharpe Ratio'] == max_sharpe]
min_variance_port = df.loc[df['Volatility'] == min_volatility]
# plot frontier, max sharpe & min Volatility values with a scatterplot
plt.style.use('seaborn-dark')
df.plot.scatter(x='Volatility', y='Returns', c='Sharpe Ratio',
cmap='RdYlGn', edgecolors='black', figsize=(10, 8), grid=True)
plt.scatter(x=sharpe_portfolio['Volatility'], y=sharpe_portfolio['Returns'], c='red', marker='D', s=200)
plt.scatter(x=min_variance_port['Volatility'], y=min_variance_port['Returns'], c='blue', marker='D', s=200 )
plt.xlabel('Volatility (Std. Deviation)')
plt.ylabel('Expected Returns')
plt.title('Efficient Frontier')
plt.show()
後はボラティリティが最小になる点とシャープレシオが最大になる点を探してプロットすると以下のようになります。
過去のスクリーンキャプチャ
今回のスクリーンキャプチャ
青い点がボラティリティが最小となるポートフォリオで、赤がシャープレシオが最大になるものです。それぞれ、どの銘柄がポートフォリオにおいて、どのくらいのウェイトを占めるのかを表示すると以下のようになります。
# print the details of the 2 special portfolios
print(min_variance_port.T)
print(sharpe_portfolio.T)
以前のデータでのポートフォリオウェイト計算
34461 Returns 0.122298 Volatility 0.222989 Sharpe Ratio 0.548451 CNP Weight 0.064623 F Weight 0.172365 WMT Weight 0.020066 GE Weight 0.035020 TSLA Weight 0.707926 29859 Returns 0.481098 Volatility 0.374205 Sharpe Ratio 1.285653 CNP Weight 0.050388 F Weight 0.004180 WMT Weight 0.000638 GE Weight 0.501385 TSLA Weight 0.443409
今回のデータでのウェイト計算、
37803 Returns 0.160356 Volatility 0.210214 Sharpe Ratio 0.762824 CNP Weight 0.141210 F Weight 0.116479 WMT Weight 0.024165 GE Weight 0.036173 TSLA Weight 0.681973 24864 Returns 0.448251 Volatility 0.330647 Sharpe Ratio 1.355679 CNP Weight 0.002026 F Weight 0.068389 WMT Weight 0.000273 GE Weight 0.418684 TSLA Weight 0.510627
前回同様、GEとTSLAに全ツッパというのが、、笑
今回の例ではこのような数字になりました。
ボラティリティを抑えて、代わりにリターンを犠牲にするタイプのポートフォリオの割にはTSLAが大きめに見えたり、シャープレシオが最大にするためにはGEとTSLAの実質2銘柄でいいとか、、いろいろ先入観とは違うような結果も出てきそうです。
今回は2018年以降のデータを使っていること、使用した銘柄数もオリジナルのページのものを使っていますので、個人的にいろいろ変更して、使ってみるのもよいかと思います。
2022/11/06 追記
以下のようなコメントをいただきました。
湯豆腐 より:
2022-11-06 02:21
いつも楽しく、拝見しております。
質問なのですが、selected = [‘CNP’, ‘F’, ‘WMT’, ‘GE’, ‘TSLA’]の部分を30銘柄などにすると蛹の繭のようななんとも言えない効率的フロンティアが出来上がります。
この繭のような効率的フロンティアは正しい結果を表しているのでしょうか。拙い質問で申し訳ありませんが、お手すきの際にご確認いただけますと幸いです。
湯豆腐様、コメントいただきありがとうございます。サナギのような形、、カイコの繭のような形というのはあり得るな、、と思います。今回は一つずつ確認してみたいと思います。
銘柄を30個、2022/11/06時点での時価総額順30銘柄、以下の銘柄で確認します。
selected = ['AAPL', 'MSFT',"GOOG","AMZN","TSLA",
"BRK-B","UNH","XOM","JNJ","V",
"JPM","WMT","NVDA","CVX","LLY",
"PG","MA","BAC","HD","PFE",
"ABBV","KO","MRK","PEP","META",
"COST","ORCL","MCD","TMO","AVGO",
]
1.5銘柄で前回のキャプチャーをとった2018/01/01から2021/07/25での形状を確認してみたいと思います。コードを以下のように一部修正すると結果は以下のような形になります。
df1=data[selected[:5]][:"2021/07/25"].copy()
display(df1.tail(2))
最強の5銘柄といったところでしょうか。
2.15銘柄で同じ期間2018/01/01から2021/07/25での形状は以下の通りです。
だいぶ丸くなってきました。
3.30銘柄で同じ期間2018/01/01から2021/07/25での形状は以下の通りです。
Expected ReturnsのY軸の数字が40から32に下がっていること、VolatilityのX軸は0.28から0.24に下がっていることが見て取れます。銘柄を増やすことで、リターンは減るけれども、ボラティリティを抑え込めることは確認できると思います。また、グラフの形状的には繭のような形になっていることも確認できます。
4.5、15、30銘柄で期間を2018/01/01から2022/11/06にした場合の形状を示します。
今年の下落がありますので、リターンの数字が減ってきたりというものもありますが、基本的に形状は繭のような形はあまり変化しないことは分かります。
5.最後に今年のみのデータ(期間を2022/01/01から2022/11/06)にしたときの30銘柄の形状は以下の通りです。
いつもの見慣れた形状の完全な逆の形ですね、、それでもうまくポートフォリオを組めばプラスのゾーンもあるようです。いろいろ研究し甲斐のありそうな部分です。
コメントをいただきました。
オキシドールたかしさん、ありがとうございます。記事を各励みになります。大会頑張ってください!
投資に関する免責事項プログラムや考え方の情報の提供・作業代行を目的としており、投資勧誘を目的とするものではありません。
過去のデータを用いた解析は、将来の株価の推移を保証するものではないことをはご承知おきください。
また、どのような銘柄を選定するか、等ウェイトではどうなのかといった、ウェイトの掛け方等、さらなるブラッシュアップが可能であると思います。
---
Pytho,投資関係に関する記事をご紹介します。
コメント
いつも楽しく、拝見しております。
質問なのですが、selected = [‘CNP’, ‘F’, ‘WMT’, ‘GE’, ‘TSLA’]の部分を30銘柄などにすると蛹の繭のようななんとも言えない効率的フロンティアが出来上がります。
この繭のような効率的フロンティアは正しい結果を表しているのでしょうか。
拙い質問で申し訳ありませんが、お手すきの際にご確認いただけますと幸いです。
早速検証してくださり、本当にありがとうございました。
大学の授業で扱う部分であったため、より理解が深まりました。
これからも、一ファンとして陰ながら応援しております。
こちらこそ、引き続きよろしくお願いいたします。