๐งฎ Financial/Retirement Planner: From Inputs to Monte Carlo Insights
A full-stack, tax-aware financial planning engine built with Python, Flask, JavaScript, and modern web technologies — combining deterministic, stochastic, and optimizer-based simulations.
๐ Contents
1๏ธโฃ Why I Built This
Financial/Retirement Planning is one of the most consequential financial decisions we make — yet most online calculators are oversimplified, assume linear growth, and ignore taxes, inflation volatility, and withdrawal sequence risk.
As a data scientist + finance director, I built this planner to model how real portfolios behave, not just how they grow in theory.
Goals:
-
Capture market uncertainty using Monte Carlo simulations.
-
Integrate Canadian tax rules (RRSP, TFSA, OAS clawback, RRIF minimums).
-
Let users experiment live with assumptions and instantly visualize outcomes.
-
Build everything with production-grade web tech I use professionally:
Python · Flask · PostgreSQL · Bootstrap · Chart.js · JavaScript · AWS EC2.
๐ Try it live: databykevin.com/retirement
2๏ธโฃ How I Built It
-
Flask (Python) → backend routing, REST APIs, deterministic + stochastic logic.
-
NumPy / Pandas → financial math and Monte Carlo engine.
-
Plotly + Chart.js → responsive, interactive visualizations.
-
Bootstrap 5 → clean and mobile-friendly UI.
-
PostgreSQL @ AWS EC2 → persistent user scenarios.
-
Gunicorn + Nginx → stable production hosting.
-
Modern JS (ES6) → dynamic input syncing, tooltips, auto-recalc, optimizer logic.
3๏ธโฃ Architecture & Technology Stack
๐งฉ Backend — Flask API & Python Logic
Handles input parsing, financial modeling, and scenario CRUD operations via Flask Blueprints.
๐๏ธ Database — PostgreSQL on EC2
Stores user accounts + serialized JSON inputs.
Uses SQLAlchemy ORM for migrations and integrity.
๐ป Frontend — Bootstrap + JavaScript + Plotly
Responsive UI, live re-runs on input changes, and dynamic charts (balances, taxes, optimizer).
โ๏ธ Deployment
Browser → HTTPS → Flask (Gunicorn + Nginx) → PostgreSQL on EC2
4๏ธโฃ Database Design & Migrations
| Table | Purpose |
|---|---|
| users | Credentials + metadata |
| retirement_scenarios | Scenario JSON inputs, timestamps, FK→users |
Migration flow
flask db migrate -m "Add retirement_scenarios table" flask db upgrade
Verify in psql:
\c blogdb \dt retirement_scenarios SELECT * FROM retirement_scenarios;
5๏ธโฃ Frontend Integration & UX Enhancements
-
AJAX Save / Load for user scenarios
-
Bootstrap modals → friendly naming dialogs
-
Autosave reminders + status badge (“Saved โ / Unsaved *”)
-
Keyboard + screen-reader accessibility
-
Fully responsive on desktop → mobile
6๏ธโฃ How to Input and Use the Planner
This section walks first-time users through every major control in the planner.
๐ Step 1 – Starting Balances
Enter your current portfolio split:
-
Taxable ($) — non-registered accounts (taxed annually).
-
RRSP ($) — tax-deferred; withdrawals taxable.
-
TFSA ($) — tax-free growth & withdrawals.
๐ก The “Total allocated” line checks that the three add up to your total investable assets.
๐ฐ Step 2 – Pre-Retirement Savings & Growth
-
Monthly Savings ($) — amount you currently invest monthly.
-
Annual Saving Increase Rate (%) — yearly escalation before retirement.
-
Pre-Retirement Return Rate (%) — expected annual return before retirement.
Planner auto-routes yearly savings TFSA → RRSP → Taxable, applying TFSA caps that grow ≈ 2.1 %/yr from $7,000 (2025 base).
๐ Step 3 – Retirement Timing & Expenses
-
Start Age / End Age — simulation range (life expectancy).
-
Monthly Spend ($) — after-tax living cost in today’s dollars.
-
Post-Retirement Return Rate (%) — expected investment return after retiring.
-
Inflation Rate (%) — cost-of-living growth applied to expenses and OAS.
You can instantly test longevity risk by raising End Age and rerunning.
๐จ๐ฆ Step 4 – OAS / RRIF Details
-
OAS Monthly ($), Start Age, Clawback Threshold, Rate (%), Index OAS โ
→ models income-tested OAS clawback (15 % above threshold ≈ $90 k). -
RRIF Start Age → auto-applies CRA minimum withdrawals (≈ 5.28 % @ 71).
Inflation indexing is optional via checkbox.
๐ Step 5 – Tax Brackets & Taxable Drag
-
Bracket 1/2 Limits + Rates → switch on progressive tax mode.
-
Taxable Drag (%) → yearly friction on Taxable growth (dividends, interest, gains).
Use the Taxable Drag Helper Table to estimate based on:
• allocation % • yield % • effective tax rate %
Auto-apply fills the field for you.
๐งพ Step 6 – Withdrawal Order & Optimizer
Select order:
Taxable → TFSA → RRSP TFSA → Taxable → RRSP RRSP → Taxable → TFSA ... (6 combinations)
Then click Run Plan.
The system:
-
Runs deterministic and tax-aware simulation.
-
Displays tables + charts.
-
If “Run Optimizer” is ON, computes the most tax-efficient hybrid order.
๐ Step 7 – Review Outputs
-
Table A – Six-Withdrawal Ledger: year-by-year inflows/outflows per account, OAS, tax overlay, ending balances.
-
Charts:
• Withdrawal orders (bar) • Taxes (stack) • Balances (line) • Monte Carlo bands (P10–P90). -
Download CSV (Part A) → export your projection for Excel review.
๐พ Step 8 – Save Your Scenario
Click Save Scenario, name it (e.g. “Base Case 2025”), and confirm.
Your inputs serialize to JSON and save in PostgreSQL.
Later, choose Load Scenario to instantly recall it for comparison.
7๏ธโฃ Tax-Aware Engine & Withdrawal Optimizer
Highlights:
-
Progressive tax bands or flat rate mode.
-
RRIF minimums auto-enforced ≥ 71.
-
OAS clawback 15 % above threshold.
-
Taxable Drag Helper links directly to portfolio mix.
-
Optimizer (beta) → analyzes all 6 orders + Next-$1 Analyzer to suggest best tax-aware draw sequence.
8๏ธโฃ Monte Carlo Simulation & Risk Visualization
-
300 runs per simulation with geometric-mean correction (no +½σ² bias).
-
Separate distributions for returns and inflation.
-
Produces P10/P50/P90 bands & median line.
-
Displays deterministic vs probabilistic results side-by-side.
-
Clickable legend to toggle scenarios or optimizers.
9๏ธโฃ Sample Logic (Python + JavaScript)
Python (core loop)
for age in range(current_age, end_age + 1): if age < retire_age: assets += annual_saving * (1 + return_rate) else: draw = living_expense - cpp - oas assets = (assets - draw) * (1 + post_ret_rate)
Monte Carlo
returns = np.random.normal(mu, sigma, years) for r in returns: assets = (assets - withdrawal) * (1 + r)
JavaScript excerpt
function allocateByOrder(netNeed, balances, order, rrspFlat){ // dynamically draws from TFSA, RRSP, or Taxable with live tax overlay }
๐ 10๏ธโฃ API Endpoints & Scenario Management
| Endpoint | Method | Purpose |
|---|---|---|
/scenarios/save |
POST | Save or update scenario |
/scenarios/list |
GET | List user scenarios |
/scenarios/load/<id> |
GET | Load scenario |
/scenarios/delete/<id> |
DELETE | Delete scenario |
All protected via @login_required, user-scoped, and validated server-side.
11๏ธโฃ Security Hardening
-
Flask-Login authentication + pbkdf2_sha256 hashing
-
HTTPS enforced via Nginx
-
CSRF tokens on forms
-
ORM queries only (no SQL injection)
-
Rate limiting & session expiry
-
Cloud-firewall (DB port restricted to localhost)
-
Audit logging of save/load events
12๏ธโฃ What I Learned
-
Sequence risk & volatility dominate outcomes.
-
Taxes and draw order matter as much as return rate.
-
Monte Carlo translates fear into probability.
-
Bridging Python accuracy with JavaScript interactivity creates trust.
-
Good UX equals better financial decisions.
13๏ธโฃ Roadmap & Closing Thoughts
Upcoming
-
Glidepath rebalancing and dynamic inflation.
-
Multi-goal planning (education, home, retirement).
-
AI-based withdrawal optimizer v2.
-
PDF/CSV exports and summary dashboards.
-
User profiles with persistent preferences.
๐ฌ Final Reflection
This planner unites finance, data science, and user experience —
mirroring my automation work in real-estate and portfolio analytics.
Financial Planning/Retirement is personal.
It should be data-driven, transparent, and actionable — not guesswork.
๐ Try it yourself: databykevin.com/retirement