【EN/JP】目指せFinancial independence! FIRE Simulation Dashboard: Python+Streamlit【Free App♪】

Python code
スポンサーリンク

【日本語での詳細解説はすぐ下にあります】

For English speakers: This article provides the FIRE Simulator (FREE) written in Python. If you would like to run the simulator without special settings and codings, please click here (running on Streamlit Cloud). All comments and variable names in the following code were written in English. So, you may have no trouble understanding the code if you are familiar with Python, Plotly, and Streamlit. (A complete code is shown in Chapter 4).
If you have any questions, do not hesitate to ask me (contact form). Please enjoy it♪

こんにちは、ときか姉です♪

最近世界中で使われるようになった”FIRE”という言葉があります。

Financially Independent, Retire Early(経済的自立, 早期退職)の頭文字をとって生まれたこの言葉。不労所得を得て早期退職し、好きなことに没頭できるのであればたしかにHappy retire, Happy lifeといっても過言ではないと思います。

とはいえ、実現するのはそう簡単ではありません。

「FIREしたいけどどう計画したら良いかわからない」

「初期投資額はどれぐらい?」「月額投資額はいくら?」「利率は?」

「生活費と年金を入れて一体いくら貯めれば安心してリタイアできるの?」

このような疑問をお持ちの方は多いと思います。

この記事ではそのような方のために、FIRE達成に向けた計画を練るために活用できるFIRE simulatorを紹介します。PythonとStreamlitで書かれたコードを無料で提供します。ぜひご活用ください♪

とにかく動かしてみたいという方はこちらから(Streamlit Cloud経由での実行なので設定不要です。PC推奨)

1. この記事はこんな人におすすめ / Potential readers of this article

この記事は以下のような方におすすめです。

  • FIREを目指して本気で計画を立てたい
  • Financial dataの扱いに関して学びたい
  • Financial dataのアプリを作ってみたい
  • StreamlitによるGUIの作り方を学びたい
  • Plotlyによるグラフ描画の細かいところを学びたい

FIREにご興味がない方でも、Python学習、特にPlotlyやStreamlitの扱いに関してご参考になるかと思います。

コードのみ欲しいという方は、文末にございますので読み飛ばしてくださいね♪

なお、実際の株価やビットコイン価格の取得・ローソク足描画などは別途書いていますのでそちらをご参照くだされば幸いです。

[関連記事はこちら↓です♪]

2. FIREシミュレータ概要 / Overview of the FIRE simulator

Steramlitがインストールされている状態を前提にしています。

実行するにはターミナルで以下をタイプしてください。

streamlit run fire-simulator.py

(設定せずにとにかく動かしてみたいという方はこちらから(Streamlit Cloud経由で直ちに実行されます。PC推奨)

デフォルトのブラウザが立ち上がり、以下のような画面がブラウザ内に表示されます。

(赤いボックスとテキストは後の解説のためにあとから加えたものです。)

各ボックスの詳細を以下に記します。

[Streamlitをもっと学ぶなら↓]

2-1. 初期設定 / UI widgets for parameter settings

  • 基本パラメータ(Base): 投資期間(investment period [year]), 通過タイプ(currency type, JPY/USD)
  • 投資パラメータ(Investment): 初期資産(initial amount), 投資月額(monthly investment), 投資利率(investment interest)
  • 経済的自立パラメータ(Financial independence): 生活費(living cost), 年金月額(pension amount), リタイア年(retirement year), 年金開始年(pension start year)

UIウィジェットは、Streamlitのselect box, number input, sliderbarを用いてsidebarに作成・配置しました。

このSidebarは自在に表示のオン・オフ切り替えができます。

2-2. 初期パラメータと結果要約 / Initial parameters and result summary

メインパネルの上部には、Sidebarで選択した初期設定の値がテキスト表示されます。

一番左が投資パラメータ、真ん中が経済的自立パラメータです。

そして一番右の”Final summary”には、結果の要約が表示されます。これには、投資期間最終年における総資産(Total amount at final year), 投資元本総額(Final principals), リターン総額(Total returns)が含まれます。

2-3. グラフ描画 / Graphs for simulation results

シミュレーション結果をメインパネル下部に2つのグラフで表示します。

左のグラフは投資リターン(Investment return)に関してで、右は経済的自立(Financial independence)に関してです。

左の”Investment return”グラフでは、投資元本が黒線で、利子が加算された総資産が黄緑のバーで描画されます。

投資年最終額がそれぞれ水平の線とテキストで描画されます。

右の”Financial independence”のグラフでは、生活費が黒線で、総資産が青いバーで描画されます。ここでの総資産は生活費や年金を考慮して求められたものです。もし破産した場合、その年を赤い縦線とともに示します。

2つのグラフはPlotlyによって作成されています。そのため、インタラクティブな描画変更(拡大など)が可能です。

また、サイドバーでパラメータを変更するとただちにグラフが再描画されます。

以上がこのFIRE Simulatorの概要になります。

[トレード口座開設はコチラ↓]

3. コードの解説 / Code description (Python+Streamlit)

以下では、FIRE Simulatorのコードの中身を解説していきます。

最後に全体のコードをとにかく動かしてみたいという方は以降は読み飛ばしてそちらをご覧ください。

[あわせてこちらの記事↓もどうぞ]

3-1. ライブラリのインポート / Import libraries

本FIREシミュレータに必要なライブラリは多くはありません。

データを扱うためのnumpy,とpandas, グラフ描画のためのplotly, UI設置のためのstreamlitの四種のみです。それぞれをインポートします。

# Copyright (C) 2024 Tokikanee
# E-mail: female.pythonista@gmail.com

# Library import
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import streamlit as st

3-2. クラスと変数の作成 / Preparation of class and variables

シミュレータ本体として、クラスfire_simulatorを作成します。

初期パラメータとして、グラフの縦横幅やシミュレーション結果を格納する各種変数を用意しておきます。

class fire_simulator():
    def __init__(self):
        self.graph_w=1200 # Graph width, グラフの横幅
        self.graph_h=800 # Graph height, グラフの縦幅
        self.amounts=[] # Total amounts wo/ living cost, 総資産(生活費を含めない)
        self.amounts_fi=[] # Total amounts for [f]inanncial [i]ndependence w/ living cost, 総資産(生活費を含める)
        self.principals=[] # Total principals, 元本
        self.returns=[] # Total return, リターン
        self.living_costs=[] # Living cost (monthly), 毎月生活費
        self.start_year=2024 # Start year of simulation, シミュレーション開始年
        self.total_period=50 # Total period of FIRE simulation (year), 全シミュレーション年
        self.end_year=self.start_year+self.total_period, # End year of simulation, シミュレーション終了年
        self.bankruptcy_year=[] # bankruptcy year if it occurred, 破産した場合の年
        self.df=pd.DataFrame([]) # Pandas DataFrame for summary data, 全データをまとめるためのPandasのDataFrame

3-2. 初期パラメータとUI配置 / Setting of UIs for initial parameters

前述のように、シミュレーションのパラメータは、サイドバー内に設置したUIにて設定します。

関数set_default_ui内でUIを作成・配置します。

  • 基本パラメータ:投資期間と通貨選択
  • 投資パラメータ:初期投資額、毎月投資額、利率
  • 経済的自立パラメータ:生活費、年金額、リタイア年、年金開始年

となります。

def set_default_ui(self):
    # Initial setting of dashboard, Dashboardの初期設定
    st.set_page_config(
        page_title = 'FIRE Simulator made by Tokikanee♪',
        layout="wide")
        
    # Function UI configuration for FIRE simulation parameters, 
    # FIREシミュレーションのパラメータ設定用UIの配置

    # Basic parameters: investment period, currency name), 
    # 基本パラメータ: 投資期間, 通貨選択
    st.sidebar.title("FIRE parameters (base)")
    self.period=st.sidebar.selectbox("Period (year)",["5","10","20","30","40","50"],3)
    self.currency_name=st.sidebar.selectbox("Currency",["JPY","USD"])

    # Investment parameters: Initial amount, monthly investment, investment interest (yearly)
    # 投資のパラメータ: 初期投資額, 毎月投資額, 利率(年)
    st.sidebar.title("FIRE parameters (Investment)")
    self.initial_amount=st.sidebar.number_input(f"Initial amounts      {self.currency_name}",10000000,step=10000)
    self.investment=st.sidebar.number_input(f"Investment (monthly) {self.currency_name}",100000,step=10000)
    self.investment_interest=st.sidebar.slider(label="Investment interest [%]",min_value=0,max_value=20,value=4)

    # Parameters for financidal independence: living cost (monthly), pension amount (monthly), retirement year, pension start year
    # 経済的自立のためのパラメータ: 生活費月額, 年金月額, リタイア年, 年金開始年
    st.sidebar.title("FIRE parameters (Fin. indep.)")
    self.living_cost=st.sidebar.number_input(f"Living cost (monthly) {self.currency_name}",300000,step=10000)
    self.pension_amount=st.sidebar.number_input(f"Pension amount (monthly) {self.currency_name}",100000,step=10000)
    self.retirement_year=st.sidebar.selectbox("Retirement year",range(self.start_year,2100),index=16)
    self.pension_start_year=st.sidebar.selectbox("Pension start year",range(self.start_year,2100),index=21)

    # End year of investment simulation
    # 投資シミュレーションの終了年
    self.end_year=self.start_year+int(self.period)

    # Title of the simulator
    # シミュレータのタイトル
    st.title(f"FIRE Simulator made by Tokikanee♪")

[トレード口座開設はコチラ↓]

3-3. シミュレーション実行 / Simulation

本FIREシミュレータの主要箇所で、実際にシミュレーションを実行するコードになります。

関数fire_simulationを実行し、リターンやFIRE可否を計算します。

コード内コメントの通りですが、いくつか補足します。

基本的には、予め与えられている期間(50年間)ループして計算します。投資利率に基づいて総資産を求めます。

初期パラメータで与えられる投資期間は、投資シミュレーションの描画のために切り出す期間となります。

また、年金開始年で分岐が生じます。これ以降は生活費と年金の差額が総資産から引かれます。

def fire_simulation(self):
    # Function for investment simulation & financial independence 
    
    # Setting initial year
    # 初年度の値を設定
    self.amounts=[self.initial_amount]
    self.amounts_fi=[self.initial_amount]
    self.principals=[self.initial_amount]
    self.returns=[0]
    self.living_costs=[np.nan]
    years=[self.start_year]

    # Main loop
    # メインループ
    for i in range(1,int(self.total_period)+2):

        # Increment of year
        # 年を増やす
        year=years[i-1]+1

        # Calculation of yearly amount of investment
        # 投資年額を計算
        investment_yearly=self.investment*12
        
        # Accumulation of principals
        # 投資元本の累積
        principal=self.principals[i-1]+investment_yearly

        # Summation of amount until previous year and current year investment
        # 前年までの総資産に今年の投資額を合算
        amount=self.amounts[i-1]+investment_yearly
        
        # Calculation of interest
        # 利子を計算
        interest=amount*self.investment_interest/100
        
        if year>=self.pension_start_year:
            # After penstion start year: living cost & pension amount should be included.
            # 年金受給年以降の処理: 生活費と年金額を考慮
            
            # Calculation of yearly expense: (mothly living cost - monthly pension) x 12 month
            # 年間支出を計算: (毎月生活費ー毎月年金額)x 12ヶ月
            cost=(self.living_cost-self.pension_amount)*12

            # Summation of previous year's amount and monthly investment deducted monthly cost
            # 前年までの総資産に、今年度の投資額からコストを差し引いた分を加算
            amount_fi=self.amounts_fi[i-1]-investment_yearly-cost

            # Calculation of interest for F.I.
            # 経済的自立の利子計算
            interest_fi=amount_fi*self.investment_interest/100
            
            # Data accumulation of living cost
            # 生活費データの蓄積
            self.living_costs.append(self.living_cost)
        else: 
            # Before pension start year
            # 年金受給開始年以前の処理 
            cost=0
            amount_fi=self.amounts_fi[i-1]+investment_yearly
            interest_fi=amount_fi*self.investment_interest/100
            self.living_costs.append(np.nan)
        
        # Recording of bankruptcy years if it occurred
        # もし破産した場合その年次を記録
        if amount_fi<0: self.bankruptcy_year.append(year)

        # Data accumulation for visualization
        # 可視化のためのデータ蓄積
        years.append(year)
        self.amounts.append(amount+interest)
        self.principals.append(principal)
        self.returns.append(amount+interest-principal)
        self.amounts_fi.append(amount_fi+interest_fi-cost)

    # Convert to DataFrame
    # DataFrameへの変換
    self.df["years"]=years
    self.df["amounts"]=self.amounts
    self.df["principals"]=self.principals
    self.df["amounts_fi"]=self.amounts_fi
    self.df["living_costs"]=self.living_costs
    
    # Extraction of the 1st year of bankruptcy
    # 破産の初年度を抽出
    if self.bankruptcy_year:
        self.bankruptcy_year=self.bankruptcy_year[0]

[トレード口座開設はコチラ↓]

3-4. 結果要約(テキスト)/ Visualization: summary

関数print_summaryを用いて、シミュレーションのパラメータと結果の要約を、シミュレータ上部にテキスト表示します。

def print_summary(self):
    # Print summary data (string)
    # 要約データの文字列表示

    # Displaying of investment simulation year and setting of container
    # 投資シミュレーション期間の表示とコンテナの配置
    st.header(f"Investment period: {self.period} years")
    container=st.container()

    with container:
        c1,c2,c3=st.columns(3)
        # Display of parameters for investment simulation
        # 投資シミュレーションのパラメータ表示
        with c1:
            st.title("[Investment]")
            st.header(f"Initial amount        : {self.initial_amount:,.0f} {self.currency_name}")
            st.header(f"Investment interest   : {self.investment_interest:.2f} %")
            st.header(f"Investment (monthly)  : {self.investment:,.0f} {self.currency_name}")
        # Display of parameters for financial independence
        # 経済的自立シミュレーションのパラメータ表示
        with c2:
            st.title("[Financial independence]")
            st.header(f"Living cost (monthly) : {self.living_cost:,.0f} {self.currency_name}")
            st.header(f"Pension (monthly)     : {self.pension_amount:,.0f} {self.currency_name}")
            st.header(f"Retirement year       : {self.retirement_year:.0f} -")
            st.header(f"Pension start year    : {self.pension_start_year:.0f} -")
        # Display of simulation results: final amount, final principals, final return, amount at retirement year
        # シミュレーションの結果表示: 総資産, 元本, リターン, リタイア時の総資産
        with c3:
            st.title(f"[Final summary]")
            st.header(f"Final amount ({self.period} years after)     : {self.amounts[int(self.period)]:,.0f} {self.currency_name}")
            st.header(f"Final principal ({self.period} years after)  : {self.principals[int(self.period)]:,.0f} {self.currency_name}")
            st.header(f"Final Return ({self.period} years after)     : {self.returns[int(self.period)]:,.0f} {self.currency_name}")
            st.header(f"Amount at Retirement ({self.retirement_year}): {self.amounts_fi[int(self.retirement_year)-int(self.start_year)]:,.0f} {self.currency_name}")

[あわせてこちらの記事↓もどうぞ♪]

3-5. 結果要約(グラフ) / Visualization: Interactive graph with Plotly

関数visualize_fire_simuationを用いて結果の要約をグラフで表示します。

左のグラフが投資シミュレーションに関するもので、右のグラフが経済的自立に関するものです。

前者は単純に投資額に対する複利計算です。後者は生活費や年金額を考慮して経済的自立が実現可能化どうかを計算します。もし破産した場合にはその年を赤の縦線で表示します。

この箇所が最も開発に時間がかかりました。

というのも、私がPlotlyの書式に慣れておらず、細かいカスタイマイズのために色々と試行錯誤する必要があったからです。

Plotlyではレイアウトを整形する際のパラメータを基本的に辞書形式で与えるのですが、その階層によって入れ子の辞書などが必要になります。その辺りに慣れるまで苦戦しました。。^^;

この記事では割愛しますが、いずれPlotlyのtipsも記事にしたいと思います♪

def visualize_fire_simulation(self):
    # Visualization of the FIRE simulation
    # FIREシミュレーションの可視化
    
    # Setting a placeholder as a main figure
    # メイン図としてplaceholderを設置
    placeholder = st.empty()
    
    # Place a container
    # コンテナを設置
    with placeholder.container():

        # Setting of two columns for investment return (c1) and financial independence (c2)
        # 投資リターン用(c1)と経済的自立用(c2)に2つのパネルを用意
        c1,c2=st.columns(2)
        
        # Visualization of investment return
        # 投資リターンの可視化
        with c1:
            # Print title, タイトル表示
            st.title("Investment Return")
            
            # Preparation of figure object, Figureオブジェクトの用意
            fig=go.Figure()
            
            # Extraction of data for visualization of investment return
            # 投資リターン範囲のデータを抽出
            df=self.df.iloc[:int(self.period)+1]
            
            # Plot principals, 元本のプロット
            fig.add_trace(go.Scatter(x=df["years"],y=df["principals"],
                                        name="Principals",mode="lines+markers",
                                        line={"width":5,"color":"black"},marker={"size":20}))
            
            # Plot amounts, 総資産のプロット
            fig.add_trace(go.Bar(x=df["years"],y=df["amounts"],
                                    name="Amounts",marker_color="lightgreen"))
            
            # Horizontal line plot for final pricipals, 最終元本を横線でLineプロット
            y=self.df["principals"].iloc[int(self.period)]
            fig.add_trace(go.Scatter(x=[int(self.start_year),int(self.end_year)],
                                        y=[y,y],name="Principals",
                                        mode="lines",line={"width":2,"color":"grey"},
                                        showlegend=False))
            fig.add_annotation(x=int(self.end_year),y=y,
                                text=f"Principlas {y:,.0f} {self.currency_name}",
                                showarrow=True,arrowhead=3,
                                font={"size":30,"color":"black"})
            
            # Horizontal line plot for final amount, 最終総資産を横線でLineプロット
            y=self.df["amounts"].iloc[int(self.period)]
            fig.add_trace(go.Scatter(x=[int(self.start_year),int(self.end_year)],
                                        y=[y,y],name="Final amounts",
                                        mode="lines",
                                        line={"width":2,"color":"darkgreen"},
                                        showlegend=False))
            fig.add_annotation(x=int(self.end_year),y=y,
                                text=f"Final amount {y:,.0f} {self.currency_name}",
                                showarrow=True,arrowhead=3,
                                font={"size":30,"color":"black"})
            
            # Graph formatting by layout object, Layoutオブジェクトによるグラフの整形
            layout = go.Layout(
                width=self.graph_w, height=self.graph_h,
                plot_bgcolor="white",
                xaxis = dict(
                    title = 'Year',
                    showgrid = False,
                    dtick=5,
                    tickfont={"size":30},
                    tickangle=45
                    ),
                yaxis = dict(
                    title=f"{self.currency_name}",
                    constrain="domain",
                    showgrid = False,
                    tick0=0,
                    dtick=20000000,
                    tickformat=",.0f",
                    tickfont={"size":30}
                    ),   
            )

            fig.update_layout(layout)
            st.write(fig)
            
        # Visualization of financial independence
        # 経済的自立シミュレーションの可視化
        with c2:
            # Print title, タイトル表示
            st.title("Fincial Independence")

            # Preparation of figure object, Figureオブジェクトの用意
            fig=go.Figure()
            
            # Plot living cost, 生活費のプロット
            fig.add_trace(go.Scatter(x=self.df["years"],y=self.df["living_costs"],
                                        name="Living costs",mode="lines+markers",
                                        line={"width":5,"color":"black"},marker={"size":10}))
            
            # Plot financial independence (amount with living cost and pension)
            # 経済的自立データの描画(生活費と年金を考慮した総資産)
            fig.add_trace(go.Bar(x=self.df["years"],y=self.df["amounts_fi"],
                                    name="Amounts(F.I.)",marker_color="blue"))
            
            # Vertial line plot of bankruptcy year if it occurred
            # 破産した場合に縦線で年次描画
            fig.add_trace(go.Scatter(x=[self.bankruptcy_year,self.bankruptcy_year],
                                        y=[self.df["amounts_fi"].max()*0.9,self.df["amounts_fi"].min()],
                                        name="bankruptcy Year",
                                        mode="lines",
                                        line={"width":5,"color":"red"}))
            if self.bankruptcy_year:
                fig.add_annotation(x=self.bankruptcy_year,y=self.df["amounts_fi"].max(),
                                xref="x",yref="y",
                                showarrow=False,
                                align="center",
                                text=f"{self.bankruptcy_year}",
                                font={"size":30,"color":"red"},
                                )     
                                
            # Graph formatting by layout object, Layoutオブジェクトによるグラフの整形
            layout = go.Layout(
                width=self.graph_w, height=self.graph_h,
                plot_bgcolor="white",
                xaxis = dict(
                    title = 'Year',
                    showgrid=False,
                    dtick=5,
                    tickfont={"size":30},
                    tickangle=45
                    ),
                yaxis = dict(
                    title=f"{self.currency_name}",
                    constrain="domain",
                    showgrid=False,
                    dtick=20000000,
                    tickformat=",.0f",
                    tickfont={"size":30}
                    ),
            )                
            fig.update_layout(layout)
            st.write(fig)

[トレード口座開設はコチラ↓]

3-6. シミュレーションの実行 / Running simulation

関数run_simulatorでここまでの関数を順次実行します。

def run_simulator(self):
    # Perform simulation, シミュレーションの実行
    self.set_default_ui()
    self.fire_simulation()
    self.print_summary()
    self.visualize_fire_simulation()

クラスのインスタンス化と、メソッド関数run_simulatorの実行を記述しておきます。

fs=fire_simulator()
fs.run_simulator()

4. コード完成版(コピペで動きます) / Complete code

コード全体を以下に記します。

とにかく動かしたいという方は以下のコード全体をコピペして、端末で”streamlit run [ファイル名.py]“を実行してください。

# Copyright (C) 2024 Tokikanee
# E-mail: female.pythonista@gmail.com

# Library import
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import streamlit as st

class fire_simulator():
    def __init__(self):
        self.graph_w=1200 # Graph width, グラフの横幅
        self.graph_h=800 # Graph height, グラフの縦幅
        self.amounts=[] # Total amounts wo/ living cost, 総資産(生活費を含めない)
        self.amounts_fi=[] # Total amounts for [f]inanncial [i]ndependence w/ living cost, 総資産(生活費を含める)
        self.principals=[] # Total principals, 元本
        self.returns=[] # Total return, リターン
        self.living_costs=[] # Living cost (monthly), 毎月生活費
        self.start_year=2024 # Start year of simulation, シミュレーション開始年
        self.total_period=50 # Total period of FIRE simulation (year), 全シミュレーション年
        self.end_year=self.start_year+self.total_period, # End year of simulation, シミュレーション終了年
        self.bankruptcy_year=[] # bankruptcy year if it occurred, 破産した場合の年
        self.df=pd.DataFrame([]) # Pandas DataFrame for summary data, 全データをまとめるためのPandasのDataFrame
        
    def set_default_ui(self):
        # Initial setting of dashboard, Dashboardの初期設定
        st.set_page_config(
            page_title = 'FIRE Simulator made by Tokikanee♪',
            layout="wide")
            
        # Function UI configuration for FIRE simulation parameters, 
        # FIREシミュレーションのパラメータ設定用UIの配置

        # Basic parameters: investment period, currency name), 
        # 基本パラメータ: 投資期間, 通貨選択
        st.sidebar.title("FIRE parameters (base)")
        self.period=st.sidebar.selectbox("Period (year)",["5","10","20","30","40","50"],3)
        self.currency_name=st.sidebar.selectbox("Currency",["JPY","USD"])

        # Investment parameters: Initial amount, monthly investment, investment interest (yearly)
        # 投資のパラメータ: 初期投資額, 毎月投資額, 利率(年)
        st.sidebar.title("FIRE parameters (Investment)")
        self.initial_amount=st.sidebar.number_input(f"Initial amounts      {self.currency_name}",10000000,step=10000)
        self.investment=st.sidebar.number_input(f"Investment (monthly) {self.currency_name}",100000,step=10000)
        self.investment_interest=st.sidebar.slider(label="Investment interest [%]",min_value=0,max_value=20,value=4)

        # Parameters for financidal independence: living cost (monthly), pension amount (monthly), retirement year, pension start year
        # 経済的自立のためのパラメータ: 生活費月額, 年金月額, リタイア年, 年金開始年
        st.sidebar.title("FIRE parameters (Fin. indep.)")
        self.living_cost=st.sidebar.number_input(f"Living cost (monthly) {self.currency_name}",300000,step=10000)
        self.pension_amount=st.sidebar.number_input(f"Pension amount (monthly) {self.currency_name}",100000,step=10000)
        self.retirement_year=st.sidebar.selectbox("Retirement year",range(self.start_year,2100),index=16)
        self.pension_start_year=st.sidebar.selectbox("Pension start year",range(self.start_year,2100),index=21)

        # End year of investment simulation
        # 投資シミュレーションの終了年
        self.end_year=self.start_year+int(self.period)

        # Title of the simulator
        # シミュレータのタイトル
        st.title(f"FIRE Simulator made by Tokikanee♪")

    def fire_simulation(self):
        # Function for investment simulation & financial independence 
        
        # Setting initial year
        # 初年度の値を設定
        self.amounts=[self.initial_amount]
        self.amounts_fi=[self.initial_amount]
        self.principals=[self.initial_amount]
        self.returns=[0]
        self.living_costs=[np.nan]
        years=[self.start_year]

        # Main loop
        # メインループ
        for i in range(1,int(self.total_period)+2):

            # Increment of year
            # 年を増やす
            year=years[i-1]+1

            # Calculation of yearly amount of investment
            # 投資年額を計算
            investment_yearly=self.investment*12
            
            # Accumulation of principals
            # 投資元本の累積
            principal=self.principals[i-1]+investment_yearly

            # Summation of amount until previous year and current year investment
            # 前年までの総資産に今年の投資額を合算
            amount=self.amounts[i-1]+investment_yearly
            
            # Calculation of interest
            # 利子を計算
            interest=amount*self.investment_interest/100
            
            if year>=self.pension_start_year:
                # After penstion start year: living cost & pension amount should be included.
                # 年金受給年以降の処理: 生活費と年金額を考慮
                
                # Calculation of yearly expense: (mothly living cost - monthly pension) x 12 month
                # 年間支出を計算: (毎月生活費ー毎月年金額)x 12ヶ月
                cost=(self.living_cost-self.pension_amount)*12

                # Summation of previous year's amount and monthly investment deducted monthly cost
                # 前年までの総資産に、今年度の投資額からコストを差し引いた分を加算
                amount_fi=self.amounts_fi[i-1]-investment_yearly-cost

                # Calculation of interest for F.I.
                # 経済的自立の利子計算
                interest_fi=amount_fi*self.investment_interest/100
                
                # Data accumulation of living cost
                # 生活費データの蓄積
                self.living_costs.append(self.living_cost)
            else: 
                # Before pension start year
                # 年金受給開始年以前の処理 
                cost=0
                amount_fi=self.amounts_fi[i-1]+investment_yearly
                interest_fi=amount_fi*self.investment_interest/100
                self.living_costs.append(np.nan)
            
            # Recording of bankruptcy years if it occurred
            # もし破産した場合その年次を記録
            if amount_fi<0: self.bankruptcy_year.append(year)

            # Data accumulation for visualization
            # 可視化のためのデータ蓄積
            years.append(year)
            self.amounts.append(amount+interest)
            self.principals.append(principal)
            self.returns.append(amount+interest-principal)
            self.amounts_fi.append(amount_fi+interest_fi-cost)

        # Convert to DataFrame
        # DataFrameへの変換
        self.df["years"]=years
        self.df["amounts"]=self.amounts
        self.df["principals"]=self.principals
        self.df["amounts_fi"]=self.amounts_fi
        self.df["living_costs"]=self.living_costs
        
        # Extraction of the 1st year of bankruptcy
        # 破産の初年度を抽出
        if self.bankruptcy_year:
            self.bankruptcy_year=self.bankruptcy_year[0]

    def print_summary(self):
        # Print summary data (string)
        # 要約データの文字列表示

        # Displaying of investment simulation year and setting of container
        # 投資シミュレーション期間の表示とコンテナの配置
        st.header(f"Investment period: {self.period} years")
        container=st.container()

        with container:
            c1,c2,c3=st.columns(3)
            # Display of parameters for investment simulation
            # 投資シミュレーションのパラメータ表示
            with c1:
                st.title("[Investment]")
                st.header(f"Initial amount        : {self.initial_amount:,.0f} {self.currency_name}")
                st.header(f"Investment interest   : {self.investment_interest:.2f} %")
                st.header(f"Investment (monthly)  : {self.investment:,.0f} {self.currency_name}")
            # Display of parameters for financial independence
            # 経済的自立シミュレーションのパラメータ表示
            with c2:
                st.title("[Financial independence]")
                st.header(f"Living cost (monthly) : {self.living_cost:,.0f} {self.currency_name}")
                st.header(f"Pension (monthly)     : {self.pension_amount:,.0f} {self.currency_name}")
                st.header(f"Retirement year       : {self.retirement_year:.0f} -")
                st.header(f"Pension start year    : {self.pension_start_year:.0f} -")
            # Display of simulation results: final amount, final principals, final return, amount at retirement year
            # シミュレーションの結果表示: 総資産, 元本, リターン, リタイア時の総資産
            with c3:
                st.title(f"[Final summary]")
                st.header(f"Final amount ({self.period} years after)     : {self.amounts[int(self.period)]:,.0f} {self.currency_name}")
                st.header(f"Final principal ({self.period} years after)  : {self.principals[int(self.period)]:,.0f} {self.currency_name}")
                st.header(f"Final Return ({self.period} years after)     : {self.returns[int(self.period)]:,.0f} {self.currency_name}")
                st.header(f"Amount at Retirement ({self.retirement_year}): {self.amounts_fi[int(self.retirement_year)-int(self.start_year)]:,.0f} {self.currency_name}")
        
    def visualize_fire_simulation(self):
        # Visualization of the FIRE simulation
        # FIREシミュレーションの可視化
        
        # Setting a placeholder as a main figure
        # メイン図としてplaceholderを設置
        placeholder = st.empty()
        
        # Place a container
        # コンテナを設置
        with placeholder.container():

            # Setting of two columns for investment return (c1) and financial independence (c2)
            # 投資リターン用(c1)と経済的自立用(c2)に2つのパネルを用意
            c1,c2=st.columns(2)
            
            # Visualization of investment return
            # 投資リターンの可視化
            with c1:
                # Print title, タイトル表示
                st.title("Investment Return")
                
                # Preparation of figure object, Figureオブジェクトの用意
                fig=go.Figure()
                
                # Extraction of data for visualization of investment return
                # 投資リターン範囲のデータを抽出
                df=self.df.iloc[:int(self.period)+1]
                
                # Plot principals, 元本のプロット
                fig.add_trace(go.Scatter(x=df["years"],y=df["principals"],
                                         name="Principals",mode="lines+markers",
                                         line={"width":5,"color":"black"},marker={"size":20}))
                
                # Plot amounts, 総資産のプロット
                fig.add_trace(go.Bar(x=df["years"],y=df["amounts"],
                                     name="Amounts",marker_color="lightgreen"))
                
                # Horizontal line plot for final pricipals, 最終元本を横線でLineプロット
                y=self.df["principals"].iloc[int(self.period)]
                fig.add_trace(go.Scatter(x=[int(self.start_year),int(self.end_year)],
                                         y=[y,y],name="Principals",
                                         mode="lines",line={"width":2,"color":"grey"},
                                         showlegend=False))
                fig.add_annotation(x=int(self.end_year),y=y,
                                   text=f"Principlas {y:,.0f} {self.currency_name}",
                                   showarrow=True,arrowhead=3,
                                   font={"size":30,"color":"black"})
                
                # Horizontal line plot for final amount, 最終総資産を横線でLineプロット
                y=self.df["amounts"].iloc[int(self.period)]
                fig.add_trace(go.Scatter(x=[int(self.start_year),int(self.end_year)],
                                         y=[y,y],name="Final amounts",
                                         mode="lines",
                                         line={"width":2,"color":"darkgreen"},
                                         showlegend=False))
                fig.add_annotation(x=int(self.end_year),y=y,
                                   text=f"Final amount {y:,.0f} {self.currency_name}",
                                   showarrow=True,arrowhead=3,
                                   font={"size":30,"color":"black"})
                
                # Graph formatting by layout object, Layoutオブジェクトによるグラフの整形
                layout = go.Layout(
                    width=self.graph_w, height=self.graph_h,
                    plot_bgcolor="white",
                    xaxis = dict(
                        title = 'Year',
                        showgrid = False,
                        dtick=5,
                        tickfont={"size":30},
                        tickangle=45
                        ),
                    yaxis = dict(
                        title=f"{self.currency_name}",
                        constrain="domain",
                        showgrid = False,
                        tick0=0,
                        dtick=20000000,
                        tickformat=",.0f",
                        tickfont={"size":30}
                        ),   
                )

                fig.update_layout(layout)
                st.write(fig)
                
            # Visualization of financial independence
            # 経済的自立シミュレーションの可視化
            with c2:
                # Print title, タイトル表示
                st.title("Fincial Independence")

                # Preparation of figure object, Figureオブジェクトの用意
                fig=go.Figure()
                
                # Plot living cost, 生活費のプロット
                fig.add_trace(go.Scatter(x=self.df["years"],y=self.df["living_costs"],
                                         name="Living costs",mode="lines+markers",
                                         line={"width":5,"color":"black"},marker={"size":10}))
                
                # Plot financial independence (amount with living cost and pension)
                # 経済的自立データの描画(生活費と年金を考慮した総資産)
                fig.add_trace(go.Bar(x=self.df["years"],y=self.df["amounts_fi"],
                                     name="Amounts(F.I.)",marker_color="blue"))
                
                # Vertical line plot of bankruptcy year if it occurred
                # 破産した場合に縦線で年次描画
                fig.add_trace(go.Scatter(x=[self.bankruptcy_year,self.bankruptcy_year],
                                         y=[self.df["amounts_fi"].max()*0.9,self.df["amounts_fi"].min()],
                                         name="bankruptcy Year",
                                         mode="lines",
                                         line={"width":5,"color":"red"}))
                if self.bankruptcy_year:
                    fig.add_annotation(x=self.bankruptcy_year,y=self.df["amounts_fi"].max(),
                                       xref="x",yref="y",
                                       showarrow=False,
                                       align="center",
                                       text=f"{self.bankruptcy_year}",
                                       font={"size":30,"color":"red"},
                                       )     
                                    
                # Graph formatting by layout object, Layoutオブジェクトによるグラフの整形
                layout = go.Layout(
                    width=self.graph_w, height=self.graph_h,
                    plot_bgcolor="white",
                    xaxis = dict(
                        title = 'Year',
                        showgrid=False,
                        dtick=5,
                        tickfont={"size":30},
                        tickangle=45
                        ),
                    yaxis = dict(
                        title=f"{self.currency_name}",
                        constrain="domain",
                        showgrid=False,
                        dtick=20000000,
                        tickformat=",.0f",
                        tickfont={"size":30}
                        ),
                )                
                fig.update_layout(layout)
                st.write(fig)


    def run_simulator(self):
        # Perform simulation, シミュレーションの実行
        self.set_default_ui()
        self.fire_simulation()
        self.print_summary()
        self.visualize_fire_simulation()

fs=fire_simulator()
fs.run_simulator()

5. まとめ

Streamlitは相変わらず爆速でアプリ開発できるので素晴らしいと思います。一方で細かいレイアウトを操作するのは残念ながら難しいようです。細やかに配置など調整したいという方は別途フロントエンドの言語を学ぶと良いかもしれません(Streamlit上でhtmlタグは使えます)。

また、Plotlyの仕様に慣れるまで結構大変でした。一度覚えてしまえばどうってことないとは思いますが、他のグラフ描画のコンセプトに慣れていると、むしろその違いにやや戸惑って2日ほどつまずいてしまいました。。涙

以上となります。いかがでしたか?楽しんでいただけましたでしょうか?

もしよろしければフォローやいいねをお願いしますね♪

この記事がみなさんのPython学習や資産形成の一助になれば幸いです♪

♪♪♪ Have a nice coding day ♪♪♪

[Streamlitをもっと学ぶなら↓]

[あわせてこちらの記事↓もお読みください♪]

[トレード口座開設はコチラ↓]

免責事項

免責事項はコチラです。

タイトルとURLをコピーしました