現代ポートフォリオ理論(MPT)をPythonで実装しよう
はじめに ~ 現代ポートフォリオ理論って何? ~
Modern Portfolio Theory(MPT) = 現代ポートフォリオ理論。
ハリー・マーコウィッツというおじさんが提唱した理論で、ノーベル賞を受賞しています。
ファイナンス理論の下敷きというか、基礎でとても大事な考え方!
この記事は、現代ポートフォリオ理論をpythonで実装します。
理論とともに、リアルワールドの株価データを使ってビジュアライズしながら見きましょう。
今日つかうコードはgitlabにおいています。
現代ポートフォリオ理論の仮定
現代ポートフォリオ理論は、いくつかの仮定の上に成り立っています。
- 投資家は合理的であり、できるだけリスクを避ける
- 投資家はリターンを最大化することが目的である
- 投資家は期待リターンを最大化する
- 手数料と税は、とりあえず考えない
- 投資家は全ての情報を取得することができ、取得できる情報のレベルも同じである
- 投資家は、無限に無利子でお金を借りる
これらの仮定を理解した上で、現代ポートフォリオ理論とは...
投資家がポートフォリオを決めるときに、取れるリスクの中で最大のリターンの期待値を求める理論です。
現代ポートフォリオ理論の目の付け所がシャープだったのは、リスクとリターンの関係がポートフォリオにどう影響を与えているか、を分析した点です。
リターンをとればリスクが増える、というのは真実です。(ハイリスク・ハイリターン)
しかし、同じリスクでも期待できるリターンには差があります。
現代ポートフォリオ理論によって計算されたリターンとリスクの関係は、ビジュアライズすることができます。
2つの銘柄を組み合わせてポートフォリオを作成し、2つの銘柄の比率を変化させていきます。
比率を変化させていくごとに現代ポートフォリオ理論に則って、リターンとリスクを計算していくと、こんなグラフができあがります。
X軸がリスク、Y軸がリターンを表しています。
これを効率的フロンティアと呼びます。
投資家が許容できるリスクの中で最大のリターンを期待できるポイントがこのグラフからひと目でわかります。
この青部分と赤部分のリスクは同じです。
しかし、期待できるリターンには差があります。
これが同じリスクでも期待できるリターンには差がある、と言った意味です。
2つの銘柄の組み入れる比率を変えることで、期待できるリターンをコントロールすることができます。
これが現代ポートフォリオ理論において一番大事なことです。
Pythonで実装しよう
2つ以上の株価データを用意しましょう。
2つのデータは時系列で、かつ、終値があれば良しです。
pandasのDataFrame形式で扱います。
Unnamed: 0 Date Open High Low Close Volume Adj Close 24 4 2018年6月4日 1234 1246 1233 1244 2091700 1244 23 3 2018年6月5日 1253 1267 1251 1266 3545600 1266 22 2 2018年6月6日 1264 1268 1258 1266 2834800 1266 21 1 2018年6月7日 1270 1279 1262 1276 3662600 1276 20 0 2018年6月8日 1276 1287 1266 1280 5072300 1280 4 4 2018年7月2日 1275 1283 1248 1251 2375800 1251 3 3 2018年7月3日 1255 1262 1226 1241 3435500 1241 2 2 2018年7月4日 1230 1270 1228 1265 2100400 1265 1 1 2018年7月5日 1269 1281 1250 1262 3467900 1262 0 0 2018年7月6日 1266 1267 1240 1248 3424000 1248
基本的には関数は使わず、べた書きでコードは貼っていきますが、株価データの取得だけはべた書きだと冗長になるので関数化しておきます。
import pandas as pd import numpy as np import matplotlib import datetime import time matplotlib.use("Agg") import matplotlib.pyplot as plt def scraping(stock, days): end = datetime.date.today() start = end - datetime.timedelta(days=days) base = "http://info.finance.yahoo.co.jp/history/?code={0}.T&{1}&{2}&tm={3}&p={4}" start_g = str(start) start_g = start_g.split("-") start_g = "sy={0}&sm={1}&sd={2}".format(start_g[0], start_g[1], start_g[2]) end = str(end) end = end.split("-") end = "ey={0}&em={1}&ed={2}".format(end[0], end[1], end[2]) page = 1 term = "d" #term = "m" result = [] while True: url = base.format(stock, start_g, end, term, page) # print(url) try: df = pd.read_html(url, header=0) except ValueError: break if len(df[1]) == 0: break result.append(df[1]) page += 1 time.sleep(1.0) try: result = pd.concat(result) result.columns = ["Date", "Open", "High", "Low", "Close", "Volume", "Adj Close"] yahoo_df = pd.DataFrame(result) yahoo_df.to_csv("./data/code_" + str(stock) + ".csv", encoding="utf-8") except ValueError: pass
NTTデータ(9613)とSCSK(9719)のデータを使います。
今日(2018年7月7日)から180営業日前までのデータを取得します。
リターンとリスクを計算するときは、私はできるだけ長期間のデータを使用するべきだと考えています。
その理由は、長期間でなければその銘柄の本当のリターンとリスクが見えないからです。
短期的には経済など外部要因が大きく株価に影響を与えることは多いですから。
このコードは、スクレイピングした株価データをdataというフォルダに格納するので、dataフォルダを作成します。
stock_list = [9613, 9719] days = 180 for s in stock_list: scraping(s, days)
株価データをconcatして、一つのDataFrameにします。
master_df = pd.DataFrame({}) for s in stock_list: df = pd.read_csv("./data/code_{0}.csv".format(s)) df = df.sort_values("Date", ascending=True) print(df.tail(10)) tmp_df = df.loc[:, ["Date", "Adj Close"]] tmp_df.columns = ["Date_{0}".format(s), "Close_{0}".format(s)] master_df = pd.concat([master_df, tmp_df["Close_{0}".format(s)]], axis=1)
データの加工は続きます。
リターンと共分散を計算します。
# Calculate annual average return # 年率を計算する前に日率を計算 # .pct_change:Rate of change # リターンを計算する便利な関数 print(master_df.tail(10)) returns_daily = master_df.pct_change() print(returns_daily.tail(10)) # Convert to annual rate. working day is 250/365. # 年率に変換. 250は年間の市場が動いている日数. returns_annual = returns_daily.mean() * 250 print("returns_annual") print(returns_annual) # Calculate covariance # 各々のリターンから共分散を計算 cov_daily = returns_daily.cov() cov_annual = cov_daily * 250 # print("cov_annual") # print(cov_annual)
DataFrameの加工は終了です。
さて、効率的フロンティアをえがきましょう!
NTTデータ(9613)とSCSK(9719)を様々な組み合わせ(50000パターン)でリターン・リスクを計算してきます。
※"Sharpe Ratio"の項目は今はムシしてください。後で使います。
# Define list of returns, volatilities, ratiosm, sharpe ratio # 諸々のリスト定義 port_returns = [] port_volatility = [] stock_weights = [] sharpe_ratio = [] # Choose a prime number. # seedは素数を選ぼう np.random.seed(101) # Number of stocks to be combined # 組み入れる株の数 num_assets = len(stock_list) # Number of trials of portfolio creation pattern # ポートフォリオ作成パターンの試行回数 num_portfolios = 50000 # Randomly calculate portfolio risks and returns # 様々な銘柄の比率でのポートフォリオのリターンとリスクを計算 for single_portfolio in range(num_portfolios): # Determination of the ratio of stocks by random number # 銘柄の比率を乱数で決定 weights = np.random.random(num_assets) weights /= np.sum(weights) # Calculate expected return on portfolio # ポートフォリオの期待リターンを計算 returns = np.dot(weights, returns_annual) # print("returns : ", returns) # Calculate portfolio volatility # ポートフォリオのボラティリティを計算 volatility = np.sqrt(np.dot(weights.T, np.dot(cov_annual, weights))) # print("volatility : ", volatility) # Calculate sharp ratio sharpe = returns / volatility sharpe_ratio.append(sharpe) # Store calculated values in list # 計算値をリストに格納 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} # Add ratio data # 計算したポートフォリオのリターンとリスクに、比率のデータを加える for counter,symbol in enumerate(stock_list): portfolio[str(symbol) + " Weight"] = [Weight[counter] for Weight in stock_weights] # Convert to DataFrame # PandasのDataFrameに変換 df = pd.DataFrame(portfolio) # Done # データフレーム完成 column_order = ["Returns", "Volatility", "Sharpe Ratio"] + [str(stock)+" Weight" for stock in stock_list] df = df[column_order] print(df.head(10))
print(df.head(10)) で計算された様々なポートフォリオのパターンが格納されています。
seabornで可視化しましょう。
# look an efficient frontier # 50000パターンのポートフォリオを可視化して、効率的フロンティアを探る plt.style.use("seaborn") df.plot.scatter(x="Volatility", y="Returns", c="Sharpe Ratio", cmap="RdYlGn", edgecolors="black", figsize=(10, 8), grid=True) plt.xlim([0,1]) plt.xlabel("Volatility (Std. Deviation)") plt.ylabel("Expected Returns") plt.title("Efficient Frontier") plt.savefig("image.png")
あまりきれいなグラフではありません。
なぜならNTTデータ(9613)とSCSK(9719)が同じ業種で同じような株価データだからです。
効率的フロンティアは、相関係数が低いほどきれいなカーブがえがけます。
SoftBank(9984)とTIS(3626)に入れ替えて、もう一度計算してみましょう。
stock_list = [9984, 3626]
NTTデータ(9613)とSCSK(9719)の過去180営業日の相関係数は、0.866926です。
対し、SoftBank(9984)とTIS(3626)は、-0.380903です。
3つ以上の銘柄で効率的フロンティアをえがく
TIS(3626), NTTデータ(9613), Yahoo(4689), SoftBank(9984)でポートフォリオをつくってみましょう。
stock_list = [3626, 9613, 4689, 9984]
もっとも効率のいいポートフォリオはどの組み合わせ?
効率のいいポートフォリオを見つけるためにシャープ・レシオを使います。
Sharp Ratio = (Expected return - Risk-free rate of return) / Standard deviation シャープ・レシオ = (期待リターン - 無リスク資産) / 標準偏差
無リスク資産とは、債券などをさしていてい、今回はポートフォリオに入れていないので、考慮なしです。
そのため、実質の計算式は、
シャープ・レシオ = 期待リターン / 標準偏差
となります。
# Calculate sharp ratio
sharpe = returns / volatility
sharpe_ratio.append(sharpe)
この数値、さきほどポートフォリオを50000パターンの計算していたコードの中に既にあります。
DataFrameの中にも"Sharp Ratio"というカラムはあります。
そのため、最終的なアウトプットであるDataFrameをシャープ・レシオでソートすると、最も効率のいいポートフォリオを導くことができます。
df = df.sort_values("Sharpe Ratio", ascending=True) print(df.tail(1)) # Returns Volatility Sharpe Ratio 3626 Weight 9613 Weight \ # 0.691151 0.618261 1.117896 0.871933 0.008536 # 4689 Weight 9984 Weight # 0.002589 0.116942
シャープ・レシオが1.117で、
資産の87.19%をTIS(3626)、0.8%をNTTデータ(9613)、0.2%をYahoo(4689)、11.69%をSoftBank(9984)に振り分けるポートフォリオが一番最適であるとなりました。
過去180営業日しかとっていないので、TIS買っとけ、みたいな分析結果になってしまいました。
ちなみに、シャープ・レシオに色をつけて、可視化するともっとわかりやすくなります。
df.plot.scatter(x="Volatility", y="Returns", c="Sharpe Ratio", cmap="RdYlGn", edgecolors="black", figsize=(10, 8), grid=True) # df.plot.scatter(x='Volatility', y='Returns', figsize=(10, 8), grid=True)
まとめ
久々にちゃんと記事書きました。褒めて。
以上、終わり!