見出し画像

新NISA何を買う?リスクとリターンでシミュレーション


1 初めに

 今回も引き続き、新NISA何を買う?を題材に、1番人気のオルカンと比較対象のリスクとリターンに注目します。5年間という期間での一括投資でのリスクとリターンを改めて可視化して比較するとともに、このリスクとリターンを使ってモンテカルロシミュレーションと呼ばれるシミュレーション方法で将来の値動きを予測してみます。PYTHONのプログラムの勉強だけでなく、資産形成にも役立ちますので、ぜひお付き合いください。

*コードがだんだん長くなってきてます。初心者の方はまずコピペで実行してみて、お気に入りのティッカーコード(例アップルならAAPL等)や集計期間の変更にチャレンジしてみてください。

前回の記事:比較できる時系列グラフと一括投資時の過去実績について

今回のゴール シミュレーション結果

モンテカルロシミュレーション結果:時系列グラフ
モンテカルロシミュレーション結果:箱ひげ図

2 豆知識

1)リスク、リターンについて

 投資における「リスク」と「リターン」の関係は、投資の成果を理解する上で中心的な概念です。リターンは投資によって得られる利益のことを指し、リスクはその投資で予想外の損失を受ける可能性の度合いを表します。一般的に、高いリターンを求める投資には高いリスクが伴い、リスクを避けようとすればするほど得られるリターンも低くなります。
 今回のプログラムでは、5年間の保有を前提にした投資シナリオを考え、その期間の年率リターンとリスク(1標準偏差)を計算しています。リスクは投資の結果のばらつきを示し、リターンは期間中の平均的な収益率を表します。このプログラムでは、計算された年率リターンとリスクを基に、日々のリスクとリターンに換算し、モンテカルロシミュレーションを通じて将来の価格変動のシミュレーションを行っています。

2)モンテカルロシミュレーションについて

モンテカルロシミュレーションは、確率的なプロセスを用いて未来の様々なシナリオを予測する手法です。ランダムなプロセスを用いて未来の出来事を数千回、数万回シミュレーションすることで、その結果の確率分布を導き出します。この手法は、金融分野におけるリスクとリターンの分析、特に不確実性が高く複雑な投資環境下での意思決定支援に非常に有効です。
 今回のプログラムでは、5年保有するという条件下で、年率リターンとリスクを算出し、それを日次のリスクとリターンに換算しています。この換算された日次リスクとリターンを用いて、モンテカルロシミュレーションを実施し、さまざまな市場環境下での資産価値の変動を模擬します。シミュレーションの結果は、将来の価格変動の幅広いシナリオをカバーし、投資戦略のリスク許容度や期待リターンに対する洞察を提供します。これにより、投資家はより情報に基づいた意思決定を行うことができるようになります。

3 実践

1)取得コードについて
 前回と同じく、新NISAで非常に人気のある通称オルカン(全世界株式)と比較するのは、S&P500(全米500社株式)ハイテク株NASDAQ100、安全資産の代表格:金(GOLD)ですが、今回もう1つ安定資産で資産形成に活用されている債権を代表して長期米国債も比較として追加しました。それぞれ相当するETFのティッカーは下記です。
 オルカン(全世界株式)・・・ACWI
 S &P500(全米500社)・・・SPY
 NASDAQ100(ハイテク)・・・QQQ
 金(GOLD)       ・・・GLD
 長期米国債      ・・・TLT
*オルカンは投資信託ですが、それぞれ同じ指数に連動するETFの値動きで比較します。
2)統計値の計算
 まずデータの取得から5年間保有した実績を計算します(詳細は前回のコード参照)。それで得られた5年間保有時の統計値を一式計算してみます。
各項目の意味は下記です。
 Mean   ・・・平均値(5年間保有した場合平均)
 Median  ・・・中央値(5年間保有した場合中央値)
 MultiPlier(Mean) ・・・何倍になったか平均値(例2なら2倍)
 MultiPlier(Median) ・・・何倍になったか中央値
 Annual Return ・・・年率リターン(5年間保有時の年率換算)
 Risk     ・・・リスク(=標準偏差。ばらつき度合い)
 Value ±1σ ・・・平均値±1σの価値(約66%の範囲)
 Value ±2σ ・・・平均値±2σの価値(約95%の範囲)

import yfinance as yf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# 対象のティッカーシンボル
tickers = ['ACWI','SPY', 'QQQ', 'TLT','GLD']

# データの取得
data = yf.download(tickers, start="2014-01-01", end=pd.Timestamp.now().strftime('%Y-%m-%d'))

# 必要な列のみ抽出
data = data[['Adj Close']]

# 月別にリサンプリングして、月の初めのデータを取得
monthly_data = data.resample('MS').first()

# 投資期間を月で指定
investment_period_months = 60

# 投資開始日を計算
start_dates = monthly_data.index[:-investment_period_months]

# 投資結果を格納するためのDataFrameを初期化
results = pd.DataFrame(index=start_dates, columns=tickers)

# 各ティッカーに対して計算
for ticker in tickers:
    for start_date in start_dates:
        end_date = start_date + pd.DateOffset(months=investment_period_months)
        initial_price = monthly_data['Adj Close'][ticker][start_date]
        final_price = monthly_data['Adj Close'][ticker][end_date]
        # 1000ドル投資時の株数
        shares_bought = 1000 / initial_price
        # 5年後の価値
        value_after_5_years = shares_bought * final_price
        results.loc[start_date, ticker] = value_after_5_years

# 結果を数値型に変換
results = results.astype(float)

#ここまで前回記事を参照。以後新規内容
# 統計値を格納するためのDataFrameを再初期化(倍率カラムを追加)
stats = pd.DataFrame(columns=['Mean', 'Median', 'Multiplier (Mean)', 'Multiplier (Median)', 'Annual Return', 'Risk', 'Value +/- 1σ', 'Value +/- 2σ'], index=tickers)

for ticker in tickers:
    # 平均値と中央値
    mean_value = results[ticker].mean()
    median_value = results[ticker].median()
    stats.loc[ticker, 'Mean'] = "{:,.0f}".format(mean_value)
    stats.loc[ticker, 'Median'] = "{:,.0f}".format(median_value)

    # 投資額からの倍率(平均値、中央値)
    stats.loc[ticker, 'Multiplier (Mean)'] = "{:.2f}".format(mean_value / 1000)
    stats.loc[ticker, 'Multiplier (Median)'] = "{:.2f}".format(median_value / 1000)

    # 年率リターンとリスク
    annual_returns = (results[ticker] / 1000) ** (1/5) - 1
    stats.loc[ticker, 'Annual Return'] = "{:.3f}".format(annual_returns.mean())
    stats.loc[ticker, 'Risk'] = "{:.3f}".format(annual_returns.std())

    # 価値変動幅(±1σ、±2σ)
    stats.loc[ticker, 'Value +/- 1σ'] = f"{mean_value - results[ticker].std():,.0f}, {mean_value + results[ticker].std():,.0f}"
    stats.loc[ticker, 'Value +/- 2σ'] = f"{mean_value - 2*results[ticker].std():,.0f}, {mean_value + 2*results[ticker].std():,.0f}"

stats
Table 集計結果

集計した結果、オルカン(ACWI)は平均1.5倍、S&P500は1.8倍です。この時のリターンの年率(Annual Return)は、オルカンが0.090、S&P500が0.127となってます。これは%に変換すると、オルカンが年率9.0%、S&P500が12.7%増えることを示してます。2014年以降で5年保有していると1年で1割、100万円が年10万増えたことになります(平均値)。投資の力は偉大だと実感できると思います。
 ただし投資にはリスクがつきもので、RIskはオルカンは0.033となってます。この数値だけではイメージしずらいと思いますので±σの範囲で見てみると、確率的にどの範囲となりそうかイメージできるのではと思います。

3)リスク・リターンの可視化
投資対象を比較する上で、リスク、リターンは重要ですが、これを散布図で示すことが投資対象の評価として参考になると思います。具体的にリスク・リターンを可視化してみます。なお、X軸がリスク(右に行くほどリスク=ばらつきが大きい)Y軸がリターン(上に行くほどリターン=儲けが大きい)ことを示してます。出力結果、オルカンはS&Pに比べてリスクもリターンもやや劣る結果であるといえます。

# データ型をfloatに変換
stats['Annual Return'] = stats['Annual Return'].astype(float)
stats['Risk'] = stats['Risk'].astype(float)

# 年率リターンとリスクの散布図
plt.figure(figsize=(10, 6))

# 各ティッカーのポイントをプロット
for ticker in tickers:
    plt.scatter(stats.loc[ticker, 'Risk'], stats.loc[ticker, 'Annual Return'], label=ticker)

# タイトルと軸ラベルの設定
plt.xlim(0, plt.xlim()[1]+0.01)
plt.ylim(0, plt.ylim()[1]+0.1)
plt.title('Risk vs. Annual Return')
plt.xlabel('Risk (Annual Std Dev)')
plt.ylabel('Annual Return')
plt.legend()
plt.grid(True)

# 散布図の表示
plt.show()
散布図 リスク・リターン

3)シミュレーション
実際に得られた年率リターン、リスクを使ってシミュレーションしてみます。まず年率リターン、リスクを日々の変動幅分へ変換し、日々そのリターン、リスクを使って1000回のシミュレーションして、そのリターンを計算しています。*比較しやすいように、1年、5年、20年の3つのグラフを同時に出力しようとしてます。

# stats DataFrameから年率リスクとリターンを取得
annual_returns = stats['Annual Return'].astype(float)
annual_risks = stats['Risk'].astype(float)

# 日次リスクとリターンに変換
daily_returns = (1 + annual_returns) ** (1/252) - 1
daily_risks = annual_risks / np.sqrt(252)

# 新しいDataFrameに結果を格納
daily_stats = pd.DataFrame(index=tickers)
daily_stats['Daily Return'] = daily_returns
daily_stats['Daily Risk'] = daily_risks

# シミュレーションに必要なパラメータ
simulations = 1000  # シミュレーションの回数
years = [1, 5, 20]  # シミュレーションを行う年数
days_in_year = 252  # 1年あたりの取引日数
initial_investment = 1000  # 初期投資額

# シミュレーション結果を格納するためのディクショナリの初期化
simulation_results = {year: {ticker: np.zeros((days_in_year * year, simulations)) for ticker in tickers} for year in years}

# 各ティッカーごとにモンテカルロシミュレーションを実行
for year in years:
    days = days_in_year * year
    for ticker in tickers:
        daily_return = daily_stats.loc[ticker, 'Daily Return']
        daily_risk = daily_stats.loc[ticker, 'Daily Risk']
        
        for i in range(simulations):
            # 日次リターンのランダムなサンプルを生成
            daily_returns = np.random.normal(daily_return, daily_risk, days)
            # 初期投資額に基づいて価格経路を計算
            price_path = initial_investment * np.cumprod(1 + daily_returns)
            simulation_results[year][ticker][:, i] = price_path

# 結果の可視化
for year, results_by_ticker in simulation_results.items():
    plt.figure(figsize=(14, 7))
    for ticker, data in results_by_ticker.items():
        days = np.arange(1, days_in_year * year + 1)
        median_prices = np.median(data, axis=1)
        std_dev = np.std(data, axis=1)
        
        # メディアン価格と標準偏差の範囲をプロット
        plt.plot(days, median_prices, label=f'{ticker} Median')
        plt.fill_between(days, median_prices - std_dev, median_prices + std_dev, alpha=0.2)
    # 初期投資額
    plt.axhline(initial_investment, color='red', linestyle='--', linewidth=1, label='Initial Investment ($1000)')
    
    plt.title(f'Monte Carlo Simulation Results after {year} Year(s)')
    plt.xlabel('Days')
    plt.ylabel('Simulated Price')
    plt.legend()
    plt.show()
モンテカルロシミュレーション結果 時系列:1年
モンテカルロシミュレーション結果 時系列:5年
モンテカルロシミュレーション結果 時系列:20年

長期トレンドで見てみると、1年後にはばらつき範囲が重なり合ってどれでも差がない可能性が高いのですが、5年後以降はその差が顕著であることがわかります。
 上記グラフは±1σですが、シミュレーション結果を箱ひげ図で示すことで、1年後、5年後、20年後のイメージがよりわかり易くなります。コードは下記です。

# 結果を箱ひげ図で表示
for year, results_by_ticker in simulation_results.items():
    plt.figure(figsize=(10, 6))
    
    # データを準備(最終日の価格結果を各ティッカーごとに収集)
    final_prices = [data[-1, :] for ticker, data in results_by_ticker.items()]
    
    # 箱ひげ図を垂直で描画
    plt.boxplot(final_prices, labels=tickers, vert=True)
    
    # 初期投資額の水平線を追加
    plt.axhline(initial_investment, color='r', linestyle='--', label='Initial Investment ($1000)')
    
    plt.title(f'Simulation Results after {year} Year(s)')
    plt.ylabel('Final Simulated Price')
    plt.xlabel('Ticker')
    plt.legend()
    plt.show()
モンテカルロシミュレーション結果 箱ひげ図:1年
モンテカルロシミュレーション結果 箱ひげ図:5年
モンテカルロシミュレーション結果 箱ひげ図:20年

上記の通り、箱ひげ図ではシミュレーション結果のばらつき具合が明確に示すことができています。長期的なトレンドが気になる人は、時系列グラフ、期待リターンとそのばらつき程度が知りたい方は箱ひげ図を使うことでお好みの予測結果を可視化できます。

補足)調査結果コメント
大人気のオルカン(ACWI)は、S&P500(SPY)に比べて、リスクもリターンも劣る結果であった。ただしオルカンが劣るわけではなく、一般的に分散投資で、金や債権を勧められていますが、今回の結果ではオルカンに比べてリターンが少なすぎるようにみられます。これは個人のリスク許容度次第であるとは思いますが参考になれば幸いです。

4 まとめ

今回は、2014年から5年間投資した結果を統計的に分析し、その分析結果をもとに今後をシミュレーションしてみました。この手法は、投資方針の検討に非常に参考になると思いますので、皆様もぜひ一度触ってみてもらって、プログラミング能力+投資リテラシー向上の両立を目指してみてください。
なお、次回はこの手法でもう少し投資方針に参考になる記事を考えてみますので、今後とも応援のほど、よろしくお願いします!!

X(旧Twitter)問い合わせ、コメント等はこちらからお願いします。

https://twitter.com/KotaKota978293


実行環境はGoogle Colabを使ってます。紹介記事は下記。

PYTHONによる時系列予測に興味がある方はこちらもどうぞ


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