Easy to type

個人的な勉強の記録です。データ分析、可視化などをメイントピックとしています。

Pythonによる超初歩的な金融資産解析(ついでのビットコイン)

概要

資産を増やす金融商品として、投資信託や株、債権なんかがメジャーです。初歩的なポートフォリオ理論では

  • 株などの資産がどのように変動するかは予測することが出来ない
  • 一方で経済は成長するので、全体を長期的に見たらプラスに成長する
  • なので、分散して投資することで安定的な利益を得ることが出来る

といったことが前提とされます。即ち、価格の変動が正規分布に従ってランダムウォークするとみなして、観測された変動の平均と分散を元に、どれだけリスクを取ることが出来るかを踏まえて資産の内訳を作ります。そのため様々な株式や債権を纏めた商品である投資信託は、理論的には優等生な商品と見なすことができます*1

正直な所、金融商品については、記載情報を見ても何を買うべきなのかよく分かりません。一方で、金融では時系列データが結構揃っていて弄ってみるだけでも面白そうです。今回はデータのハンドリングを通して、初歩的な金融資産のバランシングについて解析しました。

注意

  • マサカリ歓迎です。特にこの記事の筆者は金融業界について全くの素人です。橘玲氏の本を読んだ程度の知識しかありません。
  • この記事によって生じたあらゆる事象について、筆者は責任を負いません。
  • この記事は個人の見解と趣味の産物であり、投資を勧めるものでは全くありません。ほんとに。

材料

投資信託基準価格

以下のものをそれぞれのWebサイトからダウンロードしました。

つまり、国内系ファンドはモーニングスター、海外ETFはY! Financeです。国内ファンドはSBIで購入することが出来るものしかありませんから、そちらからもダウンロードできます。しかし自分の環境*2では、csvファイルなのに違う拡張子としてダウンロードされたり、ファイルフォーマットが分析に使いづらいものだったので利用をやめました。モーニングスターはWebサイトが重いという欠点はありますが、ヘッダーがすっきりしていて使いやすいです。

ファンドの選定基準としては、以下のとおりです。

為替価格

海外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日での値動き

f:id:ajhjhaf:20180203063146p:plain

左側がx軸の時系列を長めに取った図、右側が最近の時系列に注目した図です。一番上の段が収益率平均、中段が収益率分散、下段がシャープレシオです。

  • 収益率について、2008年ぐらいにリーマン・ショックがあって株価がめちゃめちゃに落ち込んでいます。しかしそこからは基本的に正のリターンを持っているので、儲かっていますね。ここ最近の収益率も右肩上がりです。

  • こういう図をプロットするまで信じていなかったのですが、たしかに債権はリーマン・ショックのときにも落ち込んでいませんし、資産担保が出来ています。まぁ、一方で全然プラスになっていませんが、安定的だということがわかります。2段目の分散も低いですね。

  • VTとSMBC 全海外は日本が含まれていないこと以外はポートフォリオが似ていますが、実際パフォーマンスも似ています。一方で、ここ最近は日本の株価が上昇傾向だったから、VTの方がパフォーマンスが良かったんですね。

  • VTIはVTよりパフォーマンスが良いと言われることもありますが、ここ最近は分散がかなり高いです。リターンも実際高いんですが、リスクの兼ね合いが大事です。

  • VWOの傾向から、ここ最近は新興国株が安定して上昇傾向だったことがわかります。リターンは高くとも、分散が低く、シャープレシオが凄いことになっていますね。

365日での値動き

f:id:ajhjhaf:20180203063209p:plain

  • ひふみプラスが凄いことになっています。そりゃー人気が出るわけです。ただし、90日のプロットの方を見ればわかりますが、ここ最近のパフォーマンスはやや低めです。

  • 90日の時には殆ど見られませんでしたが、365日だとファンドが始まった時にシャープレシオが安定しないように見えます。基本的に規模が小さすぎて、様子見状態で分散が小さいことが原因と推測します。

  • 短期の場合と異なり、VWOの分散が最も高いです。ただしこちらのほうが、多く言われている状態に一致するようです。その分最近の新興国株式の状態が良かったと推測できます。

  • 儲かっている時には資産がそちらに移動するようで、新興国株と日本債権のシャープレシオが見事に逆相関を描いています。

リスクリターン分布

f:id:ajhjhaf:20180203063240p:plain

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)

f:id:ajhjhaf:20180203063421p:plain

おまけ

ビットコインについての価格変動も、このスキームで調べてみましょう。今流行っているアレです。

ビットコイン価格

Yahoo! Financeからダウンロードしました。この価格はBTC-USDであることに注意してください。国内の投資信託と合わせるために、為替価格で補正する必要があります。

結果

ビットコインは値動きが激しいことが予測されるので、X=Y=30日での統計値を見ます。

f:id:ajhjhaf:20180203063834p:plain

面白いことに、なんだかんだ言ってここ最近購入していても儲かる可能性はあったようです。ただし分散が30日間で平均してもずいぶん大きいです。なので購入するタイミング次第で物凄い高値を掴みうるリスクがあります。僕が友達と会って「最近ビットコインがアツいけど、もう手遅れだよね」と話したのが11月中頃でしたが、そういえば随分伸びたものです。しかしリスクが凄まじい。場合によっては資産が60%以上丸ごと減る可能性もあるわけです。

続いて直近の30日の履歴から求めたリスクリターン分布のプロットです。

f:id:ajhjhaf:20180203064100p:plain

随分面白い形になりました。超ハイリターンハイリスク資産であるビットコインが入るとこんな感じになるんですね。この場合の最適ポートフォリオは次のとおりです。

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

履歴

*1:ただし、野村の解説にあるように、正規分布に従うランダムウォークは既に否定されています。そもそも変動が正規分布に従うなら、左右対称になっていつまでも経済が成長しません。なので個人的にも歪み正規分布と見なすほうが自然に思えます。「初歩的」と上述したのはそういうことです。

*2:Mac + Safari

*3:計算のどこに取り入れればいいのか分からない