【コピペで動く!】Google ColabでPython を用いての 効率的フロンティア と ポートフォリオの最適化 Efficient Frontier & Portfolio Optimization with Python [Part 2/2]

スポンサーリンク
投資

実際のPythonコードを示しての効率的フロンティア と ポートフォリオの最適化を行う記事になります。複数の銘柄に投資する場合、どの銘柄にどの位のウェイトで投資するのか、最適なのか?投資理論の中ではリスクとリターンの観点から、その最適解をプログラミングで導くことを目指します。

元記事は 2020/08/13 noteでの記事になります。

Python を用いての 効率的フロンティア と ポートフォリオの最適化 Efficient Frontier & Portfolio Optimization with Python [Part 2/|TF
ーーーーーーーーーーー2021/07/28 追記ーーーーーーーーーーー Pythonの一部仕様変更などでうまく動いていなかった部分を新しいサイトでは修正しました。 今後適宜修正や改修を行おうとは思いますが、すべてを改修できるわけではないことはご承知おきください。 2021/07/28時点では動いていることは確認して...

yfinance ライブラリなどに若干の変更があったりしたようで、以前のコードでは動かくなっているようですので、こちらのサイトで修正したものをリライトして、載せていきます。

2021/07/25時点で以下のコードはエラーなく動いていることを確認しています。

元ネタは以下の英文記事になります。

Efficient Frontier & Portfolio Optimization with Python [Part 2/2]
In the first part of this series, we looked at the underpinnings of Modern Portfolio Theory and generated an Efficient Frontier with the…

目次

  1. この記事の対象とするユーザー
  2. この記事で使用するもの
  3. 効率的フロンティア (Efficient Frontier)とは
  4. Google Colaboratoryのpythonでの利用方法
  5. 投資に関する免責事項プログラムや考え方の情報の提供・作業代行を目的としており、投資勧誘を目的とするものではありません。

この記事の対象とするユーザー

・効率的フロンティア (Efficient Frontier)について知識がある、もしくは興味を持っている方
・米国株のポートフォリオ運用に効率的フロンティア (Efficient Frontier)の考え方を用いて、最適化、リターンの向上並びにリスクの低減を考えている方
・解析を行うPythonプログラムコードに興味のある人

基本的にはコードをコピペすれば動くことを確認していますが、理論の内容の理解や、プログラムの理解などあった方がより深く理解できると思います。

この記事で使用するもの

・Google ColaboratoryのPython

となります。

上記の記事ではQUANDLというデータ配信サービスを使用してのデータ取得となっているので、それを
・Yahoo USからのデータ取得
・Google ColaboratoryのPythonを使う
ことにより、無料で、簡単に行おうということになります。

Retail Trading Activity Tracker: Keep track of retail sentiment
Keep your finger not he pulse of retail investor sentiment, and tracking over B USD/day of individual investors trades.

Quandlとは

QuandlはNasdaq傘下のでーは配信サービスプロバイダーになります。

https://www.nasdaq.com/about/press-center/nasdaq-acquires-quandl-advance-use-alternative-data

私もアカウントを持っていて、利用させていただいてます。
APIを利用して高品質なデータを配信しており、株価のヒストリカルデータだけでなくPBR,PERといったファンダメンタルデータなども提供しています。

API接続について

IB証券(インタラクティブ・ブローカーズ証券 )でもAPIによるデータアクセスは可能ですが、アクセススピードや簡便な操作方法など、頻繁にデータアクセスを必要する方は、Quandlはサービス契約を検討すべき・検討に値するサービスを提供しています。

IB証券(インタラクティブ・ブローカーズ証券 )のヒストリカルデータへのAPI接続については以前書いた以下の記事を参考にしてください。

効率的フロンティア (Efficient Frontier)とは

効率的フロンティアについてだけで、非常に多くの記事があるくらい奥深いものですので、ここでは概略だけ取り扱います。

みずほ証券が紹介している「ファイナンス用語集」のリンクを紹介します。

効率的フロンティア | みずほ証券 ファイナンス用語集
効率的フロンティア (Efficient Frontier)とは、分散投資を実施したときに実現するポートフォリオの中で、あるリスクの水準で最大のリターンを獲得できるポートフォリオの集合のことを指す

効率的フロンティア (Efficient Frontier)とは、分散投資を実施したときに実現するポートフォリオの中で、あるリスクの水準で最大のリターンを獲得できるポートフォリオの集合のこと

また、オリジナルの英文記事にも説明があります。

Markowitz’s Efficient Frontier in Python [Part 1/2]
Harry Markowitz’s contribution to the world of finance and economics cannot be emphasized enough. He is widely regarded as the pioneer of…

数学的なバックグラウンドに興味のある方は以下のサイトなども使って勉強すると良いと思います。

現代ポートフォリオ理論 - Wikipedia

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にした場合の形状を示します。

2022/11/06時点での時価総額上位5銘柄
2022/11/06時点での時価総額上位15銘柄
2022/11/06時点での時価総額上位30銘柄

今年の下落がありますので、リターンの数字が減ってきたりというものもありますが、基本的に形状は繭のような形はあまり変化しないことは分かります。

5.最後に今年のみのデータ(期間を2022/01/01から2022/11/06)にしたときの30銘柄の形状は以下の通りです。

いつもの見慣れた形状の完全な逆の形ですね、、それでもうまくポートフォリオを組めばプラスのゾーンもあるようです。いろいろ研究し甲斐のありそうな部分です。

コメントをいただきました。

オキシドールたかしさん、ありがとうございます。記事を各励みになります。大会頑張ってください!

投資に関する免責事項プログラムや考え方の情報の提供・作業代行を目的としており、投資勧誘を目的とするものではありません。

過去のデータを用いた解析は、将来の株価の推移を保証するものではないことをはご承知おきください。

また、どのような銘柄を選定するか、等ウェイトではどうなのかといった、ウェイトの掛け方等、さらなるブラッシュアップが可能であると思います。

---

Pytho,投資関係に関する記事をご紹介します。

Python高速化! for文は遅いので、”これ”を使うと30倍早いですよ!【Google ColabのPython:コピペで動く!】
【コピペで動く!】Pythonで1.5GBのcsvファイル読み込み高速化:1分5秒⇒4秒程度 DASK , pickle (Pythonコードあり)
【コピペで動く!】日本株、米国株で個別銘柄ベータ値(β値)を簡単に調べる方法 Python 米国株 Webサービス&コード 【Google Colabで違いをみつけろ!】
【コピペで動く!】Google ColabでPython を用いての 効率的フロンティア と ポートフォリオの最適化 Efficient Frontier & Portfolio Optimization with Python [Part 2/2]
【コピペで動く!】20行で資産運用モデル作成 Google ColabのPythonで 米国株の株価を取得し、グラフ・チャートを表示
過去50年間のS&P500の季節性の値動きから負けにくいポジション構築はできるのか考える・大統領選挙のアノマリー対応!【コピペで動く!】Google ColabのPythonで自分で調べてみよう!
曜日による米国株指数(S&P500)のパフォーマンスに違いはあるのか?有利なポジション取りはできるのか?【Google ColabのPython:コピペで動く!】Twitterで出てくる知見は本当か自分で調べてみよう。
【解決】スクレイピングでHTTP Error 403: Forbiddenでアクセスできないときに試すべき方法【コピペで動く!】【Google Colab:Python:pd.read_html,selenium】
【解決】Google ColabのPythonでエクセル(Excel)ファイルやCSVファイルに出力・入力する方法【コピペで動く!】
【コピペで動く!】レイ・ダリオ推奨「オール・ウェザー戦略」をETFで構築するには? ETFの手軽さとそのパフォーマンスの高さとは!【違いをみつけろ!】
【コピペで動く!】IB証券(インタラクティブ・ブローカーズ証券 )へのPythonでのAPI接続 ib_insync [自分が使っているPythonコード]
米国債のゼロクーポン債STRIPSについてのメモ
自動化・効率化でなにができるのか!Google FinanceやYahoo Financeからデータ取得して年初来パフォーマンスや週次騰落率のファクターチェック
米国株のティッカー(Symbol)のスクレイピングによる取得、APIによるヒストリカルデータやファンダメンタルズデータ取得について【コピペで動く!】Python,Quandl,無料枠あり
Google ColabでYouTube動画を開始・終了の時間を指定してPythonでダウンロード(音声・動画両方対応)【コピペで動く!】

コメント

  1. 湯豆腐 より:

    いつも楽しく、拝見しております。
    質問なのですが、selected = [‘CNP’, ‘F’, ‘WMT’, ‘GE’, ‘TSLA’]の部分を30銘柄などにすると蛹の繭のようななんとも言えない効率的フロンティアが出来上がります。
    この繭のような効率的フロンティアは正しい結果を表しているのでしょうか。

    拙い質問で申し訳ありませんが、お手すきの際にご確認いただけますと幸いです。

  2. 湯豆腐 より:

    早速検証してくださり、本当にありがとうございました。
    大学の授業で扱う部分であったため、より理解が深まりました。
    これからも、一ファンとして陰ながら応援しております。