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

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

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

# データ収集

楽天証券で投資信託を購入しています。
トップメニューから `口座管理 > 投資信託 > 投信あしあと` へと進むと、全解約済みの銘柄を含めた投信全体の推移を表示することができます。
推移だけならこの画面で十分に確認できるのですが、このあとの分析のために生データを取得します。
![スクリーンショット 2020-03-05 22.04.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329479/db825061-65bd-49a3-db42-8168782f9811.png)


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

![スクリーンショット 2020-03-05 22.10.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329479/e72ab947-178c-e75d-1546-41c975380df1.png)

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

# Google Colaboratoryでデータの確認

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

```python
# 実際はもっと長いです。
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でした。（人によって異なります。）

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

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

```python
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に変換します。

```python
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` で可視化してみます。

```python
import matplotlib.pyplot as plt
df1.plot()
plt.show()
```
結果がこちら。
![total_return.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329479/c9b01ac4-8abe-8d8f-93f1-51f32c495316.png)

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

残りのdata2 ~ 3でも同様にプロットしてみます。
![datas.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329479/fed152dd-d77c-c481-f131-2df0eb7bcc26.png)

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

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

以後、扱いやすいように１つの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で可視化をしてみます。

```python
pip install plotly
```
まずは楽天証券の画面を再現してみます。

## トータルリターン
```python
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329479/1fc837c8-4436-37b2-30f7-428d758c99d4.png)



## 評価額と実質投資額

<details><summary>コードはほとんど同じなので折りたたみ（クリックして展開）</summary><div>

```python
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()
```
</div></details>



![スクリーンショット 2020-03-07 16.05.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329479/4e554cb5-dc8c-d786-8bf1-c0701db6bee9.png)

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

```python
df_diff = df.diff()
```

<details><summary>コードはほとんど同じなので折りたたみ（クリックして展開）</summary><div>

```python
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()
```

</div></details>

![スクリーンショット 2020-03-07 17.16.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329479/1ee843e6-adc3-0036-2481-258fe24bf064.png)

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

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


<details><summary>コードはほとんど同じなので折りたたみ（クリックして展開）</summary><div>

```python
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()
```
</div></details>

![スクリーンショット 2020-03-08 12.04.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329479/c22e1157-5df4-126a-4ea6-d8709f27bbc4.png)

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

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

```python
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日と半数を占める結果となりました。


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

```python
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日の上昇割合についても私の投資歴の中で最高を更新したようです。

## 変化割合ヒストグラム

```python
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329479/d7c8b7d3-b9f9-b95b-f765-56f25bec9047.png)

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

## 月毎のリターン

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

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

<details><summary>コードはほとんど同じなので折りたたみ（クリックして展開）</summary><div>

![スクリーンショット 2020-03-07 23.18.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329479/c0c3d728-b69b-d002-e7cd-5707cc7dbcbb.png)


```python
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でリサンプルすると、期間内の最終日付がインデックスになってしまいプロットのラベルとずれが出てしまったので、文字列にしています。

</div></details>

![スクリーンショット 2020-03-07 23.29.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329479/fdd14756-b829-dfff-65c2-e133eb7c51db.png)

## 月毎の運用利回り

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

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

```python
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関数使えば月間リターンの差が計算できました。）

<details><summary>コードはほとんど同じなので折りたたみ（クリックして展開）</summary><div>


```python
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()
```

</div></details>

![スクリーンショット 2020-03-08 22.18.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329479/d7aca585-f168-9aec-91dc-7a2f5b0e93ef.png)

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

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

# おわり

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

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