効率的フロンティア
「虎穴に入らずんば虎子を得ず」と言う諺のように、
大きなリスクを取らなければ、大きなリターンは得られない。
多くの場合、その通りなんだけど、
リスクとリターンの大きさは必ずしも一致しない。
そこで、リスクを最小に抑えながら、リターンを最大にする方法を突き詰めて行くと、効率的フロンティアという考え方に行き着く。
ここで言う、リスクは危機を意味していて、
「危」が意味する危険=ピンチと
「機」が意味する機会=チャンスを意味する。
これを株に置き換えると、ボラティリティ(変動幅)になる。
つまり、リスクを最小に抑えながら、リターンを最大にするとは、ボラティリティ最小に抑えながら、リターンを最大にする試みである。
これの何が良いかと言うと、変動が抑えられるので、安定した運用ができる点である。変動が抑えられると心理的な安心感に繋がるし、老後の資産切り崩しの局面でも有効な考え方の様に思われる。
少し、前置きが長くなったが、下記で公開されている効率的フロンティアを算出するコードを、カスタマイズして、パイチャートや価格の推移を付け足したり、チャート上に銘柄名を表示したりと、機能拡張したので、シェアしたいと思う。(X軸が表示されない問題にも対応。)
0.事前準備
事前準備はこちら。5分もあればできると思います。
1.コード
コード先頭の「期間」や「銘柄」や「組み合わせ数」を変更し、保有銘柄や購入予定銘柄の最適配分を考える参考にしてみてはいかがだろうか。
(fix_yahoo_finance が使えなくなる問題が発生したので、参照先を変更した、2021/07/03修正版をおまけに付けておきます。)
######## 期間設定 #######
start = datetime.date(2017,1,1)
end = datetime.date.today()
######## 銘柄 ##########
selected = ['VOO','QQQ','^N225','TLT']
######## 組み合わせ数#######
num_portfolios = 50000
注意点は、下記3点。
・上場して間もない銘柄は「期間」を短くする必要がある。
・「銘柄」を多くすると時間がかかるし、見にくくなる。
・「組み合わせ数」を多くすると精度は上がるが時間がかかる。
下記をコピペして使ってみてください。
# import needed modules
import datetime
import fix_yahoo_finance as yf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
%matplotlib inline
######## 期間設定 #######
start = datetime.date(2017,1,1)
end = datetime.date.today()
######## 銘柄 ##########
selected = ['VOO','QQQ','^N225','TLT']
######## 組み合わせ数#######
num_portfolios = 50000
########################
data = yf.download(selected, start=start, end=end)["Adj Close"]
data = data.reindex(columns=selected)
# calculate daily and annual returns of the stocks
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 = 200000
#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]
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]
## グラフパラメータ設定 ###
plt.rcParams.update(plt.rcParamsDefault)
# plt.style.use('dark_background')
# plt.style.use('seaborn-pastel')
# plt.style.use('seaborn-bright')
plt.rcParams["axes.labelcolor"] = 'white'
plt.rcParams['axes.facecolor'] = 'black'
plt.rcParams['xtick.color'] = 'gray'
plt.rcParams['ytick.color'] = 'gray'
plt.rcParams['text.color'] = 'white'
## グラフ領域設定 ###
fig = plt.figure(facecolor='white',figsize=(14,6),tight_layout="True")
spec = gridspec.GridSpec(ncols=3, nrows=2,height_ratios=[1,1],width_ratios=[15,3,4])
## グラフに領域を割り当て###
ax1 = fig.add_subplot(spec[:,0], title='Efficient Frontier ( '+str(start.year) +" - "+str(end.year)+ " )")
ax2 = fig.add_subplot(spec[0,1], title='Sharpe_port' )
ax3 = fig.add_subplot(spec[1,1], title='Min_variance_port')
ax4 = fig.add_subplot(spec[0,2], title='Sharpe_port')
ax5 = fig.add_subplot(spec[1,2], title='Min_variance_port')
fig.patch.set_facecolor('black')
# plot frontier, max sharpe & min Volatility values with a scatterplot
df.plot.scatter(x='Volatility', y='Returns', c='Sharpe Ratio',
cmap='jet', edgecolors='black',alpha=1, ax=ax1)
ax1.scatter(x=sharpe_portfolio['Volatility'], y=sharpe_portfolio['Returns'], c='red', marker='D', s=50)
ax1.scatter(x=min_variance_port['Volatility'], y=min_variance_port['Returns'], c='blue', marker='D', s=50 )
for counter,symbol in enumerate(selected):
# portfolio[symbol+' Weight'] = [Weight[counter] for Weight in stock_weights]
stock = symbol
max_ticker = df[stock+' Weight'].max()
max_tiker_portfolio = df.loc[df[stock+' Weight'] == max_ticker]
ax1.scatter(x=max_tiker_portfolio['Volatility'], y=max_tiker_portfolio['Returns'], c='y', marker='*', s=100)
ax1.annotate(stock, (max_tiker_portfolio['Volatility']*1.005, max_tiker_portfolio['Returns']*0.995),size=12,color="white")
## パイチャート1 ###
df_pie1=sharpe_portfolio.T.iloc[3:,:]
df_pie1=df_pie1.sort_values(by=df_pie1.columns[0], axis=0, ascending=True, inplace=False)
col1=[s.replace(' Weight', '') for s in df_pie1.index.tolist()]
ax2.pie(df_pie1.iloc[:,0].tolist(), autopct="%1.1f%%",labels=col1,startangle=90)
## パイチャート2 ###
df_pie2=min_variance_port.T.iloc[3:,:]
df_pie2=df_pie2.sort_values(by=df_pie2.columns[0], axis=0, ascending=True, inplace=False)
col2=[s.replace(' Weight', '') for s in df_pie2.index.tolist()]
ax3.pie(df_pie2.iloc[:,0].tolist(), autopct="%1.1f%%",labels=col2,startangle=90)
## 積み上げグラフの元データ ###
df_all=(1+data.pct_change()).cumprod()
## 積み上げグラフ1 ###
df_sharpe=df_all.loc[:,col1]
for i in range(len(col1)):
df_sharpe.iloc[:,i]=df_sharpe.iloc[:,i] * df_pie1.iloc[:,0].values[i]
ax4.stackplot(df_sharpe.index, df_sharpe.values.T)
ax4.tick_params(axis='x', labelrotation=45)
## 積み上げグラフ2 ###
df_min=df_all.loc[:,col2]
for i in range(len(col2)):
df_min.iloc[:,i]=df_min.iloc[:,i] * df_pie2.iloc[:,0].values[i]
ax5.stackplot(df_min.index, df_min.values.T)
ax5.tick_params(axis='x', labelrotation=45)
plt.show()
## ポートフォリオをテキスト出力(ソートして出力) ###
print("\n---sharpe_portfolio-----\n")
sharpe_sort=sharpe_portfolio.iloc[:,3:].T
sharpe_sort.sort_values(by=sharpe_sort.columns[0] ,ascending=False ,axis=0,inplace=True)
print(sharpe_portfolio.iloc[:,0:2].T)
print(sharpe_sort)
print("\n---min_variance_port-----\n")
min_sort=min_variance_port.iloc[:,3:].T
min_sort.sort_values(by=min_sort.columns[0] ,ascending=False ,axis=0,inplace=True)
print(min_variance_port.iloc[:,0:2].T)
print(min_sort)
## ここまで ###
2.実行結果
実行すると下記の様な結果が出力されます。
グラフの見方
グラフ名:グラフ名に開始と終了の年を表示
縦軸:リターン、横軸:ボラティリティ、点の色:シャープレシオ
赤の点:接点ポートフォリオ(シャープレシオ最大)
青の点:最小分散ポートフォリオ
右上:赤の点のパイチャートと価格推移
右下:青の点のパイチャートと価格推移の
★:対象銘柄の最大ポートフォリオ(組み合わせ数を多くすれば100%に近くなります。)
下記の様に、接点ポートフォリオと最小分散ポートフォリオの情報がテキストでも出力されます。(占める割合の多い順に出力するように修正。)
おまけ
コードの中段の下記のコメントアウトされているパラメータを有効にすると、図の色が下記に変わったりもします。
## グラフパラメータ設定 ###
plt.style.use('seaborn-pastel')
3.解説
個人的見解ですが、少し解説してみたいと思います。
これは、過去の値動きから算出したものであり、今後も同じようになるとは限りませんが、効率的フロンティアを知っておくと、投資の選択肢の幅が広がるように思います。
例えば、下図で「COST、ROST,MNST,TJX」を見てみましょう。
この図で、ROSTとCOSTに注目すると、
リターンが同じでボラティリティが小さいなら、COSTを選択するのが合理的に見えます。
しかし、ボラティリティが大きいROSTが大きく落ち込んだ時に、ROSTを仕込むのが合理的な局面もあります。
投資タイミングや、ボラティリティとどのように付き合うかという投資手法によって、結論は異なるように思います。
次に、TJXとMNSTに注目すると、
ボラティリティが同じ程度で、MNSTのリターンが上回るなら、MNSTを選択するのが合理的に見えます。
しかし、投資資金に余裕があるのならば、下記の様に両方の株を買うと、中間のリターンを得ながら、ボラティリティを抑えることができます。(ボラティリティが同じ程度のものでも、変動するタイミングが違うので、分散するとボラティリティを下げることができるのです。)
このように、その人の投資手法や状況によって、同じ情報を見ていても結論が異なる様に思います。
しかし、効率的フロンティアの考え方を知っておくと、あなたの投資の選択肢の幅を与えてくれるでしょう。
あなたの投資が、少しでも実りあるものになれば幸いです。
では!
おつかれさん「缶コーヒー1杯ぐらい、ご馳走してあげよう」という太っ腹な方がいれば、よろしくお願いします!
参考になったら「ハートボタン、フォロー、リツイート」をお願いします。
読まれる可能性があがるので、次の記事を書くやる気が出ます。
結構、機能性を備えながらクールなデザインになったと思うんだけど…
おまけ1
Nikeを使って、ナイキのロゴっぽいもの作ってみました。
少しロゴの方向が違いますが・・・
おまけ2
銘柄指定では、個別株やVOOのようなETFが指定できますが、他にも、「.T」をつけることで「日本株の銘柄」も指定できます。
######## 銘柄 ##########
selected = ['2782.T','3064.T','9843.T','VOO','DIS','DG','KO']
実行すると、下記の様な実行結果が出力されます。
2782.T:セリア
3064.T:モノタロウ
9843.T:ニトリ
おまけ3
課金してコーヒーを御馳走してくれて方へ、おまけ機能ですが、「接点ポートフォリオに占める割合の多い順」に、下記の様に、銘柄(ETF)のチャート出力する機能を付けました。(最小分散ポートフォリオを出力するのも可能です。)
ポートフォリオの割合を考えながら、買い増し候補銘柄のチャートが見れるので、買うタイミングの参考にしてはいかがでしょうか?
チャートには、移動平均線、ボリンジャーバンド、RSI、MACD、出来高が表示されます。
ここから先は
¥ 100
この記事が気に入ったらチップで応援してみませんか?