はじめに
日経平均株価が一時4万円を超え、新NISA制度も始まり、投資ブームが起きています。巷では新NISAで積み立てるのはSP500連動投信がいいだとか、オルカン一択だとかの話題で盛り上がっています。確かに本記事執筆時点(2024年3月)では世界的に株価は上昇傾向の強気相場の真っ只中であり、盛り上がるのは尤もなんですが、相場は永遠に上昇し続けるわけでもなく、この先大きな調整や弱気相場突入もふつうにありえる話ではあります。
特に新NISA発足を期に積立投資を始めたとか、ここ数年で積立投資を始めたとかの場合、本記事執筆時点(2024年3月)までは順調に資産を増やせたと思うのですが、大きな調整や弱気相場突入に見舞われると、資産は見る見るうちに目減りして狼狽することになるでしょう。巷では、数年以上の長期積立投資を続けていくと、たとえ調整や弱気相場を経ても確実に資産は増えていくといわれています。過去のSP500指数の実績では確かにそうなっています。だから、調整や弱気相場突入に怖気づかず、粛々と積立投資を続ければいいんだと。
しかしここで、もし将来弱気相場が来るならば、弱気相場突入時点で残高をすべて現金化し、強気相場に転換した時点で安く買い戻せばさらに資産が増やせるのでは? と欲の皮がつっぱった筆者は考えてしまいます。本記事ではそんなことが可能なのかを、SP500の積立投資を対象に検証していこうと思います。(SP500連動投信の値動きではなくSP500の値動きを使用して検証します)
日本から見ると生のSP500の値動きだけでは為替の影響が反映されていないので、本来ならば円建てのSP500連動投信の値動きを見るのが正しい方法だと思います。本記事ではSP500の長期間の値動きを観察しますので、長期間のデータが容易に入手可能な生のSP500の値動きを使用することにします。
ちなみに、筆者は金融の専門家ではなく、ただのプー太郎の爺なので、専門的に見れば誤ったことを言っている可能性があることをあらかじめご了承ください。
SP500データの入手
SP500の値動きのデータはStooqというサイトから入手できます。
pythonのapiも用意されており、以下のコードで必要な期間の値動きをcsvファイルで取得できます。
import pandas_datareader.stooq as web
from datetime import datetime
# 1789/5/1以降取得可能
start_date = datetime(1789,5,1)
end_date = datetime(2024,2,29)
dr = web.StooqDailyReader('^SPX', start=start_date, end=end_date)
df = dr.read()
df = df.sort_values('Date')
df.to_csv('SPX.csv', header=False, date_format="%Y/%m/%d")
なんと1789/5/1(江戸時代寛政元年)以降のデータが入手できます。
1789/05/01,0.51,0.51,0.51,0.51,
1789/06/01,0.51,0.51,0.51,0.51,
1789/07/01,0.5,0.5,0.5,0.5,
1789/08/01,0.5,0.51,0.5,0.51,
...
2024/02/27,5074.6,5080.69,5057.29,5078.18,2154332795.0
2024/02/28,5067.2,5077.37,5058.35,5069.76,2091422108.0
2024/02/29,5085.36,5104.99,5061.89,5096.27,3317173868.0
寛政元年にはGAFAなんて当然存在せず、当時の構成銘柄で今でも継続している銘柄なんてあるんだろうか?235年間で構成銘柄が入れ替わりながらも指数の連続性を保ちながら継続してきたってことなんですね。
銘柄の入れ替わりっていうのが重要な点で、このことにより勢いを失った銘柄や時代にそぐわない銘柄は淘汰され新たな主役に入れ替わり、指数が「価値」そのものをあらわすことになるわけです。だから、個別銘柄を保持し続けてもその銘柄の株価が勢いを失っていけば価値は毀損されてしまうけど、この指数ならばその心配が少ないということなんでしょう。
SP500指数の可視化
入手したSP500の時系列データをmplfinanceにより可視化してみます。
1970年以降のデータを使用します。何故に1970年以降なのかというと、1971年に米国で金兌換停止(ニクソンショック)があり、保有するゴールドの量に縛られずにお札を刷って金融緩和できるようになり、以降現代と同様の景気後退時の金融政策がとれるよになったためです。(参考文献:米国株S&P500の歴代弱気相場データ【1835年〜2020年版】by yuta)
以下のコードでSP500の値動きを可視化。
import mplfinance as mpf
import pandas as pd
input_path = "./SPX19700101_20240229.csv"
skips = 0
df = pd.read_csv(input_path, header=None, skiprows=skips,
names=['Date','Open','High','Low','Close','Volume'], encoding='UTF-8')
df['Date'] = pd.to_datetime(df['Date'])
df = df.set_index("Date")
mpf.plot(df, type='line', volume=False, savefig='spx_all.png')
見事な右肩上がり!対数グラフに表現すべきといってもおかしくないデータ。
SP500の値動きは右肩上がりではあるものの、ところどころで調整や景気後退の痕(弱気相場期間)が見られます。全体を通してみると、期間が長い右肩上がりの強気相場とそれよりも期間の短い弱気相場が交互に繰り返しつつ、弱気相場の終点(=次の強気相場の起点)がその1回前の強気相場の起点よりも高くなっています。そのため、全体を通して右肩上がりのグラフになっています。
弱気相場の認識
SP500の過去の値動きは概観できたので、SP500の(というかSP500連動投信の)投資戦略を検討していきたいと思います。
SP500は、強気相場と弱気相場が交互に繰り返していることは前節で述べました。
すぐ思いつくのが、
- 強気相場のできるだけ早い時期に投資資金を全額投入する
- 強気相場が続いている間はほったらかし(何もしない)
- 強気相場から弱気相場に切り替わったら全額を現金化
- 弱気相場が続いている間はほったらかし(何もしない)
- 弱気相場から強気相場に切り替わったら全額投入
を繰り返すということです。
1971年以降、SP500の弱気相場は7回あったそうです。(参考文献:S&P500の歴代弱気相場データ)
開始 | 終了 | 下落期間 | 下落率(%) |
---|---|---|---|
1973年01月 | 1974年10月 | 21ヶ月 | -48% |
1980年11月 | 1982年08月 | 20ヶ月 | -27% |
1987年08月 | 1987年12月 | 3ヶ月 | -34% |
1990年07月 | 1990年10月 | 3ヶ月 | -20% |
2000年03月 | 2002年10月 | 30ヶ月 | -49% |
2007年10月 | 2009年03月 | 17ヶ月 | -57% |
2020年02月 | 2020年04月 | 2ヶ月 | -35% |
弱気相場の定義は、「過去52週間の最高値から20%下落したとき」ということのようです。しかしどうもこれは「弱気相場の定義」というよりも「弱気相場期間に入っていると認識する方法」という意味合いのようで、何をもって弱気相場開始と定義し、何をもって弱気相場終了(=強気相場開始)とするのかはネットからは調べきれていません。「過去52週間の最高値から20%下落したとき」が弱気相場期間内であることは間違いないようですが。
少し気になる点は、「過去52週間の最高値から20%下落したとき」に弱気相場であることが初めて認識できるとするならば、弱気相場と認識できた時点で資産防衛のために持ち高を現金化したとしても、すでに高値から20%も下落している訳であまりおいしくないのでは?という点です。
ここで、筆者なりの弱気相場/強気相場の開始日/終了日の定義を試みます。
- 弱気相場の認識日=過去52週の最高値から20%下落した日
- 弱気相場の開始日=弱気相場の認識日の過去52週の最高値をつけた日
- 強気相場の認識日=過去52週の最安値から20%上昇した日
- 強気相場の開始日=強気相場の認識日の過去52週の最安値をつけた日
- 弱気相場の開始日=強気相場の終了日
- 弱気相場の終了日=強気相場の開始日
このなんちゃって定義を元に、弱気相場の期間をSP500値動きに追記してみます。
赤線で挟まれた期間が弱気相場の期間ですが、これでは見難いので強気相場とそれに続く弱気相場の期間毎に拡大してプロットします。赤い網掛けの期間が検出した弱気相場の期間で、赤い▼は弱気相場を認識した日、緑の▲は強気相場を認識した日をあらわします。(このチャートを作成したコードは本稿のおまけに掲載しています)
弱気相場:1973/01/11(120.24) から 1974/10/03(62.28)まで 48.2%下落) 認識日=1973/11/27(95.7)
強気相場:1974/10/03(62.28) から 1981/03/25(137.11)まで 120.2%上昇 認識日=1974/11/05(75.11)
弱気相場:1981/03/25(137.11) から 1982/08/12(102.42)まで 25.3%下落) 認識日=1982/03/05(109.34)
強気相場:1982/08/12(102.42) から 1987/08/25(336.77)まで 228.8%上昇 認識日=1982/09/14(123.1)
弱気相場:1987/08/25(336.77) から 1987/12/04(223.92)まで 33.5%下落) 認識日=1987/10/19(224.84)
いわゆるブラックマンデー
強気相場:1987/12/04(223.92) から 2000/03/24(1527.46)まで 582.1%上昇 認識日=1988/03/08(269.43)
弱気相場:2000/03/24(1527.46) から 2001/09/21(965.8)まで 36.8%下落) 認識日=2001/03/12(1180.16)
ITバブル崩壊
強気相場:2001/09/21(965.8) から 2002/01/04(1172.51)まで 21.4%上昇 認識日=2001/12/05(1170.35)
弱気相場:2002/01/04(1172.51) から 2002/07/23(797.7)まで 32.0%下落) 認識日=2002/07/10(920.47)
ITバブル崩壊の続き
強気相場:2002/07/23(797.7) から 2007/10/09(1565.15)まで 96.2%上昇 認識日=2002/08/22(962.7)
弱気相場:2007/10/09(1565.15) から 2008/11/20(752.44)まで 51.9%下落) 認識日=2008/07/09(1244.69)
リーマンショック
強気相場:2008/11/20(752.44) から 2009/01/06(934.7)まで 24.2%上昇 認識日=2008/12/08(909.7)
弱気相場:2009/01/06(934.7) から 2009/03/09(676.53)まで 27.6%下落) 認識日=2009/02/23(743.33)
強気相場:2009/03/09(676.53) から 2020/02/19(3386.15)まで 400.5%上昇 認識日=2009/03/23(822.92)
弱気相場:2020/02/19(3386.15) から 2020/03/23(2237.4)まで 33.9%下落) 認識日=2020/03/12(2480.64)
コロナショック
次の強気相場認識日2020/04/08(2749.98)
参考文献とは開始、終了日や下落率が微妙に異なり、起こった回数も差がありますが、弱気相場の定義も異なるので良しとしましょう。
これらのチャートを見て思うことは、弱気相場の認識は下落が相当進んでからしかできない、ということです。2020/02/19から始まった直近の弱気相場では、過去52週の最高値から20%下落し弱気相場と認識できた2020/03/12の直後に上昇相場に転換しています(上昇転換と認識できたのは反転から1ヶ月後)。
弱気相場の終了日(次の強気相場の開始日)は、その日以降最長で52週後にしかわからないという点も問題ありです。
投資戦略
巷で言われていることは、上昇相場だろうが下落相場だろうが指数連動投信を長期間毎月コツコツと積み立ていくと何倍もの利益が得られる、ということ。SP500指数のこれまでの値動きを見ると確かにそうなんだろうと思う。SP500指数が構成銘柄を指数上昇に有利なように入れ替えながら維持され続けるならば、経済成長が続く限り今後も同じ傾向で推移していくことでしょう。
だから、定石通り毎月一定額を積み立てていくという投資戦略で良いのですが、この先弱気相場に転換したときどうすべきかの作戦を練ってみます。
当初は
- 強気相場から弱気相場に切り替わったら残高全額を現金化
- 弱気相場が続いている間はほったらかし(積み立ては継続)
- 弱気相場から強気相場に切り替わったら全額買い戻し
と考えていたのですが...
そもそも弱気相場に切り替わったと認識できるのが、直近の高値から20%下落したときなんで、そこで売却しても、さらに安値で買い戻せる保証がありません。過去の事例では弱気相場を認識後も下がり続けるというわけではなく戻ったりもしてるので買い戻しのタイミングが全くわからないように思えます。
強気相場から弱気相場に切り替わりがわかった時点で残高を現金化し、弱気相場から強気相場に切り替わりがわかった時点で買い戻しても、そのときは強気相場の真っ只中な訳で先に売ったときよりも高くなっていることが多いことが過去のグラフからわかります。
結局この方法は使えませんね。
一定額の積立投資は「ドルコスト平均法」とよばれ、下がったときはより多くの口数を購入できるので、一括投資よりも有利といわれています。そこで、
- 弱気相場を認識したら積立金額を増額し
- 強気相場を認識したら積立金額をもとに戻す
という戦略を考えてみます。
1970年1月以降、SP500指数と全く同じ基準価格の投信を毎月初めに$1づつ購入し続けたとすると...
import pandas as pd
input_path = "./SPX19700101_20240229.csv"
skips = 0
df = pd.read_csv(input_path, header=None, skiprows=skips,
names=['Date','Open','High','Low','Close','Volume'], encoding='UTF-8')
df['Date'] = pd.to_datetime(df['Date'])
df = df.set_index("Date")
# 月足変換
d_ohlcv = {'Open': 'first','High': 'max','Low': 'min','Close': 'last','Volume': 'sum'}
df_m = df.resample('MS').agg(d_ohlcv)
# 1970/1以降毎月積立投資したとすると資産は積立額の何倍に増えたか
v1 = 0
for i in range(len(df_m)):
v1 = v1 + 1.0/df_m['Close'].iloc[i]
sum1 = v1 * df_m['Close'].iloc[len(df_m)-1]
n1 = len(df_m)
a1 = sum1 / n1
print('口数={}, 倍率={:.2f}'.format(v1, a1))
2024年2月時点では、
口数=2.350458211443724, 倍率=18.43
となりました。
1970/1以降弱気相場検出の翌月から強気相場検出月までのみ毎月初めに$1づつ積み立てててみると、
# 1970/1以降弱気相場検出の翌月から強気相場検出月までのみ積み立てた場合
bear_starts = ['1973/12/1','1982/4/1','1987/11/1','2001/4/1',
'2002/8/1','2008/8/1','2009/3/1','2020/4/1']
bear_ends =['1974/12/1','1982/10/1','1988/4/1','2002/1/1',
'2002/9/1','2009/1/1','2009/4/1','2020/5/1']
v2 = 0
n2 = 0
for i in range(len(bear_starts)):
idts = df_m.index.get_loc(bear_starts[i])
idte = df_m.index.get_loc(bear_ends[i])
for j in range(idts, idte+1):
v2 = v2 + 1.0/df_m['Close'].iloc[j]
n2 = n2 + 1
sum2 = v2 * df_m['Close'].iloc[len(df_m)-1]
a2 = sum2 / n2
print('口数={}, 倍率={:.2f}'.format(v2, a2))
2024年2月時点では、
口数=0.26440513200025156, 倍率=28.07
でした。
弱気相場期間に積み立てると資産は積立額の何倍に増えたかの倍率は全期間積み立てたよりも1.5倍のパフォーマンスを示しました。全期間中の弱気相場期間が占める割合は短いので、それほど資産が増えるわけではありませんけど。
次に、1970/1以降弱気相場検出月の翌月に残高を現金化し、強気相場検出月の翌月に全額買い戻す(その間も積み立ては継続)としてみると、
# 1970/1以降毎月積立投資、弱気相場検出で残高を現金化し、強気相場検出で全額買い戻す
v4 = 0
cs = 0
bear_idx = 0
idts = df_m.index.get_loc(bear_starts[bear_idx])
idte = df_m.index.get_loc(bear_ends[bear_idx])
for i in range(len(df_m)):
v4 = v4 + 1.0/df_m['Close'].iloc[i]
if i==(idts+1):
cs = v4 * df_m['Close'].iloc[i]
v4 = 0
if i==(idte+1):
v4 = v4 + cs/df_m['Close'].iloc[i]
cs = 0
bear_idx = bear_idx + 1
if bear_idx<len(bear_starts):
idts = df_m.index.get_loc(bear_starts[bear_idx])
idte = df_m.index.get_loc(bear_ends[bear_idx])
sum4 = v4 * df_m['Close'].iloc[len(df_m)-1]
n4 = len(df_m)
a4 = sum4 / n4
print('口数={}, 倍率={:.2f}'.format(v4, a4))
2024年2月時点では、
口数=3.135145134516457, 倍率=24.58
でした。
単純な定期積み立てと比較して1.33倍のパフォーマンスを得ました。ただ、その内訳を見てみると、リーマンショックの時に急激に指数が下落し、弱気を検知した後もさらに大きく下落したため、買い戻し時に大きく口数を増やすことができたことが、パフォーマンスが良かった要因です。その時以外の弱気相場では逆に口数を減らしていたときも多かった。
今後リーマンショック級の弱気相場を期待するならこの方法はいいのかもしれませんが、そんなことを期待して普通の弱気相場の度に口数を減らすのは投機的な悪手だと思います。下落が始まった時にそれがリーマンショック級だと自身で確信が持てるなら全残高売却は当然アリだと思いますが...
弱気相場期間に積立額を増やすのは良手であり、弱気相場認識時に後で買い戻すつもりで残高を現金化するのは過去の例ではリーマンショックの影響でうまくいったこともありましたが悪手のようだということがわかりました。
一定額を積み立て続ける投資手法はドルコスト平均法といわれている既知の良手ですが、下がったら買い増すことにより口数残高を増加させて目標の口数を得るバリュー平均法といわれている手法もあります。バリュー平均法では下がったら買い増すので下がり続ける場合は投資資金が膨れ上がる問題があり、ゴールの口数は達成できたとしてもその時点でも基準価格次第でドルコスト平均法と比較して必ずしもパフォーマンスが良いとは限りません。
要は必勝法は存在しない訳ですが、来るべき弱気相場突入時には、狼狽して残高を現金化するのは悪手であり、むしろ買い増すのが良手のように見えます。NISAなら積立投資枠で積み立てて、弱気相場に投入したら資金の許す範囲で成長投資枠を使ってスポット購入するようなかたちでしょう。
まとめ
SP500の値動きのデータを取得し、弱気相場期間の抽出を試みました。
この結果を元に、SP500連動投信を積立投資をする場合、弱気相場期間にどのような投資行動をとるのがよいかの検討を行いました。
その結果、弱気相場突入時に残高を現金化しその後強気相場に反転したら全額買い戻すのは悪手であることがわかりました。リーマンショック級の弱気相場の場合はうまくいく可能性がありますが、弱気相場突入時に何をもってリーマンショック級と判断するかの指標がないので慎重な行動が必要です。
弱気相場突入時に買い増すのが将来のゴール時点でのパフォーマンスがよくなることもわかりました。
おまけ
弱気相場期間の抽出に使用したコードを載せておきます。
import mplfinance as mpf
import pandas as pd
def main():
input_path = "./SPX19700101_20240229.csv"
skips = 0
df = pd.read_csv(input_path, header=None, skiprows=skips,
names=['Date','Open','High','Low','Close','Volume'], encoding='UTF-8')
df['Date'] = pd.to_datetime(df['Date'])
df = df.set_index("Date")
term = 52*5 # だいたい52週
imax52w = 0
imin52w = 0
bear = False
bear_starts = []
bear_ends = []
bear_start_signals = []
bear_end_signals = []
for i in range(term, len(df)-term):
if not bear:
# 弱気相場の認識日=過去52週の最高値から20%下落した日
istt = max(i-term, imin52w)
vmax52w = max(df['Close'][istt:i-1])
idxmax52w = df['Close'][istt:i-1].idxmax()
imax52w = df.index.get_loc(idxmax52w)
if df['Close'].iloc[i]<=vmax52w*0.8:
bear_starts.append(idxmax52w)
bear_start_signals.append(df.index[i])
bear = True
else:
# 強気相場の認識日=過去52週の最安値から20%上昇した日
istt = max(i-term, imax52w)
vmin52w = min(df['Close'][istt:i-1])
idxmin52w = df['Close'][istt:i-1].idxmin()
imin52w = df.index.get_loc(idxmin52w)
if df['Close'].iloc[i]>=vmin52w*1.2:
bear_ends.append(idxmin52w)
bear_end_signals.append(df.index[i])
bear = False
# 全期間のプロット
vlines1 = dict(vlines=bear_starts+bear_ends,colors='r',linewidths=0.1)
mpf.plot(df, type='line', volume=False, vlines=vlines1, savefig='spx_recessions.png')
# 強気相場後の弱気相場を区間として切り出してプロット
for i in range(len(bear_ends)):
if i==0: plot_start = df.index[0]
else: plot_start = df.index[df.index.get_loc(bear_ends[i-1])]
if i==len(bear_starts)-1: plot_end = df.index[len(df)-1]
else: plot_end = df.index[df.index.get_loc(bear_end_signals[i])]
df_plot = df[plot_start:plot_end]
vl = []
vcolors = []
for j in range(df.index.get_loc(bear_starts[i]), df.index.get_loc(bear_ends[i])):
vl.append(df.index[j])
vcolors.append('r')
vlines1 = dict(vlines=vl,colors=vcolors,alpha=0.1, linewidths=0.5)
adddf = pd.DataFrame()
adddf['Close'] = df['Close']
adddf.loc[df.index==bear_start_signals[i], "signal1"] = df[df.index == bear_start_signals[i]].Close
if i>0:
adddf.loc[df.index==bear_end_signals[i-1], "signal2"] = df[df.index == bear_end_signals[i-1]].Close
adddf_plot = adddf[plot_start:plot_end]
apd1 = mpf.make_addplot(adddf_plot['signal1'],color='r',
scatter=True,markersize=100,marker='v')
if i>0:
adp2 = mpf.make_addplot(adddf_plot['signal2'],color='g',
scatter=True,markersize=100,marker='^')
addplot = [apd1, adp2]
else:
addplot = [apd1]
tlines = {'tlines':[(plot_start, bear_starts[i]), (bear_starts[i], bear_ends[i]),
(bear_ends[i], bear_end_signals[i])],
'tline_use':['Close'], 'linestyle':'dotted', 'linewidths':0.5,'colors':'r'}
mpf.plot(df_plot, addplot=addplot, tlines=tlines, type='line', volume=False,
vlines=vlines1, savefig='spx_'+str(i)+'.png')
if i>0:
gain = (df['Close'][bear_starts[i]]-df['Close'][plot_start])/df['Close'][plot_start]
print('強気相場:{0}({1}) から {2}({3})まで {4:.1f}%上昇 認識日={5}({6})'.format(
plot_start.strftime('%Y/%m/%d'), df['Close'][plot_start],
bear_starts[i].strftime('%Y/%m/%d'), df['Close'][bear_starts[i]], 100.0*gain,
bear_end_signals[i-1].strftime('%Y/%m/%d'), df['Close'][bear_end_signals[i-1]]))
loss = (df['Close'][bear_starts[i]]-df['Close'][bear_ends[i]])/df['Close'][bear_starts[i]]
print('弱気相場:{0}({1}) から {2}({3})まで {4:.1f}%下落) 認識日={5}({6})\n'.format(
bear_starts[i].strftime('%Y/%m/%d'), df['Close'][bear_starts[i]],
bear_ends[i].strftime('%Y/%m/%d'), df['Close'][bear_ends[i]], 100.0*loss,
bear_start_signals[i].strftime('%Y/%m/%d'), df['Close'][bear_start_signals[i]]))
if i==(len(bear_ends)-1):
print('次の強気相場認識日{0}({1})'.format(bear_end_signals[i].strftime('%Y/%m/%d'), df['Close'][bear_end_signals[i]]))
if __name__ == '__main__':
main()
~~