見出し画像

ドルコスト平均法と一括投資をPythonでシミュレーションしてみる

ドルコスト平均法と一括投資を比べてみたくなった。
一番単純なのは、リターンを一定にして比べてみる方法。
これは簡単で、一括投資は等比数列の公式どおりだ。
$${y = ar^{n-1}}$$
ドルコスト平均法にしたとて、等比級数の和になるだけだ。高校の数学の知識だけでも計算が出来て、
$${y=\frac{a(1-r^n)}{(1-r)}}$$

ところが、こんな比較に意味はない。
だって、リターンが一定ならば、一定でなくても常にプラスなら、増え続けることは自明もいいところで、直感だけで一括投資のほうが勝ってる。
知りたいのは、リターンが定数ではなく分布する値で、リターンがマイナスの期間もあるなかで、一括投資とドルコスト平均法のどちらが有利か
だろう。
リターンが毎月(毎日でもいい)変わる、つまり株価なり投信の基準価格が変動するとなると、かなり複雑。リターンがどういう分布に従っているのかから、運用後の分布を統計学的に推定もできなくはない気はするけど、男らしく1000回くらい乱数シミュレーションをしてみて、百聞は一見に如かずをやろうかなと。


前提条件

一括投資と毎月投資を比べるため、リターンは月次リターンということでシミュレーションをする。
難しいのが、リターンをどう考えるか。
スクレイピングしたっていいんだけども、それでシミュレーションを回すのは難しい。
2000年から2010年までTOPIXに毎月投資した場合と、2001年から2011年まで、2002年から2022年まで、2003年から2024年まで投資した場合それぞれを比べても、それはn=4ではない。
じゃあ、1900年から1910年まで、1910年から1920年まで、というように10年刻みでデータを取ってこれたとして、2020年まで12回のシミュレーションができたことになるかというと、それだって違う。
株価の変動は、歴史上1回限りの現象なのだ。切り出してきてもサンプルサイズが増えるというわけでもない。
そういうわけで、シミュレーションには乱数を生成させることにする。
個々の個別株の価格変動の振舞いから、リターンは正規分布するとしていいっぽい。
株式の歴史的なリターンは、年間7%くらいというので、月間リターンが+0.7%にしてみた。標準偏差、投資の世界だとボラティリティというらしいので以後ボラティリティと呼ばせてもらうが、ボラティリティはVIX指数などを参考に0.18くらいにしてみる。
(後になってわかったが、この標準偏差は個々の銘柄の平均なので、指数の標準偏差ではない。そりゃそうだ。)
投資期間は10年、120か月として話を進める。

乱数シミュレーションの準備

一括投資と月次投資

Pythonを使ってnumpyを呼び出し、乱数生成を試みる。
Rのほうがと思うところはあったけど、処理がややこしくなったらRだと遅いかもと。
1か月目から120か月目まで、横方向に乱数を並べる。
この乱数が月次リターンをシミュレーションしてることと考える。
作った乱数、全部の要素を乗算すれば、120か月目のリターンとなる。

import numpy as np

# 120回の正規分布を生成し、最初の値を1.0にして121列にする
simulated_returns = np.random.normal(loc=1.007, scale=0.18, size=120)

まずは、考えやすい一括投資の場合を考える。
この配列の全ての要素を掛け合わせれば(乗算すれば)、0か月目に投資した元本の最終月に何倍になったかを計算してることになる。
入れなくてもいいんだけど、0か月目に1を投入してることとみなして、後に述べるドルコスト平均法と考え方を寄せるために、1をinsertしておくことにする。
次に、ドルコスト平均法の場合を考える。
要するに、上と同じ乱数の数列を使って、0か月目に1、1か月目から1、2か月目から1、・・・・120か月目に1という感じで、毎月1ずつ投資したとして、やはり要素を全て乗算。トータル120を入れてるので、それで割ればいい。で、考えたコードはこんな。

# 一括投資のリターン
simulated_returns = np.random.normal(loc=1.007, scale=0.18, sizze=120)
simulated_returns_with_initial_lump_sum = np.insert(simulated_returns, 0, 1.0)    
cumulative_return_lump_sum = np.cumprod(simulated_returns_with_initial_lump_sum)
final_returns_lump_sum.append(cumulative_return_lump_sum[-1] * 120/120)
# 月次投資のリターン
simulated_returns_with_initial = np.insert(simulated_returns, 0, 1.0)
investment_matrix = np.array([[1.0 if j < i else simulated_returns_with_initial[j] for j in range(121)] for i in range(1, 121)])
cumulative_returns = np.cumprod(investment_matrix, axis=1)
final_returns_monthly.append(cumulative_returns[:, -1].sum()/120)

cumprodを使ったのは、最終的なリターンだけでなく、そこまでの推移も確認できるように、計算だけはやらせておく作戦(しかし、面倒くさくなってやめた)。
一括投資は0か月目に1を投資して何倍になるか。(月次投資と考え方を合わせるため、リターンに120を掛けて120の元本で割ってる。意味はない。)
月次投資は0か月目から毎月1を投資し続けて、投資総額の120が何倍になったか。


年初投資もいるよね?

と、ここまできて、比べるべきはドルコスト平均法と一括投資で本当にいいのか?と根底をゆるがす疑問。
だって、新NISAで一括投資かドルコストのどっちがいいとか言ってる人たちって、新NISA枠1800万円を一括投資したいわけでもないでしょ?制度上できないのもあるけど。
年初に120万円投資するのと、毎月10万円ずつ年間に120万円投資するとでは、10年でどれだけ違うのか?
じゃないんですかね、知りたいのは。そんなわけで、年初投資もシミュレーションに加える準備をしてみる。

rows_to_select = [i for i in range(120) if (i + 1) % 12 == 1]
modified_investment_matrix = investment_matrix[rows_to_select]
cumulative_returns_modified = np.cumprod(modified_investment_matrix, axis=1)
final_returns_yearly.append((cumulative_returns_modified[:, -1] * 12).sum()/120)

月次投資で作った配列investment_matrixを、12行おきに抜き出した。
投資総額が1/12になってしまうので、後で掛けて補正。
12ずつ0か月目、12か月目、24か月目、36か月目、・・・という感じで12か月おきで12ずつ、最終的に120を投資しして何倍になるかを意味してる。

いよいよ可視化

いよいよ可視化してみる。
Forループで何度も(←アホ)
せっかくNumpy使ってるんだから内包表記でテンソル作れって?
うるせえ、内包表記を入れ子に書く脳のリソース使うなら、ループさせちまうわ。たかだか1000回やそこらの小シミュレーション余裕なはず。

import numpy as np
import matplotlib.pyplot as plt

# リターンのデータを再生成
final_returns_monthly = []
final_returns_yearly = []
final_returns_lump_sum = []

# 1000回の試行を行いデータを生成
for _ in range(1000):
    simulated_returns = np.random.normal(loc=1.007, scale=0.18, size=120)
    
    # 月次投資のリターン
    simulated_returns_with_initial = np.insert(simulated_returns, 0, 1.0)
    investment_matrix = np.array([[1.0 if j < i else simulated_returns_with_initial[j] for j in range(121)] for i in range(1, 121)])
    cumulative_returns = np.cumprod(investment_matrix, axis=1)
    final_returns_monthly.append(cumulative_returns[:, -1].sum()/120)

    # 年初投資のリターン
    rows_to_select = [i for i in range(120) if (i + 1) % 12 == 1]
    modified_investment_matrix = investment_matrix[rows_to_select]
    cumulative_returns_modified = np.cumprod(modified_investment_matrix, axis=1)
    final_returns_yearly.append((cumulative_returns_modified[:, -1] * 12).sum()/120)

    # 一括投資のリターン
    simulated_returns_with_initial_lump_sum = np.insert(simulated_returns, 0, 1.0)
    cumulative_return_lump_sum = np.cumprod(simulated_returns_with_initial_lump_sum)
    final_returns_lump_sum.append(cumulative_return_lump_sum[-1] * 120/120)

# 共通のビンを対数スケールで定義
log_bins = np.logspace(-4, 3, 40)
linear_bins = np.linspace(0, 2, 40)

# プロット作成
fig, axes = plt.subplots(1, 2, figsize=(14, 6))

# 対数スケールのヒストグラム
axes[0].hist(final_returns_monthly, bins=log_bins, alpha=0.5, label="Monthly Investment", edgecolor='black', density=False)
axes[0].hist(final_returns_yearly, bins=log_bins, alpha=0.5, label="Yearly Investment", edgecolor='black', density=False)
axes[0].hist(final_returns_lump_sum, bins=log_bins, alpha=0.5, label="Lump Sum Investment", edgecolor='black', density=False)
axes[0].set_xscale('log')  # x軸を対数スケールに変更
axes[0].set_title("Comparison of Investment Strategies with Log Scale")
axes[0].set_xlabel("Final Return")
axes[0].set_ylabel("Frequency")
axes[0].legend()

# 真数スケールのヒストグラム
axes[1].hist(final_returns_monthly, bins=linear_bins, alpha=0.5, label="Monthly Investment", edgecolor='black', density=False)
axes[1].hist(final_returns_yearly, bins=linear_bins, alpha=0.5, label="Yearly Investment", edgecolor='black', density=False)
axes[1].hist(final_returns_lump_sum, bins=linear_bins, alpha=0.5, label="Lump Sum Investment", edgecolor='black', density=False)
axes[1].set_title("Comparison of Investment Strategies with Linear Scale")
axes[1].set_xlabel("Final Return")
axes[1].set_ylabel("Frequency")
axes[1].legend()

plt.tight_layout()
plt.show()
左:横軸が対数のヒストグラム
右:横軸が真数のヒストグラム

あれあれ、一括投資のほうがばらつきが大きくなるのは想像してたけどさぁ、元本割れの確率高過ぎない?一括投資だとほとんど元本割れしてるようなシミュレーション結果。
各条件のリターンの平均や中央値、標準偏差を出力させてみる。

print("Monthly Investment mean:",np.array(final_returns_monthly).mean())
print("Monthly Investment percentile50:",np.percentile(final_returns_monthly,50))
print("Monthly Investment std:",np.array(final_returns_monthly).std(),"\n")
print("Yearly Investment mean:",np.array(final_returns_yearly).mean())
print("Yearly Investment percentile50:",np.percentile(final_returns_yearly,50))
print("Yearly Investment std:",np.array(final_returns_yearly).std(),"\n")

print("Lump Sum Investment mean:",np.array(final_returns_lump_sum).mean())
print("Lump Sum Investment percentile50:",np.percentile(final_returns_lump_sum,50))
print("Lump Sum Investment std:",np.array(final_returns_lump_sum).std())


えーと、月次リターン0.7%(≒年次リターン8.4%)でシミュレーションしたけど、ボラティリティが18%だと、いずれの投資パターンでも半数以上は元本割れ。とはいえ、平均は元本以上になるってことは、たまに大当たりを引くから、それが平均リターンを引き上げるっていう結果か。
本題に戻って、
平均リターンは
一括投資>年初投資>月次投資
リターンの中央値は
月次投資>年次投資>一括投資

想像通りではあるけど、思った以上に月次投資と年初投資は違いがない。

時間分散なんかより、大事なのはボラティリティのほう

理屈から考えて、株式投資のリターンがマイナスだったら、だれも株式投資なんかしないわけで、マイナスの年があったとしても、それでも市場全体の平均リターンはプラスでないとおかしいんですよ。
なんで元本割れになったかというと、ボラティリティが大きく設定し過ぎたからなんですよね。
VIX指数を参考に設定したらからなんですけど、VIXの数値、これは市場全体でのボラティリティじゃなくて、個々の銘柄のボラティリティの平均
じゃあ、個々のボラティリティから市場全体のボラティリティに簡単に変換できるかというとですね、できそうでできない。
理論的には、正規分布に従う標本がn個あったとして、n個平均の標準偏差は、
$${\frac{1}{\sqrt{n}}}$$
になるとなるんだけど、株式ではそうならんだろと。
個々の銘柄は時価総額も違うし、環境も違うし。
なにより、無限に組み入れ銘柄を増やせば、ボラティリティがゼロになるかといったら、そんなわっきゃない。もちろん、個別株に比べれば指数のほうがボラティリティはグッと下がるけれども。
そんなわけで、実測をググってみると、月次のボラティリティは5%くらいっぽい。
というわけで、

simulated_returns = np.random.normal(loc=1.007, scale=0.05, size=120)

で再シミュレーション

左:横軸が対数のヒストグラム右:横軸が真数のヒストグラム



ずいぶん、元本割れのリスクは減ったなという印象。
それはとにかくとして、
平均リターンは
一括投資>年初投資>月次投資
リターンの中央値は
月次投資>年次投資>一括投資
という結果は変わらず。

そして、やっぱり月次投資と年初投資くらいじゃ大した違いがない。
一括投資と比べれば、だいぶ違うけど、
10年分一括で投資できるほど遊んでるお金がある人ってそもそもいないと思うんですよね
退職金とか遺産とかそういう臨時収入があった直後ならとにかく、なにかしら、土地やら投資信託やらで運用したり、あるいは事業資金にしてるから、普通そうそう余っちゃいないわけで。
で、選択肢である年初投資と月次投資になるんだろうけど、その程度の時間分散、さっきも言ったように、結論を言えば大差ない。

やっぱり、個別株投資はリスク高いなという話

結局、時間分散より、銘柄のボラティリティなわけで。
個別株やるなら、このシミュレーションだと半数以上は10年で元本割れするわけですよ。
もちろん、平均リターンはプラスになる可能性が高いけども、イメージでいうと、10銘柄中9銘柄は元本を割って、一つだけが大当たりするからペイするという感じ。
数銘柄だけだと、元本割るだけお終いな可能性が高いわけで。
組み入れ銘柄を増やすことで株式の平均リターンを確保しつつ、ボラティリティを抑える、そうすると勝ち目がグッとあがる、それがインデックス投資のキモなんだなと。


この記事が気に入ったらサポートをしてみませんか?