🚧 This site is under construction. Content and features are being added. Stay tuned! 🚧

Building a Smarter Retirement Planner - Monte Carlo Simulation

From fixed returns to real-life uncertainty — how I built a flexible, risk-aware tool for better financial planning

Posted by Kevin Cho on July 28, 2025

Python

🧮 Retirement Planner: From Inputs to Monte Carlo Insights

 

1. Why I Built This

Planning for retirement is one of the most critical financial decisions we make in life. It involves estimating how much to save, how investments will grow, when to retire, and how long your assets need to last. Yet, despite its importance, many retirement planning tools are either overly simplistic or require cumbersome manual inputs — often disconnected from real-world variables like inflation volatility or market uncertainty.

As a data scientist and financial analyst, I saw an opportunity to build a dynamic, scenario-based retirement planner that empowers users to model their retirement journey with realistic assumptions and flexible inputs. This project combines:

  • Robust financial modeling through Python-powered calculations including Monte Carlo simulations to estimate probabilities of running out of money,

  • Interactive visualizations with Plotly to bring data insights to life,

  • User-centric features like saving/loading multiple retirement scenarios for comparison,

  • Modern web app architecture with Flask backend, PostgreSQL database hosted on AWS EC2, and a responsive frontend built with Bootstrap.

 In my experience, traditional retirement calculators fail to:

  • Capture variability in investment returns and inflation through stochastic modeling,

  • Provide users with the ability to store and compare multiple personalized scenarios,

  • Offer an intuitive, integrated interface that connects complex backend logic seamlessly with frontend interaction,

  • Scale flexibly for additional financial assumptions or extensions in the future.

This project was motivated by the desire to automate and streamline the entire retirement planning process — reducing manual errors, increasing transparency, and enabling quick “what-if” analyses to improve financial decision-making. The end goal was to build a tool that is not only accurate and flexible but also accessible and user-friendly.

What You Will Learn from This Case Study:

In this detailed walkthrough, I will share how I built this full-stack retirement planner from scratch. You will get insights into:

  • Designing scalable database schemas for scenario storage,

  • Integrating Python financial models with Flask REST APIs,

  • Creating responsive and interactive frontend forms and charts,

  • Handling asynchronous save/load of user scenarios with AJAX,

  • Deploying the app securely on AWS EC2 with PostgreSQL,

  • Overcoming challenges with JSON handling in databases and frontend-backend syncing,

  • And much more.

Whether you are a developer interested in full-stack data projects or a financial analyst aiming to leverage Python for automation, this case study will provide you with practical guidance and reusable code patterns.


2. How I Built It

I combined the following technologies:

  • Flask (Python) — backend logic and form handling,

  • Plotly — interactive charts,

  • NumPy — powering Monte Carlo simulation logic,

  • Pandas — creating detailed year-by-year projections,

  • Jinja2 Templates — keeping inputs and dropdowns stateful,

  • Bootstrap 5 — modern, responsive UI,

  • PostgreSQL — storing user data persistently,

  • AWS EC2 — hosting the app live on https://databykevin.com/.


3. High-Level Architecture & Technology Stack

Creating a maintainable, scalable retirement planner required careful design integrating backend calculations, data persistence, and a responsive frontend.

Backend — Flask API & Python Logic

  • Flask provides REST API endpoints for scenario management and retirement calculations.

  • Financial modeling including Monte Carlo simulation implemented in Python modules for modularity.

  • Flask Blueprints organize routes cleanly for page views and API endpoints.

Database — PostgreSQL on AWS EC2

  • PostgreSQL stores user accounts and retirement scenarios securely.

  • Hosted on an AWS EC2 instance, ensuring control and scalability.

  • SQLAlchemy ORM enables smooth Python integration with the database.

Frontend — Bootstrap, JavaScript & Plotly

  • Bootstrap 5 ensures responsive design across devices.

  • JavaScript manages dynamic behaviors, tooltips, form serialization, and AJAX scenario saves/loads.

  • Plotly.js delivers rich, interactive financial charts.

Deployment & Environment

  • The app runs on AWS EC2 with Gunicorn and Nginx for robust production hosting.

  • Sensitive credentials are stored in environment variables loaded via Python-dotenv.

Architecture Diagram (Simplified)

User Browser
     |
     | (HTTPS)
     v
Flask App (Gunicorn + Nginx on EC2)
     |
     | (SQLAlchemy ORM)
     v
PostgreSQL DB (AWS EC2 Instance)
 

This architecture balances flexibility, security, and maintainability, enabling seamless user interaction and future expansion.


4. Database Design & Migrations

A solid database schema is essential for securely storing and managing user retirement scenarios.

Key Tables

  • Users: stores user credentials and metadata.

  • RetirementScenario: stores user-specific retirement scenarios with:

    • id (primary key),

    • user_id (foreign key to Users),

    • scenario_name (string),

    • inputs_json (JSON string of user inputs),

    • created_at and updated_at timestamps.

Migration Workflow

  1. Define the RetirementScenario model in Python (e.g., in main.py or models module).

  2. Establish relationships in the User model for back-reference.

  3. Run flask db migrate -m "Add retirement_scenarios table" to create migration scripts.

  4. Review migration files under migrations/versions for correctness.

  5. Apply migrations with flask db upgrade to update the live database.

  6. Verify table creation and content by connecting to PostgreSQL on EC2:

    sudo -u postgres psql \c blogdb \dt retirement_scenarios SELECT * FROM retirement_scenarios; 

Critical Points

  • Ensure environment variables correctly point to your EC2 PostgreSQL instance.

  • Maintain foreign key integrity and timestamp automation.

  • Backup migrations folder before major changes.


5. Frontend UI Integration & User Experience Enhancements

Making the retirement planner intuitive encourages users to save and compare multiple scenarios.

Scenario Save & Load

  • Add a Save Scenario button to prompt naming and save inputs via AJAX to /scenarios/save.

  • Provide a dropdown or sidebar listing saved scenarios retrieved from /scenarios/list.

  • Selecting a scenario loads inputs asynchronously to pre-fill the form for quick comparisons.

UI Enhancements

  • Use Bootstrap modals for user prompts instead of prompt() dialogs.

  • Show saved/unsaved status clearly, provide autosave reminders.

  • Validate scenario names to avoid duplicates or invalid characters.

  • Support renaming, duplication, and deletion for full lifecycle management.

Accessibility & Responsiveness

  • Ensure keyboard navigation and screen reader support via ARIA attributes.

  • Responsive design to support desktop, tablet, and mobile users.

Error Handling & Edge Cases

  • Graceful network error handling and user notifications.

  • Session expiration prompts and login enforcement.

  • Input sanitation both client and server side.


6. Features

  • Inputs for retirement age, lifespan, savings, expenses, and CPP support.

  • Support for asset liquidation events at user-specified ages.

  • Monte Carlo simulation with customizable return and inflation volatility.

  • Detailed year-by-year tables showing asset balances, withdrawals, and returns.

  • Visualizations of asset projections and risk percentiles.

  • Output summaries including probability of depletion by key ages.


7. Key Concepts Explained

Arithmetic vs Geometric Return

  • Arithmetic is a simple average; geometric accounts for compounding and volatility drag.

  • Arithmetic vs Geometric Return

  • Arithmetic mean: simple average return (e.g., 10% for S&P 500)

  • Geometric mean: adjusts for compounding and volatility drag,

  • Geometric mean: adjusts for compounding and volatility

  • Geometric ≈ Arithmetic - (σ² / 2)

  • If return = 10% and std dev = 15%, then: Geometric return ≈ 10% - (0.15² / 2) = 8.875%

  • In my simulation: adjusted_return = expected_return - 0.5 * std_dev ** 2

  • MC simulation uses geometric mean, adjusting expected returns accordingly.

Monte Carlo Simulation

  • Runs thousands of randomized simulations modeling yearly returns and expenses.

  • Provides a distribution of outcomes, showing risks and expected performance.

  • Outputs: 10th, 50th (median), and 90th percentile projections Depletion risk probabilities Year-by-year chart with asset bounds


8. Sample Python Logic

for age in range(current_age, expected_lifespan + 1): 
if age < retirement_age: assets += monthly_savings * 12 else: 
assets -= (monthly_expense - cpp) * 12 assets *= (1 + return_rate - inflation_rate) 

9. Sample Monte Carlo Snippet

returns = np.random.normal(expected_return, std_dev, num_years) 
assets = [] for r in returns: a = (a - withdrawal) * (1 + r) assets.append(a) 

10. Backend API Endpoints and Scenario CRUD Operations

To enable seamless scenario management, the backend provides RESTful API endpoints that allow authenticated users to Create, Read, Update, and Delete (CRUD) their retirement scenarios.

Key Blueprint Setup

  • All routes related to retirement scenarios are grouped under a dedicated Flask blueprint, e.g., scenarios_bp, with prefix /scenarios.

  • Authentication is enforced via @login_required decorators to protect user data.


1. Save Scenario (POST /scenarios/save)

Purpose: Create a new scenario or update an existing one by scenario name.

Workflow:

  • Extract scenario_name and inputs_json (all user inputs serialized as JSON) from request body.

  • Check if a scenario with the same name exists for the current user:

    • If exists, update inputs_json and timestamp.

    • Otherwise, create a new RetirementScenario record.

  • Commit changes to the database.

  • Return success or error message as JSON.

Example Code Snippet:

python

@scenarios_bp.route("/save", methods=["POST"]) @login_required def save_scenario(): data = request.get_json() scenario_name = data.get("scenario_name") inputs_json = data.get("inputs_json") if not scenario_name or not inputs_json: return jsonify({"error": "Missing scenario_name or inputs_json"}), 400


2. List Scenarios (GET /scenarios/list)

Purpose: Retrieve all saved scenarios for the logged-in user.

Workflow:

  • Query all RetirementScenario entries filtered by user_id.

  • Serialize each scenario’s id, scenario_name, created_at, and updated_at for frontend display.

  • Return the list as JSON.


3. Load Scenario (GET /scenarios/load/<scenario_id>)

Purpose: Load details of a selected scenario by its ID.

Workflow:

  • Query the RetirementScenario record by scenario_id and user_id.

  • Return the scenario data as JSON.

  • Return 404 error if not found or unauthorized.


Security and Validation Notes

  • Authorization: Ensure users can only access their own scenarios by always filtering with user_id=current_user.id.

  • Input Validation: Sanitize and validate incoming JSON payloads for required fields.

  • Error Handling: Return meaningful HTTP status codes and error messages.

  • Rate Limiting: Consider throttling to prevent abuse of save/load APIs.

  • JSON Storage: Storing complex input parameters as JSON offers flexibility and avoids schema changes for input adjustments.


Integration with Frontend

  • Frontend JavaScript calls these endpoints asynchronously via fetch or axios.

  • Save actions send the entire input form as JSON to /scenarios/save.

  • Listing and loading scenarios enable users to pick from saved configurations to pre-fill forms.

 

11. Frontend UI Integration & User Experience Enhancements

A smooth and intuitive user experience is essential for encouraging users to save, load, and manage multiple retirement scenarios effectively. This section outlines key frontend implementations to integrate with the backend API endpoints and enhance usability.


1. Scenario Save Functionality

  • Add a Save Scenario button on the retirement planner page.

  • When clicked, prompt the user to enter a Scenario Name.

  • Serialize all current form inputs into a JSON object.

  • Send the JSON and scenario name via a POST request to the backend /scenarios/save endpoint.

  • Show success or error messages based on the response.

Example JavaScript snippet:

js

function saveScenario() { const scenarioName = prompt("Enter a name for this scenario:"); if (!scenarioName) return alert("Scenario name is required."); // Collect all input fields into an object const inputs = {}; document.querySelectorAll('form input, form select').forEach(el => { inputs[el.name] = el.value; }); fetch('/scenarios/save', { method: 'POST', headers: 


2. Scenario List and Load

  • Display a dropdown menu or sidebar list of saved scenarios retrieved via /scenarios/list.

  • Allow users to select a scenario and click Load, triggering a GET request to /scenarios/load/<id>.

  • On successful response, parse the JSON of saved inputs and populate the form fields automatically.

  • Provide feedback or error messages if loading fails.


3. UI Enhancements

  • Clear indication of saved vs unsaved changes (e.g., show an asterisk * or status message).

  • Autosave prompts or reminders to save before navigating away.

  • Validation on scenario names (no duplicates, no special chars).

  • Use Bootstrap modals for input prompts to avoid disruptive prompt() dialogs.

  • Support scenario deletion and renaming for full lifecycle management.


4. Accessibility & Responsiveness

  • Ensure all buttons, forms, and dynamic lists are accessible via keyboard and screen readers.

  • Use semantic HTML and ARIA attributes.

  • Make the UI responsive for mobile and tablet users.


5. Error Handling & Edge Cases

  • Handle network errors gracefully with retry options.

  • Notify users if their session expires or if they are not logged in.

  • Sanitize user inputs and scenario names both client and server side.


6. Optional: Import/Export

  • Allow users to export scenario JSON to their device for backup or sharing.

  • Provide an import function to load external scenario files.

 

12. Security Hardening and Best Practices

Ensuring your application is secure protects both user data and your reputation. Here are the critical security aspects to consider for your retirement planner, especially as it handles sensitive financial inputs and personalized scenarios.


1. Authentication & Authorization

  • Use Flask-Login to manage user sessions securely.

  • Protect all scenario save/load endpoints with @login_required decorators.

  • Implement password hashing with a strong algorithm like pbkdf2_sha256 (which you’re already doing).


2. Data Validation and Sanitization

  • Validate all inputs on the server side to prevent malicious payloads.

  • Use schemas or validation libraries (e.g., marshmallow) to strictly enforce data types and formats.

  • Sanitize inputs to prevent SQL Injection and Cross-Site Scripting (XSS).


3. Secure API Communication

  • Use HTTPS for all communications to encrypt data in transit.

  • Protect API endpoints from Cross-Site Request Forgery (CSRF) attacks. 

  • Implement rate limiting to prevent brute-force attacks on login and API endpoints.


4. Database Security

  • Use parameterized queries or ORM (SQLAlchemy) to avoid injection vulnerabilities.


5. Error Handling

  • Do not expose sensitive stack traces or error details to end users.


6. Logging and Monitoring

  • Log login attempts, scenario saves, and loads with timestamps and user IDs.


7. Session Security

  • Use secure, HttpOnly cookies for session storage.

  • Consider implementing Multi-Factor Authentication (MFA) for added account security.


8. Deployment Best Practices

  • Keep your EC2 instance and all dependencies updated with security patches.

  • Use firewall rules (e.g., AWS Security Groups) to restrict access to your database.


9. User Privacy

  • Explicitly inform users about data collection and storage in a Privacy Policy.

  • Allow users to delete their saved scenarios or accounts.

  • Use data encryption at rest if storing particularly sensitive data.


 

13. What I Learned

  • Volatility and sequence risk are critical in retirement projections.

  • Users need control and clear insights, not just deterministic numbers.

  • Monte Carlo simulation offers a richer understanding of risks and outcomes.


14. Roadmap & Closing Thoughts

  • Next steps include adding glidepath rebalancing, dynamic inflation models, and enhanced withdrawal strategies.

  • User Scenario Management: Enable users to save, load, rename, duplicate, delete, and optionally export/import retirement scenarios for easy comparison and sharing.

  • Enhanced Visualization: Improve charts with interactivity, multiple scenario overlays, and summary dashboards showcasing key retirement metrics like depletion risk and asset growth.

  • Advanced Monte Carlo Features: Support custom statistical distributions, variable withdrawal strategies, and longer simulation runs to better capture investment and longevity risks.

  • User Input & Experience Enhancements: Add real-time input validation, preset user profiles, and persist user preferences for a smoother, personalized experience.

  • Backend Analytics & Notifications: Collect anonymized aggregate data for insights, track feature usage, optimize performance, and provide email alerts and goal tracking for better user engagement.

  • This project is a blend of finance, technology, and user experience — mirroring my professional automation work.

  • Retirement is personal; projections should be tailored, transparent, and actionable.

Try the live tool at https://databykevin.com/retirement

 

Login or Register to comment.


Comments

No comments yet.