Pythonによる超初歩的な金融資産解析(ついでのビットコイン)
概要
資産を増やす金融商品として、投資信託や株、債権なんかがメジャーです。初歩的なポートフォリオ理論では
- 株などの資産がどのように変動するかは予測することが出来ない
- 一方で経済は成長するので、全体を長期的に見たらプラスに成長する
- なので、分散して投資することで安定的な利益を得ることが出来る
といったことが前提とされます。即ち、価格の変動が正規分布に従ってランダムウォークするとみなして、観測された変動の平均と分散を元に、どれだけリスクを取ることが出来るかを踏まえて資産の内訳を作ります。そのため様々な株式や債権を纏めた商品である投資信託は、理論的には優等生な商品と見なすことができます*1。
正直な所、金融商品については、記載情報を見ても何を買うべきなのかよく分かりません。一方で、金融では時系列データが結構揃っていて弄ってみるだけでも面白そうです。今回はデータのハンドリングを通して、初歩的な金融資産のバランシングについて解析しました。
注意
- マサカリ歓迎です。特にこの記事の筆者は金融業界について全くの素人です。橘玲氏の本を読んだ程度の知識しかありません。
- この記事によって生じたあらゆる事象について、筆者は責任を負いません。
- この記事は個人の見解と趣味の産物であり、投資を勧めるものでは全くありません。ほんとに。
材料
投資信託基準価格
以下のものをそれぞれのWebサイトからダウンロードしました。
- 三井住友 - 三井住友・DCつみたてNISA・全海外株インデックスファンド: モーニングスターより
- レオス-ひふみプラス: モーニングスターより
- ニッセイ -ニッセイTOPIXインデックスファンド: モーニングスターより
- ニッセイ-ニッセイ日経225インデックスファンド: モーニングスターより
- 三井住友・日本債券インデックス・ファンド: モーニングスターより
- Vanguard total world stock(VT): Yahoo! Financeより
- Vanguard total stock marcket ETF(VTI): Yahoo! Fiananceより
- Vanguard FTSE Emerging Markets ETF(VWO): Yahoo! Fiananceより
つまり、国内系ファンドはモーニングスター、海外ETFはY! Financeです。国内ファンドはSBIで購入することが出来るものしかありませんから、そちらからもダウンロードできます。しかし自分の環境*2では、csvファイルなのに違う拡張子としてダウンロードされたり、ファイルフォーマットが分析に使いづらいものだったので利用をやめました。モーニングスターはWebサイトが重いという欠点はありますが、ヘッダーがすっきりしていて使いやすいです。
ファンドの選定基準としては、以下のとおりです。
- SMBC全海外: 日本を除いた全世界ファンド
- ひふみプラス: 高パフォーマンスのアクティブファンド
- TOPIX: 日本株式のベンチマーク
- 日系225: 大型日本株式のベンチマーク & TOPIXとの比較
- 日本債権: 債権を入れることのリスクのヘッジ率を知りたかった
- VT: 全世界株式分散投資ファンド
- VTI: 米国株式投資ファンド
- VWO: 新興国分散投資ファンド
為替価格
海外ETFについては、ドル価格を円価格に修正する必要があります。今回はマネースクウェア・ジャパンからダウンロードしました。より長期のデータソースを知っている人が居たら教えてくれると助かります…。
実際には為替手数料がこの価格に加わる気がしますが、明るくないので割愛しました。
手法
いつものごとくPythonでやります。先ずダウンロードしてきたcsvファイルを読み込んでデータフレームを作ります。Y!から持ってきたデータはいいのですが、モーニングスターのそれはheaderが日本語です。なので、事前にエクセルなんかで英語に書き換えておかないとpandasが怒ります。注意して下さい。
import pandas as pd import numpy as np # USD/JPY比 exchange = pd.read_csv("USDJPY.csv", index_col=0, parse_dates=True) exchange = exchange[["close"]] # 日本系投資信託 smbc_astock = pd.read_csv("smbc_astock.csv", index_col=0, parse_dates=True) smbc_astock.columns = ["smbc_astock"] hifumi = pd.read_csv("hifumi.csv", index_col=0, parse_dates=True) hifumi.columns = ["hifumi"] topix = pd.read_csv("topix.csv", index_col=0, parse_dates=True) topix.columns = ["topix"] nikkei = pd.read_csv("nikkei.csv", index_col=0, parse_dates=True) nikkei.columns = ["nikkei"] smbc_jcredit = pd.read_csv("smbc_jcredit.csv", index_col=0, parse_dates=True) smbc_jcredit.columns = ["smbc_jcredit"] # 海外ETF # USDで売られているので、為替価格の調整をする vti = pd.read_csv("VTI.csv", index_col=0, parse_dates=True) vti = vti[["Adj Close"]] vti.columns = ["vti"] vti = pd.DataFrame(vti["vti"] * exchange["close"], columns = ["vti"]) vti = vti.dropna() vt = pd.read_csv("VT.csv", index_col=0, parse_dates=True) vt = vt[["Adj Close"]] vt.columns = ["vt"] vt = pd.DataFrame(vt["vt"] * exchange["close"], columns=["vt"]) vt = vt.dropna() vwo = pd.read_csv("VWO.csv", index_col=0, parse_dates=True) vwo = vwo[["Adj Close"]] vwo.columns = ["vwo"] vwo = pd.DataFrame(vwo["vwo"] * exchange["close"], columns=["vwo"]) vwo = vwo.dropna() # 全部結合する df = pd.DataFrame([]) df = df.join([ smbc_astock, hifumi, topix, nikkei, smbc_jcredit, vti, vt, vwo, ], how="outer") # 日付情報を初日分から生成 date = pd.DataFrame(index=pd.date_range(start=df.index[0], end="2018-01-30", period=1)) df = df.join(date, how="outer") # dataframe用のラベル order = [ "smbc_astock", "hifumi", "topix", "nikkei", "smbc_jcredit", "vti", "vt", "vwo" ] df = df[order]
続いて収益率を計算していきます。収益率は購入時の価格と比較した際の、得られる利益のパーセント値です。たとえば100円で購入した資産が110円になったら収益率は10%ですね。
今回はX[年|日]後の収益率を計算し、更に過去Y[年|日]間の収益率から、収益率の統計値を算出します。XとYについては、短期の動きと長期の動きのどちらを見たいのかで、短期: (X=Y=90日)、長期: (X=365年, Y=365年)としました。
今回の計算は最初のデータが有る日から、全日程を計算対象としています。ただし土日祝祭日は証券取引所が開いていないため、NaNになります。とはいえpandasが統計値を計算する時には、これを自動で取り除いてくれます。各商品毎に収益率を計算しているのは、日本とアメリカでNaNになる部分が異なるからです。
統計値については平均、分散、そしてシャープレシオを計算します。シャープレシオは平均/分散で定義される統計値です。高いほど良い金融資産というようにみなされるんだとか。
import matplotlib.pyplot as plt # 収益率の計算 diff = 90 for i, o in enumerate(order): ds = df[o].dropna() rds = (ds.iloc[diff:].reset_index(drop=True) - ds.iloc[:-diff].reset_index(drop=True)) / ds.iloc[:-diff].reset_index(drop=True) * 100 rds.index = ds.index[diff:] rdf = pd.DataFrame(rds) if i == 0: r = rdf else: r = r.join(rdf, how="outer") r = r.join(date) # 統計値の計算 ave_return_list = [] std_return_list = [] sharpe_ratio_list = [] term = 90 for i in range(len(r) - term): ave_return_list.append(r.iloc[i:term+i].mean()) std_return_list.append(r.iloc[i:term+i].std()) sharpe_ratio_list.append((r.iloc[i:term+i].mean() / r.iloc[i:term+i].std()).to_dict()) ave_return_df = pd.DataFrame(ave_return_list, index=r.index[term:])[order] std_return_df = pd.DataFrame(std_return_list, index=r.index[term:])[order] sharpe_ratio_df = pd.DataFrame(sharpe_ratio_list, index=r.index[term:])[order] # 可視化 fig = plt.figure(figsize=(15,10)) ax = fig.add_subplot(3,2,1) ave_return_df.plot(title="Long term", xticks=[], ax=ax, legend=False) ax.set_ylabel("Return") ax = fig.add_subplot(3,2,2) ave_return_df.plot(title="Short term", xticks=[], xlim=("2016-10-01", "2017-1-30"), ylim=(-5,15), ax=ax, legend=False) ax = fig.add_subplot(3,2,3) std_return_df.plot(xticks=[], ylim=(0,40), ax=ax, legend=False) ax.set_ylabel("Risk") ax = fig.add_subplot(3,2,4) std_return_df.plot(xticks=[], xlim=("2016-10-01", "2017-1-30"), ylim=(0,10), ax=ax, legend=False) ax = fig.add_subplot(3,2,5) sharpe_ratio_df.plot(ax=ax, ylim=(-5,20), legend=False) ax.set_ylabel("Sharpe ratio") ax = fig.add_subplot(3,2,6) sharpe_ratio_df.plot(xlim=("2016-10-01", "2017-1-30"), ylim=(0, 4), ax=ax, legend=True) ax.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.) plt.show()
最後に、計算した収益率からリスクリターン分布のプロットをします。この図は、横軸に収益率の分散、縦軸に収益率の平均をとったものです。詳細はWikipediaを見てもらうとして、今回挙げた金融商品を一定の割合で保持していた場合に、過去365日間での収益はいかほどだったのかを調べます。
金融資産の割合は、ディリクレ分布からパラメータを適当に変えて生成してみます。最終的に、シャープレシオの順にソートし、1年前に買っていたら今頃儲かっていたという、金融資産の割合を調べます。乱数から統計値を計算する際に、高速化のために逆ソートしてから計算しています。ソートせずに後ろからテーブルを出力しようとすると遅いです。
可視化は最終的に出力したテーブルから出しても良いのですが、そちらだと色が一色になって重なりが見えづらいです。
from scipy.stats import dirichlet import matplotlib.pyplot as plt %matplotlib inline # 収益率の計算 diff = 365 for i, o in enumerate(order): ds = df[o].dropna() rds = (ds.iloc[diff:].reset_index(drop=True) - ds.iloc[:-diff].reset_index(drop=True)) / ds.iloc[:-diff].reset_index(drop=True) * 100 rds.index = ds.index[diff:] rdf = pd.DataFrame(rds) if i == 0: r = rdf else: r = r.join(rdf, how="outer") r = r.join(date) [f:id:ajhjhaf:20180203000841p:plain] # 資産割合の乱数を生成し、その度に可視化 fig = plt.figure(figsize=(15,5)) ax = fig.add_subplot(1,1,1) result = [] term = 365 for alpha in [0.5, 5, 50, 500]: for i in range(2000): w = dirichlet.rvs(alpha=[alpha / df.shape[1]]*df.shape[1])[0] ave_r = r.sort_index(ascending=False).iloc[:term].mean() var_r = r.sort_index(ascending=False).iloc[:term].cov() e_ret = np.dot(w, ave_r) e_ris = np.sqrt(np.dot(np.dot(w, var_r), w.T)) d = {"weight": w, "return": e_ret, "risk": e_ris, "ratio": e_ret / e_ris} result.append(d) ax.plot(e_ris, e_ret, "o", ms=3) ax.set_xlabel("Risk(Standard deviation)") ax.set_ylabel("Return(Average)") ax.plot([0, 40], [0, 40]) # 集計 result_df = pd.DataFrame(result) result_df = result_df.sort_values("ratio", ascending=False) weight_df = (pd.DataFrame(result_df["weight"].values.tolist(), columns=order) * 100).round() weight_df.index = result_df.index result_weight_df = weight_df.join(result_df.sort_values("ratio", ascending=False)[["ratio", "return", "risk"]] / diff * 365)
結果
あまり大した考察は出来ませんが、コメントです。
90日での値動き
左側がx軸の時系列を長めに取った図、右側が最近の時系列に注目した図です。一番上の段が収益率平均、中段が収益率分散、下段がシャープレシオです。
収益率について、2008年ぐらいにリーマン・ショックがあって株価がめちゃめちゃに落ち込んでいます。しかしそこからは基本的に正のリターンを持っているので、儲かっていますね。ここ最近の収益率も右肩上がりです。
こういう図をプロットするまで信じていなかったのですが、たしかに債権はリーマン・ショックのときにも落ち込んでいませんし、資産担保が出来ています。まぁ、一方で全然プラスになっていませんが、安定的だということがわかります。2段目の分散も低いですね。
VTとSMBC 全海外は日本が含まれていないこと以外はポートフォリオが似ていますが、実際パフォーマンスも似ています。一方で、ここ最近は日本の株価が上昇傾向だったから、VTの方がパフォーマンスが良かったんですね。
VTIはVTよりパフォーマンスが良いと言われることもありますが、ここ最近は分散がかなり高いです。リターンも実際高いんですが、リスクの兼ね合いが大事です。
VWOの傾向から、ここ最近は新興国株が安定して上昇傾向だったことがわかります。リターンは高くとも、分散が低く、シャープレシオが凄いことになっていますね。
365日での値動き
ひふみプラスが凄いことになっています。そりゃー人気が出るわけです。ただし、90日のプロットの方を見ればわかりますが、ここ最近のパフォーマンスはやや低めです。
90日の時には殆ど見られませんでしたが、365日だとファンドが始まった時にシャープレシオが安定しないように見えます。基本的に規模が小さすぎて、様子見状態で分散が小さいことが原因と推測します。
短期の場合と異なり、VWOの分散が最も高いです。ただしこちらのほうが、多く言われている状態に一致するようです。その分最近の新興国株式の状態が良かったと推測できます。
リスクリターン分布
sharpe ratio順にソートしたものを上位10個だけ出力します。見やすくするために、1%以下の数値については丸めています。
index | smbc_astock | hifumi | topix | nikkei | smbc_jcredit | vti | vt | vwo | ratio | return | risk |
---|---|---|---|---|---|---|---|---|---|---|---|
1386 | 0 | 16 | 0 | 0 | 84 | 0 | 0 | 0 | 5.20125 | 5.88951 | 1.13233 |
313 | 0 | 12 | 0 | 3 | 84 | 0 | 0 | 0 | 5.09683 | 5.3542 | 1.0505 |
1108 | 0 | 12 | 1 | 0 | 83 | 0 | 4 | 0 | 4.97517 | 5.50391 | 1.10628 |
1103 | 0 | 6 | 0 | 7 | 86 | 0 | 0 | 0 | 4.95763 | 4.2625 | 0.859785 |
1302 | 0 | 11 | 0 | 0 | 87 | 1 | 1 | 0 | 4.95401 | 4.94603 | 0.998389 |
1495 | 0 | 4 | 0 | 8 | 86 | 0 | 2 | 0 | 4.93894 | 3.98458 | 0.806767 |
1470 | 0 | 12 | 0 | 0 | 88 | 0 | 0 | 0 | 4.50332 | 4.88915 | 1.08568 |
416 | 7 | 0 | 5 | 0 | 87 | 0 | 1 | 0 | 4.37415 | 3.30528 | 0.755639 |
1355 | 0 | 0 | 0 | 13 | 86 | 0 | 0 | 0 | 4.23473 | 3.36452 | 0.794506 |
効率的フロンティア曲線っぽいものが見えます。が、どこか歪な形ですね。今回は全部で8000個の乱数を生成しているんですが、出し方が完全にランダムなので効率があまり良くありません。シャープレシオが高くなるようにサンプリングを工夫すれば、もっとはっきりと見えるようになると思います。
表を単純に出力しただけでは、リスクをなんとか下げようとしてしまい、債権ばっかになってしまいます。これはこれで理想的なポートフォリオなんですが、リターンは大きくありません。試しにリターンが20%以上のバランスだけ見てみます。
index | smbc_astock | hifumi | topix | nikkei | smbc_jcredit | vti | vt | vwo | ratio | return | risk |
---|---|---|---|---|---|---|---|---|---|---|---|
1280 | 0 | 73 | 0 | 0 | 26 | 1 | 0 | 0 | 1.66061 | 20.6777 | 12.4519 |
1945 | 0 | 76 | 0 | 1 | 23 | 0 | 0 | 0 | 1.63506 | 21.3648 | 13.0667 |
217 | 0 | 75 | 0 | 0 | 24 | 0 | 0 | 1 | 1.63401 | 21.1787 | 12.9612 |
169 | 0 | 80 | 0 | 0 | 17 | 0 | 2 | 0 | 1.59045 | 22.6507 | 14.2417 |
176 | 0 | 87 | 0 | 0 | 12 | 1 | 0 | 0 | 1.58291 | 24.1981 | 15.2871 |
1440 | 0 | 84 | 0 | 0 | 14 | 0 | 0 | 2 | 1.5753 | 23.5152 | 14.9275 |
1246 | 0 | 87 | 0 | 1 | 12 | 1 | 0 | 0 | 1.57514 | 24.2402 | 15.3892 |
617 | 0 | 89 | 0 | 0 | 10 | 1 | 0 | 0 | 1.57479 | 24.7418 | 15.7112 |
1264 | 0 | 89 | 0 | 0 | 10 | 0 | 1 | 0 | 1.57079 | 24.71 | 15.7309 |
ひふみは面白いですね。インデックスファンドに勝利してるアクティブファンドって点で凄いと思いました。ただし、これが過去1年間の後ろ向き分析ということには注意して下さい。これから購入しても、同じパフォーマンスが得られるとは限りません。
- このプロットには、自分のポートフォリオを載せることも出来ます。以下を乱数のプロットが終わった後に記述すればOKです。リバランスするときなんかに、役立てられると思います。
# 保有資産の割合を入力 w = np.array([0.5, 0, 0, 0, 0, 0.35, 0.15, 0]) expected_return = np.dot(w, ave_r) expected_risk = np.sqrt(np.dot(np.dot(w, var_r), w.T)) sharpe_ratio = expected_return / expected_risk print("{0}日の期待リターンは{1}%でした".format(diff, expected_return)) print("{0}日の期待リスクは{1}%でした".format(diff, expected_risk)) print("{0}日のシャープレシオは{1}%でした".format(diff, sharpe_ratio)) ax.plot(expected_risk, expected_return, "^", ms=20)
おまけ
ビットコインについての価格変動も、このスキームで調べてみましょう。今流行っているアレです。
ビットコイン価格
Yahoo! Financeからダウンロードしました。この価格はBTC-USDであることに注意してください。国内の投資信託と合わせるために、為替価格で補正する必要があります。
結果
ビットコインは値動きが激しいことが予測されるので、X=Y=30日での統計値を見ます。
面白いことに、なんだかんだ言ってここ最近購入していても儲かる可能性はあったようです。ただし分散が30日間で平均してもずいぶん大きいです。なので購入するタイミング次第で物凄い高値を掴みうるリスクがあります。僕が友達と会って「最近ビットコインがアツいけど、もう手遅れだよね」と話したのが11月中頃でしたが、そういえば随分伸びたものです。しかしリスクが凄まじい。場合によっては資産が60%以上丸ごと減る可能性もあるわけです。
続いて直近の30日の履歴から求めたリスクリターン分布のプロットです。
随分面白い形になりました。超ハイリターンハイリスク資産であるビットコインが入るとこんな感じになるんですね。この場合の最適ポートフォリオは次のとおりです。
index | smbc_astock | hifumi | topix | nikkei | smbc_jcredit | vti | vt | vwo | bc | ratio | return | risk |
---|---|---|---|---|---|---|---|---|---|---|---|---|
3856 | 2 | 43 | 0 | 1 | 29 | 0 | 2 | 20 | 2 | 87.9018 | 73.4374 | 10.1646 |
864 | 0 | 0 | 3 | 16 | 2 | 0 | 0 | 75 | 5 | 85.8413 | 94.9392 | 13.4562 |
2906 | 36 | 23 | 2 | 0 | 17 | 2 | 6 | 12 | 2 | 84.7379 | 68.2586 | 9.80057 |
3413 | 4 | 21 | 0 | 17 | 0 | 12 | 1 | 42 | 3 | 84.1403 | 90.7351 | 13.1203 |
2903 | 45 | 17 | 1 | 3 | 7 | 4 | 2 | 19 | 2 | 83.465 | 73.1678 | 10.6656 |
2375 | 52 | 21 | 0 | 3 | 5 | 2 | 0 | 14 | 2 | 83.228 | 75.7125 | 11.068 |
2958 | 4 | 52 | 8 | 0 | 8 | 0 | 10 | 15 | 2 | 82.6065 | 90.1786 | 13.2819 |
3859 | 6 | 15 | 0 | 1 | 1 | 15 | 14 | 45 | 3 | 82.3415 | 89.5655 | 13.2341 |
2399 | 7 | 0 | 2 | 1 | 6 | 0 | 0 | 79 | 5 | 81.6456 | 97.8794 | 14.5858 |
先程までのポートフォリオとは随分異なります。興味深いのはVWOの割合が高くなった点です。ビットコインも新興国株も投機的なところがありますので、この2つを持つことでリスクヘッジすることが出来るんでしょうか笑。仮想通貨界隈では、本格的に市場に出回る前のものをバラバラに買って、†神に祈る†といった投資方法があるようですが、このポートフォリオでもごく少量だけ保持することが示唆された点でも、それは結構合理的なのかもしれません。
とはいえ、仮想通貨での収入は雑収入扱いになるので、最大で50%の所得税がかかります。その点を考慮すると、また違った結果になりそうで面白いですね*3。
履歴
- 2018-02-02: 公開
- 2018-02-02: 信託報酬についてよくある勘違いをしていたのでコード、結果共に修正