Pythonで一括投資とドル・コスト平均法を比較してみた

## 背景
長期投資では、高値掴みを避けるために、購入時期を分散させる「ドル・コスト平均法」があります。これは、定期的に一定額を買い続ける手法です。

この手法に対しては、ドル・コスト平均法はゆっくり同じリスクをとっているだけで意味がないという反論もあります。確かにリスクは下がるが、これはお金をしばらく投資しないことによるリスク低下であり、機会損失を発生させてしまっています。株式のリターンが正規分布に従い、期待リターンがプラスであるならば、一括投資すべきだという考え方です。

そこで、ドル・コスト平均法と一括投資でどのような違いが出てくるかをシミュレーションしてみます。

## データ

以前の記事の分析で得られたパラメータを用いてシミュレーションを行います。


* 上場インデックスファンドTOPIX(1308)の2011/7/22～2021/7/21のデータについて分析
* 全データの5%を異常、95%を正常と仮定し、OneClassSVMで正常状態と異常状態に分けることで、それぞれの状態内では株価のリターン（前日比率）が正規分布していることが確認できた
* 正常状態では、株価の前日比率は以下
    * 平均：1.0005478168389164
    * 標準偏差：0.01024285501985374
* 異常状態では、株価の前日比率は以下
    * 平均：0.9971137713072488
    * 標準偏差：0.029152921438795287
* 全データの5%が異常状態

https://qiita.com/Mukai1/items/4d06436d0495cca2dd12

## シミュレーションの為の関数を定義

```python
import pandas as pd
from tqdm import tqdm
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib

# パラメータを設定
loc1 = 1.0005478168389164
scale1 = 0.01024285501985374
loc2 = 0.9971137713072488
scale2 = 0.029152921438795287

# 関数を定義
def make_return():
    if np.random.rand() < 0.05:
        a = np.random.normal(loc2, scale2)
        svm_result = -1
    else:
        a = np.random.normal(loc1, scale1)
        svm_result = 1
    return a, svm_result
```

## 5年分のシミュレーションを100回実行

5年間のシミュレーションを100回行い、df_sim1に格納します。これが、毎日の終値の前日比率のシミュレーション結果です。

```python
# 5年間のシミュレーションデータを100個作成
df_sim1 = pd.DataFrame()

from tqdm import tqdm
for j in tqdm(range(100)):
    return_list = []
    # 1年間を240日とし、5年分を計算
    for i in range(240 * 5):
        a,svm_result = make_return()
        return_list.append(a)
    return_list = np.array(return_list)
    df_sim1[j] = return_list
df_sim1
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1951451/d4656934-0ebb-d964-5162-f0c8243ea211.png)

## 一括投資した場合

初日に100万円を投資した結果をdf_onetimeに格納します。No.3のシミュレーション結果では、5年後に84万になっているが、No.2のシミュレーション結果では5年後に447万円になっていることが分かります。このようなシミュレーションが100回行われています。

```python
df_onetime = df_sim1.copy()

# 一括投資として、初日に100万円を投資
df_onetime['一括投資'] = 0
df_onetime.loc[0, '一括投資'] = 100

# 一括投資結果という列に、投資した結果を格納
for j in tqdm(df_onetime.columns[:-1]):
    df_onetime['一括投資結果'] = df_onetime['一括投資']
    for i in range(1, len(df_onetime)):
        df_onetime.loc[i, "一括投資結果"]=df_onetime.loc[i-1, "一括投資結果"] * df_onetime.loc[i, j]
    
    # 計算結果を各列に移す
    df_onetime[j] = df_onetime["一括投資結果"]
    
# 初日に100万円を一括投資した結果
df_onetime
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1951451/b3e8cb57-26c3-883e-bac4-4be16759c7cb.png)

## ドル・コスト平均法の場合

500日後まで、5日に1回1万円を投資した結果をdf_dollcostに格納します。No.3のシミュレーション結果では、5年後に83万になっているが、No.2のシミュレーション結果では5年後に359万円になっていることが分かります。一括投資の時よりも、少なくなっていることが分かります。


```python
df_dollcost=df_sim1.copy()

# 5日に一回1万円を500日後まで入金
df_dollcost["ドルコスト平均"] = 0
for i in np.arange(0,500,5):
    df_dollcost.loc[i, "ドルコスト平均"] = 1

df_dollcost.loc[0, 'ドルコスト平均結果'] = 1

#　ドルコスト平均結果列に計算結果を格納
for j in tqdm(df_dollcost.columns[:-1]):
    for i in range(1, len(df_dollcost)):
        df_dollcost.loc[i, "ドルコスト平均結果"] = df_dollcost.loc[i-1, "ドルコスト平均結果"] * \
        df_dollcost.loc[i, j] + df_dollcost.loc[i, "ドルコスト平均"]
        
    # 計算結果を各列に移す
    df_dollcost[j]=df_dollcost['ドルコスト平均結果']        

#ドル・コスト平均法の結果を確認
df_dollcost
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1951451/62f33049-5571-a1be-c084-0bbffe723aac.png)


## 結果の可視化
一括投資の結果を可視化します。最終的に100万円を下回ったグラフを赤色、上回ったグラフを緑色にしています。100万円を下回ったのは20/100回で、中央値は145万円、最大値は447万円、最小値は46万円となりました。

```python
#結果を可視化
plt.figure(figsize = (15, 4))
num = 0
for i in df_onetime.columns[:-2]:
    temp = df_onetime[i]
    if temp.iloc[-1] < 100:
        plt.plot(temp.index, temp, alpha = 0.2, c = 'r')
        num = num+1
    else:
        plt.plot(temp.index, temp, alpha = 0.2, c = 'g')
        
print("損失回数：{}/100".format(num))
print("最大値:{:.3f}".format(df_onetime.iloc[-1, :-2].max()))
print("中央値:{:.3f}".format(df_onetime.iloc[-1, :-2].median()))
print("最小値:{:.3f}".format(df_onetime.iloc[-1, :-2].min()))

plt.title('一括投資シミュレーション結果(5年間)')
plt.ylabel('資産額')
plt.xlabel('日')
plt.ylim(0, 500)
plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1951451/c4112875-c5ad-9ef1-ce3e-4a5d26144210.png)


同様に、ドル・コスト平均法の結果を可視化します。100万円を下回ったのは21/100回で、中央値は134万円、最大値は360万円、最小値は52万円となりました。

```python
#結果を可視化
plt.figure(figsize = (15, 4))
num = 0
for i in df_dollcost.columns[:-2]:
    temp = df_dollcost[i]
    if temp.iloc[-1] < 100:
        plt.plot(temp.index, temp, alpha = 0.2, c = 'r')
        num=num+1
    else:
        plt.plot(temp.index, temp, alpha = 0.2, c = 'g')
        
print("損失回数：{}/100".format(num))
print("最大値:{:.3f}".format(df_dollcost.iloc[-1, :-2].max()))
print("中央値:{:.3f}".format(df_dollcost.iloc[-1, :-2].median()))
print("最小値:{:.3f}".format(df_dollcost.iloc[-1, :-2].min()))

plt.title('ドルコスト平均法シミュレーション結果(5年間)')
plt.ylabel('資産額')
plt.xlabel('日')
plt.ylim(0, 500)
plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1951451/1355d3a0-4bef-823c-68a7-b11a6fcfebda.png)


最後に2つの手法の投資結果の分布を比較します。ドル・コスト平均法では、大きく損をする確率は若干低いようですが、一括投資のグラフの方が右によっており、良さそうに見えます。

```python
import seaborn as sns
sns.kdeplot(df_dollcost.iloc[-1,:-2], label = 'ドル・コスト平均法')
sns.kdeplot(df_onetime.iloc[-1,:-2],label = '一括投資')

plt.title('100万円投資したときの5年後の資産額')
plt.xlabel('資産額(万円)')
plt.legend()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1951451/2635af2d-baeb-3738-73a5-fddc37c25f86.png)

## まとめ
一括投資、ドル・コスト平均法ともに、100万円を投資していますが、リターンは以下のように結構異なる結果となっています。

* 一括投資をした場合（初日に100万円を投資し、5年間経過するまで待つ）
    * 100万円を下回ったのは20/100回で、中央値は145万円、最大値は447万円、最小値は46万円となった。
* ドル・コスト平均法を実施した場合（500日後まで、5日に1回1万円を投資、その後初日から5年経過するまで待つ）
    * 100万円を下回ったのは21/100回で、中央値は134万円、最大値は360万円、最小値は52万円となった。

ドル・コスト平均法を使うと、大きく損をするリスクを若干減らせそうですが、その分大きく得をする確率がかなり減ってしまっています。今投資できる余剰資金があれば、ドル・コスト平均法など考えずに、一括投資してしまった方がよさそうです。

## 他の記事


https://qiita.com/Mukai1/items/4d06436d0495cca2dd12

https://qiita.com/Mukai1/items/8b57e9ecc6b01960ef55

https://qiita.com/Mukai1/items/7c4ba49c1a53356ed8c2