Streamlitで長期積立投資シミュレーションのWebアプリを作成

# 目的
- 新NISA制度の影響か、以前作成した[長期積立投資シミュレーションの記事](https://qiita.com/yasuyuki-s/items/54a89389cbcfd3c625c9)のアクセス数が非常に伸びた
- 同プログラムは特定のリターン・リスク（標準偏差）を持つ資産に毎月積立投資を行った場合の長期資産推移をモンテカルロ法により算定するもの
- PythonでWebアプリを作成できるStreamlitの習作を兼ねて、多くの人がより手軽に使えるよう同プログラムをWebアプリ化する

# 実行例
- 以下のStreamlit Cloudにて公開中

https://long-term-investment-test.streamlit.app/

- インプット項目を入力しボタンをクリックすると、資産推移のP10～P90のパーセンタイル値、累計投資額（元本）などをインタラクティブなグラフや表で出力します
![Animation2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1350685/2ebae938-b051-c402-a7d6-18e96f799c62.gif)

- リソース制約により、Streamlit Cloud上では投資期間とモンテカルロ法の試行回数の上限を設定しています（ローカル環境で実行する場合この制約はありません）

# 実装方法
- ソースコード全文は下記を参照してください

https://github.com/yasuyuki-s/streamlit-test/blob/main/streamlit_app.py

- 計算の中身については以前作成したものをほぼ流用しているので、下記記事をご参照ください

https://qiita.com/yasuyuki-s/items/54a89389cbcfd3c625c9

- 以下、上記をベースにStreamlitを用いてWebアプリ化した部分を中心に記述します
## 1.インプット項目の配置
```python
#ライブラリ読み込み
import streamlit as st

# 期待対数収益率、標準偏差（%,年換算）
mu = st.number_input("Annualized Return(%)", min_value=0.0, max_value=None, value=8.0, step=0.1, help="対象資産の対数収益率（年換算）。対数収益率が不明の場合、収益率を近似値として使用しても可")
s = st.number_input("Annualized Risk(%)", min_value = 0.0, max_value=None, value=20.0, step=0.1, help="対数収益率（年換算）の標準偏差")
# 投資期間（年）
dur_y = st.number_input("Investment Duration（year）", min_value=1, max_value=20, value=10, step=1, help="積立投資を行う期間。Streamlit Cloudのリソース制約上、上限は20年")
# 初期投資額
x_init = st.number_input("Initial investment amount", min_value=0, max_value=None, value=0, step=1, help="初期投資額")
# 毎月積立額
delta_m = st.number_input("Monthly investment amount", min_value=0, max_value=None, value=10, step=1, help="毎月の積立額")
# 試行回数
n = st.number_input("Number of trials for Monte Carlo Calculation", min_value=100, max_value=3000, value=2000, step=1, help="モンテカルロ法による試行回数。Streamlit Cloudのリソース制約上、上限は3000")
```
- 計算に用いる各変数を、Streamlitの`number_input`ウィジットとして配置しています
- ローカル環境で実行する場合は、`dur_y`と`n`で設定している`max_value`の値を`None`に変更すれば、上限を撤廃して計算を実行することが可能です。
## 2.計算実行ボタンの配置
```python
if st.button("Run"):
	#リターンmu、リスクsのモデルを定義
	model = asset_model(mu,s)
	#投資期間dur_y、試行回数nのモンテカルロシミュレーションを実行
	model.run_mc(dur_y,n)
	#初期投資x_init、毎月delta_mの積立投資を行った場合の資産推移を計算
	model.exercise(x_init,delta_m)
```
- Streamlitの`button`ウィジットを配置し、ボタンクリック時の処理を記述しています。
## 3.プログレスバーの配置
```python:class asset_model
#対数収益率の長期モンテカルロシミュレーションを実施
def run_mc(self, dur_y, n):
    #プログレスバー出力
    progress_text = "Monte Carlo Calculation of "+ str(dur_y) + " years in progress."
    my_bar = st.progress(0, text=progress_text)
    #入力された投資期間を年→月へ変換
    dur_m = dur_y * 12
    my_bar.progress(10, text=progress_text) #プログレスバー出力
    # [(投資期間_月),(試行回数)]の正規分布N(μ,σ)に従う乱数行列として毎月の対数収益率を生成
    A1 = np.random.normal(self.mu_m, self.s_m,size=[dur_m,n])        
    my_bar.progress(20, text=progress_text) #プログレスバー出力
    #次元を拡張し、[(投資期間_月),(投資期間_月),(試行回数)]の行列とする
    A2 = np.tile(A1,[dur_m,1,1])
    my_bar.progress(40, text=progress_text) #プログレスバー出力
    #行列を転置し、[(試行回数),(投資期間_月),(投資期間_月)]の行列とする
    A3 = np.transpose(A2,(2,0,1))
    my_bar.progress(60, text=progress_text) #プログレスバー出力
    #各試行の上三角行列を取得
    A4 = np.triu(A3)
    my_bar.progress(80, text=progress_text) #プログレスバー出力
    #
    self.A = A4
    self.dur_m = dur_m
    my_bar.progress(100, text=progress_text) #プログレスバー出力
```
- Streamlitの`progress`ウィジットを配置し、適宜進捗状況を更新しています

```python:class asset_model
#初期投資額x_init, 毎月積立額delta_mを投資した場合の資産推移を計算
def exercise(self, x_init, delta_m):
    #プログレスバー出力
    progress_text = "Investment simulation in progress."
    my_bar = st.progress(0, text=progress_text)
    ###省略###
    
    #総資産額の推移を1年毎(12ヶ月毎)に集計する
    for i in range(11, self.dur_m,12):
        ###省略###
        #プログレスバー出力
        my_bar.progress(i/(self.dur_m-1), text=progress_text)
```
- こちらでは、ループの中に進捗状況の更新を埋め込んでいます
## 4.インタラクティブなグラフの配置
```python:def exercise（続き)
import plotly.graph_objects as go

#グラフ出力
fig = go.Figure()
fig.add_trace(go.Scatter(x=df_result["Year"],y=df_result["P90"],name="P90"))
fig.add_trace(go.Scatter(x=df_result["Year"],y=df_result["P10"],name="P10", fill="tonexty"))
fig.add_trace(go.Scatter(x=df_result["Year"],y=df_result["P70"],name="P70"))
fig.add_trace(go.Scatter(x=df_result["Year"],y=df_result["P30"],name="P30", fill="tonexty"))
fig.add_trace(go.Scatter(x=df_result["Year"],y=df_result["P50"],name="P50"))
fig.add_trace(go.Scatter(x=df_result["Year"],y=df_result["Capital"],name="Capital"))
fig.update_layout(hovermode="x")
fig.update_layout(title="Result of Simulation", xaxis_title="Year")
st.plotly_chart(fig, use_container_width=True)
```
- グラフの出力に以前は`Matplotlib`を用いていましたが、インタラクティブなグラフにするため`Plotly`を用いて出力しています。