LoginSignup
6
4

More than 3 years have passed since last update.

新型コロナウイルスの影響で株式が大暴落したので、ぼくの投資信託の実績をPythonで可視化してみた。

Last updated at Posted at 2020-03-08

2020年3月現在、 新型コロナウイルスの世界的拡大で株価が急落しています。

せっかくなのでコツコツと積み立てている投資信託の成績をPythonで可視化してみて、どれくらいのインパクトがあったのか調べてみました。私の投資信託の成績をぜひご覧あれ。

データ収集

楽天証券で投資信託を購入しています。
トップメニューから 口座管理 > 投資信託 > 投信あしあと へと進むと、全解約済みの銘柄を含めた投信全体の推移を表示することができます。
推移だけならこの画面で十分に確認できるのですが、このあとの分析のために生データを取得します。
スクリーンショット 2020-03-05 22.04.33.png

画面上で右クリックしてページのソースを表示します。
すると下の方で(1649行目以降から)それらしき配列 data1,data2,data3,data4 があるので、これをコピっておきます。

スクリーンショット 2020-03-05 22.10.39.png

デベロッパーツールからも確認できますので、お好きな方法で取得してください。

Google Colaboratoryでデータの確認

Google Colaboratoryを起動して、とりあえず二次元配列そのままで扱ってみます。

# 実際はもっと長いです。
data1 = [[1520175600000,0.000000],[1520262000000,-2.000000],[1520348400000,-1.000000]]
data2 = [[1520175600000,3758.000000],[1520262000000,3756.000000],[1520348400000,3757.000000]]
data3 = [[1520175600000,3758.000000],[1520262000000,3758.000000],[1520348400000,3758.000000]]
data4 = [[1520175600000,0.000000],[1520262000000,0.000000],[1520348400000,0.000000]]

長さはすべて488でした。(人によって異なります。)

len(data1), len(data2), len(data3), len(data4)
# => (488, 488, 488, 488)

PandasのDataframeにします。
もともとJavaScriptの二次元配列でしたが、Pythonでも配列の形が同じなのでコピペしたものをそのまま Pandas で読み込めます。ここからはdata1を抜粋して表示します。

import pandas as pd

df1 = pd.DataFrame(data1)
df1
0 1
0 1520175600000 0.0
1 1520262000000 -2.0
2 1520348400000 -1.0
3 1520434800000 -2.0
4 1520521200000 -2.0
... ... ...

1列目はタイムスタンプっぽいが小数点がないようです。桁を揃えるために1000で割り、インデックスにしたあとにDatetimeに変換します。

df1[0] = df1[0] // 1000
df1 = df1.set_index([0])
df1.index = pd.to_datetime(df1.index, unit='s')
df1
1
0
2018-03-04 15:00:00 0.0
2018-03-05 15:00:00 -2.0
2018-03-06 15:00:00 -1.0
2018-03-07 15:00:00 -2.0
2018-03-08 15:00:00 -2.0
... ...
2020-02-26 15:00:00 59802.0
2020-02-27 15:00:00 19223.0
2020-03-01 15:00:00 -245.0
2020-03-02 15:00:00 37929.0
2020-03-03 15:00:00 9971.0

直近2年分のデータが表示されました。
全解約済みの銘柄を含めた投信全体の推移 と説明があるので取得できる期間は人によって異なるかもしれません。

とりあえず、このdata1を matplotlib で可視化してみます。

import matplotlib.pyplot as plt
df1.plot()
plt.show()

結果がこちら。
total_return.png

楽天証券の 投信あしあと の画面と比較してみると、どうやらdata1はトータルリターンであることがわかります。
2019年1月頃から積み上げてきた利益約14万円ほどがコロナウイルスによる影響で一瞬にしてなくなっています。

残りのdata2 ~ 3でも同様にプロットしてみます。
datas.png

どうやら楽天証券のページのソースから得られたdata1~4はそれぞれ順番に、下記の項目と対応しているようです。

  • トータルリターン
  • 評価額
  • 実質投資額
  • 受取分配金

以後、扱いやすいように1つのDataframeにまとめ、カラム名を内容に合わせるように変更しました。(分配金のある投信を購入していないので今回は除外)
また、ほとんど休眠期間だった始めの半年ほどは除外しました。

return value invest
time
2018-11-01 15:00:00 -1.0 1629.0 1630.0
2018-11-04 15:00:00 -1.0 0.0 1.0
2018-11-05 15:00:00 0.0 20000.0 20000.0
2018-11-06 15:00:00 67.0 20067.0 20000.0
2018-11-07 15:00:00 439.0 20439.0 20000.0
... ... ... ...

Plotlyでそれっぽい可視化をする。

個人的にPlotlyの色合いなど雰囲気が好きなので、Plotlyで可視化をしてみます。

pip install plotly

まずは楽天証券の画面を再現してみます。

トータルリターン

import plotly.graph_objects as go

data = [
    go.Scatter(x=df.index, y=df['return'], name='トータルリターン'),
]

layout = go.Layout(
    width=800,
    height=450,
    xaxis = dict(
        title='時間',
        type='date',
        tickformat="%Y-%m",
        dtick='M1',
        tickangle=90,
        showgrid=False
    ),  
    yaxis = dict(
        title = '金額'
    ),
    showlegend=True,
    legend=dict(
        x=0.025,
        y=0.95,
        font=dict(
            size=15
        ),
        borderwidth=2
    ),
)

fig = go.Figure(
    data=data,
    layout=layout
)
fig.show()

スクリーンショット 2020-03-07 16.30.34.png

評価額と実質投資額

コードはほとんど同じなので折りたたみ(クリックして展開)
data = [
    go.Scatter(x=df.index, y=df['value'], name='評価額'),
    go.Scatter(x=df.index, y=df['invest'], name='投資額')
]

layout = go.Layout(
    width=800,
    height=450,
    xaxis = dict(
        title='時間',
        type='date',
        tickformat="%Y-%m",
        dtick='M1',
        tickangle=90,
        showgrid=False
    ),  
    yaxis = dict(
        title = '金額'
    ),
    legend=dict(
        x=0.025,
        y=0.95,
        font=dict(
            size=15
        ),
        borderwidth=2
    ),
)

fig = go.Figure(
    data=data,
    layout=layout
)
fig.show()

スクリーンショット 2020-03-07 16.05.42.png

リターンの前営業日差・変化率

df_diff = df.diff()

コードはほとんど同じなので折りたたみ(クリックして展開)
data = [
    go.Scatter(x=df_diff.index, y=df_diff['return'], name='リターン 前営業日差', line = dict(color='purple'))
]

layout = go.Layout(
    width=800,
    height=450,
    xaxis = dict(
        title='時間',
        type='date',
        tickformat="%Y-%m",
        dtick='M1',
        tickangle=90,
        showgrid=False
    ),  
    yaxis = dict(
        title = '金額',
    ),
    showlegend=True,
    legend=dict(
        x=0.025,
        y=0.95,
        font=dict(
            size=15
        ),
        borderwidth=2
    ),
)

fig = go.Figure(
    data=data,
    layout=layout
)
fig.show()

スクリーンショット 2020-03-07 17.16.08.png

当たり前ですが、絶対値で比べると投資額が大きくなるにつれて、リターンの変化も大きくなるので、その時の評価額に対する割合で見てみます。

df['return_change_ratio'] = df['return'].diff() / df['value']
df

コードはほとんど同じなので折りたたみ(クリックして展開)
data = [
    go.Scatter(x=df.index, y=df['return_change_ratio'], name='リターン 前営業日変化率', line = dict(color='purple'))
]

layout = go.Layout(
    width=800,
    height=450,
    xaxis = dict(
        title='時間',
        type='date',
        tickformat="%Y-%m",
        dtick='M1',
        tickangle=90,
        showgrid=False
    ),  
    yaxis = dict(
        title = '変化率',
        tickformat='%'
    ),
    showlegend=True,
    legend=dict(
        x=0.025,
        y=0.05,
        font=dict(
            size=15
        ),
        borderwidth=2
    ),
)

fig = go.Figure(
    data=data,
    layout=layout
)
fig.show()

スクリーンショット 2020-03-08 12.04.52.png

こう見ると、定期的に 1日で資産が±5%ほど変動する日 がやってきていることがわかりますね。

下落割合が大きい順にデータを並べてみました。

df.sort_values('return_change_ratio', ascending=True).head(10)
return_change_ratio
time
2020-02-24 15:00:00 -0.055890
2020-02-27 15:00:00 -0.051792
2018-12-24 15:00:00 -0.049762
2020-03-05 15:00:00 -0.043918
2019-08-25 15:00:00 -0.039766
2019-01-03 15:00:00 -0.037813
2020-02-25 15:00:00 -0.035507
2018-12-04 15:00:00 -0.034236
2020-03-03 15:00:00 -0.033716
2019-08-14 15:00:00 -0.031938

上から1, 2, 4, 7, 9番目がコロナショックによるものと思われ、10日中5日と半数を占める結果となりました。

反対に上昇幅の大きい順に並べてみました。

df.sort_values('return_change_ratio', ascending=False).head(10)
return_change_ratio
time
2020-03-02 15:00:00 0.044621
2018-12-26 15:00:00 0.040878
2020-03-04 15:00:00 0.038827
2019-01-06 15:00:00 0.026219
2019-08-13 15:00:00 0.022233
2020-02-04 15:00:00 0.021108
2019-11-04 15:00:00 0.019685
2019-06-04 15:00:00 0.018888
2018-11-07 15:00:00 0.018200
2019-09-05 15:00:00 0.017959

1, 3番目がコロナショックの影響と思われます。1日の上昇割合についても私の投資歴の中で最高を更新したようです。

変化割合ヒストグラム

data = [go.Histogram(x=df['return_change_ratio'], xbins=dict(size=0.001))]

layout = go.Layout(
    width=800,
    height=450,
    xaxis = dict(
        title="変化割合", 
        dtick=0.01,
        range=[-0.06, 0.06],
        tickformat='%'
    ),
    yaxis = dict(
        title="度数"
    )
)

fig = go.Figure(
    data=data,
    layout=layout
)
fig.show()

スクリーンショット 2020-03-08 12.34.47.png

右裾にある3個の度数1の外れ値のうち2つ、左裾にある10個ほど続く度数1のうち1, 2, 4, 7, 9番目はコロナショックのものです。

月毎のリターン

OHLCで月毎にリサンプルし、OpenとCloseの差分を取って月間のリターンの変化を調べてみました。(もっと単純なやりかたがある?)

df_month = df['return'].resample('M').ohlc()
df_month['diff'] = df_month['close'] - df_month['open']
df_month

コードはほとんど同じなので折りたたみ(クリックして展開)

スクリーンショット 2020-03-07 23.18.06.png

data = [
    go.Bar(x=df_month.index.strftime('%Y-%m'), y=df_month['diff'], name='月間のリターン', marker_color ='orange')
]

layout = go.Layout(
    width=800,
    height=450,
    xaxis = dict(
        title='時間',
        tickformat="%Y-%m",
        dtick='M1',
        tickangle=90,
        showgrid=False
    ),  
    yaxis = dict(
        title = '金額',
    ),
    showlegend=True,
    legend=dict(
        x=0.025,
        y=0.05,
        font=dict(
            size=15
        ),
        borderwidth=2
    ),
)

fig = go.Figure(
    data=data,
    layout=layout
)
fig.show()

Pandasでリサンプルすると、期間内の最終日付がインデックスになってしまいプロットのラベルとずれが出てしまったので、文字列にしています。

スクリーンショット 2020-03-07 23.29.31.png

月毎の運用利回り

毎日の積み立てに加え、不定期に買い増しや売却をしているので、厳密に月の利回りを計算しようとするとその月の何日にいくら購入・売却をしたかを考慮して計算しないといけないのですが、ここでは簡易的にその月の投資額の平均値を基準にリターンの割合を計算しました。

とは言っても、ほとんどが固定額の積み立て投資ですので、今回の簡易的な結果とあまり大きな違いはないはずです。(今回準備したデータだけで厳密に計算することは可能です。)

import numpy as np

df_month = df.resample('M').agg({'return' : lambda x: x[-1] - x[0], 'invest': np.mean})
df_month['interest'] = df_month['return'] / df_month['invest']
df_month
return invest interest
time
2018-11-30 -113.0 25081.550000 -0.004505
2018-12-31 -4160.0 39805.473684 -0.104508
2019-01-31 6285.0 85135.350000 0.073824
2019-02-28 8414.0 174170.210526 0.048309
2019-03-31 1161.0 238031.650000 0.004878
2019-04-30 5696.0 243301.684211 0.023411
2019-05-31 -16022.0 284249.052632 -0.056366
2019-06-30 20011.0 320820.857143 0.062374
2019-07-31 3929.0 352307.818182 0.011152
2019-08-31 -4843.0 399468.000000 -0.012124
2019-09-30 17063.0 460678.400000 0.037039
2019-10-31 19022.0 517172.190476 0.036781
2019-11-30 20319.0 571007.684211 0.035584
2019-12-31 21238.0 645114.238095 0.032921
2020-01-31 15858.0 688004.947368 0.023049
2020-02-29 -56921.0 732071.222222 -0.077753
2020-03-31 7343.0 817248.000000 0.008985

(OHLCを使わなくてもagg関数使えば月間リターンの差が計算できました。)

コードはほとんど同じなので折りたたみ(クリックして展開)
data = [
    go.Bar(x=df_month.index.strftime('%Y-%m'), y=df_month['interest'], name='月間の利回り', marker_color ='pink')
]

layout = go.Layout(
    width=800,
    height=450,
    xaxis = dict(
        title='時間',
        tickformat="%Y-%m",
        dtick='M1',
        tickangle=90,
        showgrid=False
    ),  
    yaxis = dict(
        title='利回り',
        range = [-0.15, 0.15],
        tickformat='%'
    ),
    showlegend=True,
    legend=dict(
        x=0.025,
        y=0.95,
        font=dict(
            size=15
        ),
        borderwidth=2
    ),
)

fig = go.Figure(
    data=data,
    layout=layout
)
fig.show()

スクリーンショット 2020-03-08 22.18.04.png

コロナウイルスが世界的に流行しはじめた2020年2月の運用利回りは 約-7.78% ほどでした。
まだまだ収束の兆しが見えず、経済への影響はこれから本格化してくると思われるので、今後の推移に注目です。

最悪の単月運用利回りは2018年12月の -10.45% でした。そういえば年末に暴落がありましたね。

おわり

場当たり的に可視化したのでひょっとすると見当違いの見方や間違いをしているかもしれません。その際はこそっとコメントをいただければと思います。

また、このデータがあるのならこういう分析が面白そうなど、ご意見やアドバイスありましたらぜひご提案ください。よろしくお願いします。最後までご覧いただきありがとうございました。

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4