Denver Data Design homeDenver Data Design home

cd ..

Retirement simulator

A Streamlit app that simulates retirement portfolios to show risk-averse investors the cost of leaving the market in a downturn.

retirement-simulator.streamlit.app

Retirement Simulator

Forecast a portfolio with five scenarios drawn from historical returns, then plan retirement income.

./open-simulator

I partnered with a local wealth management firm to visualize investor behavior. The focus was risk-averse investors who exit the market during downturns. After creating a custom probability algorithm to generate a financial model, I developed a retirement simulator. Users can forecast their financial future and generate a retirement income plan. The application was built on the Streamlit platform. The content below is designed to help other developers and data scientists start their own applications. The source code is public on GitHub for others to use. A Jupyter Notebook was used to develop the functions and models within the retirement simulator.

Creating your first Streamlit app

The easiest way to get started with Streamlit is to use GitHub Codespaces and select the sample app to speed up the process. Once your Streamlit app is launched, edit any included pages and rename the main “Hello.py” file. After renaming the main file, you must update the file name within the devcontainer.json file. Next, delete the app from the Streamlit login page, then relaunch the app using the same repository you created. When you relaunch, Streamlit will ask for your (newly named) main file name, enabling the correct architectural map for your app. Tip: Renaming the main (hello.py) file is the only edit that requires relaunching the app. Page names can be changed, and .py files can be added to store functions and models without needing to re-initiate the Streamlit app.

Dev container JSON configuration, annotated with notes on renaming the app file

Streamlit process map

The VS Code image shows the organization of the retirement simulator app, including the folders housing the data and images used for the project. The remote repository was cloned to a local, desktop environment for development. The .py files were created in the Codespace environment. Be careful with your branches. Before opening and working within the Codespace environment, any local edits need to be pushed to your remote server.

Flowchart of building and deploying a Streamlit app from a GitHub Codespace

Starting from an existing repo

If you want to save the trouble of deleting and relaunching a renamed version of “Hello.py”, fork an existing repository and make any initial edits. Upload the edited repository to GitHub and launch from this existing repo. Choose your main file name wisely, as it will appear at the top of your navigation. For example, my main file was named “Retirement_Simulator.py”; note that the underscore appears as a space.

A note on sharing

The Streamlit cloud requires your repository to be viewable by others. This open design is intended to foster a collaborative effort among developers. If you are creating an app that needs to be private, I recommend an EC2 instance on AWS.

Page elements

The page elements file, which included a footer vertical space and side content bar, was created to easily update components within the data application. Creating a vertical space function is the best way to add padding to your Streamlit app. An empty markdown element also creates space, but the page scrolls down to that empty marker when it loads. Think of the page elements as your collection of PHP based scripts. Any element that may be updated in multiple locations can be declared in this location. This could include contact information, web URLs, or any repeated widget.

Annotated Streamlit Python code for a reusable footer and a spacer function

Controlling the flow of your app with forms

While implementing your created python functions, carefully plan out the logic flow of the user. For example, the retirement app’s main page asks for an initial investment amount, the number of years, and the investor type (moderate by default). I nested this section in a Streamlit form to keep the user from pressing “Forecast your Future” before entering inputs. In this example, the form uses an “if” statement, requiring the user to enter inputs before generating the plot and data table. Without a form, this section of code would flag an error from the initial null values.

Retirement simulator form beside a chart of simulated portfolio values

Controlling the flow with fragments

Streamlit apps run from top to bottom, continuing this loop endlessly unless you tell the app otherwise. For the income planning page, an empty list was created to allow the user to fill in varying retirement scenarios developed in the planner function. During testing, the lists would continuously empty, which resulted from this circular loading. Streamlit created experimental fragments to allow the developer to effectively create a while loop within an application page. With the @st.experimental_fragment() declaration in place, the user can rerun the planner() function. The empty lists at the top of the file no longer clear their entries.

Annotated Python code using a Streamlit fragment to keep planner lists between submissions

Streamlit logic overview

The logic map visualizes the flow of actions within a Streamlit page. The page shown features the Retirement Income Planner page, as this content includes both fragment and form sections to control the page flow. The planner() function is fragmented into its own section, which then contains a form that allows the user to enter as many scenarios as desired. The sidebar includes a “Reset the Data” bar, as the table could get cumbersome and the user may want to start over. Each page includes a function that serves as the main content for the page, which was modeled after the sample app. From a coding perspective, the functions run in this order:

Annotated walkthrough of the Retirement Income Planner page

  1. st.set_page_config: the header
  2. with st.sidebar: the page-specific sidebar plus side_content()
  3. planner(): the main function for this page
  4. st.form: the form within this function
  5. footer()

The planner() function can loop because of the @st.experimental_fragment declaration.

retirement-simulator.streamlit.app

Retirement Simulator

Forecast a portfolio with five scenarios drawn from historical returns, then plan retirement income.

./open-simulator